370 Commits

Author SHA1 Message Date
qbe
04d4447273 Escape TTY in systemd service (closes #889) (#890)
## What are the changes about?

* fix templated systemd dependencies: %I -> %i
* amend systemd-specific documentation in readme with section specific to systemd-logind / autovt

## What existing issue does this resolve?

[issue #889](https://codeberg.org/fairyglade/ly/issues/889)

## Pre-requisites

- [x] I have tested & confirmed the changes work locally

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/890
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: qbe <public.github.c@hannen.at>
Co-committed-by: qbe <public.github.c@hannen.at>
2025-12-18 12:17:00 +01:00
AnErrupTion
ced8f9bee3 Fix session not being saved correctly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-17 17:32:16 +01:00
AnErrupTion
c6446db3e2 Start Ly v1.4.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-06 15:58:39 +01:00
AnErrupTion
e0692885c5 Make delay floating point in dur format
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-06 14:34:47 +01:00
AnErrupTion
b6d3aa0e1c Merge changes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-06 14:25:57 +01:00
AnErrupTion
e6966a628c Fix wrong session index + save file corruption
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-06 14:25:01 +01:00
RacerBG
cab3f1bfb5 Added the Latest Changes to the Bulgarian Translation (#879)
Signed-off-by: RacerBG <racerbg@noreply.codeberg.org>

## What are the changes about?

N/A

## What existing issue does this resolve?

N/A

## Pre-requisites

- [ x ] I have tested & confirmed the changes work locally

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/879
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: RacerBG <racerbg@noreply.codeberg.org>
Co-committed-by: RacerBG <racerbg@noreply.codeberg.org>
2025-12-06 13:39:52 +01:00
AnErrupTion
a9ff0a6d07 Only support dur format v7, set -1 color to black
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-06 10:05:41 +01:00
AnErrupTion
92beb24c80 Fix save file initialisation & incorrect saved session index
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-05 19:49:27 +01:00
hynak
7e18d906c4 [Feature] Add support for .dur file format and animations (closes #719) (#833)
Adds support for durdraw's .dur file format. Supports ascii, animations, and 16/256 color display.

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/833
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: hynak <hynak@noreply.codeberg.org>
Co-committed-by: hynak <hynak@noreply.codeberg.org>
2025-12-05 19:46:42 +01:00
AnErrupTion
3bfdc75a70 Add option to run command after inactivity delay (closes #747)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-03 20:39:27 +01:00
AnErrupTion
6cce221cbf Improve issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-02 22:37:03 +01:00
AnErrupTion
1c99574f73 Add option to run command before UI is initialised (closes #798)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-02 22:23:10 +01:00
AnErrupTion
b249dba092 Support multiple TTYs with systemd service (closes #102)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-02 22:11:39 +01:00
AnErrupTion
3365b33d6d Fix zig-clap dependency URL
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-01 22:09:41 +01:00
radsammyt
6cb53b6e38 Refactor active_input field-jumping logic (#873)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/873
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: radsammyt <radsammyt@noreply.codeberg.org>
Co-committed-by: radsammyt <radsammyt@noreply.codeberg.org>
2025-12-01 20:07:59 +01:00
AnErrupTion
d82fa82a87 Always add hostname last in the info line
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 23:50:39 +01:00
AnErrupTion
c2b3d794e8 Fix fallback UID range + add error if UID range not found
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 23:36:47 +01:00
AnErrupTion
e29bda3250 Add systemd-homed UID range
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 23:02:23 +01:00
AnErrupTion
4df2382698 Add possibility to disable auth_fails animation (closes #835)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 22:19:38 +01:00
AnErrupTion
3dfb86fe69 Merge changes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 21:59:09 +01:00
AnErrupTion
f9a001b160 Update zigini dependency & use same Git URL format everywhere
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 21:57:42 +01:00
RacerBG
5235ca47c5 Add Bulgarian Translation (#872)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/872
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: RacerBG <racerbg@noreply.codeberg.org>
Co-committed-by: RacerBG <racerbg@noreply.codeberg.org>
2025-11-30 21:52:46 +01:00
AnErrupTion
a94abf2e69 Improve bug report template & add PR template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 12:08:10 +01:00
AnErrupTion
fe354a4809 Add fallback UID range options at compile-time
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 09:47:50 +01:00
AnErrupTion
8c964d9ce5 Don't crash when failing to crawl a session directory (closes #870)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 09:30:33 +01:00
AnErrupTion
392ea6ea63 Update French locale
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 09:18:29 +01:00
AnErrupTion
10854e643a Don't crash when failing to parse arguments
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-30 09:14:10 +01:00
ebits
1980b2e479 Feature: Added option for hibernate between sleep and brightness down (#867) (closes #866)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/867
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: ebits <tspamiitesh@gmail.com>
Co-committed-by: ebits <tspamiitesh@gmail.com>
2025-11-28 19:05:17 +01:00
AnErrupTion
816be7449f Update Repology URL
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-19 22:19:12 +01:00
AnErrupTion
f0758d812e Fix potential out of bounds issue when automatically changing session
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-11-19 22:15:07 +01:00
AnErrupTion
3c0c84d067 Fix build error
I really should test PRs before merging them, shouldn't I?
2025-11-15 20:02:59 +01:00
notiant
cc07c4870a Add option to hide CapsLock and NumLock states (#864)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/864
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: notiant <codeberg.cizj4@simplelogin.com>
Co-committed-by: notiant <codeberg.cizj4@simplelogin.com>
2025-11-15 16:32:42 +01:00
RomanPro100
68ec85f412 Add missing Russian translations (#863)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/863
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: RomanPro100 <romanpro100@disroot.org>
Co-committed-by: RomanPro100 <romanpro100@disroot.org>
2025-11-09 17:35:26 +01:00
Corey Newton
1537addd67 Change default session log directory to ~/.local/state (#859)
This addresses #808 and #823 conclusively for all parties.
- It requires no additional directory creation.
- It does not impact users who do not have a problem.
- It adds additional documentation detail in both the bug template and
the readme to help users who *are* having a problem.
- Fulfills the spirit of the [XDG Spec(https://specifications.freedesktop.org/basedir-spec/latest)
but not in practice as `$XDG_CONFIG_HOME` and `$HOME` and the associated
logic are not implemented. `~/.local/state` is the fallback location.

In particular, the spec indicates:
> It may contain: actions history (**logs**, history, …)

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/859
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Corey Newton <corey.newton@zenoprax.com>
Co-committed-by: Corey Newton <corey.newton@zenoprax.com>
2025-11-05 21:57:10 +01:00
Piotr Ginał
2da3648179 Add missing Polish translations (#861)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/861
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Piotr Ginał <ginal.piotr11@gmail.com>
Co-committed-by: Piotr Ginał <ginal.piotr11@gmail.com>
2025-11-03 19:45:59 +01:00
ferreo
0cf752f3b8 [Feature] Add autologin support (#841) (closes #200)
This is the simplest change I could come up with to add working autologin, only bit I really dislike is the event but it seemed like the cleanest way without refactoring.

Co-authored-by: ferreo <harderthanfire@gmail.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/841
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: ferreo <ferreo@noreply.codeberg.org>
Co-committed-by: ferreo <ferreo@noreply.codeberg.org>
2025-10-25 23:09:24 +02:00
AnErrupTion
15c78c838a Update screenshot (for real this time)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-25 20:51:51 +02:00
AnErrupTion
4a486bd876 Update screenshot
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-25 20:49:56 +02:00
AnErrupTion
38173d8557 Add edge margin to numlock & capslock
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-25 20:39:34 +02:00
AnErrupTion
95d1d9378c Add edge margin to top-right clock
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-23 22:53:58 +02:00
AnErrupTion
6f62106fcc Merge changes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-23 22:16:24 +02:00
AnErrupTion
4171e29995 Fix build error when runit service symlink already exists
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-23 22:15:43 +02:00
AnErrupTion
80c27224e9 Create xauth directory if it doesn't exist
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-23 22:15:16 +02:00
ebits
106f157a2c [Feature] Add edge margin option (#856) (closes #848)
Allows setting a balanced margin on all sides.

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/856
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: ebits <tspamiitesh@gmail.com>
Co-committed-by: ebits <tspamiitesh@gmail.com>
2025-10-23 19:07:54 +02:00
AnErrupTion
ec16ad5dfc Remove mention of config.tty option in README (fixes #854)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-21 17:36:42 +02:00
AnErrupTion
5f22173b91 Mention only Ly v1.2.0 and above are supported in issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 19:10:44 +02:00
AnErrupTion
4bc405f239 Start Ly v1.3.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 18:12:16 +02:00
AnErrupTion
c4b68364ef Add FreeBSD-specific PAM file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 17:34:44 +02:00
AnErrupTion
1c05664c85 Fix xauth file name UB
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 16:18:05 +02:00
AnErrupTion
1fbcb10110 Open new log file after every fork() where necessary
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 15:29:47 +02:00
AnErrupTion
bfb3f925d9 Use shutdown -p now on FreeBSD, update instructions
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 13:47:52 +02:00
AnErrupTion
44c8acff16 Open new log file handle after fork()
This would also need to be done in the
nested fork() calls.

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 12:03:48 +02:00
AnErrupTion
541eae5311 Update init service for FreeBSD
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 11:23:57 +02:00
AnErrupTion
3faf3dec42 Fix login issue
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 08:41:36 +02:00
AnErrupTion
52d29bbd47 Fix FreeBSD installation in README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 00:46:56 +02:00
AnErrupTion
a3a8f11575 Add basic FreeBSD service, change default fallback TTY to 2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 00:46:09 +02:00
AnErrupTion
02f5aa702d Implement /etc/login.defs in interop, TODO for FreeBSD
We should be able to parse the "minuid" and "maxuid"
values in /etc/rc.conf to get the UID range of the
system, with default values of 1000 to 32000 (as
they don't seem to be present by default).

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 00:28:12 +02:00
AnErrupTion
ed88458efd Fix platform-specific bugs for FreeBSD compilation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-18 00:05:53 +02:00
AnErrupTion
8df9603188 Install SysVinit service as an executable
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-17 23:12:21 +02:00
AnErrupTion
412994775b Add SysVinit service (closes #224)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-17 23:10:52 +02:00
AnErrupTion
bb669c239c Remove config TTY options in OpenRC & runit services
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-17 23:08:15 +02:00
AnErrupTion
657daafec8 Fix crash after reading saved credentials for first time
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-17 22:58:50 +02:00
AnErrupTion
e36872baa6 Don't spam battery status error if already tried once
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-15 16:32:57 +02:00
AnErrupTion
4738ca81a7 Merge changes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-15 14:10:00 +02:00
Matthew Rothlisberger
1f2453f0fb Disable battery status display by default (#852)
A couple things to fix in the new battery status display configuration.

I think this should be disabled by default. My reasoning:
- Historically a conservative approach is taken with new capabilities in Ly; even the clock is disabled by default
- The existing default creates a regression (error message) for anyone without `/sys/class/power_supply/BAT0` on their system (all non-portable PCs, and laptops that use a different identifier)
- The battery status check causes animations to momentarily hang at a regular interval

Other changes:
- Comment for `battery_id` aligned with similar config switch comments (description / useful information / effect of null setting)
- `battery_id` moved to its correct alphabetical position in the config file
- Setting aligned between `Config.zig` and `config.ini` (the prototypical config file should reflect the actual default)
- Configurations prefixed with `hide_` alphabetized

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/852
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Matthew Rothlisberger <mattjrothlis@gmail.com>
Co-committed-by: Matthew Rothlisberger <mattjrothlis@gmail.com>
2025-10-15 13:58:04 +02:00
AnErrupTion
76da16904f Update French translation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 21:17:58 +02:00
AnErrupTion
e577c454da Fix merge conflict
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 21:10:17 +02:00
AnErrupTion
09c2cfb74d Remove file that was added by mistake
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 21:07:42 +02:00
AnErrupTion
b3f1e91cf6 Remember last session for each user (closes #619)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 21:05:54 +02:00
AnErrupTion
aef1dd9c1a Add more logs when logging into an X11 session
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 20:09:39 +02:00
AnErrupTion
3d977d2ff7 Fix compatibility with Zig 0.15.2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 20:05:50 +02:00
AnErrupTion
a34a5a97bd Execute shell in case exec_cmd is null
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 20:02:55 +02:00
AnErrupTion
1839e4cb44 Use LLVM in Debug (closes #832)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-14 19:42:16 +02:00
Galtrhan
b2f51e5bc8 Adjust Latvian translation & add missing file entries to build.zig (#850)
Change Latvian literal translation that did not quite fit the role of
username to more appropriate.

Added missing locale files to build.zig

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/850
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Galtrhan <galtrhan@gmail.com>
Co-committed-by: Galtrhan <galtrhan@gmail.com>
2025-10-13 21:43:46 +02:00
AnErrupTion
bd335c8c91 Add missing locales in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-13 21:39:44 +02:00
AnErrupTion
4f4855b5e9 Implement dummy active TTY getter for FreeBSD
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-10-12 22:47:03 +02:00
Galtrhan
1e2faad0f8 Add Latvian language translation (#847)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/847
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Galtrhan <galtrhan@gmail.com>
Co-committed-by: Galtrhan <galtrhan@gmail.com>
2025-10-12 20:57:28 +02:00
nyraa
cb4f1952cd Move version string to bottom-left corner (#846)
In #834, it was decided to move the version string to the bottom-left corner, and a new PR was opened to address it.

Co-authored-by: nyraa <112930946+nyraa@users.noreply.github.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/846
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: nyraa <nyraa@noreply.codeberg.org>
Co-committed-by: nyraa <nyraa@noreply.codeberg.org>
2025-10-11 09:24:15 +02:00
ebits
81a17f2904 Fix: Default battery status to row 1 when hide_key_hints and hide_version_string is true (fixes #844) (#845)
Default battery status behaviour to usize 0 when hide_key_hints and hide_version_string is true as a fix to issue [#844](https://codeberg.org/fairyglade/ly/issues/844)

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/845
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: ebits <tspamiitesh@gmail.com>
Co-committed-by: ebits <tspamiitesh@gmail.com>
2025-10-11 08:47:13 +02:00
ebits
339e39d496 Adding the battery status for the top bar alongside brightness controls (closes #821) (#826)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/826
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: ebits <tspamiitesh@gmail.com>
Co-committed-by: ebits <tspamiitesh@gmail.com>
2025-10-09 18:48:46 +02:00
mctaylors
44faa263b1 change(config): add -n flag to prevent accidental backlight disabling (#840)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/840
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: mctaylors <cantsendmails@mctaylors.ru>
Co-committed-by: mctaylors <cantsendmails@mctaylors.ru>
2025-10-05 19:26:09 +02:00
AnErrupTion
7a0520687d Add fallback TTY option (closes #838)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-29 21:30:51 +02:00
AnErrupTion
cee0e0ca4b Log more detailed config error messages (closes #801)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-26 09:54:38 +02:00
AnErrupTion
3edd1ff1be Log error name when zigini fails to parse config
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-25 13:35:28 +02:00
AnErrupTion
145ad5142c Clean up redundant authentication & session code
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-25 08:32:30 +02:00
AnErrupTion
d8b0ae34f3 Remove semi-colons in default locale
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-25 08:30:13 +02:00
AnErrupTion
a6535b9152 Update custom sessions' README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-25 08:29:36 +02:00
AnErrupTion
ee97f3b5e1 Automatically detect TTY (closes #795)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-09-07 17:44:50 +02:00
AnErrupTion
5924db58e1 Use std.mem.span + remove useless dupeZ()
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-30 13:17:19 +02:00
AnErrupTion
36e220e2ff Remove usage of std.c.stat() for xauth code
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-30 02:09:51 +02:00
AnErrupTion
0a9ceca822 Don't dupeZ() in main
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-30 01:09:12 +02:00
AnErrupTion
f9553655a3 Separate platform code from C interop code
Also, don't use isBSD() because we'll only support FreeBSD for
now. Other BSDs may not necessarily support Unicode characters
or the same ioctl constants as we do (or even ioctl at all).

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-30 00:27:04 +02:00
AnErrupTion
230874abd1 Don't forget to flush... :)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 22:54:05 +02:00
AnErrupTion
38c3ecd089 Remove unused import
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 22:42:34 +02:00
AnErrupTion
fec0815161 Always copy an example config file (partially addresses #801)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 22:18:50 +02:00
AnErrupTion
a7d6b06d21 Add partial logging in authentication process (closes #822)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 09:28:29 +02:00
AnErrupTion
1ee8010c24 Fix dinit & s6 service + don't hardcode paths in runit service
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 02:18:11 +02:00
AnErrupTion
f988bd334b Update termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 00:45:28 +02:00
AnErrupTion
aa0222948a Update config migrator
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 00:35:11 +02:00
AnErrupTion
69d39dc035 Remove config.load
config.save already makes it redundant. Besides, who would want
to save the current username & session, but not want to load it
at the next boot?

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 00:32:49 +02:00
AnErrupTion
6d7dbb9f27 Fix typo & remove unused import
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-29 00:07:32 +02:00
AnErrupTion
7cfb947187 Reduce libc usage & move more stuff to interop
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-28 01:39:19 +02:00
AnErrupTion
336847d418 Merge changes from master
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-27 23:45:52 +02:00
AnErrupTion
ff9b6279d3 Update to Zig 0.15.0 (closes #829)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-27 23:44:39 +02:00
KaiJan57
c7dea3c178 [dinit] fix deprecated loginready, replaced by login.target (#828)
Fixes [corresponding issue](https://codeberg.org/fairyglade/ly/issues/827#issue-2234461)

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/828
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: KaiJan57 <kaijan57@noreply.codeberg.org>
Co-committed-by: KaiJan57 <kaijan57@noreply.codeberg.org>
2025-08-26 18:59:43 +02:00
AnErrupTion
d7a4535007 Update screenshot
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-11 12:32:10 +02:00
djsigmann
73ecac67bf Prevent Ly from zombifying when X.org is terminated (#807) (fixes #787)
If a compositor is running when exiting Xorg, the user is met with an unresponsive black screen without the ability to switch to a different TTY (the usual `Ctrl+Alt+F{1..6}`  chord doesn't do anything).
In addition, ly is displayed as a zombie process under `ps` and cannot be killed (observed by utilizing a preexisting ssh connection to the host).

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/807
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: djsigmann <djsigmann@protonmail.com>
Co-committed-by: djsigmann <djsigmann@protonmail.com>
2025-08-05 21:45:09 +02:00
AnErrupTion
7353e75d78 Merge branch 'master' of codeberg.org:fairyglade/ly 2025-08-05 08:26:38 +02:00
AnErrupTion
cd0accfb28 Show error name instead of error if shutdown/reboot fails
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-05 08:26:13 +02:00
João Lucas
b71789912d Add enable_session_log option to control session logging (#809) (fixes #808)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/809
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: João Lucas <jlucaso@hotmail.com>
Co-committed-by: João Lucas <jlucaso@hotmail.com>
2025-08-04 00:00:18 +02:00
AnErrupTion
5bacc8870b Update repository link in README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-03 23:42:35 +02:00
Matthew Rothlisberger
a7ff18aa16 Add option for eight-color terminal output (#802)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/802
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Matthew Rothlisberger <mattjrothlis@gmail.com>
Co-committed-by: Matthew Rothlisberger <mattjrothlis@gmail.com>
2025-08-03 23:37:53 +02:00
AnErrupTion
c3d180c213 Add basic general log file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-03 15:59:47 +02:00
AnErrupTion
3f891d7f0d Workaround for session process not exiting immediately
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-03 14:56:16 +02:00
AnErrupTion
e404d5bdb3 Clean-up: std.posix.kill() returns nothing
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-03 14:55:50 +02:00
AnErrupTion
dee055748c Format code properly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-03 13:47:53 +02:00
AnErrupTion
bd2d1142b2 Don't enable bigclock by default
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-03 13:05:28 +02:00
RadsammyT
b382d74969 bigclock: add 12-hour & seconds support (#805)
Added P,A,M characters to bigclock and added 12hr and seconds support to bigclock via `bigclock_12hr` and `bigclock_seconds` in the config.
![image](/attachments/e95accff-4822-4801-8159-94411a6c644f)
Image has bigclock_12hr and bigclock_seconds enabled.

Farsi characters for P,A,M are blank since I don't know what it would look like in their language. (should i have just used the english characters as a placeholder?)

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/805
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: RadsammyT <radsammyt@gmail.com>
Co-committed-by: RadsammyT <radsammyt@gmail.com>
2025-08-03 11:16:04 +02:00
AnErrupTion
4fbbb6f0f2 Reduce nesting a bit
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 22:59:34 +02:00
AnErrupTion
ef64029795 Only clear TTY under certain circumstances
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 22:57:47 +02:00
AnErrupTion
71c694e575 Correct mention of TTY modification in README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 22:22:18 +02:00
AnErrupTion
c37aa6957a Improve README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 20:26:31 +02:00
AnErrupTion
8377f14509 Exclude unsupported distributions in packaging status
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 19:45:40 +02:00
AnErrupTion
b35c055e7b Fix clock string length issues (fixes #716)
Co-authored-by: Plash <plash@noreply.codeberg.org>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 13:08:07 +02:00
AnErrupTion
c05c32c5be Fix possible overflow with 5-digit+ UIDs (c.f. #684)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 00:57:22 +02:00
AnErrupTion
1f0274e797 Add packaging status in readme.md through Repology
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 00:02:30 +02:00
AnErrupTion
f97d0c27d3 Merge pull request 'Russian translation update' (#810) from tyusha/ly:LangUpdate into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/810
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-08-01 23:52:53 +02:00
AnErrupTion
d36147a376 Merge pull request 'fix: Reorder default PATH to prioritize /usr/local directories to solve problem with archlinux' (#814) from jlucaso/ly:fix-reorder-path into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/814
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-08-01 23:51:18 +02:00
João Lucas
8030cf5244 fix: Reorder default PATH to prioritize /usr/local directories to solve
problem with archlinux
2025-07-26 19:27:36 +02:00
AnErrupTion
933e5bdd2d Merge pull request 'fix: duplicated entry waylandsessions' (#813) from jlucaso/ly:fix-duplicate-waylandsessions-entry into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/813
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-07-26 19:26:39 +02:00
João Lucas
a9d85a6925 fix: duplicated entry waylandsessions 2025-07-26 13:55:48 -03:00
tyusha
3d3cf84292 fix 2025-07-25 16:41:06 +03:00
tyusha
19c879a201 update russian translation 2025-07-25 16:36:06 +03:00
AnErrupTion
98f59a69cc Merge pull request 'Refactor: Pull termbox2 in as a Zig dependency' (#799) from jlucaso/ly:refactor/termbox2-dependency into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/799
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-07-25 10:48:28 +02:00
João Lucas
5fb40899e5 refactor: remove termbox_extras in favor of the fork 2025-07-25 01:33:57 -03:00
jlucaso
6933c4db02 Merge branch 'master' into refactor/termbox2-dependency 2025-07-25 06:31:10 +02:00
João Lucas
c3d0864e62 fix: update termbox2 dependency URL 2025-07-25 01:29:57 -03:00
AnErrupTion
e8cb8970fd Merge pull request 'Feat: Add Japanese (ja_JP) localization' (#793) from darallium/ly:master into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/793
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-07-19 22:11:14 +02:00
darallium
c11194332c Add Japanese lang file 2025-07-19 20:44:47 +09:00
AnErrupTion
35265f5e77 Merge pull request 'fix: confined labels/box title cutting off' (#806) from radsammyt/ly:fix-confined-labels into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/806
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-07-17 10:39:08 +02:00
RadsammyT
5c3da10386 fix: confined labels cutting off
`drawConfinedLabel` didn't take into account the starting x axis
when checking to break for exceeding `max_length`.

This should fix the box title not appearing on
terminals with larger column counts.
2025-07-16 22:03:51 -04:00
AnErrupTion
1d4e32ba82 List all users in the system (fixes #373)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-10 10:06:19 +02:00
AnErrupTion
97efac0cd1 Add Matrix space link to README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-09 21:38:45 +02:00
AnErrupTion
04920e1b1b Implement custom session support (fixes #757)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 22:39:14 +02:00
AnErrupTion
48e5369f56 Fix character width calculation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 16:32:20 +02:00
AnErrupTion
918e9ad5ac Only show lang.err_lock_state once
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 12:47:24 +02:00
AnErrupTion
ce17d346e8 Remove lang.err_console_dev
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 12:45:47 +02:00
AnErrupTion
ce0b05fd6e Merge pull request 'Fix Doom fire animation; add flame height control' (#792) from asterane/ly:doom-fire-refresh into master (fixes #784)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/792
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 10:26:37 +02:00
Matthew Rothlisberger
99f3ab96ba changes fire parameters 2025-07-07 21:02:58 -04:00
Matthew Rothlisberger
2a8e221e80 improves fire gradient in true color mode 2025-07-07 21:02:58 -04:00
Matthew Rothlisberger
1c5686ea54 further improves fire behavior 2025-07-07 21:02:58 -04:00
Matthew Rothlisberger
ab23631e66 reimplements PSX Doom fire animation; adds flame height control 2025-07-07 21:02:58 -04:00
AnErrupTion
1bcbb08202 Add config.console_dev as a removed property in migrator
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-07 21:46:49 +02:00
AnErrupTion
d08b9a916e Remove config.console_dev option + handle ioctl errors
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-07 21:45:54 +02:00
AnErrupTion
e5eb8b7f6f Merge branch 'master' of codeberg.org:fairyglade/ly 2025-07-06 09:26:38 +02:00
AnErrupTion
41f4378bfe Fix XDG_RUNTIME_DIR not being set properly (fixes #781)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-06 09:25:06 +02:00
João Lucas
7b81336761 refactor: Use zig fetch for termbox2 dependency
Replaced the local `termbox2.h` header file with a proper dependency managed by the Zig package manager. This improves maintainability and makes it easier to track and update the library in the future.

The previous `termbox2.h` contained a custom `tb_get_cell` function that is not present in the upstream repository. This function has been re-implemented in Zig (`src/tui/termbox_extras.zig`) to maintain compatibility, especially for the failed-login "cascade" animation.

This change also involved updating `build.zig` and `build.zig.zon` to use the new dependency.
2025-07-04 08:32:49 -03:00
AnErrupTion
3ad0c00380 Merge pull request 'Update German translation' (#796) from Ireozar/ly:master into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/796
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-06-24 17:00:30 +02:00
Ireozar
7182d91b37 removed some special characters 2025-06-24 13:25:45 +02:00
Ireozar
de11ac8972 improved/added German localization 2025-06-24 12:34:41 +02:00
AnErrupTion
2df2c44cdf Merge pull request 'Add GameOfLife Animation' (#766) from thoxy/ly:master into master
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/766
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-06-22 15:03:32 +02:00
AnErrupTion
9a3d62333a Merge branch 'master' into master 2025-06-22 15:03:04 +02:00
AnErrupTion
2ae86a695b Finish merge
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-06-17 22:29:03 +02:00
AnErrupTion
ef78ac28a4 Remove big header about Codeberg migration
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-06-17 22:28:00 +02:00
thoxy
e7aad8de88 remove gameoflife_randomize_colors argument in main for gameoflife 2025-06-17 12:40:49 +02:00
thoxy
9b00df4337 Merge branch 'master' into master 2025-06-17 12:36:17 +02:00
thoxy
a5e38e2ce5 Remove color randomization from Game of Life animation 2025-06-17 12:35:22 +02:00
AnErrupTion
e156414062 Merge pull request 'Add option to hide version string' (#785) from dusan/ly:add-option-to-hide-version-string into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/785
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-06-13 09:28:25 +02:00
Dusan
14aae40fda Use existing label value for x 2025-06-10 14:34:20 +02:00
Dusan
3504180e95 Option to hide version string 2025-06-10 06:12:48 +02:00
thoxy
b4952cdc51 Merge branch 'master' into master 2025-05-31 18:04:56 +02:00
thoxy
5e8e0af59c Remove redundant comments and type annotations 2025-05-31 17:56:16 +02:00
AnErrupTion
a8b8292318 Add name of unknown error instead of generic string
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-31 11:04:46 +02:00
AnErrupTion
36a27f6167 Fix big clock UB in ReleaseSafe
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-31 10:32:03 +02:00
thoxy
9d4c4a3a59 add mention of the new animation in the config file 2025-05-30 21:07:38 +02:00
thoxy
78ed67b35f Merge branch 'master' into master 2025-05-30 20:07:32 +02:00
thoxy
fa46155f72 make gameoflife more configurable and fix pull reviews 2025-05-30 20:04:52 +02:00
AnErrupTion
063b3145ed Start v1.2.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 19:29:04 +02:00
AnErrupTion
fa0d8c509d Merge pull request 'Update readme.md' (#777) from a1e5Term-rf-ln/ly:new1 into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/777
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 17:49:20 +02:00
AnErrupTion
b42953fd7e Update screenshot
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 17:39:44 +02:00
B P
11d9cf8b71 Update readme.md 2025-05-30 18:35:46 +03:00
AnErrupTion
67a4dd8f9d Rewrite DOOM animation (fixes #692)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 16:11:14 +02:00
AnErrupTion
cedb7a3b02 Fix TTY not being cleared sometimes (closes #696)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 14:18:29 +02:00
AnErrupTion
02729cce21 Add brightnessctl to runtime dependency list
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 14:17:41 +02:00
AnErrupTion
d9204131aa Fix brightness up key not working (closes #763)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 09:24:07 +02:00
AnErrupTion
e90cf40e5b Update termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-29 23:52:58 +02:00
AnErrupTion
b0395ef103 Merge pull request 'Update Polish translation' (#758) from Bluudek/ly:pl_lang_update into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/758
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-05-29 23:13:27 +02:00
AnErrupTion
692e7265e3 Merge pull request 'Added dwm and gnome on Supported X11 Environments' (#753) from ManogyaDahal/ly:manogya/update_readme_supported_device into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/753
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-05-29 22:49:44 +02:00
thoxy
98af3a98c8 Add GameOfLife Animation 2025-05-27 07:07:37 +02:00
Bluudek
732888fd94 Remove invisible Unicode characters 2025-05-08 00:16:14 +02:00
Bluudek
71a06e13a6 Update Polish phrases 2025-05-08 00:12:50 +02:00
Bluudek
f74c4e92a9 Update Polish missing messages 2025-05-07 23:31:31 +02:00
ManogyaDahal
555e72a388 Updae: Added dwm in Readme 2025-04-28 14:18:12 +05:45
AnErrupTion
a1b38188d0 Merge pull request 'Add Arabic Translation' (#745) from MohamedAAbdallah/ly:master into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/745
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-04-05 07:37:04 +00:00
Mohamed A. Abdallah
751d31cae2 Update Arabic error messages for errors 2025-03-28 23:26:17 +02:00
Mohamed A. Abdallah
c6a3223a03 Update Arabic localization for actions 2025-03-28 23:10:09 +02:00
Mohamed A. Abdallah
d2803194f3 Add Arabic localization file 2025-03-28 22:57:13 +02:00
AnErrupTion
4345a99c02 Merge pull request 'Support specifying multiple directories to crawl for sessions' (#744) from jack-avery/ly:multiple_session_dirs into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/744
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-03-28 16:57:15 +00:00
jack-avery
fecc688418 snake case 2025-03-28 11:01:33 -04:00
jack-avery
b7e37ce1b7 update config comments 2025-03-28 11:00:47 -04:00
jack-avery
be5a68dd1d allow specify multiple dirs 2025-03-28 00:47:12 -04:00
AnErrupTion
622303d150 Merge pull request 'Separate Wayland/X11 environments in README' (#742) from Chiron8/ly:master into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/742
Reviewed-by: AnErrupTion <anerruption@disroot.org>
2025-03-20 18:20:20 +00:00
Chiron8
7747b27f9d Update readme.md 2025-03-20 13:08:13 +00:00
Chiron8
585ca5f0da Update readme.md 2025-03-20 08:24:25 +00:00
Chiron8
c624c35ac7 Update readme.md 2025-03-17 16:25:06 +00:00
Chiron8
beb6d04c58 Update readme.md 2025-03-17 16:19:43 +00:00
Chiron8
32793bcfeb Update readme.md 2025-03-17 16:18:51 +00:00
AnErrupTion
9ded9fd765 Remove use of deprecated aliases/types + use upstream zigini
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-16 22:45:46 +01:00
AnErrupTion
13ba52319c Clean termbox2 usage + fix animation bug
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-16 11:40:27 +01:00
AnErrupTion
1672d4a9ec Make main code less directly dependent on termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-16 11:17:38 +01:00
AnErrupTion
e0ed1b4eb1 Add animation framework
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-16 10:54:06 +01:00
AnErrupTion
fa0748ead2 Remove unused valgrind file
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-16 09:13:01 +01:00
AnErrupTion
86ea38f460 Split session crawling from TUI component
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-16 00:24:56 +01:00
AnErrupTion
9efb734fd5 Show Ly version string at top-left
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-13 13:27:18 +01:00
AnErrupTion
5f52063835 Remove duplicate information in README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-12 08:00:42 +01:00
AnErrupTion
548fa210c1 Update README with new build commands
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-12 07:58:37 +01:00
AnErrupTion
75975ea301 Add Niri to tested WM list
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-12 07:48:38 +01:00
AnErrupTion
8fa6b2cec9 Improve build system commands
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-07 11:03:45 +01:00
AnErrupTion
4dcef65b1c Fix X11 session logout
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 23:32:23 +01:00
AnErrupTion
1eca889e45 Confirm LeftWM support (closes #164)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 22:57:24 +01:00
AnErrupTion
ac1d828a5f Stop spamming err_console_dev when first time didn't work
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 22:02:01 +01:00
AnErrupTion
92845268af Add option to allow empty password or not (closes #577)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 22:00:46 +01:00
AnErrupTion
6504cd0209 Mention OS in issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:51:17 +01:00
AnErrupTion
a058a81ec9 Use proper input text box in issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:48:40 +01:00
AnErrupTion
c6db79b873 Fix issue template
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:48:03 +01:00
AnErrupTion
932c751ac2 Stop redirecting X11 output to session log (closes #693, #688)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:46:40 +01:00
AnErrupTion
4599654398 Update issue template for bugs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:41:28 +01:00
AnErrupTion
e19a23b54c Allow using up to a UTF-32 codepoint as a password asterisk (closes #715)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:16:24 +01:00
AnErrupTion
f2ca72eace Add (incomplete) Chinese translation, thanks @eonun! (closes #194)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 21:08:05 +01:00
AnErrupTion
4e859e56cb Allow modifying DOOM animation fire colors (closes #239)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 20:40:53 +01:00
AnErrupTion
78d64ad2a7 Possibly fix .Xresources not being loaded (closes #600)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 20:06:28 +01:00
AnErrupTion
d80ec8fd1f Allow changing matrix animation min/max codepoints (closes #615)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 19:48:11 +01:00
AnErrupTion
d12fa27168 Added new error and updated French translation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 19:41:05 +01:00
AnErrupTion
593a775148 Use unsigned integers only in Matrix animation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 19:36:42 +01:00
AnErrupTion
6079c01a4b Allow disabling the brightness control commands (closes #664)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 19:16:47 +01:00
AnErrupTion
9c79137c9f Remove all deprecated calls to tb_cell_buffer()
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 15:42:33 +01:00
AnErrupTion
9168266cca Don't shutdown termbox2 if authentication fails
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 15:29:56 +01:00
AnErrupTion
6cb102257c Fix Doom & Matrix animation + bug in migrator
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 15:27:38 +01:00
AnErrupTion
55abc4d7f1 Decouple TerminalBuffer and auth from Config
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 14:32:05 +01:00
AnErrupTion
f013af0dde Fix segmentation fault when using color mix after auth
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 14:31:11 +01:00
AnErrupTion
973d8fe120 Don't dynamically allocate color strings
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 13:23:47 +01:00
AnErrupTion
3e6d7a1b3b Forgot a file lol
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 13:09:19 +01:00
AnErrupTion
0c69e0412c Don't make errors blink by default
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 13:08:50 +01:00
AnErrupTion
d0ccaa4d69 Enable true color output (closes #705)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 12:47:28 +01:00
AnErrupTion
a766dc2b9c Update termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-06 11:42:19 +01:00
AnErrupTion
f54657432a Don't set XDG_CURRENT_DESKTOP and XDG_SESSION_DESKTOP if they're empty (closes #702)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 22:50:09 +01:00
AnErrupTion
87503367e9 Try to create /etc/pam.d and /usr/bin everytime when installing
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 22:36:17 +01:00
AnErrupTion
85e071a60a Code style changes in build.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 22:07:18 +01:00
AnErrupTion
24599368df Minor changes in color mix animation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 22:06:56 +01:00
AnErrupTion
d5bc6aab24 Merge pull request 'Color mix animation' (#724) from winlith/colormix into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/724
Reviewed-by: AnErrupTion <anerruption@noreply.codeberg.org>
2025-03-05 20:54:48 +00:00
AnErrupTion
c9a416b8a2 Merge branch 'master' into winlith/colormix 2025-03-05 20:53:53 +00:00
AnErrupTion
15c2b19371 Merge pull request 'Make language files uniform' (#704) from moritz-reinel/03_uniform-lang-files into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/704
Reviewed-by: AnErrupTion <anerruption@noreply.codeberg.org>
2025-03-05 20:28:57 +00:00
AnErrupTion
142073c362 Merge branch 'master' into moritz-reinel/03_uniform-lang-files 2025-03-05 20:26:01 +00:00
AnErrupTion
514931b486 Merge pull request 'fix: don't include dest_dir in config_dir' (#728) from villamorrd/master into master
Reviewed-on: https://codeberg.org/AnErrupTion/ly/pulls/728
Reviewed-by: AnErrupTion <anerruption@noreply.codeberg.org>
2025-03-05 19:53:54 +00:00
AnErrupTion
50e9e912e0 Merge branch 'master' into villamorrd/master 2025-03-05 19:52:09 +00:00
AnErrupTion
895edaf904 Update README to only include Zig 0.14.0
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 19:43:02 +01:00
AnErrupTion
bebccf4d5a Support Zig 0.14.0 only (with 1 downstream dependency fork)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 19:42:00 +01:00
AnErrupTion
346a614ba0 Mention Codeberg in the README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-03-05 02:03:40 +00:00
villamorrd
d171634f9b fix: don't include dest_dir in config_dir
This should fix $CONFIG_DIRECTORY including the installation directory in its path.
2025-02-01 20:58:52 +08:00
winlith
f5f7422d82 color mix animation 2025-01-17 22:37:48 +01:00
Moritz Reinel
117eccd65a rename language normalizer script 2024-10-26 13:19:33 +02:00
Moritz Reinel
b6726a76c6 refactored some variables + added comments 2024-10-26 13:09:30 +02:00
Moritz Reinel
0dea19c8db remove bash version of script 2024-10-21 19:43:33 +02:00
Moritz Reinel
7a82b51ac5 Revert "remove python version of lang script"
This reverts commit 7a1fce660c.
2024-10-21 19:42:57 +02:00
Moritz Reinel
fe36879dfb added note to Lang.zig and updated lang script to not process the comment 2024-10-12 22:07:29 +02:00
Moritz Reinel
47ebe641d9 remove necessity for temp file 2024-10-12 21:51:50 +02:00
Moritz Reinel
7a1fce660c remove python version of lang script 2024-10-12 21:27:19 +02:00
Moritz Reinel
06e283961d add helper script for keeping lang files in sync with Lang.zig 2024-10-12 21:26:31 +02:00
Moritz
e125d8f1aa Add error when sleep command fails (#703) 2024-10-12 20:13:25 +02:00
Ch1llyB1lly
e885a5e776 Add Hyprland in README (#701)
Adds hyprland to the list of tested desktop environments
2024-10-09 07:17:38 +02:00
Moritz
aea95b7724 Prevent output of brightness/sleep command showing on screen (fixing #695) (#699)
* Prevent output of brightness cmds showing on screen (fixing #695)

* Prevent output of sleep cmd showing on screen
2024-10-01 20:32:30 +02:00
ttsenturk
215ca5edc7 Update readme.md (#698) 2024-10-01 20:29:58 +02:00
Claudi Lleyda Moltó
022d146f76 Update Catalan translation (#689) 2024-08-21 21:19:33 +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
85 changed files with 6215 additions and 5398 deletions

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

@@ -0,0 +1,73 @@
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
- label: I have reproduced the issue on a fresh install of my OS & Ly with default settings, except ones I will mention
required: true
- label: I have confirmed this issue also occurs on the latest development version
required: true
- type: input
id: version
attributes:
label: Ly version
description: The output of `ly --version`. Please note that only Ly v1.2.0 and above are supported.
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: input
id: desktop
attributes:
label: OS + Desktop environment/Window manager
description: Which OS and DE (or WM) did you use when observing the problem?
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 (or attach) 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. The log files (located as specified by `/etc/ly/config.ini`) usually contain relevant information about the problem:
- The session log is located at `~/.local/state/ly-session.log` by default.
- The system log is located at `/var/log/ly.log` by default.
render: shell
- type: textarea
id: moreinfo
attributes:
label: Additional information
description: If you have any additional information that might be helpful in reproducing the problem, please provide it here.

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

11
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,11 @@
## What are the changes about?
_Replace this with a brief description of your changes_
## What existing issue does this resolve?
_Replace this with a reference to an existing issue, or N/A if there is none_
## Pre-requisites
- [ ] I have tested & confirmed the changes work locally

BIN
.github/screenshot.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

3
.gitignore vendored
View File

@@ -1,4 +1,5 @@
.idea/
zig-cache/
zig-out/
valgrind.log
valgrind.log
.zig-cache

510
build.zig
View File

@@ -1,33 +1,75 @@
const std = @import("std");
const builtin = @import("builtin");
const PatchMap = std.StringHashMap([]const u8);
const InitSystem = enum {
systemd,
openrc,
runit,
s6,
dinit,
sysvinit,
freebsd,
};
const min_zig_string = "0.15.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 = 4, .patch = 0 };
const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 1 };
var dest_directory: []const u8 = undefined;
var data_directory: []const u8 = undefined;
var exe_name: []const u8 = undefined;
var config_directory: []const u8 = undefined;
var prefix_directory: []const u8 = undefined;
var executable_name: []const u8 = undefined;
var init_system: InitSystem = undefined;
var default_tty_str: []const u8 = undefined;
pub fn build(b: *std.Build) !void {
dest_directory = b.option([]const u8, "dest_directory", "Specify a destination directory for installation") orelse "";
data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly). This path gets embedded into the binary") orelse "/etc/ly";
exe_name = b.option([]const u8, "name", "Specify installed executable file name (default is ly)") orelse "ly";
const bin_directory = try b.allocator.dupe(u8, data_directory);
data_directory = try std.fs.path.join(b.allocator, &[_][]const u8{ dest_directory, data_directory });
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";
init_system = b.option(InitSystem, "init_system", "Specify the target init system (default is systemd)") orelse .systemd;
const build_options = b.addOptions();
build_options.addOption([]const u8, "data_directory", bin_directory);
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;
const fallback_tty = b.option(u8, "fallback_tty", "Set the fallback TTY (default is 2). This value gets embedded into the binary") orelse 2;
const fallback_uid_min = b.option(std.posix.uid_t, "fallback_uid_min", "Set the fallback minimum UID (default is 1000). This value gets embedded into the binary") orelse 1000;
const fallback_uid_max = b.option(std.posix.uid_t, "fallback_uid_max", "Set the fallback maximum UID (default is 60000). This value gets embedded into the binary") orelse 60000;
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
build_options.addOption([]const u8, "config_directory", config_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(u8, "fallback_tty", fallback_tty);
build_options.addOption(std.posix.uid_t, "fallback_uid_min", fallback_uid_min);
build_options.addOption(std.posix.uid_t, "fallback_uid_max", fallback_uid_max);
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 = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
// Here until the native backend matures in terms of performance
.use_llvm = true,
});
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
@@ -38,25 +80,25 @@ pub fn build(b: *std.Build) !void {
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("clap", clap.module("clap"));
exe.addIncludePath(.{ .path = "include" });
const termbox_dep = b.dependency("termbox2", .{
.target = target,
.optimize = optimize,
});
exe.linkSystemLibrary("pam");
exe.linkSystemLibrary("xcb");
if (enable_x11_support) exe.linkSystemLibrary("xcb");
exe.linkLibC();
// HACK: Only fails with ReleaseSafe, so we'll override it.
const translate_c = b.addTranslateC(.{
.root_source_file = .{ .path = "include/termbox2.h" },
.root_source_file = termbox_dep.path("termbox2.h"),
.target = target,
.optimize = if (optimize == .ReleaseSafe) .ReleaseFast else optimize,
.optimize = optimize,
});
translate_c.defineCMacroRaw("TB_IMPL");
translate_c.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit)
const termbox2 = translate_c.addModule("termbox2");
exe.root_module.addImport("termbox2", termbox2);
if (optimize == .ReleaseSafe) {
std.debug.print("warn: termbox2 module is being built in ReleaseFast due to a bug.\n", .{});
}
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
@@ -68,179 +110,273 @@ pub fn build(b: *std.Build) !void {
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;
const installexe_step = b.step("installexe", "Install Ly and the selected init system service");
installexe_step.makeFn = Installer(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;
const installnoconf_step = b.step("installnoconf", "Install Ly and the selected init system service, but not the configuration file");
installnoconf_step.makeFn = Installer(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 uninstallexe_step = b.step("uninstallexe", "Uninstall Ly and remove the selected init system service");
uninstallexe_step.makeFn = Uninstaller(true).make;
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 uninstallall_step = b.step("uninstallall", "Uninstall Ly and all services");
uninstallall_step.makeFn = uninstallall;
const uninstallnoconf_step = b.step("uninstallnoconf", "Uninstall Ly and remove the selected init system service, but keep the configuration directory");
uninstallnoconf_step.makeFn = Uninstaller(false).make;
}
pub fn ExeInstaller(install_conf: bool) type {
pub fn Installer(install_config: bool) type {
return struct {
pub fn make(step: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
try install_ly(step.owner.allocator, install_conf);
}
};
}
const InitSystem = enum {
Systemd,
Openrc,
Runit,
};
pub fn ServiceInstaller(comptime init_system: InitSystem) type {
return struct {
pub fn make(step: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
const allocator = step.owner.allocator;
switch (init_system) {
.Openrc => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/init.d" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
try std.fs.cwd().copyFile("res/ly-openrc", service_dir, exe_name, .{ .override_mode = 0o755 });
},
.Runit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/sv/ly" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
var patch_map = PatchMap.init(allocator);
defer patch_map.deinit();
try std.fs.cwd().copyFile("res/ly-runit-service/conf", service_dir, "conf", .{});
try std.fs.cwd().copyFile("res/ly-runit-service/finish", service_dir, "finish", .{ .override_mode = 0o755 });
try std.fs.cwd().copyFile("res/ly-runit-service/run", service_dir, "run", .{ .override_mode = 0o755 });
},
.Systemd => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/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();
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);
try std.fs.cwd().copyFile("res/ly.service", service_dir, "ly.service", .{ .override_mode = 0o644 });
},
}
// The "-a" argument doesn't exist on FreeBSD, so we use "-p"
// instead to shutdown the system.
try patch_map.put("$PLATFORM_SHUTDOWN_ARG", if (init_system == .freebsd) "-p" else "-a");
try install_ly(allocator, patch_map, install_config);
try install_service(allocator, patch_map);
}
};
}
fn install_ly(allocator: std.mem.Allocator, install_config: bool) !void {
std.fs.cwd().makePath(data_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{data_directory});
fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: bool) !void {
const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, 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 lang_path = try std.fs.path.join(allocator, &[_][]const u8{ data_directory, "/lang" });
std.fs.cwd().makePath(lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{data_directory});
const ly_custom_sessions_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" });
std.fs.cwd().makePath(ly_custom_sessions_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory});
};
var current_dir = std.fs.cwd();
const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" });
std.fs.cwd().makePath(ly_lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path});
};
{
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/bin" });
if (!std.mem.eql(u8, dest_directory, "")) {
std.fs.cwd().makePath(exe_path) catch {
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
std.fs.cwd().makePath(exe_path) catch {
if (!std.mem.eql(u8, dest_directory, "")) {
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 current_dir.copyFile("zig-out/bin/ly", executable_dir, exe_name, .{});
try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
}
{
var config_dir = std.fs.cwd().openDir(data_directory, .{}) catch unreachable;
var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable;
defer config_dir.close();
if (install_config) {
try current_dir.copyFile("res/config.ini", config_dir, "config.ini", .{});
const patched_config = try patchFile(allocator, "res/config.ini", patch_map);
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{});
}
try current_dir.copyFile("res/xsetup.sh", config_dir, "xsetup.sh", .{});
try current_dir.copyFile("res/wsetup.sh", config_dir, "wsetup.sh", .{});
const patched_example_config = try patchFile(allocator, "res/config.ini", patch_map);
try installText(patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{});
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(lang_path, .{}) catch unreachable;
var custom_sessions_dir = std.fs.cwd().openDir(ly_custom_sessions_directory, .{}) catch unreachable;
defer custom_sessions_dir.close();
const patched_readme = try patchFile(allocator, "res/custom-sessions/README", patch_map);
try installText(patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{});
}
{
var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
defer lang_dir.close();
try current_dir.copyFile("res/lang/cat.ini", lang_dir, "cat.ini", .{});
try current_dir.copyFile("res/lang/cs.ini", lang_dir, "cs.ini", .{});
try current_dir.copyFile("res/lang/de.ini", lang_dir, "de.ini", .{});
try current_dir.copyFile("res/lang/en.ini", lang_dir, "en.ini", .{});
try current_dir.copyFile("res/lang/es.ini", lang_dir, "es.ini", .{});
try current_dir.copyFile("res/lang/fr.ini", lang_dir, "fr.ini", .{});
try current_dir.copyFile("res/lang/it.ini", lang_dir, "it.ini", .{});
try current_dir.copyFile("res/lang/pl.ini", lang_dir, "pl.ini", .{});
try current_dir.copyFile("res/lang/pt.ini", lang_dir, "pt.ini", .{});
try current_dir.copyFile("res/lang/pt_BR.ini", lang_dir, "pt_BR.ini", .{});
try current_dir.copyFile("res/lang/ro.ini", lang_dir, "ro.ini", .{});
try current_dir.copyFile("res/lang/ru.ini", lang_dir, "ru.ini", .{});
try current_dir.copyFile("res/lang/sr.ini", lang_dir, "sr.ini", .{});
try current_dir.copyFile("res/lang/sv.ini", lang_dir, "sv.ini", .{});
try current_dir.copyFile("res/lang/tr.ini", lang_dir, "tr.ini", .{});
try current_dir.copyFile("res/lang/uk.ini", lang_dir, "uk.ini", .{});
const languages = [_][]const u8{
"ar.ini",
"cat.ini",
"cs.ini",
"de.ini",
"en.ini",
"es.ini",
"fr.ini",
"it.ini",
"ja_JP.ini",
"lv.ini",
"pl.ini",
"pt.ini",
"pt_BR.ini",
"ro.ini",
"ru.ini",
"sr.ini",
"sv.ini",
"tr.ini",
"uk.ini",
"zh_CN.ini",
};
inline for (languages) |language| {
try installFile("res/lang/" ++ language, lang_dir, ly_lang_path, language, .{});
}
}
{
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/pam.d" });
if (!std.mem.eql(u8, dest_directory, "")) {
std.fs.cwd().makePath(pam_path) catch {
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
std.fs.cwd().makePath(pam_path) catch {
if (!std.mem.eql(u8, dest_directory, "")) {
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 current_dir.copyFile("res/pam.d/ly", pam_dir, "ly", .{ .override_mode = 0o644 });
try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd-autologin" else "res/pam.d/ly-linux-autologin", pam_dir, pam_path, "ly-autologin", .{ .override_mode = 0o644 });
}
}
pub fn uninstallall(step: *std.Build.Step, progress: *std.Progress.Node) !void {
_ = progress;
try std.fs.cwd().deleteTree(data_directory);
const allocator = step.owner.allocator;
fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void {
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 exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/bin/", exe_name });
try std.fs.cwd().deleteFile(exe_path);
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 pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/pam.d/ly" });
try std.fs.cwd().deleteFile(pam_path);
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 systemd_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/lib/systemd/system/ly.service" });
std.fs.cwd().deleteFile(systemd_service_path) catch {
std.debug.print("warn: systemd service not found.\n", .{});
};
const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
const openrc_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/init.d/ly" });
std.fs.cwd().deleteFile(openrc_service_path) catch {
std.debug.print("warn: openrc service not found.\n", .{});
};
const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map);
try installText(patched_conf, service_dir, service_path, "conf", .{});
const runit_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/sv/ly" });
std.fs.cwd().deleteTree(runit_service_path) catch {
std.debug.print("warn: runit service not found.\n", .{});
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 });
std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{}) catch |err| {
if (err == error.PathAlreadyExists) {
std.debug.print("warn: /run/runit/supervise.ly already exists as a symbolic link.\n", .{});
} else {
return err;
}
};
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", .{});
},
.sysvinit => {
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-sysvinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{ .mode = 0o755 });
},
.freebsd => {
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable;
defer executable_dir.close();
const patched_wrapper = try patchFile(allocator, "res/ly-freebsd-wrapper", patch_map);
try installText(patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .mode = 0o755 });
},
}
}
pub fn Uninstaller(uninstall_config: bool) type {
return struct {
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
const allocator = step.owner.allocator;
if (uninstall_config) {
try deleteTree(allocator, config_directory, "/ly", "ly config directory not found");
}
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ 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");
switch (init_system) {
.systemd => try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly@.service", "systemd service not found"),
.openrc => try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found"),
.runit => try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found"),
.s6 => {
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");
},
.dinit => try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found"),
.sysvinit => try deleteFile(allocator, config_directory, "/init.d/ly", "sysvinit service not found"),
.freebsd => try deleteFile(allocator, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"),
}
}
};
}
@@ -279,7 +415,7 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
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.debug.print("{s} version '{f}' must be greater than tagged ancestor '{f}'\n", .{ name, version, ancestor_ver });
std.process.exit(1);
}
@@ -298,3 +434,95 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
},
}
}
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 stat = try file.stat();
var buffer: [4096]u8 = undefined;
var reader = file.reader(&buffer);
var text = try reader.interface.readAlloc(allocator, stat.size);
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();
var buffer: [1024]u8 = undefined;
var writer = file.writer(&buffer);
try writer.interface.writeAll(text);
try writer.interface.flush();
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});
}

View File

@@ -1,15 +1,20 @@
.{
.name = "ly",
.version = "1.0.0",
.minimum_zig_version = "0.12.0",
.name = .ly,
.version = "1.4.0",
.fingerprint = 0xa148ffcc5dc2cb59,
.minimum_zig_version = "0.15.0",
.dependencies = .{
.clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/8c98e6404b22aafc0184e999d8f068b81cc22fa1.tar.gz",
.hash = "122014e73fd712190e109950837b97f6143f02d7e2b6986e1db70b6f4aadb5ba6a0d",
.url = "git+https://github.com/Hejsil/zig-clap#5289e0753cd274d65344bef1c114284c633536ea",
.hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e",
},
.zigini = .{
.url = "https://github.com/Kawaii-Ash/zigini/archive/ce1f322482099db058f5d9fdd05fbfa255d79723.tar.gz",
.hash = "1220e7a99793a0430e0a7c0b938cb3c98321035bc297e21cd0e2413cf740b4923b9f",
.url = "git+https://github.com/AnErrupTion/zigini?ref=zig-0.15.0#9281f47702b57779e831d7618e158abb8eb4d4a2",
.hash = "zigini-0.3.3-36M0FRJJAADZVq5HPm-hYKMpFFTr0OgjbEYcK2ijKZ5n",
},
.termbox2 = .{
.url = "git+https://github.com/AnErrupTion/termbox2?ref=master#290ac6b8225aacfd16851224682b851b65fcb918",
.hash = "N-V-__8AAGcUBQAa5vov1Yi_9AXEffFQ1e2KsXaK4dgygRKq",
},
},
.paths = .{""},

View File

@@ -1,52 +0,0 @@
# Zig Rewrite (Version 1.0.0)
## Config Options
res/config.ini contains all of the available config options and their default values.
### Additions
+ `border_fg` has been introduced to change the color of the borders.
+ `term_restore_cursor_cmd` should restore the cursor to it's usual state.
+ `vi_mode` to enable vi keybindings.
+ `sleep_key` and `sleep_cmd`.
+ `numlock` to set numlock on startup.
+ `initial_info_text` allows changing the initial text on the info line.
+ `box_title` to display a title at the top of the main box
Note: `sleep_cmd` is unset by default, meaning it's hidden and has no effect.
### Changes
+ xinitrc can be set to null to hide it.
+ `blank_password` has been renamed to `clear_password`.
+ `save_file` has been deprecated and will be removed in a future version.
### Removals
+ `wayland_specifier` has been removed.
## Save File
The save file is now in .ini format and stored in the same directory as the config.
Older save files will be migrated to the new format.
Example:
```ini
user = ash
session_index = 0
```
## Misc
+ Display server name added next to selected session.
+ getty@tty2 has been added as a conflict in res/ly.service, so if it is running, ly should still be able to start.
+ `XDG_CURRENT_DESKTOP` is now set by ly.
+ LANG is no longer set by ly.
+ X Server PID is fetched from /tmp/X{d}.lock to be able to kill the process since it detaches.
+ Non .desktop files are now ignored in sessions directory.
+ PAM auth is now done in a child process. (Fixes some issues with logging out and back in).
+ When ly receives SIGTERM, the terminal is now cleared and existing child processes are cleaned up.
+ Shift+Tab now focuses previous input.
+ Display text in the info line when authenticating.

File diff suppressed because it is too large Load Diff

347
readme.md
View File

@@ -1,207 +1,296 @@
# The Ly display manager
# 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.
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD,
designed with portability in mind (e.g. it does not require systemd to run).
Join us on Matrix over at [#ly:envs.net](https://matrix.to/#/#ly:envs.net)!
**Note**: Development happens on [Codeberg](https://codeberg.org/fairyglade/ly)
with a mirror on [GitHub](https://github.com/fairyglade/ly).
## Dependencies
- Compile-time:
- zig 0.12.0
- a C standard library
- zig 0.15.x
- libc
- pam
- xcb
- xcb (optional, required by default; needed for X11 support)
- Runtime (with default config):
- xorg
- xorg-xauth
- mcookie
- tput
- shutdown
- brightnessctl
### Debian
```
# apt install build-essential libpam0g-dev libxcb-xkb-dev
# apt install build-essential libpam0g-dev libxcb-xkb-dev xauth xserver-xorg brightnessctl
```
### 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
# dnf install kernel-devel pam-devel libxcb-devel zig xorg-x11-xauth xorg-x11-server brightnessctl
```
### FreeBSD
```
# pkg install ca_root_nss libxcb git xorg xauth
```
## Availability
[![Packaging status](https://repology.org/badge/vertical-allrepos/ly-display-manager.svg?exclude_unsupported=1)](https://repology.org/project/ly-display-manager/versions)
## Support
The following desktop environments were tested with success:
- awesome
- bspwm
- budgie
- cinnamon
- deepin
- dwl
- dwm
- enlightenment
- gnome
- i3
- kde
- labwc
- lxde
- lxqt
- mate
- maxx
- pantheon
- qtile
- spectrwm
- sway
- windowmaker
- xfce
- xmonad
Ly should work with any X desktop environment, and provides
basic wayland support (sway works very well, for example).
Ly has been tested with a wide variety of desktop environments and window
managers, all of which you can find in the sections below:
## systemd?
Unlike what you may have heard, Ly does not require `systemd`,
and was even specifically designed not to depend on `logind`.
You should be able to make it work easily with a better init,
changing the source code won't be necessary :)
[Wayland environments](#supported-wayland-environments)
[X11 environments](#supported-x11-environments)
Logs are defined by `/etc/ly/config.ini`:
- The session log is located at `~/.local/state/ly-session.log` by default.
- The system log is located at `/var/log/ly.log` by default.
## Manually building
The procedure for manually building Ly is pretty standard:
## Cloning and Compiling
Clone the repository
```
$ git clone https://github.com/fairyglade/ly
```
Change the directory to ly
```
$ git clone https://codeberg.org/fairyglade/ly.git
$ cd ly
```
Compile
```
$ zig build
```
Test in the configured tty (tty2 by default)
or a terminal emulator (but desktop environments won't start)
After building, you can (optionally) test Ly in a terminal emulator, although
authentication will **not** work:
```
# zig build run
$ zig build run
```
Install Ly and the provided systemd service file
**Important**: While you can also run Ly in a terminal emulator as root, it is
**not** recommended either. If you want to properly test Ly, please enable its
service (as described below) and reboot your machine.
The following sections show how to install Ly for a particular init system.
Because the procedure is very similar for all of them, the commands will only
be detailed for the first section (which is about systemd).
**Note**: All following sections will assume you are using LightDM for
convenience sake.
### systemd
Now, you can install Ly on your system:
```
# zig build installsystemd
# zig build installexe -Dinit_system=systemd
```
Enable the service
**Note**: The `init_system` parameter is optional and defaults to `systemd`.
Note that you also need to disable your current display manager. For example,
if LightDM is the current display manager, you can execute the following
command:
```
# systemctl enable ly.service
# systemctl disable lightdm.service
```
If you need to switch between ttys after Ly's start you also have to
disable getty on Ly's tty to prevent "login" from spawning on top of it
Then, similarly to the previous command, you need to enable the Ly service:
```
# systemctl enable ly@tty2.service
```
**Important**: Because Ly runs in a TTY, you **must** disable the TTY service
that Ly will run on, otherwise bad things will happen. For example, to disable `getty` spawning on TTY 2, you need to execute the following command:
```
# systemctl disable getty@tty2.service
```
On platforms that use systemd-logind to dynamically start `autovt@.service` instances when the switch to a new tty occurs, any ly instances for ttys *except the default tty* need to be enabled using a different mechanism: To autostart ly on switch to `tty2`, do not enable any `ly` unit directly, instead symlink `autovt@tty2.service` to `ly@tty2.service` within `/usr/lib/systemd/system/` (analogous for every other tty you want to enable ly on).
The target of the symlink, `ly@ttyN.service`, does not actually exist, but systemd nevertheless recognizes that the instanciation of `autovt@.service` with `%I` equal to `ttyN` now points to an instanciation of `ly@.service` with `%I` set to `ttyN`.
Compare to `man 5 logind.conf`, especially regarding the `NAutoVTs=` and `ReserveVT=` parameters.
On non-systemd systems, you can change the TTY Ly will run on by editing the corresponding
service file for your platform.
### OpenRC
**NOTE**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
Clone, compile and test.
Install Ly and the provided OpenRC service
```
# zig build installopenrc
```
Enable the service
```
# zig build installexe -Dinit_system=openrc
# rc-update del lightdm
# rc-update add ly
```
You can edit which tty Ly will start on by editing the `tty` option in the configuration file.
If you choose a tty that already has a login/getty running (has a basic login prompt),
then you have to disable getty, so it doesn't respawn on top of ly
```
# rc-update del agetty.tty2
```
**Note**: On Gentoo specifically, you also **must** comment out the appropriate
line for the TTY in /etc/inittab.
### runit
```
# zig build installrunit
# zig build installexe -Dinit_system=runit
# rm /var/service/lightdm
# ln -s /etc/sv/ly /var/service/
```
By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini`
You should as well disable your existing display manager service if needed, e.g.:
```
# rm /var/service/lxdm
```
The agetty service for the tty console where you are running ly should be disabled.
For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`)
you should disable the agetty-tty2 service like this:
```
# rm /var/service/agetty-tty2
```
### s6
```
# zig build installexe -Dinit_system=s6
# s6-rc -d change lightdm
# s6-service add default ly-srv
# s6-db-reload
# s6-rc -u change ly-srv
```
To disable TTY 2, edit `/etc/s6/config/tty2.conf` and set `SPAWN="no"`.
### dinit
```
# zig build installexe -Dinit_system=dinit
# dinitctl disable lightdm
# dinitctl enable ly
```
To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify
`ACTIVE_CONSOLES`.
### sysvinit
```
# zig build installexe -Dinit_system=sysvinit
# update-rc.d lightdm disable
# update-rc.d ly defaults
```
To disable TTY 2, go to `/etc/inittab` and comment out the line containing `tty2`.
### FreeBSD
```
# zig build installexe -Dprefix_directory=/usr/local -Dconfig_directory=/usr/local/etc -Dinit_system=freebsd
# sysrc lightdm_enable="NO"
```
To enable Ly, add the following entry to `/etc/gettytab`:
```
Ly:\
:lo=/usr/local/bin/ly_wrapper:\
:al=root:
```
Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys`
(TTYs in FreeBSD start at 0):
```
ttyv1 "/usr/libexec/getty Ly" xterm on secure
```
### Updating
You can also install Ly without copying the system service and the configuration file. That's
called *updating*. To update, simply run:
You can also install Ly without overrding the current configuration file. This
is 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
```
You can, of course, still select the init system of your choice when using this
command.
## Configuration
You can find all the configuration in `/etc/ly/config.ini`.
The file is commented, and includes the default values.
You can find all the configuration in `/etc/ly/config.ini`. The file is fully
commented, and includes the default values.
## Controls
Use the up and down arrow keys to change the current field, and the
left and right arrow keys to change the target desktop environment
while on the desktop field (above the login field).
## .xinitrc
If your .xinitrc doesn't work make sure it is executable and includes a shebang.
This file is supposed to be a shell script! Quoting from xinit's man page:
Use the Up/Down arrow keys to change the current field, and the Left/Right
arrow keys to scroll through the different fields (whether it be the info line,
the desktop environment, or the username). The info line is where messages and
errors are displayed.
> If no specific client program is given on the command line, xinit will look for a file in the user's home directory called .xinitrc to run as a shell script to start up client programs.
## A note on .xinitrc
If your `.xinitrc` file doesn't work ,make sure it is executable and includes a
shebang. This file is supposed to be a shell script! Quoting from `xinit`'s man
page:
> If no specific client program is given on the command line, xinit will look
> for a file in the user's home directory called .xinitrc to run as a shell
> script to start up client programs.
A typical shebang for a shell script looks like this:
On Arch Linux, the example .xinitrc (/etc/X11/xinit/xinitrc) starts like this:
```
#!/bin/sh
```
## Tips
The numlock and capslock state is printed in the top-right corner.
Use the F1 and F2 keys to respectively shutdown and reboot.
Take a look at your .xsession if X doesn't start, as it can interfere
(this file is launched with X to configure the display properly).
## PSX DOOM fire animation
To enable the famous PSX DOOM fire described by [Fabien Sanglard](http://fabiensanglard.net/doom_fire_psx/index.html),
just set `animation = doom` in `/etc/ly/config.ini`. You may also
disable the main box borders with `hide_borders = true`.
- The numlock and capslock state is printed in the top-right corner.
- Use the F1 and F2 keys to respectively shutdown and reboot.
- Take a look at your `.xsession` file if X doesn't start, as it can interfere
(this file is launched with X to configure the display properly).
## 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.
## Supported Wayland environments
## Gentoo (OpenRC) installation tip
To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).
- budgie
- cosmic
- deepin
- enlightenment
- gnome
- hyprland
- kde
- labwc
- niri
- pantheon
- sway
- weston
## Supported X11 environments
- awesome
- bspwm
- budgie
- cinnamon
- dwm
- enlightenment
- gnome
- kde
- leftwm
- lxde
- mate
- maxx
- pantheon
- qwm
- spectrwm
- windowmaker
- xfce
- xmonad
## A final note
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,126 +1,290 @@
# Ly supports 24-bit true color with styling, which means each color is a 32-bit value.
# The format is 0xSSRRGGBB, where SS is the styling, RR is red, GG is green, and BB is blue.
# Here are the possible styling options:
# TB_BOLD 0x01000000
# TB_UNDERLINE 0x02000000
# TB_REVERSE 0x04000000
# TB_ITALIC 0x08000000
# TB_BLINK 0x10000000
# TB_HI_BLACK 0x20000000
# TB_BRIGHT 0x40000000
# TB_DIM 0x80000000
# Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's
# configuration doesn't support using it, you have to manually compute the color value.
# Note that, if you want to use the default color value of the terminal, you can use the
# special value 0x00000000. This means that, if you want to use black, you *must* use
# the styling option TB_HI_BLACK (the RGB values are ignored when using this option).
# Allow empty password or not when authenticating
allow_empty_password = true
# The active animation
# none -> Nothing (default)
# doom -> PSX DOOM fire
# matrix -> CMatrix
# none -> Nothing
# doom -> PSX DOOM fire
# matrix -> CMatrix
# colormix -> Color mixing shader
# gameoflife -> John Conway's Game of Life
# dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master)
animation = none
# Format string for clock in top right corner (see strftime specification). Example: %c
clock = null
# Enable/disable big clock
bigclock = false
# 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
# You can either type it directly as a UTF-8 character (like *), or use a UTF-32
# codepoint (for example 0x2022 for a bullet point)
# If null, the password will be hidden
# Note: you can use a # by escaping it like so: \#
asterisk = *
# Erase password input on failure
clear_password = false
# The number of failed authentications before a special animation is played... ;)
# If set to 0, the animation will never be played
auth_fails = 10
# Enable vi keybindings
vi_mode = false
# Identifier for battery whose charge to display at top left
# Primary battery is usually BAT0 or BAT1
# If set to null, battery status won't be shown
battery_id = null
# The `fg` and `bg` color settings take a digit 0-8 corresponding to:
#define TB_DEFAULT 0x00
#define TB_BLACK 0x01
#define TB_RED 0x02
#define TB_GREEN 0x03
#define TB_YELLOW 0x04
#define TB_BLUE 0x05
#define TB_MAGENTA 0x06
#define TB_CYAN 0x07
#define TB_WHITE 0x08
#
# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool
# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with
# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond
# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark
# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright
# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio
# config) will be used by `ly` for `fg = 8`.
# Automatic login configuration
# This feature allows Ly to automatically log in a user without password prompt.
# IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work.
# Autologin only happens once at startup - it won't re-trigger after logout.
# PAM service name to use for automatic login
# The default service (ly-autologin) uses pam_permit to allow login without password
# The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically
auto_login_service = ly-autologin
# Session name to launch automatically
# To find available session names, check the .desktop files in:
# - /usr/share/xsessions/ (for X11 sessions)
# - /usr/share/wayland-sessions/ (for Wayland sessions)
# Use the filename without .desktop extension, or the value of DesktopNames field
# Examples: "i3", "sway", "gnome", "plasma", "xfce"
# If null, automatic login is disabled
auto_login_session = null
# Username to automatically log in
# Must be a valid user on the system
# If null, automatic login is disabled
auto_login_user = null
# Background color id
bg = 0
bg = 0x00000000
# Foreground color id
fg = 8
# Change the state and language of the big clock
# none -> Disabled (default)
# en -> English
# fa -> Farsi
bigclock = none
# Border color
border_fg = 8
# Set bigclock to 12-hour notation.
bigclock_12hr = false
# Title to show at the top of the main box
box_title = null
# Initial text to show on the info line (Defaults to hostname)
initial_info_text = null
# Set bigclock to show the seconds.
bigclock_seconds = false
# Blank main box background
# Setting to false will make it transparent
blank_box = true
# Remove main box borders
hide_borders = false
# Border foreground color id
border_fg = 0x00FFFFFF
# 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 decrease command
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%-
# Max input sizes
max_desktop_len = 100
max_login_len = 255
max_password_len = 255
# Brightness decrease key, or null to disable
brightness_down_key = F5
# Brightness increase command
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%
# Brightness increase key, or null to disable
brightness_up_key = F6
# Erase password input on failure
clear_password = false
# Format string for clock in top right corner (see strftime specification). Example: %c
# If null, the clock won't be shown
clock = null
# CMatrix animation foreground color id
cmatrix_fg = 0x0000FF00
# CMatrix animation character string head color id
cmatrix_head_col = 0x01FFFFFF
# CMatrix animation minimum codepoint. It uses a 16-bit integer
# For Japanese characters for example, you can use 0x3000 here
cmatrix_min_codepoint = 0x21
# CMatrix animation maximum codepoint. It uses a 16-bit integer
# For Japanese characters for example, you can use 0x30FF here
cmatrix_max_codepoint = 0x7B
# Color mixing animation first color id
colormix_col1 = 0x00FF0000
# Color mixing animation second color id
colormix_col2 = 0x000000FF
# Color mixing animation third color id
colormix_col3 = 0x20000000
# Custom sessions directory
# You can specify multiple directories,
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
custom_sessions = $CONFIG_DIRECTORY/ly/custom-sessions
# Input box active by default on startup
# Available inputs: session, login, password
# Available inputs: info_line, session, login, password
default_input = login
# Load the saved desktop and username
load = true
# DOOM animation fire height (1 thru 9)
doom_fire_height = 6
# Save the current desktop and login as defaults
save = true
# DOOM animation fire spread (0 thru 4)
doom_fire_spread = 2
# Deprecated - Will be removed in a future version
# New save files are now loaded from the same directory as the config
# Currently used to migrate old save files to the new version
# File in which to save and load the default desktop and login
save_file = /etc/ly/save
# DOOM animation custom top color (low intensity flames)
doom_top_color = 0x009F2707
# DOOM animation custom middle color (medium intensity flames)
doom_middle_color = 0x00C78F17
# DOOM animation custom bottom color (high intensity flames)
doom_bottom_color = 0x00FFFFFF
# Dur file path
dur_file_path = $CONFIG_DIRECTORY/ly/example.dur
# Dur offset x direction
dur_x_offset = 0
# Dur offset y direction
dur_y_offset = 0
# Set margin to the edges of the DM (useful for curved monitors)
edge_margin = 0
# Error background color id
error_bg = 0x00000000
# Error foreground color id
# Default is red and bold
error_fg = 0x01FF0000
# Foreground color id
fg = 0x00FFFFFF
# Render true colors (if supported)
# If false, output will be in eight-color mode
# All eight-color mode color codes:
# TB_DEFAULT 0x0000
# TB_BLACK 0x0001
# TB_RED 0x0002
# TB_GREEN 0x0003
# TB_YELLOW 0x0004
# TB_BLUE 0x0005
# TB_MAGENTA 0x0006
# TB_CYAN 0x0007
# TB_WHITE 0x0008
# If full color is off, the styling options still work. The colors are
# always 32-bit values with the styling in the most significant byte.
# Note: If using the dur_file animation option and the dur file's color range
# is saved as 256 with this option disabled, the file will not be drawn.
full_color = true
# Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations)
# 0 -> Pure Conway's Game of Life (will eventually stabilize)
# 10 -> Add entropy every 10 generations (recommended for continuous activity)
# 50+ -> Less frequent entropy for more natural evolution
gameoflife_entropy_interval = 10
# Game of Life animation foreground color id
gameoflife_fg = 0x0000FF00
# Game of Life frame delay (lower = faster animation, higher = slower)
# 1-3 -> Very fast animation
# 6 -> Default smooth animation speed
# 10+ -> Slower, more contemplative speed
gameoflife_frame_delay = 6
# Game of Life initial cell density (0.0 to 1.0)
# 0.1 -> Sparse, minimal activity
# 0.4 -> Balanced activity (recommended)
# 0.7+ -> Dense, chaotic patterns
gameoflife_initial_density = 0.4
# Command executed when pressing hibernate key (can be null)
hibernate_cmd = null
# Specifies the key used for hibernate (F1-F12)
hibernate_key = F4
# Remove main box borders
hide_borders = false
# Remove power management command hints
hide_key_hints = false
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# Remove keyboard lock states from the top right corner
hide_keyboard_locks = false
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Remove version number from the top left corner
hide_version_string = false
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# Command executed when no input is detected for a certain time
# If null, no command will be executed
inactivity_cmd = null
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown -a now
# Executes a command after a certain amount of seconds
inactivity_delay = 0
# Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now
# Initial text to show on the info line
# If set to null, the info line defaults to the hostname
initial_info_text = null
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# Input boxes length
input_len = 34
# Active language
# Available languages are found in /etc/ly/lang/
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
lang = en
# TTY in use
tty = 2
# 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
# Console path
console_dev = /dev/console
# Path for login.defs file (used for listing all local users on the system on
# Linux)
login_defs_path = /etc/login.defs
# Default path. If null, ly doesn't set a path.
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
# 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
# General log file path
ly_log = /var/log/ly.log
# Main box horizontal margin
margin_box_h = 2
# Main box vertical margin
margin_box_v = 1
# Event timeout in milliseconds
min_refresh_delta = 5
@@ -128,35 +292,76 @@ min_refresh_delta = 5
# Set numlock on/off at startup
numlock = false
# Default path
# If null, ly doesn't set a path
path = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/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, and load them on startup
save = true
# Service name (set to ly to use the provided pam config file)
service_name = ly
# Terminal reset command (tput is faster)
term_reset_cmd = /usr/bin/tput reset
# Session log file path
# This will contain stdout and stderr of Wayland sessions
# By default it's saved in the user's home directory
# Important: due to technical limitations, X11 and shell sessions aren't supported, which
# means you won't get any logs from those sessions.
# If null, no session log will be created
session_log = .local/state/ly-session.log
# Terminal restore cursor command
term_restore_cursor_cmd = /usr/bin/tput cnorm
# Setup command
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
# Cookie generator
mcookie_cmd = /usr/bin/mcookie
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown $PLATFORM_SHUTDOWN_ARG now
# Wayland setup command
wayland_cmd = /etc/ly/wsetup.sh
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
# Command executed when pressing sleep key (can be null)
sleep_cmd = null
# Specifies the key used for sleep (F1-F12)
sleep_key = F3
# Command executed when starting Ly (before the TTY is taken control of)
# If null, no command will be executed
start_cmd = null
# Center the session name.
text_in_center = false
# 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 (hidden if null)
xinitrc = ~/.xinitrc
# You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions
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
# You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
xsessions = $PREFIX_DIRECTORY/share/xsessions

View File

@@ -0,0 +1,23 @@
A custom session is just a desktop entry file, like for X11 and Wayland
sessions. For example:
[Desktop Entry]
Name=Fish shell
Exec=$PREFIX_DIRECTORY/bin/fish
DesktopNames=null
Terminal=true
The DesktopNames value is optional and sets the XDG_SESSION_DESKTOP and
XDG_CURRENT_DESKTOP environment variables. If equal to null or if not present,
XDG_SESSION_DESKTOP and XDG_CURRENT_DESKTOP will not be set. Otherwise, the
syntax is the same as described in the Freedesktop Desktop Entry Specification.
The Terminal value specifies if standard output and standard error should be
redirected to the session log file found in Ly's configuration file. If set to
true, Ly will consider the program is going to run in a TTY, and thus will not
redirect standard output & error. It is optional and defaults to false.
Finally, do note that, if the Terminal value is set to true, the
XDG_SESSION_TYPE environment variable will be set to "tty". Otherwise, it will
be set to "unspecified" (without quotes), which is behavior that at least
systemd recognizes (see pam_systemd's man page).

78
res/lang/ar.ini Normal file
View File

@@ -0,0 +1,78 @@
authenticating = جاري المصادقة...
brightness_down = خفض السطوع
brightness_up = رفع السطوع
capslock = capslock
err_alloc = فشل في تخصيص الذاكرة
err_bounds = out-of-bounds index
err_brightness_change = فشل في تغيير سطوع الشاشة
err_chdir = فشل في فتح مجلد المنزل
err_config = فشل في تفسير ملف الإعدادات
err_dgn_oob = رسالة سجل (Log)
err_domain = اسم نطاق غير صالح
err_empty_password = لا يُسمح بكلمة مرور فارغة
err_envlist = فشل في جلب قائمة المتغيرات البيئية
err_hostname = فشل في جلب اسم المضيف (Hostname)
err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock)
err_null = مؤشر فارغ (Null pointer)
err_numlock = فشل في ضبط Num Lock
err_pam = فشل في معاملة PAM
err_pam_abort = تم إلغاء معاملة PAM
err_pam_acct_expired = الحساب منتهي الصلاحية
err_pam_auth = خطأ في المصادقة (Authentication error)
err_pam_authinfo_unavail = فشل في الحصول على معلومات المستخدم
err_pam_authok_reqd = انتهت صلاحية رمز المصادقة (Token)
err_pam_buf = خطأ في ذاكرة التخزين المؤقت (Buffer)
err_pam_cred_err = فشل في تعيين بيانات الاعتماد (Credentials)
err_pam_cred_expired = بيانات الاعتماد منتهية الصلاحية
err_pam_cred_insufficient = بيانات الاعتماد غير كافية
err_pam_cred_unavail = فشل في الحصول على بيانات الاعتماد
err_pam_maxtries = تم بلوغ الحد الأقصى لمحاولات المصادقة
err_pam_perm_denied = تم رفض الوصول (Permission denied)
err_pam_session = خطأ في جلسة المستخدم (Session error)
err_pam_sys = خطأ في النظام (System error)
err_pam_user_unknown = المستخدم غير موجود
err_path = فشل في تعيين متغير PATH
err_perm_dir = فشل في تغيير المجلد الحالي
err_perm_group = فشل في تخفيض صلاحيات المجموعة (Group permissions)
err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions)
err_pwnam = فشل في جلب معلومات المستخدم
err_sleep = فشل في تنفيذ أمر sleep
err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY)
err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم
err_user_init = فشل في تهيئة بيانات المستخدم
err_user_uid = فشل في تعيين معرّف المستخدم (UID)
err_xauth = فشل في تنفيذ أمر xauth
err_xcb_conn = فشل في الاتصال بمكتبة XCB
err_xsessions_dir = فشل في العثور على مجلد Xsessions
err_xsessions_open = فشل في فتح مجلد Xsessions
insert = ادخال
login = تسجيل الدخول
logout = تم تسجيل خروجك
no_x11_support = تم تعطيل دعم x11 اثناء وقت الـ compile
normal = عادي
numlock = numlock
other = اخر
password = كلمة السر
restart = اعادة التشغيل
shell = shell
shutdown = ايقاف التشغيل
sleep = وضع السكون
wayland = wayland
x11 = x11
xinitrc = xinitrc

78
res/lang/bg.ini Normal file
View File

@@ -0,0 +1,78 @@
authenticating = удостоверяване...
brightness_down = намаляване на яркостта
brightness_up = увеличаване на яркостта
capslock = caps lock
custom = персонализирано
err_alloc = неуспешно заделяне на памет
err_args = неуспешен анализ на аргументите от командния ред
err_autologin_session = сесията за автоматично влизане не е намерена
err_bounds = индексът е извън границите
err_brightness_change = неуспешна промяна на яркостта
err_chdir = неуспешно отваряне на домашната папка
err_clock_too_long = низът на часовника е твърде дълъг
err_config = неуспешен анализ на конфигурационния файл
err_crawl = неуспешно обхождане на папките със сесии
err_dgn_oob = съобщение в дневника
err_domain = невалиден домейн
err_empty_password = не е позволена празна парола
err_envlist = неуспешно получаване на списъка с променливи на средата
err_get_active_tty = неуспешно откриване на активния TTY
err_hibernate = неуспешно изпълнение на командата за хибернация
err_hostname = неуспешно получаване на името на хоста
err_inactivity = неуспешно изпълнение на командата за неактивност
err_lock_state = неуспешно получаване на състоянието на заключване
err_log = неуспешно отваряне на файла с дневника
err_mlock = неуспешно заключване на паметта за паролата
err_null = нулев указател
err_numlock = неуспешно задаване на num lock
err_pam = неуспешна транзакция
err_pam_abort = прекратена транзакция
err_pam_acct_expired = изтекъл профил
err_pam_auth = грешка при удостоверяването
err_pam_authinfo_unavail = неуспешно получаване на информация за потребителя
err_pam_authok_reqd = изтекъл жетон
err_pam_buf = грешка в буфера на паметта
err_pam_cred_err = неуспешно задаване на удостоверения
err_pam_cred_expired = изтекли удостоверения
err_pam_cred_insufficient = недостатъчни удостоверения
err_pam_cred_unavail = неуспешно получаване на удостоверения
err_pam_maxtries = достигнат е максималният лимит на опитите
err_pam_perm_denied = достъпът е отказан
err_pam_session = грешка в сесията
err_pam_sys = системна грешка
err_pam_user_unknown = непознат потребител
err_path = неуспешно задаване на пътя
err_perm_dir = неуспешна смяна на текущата папка
err_perm_group = неуспешно понижаване на правата на групата
err_perm_user = неуспешно понижаване на правата на потребителя
err_pwnam = неуспешно получаване на информация за потребителя
err_sleep = неуспешно изпълнение на командата за заспиване
err_start = неуспешно изпълнение на командата за стартиране
err_battery = неуспешно зареждане на състоянието на батерията
err_switch_tty = неуспешна смяна на TTY
err_tty_ctrl = неуспешно прехвърляне на контрола над TTY
err_no_users = не са намерени потребители
err_uid_range = неуспешно динамично получаване на UID обхват
err_user_gid = неуспешно задаване на потребителския GID
err_user_init = неуспешна стартиране на потребителя
err_user_uid = неуспешно задаване на потребителския UID
err_xauth = неуспешна команда xauth
err_xcb_conn = неуспешна xcb връзка
err_xsessions_dir = папката със сесии не е намерена
err_xsessions_open = неуспешно отваряне на папката със сесии
hibernate = хибернация
insert = вмъкване
login = вход
logout = излизане
no_x11_support = поддръжката на x11 е изключена при компилирането
normal = нормално
numlock = num lock
other = друго
password = парола
restart = рестартиране
shell = обвивка
shutdown = изключване
sleep = заспиване
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -1,45 +1,78 @@
authenticating = autenticant...
brightness_down = abaixar brillantor
brightness_up = apujar brillantor
capslock = Bloq Majús
err_alloc = falla d'assignació de memòria
err_bounds = índex fora de límit
err_chdir = error al obrir carpeta home
err_console_dev = error al accedir a la consola
err_alloc = assignació de memòria fallida
err_bounds = índex fora de límits
err_brightness_change = error en canviar la brillantor
err_chdir = error en obrir la carpeta home
err_dgn_oob = missatge de registre
err_domain = domini invàlid
err_hostname = error al obtenir el nom del host
err_mlock = error al bloquejar la clau de memòria
err_envlist = error en obtenir l'envlist
err_hostname = error en obtenir el nom de l'amfitrió
err_mlock = error en bloquejar la memòria de clau
err_null = punter nul
err_numlock = error en establir el Bloq num
err_pam = error en la transacció pam
err_pam_abort = transacció pam avortada
err_pam_acct_expired = compte expirat
err_pam_auth = error d'autenticació
err_pam_authinfo_unavail = error al obtenir informació de l'usuari
err_pam_authinfo_unavail = error en obtenir la informació de l'usuari
err_pam_authok_reqd = token expirat
err_pam_buf = error de la memòria intermitja
err_pam_cred_err = error al establir les credencials
err_pam_buf = error en la memòria intermèdia
err_pam_cred_err = error en establir les credencials
err_pam_cred_expired = credencials expirades
err_pam_cred_insufficient = credencials insuficients
err_pam_cred_unavail = error al obtenir credencials
err_pam_maxtries = s'ha assolit al màxim nombre d'intents
err_pam_cred_unavail = error en obtenir credencials
err_pam_maxtries = s'ha assolit al nombre màxim d'intents
err_pam_perm_denied = permís denegat
err_pam_session = error de sessió
err_pam_sys = error de sistema
err_pam_user_unknown = usuari desconegut
err_path = error al establir la ruta
err_perm_dir = error al canviar de directori actual
err_perm_group = error al degradar els permisos de grup
err_perm_user = error al degradar els permisos de l'usuari
err_pwnam = error al obtenir la informació de l'usuari
err_user_gid = error al establir el GID de l'usuari
err_user_init = error al inicialitzar usuari
err_user_uid = error al establir el UID de l'usuari
err_xsessions_dir = error al cercar la carpeta de sessions
err_xsessions_open = error al obrir la carpeta de sessions
err_path = error en establir la ruta
err_perm_dir = error en canviar el directori actual
err_perm_group = error en degradar els permisos de grup
err_perm_user = error en degradar els permisos de l'usuari
err_pwnam = error en obtenir la informació de l'usuari
err_user_gid = error en establir el GID de l'usuari
err_user_init = error en inicialitzar usuari
err_user_uid = error en establir l'UID de l'usuari
err_xauth = error en la comanda xauth
err_xcb_conn = error en la connexió xcb
err_xsessions_dir = error en trobar la carpeta de sessions
err_xsessions_open = error en obrir la carpeta de sessions
insert = inserir
login = iniciar sessió
logout = tancar sessió
logout = sessió tancada
no_x11_support = el suport per x11 ha estat desactivat en la compilació
normal = normal
numlock = Bloq Num
password = Clau
restart = reiniciar
shell = shell
shutdown = aturar
sleep = suspendre
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = alokace paměti selhala
err_bounds = index je mimo hranice pole
err_chdir = nelze otevřít domovský adresář
err_console_dev = chyba při přístupu do konzole
err_dgn_oob = zpráva protokolu
err_domain = neplatná doména
err_hostname = nelze získat název hostitele
err_mlock = uzamčení paměti hesel selhalo
err_null = nulový ukazatel
err_pam = pam transakce selhala
err_pam_abort = pam transakce přerušena
err_pam_acct_expired = platnost účtu vypršela
@@ -29,17 +46,33 @@ err_perm_dir = nepodařilo se změnit adresář
err_perm_group = nepodařilo se snížit skupinová oprávnění
err_perm_user = nepodařilo se snížit uživatelská oprávnění
err_pwnam = nelze získat informace o uživateli
err_user_gid = nastavení GID uživatele selhalo
err_user_init = inicializace uživatele selhala
err_user_uid = nastavení UID uživateli selhalo
err_xsessions_dir = nepodařilo se najít složku relací
err_xsessions_open = nepodařilo se otevřít složku relací
login = uživatel
logout = odhlášen
numlock = numlock
password = heslo
restart = restartovat
shell = příkazový řádek
shutdown = vypnout
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,45 +1,78 @@
authenticating = authentifizieren...
brightness_down = Helligkeit-
brightness_up = Helligkeit+
capslock = Feststelltaste
err_alloc = Speicherzuweisung fehlgeschlagen
err_bounds = Listenindex ist außerhalb des Bereichs
err_chdir = Fehler beim oeffnen des home-ordners
err_console_dev = Zugriff auf die Konsole fehlgeschlagen
err_dgn_oob = Protokoll Nachricht
err_domain = Unzulaessige domain
err_hostname = Holen des Hostnames fehlgeschlagen
err_mlock = Abschließen des Passwortspeichers fehlgeschlagen
err_null = Null Zeiger
err_pam = pam Transaktion fehlgeschlagen
err_pam_abort = pam Transaktion abgebrochen
err_bounds = Index ausserhalb des Bereichs
err_brightness_change = Helligkeitsänderung fehlgeschlagen
err_chdir = Fehler beim Oeffnen des Home-Ordners
err_config = Fehler beim Verarbeiten der Konfigurationsdatei
err_dgn_oob = Diagnose-Nachricht
err_domain = Ungueltige Domain
err_empty_password = Leeres Passwort nicht zugelassen
err_envlist = Fehler beim Abrufen der Umgebungs-Variablen
err_hostname = Abrufen des Hostnames fehlgeschlagen
err_mlock = Sperren des Passwortspeichers fehlgeschlagen
err_null = Null Pointer
err_numlock = Numlock konnte nicht aktiviert werden
err_pam = PAM-Transaktion fehlgeschlagen
err_pam_abort = PAM-Transaktion abgebrochen
err_pam_acct_expired = Benutzerkonto abgelaufen
err_pam_auth = Authentifizierungs Fehler
err_pam_authinfo_unavail = holen der Benutzerinformationen fehlgeschlagen
err_pam_authok_reqd = Schluessel abgelaufen
err_pam_auth = Authentifizierungsfehler
err_pam_authinfo_unavail = Abrufen der Benutzerinformationen fehlgeschlagen
err_pam_authok_reqd = Passwort abgelaufen
err_pam_buf = Speicherpufferfehler
err_pam_cred_err = Fehler beim setzen der Anmeldedaten
err_pam_cred_err = Fehler beim Setzen der Anmeldedaten
err_pam_cred_expired = Anmeldedaten abgelaufen
err_pam_cred_insufficient = Anmeldedaten unzureichend
err_pam_cred_unavail = Fehler beim holen der Anmeldedaten
err_pam_maxtries = Maximale Versuche erreicht
err_pam_perm_denied = Zugriff Verweigert
err_pam_cred_unavail = Fehler beim Abrufen der Anmeldedaten
err_pam_maxtries = Maximale Versuchsanzahl erreicht
err_pam_perm_denied = Zugriff verweigert
err_pam_session = Sitzungsfehler
err_pam_sys = Systemfehler
err_pam_user_unknown = Unbekannter Nutzer
err_path = Fehler beim setzen des Pfades
err_perm_dir = Fehler beim wechseln des Ordners
err_perm_group = Fehler beim heruntersetzen der Gruppen Berechtigungen
err_perm_user = Fehler beim heruntersetzen der Nutzer Berechtigungen
err_pwnam = Holen der Benutzerinformationen fehlgeschlagen
err_user_gid = Fehler beim setzen der Gruppen Id des Nutzers
err_user_init = Initialisierung des Nutzers fehlgeschlagen
err_user_uid = Setzen der Benutzer Id fehlgeschlagen
err_xsessions_dir = Fehler beim finden des Sitzungsordners
err_xsessions_open = Fehler beim öffnen des Sitzungsordners
login = Anmelden
logout = Abgemeldet
numlock = Numtaste
err_path = Fehler beim Setzen des Pfades
err_perm_dir = Ordnerwechsel fehlgeschlagen
err_perm_group = Fehler beim Heruntersetzen der Gruppenberechtigungen
err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen
err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen
err_sleep = Sleep-Befehl fehlgeschlagen
err_tty_ctrl = Fehler bei der TTY-Uebergabe
err_user_gid = Fehler beim Setzen der Gruppen-ID
err_user_init = Nutzer-Initialisierung fehlgeschlagen
err_user_uid = Setzen der Benutzer-ID fehlgeschlagen
err_xauth = Xauth-Befehl fehlgeschlagen
err_xcb_conn = xcb-Verbindung fehlgeschlagen
err_xsessions_dir = Fehler beim Finden des Sitzungsordners
err_xsessions_open = Fehler beim Oeffnen des Sitzungsordners
insert = Einfügen
login = Nutzer
logout = Abmelden
no_x11_support = X11-Support bei Kompilierung deaktiviert
normal = Normal
numlock = Numlock
other = Andere
password = Passwort
restart = Neustarten
shell = shell
shell = Shell
shutdown = Herunterfahren
sleep = Sleep
wayland = wayland
x11 = X11
xinitrc = xinitrc

View File

@@ -1,16 +1,30 @@
authenticating = authenticating...
brightness_down = decrease brightness
brightness_up = increase brightness
capslock = capslock
custom = custom
err_alloc = failed memory allocation
err_args = unable to parse command line arguments
err_autologin_session = autologin session not found
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_clock_too_long = clock string too long
err_config = unable to parse config file
err_crawl = failed to crawl session directories
err_dgn_oob = log message
err_domain = invalid domain
err_empty_password = empty password not allowed
err_envlist = failed to get envlist
err_get_active_tty = failed to get active tty
err_hibernate = failed to execute hibernate command
err_hostname = failed to get hostname
err_mcookie = mcookie command failed
err_inactivity = failed to execute inactivity command
err_lock_state = failed to get lock state
err_log = failed to open log file
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
@@ -32,7 +46,13 @@ 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_sleep = failed to execute sleep command
err_start = failed to execute start command
err_battery = failed to load battery status
err_switch_tty = failed to switch tty
err_tty_ctrl = tty control transfer failed
err_no_users = no users found
err_uid_range = failed to dynamically get uid range
err_user_gid = failed to set user GID
err_user_init = failed to initialize user
err_user_uid = failed to set user UID
@@ -40,16 +60,19 @@ 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
hibernate = hibernate
insert = insert
login = login
logout = logged out
no_x11_support = x11 support disabled at compile-time
normal = normal
numlock = numlock
other = other
password = password
restart = reboot
shell = shell
shutdown = shutdown
sleep = sleep
wayland = wayland
xinitrc = xinitrc
x11 = x11
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
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
err_chdir = error al abrir la carpeta home
err_console_dev = error al acceder a la consola
err_dgn_oob = mensaje de registro
err_domain = dominio inválido
err_hostname = error al obtener el nombre de host
err_mlock = error al bloquear la contraseña de memoria
err_null = puntero nulo
err_pam = error en la transacción pam
err_pam_abort = transacción pam abortada
err_pam_acct_expired = cuenta expirada
@@ -29,17 +46,33 @@ err_perm_dir = error al cambiar el directorio actual
err_perm_group = error al degradar los permisos del grupo
err_perm_user = error al degradar los permisos del usuario
err_pwnam = error al obtener la información del usuario
err_user_gid = error al establecer el GID del usuario
err_user_init = 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
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,36 @@
capslock = verr.maj
authenticating = authentification...
brightness_down = diminuer la luminosité
brightness_up = augmenter la luminosité
capslock = verr.maj
custom = customisé
err_alloc = échec d'allocation mémoire
err_args = échec de l'analyse des arguments en lignes de commande
err_autologin_session = session de connexion automatique introuvable
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_clock_too_long = chaîne de formattage de l'horloge trop longue
err_config = échec de lecture du fichier de configuration
err_crawl = échec de la navigation des répertoires de session
err_dgn_oob = message
err_domain = domaine invalide
err_empty_password = mot de passe vide non autorisé
err_envlist = échec de lecture de la liste d'environnement
err_get_active_tty = échec de lecture du terminal actif
err_hibernate = échec de l'exécution de la commande de veille prolongée
err_hostname = échec de lecture du nom d'hôte
err_inactivity = échec de l'exécution de la commande d'inactivité
err_lock_state = échec de lecture de l'état de verrouillage
err_log = échec de l'ouverture du fichier de journal
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 +46,33 @@ 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_sleep = échec de l'exécution de la commande de veille
err_start = échec de l'exécution de la commande de démarrage
err_battery = échec de lecture de l'état de la batterie
err_switch_tty = échec du changement de terminal
err_tty_ctrl = échec du transfert de contrôle du terminal
err_no_users = aucun utilisateur trouvé
err_uid_range = échec de récupération dynamique de la plage d'UID
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
hibernate = veille prolongée
insert = insertion
login = identifiant
logout = déconnection
logout = déconnecté
no_x11_support = support pour x11 désactivé lors de la compilation
normal = normal
numlock = verr.num
other = autre
password = mot de passe
restart = redémarrer
shell = shell
shutdown = éteindre
sleep = veille
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = impossibile allocare memoria
err_bounds = indice fuori limite
err_chdir = impossibile aprire home directory
err_console_dev = impossibile aprire console
err_dgn_oob = messaggio log
err_domain = dominio non valido
err_hostname = impossibile ottenere hostname
err_mlock = impossibile ottenere lock per la password in memoria
err_null = puntatore nullo
err_pam = transazione PAM fallita
err_pam_abort = transazione PAM interrotta
err_pam_acct_expired = account scaduto
@@ -29,17 +46,33 @@ err_perm_dir = impossibile cambiare directory corrente
err_perm_group = impossibile ridurre permessi gruppo
err_perm_user = impossibile ridurre permessi utente
err_pwnam = impossibile ottenere dati utente
err_user_gid = impossibile impostare GID utente
err_user_init = impossibile inizializzare utente
err_user_uid = impossible impostare UID utente
err_xsessions_dir = impossibile localizzare cartella sessioni
err_xsessions_open = impossibile aprire cartella sessioni
login = username
logout = scollegato
numlock = numlock
password = password
restart = riavvio
shell = shell
shutdown = arresto
wayland = wayland
xinitrc = xinitrc

78
res/lang/ja_JP.ini Normal file
View File

@@ -0,0 +1,78 @@
authenticating = 認証中...
brightness_down = 明るさを下げる
brightness_up = 明るさを上げる
capslock = CapsLock
err_alloc = メモリ割り当て失敗
err_bounds = 境界外インデックス
err_brightness_change = 明るさの変更に失敗しました
err_chdir = ホームフォルダを開けませんでした
err_config = 設定ファイルを解析できません
err_dgn_oob = ログメッセージ
err_domain = 無効なドメイン
err_empty_password = 空のパスワードは許可されていません
err_envlist = 環境変数リストの取得に失敗しました
err_hostname = ホスト名の取得に失敗しました
err_mlock = パスワードメモリのロックに失敗しました
err_null = ヌルポインタ
err_numlock = NumLockの設定に失敗しました
err_pam = PAMトランザクション失敗
err_pam_abort = PAMトランザクションが中断されました
err_pam_acct_expired = アカウントの有効期限が切れています
err_pam_auth = 認証エラー
err_pam_authinfo_unavail = ユーザー情報の取得に失敗しました
err_pam_authok_reqd = トークンの有効期限が切れています
err_pam_buf = メモリバッファエラー
err_pam_cred_err = 認証情報の設定に失敗しました
err_pam_cred_expired = 認証情報の有効期限が切れています
err_pam_cred_insufficient = 認証情報が不十分です
err_pam_cred_unavail = 認証情報の取得に失敗しました
err_pam_maxtries = 最大試行回数に到達しました
err_pam_perm_denied = アクセスが拒否されました
err_pam_session = セッションエラー
err_pam_sys = システムエラー
err_pam_user_unknown = 不明なユーザー
err_path = パスの設定に失敗しました
err_perm_dir = カレントディレクトリの変更に失敗しました
err_perm_group = グループ権限のダウングレードに失敗しました
err_perm_user = ユーザー権限のダウングレードに失敗しました
err_pwnam = ユーザー情報の取得に失敗しました
err_sleep = スリープコマンドの実行に失敗しました
err_tty_ctrl = TTY制御の転送に失敗しました
err_user_gid = ユーザーGIDの設定に失敗しました
err_user_init = ユーザーの初期化に失敗しました
err_user_uid = ユーザーUIDの設定に失敗しました
err_xauth = xauthコマンドの実行に失敗しました
err_xcb_conn = XCB接続に失敗しました
err_xsessions_dir = セッションフォルダが見つかりませんでした
err_xsessions_open = セッションフォルダを開けませんでした
insert = 挿入
login = ログイン
logout = ログアウト済み
no_x11_support = X11サポートはコンパイル時に無効化されています
normal = 通常
numlock = NumLock
other = その他
password = パスワード
restart = 再起動
shell = シェル
shutdown = シャットダウン
sleep = スリープ
wayland = Wayland
x11 = X11
xinitrc = xinitrc

78
res/lang/lv.ini Normal file
View File

@@ -0,0 +1,78 @@
authenticating = autentificējas...
brightness_down = samazināt spilgtumu
brightness_up = palielināt spilgtumu
capslock = caps lock
custom = pielāgots
err_alloc = neizdevās atmiņas piešķiršana
err_bounds = indekss ārpus robežām
err_brightness_change = neizdevās mainīt spilgtumu
err_chdir = neizdevās atvērt mājas mapi
err_clock_too_long = pulksteņa virkne pārāk gara
err_config = neizdevās parsēt konfigurācijas failu
err_dgn_oob = žurnāla ziņojums
err_domain = nederīgs domēns
err_empty_password = tukša parole nav atļauta
err_envlist = neizdevās iegūt vides mainīgo sarakstu
err_get_active_tty = neizdevās iegūt aktīvo tty
err_hostname = neizdevās iegūt hostname
err_lock_state = neizdevās iegūt bloķēšanas stāvokli
err_log = neizdevās atvērt žurnāla failu
err_mlock = neizdevās bloķēt paroles atmiņu
err_null = null rādītājs
err_numlock = neizdevās iestatīt numlock
err_pam = pam transakcija neizdevās
err_pam_abort = pam transakcija pārtraukta
err_pam_acct_expired = konts novecojis
err_pam_auth = autentifikācijas kļūda
err_pam_authinfo_unavail = neizdevās iegūt lietotāja informāciju
err_pam_authok_reqd = žetons beidzies
err_pam_buf = atmiņas bufera kļūda
err_pam_cred_err = neizdevās iestatīt akreditācijas datus
err_pam_cred_expired = akreditācijas dati novecojuši
err_pam_cred_insufficient = nepietiekami akreditācijas dati
err_pam_cred_unavail = neizdevās iegūt akreditācijas datus
err_pam_maxtries = sasniegts maksimālais mēģinājumu skaits
err_pam_perm_denied = piekļuve liegta
err_pam_session = sesijas kļūda
err_pam_sys = sistēmas kļūda
err_pam_user_unknown = nezināms lietotājs
err_path = neizdevās iestatīt ceļu
err_perm_dir = neizdevās mainīt pašreizējo mapi
err_perm_group = neizdevās pazemināt grupas atļaujas
err_perm_user = neizdevās pazemināt lietotāja atļaujas
err_pwnam = neizdevās iegūt lietotāja informāciju
err_sleep = neizdevās izpildīt miega komandu
err_battery = neizdevās ielādēt akumulatora stāvokli
err_switch_tty = neizdevās pārslēgt tty
err_tty_ctrl = tty vadības nodošana neizdevās
err_no_users = lietotāji nav atrasti
err_user_gid = neizdevās iestatīt lietotāja GID
err_user_init = neizdevās inicializēt lietotāju
err_user_uid = neizdevās iestatīt lietotāja UID
err_xauth = xauth komanda neizdevās
err_xcb_conn = xcb savienojums neizdevās
err_xsessions_dir = neizdevās atrast sesiju mapi
err_xsessions_open = neizdevās atvērt sesiju mapi
insert = ievietot
login = lietotājs
logout = iziet
no_x11_support = x11 atbalsts atspējots kompilācijas laikā
normal = parastais
numlock = numlock
other = cits
password = parole
restart = restartēt
shell = terminālis
shutdown = izslēgt
sleep = snauda
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
from pathlib import Path
from sys import stderr
def process_lang_file(path: Path, lang_keys: list[str]) -> None:
# read key-value-pairs from lang file into dict
existing_entries = {}
with open(path, "r", encoding="UTF-8") as fh:
while line := fh.readline():
try:
key, value = line.split("=", 1)
existing_entries[key.strip()] = value.strip()
except ValueError: # line does not contain '='
continue
# re-write current lang file with entries in order of occurence in `lang_keys`
# and with empty lines for missing translations
with open(path, "w", encoding="UTF-8") as fh:
for item in lang_keys:
try:
fh.write(f"{item} = {existing_entries[item]}\n")
except KeyError: # no translation for `item` yet
fh.write("\n")
def main() -> None:
zig_lang_file = Path(__file__).parent.joinpath("../../src/config/Lang.zig").resolve()
if not zig_lang_file.exists():
print(f"ERROR: File '{zig_lang_file.as_posix()}' does not exist. Exiting.", file=stderr)
exit(1)
# read "language keys" from `zig_lang_file` into list
lang_keys = []
with open(zig_lang_file, "r", encoding="UTF-8") as fh:
while line := fh.readline():
# only process lines that are not empty or no comments
if not (line.strip() == "" or line.startswith("//")):
lang_keys.append(line.split(":")[0].strip())
lang_files = [f for f in Path.iterdir(Path(__file__).parent) if f.name.endswith(".ini") and f.is_file()]
for file in lang_files:
process_lang_file(file, lang_keys)
if __name__ == "__main__":
main()

View File

@@ -1,45 +1,78 @@
authenticating = uwierzytelnianie...
brightness_down = zmniejsz jasność
brightness_up = zwiększ jasność
capslock = capslock
custom = własny
err_alloc = nieudana alokacja pamięci
err_bounds = indeks poza granicami
err_autologin_session = nie znaleziono sesji autologowania
err_bounds = indeks poza zakresem
err_brightness_change = nie udało się zmienić jasności
err_chdir = nie udało się otworzyć folderu domowego
err_console_dev = nie udało się uzyskać dostępu do konsoli
err_clock_too_long = ciąg znaków zegara jest za długi
err_config = nie można przetworzyć pliku konfiguracyjnego
err_dgn_oob = wiadomość loga
err_domain = niepoprawna domena
err_empty_password = puste hasło jest niedozwolone
err_envlist = nie udało się pobrać listy zmiennych środowiskowych
err_get_active_tty = nie udało się uzyskać aktywnego tty
err_hostname = nie udało się uzyskać nazwy hosta
err_lock_state = nie udało się uzyskać stanu blokady
err_log = nie udało się otworzyć pliku logu
err_mlock = nie udało się zablokować pamięci haseł
err_null = wskaźnik zerowy
err_null = pusty wskaźnik
err_numlock = nie udało się ustawić numlock
err_pam = transakcja pam nieudana
err_pam_abort = transakcja pam przerwana
err_pam_acct_expired = konto wygasło
err_pam_auth = błąd autentyfikacji
err_pam_auth = błąd uwierzytelniania
err_pam_authinfo_unavail = nie udało się zdobyć informacji o użytkowniku
err_pam_authok_reqd = token wygasł
err_pam_buf = błąd bufora pamięci
err_pam_cred_err = nie udało się ustawić uwierzytelnienia
err_pam_cred_err = nie udało się ustawić uwierzytelnienia
err_pam_cred_expired = uwierzytelnienie wygasło
err_pam_cred_insufficient = niewystarczające uwierzytelnienie
err_pam_cred_unavail = nie udało się uzyskać uwierzytelnienia
err_pam_cred_unavail = nie udało się uzyskać uwierzytelnienia
err_pam_maxtries = osiągnięto limit prób
err_pam_perm_denied = brak uprawnień
err_pam_perm_denied = odmowa dostępu
err_pam_session = błąd sesji
err_pam_sys = błąd systemu
err_pam_user_unknown = nieznany użytkownik
err_path = nie udało się ustawić ścieżki
err_perm_dir = nie udało się zmienić obecnego katalogu
err_perm_group = nie udało się obniżyć uprawnień grupy
err_perm_user = nie udało się obniżyć uprawnień użytkownika
err_pwnam = nie udało się uzyskać informacji o użytkowniku
err_path = nie udało się ustawić ścieżki
err_perm_dir = nie udało się zmienić obecnego katalogu
err_perm_group = nie udało się obniżyć uprawnień grupy
err_perm_user = nie udało się obniżyć uprawnień użytkownika
err_pwnam = nie udało się uzyskać informacji o użytkowniku
err_sleep = nie udało się wykonać polecenia sleep
err_battery = nie udało się sprawdzić statusu baterii
err_switch_tty = nie można przełączyć tty
err_tty_ctrl = nie udało się przekazać kontroli tty
err_no_users = nie znaleziono żadnego użytkownika
err_user_gid = nie udało się ustawić GID użytkownika
err_user_init = nie udało się zainicjalizować użytkownika
err_user_init = nie udało się zainicjalizować użytkownika
err_user_uid = nie udało się ustawić UID użytkownika
err_xsessions_dir = nie udało się znaleźć folderu sesji
err_xsessions_open = nie udało się otworzyć folderu sesji
err_xauth = polecenie xauth nie powiodło się
err_xcb_conn = połączenie xcb nie powiodło się
err_xsessions_dir = nie udało się znaleźć folderu sesji
err_xsessions_open = nie udało się otworzyć folderu sesji
insert = wstaw
login = login
logout = wylogowano
no_x11_support = wsparcie X11 wyłączone podczas kompilacji
normal = normalny
numlock = numlock
other = inny
password = hasło
restart = uruchom ponownie
shell = powłoka
shutdown = wyłącz
sleep = uśpij
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = erro na atribuição de memória
err_bounds = índice fora de limites
err_chdir = erro ao abrir a pasta home
err_console_dev = erro ao aceder à consola
err_dgn_oob = mensagem de registo
err_domain = domínio inválido
err_hostname = erro ao obter o nome do host
err_mlock = erro de bloqueio de memória
err_null = ponteiro nulo
err_pam = erro na transação pam
err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada
@@ -29,17 +46,33 @@ err_perm_dir = erro ao alterar o diretório atual
err_perm_group = erro ao reduzir as permissões do grupo
err_perm_user = erro ao reduzir as permissões do utilizador
err_pwnam = erro ao obter informação do utilizador
err_user_gid = erro ao definir o GID do utilizador
err_user_init = erro ao iniciar o utilizador
err_user_uid = erro ao definir o UID do utilizador
err_xsessions_dir = erro ao localizar a pasta das sessões
err_xsessions_open = erro ao abrir a pasta das sessões
login = iniciar sessão
logout = terminar sessão
numlock = numlock
password = palavra-passe
restart = reiniciar
shell = shell
shutdown = encerrar
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = caixa alta
err_alloc = alocação de memória malsucedida
err_bounds = índice fora de limites
err_chdir = não foi possível abrir o diretório home
err_console_dev = não foi possível acessar o console
err_dgn_oob = mensagem de log
err_domain = domínio inválido
err_hostname = não foi possível obter o nome do host
err_mlock = bloqueio da memória de senha malsucedido
err_null = ponteiro nulo
err_pam = transação pam malsucedida
err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada
@@ -29,17 +46,33 @@ err_perm_dir = não foi possível alterar o diretório atual
err_perm_group = não foi possível reduzir as permissões de grupo
err_perm_user = não foi possível reduzir as permissões de usuário
err_pwnam = não foi possível obter informações do usuário
err_user_gid = não foi possível definir o GID do usuário
err_user_init = não foi possível iniciar o usuário
err_user_uid = não foi possível definir o UID do usuário
err_xsessions_dir = não foi possível encontrar a pasta das sessões
err_xsessions_open = não foi possível abrir a pasta das sessões
login = conectar
logout = desconectado
numlock = numlock
password = senha
restart = reiniciar
shell = shell
shutdown = desligar
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,8 +1,25 @@
capslock = capslock
err_console_dev = nu s-a putut accesa consola
@@ -34,12 +51,28 @@ err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator
login = utilizator
logout = opreşte sesiunea
numlock = numlock
password = parolă
restart = resetează
shell = shell
shutdown = opreşte sistemul
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
authenticating = аутентификация...
brightness_down = уменьшить яркость
brightness_up = увеличить яркость
capslock = capslock
custom = пользовательский
err_alloc = не удалось выделить память
err_autologin_session = не найдена сессия с автологином
err_bounds = за пределами индекса
err_brightness_change = не удалось изменить яркость
err_chdir = не удалось открыть домашнюю папку
err_console_dev = не удалось получить доступ к консоли
err_clock_too_long = строка часов слишком длинная
err_config = не удалось разобрать файл конфигурации
err_dgn_oob = отладочное сообщение (log)
err_domain = неверный домен
err_empty_password = пустой пароль не допустим
err_envlist = не удалось получить список переменных среды
err_get_active_tty = не удалось получить активный tty
err_hostname = не удалось получить имя хоста
err_lock_state = не удалось получить состояние lock
err_log = не удалось открыть файл log
err_mlock = сбой блокировки памяти
err_null = нулевой указатель
err_numlock = не удалось установить numlock
err_pam = pam транзакция не удалась
err_pam_abort = pam транзакция прервана
err_pam_acct_expired = срок действия аккаунта истёк
@@ -29,17 +46,33 @@ err_perm_dir = не удалось изменить текущий катало
err_perm_group = не удалось понизить права доступа группы
err_perm_user = не удалось понизить права доступа пользователя
err_pwnam = не удалось получить информацию о пользователе
err_sleep = не удалось выполнить команду sleep
err_battery = не удалось получить статус батареи
err_switch_tty = не удалось переключить tty
err_tty_ctrl = передача управления tty не удалась
err_no_users = пользователи не найдены
err_user_gid = не удалось установить GID пользователя
err_user_init = не удалось инициализировать пользователя
err_user_uid = не удалось установить UID пользователя
err_xauth = команда xauth не выполнена
err_xcb_conn = ошибка подключения xcb
err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку
insert = вставка
login = логин
logout = logged out
logout = вышел из системы
no_x11_support = поддержка x11 отключена во время компиляции
normal = обычный
numlock = numlock
other = прочие
password = пароль
restart = перезагрузить
shell = shell
shell = оболочка
shutdown = выключить
sleep = сон
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = neuspijesna alokacija memorije
err_bounds = izvan granica indeksa
err_chdir = neuspijesno otvaranje home foldera
err_console_dev = neuspijesno pristupanje konzoli
err_dgn_oob = log poruka
err_domain = nevazeci domen
err_hostname = neuspijesno trazenje hostname-a
err_mlock = neuspijesno zakljucavanje memorije lozinke
err_null = null pokazivac
err_pam = pam transakcija neuspijesna
err_pam_abort = pam transakcija prekinuta
err_pam_acct_expired = nalog istekao
@@ -25,21 +42,37 @@ err_pam_session = greska sesije
err_pam_sys = greska sistema
err_pam_user_unknown = nepoznat korisnik
err_path = neuspjelo postavljanje path-a
err_perm_dir = neuspjelo mijenjanje foldera
err_perm_dir = neuspjelo mijenjanje foldera
err_perm_group = neuspjesno snizavanje dozvola grupe
err_perm_user = neuspijesno snizavanje dozvola korisnika
err_pwnam = neuspijesno skupljanje informacija o korisniku
err_user_gid = neuspijesno postavljanje korisničkog GID-a
err_user_init = neuspijensa inicijalizacija korisnika
err_user_uid = neuspijesno postavljanje UID-a korisnika
err_xsessions_dir = neuspijesno pronalazenje foldera sesija
err_xsessions_open = neuspijesno otvaranje foldera sesija
err_xsessions_open = neuspijesno otvaranje foldera sesija
login = korisnik
logout = izlogovan
numlock = numlock
password = lozinka
restart = ponovo pokreni
shell = shell
shutdown = ugasi
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = misslyckad minnesallokering
err_bounds = utanför banan index
err_chdir = misslyckades att öppna hemkatalog
err_console_dev = misslyckades att komma åt konsol
err_dgn_oob = loggmeddelande
err_domain = okänd domän
err_hostname = misslyckades att hämta värdnamn
err_mlock = misslyckades att låsa lösenordsminne
err_null = nullpekare
err_pam = pam-transaktion misslyckades
err_pam_abort = pam-transaktion avbröts
err_pam_acct_expired = konto upphört
@@ -29,17 +46,33 @@ err_perm_dir = misslyckades att ändra aktuell katalog
err_perm_group = misslyckades att nergradera gruppbehörigheter
err_perm_user = misslyckades att nergradera användarbehörigheter
err_pwnam = misslyckades att hämta användarinfo
err_user_gid = misslyckades att ställa in användar-GID
err_user_init = misslyckades att initialisera användaren
err_user_uid = misslyckades att ställa in användar-UID
err_xsessions_dir = misslyckades att hitta sessionskatalog
err_xsessions_open = misslyckades att öppna sessionskatalog
login = inloggning
logout = utloggad
numlock = numlock
password = lösenord
restart = starta om
shell = skal
shutdown = stäng av
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = basarisiz bellek ayirma
err_bounds = sinirlarin disinda dizin
err_chdir = ev klasoru acilamadi
err_console_dev = konsola erisilemedi
err_dgn_oob = log mesaji
err_domain = gecersiz etki alani
err_hostname = ana bilgisayar adi alinamadi
err_mlock = parola bellegi kilitlenemedi
err_null = bos isaretci hatasi
err_pam = pam islemi basarisiz oldu
err_pam_abort = pam islemi durduruldu
err_pam_acct_expired = hesabin suresi dolmus
@@ -29,17 +46,33 @@ err_perm_dir = gecerli dizin degistirilemedi
err_perm_group = grup izinleri dusurulemedi
err_perm_user = kullanici izinleri dusurulemedi
err_pwnam = kullanici bilgileri alinamadi
err_user_gid = kullanici icin GID ayarlanamadi
err_user_init = kullanici oturumu baslatilamadi
err_user_uid = kullanici icin UID ayarlanamadi
err_xsessions_dir = oturumlar klasoru bulunamadi
err_xsessions_open = oturumlar klasoru acilamadi
login = kullanici
logout = oturumdan cikis yapildi
numlock = numlock
password = sifre
restart = yeniden baslat
shell = shell
shutdown = makineyi kapat
wayland = wayland
xinitrc = xinitrc

View File

@@ -1,13 +1,30 @@
capslock = capslock
err_alloc = невдале виділення пам'яті
err_bounds = поза межами індексу
err_chdir = не вдалося відкрити домашній каталог
err_console_dev = невдалий доступ до консолі
err_dgn_oob = повідомлення журналу (log)
err_domain = недійсний домен
err_hostname = не вдалося отримати ім'я хосту
err_mlock = збій блокування пам'яті
err_null = нульовий вказівник
err_pam = невдала pam транзакція
err_pam_abort = pam транзакція перервана
err_pam_acct_expired = термін дії акаунту вичерпано
@@ -29,17 +46,33 @@ err_perm_dir = не вдалося змінити поточний катало
err_perm_group = не вдалося понизити права доступу групи
err_perm_user = не вдалося понизити права доступу користувача
err_pwnam = не вдалося отримати дані користувача
err_user_gid = не вдалося змінити GID користувача
err_user_init = не вдалося ініціалізувати користувача
err_user_uid = не вдалося змінити UID користувача
err_xsessions_dir = не вдалося знайти каталог сесій
err_xsessions_open = не вдалося відкрити каталог сесій
login = логін
logout = вийти
numlock = numlock
password = пароль
restart = перезавантажити
shell = оболонка
shutdown = вимкнути
wayland = wayland
xinitrc = xinitrc

78
res/lang/zh_CN.ini Normal file
View File

@@ -0,0 +1,78 @@
capslock = 大写锁定
err_alloc = 内存分配失败
err_bounds = 索引越界
err_chdir = 无法打开home文件夹
err_dgn_oob = 日志消息
err_domain = 无效的域
err_hostname = 获取主机名失败
err_mlock = 锁定密码存储器失败
err_null = 空指针
err_pam = PAM事件失败
err_pam_abort = PAM事务已中止
err_pam_acct_expired = 帐户已过期
err_pam_auth = 身份验证错误
err_pam_authinfo_unavail = 获取用户信息失败
err_pam_authok_reqd = 口令已过期
err_pam_buf = 内存缓冲区错误
err_pam_cred_err = 设置凭据失败
err_pam_cred_expired = 凭据已过期
err_pam_cred_insufficient = 凭据不足
err_pam_cred_unavail = 无法获取凭据
err_pam_maxtries = 已达到最大尝试次数限制
err_pam_perm_denied = 拒绝访问
err_pam_session = 会话错误
err_pam_sys = 系统错误
err_pam_user_unknown = 未知用户
err_path = 无法设置路径
err_perm_dir = 更改当前目录失败
err_perm_group = 组权限降级失败
err_perm_user = 用户权限降级失败
err_pwnam = 获取用户信息失败
err_user_gid = 设置用户GID失败
err_user_init = 初始化用户失败
err_user_uid = 设置用户UID失败
err_xsessions_dir = 找不到会话文件夹
err_xsessions_open = 无法打开会话文件夹
login = 登录
logout = 注销
numlock = 数字锁定
password = 密码
shell = shell
wayland = wayland
x11 = x11
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/$EXECUTABLE_NAME
depends-on = login.target
termsignal = HUP
# ly needs access to the console while login.target already occupies it
options = shares-console

7
res/ly-freebsd-wrapper Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# On FreeBSD, even if we override the default login program, getty will still
# try to append "login -fp root" as arguments to Ly, which is not supported.
# To avoid this, we use a wrapper script that ignores these arguments before
# actually executing Ly.
exec $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME

View File

@@ -19,17 +19,13 @@ then
commandUL="/sbin/agetty"
fi
## Get the tty from the conf file
CONFTTY=$(cat /etc/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
## The execution vars
# If CONFTTY is empty then default to 2
TTY="tty${CONFTTY:-2}"
TTY="tty$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

@@ -7,6 +7,4 @@ 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}
TTY=tty$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/$EXECUTABLE_NAME tty$DEFAULT_TTY 115200

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

@@ -0,0 +1 @@
longrun

65
res/ly-sysvinit Executable file
View File

@@ -0,0 +1,65 @@
#!/bin/sh
### BEGIN INIT INFO
# Provides: ly
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Ly display manager
# Description: Starts and stops the Ly display manager
### END INIT INFO
#
# Author: AnErrupTion <anerruption@disroot.org>
#
PATH=/sbin:/usr/sbin:/bin:/usr/bin
DAEMON=/usr/bin/ly
TTY=/dev/tty$DEFAULT_TTY
PIDFILE=/var/run/ly.pid
NAME=ly
DESC="Ly display manager"
. /lib/lsb/init-functions
case "$1" in
start)
log_daemon_msg "Starting $DESC on $TTY..."
if [ -f "$PIDFILE" ]; then
log_progress_msg "$DESC is already running"
log_end_msg 0
return 0
fi
# Ensure TTY exists
[ -c "$TTY" ] || {
log_failure_msg "$TTY does not exist"
return 1
}
start-stop-daemon --start --background --make-pidfile --pidfile $PIDFILE \
--chdir / --exec /bin/sh -- -c "exec setsid sh -c 'exec <$TTY >$TTY 2>&1 $DAEMON'"
log_end_msg $?
;;
stop)
log_daemon_msg "Stopping $DESC..."
start-stop-daemon --stop --pidfile $PIDFILE --retry 5
RETVAL=$?
[ $RETVAL -eq 0 ] && rm -f "$PIDFILE"
log_end_msg $RETVAL
;;
restart)
echo "Restarting $DESC..."
$0 stop
sleep 1
$0 start
;;
status)
status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
;;
*)
echo "Usage: /etc/init.d/$NAME {start|stop|restart|status}"
exit 1
;;
esac
exit 0

View File

@@ -1,17 +1,16 @@
[Unit]
Description=TUI display manager
After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@tty2.service
Conflicts=getty@tty2.service
After=getty@%i.service
Conflicts=getty@%i.service
[Service]
Type=idle
ExecStart=/usr/bin/ly
StandardError=journal
ExecStart=$PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
StandardInput=tty
TTYPath=/dev/tty2
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
[Install]
Alias=display-manager.service
WantedBy=multi-user.target

View File

@@ -1,5 +1,7 @@
#%PAM-1.0
# OpenPAM (used in FreeBSD) doesn't support prepending "-" for ignoring missing
# modules.
auth include login
account include login
password include login

View File

@@ -0,0 +1,9 @@
#%PAM-1.0
# OpenPAM (used in FreeBSD) doesn't support prepending "-" for ignoring missing
# modules.
auth required pam_permit.so
auth include login
account include login
password include login
session include login

16
res/pam.d/ly-linux Normal file
View File

@@ -0,0 +1,16 @@
#%PAM-1.0
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

View File

@@ -0,0 +1,16 @@
#%PAM-1.0
auth required pam_permit.so
-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 [ -f "$USERXSESSION" ]; then
. "$USERXSESSION"
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
fi
exec "$@"

View File

@@ -1,31 +0,0 @@
{
pam
Memcheck:Leak
...
obj:/usr/lib/libpam.so.0.84.2
...
}
{
termbox
Memcheck:Leak
...
fun:tb_init
...
}
{
libc/dynamic
Memcheck:Leak
...
fun:_dl_catch_exception
...
}
{
libc/groups
Memcheck:Leak
...
fun:initgroups
...
}

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

24
src/Environment.zig Normal file
View File

@@ -0,0 +1,24 @@
const enums = @import("enums.zig");
const ini = @import("zigini");
const DisplayServer = enums.DisplayServer;
const Ini = ini.Ini;
pub const DesktopEntry = struct {
Exec: []const u8 = "",
Name: []const u8 = "",
DesktopNames: ?[]u8 = null,
Terminal: ?bool = null,
};
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
entry_ini: ?Ini(Entry) = null,
name: []const u8 = "",
xdg_session_desktop: ?[]const u8 = null,
xdg_session_desktop_owned: bool = false,
xdg_desktop_names: ?[]const u8 = null,
cmd: ?[]const u8 = null,
specifier: []const u8 = "",
display_server: DisplayServer = .wayland,
is_terminal: bool = false,

51
src/LogFile.zig Normal file
View File

@@ -0,0 +1,51 @@
const std = @import("std");
const LogFile = @This();
path: []const u8,
could_open_log_file: bool = undefined,
file: std.fs.File = undefined,
buffer: []u8,
file_writer: std.fs.File.Writer = undefined,
pub fn init(path: []const u8, buffer: []u8) !LogFile {
var log_file = LogFile{ .path = path, .buffer = buffer };
log_file.could_open_log_file = try openLogFile(path, &log_file);
return log_file;
}
pub fn reinit(self: *LogFile) !void {
self.could_open_log_file = try openLogFile(self.path, self);
}
pub fn deinit(self: *LogFile) void {
self.file_writer.interface.flush() catch {};
self.file.close();
}
fn openLogFile(path: []const u8, log_file: *LogFile) !bool {
var could_open_log_file = true;
open_log_file: {
log_file.file = std.fs.cwd().openFile(path, .{ .mode = .write_only }) catch std.fs.cwd().createFile(path, .{ .mode = 0o666 }) catch {
// If we could neither open an existing log file nor create a new
// one, abort.
could_open_log_file = false;
break :open_log_file;
};
}
if (!could_open_log_file) {
log_file.file = try std.fs.openFileAbsolute("/dev/null", .{ .mode = .write_only });
}
var log_file_writer = log_file.file.writer(log_file.buffer);
// Seek to the end of the log file
if (could_open_log_file) {
const stat = try log_file.file.stat();
try log_file_writer.seekTo(stat.size);
}
log_file.file_writer = log_file_writer;
return could_open_log_file;
}

View File

@@ -9,7 +9,7 @@ const ErrorHandler = packed struct {
const SharedError = @This();
data: []align(std.mem.page_size) u8,
data: []align(std.heap.page_size_min) 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);

6
src/UidRange.zig Normal file
View File

@@ -0,0 +1,6 @@
const std = @import("std");
// We set both values to 0 by default so that, in case they aren't present in
// the login.defs for some reason, then only the root username will be shown
uid_min: std.posix.uid_t = 0,
uid_max: std.posix.uid_t = 0,

View File

@@ -0,0 +1,86 @@
const std = @import("std");
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const ColorMix = @This();
const math = std.math;
const Vec2 = @Vector(2, f32);
const time_scale: f32 = 0.01;
const palette_len: usize = 12;
fn length(vec: Vec2) f32 {
return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
}
terminal_buffer: *TerminalBuffer,
frames: u64,
pattern_cos_mod: f32,
pattern_sin_mod: f32,
palette: [palette_len]Cell,
pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) ColorMix {
return .{
.terminal_buffer = terminal_buffer,
.frames = 0,
.pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
.pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
.palette = [palette_len]Cell{
Cell.init(0x2588, col1, col2),
Cell.init(0x2593, col1, col2),
Cell.init(0x2592, col1, col2),
Cell.init(0x2591, col1, col2),
Cell.init(0x2588, col2, col3),
Cell.init(0x2593, col2, col3),
Cell.init(0x2592, col2, col3),
Cell.init(0x2591, col2, col3),
Cell.init(0x2588, col3, col1),
Cell.init(0x2593, col3, col1),
Cell.init(0x2592, col3, col1),
Cell.init(0x2591, col3, col1),
},
};
}
pub fn animation(self: *ColorMix) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(_: *ColorMix) void {}
fn realloc(_: *ColorMix) anyerror!void {}
fn draw(self: *ColorMix) void {
self.frames +%= 1;
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;
for (0..self.terminal_buffer.width) |x| {
for (0..self.terminal_buffer.height) |y| {
const xi: i32 = @intCast(x);
const yi: i32 = @intCast(y);
const wi: i32 = @intCast(self.terminal_buffer.width);
const hi: i32 = @intCast(self.terminal_buffer.height);
var uv: Vec2 = .{
@as(f32, @floatFromInt(xi * 2 - wi)) / @as(f32, @floatFromInt(self.terminal_buffer.height * 2)),
@as(f32, @floatFromInt(yi * 2 - hi)) / @as(f32, @floatFromInt(self.terminal_buffer.height)),
};
var uv2: Vec2 = @splat(uv[0] + uv[1]);
for (0..3) |_| {
uv2 += uv + @as(Vec2, @splat(length(uv)));
uv += @as(Vec2, @splat(0.5)) * Vec2{
math.cos(self.pattern_cos_mod + uv2[1] * 0.2 + time * 0.1),
math.sin(self.pattern_sin_mod + uv2[0] - time * 0.1),
};
uv -= @splat(1.0 * math.cos(uv[0] + uv[1]) - math.sin(uv[0] * 0.7 - uv[1]));
}
const cell = self.palette[@as(usize, @intFromFloat(math.floor(length(uv) * 5.0))) % palette_len];
cell.put(x, y);
}
}
}

View File

@@ -1,84 +1,110 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const utils = @import("../tui/utils.zig");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
const Doom = @This();
pub const STEPS = 13;
pub const FIRE = [_]termbox.tb_cell{
utils.initCell(' ', 9, 0),
utils.initCell(0x2591, 2, 0), // Red
utils.initCell(0x2592, 2, 0), // Red
utils.initCell(0x2593, 2, 0), // Red
utils.initCell(0x2588, 2, 0), // Red
utils.initCell(0x2591, 4, 2), // Yellow
utils.initCell(0x2592, 4, 2), // Yellow
utils.initCell(0x2593, 4, 2), // Yellow
utils.initCell(0x2588, 4, 2), // Yellow
utils.initCell(0x2591, 8, 4), // White
utils.initCell(0x2592, 8, 4), // White
utils.initCell(0x2593, 8, 4), // White
utils.initCell(0x2588, 8, 4), // White
};
pub const STEPS = 12;
pub const HEIGHT_MAX = 9;
pub const SPREAD_MAX = 4;
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
buffer: []u8,
height: u8,
spread: u8,
fire: [STEPS + 1]Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Doom {
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32, fire_height: u8, fire_spread: u8) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width);
const levels =
[_]Cell{
Cell.init(' ', terminal_buffer.bg, terminal_buffer.bg),
Cell.init(0x2591, top_color, terminal_buffer.bg),
Cell.init(0x2592, top_color, terminal_buffer.bg),
Cell.init(0x2593, top_color, terminal_buffer.bg),
Cell.init(0x2588, top_color, terminal_buffer.bg),
Cell.init(0x2591, middle_color, top_color),
Cell.init(0x2592, middle_color, top_color),
Cell.init(0x2593, middle_color, top_color),
Cell.init(0x2588, middle_color, top_color),
Cell.init(0x2591, bottom_color, middle_color),
Cell.init(0x2592, bottom_color, middle_color),
Cell.init(0x2593, bottom_color, middle_color),
Cell.init(0x2588, bottom_color, middle_color),
};
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.buffer = buffer,
.height = @min(HEIGHT_MAX, fire_height),
.spread = @min(SPREAD_MAX, fire_spread),
.fire = levels,
};
}
pub fn deinit(self: Doom) void {
pub fn animation(self: *Doom) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(self: *Doom) void {
self.allocator.free(self.buffer);
}
pub fn realloc(self: *Doom) !void {
fn realloc(self: *Doom) anyerror!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 {
fn draw(self: *Doom) void {
for (0..self.terminal_buffer.width) |x| {
// We start from 1 so that we always have the topmost line when spreading fire
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;
// Get index of current cell in fire level buffer
const from = y * self.terminal_buffer.width + x;
var dest = (source - @min(source, random)) + 1;
if (self.terminal_buffer.width > dest) dest = 0 else dest -= self.terminal_buffer.width;
// Generate random data for fire propagation
const rand_loss = self.terminal_buffer.random.intRangeAtMost(u8, 0, HEIGHT_MAX);
const rand_spread = self.terminal_buffer.random.intRangeAtMost(u8, 0, self.spread * 2);
const buffer_source = self.buffer[source];
const buffer_dest_offset = random & 1;
// Select semi-random target cell
const to = from -| self.terminal_buffer.width + self.spread -| rand_spread;
const to_x = to % self.terminal_buffer.width;
const to_y = to / self.terminal_buffer.width;
if (buffer_source < buffer_dest_offset) continue;
// Get fire level of current cell
const level_buf_from = self.buffer[from];
var buffer_dest = buffer_source - buffer_dest_offset;
if (buffer_dest > 12) buffer_dest = 0;
self.buffer[dest] = @intCast(buffer_dest);
// Choose new fire level and store in level buffer
const level_buf_to = level_buf_from -| @intFromBool(rand_loss >= self.height);
self.buffer[to] = level_buf_to;
self.terminal_buffer.buffer[dest] = FIRE[buffer_dest];
self.terminal_buffer.buffer[source] = FIRE[buffer_source];
// Send known fire levels to terminal buffer
const from_cell = self.fire[level_buf_from];
const to_cell = self.fire[level_buf_to];
from_cell.put(x, y);
to_cell.put(to_x, to_y);
}
// Draw bottom line (fire source)
const src_cell = self.fire[STEPS];
src_cell.put(x, self.terminal_buffer.height - 1);
}
}
fn initBuffer(buffer: []u8, width: u64) void {
fn initBuffer(buffer: []u8, width: usize) void {
const length = buffer.len - width;
const slice_start = buffer[0..length];
const slice_end = buffer[length..];
// Initialize the framebuffer in black, except for the "fire source" as the
// last color
@memset(slice_start, 0);
@memset(slice_end, STEPS - 1);
@memset(slice_end, STEPS);
}

14
src/animations/Dummy.zig Normal file
View File

@@ -0,0 +1,14 @@
const std = @import("std");
const Animation = @import("../tui/Animation.zig");
const Dummy = @This();
pub fn animation(self: *Dummy) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(_: *Dummy) void {}
fn realloc(_: *Dummy) anyerror!void {}
fn draw(_: *Dummy) void {}

408
src/animations/DurFile.zig Normal file
View File

@@ -0,0 +1,408 @@
const std = @import("std");
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Color = TerminalBuffer.Color;
const Styling = TerminalBuffer.Styling;
const Allocator = std.mem.Allocator;
const Json = std.json;
const eql = std.mem.eql;
const flate = std.compress.flate;
fn read_decompress_file(allocator: Allocator, file_path: []const u8) ![]u8 {
const file_buffer = std.fs.cwd().openFile(file_path, .{}) catch {
return error.FileNotFound;
};
defer file_buffer.close();
var file_reader_buffer: [4096]u8 = undefined;
var decompress_buffer: [flate.max_window_len]u8 = undefined;
var file_reader = file_buffer.reader(&file_reader_buffer);
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer);
const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch {
return error.NotValidFile;
};
return file_decompressed;
}
const Frame = struct {
frameNumber: i32,
delay: f32,
contents: [][]u8,
colorMap: [][][]i32,
// allocator must be outside of struct as it will fail the json parser
pub fn deinit(self: *const Frame, allocator: Allocator) void {
for (self.contents) |con| {
allocator.free(con);
}
allocator.free(self.contents);
for (self.colorMap) |cm| {
for (cm) |int2| {
allocator.free(int2);
}
allocator.free(cm);
}
allocator.free(self.colorMap);
}
};
// https://github.com/cmang/durdraw/blob/0.29.0/durformat.md
const DurFormat = struct {
allocator: Allocator,
formatVersion: ?i64 = null,
colorFormat: ?[]const u8 = null,
encoding: ?[]const u8 = null,
framerate: ?f64 = null,
columns: ?i64 = null,
lines: ?i64 = null,
frames: std.ArrayList(Frame) = undefined,
pub fn valid(self: *DurFormat) bool {
if (self.formatVersion != null and
self.colorFormat != null and
self.encoding != null and
self.framerate != null and
self.columns != null and
self.lines != null and
self.frames.items.len >= 1)
{
// v8 may have breaking changes like changing the colormap xy direction
// (https://github.com/cmang/durdraw/issues/24)
if (self.formatVersion.? != 7) return false;
// Code currently only supports 16 and 256 color format only
if (!(eql(u8, "16", self.colorFormat.?) or eql(u8, "256", self.colorFormat.?)))
return false;
// Code currently supports only utf-8 encoding
if (!eql(u8, self.encoding.?, "utf-8")) return false;
// Sanity check on file
if (self.columns.? <= 0) return false;
if (self.lines.? <= 0) return false;
if (self.framerate.? < 0) return false;
return true;
}
return false;
}
fn parse_dur_from_json(self: *DurFormat, allocator: Allocator, dur_json_root: Json.Value) !void {
var dur_movie = if (dur_json_root.object.get("DurMovie")) |dm| dm.object else return error.NotValidFile;
// Depending on the version, a dur file can have different json object names (ie: columns vs sizeX)
self.formatVersion = if (dur_movie.get("formatVersion")) |x| x.integer else null;
self.colorFormat = if (dur_movie.get("colorFormat")) |x| try allocator.dupe(u8, x.string) else null;
self.encoding = if (dur_movie.get("encoding")) |x| try allocator.dupe(u8, x.string) else null;
self.framerate = if (dur_movie.get("framerate")) |x| x.float else null;
self.columns = if (dur_movie.get("columns")) |x| x.integer else if (dur_movie.get("sizeX")) |x| x.integer else null;
self.lines = if (dur_movie.get("lines")) |x| x.integer else if (dur_movie.get("sizeY")) |x| x.integer else null;
const frames = dur_movie.get("frames") orelse return error.NotValidFile;
self.frames = try .initCapacity(allocator, frames.array.items.len);
for (frames.array.items) |json_frame| {
var parsed_frame = try Json.parseFromValue(Frame, allocator, json_frame, .{});
defer parsed_frame.deinit();
const frame_val = parsed_frame.value;
// copy all fields to own the ptrs for deallocation, the parsed_frame has some other
// allocated memory making it difficult to deallocate without leaks
const frame: Frame = .{ .frameNumber = frame_val.frameNumber, .delay = frame_val.delay, .contents = try allocator.alloc([]u8, frame_val.contents.len), .colorMap = try allocator.alloc([][]i32, frame_val.colorMap.len) };
for (0..frame.contents.len) |i| {
frame.contents[i] = try allocator.dupe(u8, frame_val.contents[i]);
}
// colorMap is stored as an 3d array where:
// the outer (i) most array is the horizontal position of the color
// the middle (j) is the vertical position of the color
// the inner (0/1) is the foreground/background color
for (0..frame.colorMap.len) |i| {
frame.colorMap[i] = try allocator.alloc([]i32, frame_val.colorMap[i].len);
for (0..frame.colorMap[i].len) |j| {
frame.colorMap[i][j] = try allocator.alloc(i32, 2);
frame.colorMap[i][j][0] = frame_val.colorMap[i][j][0];
frame.colorMap[i][j][1] = frame_val.colorMap[i][j][1];
}
}
try self.frames.append(allocator, frame);
}
}
pub fn create_from_file(self: *DurFormat, allocator: Allocator, file_path: []const u8) !void {
const file_decompressed = try read_decompress_file(allocator, file_path);
defer allocator.free(file_decompressed);
const parsed = try Json.parseFromSlice(Json.Value, allocator, file_decompressed, .{});
defer parsed.deinit();
try parse_dur_from_json(self, allocator, parsed.value);
if (!self.valid()) {
return error.NotValidFile;
}
}
pub fn init(allocator: Allocator) DurFormat {
return .{ .allocator = allocator };
}
pub fn deinit(self: *DurFormat) void {
if (self.colorFormat) |str| self.allocator.free(str);
if (self.encoding) |str| self.allocator.free(str);
for (self.frames.items) |frame| {
frame.deinit(self.allocator);
}
self.frames.deinit(self.allocator);
}
};
const tb_color_16 = [16]u32{
Color.ECOL_BLACK,
Color.ECOL_RED,
Color.ECOL_GREEN,
Color.ECOL_YELLOW,
Color.ECOL_BLUE,
Color.ECOL_MAGENTA,
Color.ECOL_CYAN,
Color.ECOL_WHITE,
Color.ECOL_BLACK | Styling.BOLD,
Color.ECOL_RED | Styling.BOLD,
Color.ECOL_GREEN | Styling.BOLD,
Color.ECOL_YELLOW | Styling.BOLD,
Color.ECOL_BLUE | Styling.BOLD,
Color.ECOL_MAGENTA | Styling.BOLD,
Color.ECOL_CYAN | Styling.BOLD,
Color.ECOL_WHITE | Styling.BOLD,
};
// Using bold for bright colors allows for all 16 colors to be rendered on tty term
const rgb_color_16 = [16]u32{
Color.DEFAULT, // DEFAULT instead of TRUE_BLACK to not break compositors (the latter ignores transparency)
Color.TRUE_DIM_RED,
Color.TRUE_DIM_GREEN,
Color.TRUE_DIM_YELLOW,
Color.TRUE_DIM_BLUE,
Color.TRUE_DIM_MAGENTA,
Color.TRUE_DIM_CYAN,
Color.TRUE_DIM_WHITE,
Color.DEFAULT | Styling.BOLD,
Color.TRUE_RED | Styling.BOLD,
Color.TRUE_GREEN | Styling.BOLD,
Color.TRUE_YELLOW | Styling.BOLD,
Color.TRUE_BLUE | Styling.BOLD,
Color.TRUE_MAGENTA | Styling.BOLD,
Color.TRUE_CYAN | Styling.BOLD,
Color.TRUE_WHITE | Styling.BOLD,
};
// Made this table from looking at colormapping in dur source, not sure whats going on with the mapping logic
// Array indexes are dur colormappings which value maps to indexes in table above. Only needed for dur 16 color
const durcolor_table_to_color16 = [17]u32{
0, // 0 black
0, // 1 nothing?? dur source did not say why 1 is unused
4, // 2 blue
2, // 3 green
6, // 4 cyan
1, // 5 red
5, // 6 magenta
3, // 7 yellow
7, // 8 light gray
8, // 9 gray
12, // 10 bright blue
10, // 11 bright green
14, // 12 bright cyan
9, // 13 bright red
13, // 14 bright magenta
11, // 15 bright yellow
15, // 16 bright white
};
fn sixcube_to_channel(sixcube: u32) u32 {
// Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF,
// so we use 0xF0 instead with a scaler
const equal_divisions = 0xF0 / 6;
// Since the range is to 0xFF but 6 isn't divisible, we must add a scaler to get it to 0xFF at the last index (5)
const scaler = 0xFF - (equal_divisions * 5);
return if (sixcube > 0) (sixcube * equal_divisions) + scaler else 0;
}
fn convert_256_to_rgb(color_256: u32) u32 {
var rgb_color: u32 = 0;
// 0 - 15 is the standard color range, map to array table
if (color_256 < 16) {
rgb_color = rgb_color_16[color_256];
}
// 16 - 231 is the extended range
else if (color_256 < 232) {
// For extended term range we subtract by 16 to get it in a 0..(6x6x6) cube (range of 216)
// divide by 36 gets the depth of the cube (6x6x1)
// divide by 6 gets the width of the cube (6x1)
// divide by 1 gets the height of the cube (divide 1 for clarity for what we are doing)
// each channel can be 6 levels of brightness hence remander operation of 6
// finally bitshift to correct rgb channel (16 for red, 8 for green, 0 for blue)
rgb_color |= sixcube_to_channel(((color_256 - 16) / 36) % 6) << 16;
rgb_color |= sixcube_to_channel(((color_256 - 16) / 6) % 6) << 8;
rgb_color |= sixcube_to_channel(((color_256 - 16) / 1) % 6);
}
// 232 - 255 is the grayscale range
else {
// For grayscale we have a space of 232 - 255 (24)
// subtract by 232 to get it into the 0..23 range
// standard colors will contain white and black, so we do not use them in the grayscale range (0 is 0x08, 23 is 0xEE)
// this results in a skip of 0x08 for the first color and divisions of 0x0A
// example: term_col 232 = scaler + equal_divisions * (232 - 232) which becomes (scaler + 0x00) == 0x08
// example: term_col 255 = scaler + equal_divisions * (255 - 232) which becomes (scaler + 0xE6) == 0xEE
const scaler = 0x08;
// to get equal parts, the equation is:
// 0xEE = equal_divisions * 23 + scaler | top of range is 0xEE, 23 is last element value (255 minus 232)
// reordered to solve for equal_divisions:
const equal_divisions = (0xEE - scaler) / 23; // evals to 0x0A
const channel = scaler + equal_divisions * (color_256 - 232);
// gray is equal value of same channel color in rgb
rgb_color = channel | (channel << 8) | (channel << 16);
}
return rgb_color;
}
const DurFile = @This();
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
frames: u64,
time_previous: i64,
x_offset: u32,
y_offset: u32,
full_color: bool,
dur_movie: DurFormat,
frame_width: u32,
frame_height: u32,
frame_time: u32,
is_color_format_16: bool,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, log_writer: *std.io.Writer, file_path: []const u8, x_offset: u32, y_offset: u32, full_color: bool) !DurFile {
var dur_movie: DurFormat = .init(allocator);
// error state is recoverable when thrown to main and results in no background with Dummy in main
dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) {
error.FileNotFound => {
try log_writer.print("error: dur_file was not found at: {s}\n", .{file_path});
return err;
},
error.NotValidFile => {
try log_writer.print("error: dur_file loaded was invalid or not a dur file!\n", .{});
return err;
},
else => return err,
};
// 4 bit mode with 256 color is unsupported
if (!full_color and eql(u8, dur_movie.colorFormat.?, "256")) {
try log_writer.print("error: dur_file can not be 256 color encoded when not using full_color option!\n", .{});
dur_movie.deinit();
return error.InvalidColorFormat;
}
const buf_width: u32 = @intCast(terminal_buffer.width);
const buf_height: u32 = @intCast(terminal_buffer.height);
const movie_width: u32 = @intCast(dur_movie.columns.?);
const movie_height: u32 = @intCast(dur_movie.lines.?);
// Clamp to prevent user from exceeding draw window
const x_offset_clamped = std.math.clamp(x_offset, 0, buf_width - 1);
const y_offset_clamped = std.math.clamp(y_offset, 0, buf_height - 1);
// Ensure if user offsets and frame goes offscreen, it will not overflow draw
const frame_width = if ((movie_width + x_offset_clamped) < buf_width) movie_width else buf_width - x_offset_clamped;
const frame_height = if ((movie_height + y_offset_clamped) < buf_height) movie_height else buf_height - y_offset_clamped;
// Convert dur fps to frames per ms
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?);
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.frames = 0,
.time_previous = std.time.milliTimestamp(),
.x_offset = x_offset_clamped,
.y_offset = y_offset_clamped,
.full_color = full_color,
.dur_movie = dur_movie,
.frame_width = frame_width,
.frame_height = frame_height,
.frame_time = frame_time,
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"),
};
}
pub fn animation(self: *DurFile) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(self: *DurFile) void {
self.dur_movie.deinit();
}
fn realloc(_: *DurFile) anyerror!void {}
fn draw(self: *DurFile) void {
const current_frame = self.dur_movie.frames.items[self.frames];
for (0..self.frame_height) |y| {
var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator();
for (0..self.frame_width) |x| {
const codepoint: u21 = iter.nextCodepoint().?;
const color_map = current_frame.colorMap[x][y];
var color_map_0: u32 = @intCast(if (color_map[0] == -1) 0 else color_map[0]);
var color_map_1: u32 = @intCast(if (color_map[1] == -1) 0 else color_map[1]);
if (self.is_color_format_16) {
color_map_0 = durcolor_table_to_color16[color_map_0];
color_map_1 = durcolor_table_to_color16[color_map_1 + 1]; // Add 1, dur source stores it like this for some reason
}
const fg_color = if (self.full_color) convert_256_to_rgb(color_map_0) else tb_color_16[color_map_0];
const bg_color = if (self.full_color) convert_256_to_rgb(color_map_1) else tb_color_16[color_map_1];
const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color };
cell.put(x + self.x_offset, y + self.y_offset);
}
}
const time_current = std.time.milliTimestamp();
const delta_time = time_current - self.time_previous;
// Convert delay from sec to ms
const delay_time: u32 = @intFromFloat(current_frame.delay * 1000);
if (delta_time > (self.frame_time + delay_time)) {
self.time_previous = time_current;
const frame_count = self.dur_movie.frames.items.len;
self.frames = (self.frames + 1) % frame_count;
}
}

View File

@@ -0,0 +1,189 @@
const std = @import("std");
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Allocator = std.mem.Allocator;
const GameOfLife = @This();
// Visual styles - using block characters like other animations
const ALIVE_CHAR: u21 = 0x2588; // Full block █
const DEAD_CHAR: u21 = ' ';
const NEIGHBOR_DIRS = [_][2]i8{
.{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 },
.{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 },
.{ 1, 0 }, .{ 1, 1 },
};
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
current_grid: []bool,
next_grid: []bool,
frame_counter: usize,
generation: u64,
fg_color: u32,
entropy_interval: usize,
frame_delay: usize,
initial_density: f32,
dead_cell: Cell,
width: usize,
height: usize,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32) !GameOfLife {
const width = terminal_buffer.width;
const height = terminal_buffer.height;
const grid_size = width * height;
const current_grid = try allocator.alloc(bool, grid_size);
const next_grid = try allocator.alloc(bool, grid_size);
var game = GameOfLife{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.current_grid = current_grid,
.next_grid = next_grid,
.frame_counter = 0,
.generation = 0,
.fg_color = fg_color,
.entropy_interval = entropy_interval,
.frame_delay = frame_delay,
.initial_density = initial_density,
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
.width = width,
.height = height,
};
// Initialize grid
game.initializeGrid();
return game;
}
pub fn animation(self: *GameOfLife) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(self: *GameOfLife) void {
self.allocator.free(self.current_grid);
self.allocator.free(self.next_grid);
}
fn realloc(self: *GameOfLife) anyerror!void {
const new_width = self.terminal_buffer.width;
const new_height = self.terminal_buffer.height;
const new_size = new_width * new_height;
const current_grid = try self.allocator.realloc(self.current_grid, new_size);
const next_grid = try self.allocator.realloc(self.next_grid, new_size);
self.current_grid = current_grid;
self.next_grid = next_grid;
self.width = new_width;
self.height = new_height;
self.initializeGrid();
self.generation = 0;
}
fn draw(self: *GameOfLife) void {
// Update game state at controlled frame rate
self.frame_counter += 1;
if (self.frame_counter >= self.frame_delay) {
self.frame_counter = 0;
self.updateGeneration();
self.generation += 1;
// Add entropy based on configuration (0 = disabled, >0 = interval)
if (self.entropy_interval > 0 and self.generation % self.entropy_interval == 0) {
self.addEntropy();
}
}
// Render with the configured color
const alive_cell = Cell{ .ch = ALIVE_CHAR, .fg = self.fg_color, .bg = self.terminal_buffer.bg };
for (0..self.height) |y| {
const row_offset = y * self.width;
for (0..self.width) |x| {
const cell = if (self.current_grid[row_offset + x]) alive_cell else self.dead_cell;
cell.put(x, y);
}
}
}
fn updateGeneration(self: *GameOfLife) void {
// Conway's Game of Life rules with optimized neighbor counting
for (0..self.height) |y| {
const row_offset = y * self.width;
for (0..self.width) |x| {
const index = row_offset + x;
const neighbors = self.countNeighborsOptimized(x, y);
const is_alive = self.current_grid[index];
// Optimized rule application
self.next_grid[index] = switch (neighbors) {
2 => is_alive,
3 => true,
else => false,
};
}
}
// Efficient grid swap
std.mem.swap([]bool, &self.current_grid, &self.next_grid);
}
fn countNeighborsOptimized(self: *GameOfLife, x: usize, y: usize) u8 {
var count: u8 = 0;
for (NEIGHBOR_DIRS) |dir| {
const neighbor_x = @as(i32, @intCast(x)) + dir[0];
const neighbor_y = @as(i32, @intCast(y)) + dir[1];
const width_i32: i32 = @intCast(self.width);
const height_i32: i32 = @intCast(self.height);
// Toroidal wrapping with modular arithmetic
const wx: usize = @intCast(@mod(neighbor_x + width_i32, width_i32));
const wy: usize = @intCast(@mod(neighbor_y + height_i32, height_i32));
if (self.current_grid[wy * self.width + wx]) {
count += 1;
}
}
return count;
}
fn initializeGrid(self: *GameOfLife) void {
const total_cells = self.width * self.height;
// Clear grid
@memset(self.current_grid, false);
@memset(self.next_grid, false);
// Random initialization with configurable density
for (0..total_cells) |i| {
self.current_grid[i] = self.terminal_buffer.random.float(f32) < self.initial_density;
}
}
fn addEntropy(self: *GameOfLife) void {
// Add fewer random cells but in clusters for more interesting patterns
const clusters = 2;
for (0..clusters) |_| {
const cx = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.width - 2);
const cy = self.terminal_buffer.random.intRangeAtMost(usize, 1, self.height - 2);
// Small cluster around center point
for (0..3) |dy| {
for (0..3) |dx| {
if (self.terminal_buffer.random.float(f32) < 0.4) {
const x = (cx + dx) % self.width;
const y = (cy + dy) % self.height;
self.current_grid[y * self.width + x] = true;
}
}
}
}
}

View File

@@ -1,16 +1,12 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Random = std.rand.Random;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
const Allocator = std.mem.Allocator;
const Random = std.Random;
pub const FRAME_DELAY: u64 = 8;
// Allowed codepoints
pub const MIN_CODEPOINT: isize = 33;
pub const MAX_CODEPOINT: isize = 123 - MIN_CODEPOINT;
pub const FRAME_DELAY: usize = 8;
// Characters change mid-scroll
pub const MID_SCROLL_CHANGE = true;
@@ -18,24 +14,29 @@ pub const MID_SCROLL_CHANGE = true;
const Matrix = @This();
pub const Dot = struct {
value: isize,
value: ?usize,
is_head: bool,
};
pub const Line = struct {
space: isize,
length: isize,
update: isize,
space: usize,
length: usize,
update: usize,
};
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
dots: []Dot,
lines: []Line,
frame: u64,
count: u64,
frame: usize,
count: usize,
fg: u32,
head_col: u32,
min_codepoint: u16,
max_codepoint: u16,
default_cell: Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix {
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width);
@@ -48,15 +49,24 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix {
.lines = lines,
.frame = 3,
.count = 0,
.fg = fg,
.head_col = head_col,
.min_codepoint = min_codepoint,
.max_codepoint = max_codepoint - min_codepoint,
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
};
}
pub fn deinit(self: Matrix) void {
pub fn animation(self: *Matrix) Animation {
return Animation.init(self, deinit, realloc, draw);
}
fn deinit(self: *Matrix) void {
self.allocator.free(self.dots);
self.allocator.free(self.lines);
}
pub fn realloc(self: *Matrix) !void {
fn realloc(self: *Matrix) anyerror!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);
@@ -66,7 +76,7 @@ pub fn realloc(self: *Matrix) !void {
self.lines = lines;
}
pub fn draw(self: *Matrix) void {
fn draw(self: *Matrix) void {
const buf_height = self.terminal_buffer.height;
const buf_width = self.terminal_buffer.width;
self.count += 1;
@@ -75,31 +85,31 @@ pub fn draw(self: *Matrix) void {
if (self.frame > 4) self.frame = 1;
self.count = 0;
var x: u64 = 0;
var x: usize = 0;
while (x < self.terminal_buffer.width) : (x += 2) {
var tail: u64 = 0;
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 (self.dots[x].value == null 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);
const randint = self.terminal_buffer.random.int(u16);
const h = self.terminal_buffer.height;
line.length = @mod(randint, h - 3) + 3;
self.dots[x].value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT;
self.dots[x].value = @mod(randint, self.max_codepoint) + self.min_codepoint;
line.space = @mod(randint, h + 1);
}
}
var y: u64 = 0;
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)) {
while (y <= buf_height and (dot.value == ' ' or dot.value == null)) {
y += 1;
if (y > buf_height) break :height_it;
dot = &self.dots[buf_width * y + x];
@@ -108,12 +118,12 @@ pub fn draw(self: *Matrix) void {
// Find the head of this column
tail = y;
seg_len = 0;
while (y <= buf_height and dot.value != ' ' and dot.value != -1) {
while (y <= buf_height and dot.value != ' ' and dot.value != null) {
dot.is_head = false;
if (MID_SCROLL_CHANGE) {
const randint = self.terminal_buffer.random.int(i16);
const randint = self.terminal_buffer.random.int(u16);
if (@mod(randint, 8) == 0) {
dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT;
dot.value = @mod(randint, self.max_codepoint) + self.min_codepoint;
}
}
@@ -127,53 +137,52 @@ pub fn draw(self: *Matrix) void {
dot = &self.dots[buf_width * y + x];
}
const randint = self.terminal_buffer.random.int(i16);
dot.value = @mod(randint, MAX_CODEPOINT) + MIN_CODEPOINT;
const randint = self.terminal_buffer.random.int(u16);
dot.value = @mod(randint, self.max_codepoint) + self.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;
self.dots[x].value = null;
}
first_col = false;
}
}
}
var x: u64 = 0;
var x: usize = 0;
while (x < buf_width) : (x += 2) {
var y: u64 = 1;
var y: usize = 1;
while (y <= self.terminal_buffer.height) : (y += 1) {
const dot = self.dots[buf_width * y + x];
var fg: u16 = @intCast(termbox.TB_GREEN);
const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{
.ch = @intCast(dot.value.?),
.fg = if (dot.is_head) self.head_col else self.fg,
.bg = self.terminal_buffer.bg,
};
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);
cell.put(x, y - 1);
// Fill background in between columns
self.default_cell.put(x + 1, y - 1);
}
}
}
fn initBuffers(dots: []Dot, lines: []Line, width: u64, height: u64, random: Random) void {
var y: u64 = 0;
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
var y: usize = 0;
while (y <= height) : (y += 1) {
var x: u64 = 0;
var x: usize = 0;
while (x < width) : (x += 2) {
dots[y * width + x].value = -1;
dots[y * width + x].value = null;
}
}
var x: u64 = 0;
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;
line.space = @mod(random.int(u16), height) + 1;
line.length = @mod(random.int(u16), height - 3) + 3;
line.update = @mod(random.int(u16), 3) + 1;
lines[x] = line;
dots[width + x].value = ' ';

View File

@@ -1,35 +1,56 @@
const std = @import("std");
const build_options = @import("build_options");
const builtin = @import("builtin");
const enums = @import("enums.zig");
const Environment = @import("Environment.zig");
const interop = @import("interop.zig");
const TerminalBuffer = @import("tui/TerminalBuffer.zig");
const Desktop = @import("tui/components/Desktop.zig");
const Text = @import("tui/components/Text.zig");
const Config = @import("config/Config.zig");
const Allocator = std.mem.Allocator;
const utmp = interop.utmp;
const Utmp = utmp.utmp;
const SharedError = @import("SharedError.zig");
const LogFile = @import("LogFile.zig");
const Md5 = std.crypto.hash.Md5;
const utmp = interop.utmp;
const Utmp = utmp.utmpx;
pub const AuthOptions = struct {
tty: u8,
service_name: [:0]const u8,
path: ?[]const u8,
session_log: ?[]const u8,
xauth_cmd: []const u8,
setup_cmd: []const u8,
login_cmd: ?[]const u8,
x_cmd: []const u8,
session_pid: std.posix.pid_t,
};
var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(i: c_int) callconv(.C) void {
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 {
pub fn sessionSignalHandler(i: c_int) callconv(.c) void {
if (child_pid > 0) _ = std.c.kill(child_pid, i);
}
pub fn authenticate(config: Config, current_environment: Desktop.Environment, login: [:0]const u8, password: [:0]const u8) !void {
var tty_buffer: [2]u8 = undefined;
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty});
pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void {
var tty_buffer: [3]u8 = undefined;
const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty});
var pam_tty_buffer: [6]u8 = undefined;
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty});
// Set the XDG environment variables
setXdgSessionEnv(current_environment.display_server);
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names orelse "");
try setXdgEnv(allocator, tty_str, current_environment);
// Open the PAM session
var credentials = [_:null]?[*:0]const u8{ login, password };
const login_z = try allocator.dupeZ(u8, login);
defer allocator.free(login_z);
const password_z = try allocator.dupeZ(u8, password);
defer allocator.free(password_z);
var credentials = [_:null]?[*:0]const u8{ login_z, password_z };
const conv = interop.pam.pam_conv{
.conv = loginConv,
@@ -37,49 +58,68 @@ pub fn authenticate(config: Config, current_environment: Desktop.Environment, lo
};
var handle: ?*interop.pam.pam_handle = undefined;
var status = interop.pam.pam_start(config.service_name.ptr, null, &conv, &handle);
var log_writer = &log_file.file_writer.interface;
try log_writer.writeAll("[pam] starting session\n");
var status = interop.pam.pam_start(options.service_name, null, &conv, &handle);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer _ = 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
try log_writer.writeAll("[pam] setting tty\n");
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
// Do the PAM routine
try log_writer.writeAll("[pam] authenticating\n");
status = interop.pam.pam_authenticate(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_writer.writeAll("[pam] validating account\n");
status = interop.pam.pam_acct_mgmt(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_writer.writeAll("[pam] setting credentials\n");
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
try log_writer.writeAll("[pam] opening session\n");
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, 0);
var pwd: *interop.passwd = undefined;
var user_entry: interop.UsernameEntry = undefined;
{
defer interop.endpwent();
defer interop.closePasswordDatabase();
// Get password structure from username
pwd = interop.getpwnam(login.ptr) orelse return error.GetPasswordNameFailed;
user_entry = interop.getUsernameEntry(login_z) orelse return error.GetPasswordNameFailed;
}
// Set user shell if it hasn't already been set
if (pwd.pw_shell[0] == 0) {
interop.setusershell();
pwd.pw_shell = interop.getusershell();
interop.endusershell();
}
if (user_entry.shell == null) interop.setUserShell(&user_entry);
var shared_err = try SharedError.init();
defer shared_err.deinit();
log_file.deinit();
child_pid = try std.posix.fork();
if (child_pid == 0) {
startSession(config, pwd, handle, current_environment) catch |e| {
try log_file.reinit();
log_writer = &log_file.file_writer.interface;
try log_writer.writeAll("starting session\n");
startSession(log_file, allocator, options, tty_str, user_entry, handle, current_environment) catch |e| {
shared_err.writeError(e);
log_file.deinit();
std.process.exit(1);
};
log_file.deinit();
std.process.exit(0);
}
@@ -88,101 +128,113 @@ pub fn authenticate(config: Config, current_environment: Desktop.Environment, lo
{
// 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.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,
.mask = std.posix.sigemptyset(),
.flags = 0,
};
try std.posix.sigaction(std.posix.SIG.TERM, &act, null);
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
try addUtmpEntry(&entry, pwd.pw_name, child_pid);
try addUtmpEntry(&entry, user_entry.username.?, child_pid);
}
// Wait for the session to stop
_ = std.posix.waitpid(child_pid, 0);
removeUtmpEntry(&entry);
try log_file.reinit();
try resetTerminal(pwd.pw_shell, config.term_reset_cmd);
removeUtmpEntry(&entry);
if (shared_err.readError()) |err| return err;
}
fn startSession(
config: Config,
pwd: *interop.passwd,
log_file: *LogFile,
allocator: std.mem.Allocator,
options: AuthOptions,
tty_str: []u8,
user_entry: interop.UsernameEntry,
handle: ?*interop.pam.pam_handle,
current_environment: Desktop.Environment,
current_environment: Environment,
) !void {
const status = interop.initgroups(pwd.pw_name, pwd.pw_gid);
if (status != 0) return error.GroupInitializationFailed;
std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed;
std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed;
// Set the user's GID & PID
try interop.setUserContext(allocator, user_entry);
// Set up the environment
try initEnv(pwd, config.path);
try initEnv(allocator, user_entry, options.path);
// Reset the XDG environment variables
try setXdgEnv(allocator, tty_str, current_environment);
// 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.putenv(env_var.?);
for (env_list) |env_var| try interop.putEnvironmentVariable(env_var);
// Change to the user's home directory
std.posix.chdir(user_entry.home.?) catch return error.ChangeDirectoryFailed;
// Signal to the session process to give up control on the TTY
std.posix.kill(options.session_pid, std.posix.SIG.CHLD) catch return error.TtyControlTransferFailed;
// Execute what the user requested
std.posix.chdirZ(pwd.pw_dir) catch return error.ChangeDirectoryFailed;
try resetTerminal(pwd.pw_shell, config.term_reset_cmd);
switch (current_environment.display_server) {
.wayland => try executeWaylandCmd(pwd.pw_shell, config.wayland_cmd, current_environment.cmd),
.shell => try executeShellCmd(pwd.pw_shell),
.xinitrc, .x11 => {
.wayland, .shell, .custom => try executeCmd(log_file, allocator, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd),
.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, current_environment.cmd, vt);
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty});
try executeX11Cmd(log_file, allocator, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt);
},
}
}
fn initEnv(pwd: *interop.passwd, path_env: ?[:0]const u8) !void {
_ = interop.setenv("HOME", pwd.pw_dir, 1);
_ = interop.setenv("PWD", pwd.pw_dir, 1);
_ = interop.setenv("SHELL", pwd.pw_shell, 1);
_ = interop.setenv("USER", pwd.pw_name, 1);
_ = interop.setenv("LOGNAME", pwd.pw_name, 1);
fn initEnv(allocator: std.mem.Allocator, entry: interop.UsernameEntry, path_env: ?[]const u8) !void {
if (entry.home) |home| {
try interop.setEnvironmentVariable(allocator, "HOME", home, true);
try interop.setEnvironmentVariable(allocator, "PWD", home, true);
} else return error.NoHomeDirectory;
try interop.setEnvironmentVariable(allocator, "SHELL", entry.shell.?, true);
try interop.setEnvironmentVariable(allocator, "USER", entry.username.?, true);
try interop.setEnvironmentVariable(allocator, "LOGNAME", entry.username.?, true);
if (path_env) |path| {
const status = interop.setenv("PATH", path, 1);
if (status != 0) return error.SetPathFailed;
interop.setEnvironmentVariable(allocator, "PATH", path, true) catch return error.SetPathFailed;
}
}
fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
_ = interop.setenv("XDG_SESSION_TYPE", switch (display_server) {
fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environment) !void {
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_TYPE", switch (environment.display_server) {
.wayland => "wayland",
.shell => "tty",
.xinitrc, .x11 => "x11",
}, 0);
}
.custom => if (environment.is_terminal) "tty" else "unspecified",
}, false);
fn setXdgEnv(tty_str: [:0]u8, desktop_name: [:0]const u8, xdg_desktop_names: [:0]const u8) !void {
const uid = interop.getuid();
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined;
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
// 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 = std.posix.getuid();
var uid_buffer: [32]u8 = undefined; // No UID can be larger than this
const uid_str = try std.fmt.bufPrint(&uid_buffer, "/run/user/{d}", .{uid});
_ = interop.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names.ptr, 0);
_ = interop.setenv("XDG_RUNTIME_DIR", uid_str.ptr, 0);
_ = interop.setenv("XDG_SESSION_CLASS", "user", 0);
_ = interop.setenv("XDG_SESSION_ID", "1", 0);
_ = interop.setenv("XDG_SESSION_DESKTOP", desktop_name.ptr, 0);
_ = interop.setenv("XDG_SEAT", "seat0", 0);
_ = interop.setenv("XDG_VTNR", tty_str.ptr, 0);
try interop.setEnvironmentVariable(allocator, "XDG_RUNTIME_DIR", uid_str, false);
}
if (environment.xdg_desktop_names) |xdg_desktop_names| try interop.setEnvironmentVariable(allocator, "XDG_CURRENT_DESKTOP", xdg_desktop_names, false);
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_CLASS", "user", false);
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false);
if (environment.xdg_session_desktop) |desktop_name| try interop.setEnvironmentVariable(allocator, "XDG_SESSION_DESKTOP", desktop_name, false);
try interop.setEnvironmentVariable(allocator, "XDG_SEAT", "seat0", false);
try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false);
}
fn loginConv(
@@ -190,7 +242,7 @@ fn loginConv(
msg: ?[*]?*const interop.pam.pam_message,
resp: ?*?[*]interop.pam.pam_response,
appdata_ptr: ?*anyopaque,
) callconv(.C) c_int {
) callconv(.c) c_int {
const message_count: u32 = @intCast(num_msg);
const messages = msg.?;
@@ -213,7 +265,7 @@ fn loginConv(
status = interop.pam.PAM_BUF_ERR;
break :set_credentials;
};
response[i].resp = username.?.ptr;
response[i].resp = username.?;
},
interop.pam.PAM_PROMPT_ECHO_OFF => {
const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr));
@@ -221,7 +273,7 @@ fn loginConv(
status = interop.pam.PAM_BUF_ERR;
break :set_credentials;
};
response[i].resp = password.?.ptr;
response[i].resp = password.?;
},
interop.pam.PAM_ERROR_MSG => {
status = interop.pam.PAM_CONV_ERR;
@@ -234,8 +286,8 @@ fn loginConv(
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.?);
if (username) |str| allocator.free(str);
if (password) |str| allocator.free(str);
} else {
resp.?.* = response.ptr;
}
@@ -243,17 +295,6 @@ fn loginConv(
return status;
}
fn resetTerminal(shell: [*:0]const u8, term_reset_cmd: [:0]const u8) !void {
const pid = try std.posix.fork();
if (pid == 0) {
const args = [_:null]?[*:0]const u8{ shell, "-c", term_reset_cmd };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1);
}
_ = std.posix.waitpid(pid, 0);
}
fn getFreeDisplay() !u8 {
var buf: [15]u8 = undefined;
var i: u8 = 0;
@@ -270,46 +311,50 @@ fn getXPid(display_num: u8) !i32 {
const file = try std.fs.openFileAbsolute(file_name, .{});
defer file.close();
var file_buf: [20]u8 = undefined;
var fbs = std.io.fixedBufferStream(&file_buf);
var file_buffer: [32]u8 = undefined;
var file_reader = file.reader(&file_buffer);
var reader = &file_reader.interface;
_ = try file.reader().streamUntilDelimiter(fbs.writer(), '\n', 20);
const line = fbs.getWritten();
var buffer: [20]u8 = undefined;
var writer = std.Io.Writer.fixed(&buffer);
return std.fmt.parseInt(i32, std.mem.trim(u8, line, " "), 10);
const written = try reader.streamDelimiter(&writer, '\n');
return std.fmt.parseInt(i32, std.mem.trim(u8, buffer[0..written], " "), 10);
}
fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
fn createXauthFile(pwd: []const u8, buffer: []u8) ![]const u8 {
var xauth_buf: [100]u8 = undefined;
var xauth_dir: [:0]const u8 = undefined;
var xauth_dir: []const u8 = undefined;
const xdg_rt_dir = std.posix.getenv("XDG_RUNTIME_DIR");
var xauth_file: []const u8 = "lyxauth";
if (xdg_rt_dir == null) {
if (xdg_rt_dir == null) no_rt_dir: {
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 {
if (xdg_cfg_home == null) no_cfg_home: {
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config", .{pwd});
var dir = std.fs.cwd().openDir(xauth_dir, .{}) catch {
// xauth_dir isn't a directory
xauth_dir = pwd;
xauth_file = ".lyxauth";
}
break :no_cfg_home;
};
dir.close();
// xauth_dir is a directory, use it to store Xauthority
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config/ly", .{pwd});
} else {
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?});
xauth_dir = try std.fmt.bufPrint(&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";
};
}
const file = std.fs.cwd().openFile(xauth_dir, .{}) catch break :no_rt_dir;
file.close();
// xauth_dir is a file, create the parent directory
std.posix.mkdir(xauth_dir, 777) catch {
xauth_dir = pwd;
xauth_file = ".lyxauth";
};
} else {
xauth_dir = xdg_rt_dir.?;
}
@@ -319,90 +364,73 @@ fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
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, .{});
const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file });
std.fs.makeDirAbsolute(trimmed_xauth_dir) catch {};
const file = try std.fs.createFileAbsolute(xauthority, .{});
file.close();
return xauthority;
}
pub fn mcookie(cmd: [:0]const u8) ![32]u8 {
const pipe = try std.posix.pipe();
defer std.posix.close(pipe[1]);
fn mcookie() [Md5.digest_length * 2]u8 {
var buf: [4096]u8 = undefined;
std.crypto.random.bytes(&buf);
const output = std.fs.File{ .handle = pipe[0] };
defer output.close();
var out: [Md5.digest_length]u8 = undefined;
Md5.hash(&buf, &out, .{});
const pid = try std.posix.fork();
if (pid == 0) {
std.posix.close(pipe[0]);
std.posix.dup2(pipe[1], std.posix.STDOUT_FILENO) catch std.process.exit(1);
std.posix.close(pipe[1]);
const args = [_:null]?[*:0]u8{};
std.posix.execveZ(cmd.ptr, &args, std.c.environ) catch {};
std.process.exit(1);
}
const result = std.posix.waitpid(pid, 0);
if (result.status != 0) return error.McookieFailed;
var buf: [32]u8 = undefined;
const len = try output.read(&buf);
if (len != 32) return error.McookieFailed;
return buf;
return std.fmt.bytesToHex(&out, .lower);
}
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xauth_cmd: []const u8, mcookie_cmd: [:0]const u8) !void {
var pwd_buf: [100]u8 = undefined;
const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir});
fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, display_name: []u8, shell: [*:0]const u8, home: []const u8, xauth_buffer: []u8, options: AuthOptions) !void {
const xauthority = try createXauthFile(home, xauth_buffer);
try interop.setEnvironmentVariable(allocator, "XAUTHORITY", xauthority, true);
try interop.setEnvironmentVariable(allocator, "DISPLAY", display_name, true);
const xauthority = try createXauthFile(pwd);
_ = interop.setenv("XAUTHORITY", xauthority, 1);
_ = interop.setenv("DISPLAY", display_name, 1);
const mcookie_output = try mcookie(mcookie_cmd);
const magic_cookie = mcookie();
const pid = try std.posix.fork();
if (pid == 0) {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ xauth_cmd, display_name, mcookie_output }) catch std.process.exit(1);
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ options.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;
if (status.status != 0) {
try log_file.file_writer.interface.print("xauth command failed with status {d}\n", .{status.status});
return error.XauthFailed;
}
}
fn executeShellCmd(shell: [*:0]const u8) !void {
const args = [_:null]?[*:0]const u8{shell};
return std.posix.execveZ(shell, &args, std.c.environ);
}
fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, home: []const u8, options: AuthOptions, desktop_cmd: []const u8, vt: []const u8) !void {
var log_writer = &log_file.file_writer.interface;
var xauth_buffer: [256]u8 = undefined;
fn executeWaylandCmd(shell: [*:0]const u8, wayland_cmd: []const u8, desktop_cmd: []const u8) !void {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
return std.posix.execveZ(shell, &args, std.c.environ);
}
fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, desktop_cmd: []const u8, vt: []const u8) !void {
try log_writer.writeAll("[x11] getting free display\n");
const display_num = try getFreeDisplay();
var buf: [5]u8 = undefined;
const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num});
try xauth(display_name, shell, pw_dir, config.xauth_cmd, config.mcookie_cmd);
var buf: [4]u8 = undefined;
const display_name = try std.fmt.bufPrint(&buf, ":{d}", .{display_num});
const shell_z = try allocator.dupeZ(u8, shell);
defer allocator.free(shell_z);
try log_writer.writeAll("[x11] creating xauth file\n");
try xauth(log_file, allocator, display_name, shell_z, home, &xauth_buffer, options);
try log_writer.writeAll("[x11] starting x server\n");
const pid = try std.posix.fork();
if (pid == 0) {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.x_cmd, display_name, vt }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.x_cmd, display_name, vt }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
std.posix.execveZ(shell_z, &args, std.c.environ) catch {};
std.process.exit(1);
}
@@ -418,78 +446,125 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
// X Server detaches from the process.
// PID can be fetched from /tmp/X{d}.lock
try log_writer.writeAll("[x11] getting x server pid\n");
const x_pid = try getXPid(display_num);
try log_writer.writeAll("[x11] launching environment\n");
xorg_pid = try std.posix.fork();
if (xorg_pid == 0) {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ config.x_cmd_setup, desktop_cmd }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", desktop_cmd }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
std.posix.execveZ(shell_z, &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,
.mask = std.posix.sigemptyset(),
.flags = 0,
};
try std.posix.sigaction(std.posix.SIG.TERM, &act, null);
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;
// TODO: Find a more robust way to ensure that X has been terminated (pidfds?)
std.posix.kill(x_pid, std.posix.SIG.TERM) catch {};
std.Thread.sleep(std.time.ns_per_s * 1); // Wait 1 second before sending SIGKILL
std.posix.kill(x_pid, std.posix.SIG.KILL) catch return;
var status: c_int = 0;
_ = std.c.waitpid(x_pid, &status, 0);
_ = std.posix.waitpid(x_pid, 0);
}
fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void {
var maybe_log_file: ?std.fs.File = null;
if (!is_terminal) {
// For custom desktop entries, the "Terminal" value here determines if
// we redirect standard output & error or not. That is, we redirect only
// if it's equal to false (so if it's not running in a TTY).
if (options.session_log) |log_path| {
maybe_log_file = try redirectStandardStreams(global_log_file, log_path, true);
}
}
defer if (maybe_log_file) |log_file| log_file.close();
const shell_z = try allocator.dupeZ(u8, shell);
defer allocator.free(shell_z);
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", exec_cmd orelse shell });
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
return std.posix.execveZ(shell_z, &args, std.c.environ);
}
fn redirectStandardStreams(global_log_file: *LogFile, session_log: []const u8, create: bool) !std.fs.File {
const log_file = if (create) (std.fs.cwd().createFile(session_log, .{ .mode = 0o666 }) catch |err| {
try global_log_file.file_writer.interface.print("failed to create new session log file: {s}\n", .{@errorName(err)});
return err;
}) else (std.fs.cwd().openFile(session_log, .{ .mode = .read_write }) catch |err| {
try global_log_file.file_writer.interface.print("failed to open existing session log file: {s}\n", .{@errorName(err)});
return err;
});
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: []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 buf: [std.fs.max_path_bytes]u8 = undefined;
const tty_path = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf);
var ttyname_buf: [32]u8 = undefined;
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]});
// Get the TTY name (i.e. without the /dev/ prefix)
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{tty_path["/dev/".len..]});
entry.ut_line = ttyname_buf;
entry.ut_id = ttyname_buf["tty".len..7].*;
// Get the TTY ID (i.e. without the tty prefix) and truncate it to the size
// of ut_id if necessary
entry.ut_id = ttyname_buf["tty".len..(@sizeOf(@TypeOf(entry.ut_id)) + "tty".len)].*;
var username_buf: [32]u8 = undefined;
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: [256]u8 = undefined;
var host: [@sizeOf(@TypeOf(entry.ut_host))]u8 = undefined;
host[0] = 0;
entry.ut_host = host;
var tv: std.c.timeval = undefined;
_ = std.c.gettimeofday(&tv, null);
const time = try interop.getTimeOfDay();
entry.ut_tv = .{
.tv_sec = @intCast(tv.tv_sec),
.tv_usec = @intCast(tv.tv_usec),
.tv_sec = @intCast(time.seconds),
.tv_usec = @intCast(time.microseconds),
};
entry.ut_addr_v6[0] = 0;
utmp.setutent();
_ = utmp.pututline(entry);
utmp.endutent();
// FreeBSD doesn't have this field
if (builtin.os.tag == .linux) {
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.setutent();
_ = utmp.pututline(entry);
utmp.endutent();
utmp.setutxent();
_ = utmp.pututxline(entry);
utmp.endutxent();
}
fn pamDiagnose(status: c_int) anyerror {

View File

@@ -1,140 +1,58 @@
const std = @import("std");
const builtin = @import("builtin");
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 Cell = @import("tui/Cell.zig");
const termbox = interop.termbox;
const Bigclock = enums.Bigclock;
pub const WIDTH = Lang.WIDTH;
pub const HEIGHT = Lang.HEIGHT;
pub const SIZE = Lang.SIZE;
const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
const O: u32 = 0;
pub fn clockCell(animate: bool, char: u8, fg: u32, bg: u32, bigclock: Bigclock) ![SIZE]Cell {
var cells: [SIZE]Cell = undefined;
pub const WIDTH = 5;
pub const HEIGHT = 5;
pub const SIZE = WIDTH * HEIGHT;
// zig fmt: off
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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,
};
const 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
pub fn clockCell(animate: bool, char: u8, fg: u8, bg: u8) [SIZE]termbox.tb_cell {
var cells: [SIZE]termbox.tb_cell = undefined;
var tv: std.c.timeval = undefined;
_ = std.c.gettimeofday(&tv, null);
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char);
for (0..cells.len) |i| cells[i] = utils.initCell(clock_chars[i], fg, bg);
const time = try interop.getTimeOfDay();
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(time.microseconds, 500000) != 0) ' ' else char, bigclock);
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
return cells;
}
pub fn alphaBlit(buffer: [*]termbox.tb_cell, x: u64, y: u64, tb_width: u64, tb_height: u64, cells: [SIZE]termbox.tb_cell) void {
pub fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [SIZE]Cell) void {
if (x + WIDTH >= tb_width or y + HEIGHT >= tb_height) return;
for (0..HEIGHT) |yy| {
for (0..WIDTH) |xx| {
const cell = cells[yy * WIDTH + xx];
if (cell.ch != 0) buffer[(y + yy) * tb_width + (x + xx)] = cell;
cell.put(x + xx, y + yy);
}
}
}
fn toBigNumber(char: u8) []const u21 {
fn toBigNumber(char: u8, bigclock: Bigclock) [SIZE]u21 {
const locale_chars = switch (bigclock) {
.fa => fa.locale_chars,
.en => en.locale_chars,
.none => unreachable,
};
return switch (char) {
'0' => &ZERO,
'1' => &ONE,
'2' => &TWO,
'3' => &THREE,
'4' => &FOUR,
'5' => &FIVE,
'6' => &SIX,
'7' => &SEVEN,
'8' => &EIGHT,
'9' => &NINE,
':' => &S,
else => &E,
'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,
'p', 'P' => locale_chars.P,
'a', 'A' => locale_chars.A,
'm', 'M' => locale_chars.M,
':' => locale_chars.S,
else => locale_chars.E,
};
}

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

@@ -0,0 +1,28 @@
const interop = @import("../interop.zig");
pub const WIDTH = 5;
pub const HEIGHT = 5;
pub const SIZE = WIDTH * HEIGHT;
pub const X: u32 = if (interop.supportsUnicode()) 0x2593 else '#';
pub const O: u32 = 0;
// zig fmt: off
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,
P: [SIZE]u21,
A: [SIZE]u21,
M: [SIZE]u21,
};
// zig fmt: on

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

@@ -0,0 +1,115 @@
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,
},
.P = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
X,X,O,O,O,
X,X,O,O,O,
},
.A = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
X,X,O,X,X,
X,X,O,X,X,
},
.M = [_]u21{
X,X,X,X,X,
X,O,X,O,X,
X,O,X,O,X,
X,O,O,O,X,
X,O,O,O,X,
},
};
// zig fmt: on

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

@@ -0,0 +1,115 @@
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,
},
.P = [_]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,
},
.A = [_]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,
},
.M = [_]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,
},
.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

@@ -3,51 +3,93 @@ const enums = @import("../enums.zig");
const Animation = enums.Animation;
const Input = enums.Input;
const ViMode = enums.ViMode;
const Bigclock = enums.Bigclock;
allow_empty_password: bool = true,
animation: Animation = .none,
asterisk: u8 = '*',
bg: u8 = 0,
bigclock: bool = false,
animation_timeout_sec: u12 = 0,
asterisk: ?u32 = '*',
auth_fails: u64 = 10,
battery_id: ?[]const u8 = null,
auto_login_service: [:0]const u8 = "ly-autologin",
auto_login_session: ?[]const u8 = null,
auto_login_user: ?[]const u8 = null,
bg: u32 = 0x00000000,
bigclock: Bigclock = .none,
bigclock_12hr: bool = false,
bigclock_seconds: bool = false,
blank_box: bool = true,
border_fg: u8 = 8,
border_fg: u32 = 0x00FFFFFF,
box_title: ?[]const u8 = null,
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s 10%-",
brightness_down_key: ?[]const u8 = "F5",
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s +10%",
brightness_up_key: ?[]const u8 = "F6",
clear_password: bool = false,
clock: ?[:0]const u8 = null,
console_dev: [:0]const u8 = "/dev/console",
cmatrix_fg: u32 = 0x0000FF00,
cmatrix_head_col: u32 = 0x01FFFFFF,
cmatrix_min_codepoint: u16 = 0x21,
cmatrix_max_codepoint: u16 = 0x7B,
colormix_col1: u32 = 0x00FF0000,
colormix_col2: u32 = 0x000000FF,
colormix_col3: u32 = 0x20000000,
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
default_input: Input = .login,
fg: u8 = 8,
doom_fire_height: u8 = 6,
doom_fire_spread: u8 = 2,
doom_top_color: u32 = 0x00FF0000,
doom_middle_color: u32 = 0x00FFFF00,
doom_bottom_color: u32 = 0x00FFFFFF,
dur_file_path: []const u8 = build_options.config_directory ++ "/ly/example.dur",
dur_x_offset: u32 = 0,
dur_y_offset: u32 = 0,
edge_margin: u8 = 0,
error_bg: u32 = 0x00000000,
error_fg: u32 = 0x01FF0000,
fg: u32 = 0x00FFFFFF,
full_color: bool = true,
gameoflife_fg: u32 = 0x0000FF00,
gameoflife_entropy_interval: usize = 10,
gameoflife_frame_delay: usize = 6,
gameoflife_initial_density: f32 = 0.4,
hibernate_cmd: ?[]const u8 = null,
hibernate_key: []const u8 = "F4",
hide_borders: bool = false,
hide_key_hints: bool = false,
hide_keyboard_locks: bool = false,
hide_version_string: bool = false,
inactivity_cmd: ?[]const u8 = null,
inactivity_delay: u16 = 0,
initial_info_text: ?[]const u8 = null,
input_len: u8 = 34,
lang: []const u8 = "en",
load: bool = true,
login_cmd: ?[]const u8 = null,
login_defs_path: []const u8 = "/etc/login.defs",
logout_cmd: ?[]const u8 = null,
ly_log: []const u8 = "/var/log/ly.log",
margin_box_h: u8 = 2,
margin_box_v: u8 = 1,
max_desktop_len: u8 = 100,
max_login_len: u8 = 255,
max_password_len: u8 = 255,
mcookie_cmd: [:0]const u8 = "/usr/bin/mcookie",
min_refresh_delta: u16 = 5,
numlock: bool = false,
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin",
path: ?[]const u8 = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
restart_cmd: []const u8 = "/sbin/shutdown -r now",
restart_key: []const u8 = "F2",
save: bool = true,
save_file: []const u8 = "/etc/ly/save",
service_name: [:0]const u8 = "ly",
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",
term_reset_cmd: [:0]const u8 = "/usr/bin/tput reset",
term_restore_cursor_cmd: []const u8 = "/usr/bin/tput cnorm",
tty: u8 = 2,
start_cmd: ?[]const u8 = null,
text_in_center: bool = false,
vi_default_mode: ViMode = .normal,
vi_mode: bool = false,
wayland_cmd: []const u8 = build_options.data_directory ++ "/wsetup.sh",
waylandsessions: []const u8 = "/usr/share/wayland-sessions",
x_cmd: []const u8 = "/usr/bin/X",
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",
x_cmd_setup: []const u8 = build_options.data_directory ++ "/xsetup.sh",
xauth_cmd: []const u8 = "/usr/bin/xauth",
xsessions: []const u8 = "/usr/share/xsessions",
xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions",

View File

@@ -1,16 +1,35 @@
//
// NOTE: After editing this file, please run `/res/lang/normalize_lang_files.py`
// to update all the language files accordingly.
//
authenticating: []const u8 = "authenticating...",
brightness_down: []const u8 = "decrease brightness",
brightness_up: []const u8 = "increase brightness",
capslock: []const u8 = "capslock",
custom: []const u8 = "custom",
err_alloc: []const u8 = "failed memory allocation",
err_args: []const u8 = "unable to parse command line arguments",
err_autologin_session: []const u8 = "autologin session not found",
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_console_dev: []const u8 = "failed to access console",
err_clock_too_long: []const u8 = "clock string too long",
err_config: []const u8 = "unable to parse config file",
err_crawl: []const u8 = "failed to crawl session directories",
err_dgn_oob: []const u8 = "log message",
err_domain: []const u8 = "invalid domain",
err_empty_password: []const u8 = "empty password not allowed",
err_envlist: []const u8 = "failed to get envlist",
err_get_active_tty: []const u8 = "failed to get active tty",
err_hibernate: []const u8 = "failed to execute hibernate command",
err_hostname: []const u8 = "failed to get hostname",
err_mcookie: []const u8 = "mcookie command failed",
err_inactivity: []const u8 = "failed to execute inactivity command",
err_lock_state: []const u8 = "failed to get lock state",
err_log: []const u8 = "failed to open log file",
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",
@@ -32,7 +51,13 @@ 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_sleep: []const u8 = "failed to execute sleep command",
err_start: []const u8 = "failed to execute start command",
err_battery: []const u8 = "failed to load battery status",
err_switch_tty: []const u8 = "failed to switch tty",
err_tty_ctrl: []const u8 = "tty control transfer failed",
err_no_users: []const u8 = "no users found",
err_uid_range: []const u8 = "failed to dynamically get uid range",
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",
@@ -40,17 +65,19 @@ 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",
hibernate: []const u8 = "hibernate",
insert: []const u8 = "insert",
login: []const u8 = "login:",
login: []const u8 = "login",
logout: []const u8 = "logged out",
no_x11_support: []const u8 = "x11 support disabled at compile-time",
normal: []const u8 = "normal",
numlock: []const u8 = "numlock",
other: []const u8 = "other",
password: []const u8 = "password:",
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",
xinitrc: [:0]const u8 = "xinitrc",

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

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

View File

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

28
src/config/SavedUsers.zig Normal file
View File

@@ -0,0 +1,28 @@
const std = @import("std");
const SavedUsers = @This();
const User = struct {
username: []const u8,
session_index: usize,
first_run: bool,
allocated_username: bool,
};
user_list: std.ArrayList(User),
last_username_index: ?usize,
pub fn init() SavedUsers {
return .{
.user_list = .empty,
.last_username_index = null,
};
}
pub fn deinit(self: *SavedUsers, allocator: std.mem.Allocator) void {
for (self.user_list.items) |user| {
if (user.allocated_username) allocator.free(user.username);
}
self.user_list.deinit(allocator);
}

View File

@@ -1,30 +1,250 @@
// The migrator ensures compatibility with older configuration files
// Properties removed or changed since 0.6.0
// Color codes interpreted differently since 1.1.0
const std = @import("std");
const ini = @import("zigini");
const Save = @import("Save.zig");
const Config = @import("Config.zig");
const OldSave = @import("OldSave.zig");
const SavedUsers = @import("SavedUsers.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
pub fn tryMigrateSaveFile(user_buf: *[32]u8, path: []const u8) Save {
var save = Save{};
const Color = TerminalBuffer.Color;
const Styling = TerminalBuffer.Styling;
var file = std.fs.openFileAbsolute(path, .{}) catch return save;
defer file.close();
const color_properties = [_][]const u8{
"bg",
"border_fg",
"cmatrix_fg",
"colormix_col1",
"colormix_col2",
"colormix_col3",
"error_bg",
"error_fg",
"fg",
};
const reader = file.reader();
var set_color_properties =
[_]bool{ false, false, false, false, false, false, false, false, false };
var user_fbs = std.io.fixedBufferStream(user_buf);
reader.streamUntilDelimiter(user_fbs.writer(), '\n', 32) catch return save;
const user = user_fbs.getWritten();
if (user.len > 0) save.user = user;
const removed_properties = [_][]const u8{
"wayland_specifier",
"max_desktop_len",
"max_login_len",
"max_password_len",
"mcookie_cmd",
"term_reset_cmd",
"term_restore_cursor_cmd",
"x_cmd_setup",
"wayland_cmd",
"console_dev",
"load",
};
var session_buf: [20]u8 = undefined;
var session_fbs = std.io.fixedBufferStream(&session_buf);
reader.streamUntilDelimiter(session_fbs.writer(), '\n', 20) catch {};
var temporary_allocator = std.heap.page_allocator;
const session_index_str = session_fbs.getWritten();
var session_index: ?u64 = null;
if (session_index_str.len > 0) {
session_index = std.fmt.parseUnsigned(u64, session_index_str, 10) catch return save;
pub var auto_eight_colors: bool = true;
pub var maybe_animate: ?bool = null;
pub var maybe_save_file: ?[]const u8 = null;
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");
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",
};
return mapped_field;
}
inline for (color_properties, &set_color_properties) |property, *status| {
if (std.mem.eql(u8, field.key, property)) {
// Color has been set; it won't be overwritten if we default to eight-color output
status.* = true;
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
// If they're all using eight-color codes, we start in eight-color mode
const color = std.fmt.parseInt(u16, field.value, 0) catch {
auto_eight_colors = false;
return field;
};
const color_no_styling = color & 0x00FF;
const styling_only = color & 0xFF00;
// If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM,
// we have an invalid color, so do not use eight-color mode
if (color_no_styling > 0x0008 or styling_only > 0x8000) auto_eight_colors = false;
return 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";
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",
};
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;
return null;
}
inline for (removed_properties) |property| {
if (std.mem.eql(u8, field.key, property)) {
// The options don't exist anymore
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";
} else if (std.mem.eql(u8, field.value, "false")) {
mapped_field.value = "none";
}
return mapped_field;
}
if (std.mem.eql(u8, field.key, "full_color")) {
// If color mode is defined, definitely don't set it automatically
auto_eight_colors = false;
return 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(config: *Config) void {
if (maybe_animate) |animate| {
if (!animate) config.*.animation = .none;
}
if (auto_eight_colors) {
// Valid config file predates true-color mode
// Will use eight-color output instead
config.full_color = false;
// We cannot rely on Config defaults when in eight-color mode,
// because they will appear as undesired colors.
// Instead set color properties to matching eight-color codes
config.doom_top_color = Color.ECOL_RED;
config.doom_middle_color = Color.ECOL_YELLOW;
config.doom_bottom_color = Color.ECOL_WHITE;
config.cmatrix_head_col = Styling.BOLD | Color.ECOL_WHITE;
// These may be in the config, so only change those which were not set
if (!set_color_properties[0]) config.bg = Color.DEFAULT;
if (!set_color_properties[1]) config.border_fg = Color.ECOL_WHITE;
if (!set_color_properties[2]) config.cmatrix_fg = Color.ECOL_GREEN;
if (!set_color_properties[3]) config.colormix_col1 = Color.ECOL_RED;
if (!set_color_properties[4]) config.colormix_col2 = Color.ECOL_BLUE;
if (!set_color_properties[5]) config.colormix_col3 = Color.ECOL_BLACK;
if (!set_color_properties[6]) config.error_bg = Color.DEFAULT;
if (!set_color_properties[7]) config.error_fg = Styling.BOLD | Color.ECOL_RED;
if (!set_color_properties[8]) config.fg = Color.ECOL_WHITE;
}
}
pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave {
var save = OldSave{};
if (maybe_save_file) |path| {
defer temporary_allocator.free(path);
var file = std.fs.openFileAbsolute(path, .{}) catch return save;
defer file.close();
var file_buffer: [64]u8 = undefined;
var file_reader = file.reader(&file_buffer);
var reader = &file_reader.interface;
var user_writer = std.Io.Writer.fixed(user_buf);
var written = reader.streamDelimiter(&user_writer, '\n') catch return save;
if (written > 0) save.user = user_buf[0..written];
var session_buf: [20]u8 = undefined;
var session_writer = std.Io.Writer.fixed(&session_buf);
written = reader.streamDelimiter(&session_writer, '\n') catch return save;
var session_index: ?usize = null;
if (written > 0) {
session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return save;
}
save.session_index = session_index;
}
save.session_index = session_index;
return save;
}
pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, save_ini: *ini.Ini(OldSave), path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !bool {
var old_save_file_exists = true;
var user_buf: [32]u8 = undefined;
const save = save_ini.readFileToStruct(path, .{
.fieldHandler = null,
.comment_characters = "#",
}) catch no_save_file: {
old_save_file_exists = false;
break :no_save_file tryMigrateFirstSaveFile(&user_buf);
};
if (!old_save_file_exists) return false;
// Add all other users to the list
for (usernames, 0..) |username, i| {
if (save.user) |user| {
if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i;
}
try saved_users.user_list.append(allocator, .{
.username = username,
.session_index = save.session_index orelse 0,
.first_run = false,
.allocated_username = false,
});
}
return true;
}

View File

@@ -1,7 +1,11 @@
const std = @import("std");
pub const Animation = enum {
none,
doom,
matrix,
colormix,
gameoflife,
dur_file,
};
pub const DisplayServer = enum {
@@ -9,10 +13,43 @@ pub const DisplayServer = enum {
shell,
xinitrc,
x11,
custom,
};
pub const Input = enum {
info_line,
session,
login,
password,
/// Moves the current Input forwards by one entry. If `reverse`, then the Input
/// moves backwards. If `wrap` is true, then the entry will wrap back around
pub fn move(self: *Input, reverse: bool, wrap: bool) void {
const maxNum = @typeInfo(Input).@"enum".fields.len - 1;
const selfNum = @intFromEnum(self.*);
if (reverse) {
if (wrap) {
self.* = @enumFromInt(selfNum -% 1);
} else if (selfNum != 0) {
self.* = @enumFromInt(selfNum - 1);
}
} else {
if (wrap) {
self.* = @enumFromInt(selfNum +% 1);
} else if (selfNum != maxNum) {
self.* = @enumFromInt(selfNum + 1);
}
}
}
};
pub const ViMode = enum {
normal,
insert,
};
pub const Bigclock = enum {
none,
en,
fa,
};

View File

@@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const UidRange = @import("UidRange.zig");
pub const termbox = @import("termbox2");
@@ -9,111 +9,404 @@ pub const pam = @cImport({
});
pub const utmp = @cImport({
@cInclude("utmp.h");
@cInclude("utmpx.h");
});
// Exists for X11 support only
pub const xcb = @cImport({
@cInclude("xcb/xcb.h");
});
pub const c_size = u64;
pub const c_uid = u32;
pub const c_gid = u32;
pub const c_time = c_long;
pub const tm = extern struct {
tm_sec: c_int,
tm_min: c_int,
tm_hour: c_int,
tm_mday: c_int,
tm_mon: c_int,
tm_year: c_int,
tm_wday: c_int,
tm_yday: c_int,
tm_isdst: c_int,
};
pub const passwd = extern struct {
pw_name: [*:0]u8,
pw_passwd: [*:0]u8,
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("sys/types.h");
@cInclude("login_cap.h");
}
});
pw_uid: c_uid,
pw_gid: c_gid,
pw_gecos: [*:0]u8,
pw_dir: [*:0]u8,
pw_shell: [*:0]u8,
const stdlib = @cImport({
@cInclude("stdlib.h");
});
const unistd = @cImport({
@cInclude("unistd.h");
});
const grp = @cImport({
@cInclude("grp.h");
});
const system_time = @cImport({
@cInclude("sys/time.h");
});
const time = @cImport({
@cInclude("time.h");
});
pub const TimeOfDay = struct {
seconds: i64,
microseconds: i64,
};
pub const VT_ACTIVATE: c_int = 0x5606;
pub const VT_WAITACTIVE: c_int = 0x5607;
pub const UsernameEntry = struct {
username: ?[]const u8,
uid: std.posix.uid_t,
gid: std.posix.gid_t,
home: ?[]const u8,
shell: ?[]const u8,
passwd_struct: [*c]pwd.passwd,
};
pub const KDGETLED: c_int = 0x4B31;
pub const KDSETLED: c_int = 0x4B32;
pub const KDGKBLED: c_int = 0x4B64;
pub const KDSKBLED: c_int = 0x4B65;
// Contains the platform-specific code
fn PlatformStruct() type {
return switch (builtin.os.tag) {
.linux => struct {
pub const kd = @cImport({
@cInclude("sys/kd.h");
});
pub const LED_NUM: c_int = 0x02;
pub const LED_CAP: c_int = 0x04;
pub const vt = @cImport({
@cInclude("sys/vt.h");
});
pub const K_NUMLOCK: c_int = 0x02;
pub const K_CAPSLOCK: c_int = 0x04;
pub const LedState = c_char;
pub const get_led_state = kd.KDGKBLED;
pub const set_led_state = kd.KDSKBLED;
pub const numlock_led = kd.K_NUMLOCK;
pub const capslock_led = kd.K_CAPSLOCK;
pub const vt_activate = vt.VT_ACTIVATE;
pub const vt_waitactive = vt.VT_WAITACTIVE;
pub extern "c" fn localtime(timer: *const c_time) *tm;
pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, timeptr: *const tm) c_size;
pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
pub extern "c" fn putenv(name: [*:0]u8) c_int;
pub extern "c" fn getuid() c_uid;
pub extern "c" fn getpwnam(name: [*:0]const u8) ?*passwd;
pub extern "c" fn endpwent() void;
pub extern "c" fn setusershell() void;
pub extern "c" fn getusershell() [*:0]u8;
pub extern "c" fn endusershell() void;
pub extern "c" fn initgroups(user: [*:0]const u8, group: c_gid) c_int;
const SYSTEMD_HOMED_UID_MIN = 60001;
const SYSTEMD_HOMED_UID_MAX = 60513;
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 {
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
const status = grp.initgroups(username, @intCast(entry.gid));
if (status != 0) return error.GroupInitializationFailed;
std.posix.setgid(@intCast(entry.gid)) catch return error.SetUserGidFailed;
std.posix.setuid(@intCast(entry.uid)) catch return error.SetUserUidFailed;
}
// Procedure:
// 1. Open /proc/self/stat to retrieve the tty_nr field
// 2. Parse the tty_nr field to extract the major and minor device
// numbers
// 3. Then, read every /sys/class/tty/[dir]/dev, where [dir] is
// every sub-directory
// 4. Finally, compare the major and minor device numbers with the
// extracted values. If they correspond, parse [dir] to get the
// TTY ID
pub fn getActiveTtyImpl(allocator: std.mem.Allocator) !u8 {
var file_buffer: [256]u8 = undefined;
var tty_major: u16 = undefined;
var tty_minor: u16 = undefined;
{
var file = try std.fs.openFileAbsolute("/proc/self/stat", .{});
defer file.close();
var reader = file.reader(&file_buffer);
var buffer: [1024]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer);
var iterator = std.mem.splitScalar(u8, buffer[0..read], ' ');
var fields: [52][]const u8 = undefined;
var index: usize = 0;
while (iterator.next()) |field| {
fields[index] = field;
index += 1;
}
const tty_nr = try std.fmt.parseInt(u16, fields[6], 10);
tty_major = tty_nr / 256;
tty_minor = tty_nr % 256;
}
var directory = try std.fs.openDirAbsolute("/sys/class/tty", .{ .iterate = true });
defer directory.close();
var iterator = directory.iterate();
while (try iterator.next()) |entry| {
const path = try std.fmt.allocPrint(allocator, "/sys/class/tty/{s}/dev", .{entry.name});
defer allocator.free(path);
var file = try std.fs.openFileAbsolute(path, .{});
defer file.close();
var reader = file.reader(&file_buffer);
var buffer: [16]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer);
var device_iterator = std.mem.splitScalar(u8, buffer[0..(read - 1)], ':');
const device_major_str = device_iterator.next() orelse continue;
const device_minor_str = device_iterator.next() orelse continue;
const device_major = try std.fmt.parseInt(u8, device_major_str, 10);
const device_minor = try std.fmt.parseInt(u8, device_minor_str, 10);
if (device_major == tty_major and device_minor == tty_minor) {
const tty_id_str = entry.name["tty".len..];
return try std.fmt.parseInt(u8, tty_id_str, 10);
}
}
return error.NoTtyFound;
}
// This is very bad parsing, but we only need to get 2 values..
// and the format of the file seems to be standard? So this should
// be fine...
pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange {
const login_defs_file = try std.fs.cwd().openFile(file_path, .{});
defer login_defs_file.close();
const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16));
defer allocator.free(login_defs_buffer);
var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n');
var uid_range = UidRange{};
var nameFound = false;
while (iterator.next()) |line| {
const trimmed_line = std.mem.trim(u8, line, " \n\r\t");
if (std.mem.startsWith(u8, trimmed_line, "UID_MIN")) {
uid_range.uid_min = try parseValue(std.posix.uid_t, "UID_MIN", trimmed_line);
nameFound = true;
} else if (std.mem.startsWith(u8, trimmed_line, "UID_MAX")) {
uid_range.uid_max = try parseValue(std.posix.uid_t, "UID_MAX", trimmed_line);
nameFound = true;
}
}
if (!nameFound) return error.UidNameNotFound;
// This code assumes the OS has a login.defs file with UID_MIN
// and UID_MAX values defined in it, which should be the case
// for most systemd-based Linux distributions out there.
// This should be a good enough safeguard for now, as there's
// no reliable (and clean) way to check for systemd support
if (uid_range.uid_min > SYSTEMD_HOMED_UID_MIN) {
uid_range.uid_min = SYSTEMD_HOMED_UID_MIN;
}
if (uid_range.uid_max < SYSTEMD_HOMED_UID_MAX) {
uid_range.uid_max = SYSTEMD_HOMED_UID_MAX;
}
return uid_range;
}
fn parseValue(comptime T: type, name: []const u8, buffer: []const u8) !T {
var iterator = std.mem.splitAny(u8, buffer, " \t");
var maybe_value: ?T = null;
while (iterator.next()) |slice| {
// Skip the slice if it's empty (whitespace) or is the name of the
// property (e.g. UID_MIN or UID_MAX)
if (slice.len == 0 or std.mem.eql(u8, slice, name)) continue;
maybe_value = std.fmt.parseInt(T, slice, 10) catch continue;
}
return maybe_value orelse error.ValueNotFound;
}
fn readBuffer(reader: *std.Io.Reader, buffer: []u8) !usize {
var bytes_read: usize = 0;
var byte: u8 = try reader.takeByte();
while (byte != 0 and bytes_read < buffer.len) {
buffer[bytes_read] = byte;
bytes_read += 1;
byte = reader.takeByte() catch break;
}
return bytes_read;
}
},
.freebsd => struct {
pub const kbio = @cImport({
@cInclude("sys/kbio.h");
});
pub const consio = @cImport({
@cInclude("sys/consio.h");
});
pub const LedState = c_int;
pub const get_led_state = kbio.KDGETLED;
pub const set_led_state = kbio.KDSETLED;
pub const numlock_led = kbio.LED_NUM;
pub const capslock_led = kbio.LED_CAP;
pub const vt_activate = consio.VT_ACTIVATE;
pub const vt_waitactive = consio.VT_WAITACTIVE;
const FREEBSD_UID_MIN = 1000;
const FREEBSD_UID_MAX = 32000;
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
// FreeBSD has initgroups() in unistd
const status = unistd.initgroups(username, @intCast(entry.gid));
if (status != 0) return error.GroupInitializationFailed;
// FreeBSD sets the GID and UID with setusercontext()
const result = pwd.setusercontext(null, entry.passwd_struct, @intCast(entry.uid), pwd.LOGIN_SETALL);
if (result != 0) return error.SetUserUidFailed;
}
pub fn getActiveTtyImpl(_: std.mem.Allocator) !u8 {
return error.FeatureUnimplemented;
}
pub fn getUserIdRange(_: std.mem.Allocator, _: []const u8) !UidRange {
return .{
// Hardcoded default values chosen from
// /usr/src/usr.sbin/pw/pw_conf.c
.uid_min = FREEBSD_UID_MIN,
.uid_max = FREEBSD_UID_MAX,
};
}
},
else => @compileError("Unsupported target: " ++ builtin.os.tag),
};
}
const platform_struct = PlatformStruct();
pub fn supportsUnicode() bool {
return builtin.os.tag == .linux or builtin.os.tag == .freebsd;
}
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) []u8 {
const timer = std.time.timestamp();
const tm_info = localtime(&timer);
const len = strftime(buf, buf.len, format, tm_info);
if (len < 0) return error.CannotGetFormattedTime;
const tm_info = time.localtime(&timer);
const len = time.strftime(buf, buf.len, format, tm_info);
return buf[0..len];
}
pub fn getLockState(console_dev: [:0]const u8) !struct {
pub fn getTimeOfDay() !TimeOfDay {
var tv: system_time.timeval = undefined;
const status = system_time.gettimeofday(&tv, null);
if (status != 0) return error.FailedToGetTimeOfDay;
return .{
.seconds = @intCast(tv.tv_sec),
.microseconds = @intCast(tv.tv_usec),
};
}
pub fn getActiveTty(allocator: std.mem.Allocator) !u8 {
return platform_struct.getActiveTtyImpl(allocator);
}
pub fn switchTty(tty: u8) !void {
var status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.vt_activate, tty);
if (status != 0) return error.FailedToActivateTty;
status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.vt_waitactive, tty);
if (status != 0) return error.FailedToWaitForActiveTty;
}
pub fn getLockState() !struct {
numlock: bool,
capslock: bool,
} {
const fd = std.c.open(console_dev, .{ .ACCMODE = .RDONLY });
if (fd < 0) return error.CannotOpenConsoleDev;
defer _ = std.c.close(fd);
var numlock = false;
var capslock = false;
if (builtin.os.tag.isBSD()) {
var led: c_int = undefined;
_ = std.c.ioctl(fd, KDGETLED, &led);
numlock = (led & LED_NUM) != 0;
capslock = (led & LED_CAP) != 0;
} else {
var led: c_char = undefined;
_ = std.c.ioctl(fd, KDGKBLED, &led);
numlock = (led & K_NUMLOCK) != 0;
capslock = (led & K_CAPSLOCK) != 0;
}
var led: platform_struct.LedState = undefined;
const status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.get_led_state, &led);
if (status != 0) return error.FailedToGetLockState;
return .{
.numlock = numlock,
.capslock = capslock,
.numlock = (led & platform_struct.numlock_led) != 0,
.capslock = (led & platform_struct.capslock_led) != 0,
};
}
pub fn setNumlock(val: bool) !void {
var led: c_char = undefined;
_ = std.c.ioctl(0, KDGKBLED, &led);
var led: platform_struct.LedState = undefined;
var status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.get_led_state, &led);
if (status != 0) return error.FailedToGetNumlock;
const numlock = (led & K_NUMLOCK) != 0;
const numlock = (led & platform_struct.numlock_led) != 0;
if (numlock != val) {
const status = std.c.ioctl(std.posix.STDIN_FILENO, KDSKBLED, led ^ K_NUMLOCK);
status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.set_led_state, led ^ platform_struct.numlock_led);
if (status != 0) return error.FailedToSetNumlock;
}
}
pub fn setUserContext(allocator: std.mem.Allocator, entry: UsernameEntry) !void {
const username_z = try allocator.dupeZ(u8, entry.username.?);
defer allocator.free(username_z);
return platform_struct.setUserContextImpl(username_z.ptr, entry);
}
pub fn setUserShell(entry: *UsernameEntry) void {
unistd.setusershell();
const shell = unistd.getusershell();
entry.shell = std.mem.span(shell);
unistd.endusershell();
}
pub fn setEnvironmentVariable(allocator: std.mem.Allocator, name: []const u8, value: []const u8, replace: bool) !void {
const name_z = try allocator.dupeZ(u8, name);
defer allocator.free(name_z);
const value_z = try allocator.dupeZ(u8, value);
defer allocator.free(value_z);
const status = stdlib.setenv(name_z.ptr, value_z.ptr, @intFromBool(replace));
if (status != 0) return error.SetEnvironmentVariableFailed;
}
pub fn putEnvironmentVariable(name_and_value: [*c]u8) !void {
const status = stdlib.putenv(name_and_value);
if (status != 0) return error.PutEnvironmentVariableFailed;
}
pub fn getNextUsernameEntry() ?UsernameEntry {
const entry = pwd.getpwent();
if (entry == null) return null;
return .{
.username = if (entry.*.pw_name) |name| std.mem.span(name) else null,
.uid = @intCast(entry.*.pw_uid),
.gid = @intCast(entry.*.pw_gid),
.home = if (entry.*.pw_dir) |dir| std.mem.span(dir) else null,
.shell = if (entry.*.pw_shell) |shell| std.mem.span(shell) else null,
.passwd_struct = entry,
};
}
pub fn getUsernameEntry(username: [:0]const u8) ?UsernameEntry {
const entry = pwd.getpwnam(username);
if (entry == null) return null;
return .{
.username = if (entry.*.pw_name) |name| std.mem.span(name) else null,
.uid = @intCast(entry.*.pw_uid),
.gid = @intCast(entry.*.pw_gid),
.home = if (entry.*.pw_dir) |dir| std.mem.span(dir) else null,
.shell = if (entry.*.pw_shell) |shell| std.mem.span(shell) else null,
.passwd_struct = entry,
};
}
pub fn closePasswordDatabase() void {
pwd.endpwent();
}
// This is very bad parsing, but we only need to get 2 values... and the format
// of the file doesn't seem to be standard? So this should be fine...
pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange {
return platform_struct.getUserIdRange(allocator, file_path);
}

File diff suppressed because it is too large Load Diff

61
src/tui/Animation.zig Normal file
View File

@@ -0,0 +1,61 @@
const Animation = @This();
const VTable = struct {
deinit_fn: *const fn (ptr: *anyopaque) void,
realloc_fn: *const fn (ptr: *anyopaque) anyerror!void,
draw_fn: *const fn (ptr: *anyopaque) void,
};
pointer: *anyopaque,
vtable: VTable,
pub fn init(
pointer: anytype,
comptime deinit_fn: fn (ptr: @TypeOf(pointer)) void,
comptime realloc_fn: fn (ptr: @TypeOf(pointer)) anyerror!void,
comptime draw_fn: fn (ptr: @TypeOf(pointer)) void,
) Animation {
const Pointer = @TypeOf(pointer);
const Impl = struct {
pub fn deinitImpl(ptr: *anyopaque) void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(.always_inline, deinit_fn, .{impl});
}
pub fn reallocImpl(ptr: *anyopaque) anyerror!void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(.always_inline, realloc_fn, .{impl});
}
pub fn drawImpl(ptr: *anyopaque) void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(.always_inline, draw_fn, .{impl});
}
const vtable = VTable{
.deinit_fn = deinitImpl,
.realloc_fn = reallocImpl,
.draw_fn = drawImpl,
};
};
return .{
.pointer = pointer,
.vtable = Impl.vtable,
};
}
pub fn deinit(self: *Animation) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(.auto, self.vtable.deinit_fn, .{impl});
}
pub fn realloc(self: *Animation) anyerror!void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(.auto, self.vtable.realloc_fn, .{impl});
}
pub fn draw(self: *Animation) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
return @call(.auto, self.vtable.draw_fn, .{impl});
}

23
src/tui/Cell.zig Normal file
View File

@@ -0,0 +1,23 @@
const interop = @import("../interop.zig");
const termbox = interop.termbox;
const Cell = @This();
ch: u32,
fg: u32,
bg: u32,
pub fn init(ch: u32, fg: u32, bg: u32) Cell {
return .{
.ch = ch,
.fg = fg,
.bg = bg,
};
}
pub fn put(self: Cell, x: usize, y: usize) void {
if (self.ch == 0) return;
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), self.ch, self.fg, self.bg);
}

View File

@@ -1,22 +1,66 @@
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 Cell = @import("Cell.zig");
const Random = std.rand.Random;
const Random = std.Random;
const termbox = interop.termbox;
const TerminalBuffer = @This();
pub const InitOptions = struct {
fg: u32,
bg: u32,
border_fg: u32,
margin_box_h: u8,
margin_box_v: u8,
input_len: u8,
};
pub const Styling = struct {
pub const BOLD = termbox.TB_BOLD;
pub const UNDERLINE = termbox.TB_UNDERLINE;
pub const REVERSE = termbox.TB_REVERSE;
pub const ITALIC = termbox.TB_ITALIC;
pub const BLINK = termbox.TB_BLINK;
pub const HI_BLACK = termbox.TB_HI_BLACK;
pub const BRIGHT = termbox.TB_BRIGHT;
pub const DIM = termbox.TB_DIM;
};
pub const Color = struct {
pub const DEFAULT = 0x00000000;
pub const TRUE_BLACK = Styling.HI_BLACK;
pub const TRUE_RED = 0x00FF0000;
pub const TRUE_GREEN = 0x0000FF00;
pub const TRUE_YELLOW = 0x00FFFF00;
pub const TRUE_BLUE = 0x000000FF;
pub const TRUE_MAGENTA = 0x00FF00FF;
pub const TRUE_CYAN = 0x0000FFFF;
pub const TRUE_WHITE = 0x00FFFFFF;
pub const TRUE_DIM_RED = 0x00800000;
pub const TRUE_DIM_GREEN = 0x00008000;
pub const TRUE_DIM_YELLOW = 0x00808000;
pub const TRUE_DIM_BLUE = 0x00000080;
pub const TRUE_DIM_MAGENTA = 0x00800080;
pub const TRUE_DIM_CYAN = 0x00008080;
pub const TRUE_DIM_WHITE = 0x00C0C0C0;
pub const ECOL_BLACK = 1;
pub const ECOL_RED = 2;
pub const ECOL_GREEN = 3;
pub const ECOL_YELLOW = 4;
pub const ECOL_BLUE = 5;
pub const ECOL_MAGENTA = 6;
pub const ECOL_CYAN = 7;
pub const ECOL_WHITE = 8;
};
random: Random,
width: u64,
height: u64,
buffer: [*]termbox.tb_cell,
fg: u8,
bg: u8,
border_fg: u8,
width: usize,
height: usize,
fg: u32,
bg: u32,
border_fg: u32,
box_chars: struct {
left_up: u32,
left_down: u32,
@@ -27,26 +71,24 @@ box_chars: struct {
left: u32,
right: u32,
},
labels_max_length: u64,
box_x: u64,
box_y: u64,
box_width: u64,
box_height: u64,
labels_max_length: usize,
box_x: usize,
box_y: usize,
box_width: usize,
box_height: usize,
margin_box_v: u8,
margin_box_h: u8,
blank_cell: Cell,
pub fn init(config: Config, labels_max_length: u64) TerminalBuffer {
var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp()));
pub fn init(options: InitOptions, labels_max_length: usize, random: Random) TerminalBuffer {
return .{
.random = prng.random(),
.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()) .{
.fg = options.fg,
.bg = options.bg,
.border_fg = options.border_fg,
.box_chars = if (interop.supportsUnicode()) .{
.left_up = 0x250C,
.left_down = 0x2514,
.right_up = 0x2510,
@@ -68,35 +110,46 @@ pub fn init(config: Config, labels_max_length: u64) TerminalBuffer {
.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,
.box_width = (2 * options.margin_box_h) + options.input_len + 1 + labels_max_length,
.box_height = 7 + (2 * options.margin_box_v),
.margin_box_v = options.margin_box_v,
.margin_box_h = options.margin_box_h,
.blank_cell = Cell.init(' ', options.fg, options.bg),
};
}
pub fn cascade(self: TerminalBuffer) bool {
var changes = false;
var changed = false;
var y = self.height - 2;
while (y > 0) : (y -= 1) {
for (0..self.width) |x| {
const c: u8 = @truncate(self.buffer[(y - 1) * self.width + x].ch);
if (std.ascii.isWhitespace(c)) continue;
var cell: ?*termbox.tb_cell = undefined;
var cell_under: ?*termbox.tb_cell = undefined;
const c_under: u8 = @truncate(self.buffer[y * self.width + x].ch);
if (!std.ascii.isWhitespace(c_under)) continue;
_ = termbox.tb_get_cell(@intCast(x), @intCast(y - 1), 1, &cell);
_ = termbox.tb_get_cell(@intCast(x), @intCast(y), 1, &cell_under);
changes = true;
// This shouldn't happen under normal circumstances, but because
// this is a *secret* animation, there's no need to care that much
if (cell == null or cell_under == null) continue;
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;
self.buffer[y * self.width + x] = self.buffer[(y - 1) * self.width + x];
self.buffer[(y - 1) * self.width + x].ch = ' ';
_ = 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 changes;
return changed;
}
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
@@ -115,76 +168,93 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool)
_ = 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);
var c1 = Cell.init(self.box_chars.top, self.border_fg, self.bg);
var c2 = Cell.init(self.box_chars.bottom, self.border_fg, self.bg);
for (0..self.box_width) |i| {
_ = utils.putCell(@intCast(x1 + i), @intCast(y1 - 1), &c1);
_ = utils.putCell(@intCast(x1 + i), @intCast(y2), &c2);
c1.put(x1 + i, y1 - 1);
c2.put(x1 + i, y2);
}
c1.ch = self.box_chars.left;
c2.ch = self.box_chars.right;
for (0..self.box_height) |i| {
_ = utils.putCell(@intCast(x1 - 1), @intCast(y1 + i), &c1);
_ = utils.putCell(@intCast(x2), @intCast(y1 + i), &c2);
c1.put(x1 - 1, y1 + i);
c2.put(x2, y1 + i);
}
}
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(@intCast(x1 + x), @intCast(y1 + y), &blank);
self.blank_cell.put(x1 + x, y1 + y);
}
}
}
}
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
x: u64,
y: u64,
visible_length: u64,
start_x: usize,
x: usize,
y: usize,
full_visible_length: usize,
visible_length: usize,
} {
const x = self.box_x + self.margin_box_h + self.labels_max_length + 1;
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: u64, y: u64) void {
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: u32, bg: u32) 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, self.fg, self.bg);
var i: c_int = @intCast(x);
while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) {
_ = termbox.tb_set_cell(i, yc, codepoint, fg, bg);
}
}
pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64, max_length: u64) void {
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);
var i: c_int = @intCast(x);
while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) {
if (i - @as(c_int, @intCast(x)) >= max_length) break;
_ = termbox.tb_set_cell(i, yc, codepoint, self.fg, self.bg);
}
}
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void {
const yc: c_int = @intCast(y);
const cell = utils.initCell(char, self.fg, self.bg);
for (0..length) |xx| _ = utils.putCell(@intCast(x + xx), yc, &cell);
pub fn drawCharMultiple(self: TerminalBuffer, char: u32, x: usize, y: usize, length: usize) void {
const cell = Cell.init(char, self.fg, self.bg);
for (0..length) |xx| cell.put(x + xx, y);
}
// Every codepoint is assumed to have a width of 1.
// Since Ly is normally 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: c_int = 0;
while (utf8.nextCodepoint()) |codepoint| i += termbox.tb_wcwidth(codepoint);
return @intCast(i);
}

View File

@@ -1,223 +0,0 @@
const std = @import("std");
const enums = @import("../../enums.zig");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Ini = @import("zigini").Ini;
const Lang = @import("../../config/Lang.zig");
const Allocator = std.mem.Allocator;
const EnvironmentList = std.ArrayList(Environment);
const DisplayServer = enums.DisplayServer;
const termbox = interop.termbox;
const Desktop = @This();
pub const Environment = struct {
entry_ini: ?Ini(Entry) = null,
name: [:0]const u8 = "",
xdg_session_desktop: [:0]const u8 = "",
xdg_desktop_names: ?[:0]const u8 = "",
cmd: []const u8 = "",
specifier: []const u8 = "",
display_server: DisplayServer = .wayland,
};
const DesktopEntry = struct {
Exec: []const u8 = "",
Name: [:0]const u8 = "",
DesktopNames: ?[]const u8 = null,
};
pub const Entry = struct { @"Desktop Entry": DesktopEntry = DesktopEntry{} };
allocator: Allocator,
buffer: *TerminalBuffer,
environments: EnvironmentList,
current: u64,
visible_length: u64,
x: u64,
y: u64,
lang: Lang,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64, lang: Lang) !Desktop {
return .{
.allocator = allocator,
.buffer = buffer,
.environments = try EnvironmentList.initCapacity(allocator, max_length),
.current = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.lang = lang,
};
}
pub fn deinit(self: Desktop) void {
for (self.environments.items) |*environment| {
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
if (environment.xdg_desktop_names) |desktop_name| self.allocator.free(desktop_name);
self.allocator.free(environment.xdg_session_desktop);
}
self.environments.deinit();
}
pub fn position(self: *Desktop, x: u64, y: u64, visible_length: u64) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
}
pub fn addEnvironment(self: *Desktop, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
for (desktop_names_z) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names_z;
}
errdefer {
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
}
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
errdefer self.allocator.free(session_desktop);
try self.environments.append(.{
.entry_ini = null,
.name = entry.Name,
.xdg_session_desktop = session_desktop,
.xdg_desktop_names = xdg_desktop_names,
.cmd = entry.Exec,
.specifier = switch (display_server) {
.wayland => self.lang.wayland,
.x11 => self.lang.x11,
else => self.lang.other,
},
.display_server = display_server,
});
self.current = self.environments.items.len - 1;
}
pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
const entry = entry_ini.data.@"Desktop Entry";
var xdg_desktop_names: ?[:0]const u8 = null;
if (entry.DesktopNames) |desktop_names| {
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
for (desktop_names_z) |*c| {
if (c.* == ';') c.* = ':';
}
xdg_desktop_names = desktop_names_z;
}
errdefer {
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
}
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
errdefer self.allocator.free(session_desktop);
try self.environments.append(.{
.entry_ini = entry_ini,
.name = entry.Name,
.xdg_session_desktop = session_desktop,
.xdg_desktop_names = xdg_desktop_names,
.cmd = entry.Exec,
.specifier = switch (display_server) {
.wayland => self.lang.wayland,
.x11 => self.lang.x11,
else => self.lang.other,
},
.display_server = display_server,
});
self.current = self.environments.items.len - 1;
}
pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void {
var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return;
defer iterable_directory.close();
var iterator = iterable_directory.iterate();
while (try iterator.next()) |item| {
if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue;
const entry_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ path, item.name });
defer self.allocator.free(entry_path);
var entry_ini = Ini(Entry).init(self.allocator);
_ = try entry_ini.readFileToStruct(entry_path);
errdefer entry_ini.deinit();
var xdg_session_desktop: []const u8 = undefined;
const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
if (maybe_desktop_names) |desktop_names| {
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
} else {
// if DesktopNames is empty, we'll take the name of the session file
xdg_session_desktop = std.fs.path.stem(item.name);
}
try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server);
}
}
pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, insert_mode: bool) void {
if (maybe_event) |event| blk: {
if (event.type != termbox.TB_EVENT_KEY) break :blk;
switch (event.key) {
termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(),
termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(),
else => {
if (!insert_mode) {
switch (event.ch) {
'h' => self.goLeft(),
'l' => self.goRight(),
else => {},
}
}
},
}
}
_ = termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y));
}
pub fn draw(self: Desktop) void {
const environment = self.environments.items[self.current];
const length = @min(environment.name.len, self.visible_length - 3);
if (length == 0) return;
const x = self.buffer.box_x + self.buffer.margin_box_h;
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
self.buffer.drawLabel(environment.specifier, x, y);
_ = termbox.tb_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);
self.buffer.drawLabel(environment.name, self.x + 2, self.y);
}
fn goLeft(self: *Desktop) void {
if (self.current == 0) {
self.current = self.environments.items.len - 1;
return;
}
self.current -= 1;
}
fn goRight(self: *Desktop) void {
if (self.current == self.environments.items.len - 1) {
self.current = 0;
return;
}
self.current += 1;
}

View File

@@ -1,28 +1,45 @@
const std = @import("std");
const utils = @import("../utils.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const generic = @import("generic.zig");
const Allocator = std.mem.Allocator;
const MessageLabel = generic.CyclableLabel(Message, Message);
const InfoLine = @This();
text: []const u8 = "",
width: u8 = 0,
const Message = struct {
width: u8,
text: []const u8,
bg: u32,
fg: u32,
};
pub fn setText(self: *InfoLine, text: []const u8) !void {
self.width = if (text.len > 0) try utils.strWidth(text) else 0;
self.text = text;
label: MessageLabel,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
return .{
.label = MessageLabel.init(allocator, buffer, drawItem, null, null),
};
}
pub fn draw(self: InfoLine, buffer: TerminalBuffer) void {
if (self.width > 0 and buffer.box_width > self.width) {
const label_y = buffer.box_y + buffer.margin_box_v;
const x = buffer.box_x + ((buffer.box_width - self.width) / 2);
buffer.drawLabel(self.text, x, label_y);
}
pub fn deinit(self: *InfoLine) void {
self.label.deinit();
}
pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void {
// draw over the area
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
if (text.len == 0) return;
try self.label.addItem(.{
.width = try TerminalBuffer.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);
@@ -31,3 +48,13 @@ pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void
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,70 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const enums = @import("../../enums.zig");
const Environment = @import("../../Environment.zig");
const generic = @import("generic.zig");
const UserList = @import("UserList.zig");
const Allocator = std.mem.Allocator;
const DisplayServer = enums.DisplayServer;
const Env = struct {
environment: Environment,
index: usize,
};
const EnvironmentLabel = generic.CyclableLabel(Env, *UserList);
const Session = @This();
label: EnvironmentLabel,
user_list: *UserList,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, user_list: *UserList) Session {
return .{
.label = EnvironmentLabel.init(allocator, buffer, drawItem, sessionChanged, user_list),
.user_list = user_list,
};
}
pub fn deinit(self: *Session) void {
for (self.label.list.items) |*env| {
if (env.environment.entry_ini) |*entry_ini| entry_ini.deinit();
if (env.environment.xdg_session_desktop_owned) {
self.label.allocator.free(env.environment.xdg_session_desktop.?);
}
}
self.label.deinit();
}
pub fn addEnvironment(self: *Session, environment: Environment) !void {
const env = Env{ .environment = environment, .index = self.label.list.items.len };
try self.label.addItem(env);
addedSession(env, self.user_list);
}
fn addedSession(env: Env, user_list: *UserList) void {
const user = user_list.label.list.items[user_list.label.current];
if (!user.first_run) return;
user.session_index.* = env.index;
}
fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void {
if (maybe_user_list) |user_list| {
user_list.label.list.items[user_list.label.current].session_index.* = env.index;
}
}
fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize) bool {
const length = @min(env.environment.name.len, label.visible_length - 3);
if (length == 0) return false;
const nx = if (label.text_in_center) (label.x + (label.visible_length - env.environment.name.len) / 2) else (label.x + 2);
label.first_char_x = nx + env.environment.name.len;
label.buffer.drawLabel(env.environment.specifier, x, y);
label.buffer.drawLabel(env.environment.name, nx, label.y);
return true;
}

View File

@@ -1,10 +1,9 @@
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 DynamicString = std.ArrayListUnmanaged(u8);
const termbox = interop.termbox;
@@ -13,15 +12,17 @@ const Text = @This();
allocator: Allocator,
buffer: *TerminalBuffer,
text: DynamicString,
end: u64,
cursor: u64,
visible_start: u64,
visible_length: u64,
x: u64,
y: u64,
end: usize,
cursor: usize,
visible_start: usize,
visible_length: usize,
x: usize,
y: usize,
masked: bool,
maybe_mask: ?u32,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Text {
const text = try DynamicString.initCapacity(allocator, max_length);
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u32) Text {
const text: DynamicString = .empty;
return .{
.allocator = allocator,
@@ -33,14 +34,16 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Tex
.visible_length = 0,
.x = 0,
.y = 0,
.masked = masked,
.maybe_mask = maybe_mask,
};
}
pub fn deinit(self: Text) void {
self.text.deinit();
pub fn deinit(self: *Text) void {
self.text.deinit(self.allocator);
}
pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void {
pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
@@ -78,10 +81,25 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !
}
}
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;
@@ -96,13 +114,6 @@ pub fn draw(self: Text) void {
self.buffer.drawLabel(visible_slice, self.x, self.y);
}
pub fn drawMasked(self: Text, mask: u8) void {
const length = @min(self.text.items.len, self.visible_length - 1);
if (length == 0) return;
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
}
pub fn clear(self: *Text) void {
self.text.clearRetainingCapacity();
self.end = 0;
@@ -142,7 +153,7 @@ fn backspace(self: *Text) void {
fn write(self: *Text, char: u8) !void {
if (char == 0) return;
try self.text.insert(self.cursor, char);
try self.text.insert(self.allocator, self.cursor, char);
self.end += 1;
self.goRight();

View File

@@ -0,0 +1,88 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const generic = @import("generic.zig");
const Session = @import("Session.zig");
const SavedUsers = @import("../../config/SavedUsers.zig");
const StringList = std.ArrayListUnmanaged([]const u8);
const Allocator = std.mem.Allocator;
pub const User = struct {
name: []const u8,
session_index: *usize,
allocated_index: bool,
first_run: bool,
};
const UserLabel = generic.CyclableLabel(User, *Session);
const UserList = @This();
label: UserLabel,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, usernames: StringList, saved_users: *SavedUsers, session: *Session) !UserList {
var userList = UserList{
.label = UserLabel.init(allocator, buffer, drawItem, usernameChanged, session),
};
for (usernames.items) |username| {
if (username.len == 0) continue;
var maybe_session_index: ?*usize = null;
var first_run = true;
for (saved_users.user_list.items) |*saved_user| {
if (std.mem.eql(u8, username, saved_user.username)) {
maybe_session_index = &saved_user.session_index;
first_run = saved_user.first_run;
break;
}
}
var allocated_index = false;
if (maybe_session_index == null) {
maybe_session_index = try allocator.create(usize);
maybe_session_index.?.* = 0;
allocated_index = true;
}
try userList.label.addItem(.{
.name = username,
.session_index = maybe_session_index.?,
.allocated_index = allocated_index,
.first_run = first_run,
});
}
return userList;
}
pub fn deinit(self: *UserList) void {
for (self.label.list.items) |user| {
if (user.allocated_index) {
self.label.allocator.destroy(user.session_index);
}
}
self.label.deinit();
}
pub fn getCurrentUsername(self: UserList) []const u8 {
return self.label.list.items[self.label.current].name;
}
fn usernameChanged(user: User, maybe_session: ?*Session) void {
if (maybe_session) |session| {
if (user.session_index.* >= session.label.list.items.len) return;
session.label.current = user.session_index.*;
}
}
fn drawItem(label: *UserLabel, user: User, _: usize, _: usize) bool {
const length = @min(user.name.len, label.visible_length - 3);
if (length == 0) return false;
const x = if (label.text_in_center) (label.x + (label.visible_length - user.name.len) / 2) else (label.x + 2);
label.first_char_x = x + user.name.len;
label.buffer.drawLabel(user.name, x, label.y);
return true;
}

View File

@@ -0,0 +1,117 @@
const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
return struct {
const Allocator = std.mem.Allocator;
const ItemList = std.ArrayListUnmanaged(ItemType);
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
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,
change_item_fn: ?ChangeItemFn,
change_item_arg: ?ChangeItemType,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType) Self {
return .{
.allocator = allocator,
.buffer = buffer,
.list = .empty,
.current = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.first_char_x = 0,
.text_in_center = false,
.draw_item_fn = draw_item_fn,
.change_item_fn = change_item_fn,
.change_item_arg = change_item_arg,
};
}
pub fn deinit(self: *Self) void {
self.list.deinit(self.allocator);
}
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(self.allocator, 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 {
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
if (self.change_item_fn) |change_item_fn| {
@call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg });
}
}
fn goRight(self: *Self) void {
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
if (self.change_item_fn) |change_item_fn| {
@call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg });
}
}
};
}

View File

@@ -1,26 +0,0 @@
const std = @import("std");
const interop = @import("../interop.zig");
const termbox = interop.termbox;
pub fn initCell(ch: u32, fg: u16, bg: u16) termbox.tb_cell {
return .{
.ch = ch,
.fg = fg,
.bg = bg,
};
}
pub fn putCell(x: i32, y: i32, cell: *const termbox.tb_cell) c_int {
return termbox.tb_set_cell(x, 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;
}