Compare commits

...

136 Commits

Author SHA1 Message Date
koru 32436d438e Update `err_crawl` for Russian lang (#1011)
See: https://codeberg.org/fairyglade/ly/pulls/997#issuecomment-15674144
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1011
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-06-06 11:54:29 +02:00
Wicin-134 ace79f76b5 Overhaul language files (#997)
- Added sr_Cyrl.ini as new Cyrillic  variant for the Serbian Language
- Added zh_TW.ini (Traditional Chinese)
- Completed ALL missing keys in EVERY translation

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/997
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-06-06 11:48:31 +02:00
AnErrupTion 35be66e66f
termbox2: Log init errors
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-30 12:17:25 +02:00
AnErrupTion 0ac11065f4
ly-kmsconvt: Set TERM=linux
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-30 12:17:06 +02:00
AnErrupTion 9d8ccf6709
Fix termbox already being initialised when reclaiming
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-23 12:31:05 +02:00
AnErrupTion 6f3fdc4708
Merge branch 'master' of codeberg.org:fairyglade/ly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-22 17:16:53 +02:00
AnErrupTion 0cee1c039a
ly-kmsconvt: Remove usage of gone --seats argument
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-22 17:15:17 +02:00
Louis Pate f03aca0379 Be specific about zig version in readme (#996)
## What are the changes about?

There have been quite a few issues about the required zig version which
have appeared on codeberg recently. Largely these issues are opened
(like mine, #995) because individuals attempt to build using development
versions of zig and not the tagged releases.

The lack of a locked version (only a minimum) creates these issues.
Barring a locking mechanism, which I do not feel is my place to
implement, we should be clear to newer developers in Zig that you need
this specific version, not a development/master versioned release.

This is WIP mainly because I feel that a version file would be a better solution
than a README note, and I don't have enough zig experience or knowledge of
this repo to make a guess at what is best for this scenario.

## What existing issue does this resolve?

N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have run `zig fmt` throughout my changes

Co-authored-by: Louis Pate <louispate@gmail.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/996
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-05-20 20:17:11 +02:00
AnErrupTion 0cd8b2ebfc
README: Add section for testing config changes (closes #994)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-18 19:46:28 +02:00
AnErrupTion 4c066ce564
Handle termbox2 errors
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-17 13:26:46 +02:00
AnErrupTion 05c1d4bece
Improve errors for lock state
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-16 10:33:06 +02:00
AnErrupTion 78794b3e10
Stream dur reading instead of reading all at once
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-15 23:22:50 +02:00
AnErrupTion afee1d9194
Remove TODO and stop forcing LLVM usage (again)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-15 21:46:20 +02:00
AnErrupTion 9b1965a3d8
Always build translate-c in Debug
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-15 21:42:13 +02:00
AnErrupTion 741e9e0345
Use correct naming convention for functions in DurFile.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-12 20:35:17 +02:00
AnErrupTion de8579854c
Use $EXECUTABLE_NAME in kmscon service
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-11 21:03:01 +02:00
AnErrupTion ee3196bab8
Fix labels_max_length calculation (closes #984)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-11 21:00:52 +02:00
AnErrupTion 9ff4ddd129
Improve keyboard handling (closes #982)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-10 13:55:19 +02:00
Titanium Brain b3830d5bb6 fix(dur): apply correct offset for animations bigger than the terminal (#966)
Animations bigger than the rendering area would have an alignment to the
top left instead of center.

## What are the changes about?

Fixes incorrect offset calculations for dur movies bigger than the screen.

## What existing issue does this resolve?
N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have run `zig fmt` throughout my changes

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/966
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-05-09 21:06:45 +02:00
Titanium Brain b8ae126623 Apply the typestate pattern to DurFormat (#972) 2026-05-04 12:34:32 +02:00
AnErrupTion 4db9295102
Fix building without X11
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-03 20:05:44 +02:00
AnErrupTion 864f5f2892
Implement syslog functionality (closes #
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-01 21:51:27 +02:00
AnErrupTion c50af66407
Fix log file race condition
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-01 20:54:54 +02:00
MartorSkull fdf241bed5 Add option to move the box relative to the screen size (#964)
## What are the changes about?

Added a new option in the configuration file for moving the box relative to the screen size.

```
box_h_position = 0.5
box_v_position = 0.5
```

The big clock is centered relative to the box. In the cases where it would be outside of the screen, it moves the box to fit in the screen.

## What existing issue does this resolve?

Add more options for personalization

## Examples

Normal usage:
```
box_h_position = 0.15
box_v_position = 0.35
```
![image](/attachments/6595dfa9-aade-45f4-887c-e5db7f8d5a89)

Clock would be outside of the screen vertically:
```
box_h_position = 0.15
box_v_position = -1.0
```
![image](/attachments/0d6bdcc4-e9dd-4671-a65d-b8b9f063ffb6)

Clock would be outside of the screen horizontally and vertically:
```
box_h_position = -1.0
box_v_position = -1.0
input_len = 3
```
![image](/attachments/630b07fd-b400-4e71-8a67-1baf3a6700a0)

Clock would be outside of the screen horizontally and vertically on the bottom left of the screen:
```
box_h_position = 2.0
box_v_position = 2.0
input_len = 3
```
![image](/attachments/28902967-11a8-4c02-a4c9-1b92f9a728ee)

## What existing issue does this resolve?

_Replace this with a reference to an existing issue, or N/A if there is none_

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have run `zig fmt` throughout my changes

Co-authored-by: AnErrupTion <anerruption+codeberg@disroot.org>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/964
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-05-01 20:09:34 +02:00
AnErrupTion 79eebd8ee0
Prefer std.log instead of stderr directly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-01 17:43:28 +02:00
AnErrupTion 3869bfd2f9
Add config validation argument (closes #969)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-01 17:40:04 +02:00
AnErrupTion 5905e054c5
Start Ly v1.5.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-05-01 07:47:48 +02:00
AnErrupTion 807f6d249a
Remove further & all @cImport() usage in interop
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-04-30 22:48:29 +02:00
AnErrupTion 15cd0c4779
Use SIGINT instead of SIGCHILD for TTY control transfer
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-04-30 14:38:56 +02:00
AnErrupTion 51c5c3ee0b
Fix waitpid() being interrupted by SIGCHLD
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-04-29 22:52:58 +02:00
AnErrupTion 59c07aa3ba
Fix xauth log not being flushed
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-04-28 19:35:21 +02:00
AnErrupTion 80d4b114f3
Fix 32-bit issues on Ly's side
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-04-26 18:37:33 +02:00
RadsammyT 4f45d92ea8 Fix: battery label positioning, custom keybinds not disappearing on `hide_key_hints = false` (#970)
## What are the changes about?

Fixes battery label positioning in regards to custom binds.
Since the label was on the top-left, it only accounted for the first line of built-in keybinds, and it didn't account for the other lines of custom ones.

This also fixes the custom keybinds not disappearing on `hide_key_hints = false`, which is my bad. whoops.

Also, with https://codeberg.org/fairyglade/ly/pulls/963 being a thing, we should probably think about deprecating this hardcoded battery label in favor of a custom label command, top-left by default.

## What existing issue does this resolve?

N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have run `zig fmt` throughout my changes

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/970
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-04-26 09:37:39 +02:00
AnErrupTion 5edf5251f6 Update to Zig 0.16.0 (#962)
Signed-off-by: AnErrupTion <anerruption@disroot.org>

## What are the changes about?

Ports the code base to Zig 0.16.0.

## What existing issue does this resolve?

N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have run `zig fmt` throughout my changes

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/962
2026-04-25 17:37:34 +02:00
Jackson Delahunt eec83179b9 config: add shell option to hide the shell session (#955)
The shell session is unconditionally added to the session list with no way to hide it. This is inconsistent with `xinitrc`, which is omitted from the list when set to `null`.

This change adds a `shell` boolean config option (default `true`). Setting it to `false` hides the shell session from the list, following the same pattern as `xinitrc`.

Co-authored-by: Jackson Delahunt <jackson@stemn.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/955
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: Jackson Delahunt <sabrehagen@noreply.codeberg.org>
Co-committed-by: Jackson Delahunt <sabrehagen@noreply.codeberg.org>
2026-04-01 19:00:37 +02:00
Jackson Delahunt b8048234d9 config: add show_tty option to display active TTY in top right corner (#956)
When running multiple ly instances across different TTYs there is no way to tell which TTY a given login screen belongs to at a glance.

This change adds a `show_tty` boolean config option (default `false`) that displays the active TTY number (e.g. `tty3`) in the top right corner. When the clock is also enabled the TTY label sits immediately to its right on the same row. When the clock is disabled it occupies the top right corner on its own.

I'm open to advice from the maintainers on the placement of the TTY label — positioning it next to the clock is simply my personal preference and it doesn't need to stay there if a different position is more appropriate.

Co-authored-by: Jackson Delahunt <jackson@stemn.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/956
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: Jackson Delahunt <sabrehagen@noreply.codeberg.org>
Co-committed-by: Jackson Delahunt <sabrehagen@noreply.codeberg.org>
2026-04-01 18:51:37 +02:00
Jackson Delahunt 10a873acb9 config: allow waylandsessions and xsessions to be set to null (#954)
`waylandsessions` and `xsessions` are currently non-optional string fields, so there is no clean way to disable session type discovery for users who do not use Wayland or X11. Setting them to a nonexistent path works but produces log errors on every startup.

This change makes both fields optional (`?[]const u8`), consistent with other nullable config fields such as `xinitrc`. Setting either to `null` in `config.ini` cleanly skips crawling for that session type with no side effects.

Co-authored-by: Jackson Delahunt <jackson@stemn.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/954
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: Jackson Delahunt <sabrehagen@noreply.codeberg.org>
Co-committed-by: Jackson Delahunt <sabrehagen@noreply.codeberg.org>
2026-03-29 08:32:27 +02:00
AnErrupTion 142476041d
Update French translation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-28 12:09:41 +01:00
RacerBG fad683e035 Update the Bulgarian translation (#952)
## What are the changes about?

As the title says.

## What existing issue does this resolve?

N/A

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/952
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: RacerBG <racerbg@noreply.codeberg.org>
Co-committed-by: RacerBG <racerbg@noreply.codeberg.org>
2026-03-28 12:07:10 +01:00
AnErrupTion e882eea22a
Improve bug report template
Notably, don't make the issue reproduction on a fresh install required.
This'll likely filter out the honest people who have actually done it
from the others who haven't.

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-27 23:18:16 +01:00
RadsammyT fe6942d406 fix: custom label and bind ordering (#951)
## What are the changes about?

Fixes the order of custom labels and binds because of a HashMap shenanigan (no guaranteed order), so we use `ArrayHashMap` instead which preserves insertion order.  They should now be shown in the order they are declared in the config.

![image](/attachments/7a928c5f-fbbe-4a60-b120-3feddbcdfdb6)

![image](/attachments/22afe011-00a0-4a29-90ab-060e1d059c75)

## What existing issue does this resolve?

!950

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have run `zig fmt` throughout my changes

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/951
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: RadsammyT <radsammyt@gmail.com>
Co-committed-by: RadsammyT <radsammyt@gmail.com>
2026-03-27 22:46:37 +01:00
AnErrupTion 5b7c7dfdf5
migrator.zig: Run zig fmt
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-27 21:34:32 +01:00
AnErrupTion 074bb0a68a
Improve custom command sample config readability (closes #949)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-27 20:34:39 +01:00
AnErrupTion e0a3364169
Merge branch 'master' of codeberg.org:fairyglade/ly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-27 17:17:18 +01:00
RadsammyT 7a8d913531 Feature: Add custom command & label support (#945)
## What are the changes about?

Adds customizable commands and labels to ly.
Solves https://codeberg.org/fairyglade/ly/issues/905.

Since Ly doesn't use INI headers. I use them exclusively for declarations of custom commands and labels.

### Commands

Bind a keybind to a command, and add a hint to the HUD. Useful for use cases like display brightness, switching between GPUs, etc.

Supports localization in the `name` field only. ex: where `lang = es`: `$brightness_up` => `bajar brillo`

Declared in config.ini with the following:

```ini
[cmd:F8]
name = custom command 2
cmd = touch /tmp/ly.gaming
```

### Labels

Add a label to the HUD. As specified in #905.
The text of the label corresponds to the output of the command specified in `[lbl:NAME]`.
Only shows the first line of the output.

Declared in config.ini with the following:

```ini
[lbl:kernel]
cmd = uname -srn
refresh = 0
```

Example to add to the config.ini:
```ini
# Declare a command with the F8 binding.
[cmd:F8]
#The name of the command to show up in Ly.
name = custom command
cmd = touch /tmp/ly.gaming

# Declare a label with an ID. This ID should be unique across all labels.
[lbl:kernel]
cmd = uname -srn
# In frames, the time to re-run the command and update the label. If 0, only run once- do not refresh.
refresh = 0

# Once you're done setting up labels and commands, add an empty header
# below to continue configurating the rest of Ly.
# Put other settings not belonging to custom commands/labels below here.
[]

```

## Pre-requisites

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

![image](/attachments/f9373ac9-567e-4f47-987c-1df6f4ee0d84)

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/945
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: RadsammyT <radsammyt@gmail.com>
Co-committed-by: RadsammyT <radsammyt@gmail.com>
2026-03-27 17:15:49 +01:00
AnErrupTion 984ac596af
Group for loops in event loop
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-26 21:55:35 +01:00
AnErrupTion ed486c29d2
Add xauth file as X server argument
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-26 21:10:50 +01:00
AnErrupTion a6fc5d67e8
Use upstream zigini library
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-25 22:43:05 +01:00
AnErrupTion 549576aa3e
Make box widget not position-dependent
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-25 22:06:57 +01:00
AnErrupTion 3758b5da1b
Mention -quiet argument for X11 server (closes #722)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-25 21:10:09 +01:00
AnErrupTion 5e1c681385
Update screenshot (closes #948)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-25 20:49:53 +01:00
AnErrupTion ac78ccc398
Update Kawaii-Ash's GitHub username
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-25 20:06:59 +01:00
Tom e548333473 Improve README.md (#946)
Signed-off-by: Tom <tom@helix.me.uk>

## What are the changes about?

Sentence structure updates and converting text to md syntax

## What existing issue does this resolve?

Fixes: #943

- Updated a few sentences to make them more understandable
- Converted the **Note**'s etc to md formatting.

## Pre-requisites

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/946
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: Tom <tripplehelix@noreply.codeberg.org>
Co-committed-by: Tom <tripplehelix@noreply.codeberg.org>
2026-03-21 19:30:23 +01:00
AnErrupTion 60e3380375
Switch to single-instance Widget model
And make widget() functions return pointers to widgets instead of just
widgets

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-21 16:19:33 +01:00
AnErrupTion aa392837bc
Require "zig fmt" to be run in a PR
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-21 16:01:03 +01:00
AnErrupTion abe72c74ff
Optimise event loop initialisation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-18 20:26:00 +01:00
Mr. Cat dda56eab37 Fix compilation error when building without X11 support (#947)
## What are the changes about?

Add an argument to info log function to fix compiler error.

## What existing issue does this resolve?

If building with `zig build -Denable_x11_support=false` there is a compiler error about a missing argument.
```
install
└─ install ly
   └─ compile exe ly Debug native 1 errors
src/main.zig:730:27: error: member function expected 3 argument(s), found 2
        try state.log_file.info(
            ~~~~~~~~~~~~~~^~~~~
ly-core/src/LogFile.zig:26:5: note: function declared here
pub fn info(self: *LogFile, category: []const u8, comptime message: []const u8, args: anytype) !void {
~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    callMain [inlined]: /home/oskar/.cache/zig/p/N-V-__8AACFNhBSaulceFT2Wx6Mx-ycGtZh7CEztyVdtX2jW/lib/std/start.zig:627:37
    callMainWithArgs [inlined]: /home/oskar/.cache/zig/p/N-V-__8AACFNhBSaulceFT2Wx6Mx-ycGtZh7CEztyVdtX2jW/lib/std/start.zig:587:20
    main: /home/oskar/.cache/zig/p/N-V-__8AACFNhBSaulceFT2Wx6Mx-ycGtZh7CEztyVdtX2jW/lib/std/start.zig:602:28
    1 reference(s) hidden; use '-freference-trace=4' to see all references
```

## Pre-requisites

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/947
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: Mr. Cat <mrcat@posteo.com>
Co-committed-by: Mr. Cat <mrcat@posteo.com>
2026-03-18 20:18:45 +01:00
AnErrupTion 9c50297059
Fix insert mode hack + fix bugs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 23:58:06 +01:00
AnErrupTion acac884cfe
Add support for local keybinds
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 22:58:39 +01:00
AnErrupTion a89c918c5d
Move back custom widgets into main project
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 21:59:24 +01:00
AnErrupTion 64539f4342
Split UI code into ly-ui library
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 21:44:33 +01:00
AnErrupTion 93696a6b30
Remove unused import + add TODO
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 21:03:59 +01:00
AnErrupTion 4f26eeada0
Fix double spacing issue in labels
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 20:10:47 +01:00
AnErrupTion 83e98a185f
Add TODO in main.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 20:01:47 +01:00
AnErrupTion 128dcb16f8
Merge branch 'master' of codeberg.org:fairyglade/ly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-03-17 19:58:58 +01:00
Luna 3a4109eb2d Add toggle visibility to password (#938)
## What are the changes about?

I have add a keybinding to toggle the visibility of the password

## 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/938
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: Luna <luna-39@noreply.codeberg.org>
Co-committed-by: Luna <luna-39@noreply.codeberg.org>
2026-03-17 12:27:23 +01:00
GalaxyShard 7cefff4570 Add Esperanto translation (#942)
Title.

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/942
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: GalaxyShard <dominic.adragna@byteroach.com>
Co-committed-by: GalaxyShard <dominic.adragna@byteroach.com>
2026-03-16 23:16:43 +01:00
OSVidYapan f31c55b562 Improve README.md (#940)
For anyone who doesn't know see this thread;
https://codeberg.org/fairyglade/ly/pulls/934
This is the original post i have fixed the commit log issue
(by recreating entire new fork)
Note that is is also grammarly fixed version.
(mostly please advise if i still have those mistakes)

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/940
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
Co-authored-by: OSVidYapan <osvidyapan@noreply.codeberg.org>
Co-committed-by: OSVidYapan <osvidyapan@noreply.codeberg.org>
2026-03-16 21:13:08 +01:00
AnErrupTion 9cde291ac7
Remove unused termbox alias
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-27 22:19:09 +01:00
AnErrupTion 03d976171a
Fix battery level overlapping shutdown label (fixes #935)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-27 20:28:11 +01:00
AnErrupTion b01e4afc79
Make default startup script more compatible
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-12 17:10:52 +01:00
AnErrupTion 5a4605ffb6
Add layering system for widgets
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-12 11:29:14 +01:00
AnErrupTion 01dcfa207e
Show UI errors in info line again
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-12 11:20:54 +01:00
AnErrupTion 32d5330efb
Move the event loop to a separate function
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-12 01:19:19 +01:00
AnErrupTion 5564fed664
Add Widget.calculateTimeout function
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-12 00:27:07 +01:00
AnErrupTion 7c7aed9cb2
Make animation timeout independent of event loop
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-11 23:51:32 +01:00
AnErrupTion 57c96a3478
Fix animation timeout bug + remove redundant check
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-11 22:55:10 +01:00
AnErrupTion 6773f74788
Add widget display name to improve logging
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-11 21:51:07 +01:00
AnErrupTion b389e379fa
Make handling inputs widget-independent
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-11 21:00:00 +01:00
hynak 4a72e41e44 Fix default startup script (#929)
## What are the changes about?

Discussed in !920. Adds fixes to the startup script by removing the array usage (some shells use arrays different/unsupported) and adds stdout_behavior Inherit flag to the child process to propagate the echos to the TTY.

## 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/929
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: hynak <hynak@noreply.codeberg.org>
Co-committed-by: hynak <hynak@noreply.codeberg.org>
2026-02-10 22:20:30 +01:00
AnErrupTion d268d5bb45
Add the cascade animation as a separate widget
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-10 17:43:55 +01:00
Cyaxares f320d3f666 Add Kurdish translation (#930)
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/930
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Cyaxares <cyaxares@noreply.codeberg.org>
Co-committed-by: Cyaxares <cyaxares@noreply.codeberg.org>
2026-02-10 10:47:50 +01:00
AnErrupTion 70e95f094a
Fix incorrect key parsing
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-10 10:41:08 +01:00
AnErrupTion 207b352888
Add central Widget struct + clean up code
In particular, move all termbox2 usage to TerminalBuffer.zig &
keyboard.zig

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-10 00:22:27 +01:00
AnErrupTion 99dba44e46
Rename min_refresh_delta option to animation_frame_delay (closes #925)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-09 11:38:56 +01:00
AnErrupTion d1810d8c98
Fix numlock & capslock positioning
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-09 11:20:27 +01:00
AnErrupTion 852a602032
Support more configurable keybindings (closes #679)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-09 11:11:00 +01:00
AnErrupTion cf5f62661c
Remove resolution_changed bool
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 22:23:07 +01:00
AnErrupTion 1db780c7a7
Better handle screen resizes
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 22:18:00 +01:00
AnErrupTion 941b7e0dae
Properly calculate string lengths
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 22:04:09 +01:00
AnErrupTion e9e2d51261
Better bigclock positioning
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 21:36:18 +01:00
AnErrupTion 769aefd6e9
Add separate big label widget for bigclock
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 21:14:49 +01:00
AnErrupTion f678e3bb28
Add update function for Label
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 19:50:32 +01:00
AnErrupTion f22593f828
Add Label component & make colors custom
This commit also makes Ly more resilient to (impossible) screen
resolutions.

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 17:40:50 +01:00
AnErrupTion 7bbdebe58b
Fix center offset error
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 15:09:55 +01:00
AnErrupTion bca38856b1
Completely refactor widget placement code
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-08 14:53:36 +01:00
laur 8c08359e51 custom-sessions/README: remove extraneous comma (#927)
## What are the changes about?

Fix a typo.

## What existing issue does this resolve?

N/A

Co-authored-by: laur <laur.aliste@gmail.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/927
Co-authored-by: laur <laur@noreply.codeberg.org>
Co-committed-by: laur <laur@noreply.codeberg.org>
2026-02-08 13:16:25 +01:00
AnErrupTion fd81da7cbd
Organise imports
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-07 17:01:11 +01:00
AnErrupTion 950eeed3ee
Log which VT is used
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-07 10:48:05 +01:00
AnErrupTion ce0d00771d
Use the unifont engine for KMSCON
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-07 10:43:08 +01:00
WinuxVidYapan b032c9b296 Update Turkish translations (#917)
So i am sorry for unintended behavior i am new to Codeberg and pull requests
But here are the words.

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/917
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: WinuxVidYapan <winuxvidyapan@noreply.codeberg.org>
Co-committed-by: WinuxVidYapan <winuxvidyapan@noreply.codeberg.org>
2026-02-05 20:19:41 +01:00
AnErrupTion 7744745f09
Don't try to switch TTY with KMSCON
It can do it automatically (see the corresponding service file).

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 19:55:40 +01:00
AnErrupTion 11735290b8
Fix active TTY detection for KMSCON
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 19:50:48 +01:00
AnErrupTion 21fca058e7
Don't install startup.sh with installnoconf
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 15:03:05 +01:00
AnErrupTion 2b46a81796
Add basic KMSCON support (closes #886)
It's not perfect yet, but at least it works!

Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-05 15:01:45 +01:00
hynak 72f43fbc56 Add shell script showing how change TTY colors (#920)
## What are the changes about?

What was discussed in !912 before I accidentally caused it to auto merge (still not sure how that happened). I assume this is what was meant when asking for it to be in the startup script commented out.

## 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/920
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: hynak <hynak@noreply.codeberg.org>
Co-committed-by: hynak <hynak@noreply.codeberg.org>
2026-02-04 20:36:55 +01:00
AnErrupTion b00d6899e5
Fix buffer not resizing with no animation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-04 20:16:53 +01:00
AnErrupTion 7ce8ff61fe
Unify ini parsing logic
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-02-01 10:41:06 +01:00
AnErrupTion 1a04a608a1
Remove dummy animation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-31 22:51:44 +01:00
AnErrupTion b0dcc12785
Move UI drawing to separate function
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-31 22:35:28 +01:00
AnErrupTion b672d04dc6
Improve authentication logging
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-31 11:46:55 +01:00
AnErrupTion 2e04ea4d79
Add time, severity & category in logs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-31 11:17:36 +01:00
AnErrupTion 0c12008327
Move more termbox usage into TerminalBuffer
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-31 10:36:08 +01:00
AnErrupTion 7934060d3b
Credit Kawaii-Ash in the README
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-29 21:31:45 +01:00
AnErrupTion a158098df0
Add config.x_vt option (fixes #910)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-26 20:45:33 +01:00
AnErrupTion 5bfa1670cc
Better systemd-homed user detection (fixes #913)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-26 20:36:44 +01:00
hynak a4076b83da [dur] Add support for alignments and negative offsets + Ly logo (#893)
## What are the changes about?
Add support for letting a user use a negative offset (#880), alignment, and logo. Below is example of the logo file, I hope it is what was request :). It has no padding so a user can move the alignment and offset to get it how they want on screen.

This technically is good to go, except I didn't upload the logo file as I'm not sure where to add the animation file to get it to here: $CONFIG_DIRECTORY/ly/example.dur

![logo-preview](/attachments/5a829dbd-7708-4d0a-9841-d024902ede68)

## What existing issue does this resolve?

#880

## Pre-requisites

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/893
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: hynak <hynak@noreply.codeberg.org>
Co-committed-by: hynak <hynak@noreply.codeberg.org>
2026-01-25 23:08:42 +01:00
AnErrupTion 2eea683078
Fix wrong session being chosen in autologin (closes #911)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-18 21:35:49 +01:00
AnErrupTion d7f64676ee
Split core code into ly-core library
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-18 21:07:53 +01:00
AnErrupTion 456916f059
Remove unused import in auth.zig
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-18 20:49:32 +01:00
AnErrupTion 2b1e4dc6c9
Fix undefined value in XCB connection check
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-18 18:03:21 +01:00
AnErrupTion 94c306758a
Update Matrix space link (envs.net migration)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-17 16:58:05 +01:00
AnErrupTion 82d24d7725
Add donation links
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-17 10:48:12 +01:00
AnErrupTion 5a51d5ced5
Fix building on musl (closes #760)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-06 22:27:36 +01:00
AnErrupTion 135d1e40f6
Update termbox2
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-01-06 22:18:31 +01:00
AnErrupTion c0c400e0b6
Recursively create xauth file directory if non-existent
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 19:35:40 +01:00
AnErrupTion 9e4147bfb4
Fix invalid XDG_RUNTIME_DIR if D-Bus isn't used
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 19:29:47 +01:00
AnErrupTion 8e893932f2
Clamp user session index if invalid
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 18:09:46 +01:00
AnErrupTion 26e7585b0b
Don't forget to allocate :D
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:43:00 +01:00
AnErrupTion b1cb576f67
Check for session file name in autologin (closes #895)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:38:22 +01:00
AnErrupTion add7f25f0d
Create session log directory if non-existent (closes #896)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-12-30 17:20:04 +01:00
Theo Gaige 2e7bb3eb58 Use buf_height and buf_width in Matrix.draw() (#903)
## What are the changes about?

buf_height is declared at the start of the draw() function of the
Matrix animation but both buf_height and self.terminal_buffer.height are
used in the function. replace every occurence of
self.terminal_buffer.height by buf_height for consistency.

The same goes for buf_width and self.terminal_buffer.width.

no functionnal changes

## 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/903
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: Theo Gaige <theo.gaige@free.fr>
Co-committed-by: Theo Gaige <theo.gaige@free.fr>
2025-12-29 23:39:10 +01:00
ViSzKe e57de5172e Update Swedish translation (#899)
## What are the changes about?

Adding proper Swedish translation

## What existing issue does this resolve?

The sub-standard Swedish translation

## Pre-requisites

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/899
Reviewed-by: AnErrupTion <anerruption@disroot.org>
Co-authored-by: ViSzKe <vilgot.sz.k@gmail.com>
Co-committed-by: ViSzKe <vilgot.sz.k@gmail.com>
2025-12-29 23:36:47 +01:00
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
91 changed files with 7891 additions and 3199 deletions

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: AnErrupTion
liberapay: ShiningLea

View File

@ -12,8 +12,8 @@ body:
- label: I have looked for any other duplicate issues - label: I have looked for any other duplicate issues
required: true required: true
- label: I have reproduced the issue on a fresh install of my OS & Ly with default settings, except ones I will mention - label: I have reproduced the issue on a fresh install of my OS & Ly with default settings, except ones I will mention
required: true required: false
- label: I have confirmed this issue also occurs on the latest development version - label: I have confirmed this issue also occurs on the latest development version (found in the `master` branch)
required: true required: true
- type: input - type: input
id: version id: version

View File

@ -9,3 +9,4 @@ _Replace this with a reference to an existing issue, or N/A if there is none_
## Pre-requisites ## Pre-requisites
- [ ] I have tested & confirmed the changes work locally - [ ] I have tested & confirmed the changes work locally
- [ ] I have run `zig fmt` throughout my changes

BIN
.github/screenshot.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 52 KiB

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ zig-cache/
zig-out/ zig-out/
valgrind.log valgrind.log
.zig-cache .zig-cache
zig-pkg

298
build.zig
View File

@ -12,7 +12,7 @@ const InitSystem = enum {
freebsd, freebsd,
}; };
const min_zig_string = "0.15.0"; const min_zig_string = "0.16.0";
const current_zig = builtin.zig_version; const current_zig = builtin.zig_version;
// Implementing zig version detection through compile time // Implementing zig version detection through compile time
@ -23,7 +23,7 @@ comptime {
} }
} }
const ly_version = std.SemanticVersion{ .major = 1, .minor = 3, .patch = 0 }; const ly_version = std.SemanticVersion{ .major = 1, .minor = 5, .patch = 0 };
var dest_directory: []const u8 = undefined; var dest_directory: []const u8 = undefined;
var config_directory: []const u8 = undefined; var config_directory: []const u8 = undefined;
@ -67,37 +67,24 @@ pub fn build(b: *std.Build) !void {
.root_source_file = b.path("src/main.zig"), .root_source_file = b.path("src/main.zig"),
.target = target, .target = target,
.optimize = optimize, .optimize = optimize,
.link_libc = true,
}), }),
// Here until the native backend matures in terms of performance
.use_llvm = true,
}); });
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize }); const ly_ui = b.dependency("ly_ui", .{
exe.root_module.addImport("zigini", zigini.module("zigini")); .target = target,
.optimize = optimize,
.enable_x11_support = enable_x11_support,
});
exe.root_module.addImport("ly-ui", ly_ui.module("ly-ui"));
exe.root_module.addOptions("build_options", build_options); exe.root_module.addOptions("build_options", build_options);
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize }); const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("clap", clap.module("clap")); exe.root_module.addImport("clap", clap.module("clap"));
const termbox_dep = b.dependency("termbox2", .{ exe.root_module.linkSystemLibrary("pam", .{});
.target = target, if (enable_x11_support) exe.root_module.linkSystemLibrary("xcb", .{});
.optimize = optimize,
});
exe.linkSystemLibrary("pam");
if (enable_x11_support) exe.linkSystemLibrary("xcb");
exe.linkLibC();
const translate_c = b.addTranslateC(.{
.root_source_file = termbox_dep.path("termbox2.h"),
.target = target,
.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);
b.installArtifact(exe); b.installArtifact(exe);
@ -128,6 +115,8 @@ pub fn build(b: *std.Build) !void {
pub fn Installer(install_config: bool) type { pub fn Installer(install_config: bool) type {
return struct { return struct {
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const allocator = step.owner.allocator; const allocator = step.owner.allocator;
var patch_map = PatchMap.init(allocator); var patch_map = PatchMap.init(allocator);
@ -142,71 +131,75 @@ pub fn Installer(install_config: bool) type {
// instead to shutdown the system. // instead to shutdown the system.
try patch_map.put("$PLATFORM_SHUTDOWN_ARG", if (init_system == .freebsd) "-p" else "-a"); try patch_map.put("$PLATFORM_SHUTDOWN_ARG", if (init_system == .freebsd) "-p" else "-a");
try install_ly(allocator, patch_map, install_config); try install_ly(allocator, io, patch_map, install_config);
try install_service(allocator, patch_map); try install_service(allocator, io, patch_map);
} }
}; };
} }
fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: bool) !void { fn install_ly(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap, install_config: bool) !void {
const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly" }); const ly_config_directory = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly" });
std.fs.cwd().makePath(ly_config_directory) catch { std.Io.Dir.cwd().createDirPath(io, ly_config_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory}); std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
}; };
const ly_custom_sessions_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" }); const ly_custom_sessions_directory = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" });
std.fs.cwd().makePath(ly_custom_sessions_directory) catch { std.Io.Dir.cwd().createDirPath(io, ly_custom_sessions_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory}); std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory});
}; };
const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" }); const ly_lang_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" });
std.fs.cwd().makePath(ly_lang_path) catch { std.Io.Dir.cwd().createDirPath(io, ly_lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path}); 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, prefix_directory, "/bin" }); const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
std.fs.cwd().makePath(exe_path) catch { std.Io.Dir.cwd().createDirPath(io, exe_path) catch {
if (!std.mem.eql(u8, dest_directory, "")) { if (!std.mem.eql(u8, dest_directory, "")) {
std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path}); std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path});
} }
}; };
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable; var executable_dir = std.Io.Dir.cwd().openDir(io, exe_path, .{}) catch unreachable;
defer executable_dir.close(); defer executable_dir.close(io);
try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{}); try installFile(io, "zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
} }
{ {
var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable; var config_dir = std.Io.Dir.cwd().openDir(io, ly_config_directory, .{}) catch unreachable;
defer config_dir.close(); defer config_dir.close(io);
if (install_config) { if (install_config) {
const patched_config = try patchFile(allocator, "res/config.ini", patch_map); const patched_config = try patchFile(allocator, io, "res/config.ini", patch_map);
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{}); try installText(io, patched_config, config_dir, ly_config_directory, "config.ini", .{});
try installFile(io, "res/startup.sh", config_dir, ly_config_directory, "startup.sh", .{ .permissions = .fromMode(0o755) });
} }
const patched_example_config = try patchFile(allocator, "res/config.ini", patch_map); const patched_example_config = try patchFile(allocator, io, "res/config.ini", patch_map);
try installText(patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{}); try installText(io, patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{});
const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map); const patched_setup = try patchFile(allocator, io, "res/setup.sh", patch_map);
try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 }); try installText(io, patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .permissions = .fromMode(0o755) });
try installFile(io, "res/example.dur", config_dir, ly_config_directory, "example.dur", .{ .permissions = .fromMode(0o755) });
} }
{ {
var custom_sessions_dir = std.fs.cwd().openDir(ly_custom_sessions_directory, .{}) catch unreachable; var custom_sessions_dir = std.Io.Dir.cwd().openDir(io, ly_custom_sessions_directory, .{}) catch unreachable;
defer custom_sessions_dir.close(); defer custom_sessions_dir.close(io);
const patched_readme = try patchFile(allocator, "res/custom-sessions/README", patch_map); const patched_readme = try patchFile(allocator, io, "res/custom-sessions/README", patch_map);
try installText(patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{}); try installText(io, patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{});
} }
{ {
var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable; var lang_dir = std.Io.Dir.cwd().openDir(io, ly_lang_path, .{}) catch unreachable;
defer lang_dir.close(); defer lang_dir.close(io);
const languages = [_][]const u8{ const languages = [_][]const u8{
"ar.ini", "ar.ini",
@ -232,63 +225,66 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config:
}; };
inline for (languages) |language| { inline for (languages) |language| {
try installFile("res/lang/" ++ language, lang_dir, ly_lang_path, language, .{}); try installFile(io, "res/lang/" ++ language, lang_dir, ly_lang_path, language, .{});
} }
} }
{ {
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" }); const pam_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
std.fs.cwd().makePath(pam_path) catch { std.Io.Dir.cwd().createDirPath(io, pam_path) catch {
if (!std.mem.eql(u8, dest_directory, "")) { if (!std.mem.eql(u8, dest_directory, "")) {
std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path}); std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path});
} }
}; };
var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable; var pam_dir = std.Io.Dir.cwd().openDir(io, pam_path, .{}) catch unreachable;
defer pam_dir.close(); defer pam_dir.close(io);
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(io, if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .permissions = .fromMode(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 }); try installFile(io, if (init_system == .freebsd) "res/pam.d/ly-freebsd-autologin" else "res/pam.d/ly-linux-autologin", pam_dir, pam_path, "ly-autologin", .{ .permissions = .fromMode(0o644) });
} }
} }
fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void { fn install_service(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap) !void {
switch (init_system) { switch (init_system) {
.systemd => { .systemd => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" }); const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" });
std.fs.cwd().makePath(service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
defer service_dir.close(); defer service_dir.close(io);
const patched_service = try patchFile(allocator, "res/ly@.service", patch_map); const patched_service = try patchFile(allocator, io, "res/ly@.service", patch_map);
try installText(patched_service, service_dir, service_path, "ly@.service", .{ .mode = 0o644 }); try installText(io, patched_service, service_dir, service_path, "ly@.service", .{ .permissions = .fromMode(0o644) });
const patched_kmsconvt_service = try patchFile(allocator, io, "res/ly-kmsconvt@.service", patch_map);
try installText(io, patched_kmsconvt_service, service_dir, service_path, "ly-kmsconvt@.service", .{ .permissions = .fromMode(0o644) });
}, },
.openrc => { .openrc => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" }); const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
std.fs.cwd().makePath(service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
defer service_dir.close(); defer service_dir.close(io);
const patched_service = try patchFile(allocator, "res/ly-openrc", patch_map); const patched_service = try patchFile(allocator, io, "res/ly-openrc", patch_map);
try installText(patched_service, service_dir, service_path, executable_name, .{ .mode = 0o755 }); try installText(io, patched_service, service_dir, service_path, executable_name, .{ .permissions = .fromMode(0o755) });
}, },
.runit => { .runit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" }); const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" });
std.fs.cwd().makePath(service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
defer service_dir.close(); defer service_dir.close(io);
const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" }); const supervise_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map); const patched_conf = try patchFile(allocator, io, "res/ly-runit-service/conf", patch_map);
try installText(patched_conf, service_dir, service_path, "conf", .{}); try installText(io, patched_conf, service_dir, service_path, "conf", .{});
try installFile("res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .override_mode = 0o755 }); try installFile(io, "res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .permissions = .fromMode(0o755) });
const patched_run = try patchFile(allocator, "res/ly-runit-service/run", patch_map); const patched_run = try patchFile(allocator, io, "res/ly-runit-service/run", patch_map);
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 }); try installText(io, patched_run, service_dir, service_path, "run", .{ .permissions = .fromMode(0o755) });
std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{}) catch |err| { std.Io.Dir.cwd().symLink(io, "/run/runit/supervise.ly", supervise_path, .{}) catch |err| {
if (err == error.PathAlreadyExists) { if (err == error.PathAlreadyExists) {
std.debug.print("warn: /run/runit/supervise.ly already exists as a symbolic link.\n", .{}); std.debug.print("warn: /run/runit/supervise.ly already exists as a symbolic link.\n", .{});
} else { } else {
@ -298,49 +294,49 @@ fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void {
std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{}); std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{});
}, },
.s6 => { .s6 => {
const admin_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" }); const admin_service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" });
std.fs.cwd().makePath(admin_service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, admin_service_path) catch {};
var admin_service_dir = std.fs.cwd().openDir(admin_service_path, .{}) catch unreachable; var admin_service_dir = std.Io.Dir.cwd().openDir(io, admin_service_path, .{}) catch unreachable;
defer admin_service_dir.close(); defer admin_service_dir.close(io);
const file = try admin_service_dir.createFile("ly-srv", .{}); const file = try admin_service_dir.createFile(io, "ly-srv", .{});
file.close(); file.close(io);
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" }); const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" });
std.fs.cwd().makePath(service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
defer service_dir.close(); defer service_dir.close(io);
const patched_run = try patchFile(allocator, "res/ly-s6/run", patch_map); const patched_run = try patchFile(allocator, io, "res/ly-s6/run", patch_map);
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 }); try installText(io, patched_run, service_dir, service_path, "run", .{ .permissions = .fromMode(0o755) });
try installFile("res/ly-s6/type", service_dir, service_path, "type", .{}); try installFile(io, "res/ly-s6/type", service_dir, service_path, "type", .{});
}, },
.dinit => { .dinit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" }); const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" });
std.fs.cwd().makePath(service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
defer service_dir.close(); defer service_dir.close(io);
const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map); const patched_service = try patchFile(allocator, io, "res/ly-dinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{}); try installText(io, patched_service, service_dir, service_path, "ly", .{});
}, },
.sysvinit => { .sysvinit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" }); const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
std.fs.cwd().makePath(service_path) catch {}; std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable; var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
defer service_dir.close(); defer service_dir.close(io);
const patched_service = try patchFile(allocator, "res/ly-sysvinit", patch_map); const patched_service = try patchFile(allocator, io, "res/ly-sysvinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{ .mode = 0o755 }); try installText(io, patched_service, service_dir, service_path, "ly", .{ .permissions = .fromMode(0o755) });
}, },
.freebsd => { .freebsd => {
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" }); const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable; var executable_dir = std.Io.Dir.cwd().openDir(io, exe_path, .{}) catch unreachable;
defer executable_dir.close(); defer executable_dir.close(io);
const patched_wrapper = try patchFile(allocator, "res/ly-freebsd-wrapper", patch_map); const patched_wrapper = try patchFile(allocator, io, "res/ly-freebsd-wrapper", patch_map);
try installText(patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .mode = 0o755 }); try installText(io, patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .permissions = .fromMode(0o755) });
}, },
} }
} }
@ -348,33 +344,35 @@ fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void {
pub fn Uninstaller(uninstall_config: bool) type { pub fn Uninstaller(uninstall_config: bool) type {
return struct { return struct {
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void { pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
var threaded: std.Io.Threaded = .init_single_threaded;
const io = threaded.io();
const allocator = step.owner.allocator; const allocator = step.owner.allocator;
if (uninstall_config) { if (uninstall_config) {
try deleteTree(allocator, config_directory, "/ly", "ly config directory not found"); try deleteTree(allocator, io, config_directory, "/ly", "ly config directory not found");
} }
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ prefix_directory, "/bin/", executable_name }); const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ prefix_directory, "/bin/", executable_name });
var success = true; var success = true;
std.fs.cwd().deleteFile(exe_path) catch { std.Io.Dir.cwd().deleteFile(io, exe_path) catch {
std.debug.print("warn: ly executable not found\n", .{}); std.debug.print("warn: ly executable not found\n", .{});
success = false; success = false;
}; };
if (success) std.debug.print("info: deleted {s}\n", .{exe_path}); if (success) std.debug.print("info: deleted {s}\n", .{exe_path});
try deleteFile(allocator, config_directory, "/pam.d/ly", "ly pam file not found"); try deleteFile(allocator, io, config_directory, "/pam.d/ly", "ly pam file not found");
switch (init_system) { switch (init_system) {
.systemd => try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly@.service", "systemd service not found"), .systemd => try deleteFile(allocator, io, prefix_directory, "/lib/systemd/system/ly@.service", "systemd service not found"),
.openrc => try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found"), .openrc => try deleteFile(allocator, io, config_directory, "/init.d/ly", "openrc service not found"),
.runit => try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found"), .runit => try deleteTree(allocator, io, config_directory, "/sv/ly", "runit service not found"),
.s6 => { .s6 => {
try deleteTree(allocator, config_directory, "/s6/sv/ly-srv", "s6 service not found"); try deleteTree(allocator, io, config_directory, "/s6/sv/ly-srv", "s6 service not found");
try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found"); try deleteFile(allocator, io, 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"), .dinit => try deleteFile(allocator, io, config_directory, "/dinit.d/ly", "dinit service not found"),
.sysvinit => try deleteFile(allocator, config_directory, "/init.d/ly", "sysvinit service not found"), .sysvinit => try deleteFile(allocator, io, config_directory, "/init.d/ly", "sysvinit service not found"),
.freebsd => try deleteFile(allocator, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"), .freebsd => try deleteFile(allocator, io, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"),
} }
} }
}; };
@ -392,11 +390,11 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
"--match", "--match",
"*.*.*", "*.*.*",
"--tags", "--tags",
}, &status, .Ignore) catch { }, &status, .ignore) catch {
return version_str; return version_str;
}; };
var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r"); var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r");
git_describe = std.mem.trimLeft(u8, git_describe, "v"); git_describe = std.mem.trimStart(u8, git_describe, "v");
switch (std.mem.count(u8, git_describe, "-")) { switch (std.mem.count(u8, git_describe, "-")) {
0 => { 0 => {
@ -409,7 +407,7 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
2 => { 2 => {
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9). // Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
var it = std.mem.splitScalar(u8, git_describe, '-'); var it = std.mem.splitScalar(u8, git_describe, '-');
const tagged_ancestor = std.mem.trimLeft(u8, it.first(), "v"); const tagged_ancestor = std.mem.trimStart(u8, it.first(), "v");
const commit_height = it.next().?; const commit_height = it.next().?;
const commit_id = it.next().?; const commit_id = it.next().?;
@ -436,25 +434,26 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
} }
fn installFile( fn installFile(
io: std.Io,
source_file: []const u8, source_file: []const u8,
destination_directory: std.fs.Dir, destination_directory: std.Io.Dir,
destination_directory_path: []const u8, destination_directory_path: []const u8,
destination_file: []const u8, destination_file: []const u8,
options: std.fs.Dir.CopyFileOptions, options: std.Io.Dir.CopyFileOptions,
) !void { ) !void {
try std.fs.cwd().copyFile(source_file, destination_directory, destination_file, options); try std.Io.Dir.cwd().copyFile(source_file, destination_directory, destination_file, io, options);
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file }); 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 { fn patchFile(allocator: std.mem.Allocator, io: std.Io, source_file: []const u8, patch_map: PatchMap) ![]const u8 {
var file = try std.fs.cwd().openFile(source_file, .{}); var file = try std.Io.Dir.cwd().openFile(io, source_file, .{});
defer file.close(); defer file.close(io);
const stat = try file.stat(); const stat = try file.stat(io);
var buffer: [4096]u8 = undefined; var buffer: [4096]u8 = undefined;
var reader = file.reader(&buffer); var reader = file.reader(io, &buffer);
var text = try reader.interface.readAlloc(allocator, stat.size); var text = try reader.interface.readAlloc(allocator, @intCast(stat.size));
var iterator = patch_map.iterator(); var iterator = patch_map.iterator();
while (iterator.next()) |kv| { while (iterator.next()) |kv| {
@ -467,17 +466,18 @@ fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: P
} }
fn installText( fn installText(
io: std.Io,
text: []const u8, text: []const u8,
destination_directory: std.fs.Dir, destination_directory: std.Io.Dir,
destination_directory_path: []const u8, destination_directory_path: []const u8,
destination_file: []const u8, destination_file: []const u8,
options: std.fs.File.CreateFlags, options: std.Io.File.CreateFlags,
) !void { ) !void {
var file = try destination_directory.createFile(destination_file, options); var file = try destination_directory.createFile(io, destination_file, options);
defer file.close(); defer file.close(io);
var buffer: [1024]u8 = undefined; var buffer: [1024]u8 = undefined;
var writer = file.writer(&buffer); var writer = file.writer(io, &buffer);
try writer.interface.writeAll(text); try writer.interface.writeAll(text);
try writer.interface.flush(); try writer.interface.flush();
@ -486,13 +486,14 @@ fn installText(
fn deleteFile( fn deleteFile(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
io: std.Io,
prefix: []const u8, prefix: []const u8,
file: []const u8, file: []const u8,
warning: []const u8, warning: []const u8,
) !void { ) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file }); const path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file });
std.fs.cwd().deleteFile(path) catch |err| { std.Io.Dir.cwd().deleteFile(io, path) catch |err| {
if (err == error.FileNotFound) { if (err == error.FileNotFound) {
std.debug.print("warn: {s}\n", .{warning}); std.debug.print("warn: {s}\n", .{warning});
return; return;
@ -506,13 +507,14 @@ fn deleteFile(
fn deleteTree( fn deleteTree(
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
io: std.Io,
prefix: []const u8, prefix: []const u8,
directory: []const u8, directory: []const u8,
warning: []const u8, warning: []const u8,
) !void { ) !void {
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory }); const path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory });
var dir = std.fs.cwd().openDir(path, .{}) catch |err| { var dir = std.Io.Dir.cwd().openDir(io, path, .{}) catch |err| {
if (err == error.FileNotFound) { if (err == error.FileNotFound) {
std.debug.print("warn: {s}\n", .{warning}); std.debug.print("warn: {s}\n", .{warning});
return; return;
@ -520,9 +522,9 @@ fn deleteTree(
return err; return err;
}; };
dir.close(); dir.close(io);
try std.fs.cwd().deleteTree(path); try std.Io.Dir.cwd().deleteTree(io, path);
std.debug.print("info: deleted {s}\n", .{path}); std.debug.print("info: deleted {s}\n", .{path});
} }

View File

@ -1,21 +1,20 @@
.{ .{
.name = .ly, .name = .ly,
.version = "1.3.0", .version = "1.5.0",
.fingerprint = 0xa148ffcc5dc2cb59, .fingerprint = 0xa148ffcc5dc2cb59,
.minimum_zig_version = "0.15.0", .minimum_zig_version = "0.16.0",
.dependencies = .{ .dependencies = .{
.ly_ui = .{
.path = "ly-ui",
},
.clap = .{ .clap = .{
.url = "git+https://github.com/Hejsil/zig-clap#5289e0753cd274d65344bef1c114284c633536ea", .url = "git+https://github.com/Hejsil/zig-clap#fc1e5cc3f6d9d3001112385ee6256d694e959d2f",
.hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e", .hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1",
},
.zigini = .{
.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 = .{""}, .paths = .{
"build.zig",
"build.zig.zon",
"src",
},
} }

2
create_vendor_tarball.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/sh
tar --zstd -cvf vendor.tar.zst zig-pkg ly-ui/zig-pkg ly-core/zig-pkg

73
ly-core/build.zig Normal file
View File

@ -0,0 +1,73 @@
const std = @import("std");
const Translator = @import("translate_c").Translator;
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support") orelse true;
const mod = b.addModule("ly-core", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
mod.addImport("zigini", zigini.module("zigini"));
const translate_c = b.dependency("translate_c", .{
.target = target,
});
addCImport(b, mod, translate_c, target, optimize, "pam", "#include <security/pam_appl.h>");
addCImport(b, mod, translate_c, target, optimize, "utmp", "#include <utmpx.h>");
if (enable_x11_support) {
addCImport(b, mod, translate_c, target, optimize, "xcb", "#include <xcb/xcb.h>");
}
if (target.result.os.tag == .freebsd) {
addCImport(b, mod, translate_c, target, optimize, "pwd",
\\#include <pwd.h>
\\#include <sys/types.h>
\\#include <login_cap.h>
);
} else {
addCImport(b, mod, translate_c, target, optimize, "pwd", "#include <pwd.h>");
}
addCImport(b, mod, translate_c, target, optimize, "stdlib", "#include <stdlib.h>");
addCImport(b, mod, translate_c, target, optimize, "unistd", "#include <unistd.h>");
addCImport(b, mod, translate_c, target, optimize, "grp", "#include <grp.h>");
addCImport(b, mod, translate_c, target, optimize, "system_time", "#include <sys/time.h>");
addCImport(b, mod, translate_c, target, optimize, "time", "#include <time.h>");
if (target.result.os.tag == .linux) {
addCImport(b, mod, translate_c, target, optimize, "kd", "#include <sys/kd.h>");
addCImport(b, mod, translate_c, target, optimize, "vt", "#include <sys/vt.h>");
} else if (target.result.os.tag == .freebsd) {
addCImport(b, mod, translate_c, target, optimize, "kbio", "#include <sys/kbio.h>");
addCImport(b, mod, translate_c, target, optimize, "consio", "#include <sys/consio.h>");
}
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
}
fn addCImport(
b: *std.Build,
mod: *std.Build.Module,
translate_c: *std.Build.Dependency,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
comptime name: []const u8,
comptime bytes: []const u8,
) void {
const pam: Translator = .init(translate_c, .{
.c_source_file = b.addWriteFiles().add(name ++ ".h", bytes),
.target = target,
.optimize = optimize,
});
mod.addImport(name, pam.mod);
}

21
ly-core/build.zig.zon Normal file
View File

@ -0,0 +1,21 @@
.{
.name = .ly_core,
.version = "1.1.0",
.fingerprint = 0xddda7afda795472,
.minimum_zig_version = "0.16.0",
.dependencies = .{
.zigini = .{
.url = "git+https://github.com/AshAmetrine/zigini?ref=master#a665d081dda42664a96da2840ea09c5ccf9d0692",
.hash = "zigini-0.5.0-BSkB7e9WAACfyCBABNZiWL3gFMw18GKn3qBcPs8L1Ec1",
},
.translate_c = .{
.url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7",
.hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

106
ly-core/src/LogFile.zig Normal file
View File

@ -0,0 +1,106 @@
const std = @import("std");
const interop = @import("interop.zig");
const LogFile = @This();
maybe_path: ?[]const u8,
could_open_log_file: bool = undefined,
maybe_file: ?std.Io.File = null,
buffer: []u8,
maybe_file_writer: ?std.Io.File.Writer = null,
pub fn init(io: std.Io, path: ?[]const u8, buffer: []u8) !LogFile {
var log_file = LogFile{
.maybe_path = path,
.buffer = buffer,
};
if (path) |p| {
log_file.could_open_log_file = try openLogFile(io, p, &log_file);
} else {
std.posix.system.openlog("ly", 0, 0);
log_file.could_open_log_file = true;
}
return log_file;
}
pub fn reinit(self: *LogFile, io: std.Io) !void {
if (self.maybe_path) |path| {
self.could_open_log_file = try openLogFile(io, path, self);
} else {
std.posix.system.openlog("ly", 0, 0);
self.could_open_log_file = true;
}
}
pub fn deinit(self: *LogFile, io: std.Io) void {
if (self.maybe_file) |file| {
file.close(io);
} else {
std.posix.system.closelog();
}
}
pub fn info(self: *LogFile, io: std.Io, category: []const u8, comptime message: []const u8, args: anytype) !void {
if (self.maybe_file_writer) |*writer| {
var buffer: [128:0]u8 = undefined;
const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S");
try writer.interface.print("{s} [info/{s}] ", .{ time, category });
try writer.interface.print(message, args);
try writer.interface.writeByte('\n');
try writer.interface.flush();
} else {
var buffer: [1024]u8 = undefined;
const slice = try std.fmt.bufPrint(&buffer, message, args);
const msg = try std.fmt.bufPrintZ(buffer[slice.len..], "[info/{s}] {s}", .{ category, slice });
std.posix.system.syslog(std.posix.LOG.INFO, msg.ptr);
}
}
pub fn err(self: *LogFile, io: std.Io, category: []const u8, comptime message: []const u8, args: anytype) !void {
if (self.maybe_file_writer) |*writer| {
var buffer: [128:0]u8 = undefined;
const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S");
try writer.interface.print("{s} [err/{s}] ", .{ time, category });
try writer.interface.print(message, args);
try writer.interface.writeByte('\n');
try writer.interface.flush();
} else {
var buffer: [1024]u8 = undefined;
const slice = try std.fmt.bufPrint(&buffer, message, args);
const msg = try std.fmt.bufPrintZ(buffer[slice.len..], "[info/{s}] {s}", .{ category, slice });
std.posix.system.syslog(std.posix.LOG.ERR, msg.ptr);
}
}
fn openLogFile(io: std.Io, path: []const u8, log_file: *LogFile) !bool {
var could_open_log_file = true;
open_log_file: {
log_file.maybe_file = std.Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }) catch std.Io.Dir.cwd().createFile(io, path, .{ .permissions = .fromMode(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.maybe_file = try std.Io.Dir.openFileAbsolute(io, "/dev/null", .{ .mode = .write_only });
}
var log_file_writer = log_file.maybe_file.?.writer(io, log_file.buffer);
// Seek to the end of the log file
if (could_open_log_file) {
const stat = try log_file.maybe_file.?.stat(io);
try log_file_writer.seekTo(stat.size);
}
log_file.maybe_file_writer = log_file_writer;
return could_open_log_file;
}

View File

@ -0,0 +1,52 @@
const std = @import("std");
const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror));
const PaddingInt = std.meta.Int(.unsigned, 8 - (@bitSizeOf(ErrInt) + @bitSizeOf(bool)) % 8);
const ErrorHandler = packed struct {
has_error: bool = false,
err_int: ErrInt = 0,
padding: PaddingInt = 0,
};
const SharedError = @This();
data: []align(std.heap.page_size_min) u8,
write_error_event_fn: ?*const fn (anyerror, *anyopaque) anyerror!void,
ctx: ?*anyopaque,
pub fn init(
write_error_event_fn: ?*const fn (anyerror, *anyopaque) anyerror!void,
ctx: ?*anyopaque,
) !SharedError {
const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), .{ .READ = true, .WRITE = true }, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0);
return .{
.data = data,
.write_error_event_fn = write_error_event_fn,
.ctx = ctx,
};
}
pub fn deinit(self: *SharedError) void {
std.posix.munmap(self.data);
}
pub fn writeError(self: SharedError, err: anyerror) void {
var writer: std.Io.Writer = .fixed(self.data);
writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }, .native) catch {};
if (self.write_error_event_fn) |write_error_event_fn| {
@call(.auto, write_error_event_fn, .{ err, self.ctx.? }) catch {};
}
}
pub fn readError(self: SharedError) ?anyerror {
var reader: std.Io.Reader = .fixed(self.data);
const err_handler = try reader.takeStruct(ErrorHandler, .native);
if (err_handler.has_error)
return @errorFromInt(err_handler.err_int);
return null;
}

View File

@ -1,51 +1,17 @@
const std = @import("std"); const std = @import("std");
const builtin = @import("builtin"); const builtin = @import("builtin");
const UidRange = @import("UidRange.zig"); const UidRange = @import("UidRange.zig");
const pwd = @import("pwd");
const stdlib = @import("stdlib");
const unistd = @import("unistd");
const grp = @import("grp");
const system_time = @import("system_time");
const time = @import("time");
pub const termbox = @import("termbox2"); pub const pam = @import("pam");
pub const utmp = @import("utmp");
pub const pam = @cImport({
@cInclude("security/pam_appl.h");
});
pub const utmp = @cImport({
@cInclude("utmpx.h");
});
// Exists for X11 support only // Exists for X11 support only
pub const xcb = @cImport({ pub const xcb = @import("xcb");
@cInclude("xcb/xcb.h");
});
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");
}
});
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 { pub const TimeOfDay = struct {
seconds: i64, seconds: i64,
@ -65,13 +31,8 @@ pub const UsernameEntry = struct {
fn PlatformStruct() type { fn PlatformStruct() type {
return switch (builtin.os.tag) { return switch (builtin.os.tag) {
.linux => struct { .linux => struct {
pub const kd = @cImport({ pub const kd = @import("kd");
@cInclude("sys/kd.h"); pub const vt = @import("vt");
});
pub const vt = @cImport({
@cInclude("sys/vt.h");
});
pub const LedState = c_char; pub const LedState = c_char;
pub const get_led_state = kd.KDGKBLED; pub const get_led_state = kd.KDGKBLED;
@ -81,15 +42,12 @@ fn PlatformStruct() type {
pub const vt_activate = vt.VT_ACTIVATE; pub const vt_activate = vt.VT_ACTIVATE;
pub const vt_waitactive = vt.VT_WAITACTIVE; pub const vt_waitactive = vt.VT_WAITACTIVE;
const SYSTEMD_HOMED_UID_MIN = 60001;
const SYSTEMD_HOMED_UID_MAX = 60513;
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void { pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
const status = grp.initgroups(username, @intCast(entry.gid)); const status = grp.initgroups(username, @intCast(entry.gid));
if (status != 0) return error.GroupInitializationFailed; if (status != 0) return error.GroupInitializationFailed;
std.posix.setgid(@intCast(entry.gid)) catch return error.SetUserGidFailed; if (isError(std.posix.system.setgid(@intCast(entry.gid)))) return error.SetUserGidFailed;
std.posix.setuid(@intCast(entry.uid)) catch return error.SetUserUidFailed; if (isError(std.posix.system.setuid(@intCast(entry.uid)))) return error.SetUserUidFailed;
} }
// Procedure: // Procedure:
@ -101,16 +59,29 @@ fn PlatformStruct() type {
// 4. Finally, compare the major and minor device numbers with the // 4. Finally, compare the major and minor device numbers with the
// extracted values. If they correspond, parse [dir] to get the // extracted values. If they correspond, parse [dir] to get the
// TTY ID // TTY ID
pub fn getActiveTtyImpl(allocator: std.mem.Allocator) !u8 { pub fn getActiveTtyImpl(allocator: std.mem.Allocator, io: std.Io, use_kmscon_vt: bool) !u8 {
var file_buffer: [256]u8 = undefined; var file_buffer: [256]u8 = undefined;
if (use_kmscon_vt) {
var file = try std.Io.Dir.openFileAbsolute(io, "/sys/class/tty/tty0/active", .{});
defer file.close(io);
var reader = file.reader(io, &file_buffer);
var buffer: [16]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer);
const tty = buffer[0..(read - 1)];
return std.fmt.parseInt(u8, tty["tty".len..], 10);
}
var tty_major: u16 = undefined; var tty_major: u16 = undefined;
var tty_minor: u16 = undefined; var tty_minor: u16 = undefined;
{ {
var file = try std.fs.openFileAbsolute("/proc/self/stat", .{}); var file = try std.Io.Dir.openFileAbsolute(io, "/proc/self/stat", .{});
defer file.close(); defer file.close(io);
var reader = file.reader(&file_buffer); var reader = file.reader(io, &file_buffer);
var buffer: [1024]u8 = undefined; var buffer: [1024]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer); const read = try readBuffer(&reader.interface, &buffer);
@ -128,18 +99,18 @@ fn PlatformStruct() type {
tty_minor = tty_nr % 256; tty_minor = tty_nr % 256;
} }
var directory = try std.fs.openDirAbsolute("/sys/class/tty", .{ .iterate = true }); var directory = try std.Io.Dir.openDirAbsolute(io, "/sys/class/tty", .{ .iterate = true });
defer directory.close(); defer directory.close(io);
var iterator = directory.iterate(); var iterator = directory.iterate();
while (try iterator.next()) |entry| { while (try iterator.next(io)) |entry| {
const path = try std.fmt.allocPrint(allocator, "/sys/class/tty/{s}/dev", .{entry.name}); const path = try std.fmt.allocPrint(allocator, "/sys/class/tty/{s}/dev", .{entry.name});
defer allocator.free(path); defer allocator.free(path);
var file = try std.fs.openFileAbsolute(path, .{}); var file = try std.Io.Dir.openFileAbsolute(io, path, .{});
defer file.close(); defer file.close(io);
var reader = file.reader(&file_buffer); var reader = file.reader(io, &file_buffer);
var buffer: [16]u8 = undefined; var buffer: [16]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer); const read = try readBuffer(&reader.interface, &buffer);
@ -162,11 +133,14 @@ fn PlatformStruct() type {
// This is very bad parsing, but we only need to get 2 values.. // 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 // and the format of the file seems to be standard? So this should
// be fine... // be fine...
pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange { pub fn getUserIdRange(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8) !UidRange {
const login_defs_file = try std.fs.cwd().openFile(file_path, .{}); const login_defs_file = try std.Io.Dir.cwd().openFile(io, file_path, .{});
defer login_defs_file.close(); defer login_defs_file.close(io);
const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16)); var buffer: [4096]u8 = undefined;
var reader = login_defs_file.reader(io, &buffer);
const login_defs_buffer = try reader.interface.allocRemaining(allocator, .unlimited);
defer allocator.free(login_defs_buffer); defer allocator.free(login_defs_buffer);
var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n'); var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n');
@ -187,19 +161,6 @@ fn PlatformStruct() type {
if (!nameFound) return error.UidNameNotFound; 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; return uid_range;
} }
@ -231,13 +192,8 @@ fn PlatformStruct() type {
} }
}, },
.freebsd => struct { .freebsd => struct {
pub const kbio = @cImport({ pub const kbio = @import("kbio");
@cInclude("sys/kbio.h"); pub const consio = @import("consio");
});
pub const consio = @cImport({
@cInclude("sys/consio.h");
});
pub const LedState = c_int; pub const LedState = c_int;
pub const get_led_state = kbio.KDGETLED; pub const get_led_state = kbio.KDGETLED;
@ -260,11 +216,11 @@ fn PlatformStruct() type {
if (result != 0) return error.SetUserUidFailed; if (result != 0) return error.SetUserUidFailed;
} }
pub fn getActiveTtyImpl(_: std.mem.Allocator) !u8 { pub fn getActiveTtyImpl(_: std.mem.Allocator, _: std.Io, _: bool) !u8 {
return error.FeatureUnimplemented; return error.FeatureUnimplemented;
} }
pub fn getUserIdRange(_: std.mem.Allocator, _: []const u8) !UidRange { pub fn getUserIdRange(_: std.mem.Allocator, _: std.Io, _: []const u8) !UidRange {
return .{ return .{
// Hardcoded default values chosen from // Hardcoded default values chosen from
// /usr/src/usr.sbin/pw/pw_conf.c // /usr/src/usr.sbin/pw/pw_conf.c
@ -279,12 +235,27 @@ fn PlatformStruct() type {
const platform_struct = PlatformStruct(); const platform_struct = PlatformStruct();
pub fn isError(result: anytype) bool {
if (@typeInfo(@TypeOf(result)).int.signedness == .signed) {
return result < 0;
}
if (@typeInfo(@TypeOf(result)).int.signedness == .unsigned) {
return switch (builtin.os.tag) {
.linux => std.os.linux.errno(result) != .SUCCESS,
else => @compileError("interop.isError() not implemented for current target!"),
};
}
unreachable;
}
pub fn supportsUnicode() bool { pub fn supportsUnicode() bool {
return builtin.os.tag == .linux or builtin.os.tag == .freebsd; return builtin.os.tag == .linux or builtin.os.tag == .freebsd;
} }
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) []u8 { pub fn timeAsString(io: std.Io, buf: [:0]u8, format: [:0]const u8) []u8 {
const timer = std.time.timestamp(); const timer: isize = @intCast(std.Io.Timestamp.now(io, .real).toSeconds());
const tm_info = time.localtime(&timer); const tm_info = time.localtime(&timer);
const len = time.strftime(buf, buf.len, format, tm_info); const len = time.strftime(buf, buf.len, format, tm_info);
@ -303,8 +274,8 @@ pub fn getTimeOfDay() !TimeOfDay {
}; };
} }
pub fn getActiveTty(allocator: std.mem.Allocator) !u8 { pub fn getActiveTty(allocator: std.mem.Allocator, io: std.Io, use_kmscon_vt: bool) !u8 {
return platform_struct.getActiveTtyImpl(allocator); return platform_struct.getActiveTtyImpl(allocator, io, use_kmscon_vt);
} }
pub fn switchTty(tty: u8) !void { pub fn switchTty(tty: u8) !void {
@ -407,6 +378,6 @@ pub fn closePasswordDatabase() void {
// This is very bad parsing, but we only need to get 2 values... and the format // 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... // 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 { pub fn getUserIdRange(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8) !UidRange {
return platform_struct.getUserIdRange(allocator, file_path); return platform_struct.getUserIdRange(allocator, io, file_path);
} }

78
ly-core/src/root.zig Normal file
View File

@ -0,0 +1,78 @@
const std = @import("std");
pub const ini = @import("zigini");
pub const interop = @import("interop.zig");
pub const UidRange = @import("UidRange.zig");
pub const LogFile = @import("LogFile.zig");
pub const SharedError = @import("SharedError.zig");
pub fn IniParser(comptime Struct: type) type {
return struct {
const Self = @This();
const temporary_allocator = std.heap.page_allocator;
pub const Error = struct {
type_name: []const u8,
key: []const u8,
value: []const u8,
error_name: []const u8,
};
pub var global_errors: std.ArrayList(Error) = .empty;
ini_struct: ini.Ini(Struct),
structure: Struct,
maybe_load_error: ?anyerror,
errors: std.ArrayList(Error),
pub fn init(
allocator: std.mem.Allocator,
io: std.Io,
path: []const u8,
field_handler: ?fn (allocator: std.mem.Allocator, field: ini.IniField) ?ini.IniField,
) !Self {
var ini_struct = ini.Ini(Struct).init(allocator);
errdefer ini_struct.deinit();
var maybe_load_error: ?anyerror = null;
const structure = ini_struct.readFileToStruct(io, path, .{
.fieldHandler = field_handler,
.errorHandler = errorHandler,
.comment_characters = "#",
}) catch |err| load_error: {
maybe_load_error = err;
break :load_error Struct{};
};
return .{
.ini_struct = ini_struct,
.structure = structure,
.maybe_load_error = maybe_load_error,
.errors = global_errors,
};
}
pub fn deinit(self: *Self) void {
self.ini_struct.deinit();
for (0..global_errors.items.len) |i| {
const err = global_errors.items[i];
temporary_allocator.free(err.type_name);
temporary_allocator.free(err.key);
temporary_allocator.free(err.value);
}
global_errors.deinit(temporary_allocator);
}
fn errorHandler(type_name: []const u8, key: []const u8, value: []const u8, err: anyerror) void {
global_errors.append(temporary_allocator, .{
.type_name = temporary_allocator.dupe(u8, type_name) catch return,
.key = temporary_allocator.dupe(u8, key) catch return,
.value = temporary_allocator.dupe(u8, value) catch return,
.error_name = @errorName(err),
}) catch return;
}
};
}

57
ly-ui/build.zig Normal file
View File

@ -0,0 +1,57 @@
const std = @import("std");
const Translator = @import("translate_c").Translator;
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support") orelse true;
const mod = b.addModule("ly-ui", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
});
const ly_core = b.dependency("ly_core", .{
.target = target,
.optimize = optimize,
.enable_x11_support = enable_x11_support,
});
mod.addImport("ly-core", ly_core.module("ly-core"));
const termbox_dep = b.dependency("termbox2", .{
.target = target,
.optimize = optimize,
});
const translate_c_dep = b.dependency("translate_c", .{
.target = target,
});
const termbox2: Translator = .init(translate_c_dep, .{
.c_source_file = termbox_dep.path("termbox2.h"),
.target = target,
.optimize = optimize,
});
termbox2.defineCMacro("TB_IMPL", null);
// TODO 0.16.0: Workaround until Aro gets better...
// https://codeberg.org/ziglang/translate-c/issues/319
termbox2.defineCMacro("_XOPEN_SOURCE", "700");
termbox2.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit)
// TODO 0.16.0: Including <fcntl.h> with -OReleaseSafe causes
// __attribute__(__error__()) to be called. Below
// is the workaround.
termbox2.defineCMacro("_FORTIFY_SOURCE", "0");
// TODO 0.16.0: Needed for now
if (target.result.os.tag == .freebsd) {
termbox2.defineCMacro("__BSD_VISIBLE", "1");
}
mod.addImport("termbox2", termbox2.mod);
const mod_tests = b.addTest(.{
.root_module = mod,
});
const run_mod_tests = b.addRunArtifact(mod_tests);
const test_step = b.step("test", "Run tests");
test_step.dependOn(&run_mod_tests.step);
}

24
ly-ui/build.zig.zon Normal file
View File

@ -0,0 +1,24 @@
.{
.name = .ly_ui,
.version = "1.1.0",
.fingerprint = 0x8d11bf85a74ec803,
.minimum_zig_version = "0.16.0",
.dependencies = .{
.ly_core = .{
.path = "../ly-core",
},
.termbox2 = .{
.url = "git+https://github.com/AnErrupTion/termbox2?ref=master#c7f241e8888ce243e1748b05c26a42fcfaaad936",
.hash = "N-V-__8AAAUXBQD6Fwpi9m0MBqWXFFaqW5l1lVrJC2Ynj7a-",
},
.translate_c = .{
.url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7",
.hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q",
},
},
.paths = .{
"build.zig",
"build.zig.zon",
"src",
},
}

View File

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

221
ly-ui/src/Position.zig Normal file
View File

@ -0,0 +1,221 @@
const Position = @This();
x: usize,
y: usize,
pub fn init(x: usize, y: usize) Position {
return .{
.x = x,
.y = y,
};
}
pub fn add(self: Position, other: Position) Position {
return .{
.x = self.x + other.x,
.y = self.y + other.y,
};
}
pub fn addIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x + if (condition) other.x else 0,
.y = self.y + if (condition) other.y else 0,
};
}
pub fn addX(self: Position, x: usize) Position {
return .{
.x = self.x + x,
.y = self.y,
};
}
pub fn addY(self: Position, y: usize) Position {
return .{
.x = self.x,
.y = self.y + y,
};
}
pub fn addXIf(self: Position, x: usize, condition: bool) Position {
return .{
.x = self.x + if (condition) x else 0,
.y = self.y,
};
}
pub fn addYIf(self: Position, y: usize, condition: bool) Position {
return .{
.x = self.x,
.y = self.y + if (condition) y else 0,
};
}
pub fn addXFrom(self: Position, other: Position) Position {
return .{
.x = self.x + other.x,
.y = self.y,
};
}
pub fn addYFrom(self: Position, other: Position) Position {
return .{
.x = self.x,
.y = self.y + other.y,
};
}
pub fn addXFromIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x + if (condition) other.x else 0,
.y = self.y,
};
}
pub fn addYFromIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x,
.y = self.y + if (condition) other.y else 0,
};
}
pub fn remove(self: Position, other: Position) Position {
return .{
.x = self.x - other.x,
.y = self.y - other.y,
};
}
pub fn removeIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x - if (condition) other.x else 0,
.y = self.y - if (condition) other.y else 0,
};
}
pub fn removeX(self: Position, x: usize) Position {
return .{
.x = self.x - x,
.y = self.y,
};
}
pub fn removeY(self: Position, y: usize) Position {
return .{
.x = self.x,
.y = self.y - y,
};
}
pub fn removeXIf(self: Position, x: usize, condition: bool) Position {
return .{
.x = self.x - if (condition) x else 0,
.y = self.y,
};
}
pub fn removeYIf(self: Position, y: usize, condition: bool) Position {
return .{
.x = self.x,
.y = self.y - if (condition) y else 0,
};
}
pub fn removeXFrom(self: Position, other: Position) Position {
return .{
.x = self.x - other.x,
.y = self.y,
};
}
pub fn removeYFrom(self: Position, other: Position) Position {
return .{
.x = self.x,
.y = self.y - other.y,
};
}
pub fn removeXFromIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x - if (condition) other.x else 0,
.y = self.y,
};
}
pub fn removeYFromIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x,
.y = self.y - if (condition) other.y else 0,
};
}
pub fn invert(self: Position, other: Position) Position {
return .{
.x = other.x - self.x,
.y = other.y - self.y,
};
}
pub fn invertIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = if (condition) other.x - self.x else self.x,
.y = if (condition) other.y - self.y else self.y,
};
}
pub fn invertX(self: Position, width: usize) Position {
return .{
.x = width - self.x,
.y = self.y,
};
}
pub fn invertY(self: Position, height: usize) Position {
return .{
.x = self.x,
.y = height - self.y,
};
}
pub fn invertXIf(self: Position, width: usize, condition: bool) Position {
return .{
.x = if (condition) width - self.x else self.x,
.y = self.y,
};
}
pub fn invertYIf(self: Position, height: usize, condition: bool) Position {
return .{
.x = self.x,
.y = if (condition) height - self.y else self.y,
};
}
pub fn resetXFrom(self: Position, other: Position) Position {
return .{
.x = other.x,
.y = self.y,
};
}
pub fn resetYFrom(self: Position, other: Position) Position {
return .{
.x = self.x,
.y = other.y,
};
}
pub fn resetXFromIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = if (condition) other.x else self.x,
.y = self.y,
};
}
pub fn resetYFromIf(self: Position, other: Position, condition: bool) Position {
return .{
.x = self.x,
.y = if (condition) other.y else self.y,
};
}

View File

@ -0,0 +1,679 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const Random = std.Random;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const LogFile = ly_core.LogFile;
const SharedError = ly_core.SharedError;
pub const termbox = @import("termbox2");
const Cell = @import("Cell.zig");
const keyboard = @import("keyboard.zig");
const Position = @import("Position.zig");
const Widget = @import("Widget.zig");
const TerminalBuffer = @This();
pub const KeybindCallbackFn = *const fn (*anyopaque) anyerror!bool;
pub const KeybindMap = std.AutoHashMap(keyboard.Key, struct {
callback: KeybindCallbackFn,
context: *anyopaque,
});
pub const InitOptions = struct {
fg: u32,
bg: u32,
border_fg: u32,
full_color: bool,
is_tty: bool,
};
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;
};
pub const START_POSITION = Position.init(0, 0);
log_file: *LogFile,
random: Random,
width: usize,
height: usize,
fg: u32,
bg: u32,
border_fg: u32,
box_chars: struct {
left_up: u32,
left_down: u32,
right_up: u32,
right_down: u32,
top: u32,
bottom: u32,
left: u32,
right: u32,
},
blank_cell: Cell,
full_color: bool,
termios: ?std.posix.termios,
keybinds: KeybindMap,
handlable_widgets: std.ArrayList(*Widget),
run: bool,
update: bool,
active_widget_index: usize,
pub fn init(
allocator: Allocator,
io: std.Io,
options: InitOptions,
log_file: *LogFile,
random: Random,
) !TerminalBuffer {
// Initialize termbox
var err = termbox.tb_init();
if (err != 0) {
try log_file.err(
io,
"tui",
"failed to initialise termbox2: {s}, term: {s}",
.{ termbox.tb_strerror(err), std.c.getenv("TERM").? },
);
return error.TermboxInitFailed;
}
if (options.full_color) {
err = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
if (err != 0) {
try log_file.err(
io,
"tui",
"failed to set termbox2 output mode to 24-bit color: {s}",
.{termbox.tb_strerror(err)},
);
return error.TermboxSetOutputModeFailed;
}
try log_file.info(
io,
"tui",
"termbox2 set to 24-bit color output mode",
.{},
);
} else {
try log_file.info(
io,
"tui",
"termbox2 set to eight-color output mode",
.{},
);
}
// Let's take some precautions here and clear the back buffer as well
try clearScreen(true);
const width = getWidth();
const height = getHeight();
try log_file.info(
io,
"tui",
"screen resolution is {d}x{d}",
.{ width, height },
);
return .{
.log_file = log_file,
.random = random,
.width = width,
.height = height,
.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,
.right_down = 0x2518,
.top = 0x2500,
.bottom = 0x2500,
.left = 0x2502,
.right = 0x2502,
} else .{
.left_up = '+',
.left_down = '+',
.right_up = '+',
.right_down = '+',
.top = '-',
.bottom = '-',
.left = '|',
.right = '|',
},
.blank_cell = Cell.init(' ', options.fg, options.bg),
.full_color = options.full_color,
// Needed to reclaim the TTY after giving up its control
.termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO),
.keybinds = KeybindMap.init(allocator),
.handlable_widgets = .empty,
.run = true,
.update = true,
.active_widget_index = 0,
};
}
pub fn deinit(self: *TerminalBuffer) void {
self.keybinds.deinit();
TerminalBuffer.shutdown() catch {};
}
pub fn runEventLoop(
self: *TerminalBuffer,
allocator: Allocator,
io: std.Io,
shared_error: SharedError,
layers: [][]*Widget,
active_widget: *Widget,
inactivity_delay: u16,
position_widgets_fn: *const fn (*anyopaque) anyerror!void,
inactivity_event_fn: ?*const fn (*anyopaque) anyerror!void,
context: *anyopaque,
) !void {
try self.registerGlobalKeybind(io, "Ctrl+K", &moveCursorUp, self);
try self.registerGlobalKeybind(io, "Up", &moveCursorUp, self);
try self.registerGlobalKeybind(io, "Ctrl+J", &moveCursorDown, self);
try self.registerGlobalKeybind(io, "Down", &moveCursorDown, self);
try self.registerGlobalKeybind(io, "Tab", &wrapCursor, self);
try self.registerGlobalKeybind(io, "Shift+Tab", &wrapCursorReverse, self);
defer self.handlable_widgets.deinit(allocator);
var i: usize = 0;
for (layers) |layer| {
for (layer) |widget| {
try widget.update(context);
if (widget.vtable.handle_fn != null) {
try self.handlable_widgets.append(allocator, widget);
if (widget.id == active_widget.id) self.active_widget_index = i;
i += 1;
}
}
}
try @call(.auto, position_widgets_fn, .{context});
var event: termbox.tb_event = undefined;
var inactivity_cmd_ran = false;
var inactivity_time_start = try interop.getTimeOfDay();
while (self.run) {
var maybe_timeout: ?usize = null;
if (self.update) {
try TerminalBuffer.clearScreen(false);
// Reset cursor
const current_widget = self.getActiveWidget();
current_widget.handle(null) catch |err| {
shared_error.writeError(error.SetCursorFailed);
try self.log_file.err(
io,
"tui",
"failed to set cursor in active widget '{s}': {s}",
.{ current_widget.display_name, @errorName(err) },
);
};
for (layers) |layer| {
for (layer) |widget| {
try widget.update(context);
widget.draw();
if (try widget.calculateTimeout(context)) |widget_timeout| {
if (maybe_timeout == null or widget_timeout < maybe_timeout.?) maybe_timeout = widget_timeout;
}
}
}
try TerminalBuffer.presentBuffer();
}
if (inactivity_event_fn) |inactivity_fn| {
const time = try interop.getTimeOfDay();
if (!inactivity_cmd_ran and time.seconds - inactivity_time_start.seconds > inactivity_delay) {
try @call(.auto, inactivity_fn, .{context});
inactivity_cmd_ran = true;
}
}
const event_error = if (maybe_timeout) |timeout| termbox.tb_peek_event(&event, @intCast(timeout)) else termbox.tb_poll_event(&event);
self.update = maybe_timeout != null or event_error >= 0;
if (event_error < 0) continue;
// Input of some kind was detected, so reset the inactivity timer
inactivity_time_start = try interop.getTimeOfDay();
if (event.type == termbox.TB_EVENT_RESIZE) {
self.width = TerminalBuffer.getWidth();
self.height = TerminalBuffer.getHeight();
try self.log_file.info(
io,
"tui",
"screen resolution updated to {d}x{d}",
.{ self.width, self.height },
);
for (layers) |layer| {
for (layer) |widget| {
widget.realloc() catch |err| {
shared_error.writeError(error.WidgetReallocationFailed);
try self.log_file.err(
io,
"tui",
"failed to reallocate widget '{s}': {s}",
.{ widget.display_name, @errorName(err) },
);
};
}
}
try @call(.auto, position_widgets_fn, .{context});
self.update = true;
continue;
}
var maybe_keys = try self.handleKeybind(allocator, event);
if (maybe_keys) |*keys| {
defer keys.deinit(allocator);
const current_widget = self.getActiveWidget();
for (keys.items) |key| {
current_widget.handle(key) catch |err| {
shared_error.writeError(error.CurrentWidgetHandlingFailed);
try self.log_file.err(
io,
"tui",
"failed to handle active widget '{s}': {s}",
.{ current_widget.display_name, @errorName(err) },
);
};
}
self.update = true;
}
}
}
pub fn stopEventLoop(self: *TerminalBuffer) void {
self.run = false;
}
pub fn drawNextFrame(self: *TerminalBuffer, value: bool) void {
self.update = value;
}
pub fn getActiveWidget(self: *TerminalBuffer) *Widget {
return self.handlable_widgets.items[self.active_widget_index];
}
pub fn setActiveWidget(self: *TerminalBuffer, widget: *Widget) void {
for (self.handlable_widgets.items, 0..) |widg, i| {
if (widg.id == widget.id) self.active_widget_index = i;
}
}
pub fn getWidth() usize {
return @intCast(termbox.tb_width());
}
pub fn getHeight() usize {
return @intCast(termbox.tb_height());
}
pub fn setCursor(x: usize, y: usize) !void {
if (termbox.tb_set_cursor(@intCast(x), @intCast(y)) != 0) {
return error.TermboxSetCursorFailed;
}
}
pub fn clearScreen(clear_back_buffer: bool) !void {
if (termbox.tb_clear() != 0) return error.TermboxClearFailed;
if (clear_back_buffer) try clearBackBuffer();
}
pub fn shutdown() !void {
if (termbox.tb_shutdown() != 0) return error.TermboxShutdownFailed;
}
pub fn presentBuffer() !void {
if (termbox.tb_present() != 0) return error.TermboxPresentFailed;
}
pub fn getCell(x: usize, y: usize) ?Cell {
var maybe_cell: ?*termbox.tb_cell = undefined;
if (termbox.tb_get_cell(
@intCast(x),
@intCast(y),
1,
&maybe_cell,
) != 0) {
return null;
}
if (maybe_cell) |cell| {
return Cell.init(cell.ch, cell.fg, cell.bg);
}
return null;
}
pub fn setCell(x: usize, y: usize, cell: Cell) !void {
if (termbox.tb_set_cell(
@intCast(x),
@intCast(y),
cell.ch,
cell.fg,
cell.bg,
) != 0) {
return error.TermboxSetCellFailed;
}
}
pub fn setCellBoundsChecked(self: *TerminalBuffer, x: isize, y: isize, cell: Cell) !void {
if (0 <= x and x < self.width and 0 <= y and y < self.height) {
try cell.put(@intCast(x), @intCast(y));
}
}
pub fn reclaim(self: TerminalBuffer) !void {
if (self.termios) |termios| {
// Take back control of the TTY
const err = termbox.tb_init();
if (err != 0 and err != termbox.TB_ERR_INIT_ALREADY) return error.TermboxReinitFailed;
if (self.full_color and termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR) != 0) {
return error.TermboxSetOutputModeFailed;
}
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, termios);
}
}
pub fn registerKeybind(
self: *TerminalBuffer,
io: std.Io,
keybinds: *KeybindMap,
keybind: []const u8,
callback: KeybindCallbackFn,
context: *anyopaque,
) !void {
const key = try self.parseKeybind(io, keybind);
keybinds.put(key, .{
.callback = callback,
.context = context,
}) catch |err| {
try self.log_file.err(
io,
"tui",
"failed to register keybind {s}: {s}",
.{ keybind, @errorName(err) },
);
};
}
pub fn registerGlobalKeybind(
self: *TerminalBuffer,
io: std.Io,
keybind: []const u8,
callback: KeybindCallbackFn,
context: *anyopaque,
) !void {
try self.registerKeybind(io, &self.keybinds, keybind, callback, context);
}
pub fn simulateKeybind(self: *TerminalBuffer, io: std.Io, keybind: []const u8) !bool {
const key = try self.parseKeybind(io, keybind);
if (self.keybinds.get(key)) |binding| {
return try @call(
.auto,
binding.callback,
.{binding.context},
);
}
const current_widget = self.getActiveWidget();
if (current_widget.keybinds) |keybinds| {
if (keybinds.get(key)) |binding| {
return try @call(
.auto,
binding.callback,
.{binding.context},
);
}
}
return true;
}
pub fn drawText(
text: []const u8,
x: usize,
y: usize,
fg: u32,
bg: u32,
) !void {
const utf8view = std.unicode.Utf8View.init(text) catch return;
var utf8 = utf8view.iterator();
var i = x;
while (utf8.nextCodepoint()) |codepoint| : (i += @intCast(termbox.tb_wcwidth(codepoint))) {
const cell = Cell.init(codepoint, fg, bg);
try cell.put(i, y);
}
}
pub fn drawConfinedText(
text: []const u8,
x: usize,
y: usize,
max_length: usize,
fg: u32,
bg: u32,
) !void {
const utf8view = std.unicode.Utf8View.init(text) catch return;
var utf8 = utf8view.iterator();
var i = x;
while (utf8.nextCodepoint()) |codepoint| : (i += @intCast(termbox.tb_wcwidth(codepoint))) {
if (i - x >= max_length) break;
const cell = Cell.init(codepoint, fg, bg);
try cell.put(i, y);
}
}
pub fn drawCharMultiple(
char: u32,
x: usize,
y: usize,
length: usize,
fg: u32,
bg: u32,
) !void {
const cell = Cell.init(char, fg, bg);
for (0..length) |xx| try 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) usize {
const utf8view = std.unicode.Utf8View.init(str) catch return str.len;
var utf8 = utf8view.iterator();
var length: c_int = 0;
while (utf8.nextCodepoint()) |codepoint| {
length += termbox.tb_wcwidth(codepoint);
}
return @intCast(length);
}
fn clearBackBuffer() !void {
if (termbox.global.initialized == 0) return;
// Clear the TTY because termbox2 doesn't seem to do it properly
const capability = termbox.global.caps[termbox.TB_CAP_CLEAR_SCREEN];
const capability_slice = std.mem.span(capability);
const result = std.posix.system.write(termbox.global.ttyfd, capability_slice.ptr, capability_slice.len);
if (result != capability_slice.len) return error.PartialClearBackBuffer;
if (result < 0) return error.ClearBackBufferFailed;
}
fn parseKeybind(self: *TerminalBuffer, io: std.Io, keybind: []const u8) !keyboard.Key {
var key = std.mem.zeroes(keyboard.Key);
var iterator = std.mem.splitScalar(u8, keybind, '+');
while (iterator.next()) |item| {
var found = false;
inline for (std.meta.fields(keyboard.Key)) |field| {
if (std.ascii.eqlIgnoreCase(field.name, item)) {
@field(key, field.name) = true;
found = true;
break;
}
}
if (!found) {
try self.log_file.err(
io,
"tui",
"failed to parse key {s} of keybind {s}",
.{ item, keybind },
);
}
}
return key;
}
fn handleKeybind(
self: *TerminalBuffer,
allocator: Allocator,
tb_event: termbox.tb_event,
) !?std.ArrayList(keyboard.Key) {
var keys = try keyboard.getKeyList(allocator, tb_event);
for (keys.items) |key| {
if (self.keybinds.get(key)) |binding| {
const passthrough_event = try @call(
.auto,
binding.callback,
.{binding.context},
);
if (!passthrough_event) {
keys.deinit(allocator);
return null;
}
return keys;
}
const current_widget = self.getActiveWidget();
if (current_widget.keybinds) |keybinds| {
if (keybinds.get(key)) |binding| {
const passthrough_event = try @call(
.auto,
binding.callback,
.{binding.context},
);
if (!passthrough_event) {
keys.deinit(allocator);
return null;
}
return keys;
}
}
}
return keys;
}
fn moveCursorUp(ptr: *anyopaque) !bool {
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
if (state.active_widget_index == 0) return false;
state.active_widget_index -= 1;
state.update = true;
return false;
}
fn moveCursorDown(ptr: *anyopaque) !bool {
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
if (state.active_widget_index == state.handlable_widgets.items.len - 1) return false;
state.active_widget_index += 1;
state.update = true;
return false;
}
fn wrapCursor(ptr: *anyopaque) !bool {
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
state.active_widget_index = (state.active_widget_index + 1) % state.handlable_widgets.items.len;
state.update = true;
return false;
}
fn wrapCursorReverse(ptr: *anyopaque) !bool {
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
state.active_widget_index = if (state.active_widget_index == 0) state.handlable_widgets.items.len - 1 else state.active_widget_index - 1;
state.update = true;
return false;
}

186
ly-ui/src/Widget.zig Normal file
View File

@ -0,0 +1,186 @@
const Widget = @This();
const keyboard = @import("keyboard.zig");
const TerminalBuffer = @import("TerminalBuffer.zig");
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,
update_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!void,
handle_fn: ?*const fn (ptr: *anyopaque, maybe_key: ?keyboard.Key) anyerror!void,
calculate_timeout_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!?usize,
};
pub var idCounter: u64 = 0;
id: u64,
display_name: []const u8,
keybinds: ?TerminalBuffer.KeybindMap,
pointer: *anyopaque,
vtable: VTable,
pub fn init(
display_name: []const u8,
keybinds: ?TerminalBuffer.KeybindMap,
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,
comptime update_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!void,
comptime handle_fn: ?fn (ptr: @TypeOf(pointer), maybe_key: ?keyboard.Key) anyerror!void,
comptime calculate_timeout_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!?usize,
) Widget {
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) !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},
);
}
pub fn updateImpl(ptr: *anyopaque, ctx: *anyopaque) !void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(
.always_inline,
update_fn.?,
.{ impl, ctx },
);
}
pub fn handleImpl(ptr: *anyopaque, maybe_key: ?keyboard.Key) !void {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(
.always_inline,
handle_fn.?,
.{ impl, maybe_key },
);
}
pub fn calculateTimeoutImpl(ptr: *anyopaque, ctx: *anyopaque) !?usize {
const impl: Pointer = @ptrCast(@alignCast(ptr));
return @call(
.always_inline,
calculate_timeout_fn.?,
.{ impl, ctx },
);
}
const vtable = VTable{
.deinit_fn = if (deinit_fn != null) deinitImpl else null,
.realloc_fn = if (realloc_fn != null) reallocImpl else null,
.draw_fn = drawImpl,
.update_fn = if (update_fn != null) updateImpl else null,
.handle_fn = if (handle_fn != null) handleImpl else null,
.calculate_timeout_fn = if (calculate_timeout_fn != null) calculateTimeoutImpl else null,
};
};
idCounter += 1;
return .{
.id = idCounter,
.display_name = display_name,
.keybinds = keybinds,
.pointer = pointer,
.vtable = Impl.vtable,
};
}
pub fn deinit(self: *Widget) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
if (self.vtable.deinit_fn) |deinit_fn| {
return @call(
.auto,
deinit_fn,
.{impl},
);
}
}
pub fn realloc(self: *Widget) !void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
if (self.vtable.realloc_fn) |realloc_fn| {
return @call(
.auto,
realloc_fn,
.{impl},
);
}
}
pub fn draw(self: *Widget) void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
@call(
.auto,
self.vtable.draw_fn,
.{impl},
);
}
pub fn update(self: *Widget, ctx: *anyopaque) !void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
if (self.vtable.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ impl, ctx },
);
}
}
pub fn handle(self: *Widget, maybe_key: ?keyboard.Key) !void {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
if (self.vtable.handle_fn) |handle_fn| {
return @call(
.auto,
handle_fn,
.{ impl, maybe_key },
);
}
}
pub fn calculateTimeout(self: *Widget, ctx: *anyopaque) !?usize {
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
if (self.vtable.calculate_timeout_fn) |calculate_timeout_fn| {
return @call(
.auto,
calculate_timeout_fn,
.{ impl, ctx },
);
}
return null;
}

View File

@ -0,0 +1,237 @@
const BigLabel = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const ly_core = @import("ly-core");
const interop = ly_core.interop;
const en = @import("bigLabelLocales/en.zig");
const fa = @import("bigLabelLocales/fa.zig");
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
pub const CHAR_WIDTH = 5;
pub const CHAR_HEIGHT = 5;
pub const CHAR_SIZE = CHAR_WIDTH * CHAR_HEIGHT;
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
pub const O: u32 = 0;
// zig fmt: off
pub const LocaleChars = struct {
ZERO: [CHAR_SIZE]u21,
ONE: [CHAR_SIZE]u21,
TWO: [CHAR_SIZE]u21,
THREE: [CHAR_SIZE]u21,
FOUR: [CHAR_SIZE]u21,
FIVE: [CHAR_SIZE]u21,
SIX: [CHAR_SIZE]u21,
SEVEN: [CHAR_SIZE]u21,
EIGHT: [CHAR_SIZE]u21,
NINE: [CHAR_SIZE]u21,
S: [CHAR_SIZE]u21,
E: [CHAR_SIZE]u21,
P: [CHAR_SIZE]u21,
A: [CHAR_SIZE]u21,
M: [CHAR_SIZE]u21,
};
// zig fmt: on
pub const BigLabelLocale = enum {
en,
fa,
};
instance: ?Widget = null,
allocator: ?Allocator = null,
buffer: *TerminalBuffer,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
component_pos: Position,
children_pos: Position,
pub fn init(
buffer: *TerminalBuffer,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
) BigLabel {
return .{
.instance = null,
.allocator = null,
.buffer = buffer,
.text = text,
.max_width = max_width,
.fg = fg,
.bg = bg,
.locale = locale,
.update_fn = update_fn,
.calculate_timeout_fn = calculate_timeout_fn,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn deinit(self: *BigLabel) void {
if (self.allocator) |allocator| allocator.free(self.text);
}
pub fn widget(self: *BigLabel) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"BigLabel",
null,
self,
deinit,
null,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
}
pub fn setTextAlloc(
self: *BigLabel,
allocator: Allocator,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.allocPrint(allocator, fmt, args);
self.allocator = allocator;
}
pub fn setTextBuf(
self: *BigLabel,
buffer: []u8,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.bufPrint(buffer, fmt, args);
self.allocator = null;
}
pub fn setText(self: *BigLabel, text: []const u8) void {
self.text = text;
self.allocator = null;
}
pub fn positionX(self: *BigLabel, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text) * CHAR_WIDTH);
}
pub fn positionY(self: *BigLabel, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(CHAR_HEIGHT);
}
pub fn positionXY(self: *BigLabel, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
TerminalBuffer.strWidth(self.text) * CHAR_WIDTH,
CHAR_HEIGHT,
).add(original_pos);
}
pub fn childrenPosition(self: BigLabel) Position {
return self.children_pos;
}
fn draw(self: *BigLabel) void {
for (self.text, 0..) |c, i| {
const clock_cell = clockCell(
c,
self.fg,
self.bg,
self.locale,
);
alphaBlit(
self.component_pos.x + i * (CHAR_WIDTH + 1),
self.component_pos.y,
self.buffer.width,
self.buffer.height,
clock_cell,
);
}
}
fn update(self: *BigLabel, context: *anyopaque) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, context },
);
}
}
fn calculateTimeout(self: *BigLabel, ctx: *anyopaque) !?usize {
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
return @call(
.auto,
calculate_timeout_fn,
.{ self, ctx },
);
}
return null;
}
fn clockCell(char: u8, fg: u32, bg: u32, locale: BigLabelLocale) [CHAR_SIZE]Cell {
var cells: [CHAR_SIZE]Cell = undefined;
//@divTrunc(time.microseconds, 500000) != 0)
const clock_chars = toBigNumber(char, locale);
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
return cells;
}
fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [CHAR_SIZE]Cell) void {
if (x + CHAR_WIDTH >= tb_width or y + CHAR_HEIGHT >= tb_height) return;
for (0..CHAR_HEIGHT) |yy| {
for (0..CHAR_WIDTH) |xx| {
const cell = cells[yy * CHAR_WIDTH + xx];
cell.put(x + xx, y + yy) catch {};
}
}
}
fn toBigNumber(char: u8, locale: BigLabelLocale) [CHAR_SIZE]u21 {
const locale_chars = switch (locale) {
.fa => fa.locale_chars,
.en => en.locale_chars,
};
return switch (char) {
'0' => locale_chars.ZERO,
'1' => locale_chars.ONE,
'2' => locale_chars.TWO,
'3' => locale_chars.THREE,
'4' => locale_chars.FOUR,
'5' => locale_chars.FIVE,
'6' => locale_chars.SIX,
'7' => locale_chars.SEVEN,
'8' => locale_chars.EIGHT,
'9' => locale_chars.NINE,
'p', 'P' => locale_chars.P,
'a', 'A' => locale_chars.A,
'm', 'M' => locale_chars.M,
':' => locale_chars.S,
else => locale_chars.E,
};
}

View File

@ -0,0 +1,190 @@
const std = @import("std");
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
const Box = @This();
instance: ?Widget = null,
buffer: *TerminalBuffer,
horizontal_margin: usize,
vertical_margin: usize,
width: usize,
height: usize,
show_borders: bool,
blank_box: bool,
top_title: ?[]const u8,
bottom_title: ?[]const u8,
border_fg: u32,
title_fg: u32,
bg: u32,
update_fn: ?*const fn (*Box, *anyopaque) anyerror!void,
left_pos: Position,
right_pos: Position,
children_pos: Position,
pub fn init(
buffer: *TerminalBuffer,
horizontal_margin: usize,
vertical_margin: usize,
width: usize,
height: usize,
show_borders: bool,
blank_box: bool,
top_title: ?[]const u8,
bottom_title: ?[]const u8,
border_fg: u32,
title_fg: u32,
bg: u32,
update_fn: ?*const fn (*Box, *anyopaque) anyerror!void,
) Box {
return .{
.instance = null,
.buffer = buffer,
.horizontal_margin = horizontal_margin,
.vertical_margin = vertical_margin,
.width = width,
.height = height,
.show_borders = show_borders,
.blank_box = blank_box,
.top_title = top_title,
.bottom_title = bottom_title,
.border_fg = border_fg,
.title_fg = title_fg,
.bg = bg,
.update_fn = update_fn,
.left_pos = TerminalBuffer.START_POSITION,
.right_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn widget(self: *Box) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Box",
null,
self,
null,
null,
draw,
update,
null,
null,
);
return &self.instance.?;
}
pub fn positionXY(self: *Box, original_pos: Position) void {
if (self.buffer.width < 2 or self.buffer.height < 2) return;
self.left_pos = original_pos;
self.right_pos = Position.init(
@min(self.buffer.width, self.width),
@min(self.buffer.height, self.height),
).add(self.left_pos);
self.children_pos = Position.init(
self.horizontal_margin,
self.vertical_margin,
).add(self.left_pos);
}
pub fn childrenPosition(self: Box) Position {
return self.children_pos;
}
fn draw(self: *Box) void {
if (self.show_borders) {
var left_up = Cell.init(
self.buffer.box_chars.left_up,
self.border_fg,
self.bg,
);
var right_up = Cell.init(
self.buffer.box_chars.right_up,
self.border_fg,
self.bg,
);
var left_down = Cell.init(
self.buffer.box_chars.left_down,
self.border_fg,
self.bg,
);
var right_down = Cell.init(
self.buffer.box_chars.right_down,
self.border_fg,
self.bg,
);
var top = Cell.init(
self.buffer.box_chars.top,
self.border_fg,
self.bg,
);
var bottom = Cell.init(
self.buffer.box_chars.bottom,
self.border_fg,
self.bg,
);
left_up.put(self.left_pos.x - 1, self.left_pos.y - 1) catch {};
right_up.put(self.right_pos.x, self.left_pos.y - 1) catch {};
left_down.put(self.left_pos.x - 1, self.right_pos.y) catch {};
right_down.put(self.right_pos.x, self.right_pos.y) catch {};
for (0..self.width) |i| {
top.put(self.left_pos.x + i, self.left_pos.y - 1) catch {};
bottom.put(self.left_pos.x + i, self.right_pos.y) catch {};
}
top.ch = self.buffer.box_chars.left;
bottom.ch = self.buffer.box_chars.right;
for (0..self.height) |i| {
top.put(self.left_pos.x - 1, self.left_pos.y + i) catch {};
bottom.put(self.right_pos.x, self.left_pos.y + i) catch {};
}
}
if (self.blank_box) {
for (0..self.height) |y| {
for (0..self.width) |x| {
self.buffer.blank_cell.put(self.left_pos.x + x, self.left_pos.y + y) catch {};
}
}
}
if (self.top_title) |title| {
TerminalBuffer.drawConfinedText(
title,
self.left_pos.x,
self.left_pos.y - 1,
self.width,
self.title_fg,
self.bg,
) catch {};
}
if (self.bottom_title) |title| {
TerminalBuffer.drawConfinedText(
title,
self.left_pos.x,
self.left_pos.y + self.height,
self.width,
self.title_fg,
self.bg,
) catch {};
}
}
fn update(self: *Box, ctx: *anyopaque) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, ctx },
);
}
}

View File

@ -0,0 +1,153 @@
const Label = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const Cell = @import("../Cell.zig");
const Position = @import("../Position.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Widget = @import("../Widget.zig");
instance: ?Widget,
allocator: ?Allocator,
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
update_fn: ?*const fn (*Label, *anyopaque) anyerror!void,
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
component_pos: Position,
children_pos: Position,
pub fn init(
text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
update_fn: ?*const fn (*Label, *anyopaque) anyerror!void,
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
) Label {
return .{
.instance = null,
.allocator = null,
.text = text,
.max_width = max_width,
.fg = fg,
.bg = bg,
.update_fn = update_fn,
.calculate_timeout_fn = calculate_timeout_fn,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
};
}
pub fn deinit(self: *Label) void {
if (self.allocator) |allocator| allocator.free(self.text);
}
pub fn widget(self: *Label) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Label",
null,
self,
deinit,
null,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
}
pub fn setTextAlloc(
self: *Label,
allocator: Allocator,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.allocPrint(allocator, fmt, args);
self.allocator = allocator;
}
pub fn setTextBuf(
self: *Label,
buffer: []u8,
comptime fmt: []const u8,
args: anytype,
) !void {
self.text = try std.fmt.bufPrint(buffer, fmt, args);
self.allocator = null;
}
pub fn setText(self: *Label, text: []const u8) void {
self.text = text;
self.allocator = null;
}
pub fn positionX(self: *Label, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text));
}
pub fn positionY(self: *Label, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(1);
}
pub fn positionXY(self: *Label, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
TerminalBuffer.strWidth(self.text),
1,
).add(original_pos);
}
pub fn childrenPosition(self: Label) Position {
return self.children_pos;
}
fn draw(self: *Label) void {
if (self.max_width) |width| {
TerminalBuffer.drawConfinedText(
self.text,
self.component_pos.x,
self.component_pos.y,
width,
self.fg,
self.bg,
) catch {};
return;
}
TerminalBuffer.drawText(
self.text,
self.component_pos.x,
self.component_pos.y,
self.fg,
self.bg,
) catch {};
}
fn update(self: *Label, ctx: *anyopaque) !void {
if (self.update_fn) |update_fn| {
return @call(
.auto,
update_fn,
.{ self, ctx },
);
}
}
fn calculateTimeout(self: *Label, ctx: *anyopaque) !?usize {
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
return @call(
.auto,
calculate_timeout_fn,
.{ self, ctx },
);
}
return null;
}

View File

@ -0,0 +1,246 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Position = @import("../Position.zig");
const Widget = @import("../Widget.zig");
const DynamicString = std.ArrayListUnmanaged(u8);
const Text = @This();
instance: ?Widget,
allocator: Allocator,
buffer: *TerminalBuffer,
text: DynamicString,
end: usize,
cursor: usize,
visible_start: usize,
width: usize,
component_pos: Position,
children_pos: Position,
should_insert: bool,
masked: bool,
maybe_mask: ?u32,
fg: u32,
bg: u32,
keybinds: TerminalBuffer.KeybindMap,
pub fn init(
allocator: Allocator,
io: std.Io,
buffer: *TerminalBuffer,
should_insert: bool,
masked: bool,
maybe_mask: ?u32,
width: usize,
fg: u32,
bg: u32,
) !*Text {
var self = try allocator.create(Text);
self.* = Text{
.instance = null,
.allocator = allocator,
.buffer = buffer,
.text = .empty,
.end = 0,
.cursor = 0,
.visible_start = 0,
.width = width,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
.should_insert = should_insert,
.masked = masked,
.maybe_mask = maybe_mask,
.fg = fg,
.bg = bg,
.keybinds = .init(allocator),
};
try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self);
try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self);
try buffer.registerKeybind(io, &self.keybinds, "Delete", &delete, self);
try buffer.registerKeybind(io, &self.keybinds, "Backspace", &backspace, self);
try buffer.registerKeybind(io, &self.keybinds, "Ctrl+U", &clearTextEntry, self);
return self;
}
pub fn deinit(self: *Text) void {
self.text.deinit(self.allocator);
self.keybinds.deinit();
self.allocator.destroy(self);
}
pub fn widget(self: *Text) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Text",
self.keybinds,
self,
deinit,
null,
draw,
null,
handle,
null,
);
return &self.instance.?;
}
pub fn positionX(self: *Text, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addX(self.width);
}
pub fn positionY(self: *Text, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = original_pos.addY(1);
}
pub fn positionXY(self: *Text, original_pos: Position) void {
self.component_pos = original_pos;
self.children_pos = Position.init(
self.width,
1,
).add(original_pos);
}
pub fn childrenPosition(self: Text) Position {
return self.children_pos;
}
pub fn clear(self: *Text) void {
self.text.clearRetainingCapacity();
self.end = 0;
self.cursor = 0;
self.visible_start = 0;
}
pub fn toggleMask(self: *Text) void {
self.masked = !self.masked;
}
pub fn handle(self: *Text, maybe_key: ?keyboard.Key) !void {
if (maybe_key) |key| {
if (self.should_insert) {
const maybe_character = key.getEnabledPrintableAscii();
if (maybe_character) |character| try self.write(character);
}
}
if (self.masked and self.maybe_mask == null) {
try TerminalBuffer.setCursor(
self.component_pos.x,
self.component_pos.y,
);
return;
}
try TerminalBuffer.setCursor(
self.component_pos.x + (self.cursor - self.visible_start),
self.component_pos.y,
);
}
fn draw(self: *Text) void {
if (self.masked) {
if (self.maybe_mask) |mask| {
if (self.width < 1) return;
const length = @min(TerminalBuffer.strWidth(self.text.items), self.width - 1);
if (length == 0) return;
TerminalBuffer.drawCharMultiple(
mask,
self.component_pos.x,
self.component_pos.y,
length,
self.fg,
self.bg,
) catch {};
}
return;
}
const str_length = TerminalBuffer.strWidth(self.text.items);
const length = @min(str_length, self.width);
if (length == 0) return;
const visible_slice = vs: {
if (str_length > self.width and self.cursor < str_length) {
break :vs self.text.items[self.visible_start..(self.width + self.visible_start)];
} else {
break :vs self.text.items[self.visible_start..];
}
};
TerminalBuffer.drawText(
visible_slice,
self.component_pos.x,
self.component_pos.y,
self.fg,
self.bg,
) catch {};
}
fn goLeft(ptr: *anyopaque) !bool {
var self: *Text = @ptrCast(@alignCast(ptr));
if (self.cursor == 0) return false;
if (self.visible_start > 0) self.visible_start -= 1;
self.cursor -= 1;
return false;
}
fn goRight(ptr: *anyopaque) !bool {
var self: *Text = @ptrCast(@alignCast(ptr));
if (self.cursor >= self.end) return false;
if (self.cursor - self.visible_start == self.width - 1) self.visible_start += 1;
self.cursor += 1;
return false;
}
fn delete(ptr: *anyopaque) !bool {
var self: *Text = @ptrCast(@alignCast(ptr));
if (self.cursor >= self.end or !self.should_insert) return false;
_ = self.text.orderedRemove(self.cursor);
self.end -= 1;
return false;
}
fn backspace(ptr: *anyopaque) !bool {
const self: *Text = @ptrCast(@alignCast(ptr));
if (self.cursor == 0 or !self.should_insert) return false;
_ = try goLeft(ptr);
_ = try delete(ptr);
return false;
}
fn write(self: *Text, char: u8) !void {
if (char == 0) return;
try self.text.insert(self.allocator, self.cursor, char);
self.end += 1;
_ = try goRight(self);
}
fn clearTextEntry(ptr: *anyopaque) !bool {
var self: *Text = @ptrCast(@alignCast(ptr));
if (!self.should_insert) return false;
self.clear();
self.buffer.drawNextFrame(true);
return false;
}

View File

@ -1,8 +1,7 @@
const Lang = @import("Lang.zig"); const BigLabel = @import("../BigLabel.zig");
const LocaleChars = BigLabel.LocaleChars;
const LocaleChars = Lang.LocaleChars; const X = BigLabel.X;
const X = Lang.X; const O = BigLabel.O;
const O = Lang.O;
// zig fmt: off // zig fmt: off
pub const locale_chars = LocaleChars{ pub const locale_chars = LocaleChars{

View File

@ -1,8 +1,7 @@
const Lang = @import("Lang.zig"); const BigLabel = @import("../BigLabel.zig");
const LocaleChars = BigLabel.LocaleChars;
const LocaleChars = Lang.LocaleChars; const X = BigLabel.X;
const X = Lang.X; const O = BigLabel.O;
const O = Lang.O;
// zig fmt: off // zig fmt: off
pub const locale_chars = LocaleChars{ pub const locale_chars = LocaleChars{

View File

@ -0,0 +1,172 @@
const std = @import("std");
const Cell = @import("../Cell.zig");
const keyboard = @import("../keyboard.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Position = @import("../Position.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, usize) void;
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
const Self = @This();
allocator: Allocator,
buffer: *TerminalBuffer,
list: ItemList,
current: usize,
width: usize,
component_pos: Position,
children_pos: Position,
text_in_center: bool,
fg: u32,
bg: u32,
cursor: usize,
draw_item_fn: DrawItemFn,
change_item_fn: ?ChangeItemFn,
change_item_arg: ?ChangeItemType,
keybinds: TerminalBuffer.KeybindMap,
pub fn init(
allocator: Allocator,
io: std.Io,
buffer: *TerminalBuffer,
draw_item_fn: DrawItemFn,
change_item_fn: ?ChangeItemFn,
change_item_arg: ?ChangeItemType,
width: usize,
text_in_center: bool,
fg: u32,
bg: u32,
) !*Self {
var self = try allocator.create(Self);
self.* = .{
.allocator = allocator,
.buffer = buffer,
.list = .empty,
.current = 0,
.width = width,
.component_pos = TerminalBuffer.START_POSITION,
.children_pos = TerminalBuffer.START_POSITION,
.text_in_center = text_in_center,
.fg = fg,
.bg = bg,
.cursor = 0,
.draw_item_fn = draw_item_fn,
.change_item_fn = change_item_fn,
.change_item_arg = change_item_arg,
.keybinds = .init(allocator),
};
try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self);
try buffer.registerKeybind(io, &self.keybinds, "Ctrl+H", &goLeft, self);
try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self);
try buffer.registerKeybind(io, &self.keybinds, "Ctrl+L", &goRight, self);
return self;
}
pub fn deinit(self: *Self) void {
self.list.deinit(self.allocator);
self.keybinds.deinit();
self.allocator.destroy(self);
}
pub fn positionX(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.cursor = self.component_pos.x + 2;
self.children_pos = original_pos.addX(self.width);
}
pub fn positionY(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.cursor = self.component_pos.x + 2;
self.children_pos = original_pos.addY(1);
}
pub fn positionXY(self: *Self, original_pos: Position) void {
self.component_pos = original_pos;
self.cursor = self.component_pos.x + 2;
self.children_pos = Position.init(
self.width,
1,
).add(original_pos);
}
pub fn childrenPosition(self: Self) Position {
return self.children_pos;
}
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, _: ?keyboard.Key) !void {
try TerminalBuffer.setCursor(
self.component_pos.x + self.cursor + 2,
self.component_pos.y,
);
}
pub fn draw(self: *Self) void {
if (self.list.items.len == 0) return;
if (self.width < 2) return;
var left_arrow = Cell.init('<', self.fg, self.bg);
var right_arrow = Cell.init('>', self.fg, self.bg);
left_arrow.put(self.component_pos.x, self.component_pos.y) catch {};
right_arrow.put(
self.component_pos.x + self.width - 1,
self.component_pos.y,
) catch {};
const current_item = self.list.items[self.current];
const x = self.component_pos.x + 2;
const y = self.component_pos.y;
const width = self.width - 2;
@call(
.auto,
self.draw_item_fn,
.{ self, current_item, x, y, width },
);
}
fn goLeft(ptr: *anyopaque) !bool {
var self: *Self = @ptrCast(@alignCast(ptr));
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 },
);
}
return false;
}
fn goRight(ptr: *anyopaque) !bool {
var self: *Self = @ptrCast(@alignCast(ptr));
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 },
);
}
return false;
}
};
}

711
ly-ui/src/keyboard.zig Normal file
View File

@ -0,0 +1,711 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const KeyList = std.ArrayList(Key);
const TerminalBuffer = @import("TerminalBuffer.zig");
const termbox = TerminalBuffer.termbox;
pub const Key = packed struct {
ctrl: bool,
shift: bool,
alt: bool,
f1: bool,
f2: bool,
f3: bool,
f4: bool,
f5: bool,
f6: bool,
f7: bool,
f8: bool,
f9: bool,
f10: bool,
f11: bool,
f12: bool,
insert: bool,
delete: bool,
home: bool,
end: bool,
pageup: bool,
pagedown: bool,
up: bool,
down: bool,
left: bool,
right: bool,
tab: bool,
backspace: bool,
enter: bool,
@" ": bool,
@"!": bool,
@"`": bool,
esc: bool,
@"[": bool,
@"\\": bool,
@"]": bool,
@"/": bool,
_: bool,
@"'": bool,
@"\"": bool,
@",": bool,
@"-": bool,
@".": bool,
@"#": bool,
@"$": bool,
@"%": bool,
@"&": bool,
@"*": bool,
@"(": bool,
@")": bool,
@"+": bool,
@"=": bool,
@":": bool,
@";": bool,
@"<": bool,
@">": bool,
@"?": bool,
@"@": bool,
@"^": bool,
@"~": bool,
@"{": bool,
@"}": bool,
@"|": bool,
@"0": bool,
@"1": bool,
@"2": bool,
@"3": bool,
@"4": bool,
@"5": bool,
@"6": bool,
@"7": bool,
@"8": bool,
@"9": bool,
a: bool,
b: bool,
c: bool,
d: bool,
e: bool,
f: bool,
g: bool,
h: bool,
i: bool,
j: bool,
k: bool,
l: bool,
m: bool,
n: bool,
o: bool,
p: bool,
q: bool,
r: bool,
s: bool,
t: bool,
u: bool,
v: bool,
w: bool,
x: bool,
y: bool,
z: bool,
pub fn getEnabledPrintableAscii(self: Key) ?u8 {
if (self.ctrl or self.alt) return null;
inline for (std.meta.fields(Key)) |field| {
if (field.name.len == 1 and std.ascii.isPrint(field.name[0]) and @field(self, field.name)) {
if (self.shift) {
if (!std.ascii.isAlphanumeric(field.name[0])) return null;
return std.ascii.toUpper(field.name[0]);
}
return field.name[0];
}
}
return null;
}
};
pub fn getKeyList(allocator: Allocator, tb_event: termbox.tb_event) !KeyList {
var keys: KeyList = .empty;
var key = std.mem.zeroes(Key);
if (tb_event.mod & termbox.TB_MOD_CTRL != 0) key.ctrl = true;
if (tb_event.mod & termbox.TB_MOD_SHIFT != 0) key.shift = true;
if (tb_event.mod & termbox.TB_MOD_ALT != 0) key.alt = true;
if (tb_event.key == termbox.TB_KEY_BACK_TAB) {
key.shift = true;
key.tab = true;
} else if (tb_event.key > termbox.TB_KEY_BACK_TAB) {
const code = 0xFFFF - tb_event.key;
switch (code) {
0 => key.f1 = true,
1 => key.f2 = true,
2 => key.f3 = true,
3 => key.f4 = true,
4 => key.f5 = true,
5 => key.f6 = true,
6 => key.f7 = true,
7 => key.f8 = true,
8 => key.f9 = true,
9 => key.f10 = true,
10 => key.f11 = true,
11 => key.f12 = true,
12 => key.insert = true,
13 => key.delete = true,
14 => key.home = true,
15 => key.end = true,
16 => key.pageup = true,
17 => key.pagedown = true,
18 => key.up = true,
19 => key.down = true,
20 => key.left = true,
21 => key.right = true,
else => {},
}
} else if (tb_event.ch < 128) {
const code = if (tb_event.ch == 0 and tb_event.key < 128) tb_event.key else tb_event.ch;
switch (code) {
// Non-standard control codes
0 => {
key.ctrl = true;
key.@"2" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"`" = true;
},
1 => {
key.ctrl = true;
key.a = true;
},
2 => {
key.ctrl = true;
key.b = true;
},
3 => {
key.ctrl = true;
key.c = true;
},
4 => {
key.ctrl = true;
key.d = true;
},
5 => {
key.ctrl = true;
key.e = true;
},
6 => {
key.ctrl = true;
key.f = true;
},
7 => {
key.ctrl = true;
key.g = true;
},
8 => {
key.ctrl = true;
key.h = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.backspace = true;
},
9 => {
key.ctrl = true;
key.i = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.tab = true;
},
10 => {
key.ctrl = true;
key.j = true;
},
11 => {
key.ctrl = true;
key.k = true;
},
12 => {
key.ctrl = true;
key.l = true;
},
13 => {
key.ctrl = true;
key.m = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.enter = true;
},
14 => {
key.ctrl = true;
key.n = true;
},
15 => {
key.ctrl = true;
key.o = true;
},
16 => {
key.ctrl = true;
key.p = true;
},
17 => {
key.ctrl = true;
key.q = true;
},
18 => {
key.ctrl = true;
key.r = true;
},
19 => {
key.ctrl = true;
key.s = true;
},
20 => {
key.ctrl = true;
key.t = true;
},
21 => {
key.ctrl = true;
key.u = true;
},
22 => {
key.ctrl = true;
key.v = true;
},
23 => {
key.ctrl = true;
key.w = true;
},
24 => {
key.ctrl = true;
key.x = true;
},
25 => {
key.ctrl = true;
key.y = true;
},
26 => {
key.ctrl = true;
key.z = true;
},
27 => {
key.ctrl = true;
key.@"3" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.esc = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"[" = true;
},
28 => {
key.ctrl = true;
key.@"4" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"\\" = true;
},
29 => {
key.ctrl = true;
key.@"5" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"]" = true;
},
30 => {
key.ctrl = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"6" = true;
},
31 => {
key.ctrl = true;
key.@"7" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key.@"/" = true;
try keys.append(allocator, key);
key = std.mem.zeroes(Key);
key._ = true;
},
// Standard ASCII characters
32 => {
key = std.mem.zeroes(Key);
key.@" " = true;
},
33 => {
key = std.mem.zeroes(Key);
key.@"!" = true;
},
34 => {
key = std.mem.zeroes(Key);
key.@"\"" = true;
},
35 => {
key = std.mem.zeroes(Key);
key.@"#" = true;
},
36 => {
key = std.mem.zeroes(Key);
key.@"$" = true;
},
37 => {
key = std.mem.zeroes(Key);
key.@"%" = true;
},
38 => {
key = std.mem.zeroes(Key);
key.@"&" = true;
},
39 => {
key = std.mem.zeroes(Key);
key.@"'" = true;
},
40 => {
key = std.mem.zeroes(Key);
key.@"(" = true;
},
41 => {
key = std.mem.zeroes(Key);
key.@")" = true;
},
42 => {
key = std.mem.zeroes(Key);
key.@"*" = true;
},
43 => {
key = std.mem.zeroes(Key);
key.@"+" = true;
},
44 => {
key = std.mem.zeroes(Key);
key.@"," = true;
},
45 => {
key = std.mem.zeroes(Key);
key.@"-" = true;
},
46 => {
key = std.mem.zeroes(Key);
key.@"." = true;
},
47 => {
key = std.mem.zeroes(Key);
key.@"/" = true;
},
48 => {
key = std.mem.zeroes(Key);
key.@"0" = true;
},
49 => {
key = std.mem.zeroes(Key);
key.@"1" = true;
},
50 => {
key = std.mem.zeroes(Key);
key.@"2" = true;
},
51 => {
key = std.mem.zeroes(Key);
key.@"3" = true;
},
52 => {
key = std.mem.zeroes(Key);
key.@"4" = true;
},
53 => {
key = std.mem.zeroes(Key);
key.@"5" = true;
},
54 => {
key = std.mem.zeroes(Key);
key.@"6" = true;
},
55 => {
key = std.mem.zeroes(Key);
key.@"7" = true;
},
56 => {
key = std.mem.zeroes(Key);
key.@"8" = true;
},
57 => {
key = std.mem.zeroes(Key);
key.@"9" = true;
},
58 => {
key = std.mem.zeroes(Key);
key.@":" = true;
},
59 => {
key = std.mem.zeroes(Key);
key.@";" = true;
},
60 => {
key = std.mem.zeroes(Key);
key.@"<" = true;
},
61 => {
key = std.mem.zeroes(Key);
key.@"=" = true;
},
62 => {
key = std.mem.zeroes(Key);
key.@">" = true;
},
63 => {
key = std.mem.zeroes(Key);
key.@"?" = true;
},
64 => {
key = std.mem.zeroes(Key);
key.@"@" = true;
},
65 => {
key.shift = true;
key.a = true;
},
66 => {
key.shift = true;
key.b = true;
},
67 => {
key.shift = true;
key.c = true;
},
68 => {
key.shift = true;
key.d = true;
},
69 => {
key.shift = true;
key.e = true;
},
70 => {
key.shift = true;
key.f = true;
},
71 => {
key.shift = true;
key.g = true;
},
72 => {
key.shift = true;
key.h = true;
},
73 => {
key.shift = true;
key.i = true;
},
74 => {
key.shift = true;
key.j = true;
},
75 => {
key.shift = true;
key.k = true;
},
76 => {
key.shift = true;
key.l = true;
},
77 => {
key.shift = true;
key.m = true;
},
78 => {
key.shift = true;
key.n = true;
},
79 => {
key.shift = true;
key.o = true;
},
80 => {
key.shift = true;
key.p = true;
},
81 => {
key.shift = true;
key.q = true;
},
82 => {
key.shift = true;
key.r = true;
},
83 => {
key.shift = true;
key.s = true;
},
84 => {
key.shift = true;
key.t = true;
},
85 => {
key.shift = true;
key.u = true;
},
86 => {
key.shift = true;
key.v = true;
},
87 => {
key.shift = true;
key.w = true;
},
88 => {
key.shift = true;
key.x = true;
},
89 => {
key.shift = true;
key.y = true;
},
90 => {
key.shift = true;
key.z = true;
},
91 => {
key = std.mem.zeroes(Key);
key.@"[" = true;
},
92 => {
key = std.mem.zeroes(Key);
key.@"\\" = true;
},
93 => {
key = std.mem.zeroes(Key);
key.@"]" = true;
},
94 => {
key = std.mem.zeroes(Key);
key.@"^" = true;
},
95 => {
key = std.mem.zeroes(Key);
key._ = true;
},
96 => {
key = std.mem.zeroes(Key);
key.@"`" = true;
},
97 => {
key.a = true;
},
98 => {
key.b = true;
},
99 => {
key.c = true;
},
100 => {
key.d = true;
},
101 => {
key.e = true;
},
102 => {
key.f = true;
},
103 => {
key.g = true;
},
104 => {
key.h = true;
},
105 => {
key.i = true;
},
106 => {
key.j = true;
},
107 => {
key.k = true;
},
108 => {
key.l = true;
},
109 => {
key.m = true;
},
110 => {
key.n = true;
},
111 => {
key.o = true;
},
112 => {
key.p = true;
},
113 => {
key.q = true;
},
114 => {
key.r = true;
},
115 => {
key.s = true;
},
116 => {
key.t = true;
},
117 => {
key.u = true;
},
118 => {
key.v = true;
},
119 => {
key.w = true;
},
120 => {
key.x = true;
},
121 => {
key.y = true;
},
122 => {
key.z = true;
},
123 => {
key.@"{" = true;
},
124 => {
key = std.mem.zeroes(Key);
key.@"|" = true;
},
125 => {
key = std.mem.zeroes(Key);
key.@"}" = true;
},
126 => {
key = std.mem.zeroes(Key);
key.@"~" = true;
},
127 => {
key = std.mem.zeroes(Key);
key.backspace = true;
},
else => {},
}
}
try keys.append(allocator, key);
return keys;
}

13
ly-ui/src/root.zig Normal file
View File

@ -0,0 +1,13 @@
pub const ly_core = @import("ly-core");
pub const Cell = @import("Cell.zig");
pub const keyboard = @import("keyboard.zig");
pub const Position = @import("Position.zig");
pub const TerminalBuffer = @import("TerminalBuffer.zig");
pub const Widget = @import("Widget.zig");
pub const BigLabel = @import("components/BigLabel.zig");
pub const Box = @import("components/Box.zig");
pub const CyclableLabel = @import("components/generic.zig").CyclableLabel;
pub const Label = @import("components/Label.zig");
pub const Text = @import("components/Text.zig");

165
readme.md
View File

@ -2,25 +2,33 @@
![Ly screenshot](.github/screenshot.png "Ly screenshot") ![Ly screenshot](.github/screenshot.png "Ly screenshot")
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD, _Note: the above animation can be found [here](https://codeberg.org/attachments/f336d6ac-8331-4323-91fc-0e4619803401)!_
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)! Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD, designed with portability in mind and doesn't require systemd to run.
**Note**: Development happens on [Codeberg](https://codeberg.org/fairyglade/ly) Join us on Matrix over at [#ly-dm:matrix.org](https://matrix.to/#/#ly-dm:matrix.org)!
with a mirror on [GitHub](https://github.com/fairyglade/ly).
> [!NOTE]
> Development happens on [Codeberg](https://codeberg.org/fairyglade/ly) with a mirror on [GitHub](https://github.com/fairyglade/ly).
## Dependencies ## Dependencies
- Compile-time: - Compile-time:
- zig 0.15.x - zig 0.16.x (you must use a __release version__ of zig; check that `zig version` does not have a `-dev*` suffix)
- libc - libc
- pam - pam
- xcb (optional, required by default; needed for X11 support) - xcb (optional, required by default; needed for X11 support)
- Runtime (with default config): - Runtime (with default config):
- xorg - xorg
- xorg-xauth - xorg-xauth
- shutdown - shutdown
- brightnessctl - brightnessctl
### Debian ### Debian
@ -31,8 +39,8 @@ with a mirror on [GitHub](https://github.com/fairyglade/ly).
### Fedora ### Fedora
**Warning**: You may encounter issues with SELinux on Fedora. > [!WARNING]
It is recommended to add a rule for Ly as it currently does not ship one. > 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 zig xorg-x11-xauth xorg-x11-server brightnessctl # dnf install kernel-devel pam-devel libxcb-devel zig xorg-x11-xauth xorg-x11-server brightnessctl
@ -50,16 +58,22 @@ It is recommended to add a rule for Ly as it currently does not ship one.
## Support ## Support
Ly has been tested with a wide variety of desktop environments and window Every environment that works on other login managers also should work on Ly.
managers, all of which you can find in the sections below:
[Wayland environments](#supported-wayland-environments) - Unlike most login managers Ly has an xinitrc and shell entry.
[X11 environments](#supported-x11-environments) - If you installed your favorite environment and you don't see it, that's because Ly doesn't automatically refresh itself. To fix this you should restart Ly service (depends on your init system) or the easy way is to reboot your system.
- If your environment is still missing then check at `/usr/share/xsessions` or `/usr/share/wayland-sessions` to see if a .desktop file is present.
- If there isn't a .desktop file then create a new one at `/etc/ly/custom-sessions` that launches your favorite environment. These .desktop files can be only seen by Ly and if you want them system-wide you also can create at those directories instead.
- If Xorg sessions don't work then check if your distro compiles Ly with Xorg.
Logs are defined by `/etc/ly/config.ini`: Logs are defined by `/etc/ly/config.ini`:
- The session log is located at `~/.local/state/ly-session.log` by default. - 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. - The system log is located at `/var/log/ly.log` by default.
## Manually building ## Manually building
@ -72,23 +86,22 @@ $ cd ly
$ zig build $ zig build
``` ```
After building, you can (optionally) test Ly in a terminal emulator, although After building, you can (optionally) test Ly in a terminal emulator, although authentication will **not** work:
authentication will **not** work:
``` ```
$ zig build run $ zig build run
``` ```
**Important**: While you can also run Ly in a terminal emulator as root, it is > [!IMPORTANT]
**not** recommended either. If you want to properly test Ly, please enable its > While you can run Ly in a terminal emulator as root, it is **not** recommended. If you want to test Ly, please enable its service (as described below) and reboot your machine.
service (as described below) and reboot your machine.
The following sections show how to install Ly for a particular init system. > [!NOTE]
Because the procedure is very similar for all of them, the commands will only > You can, however, test your configuration file changes like that. Note that you must do Ctrl+C in order to exit Ly.
be detailed for the first section (which is about systemd).
**Note**: All following sections will assume you are using LightDM for The next sections will explain how to use Ly with a variety of init systems. Detailed explanation is only given for systemd, but should be applicable for all.
convenience sake.
> [!NOTE]
> All following sections will assume you are using LightDM for convenience sake.
### systemd ### systemd
@ -98,11 +111,10 @@ Now, you can install Ly on your system:
# zig build installexe -Dinit_system=systemd # zig build installexe -Dinit_system=systemd
``` ```
**Note**: The `init_system` parameter is optional and defaults to `systemd`. > [!NOTE]
> The `init_system` parameter is optional and defaults to `systemd`.
Note that you also need to disable your current display manager. For example, Note that you also need to disable your current display manager. For example, if you are using LightDM, you can execute the following command:
if LightDM is the current display manager, you can execute the following
command:
``` ```
# systemctl disable lightdm.service # systemctl disable lightdm.service
@ -114,16 +126,20 @@ Then, similarly to the previous command, you need to enable the Ly service:
# systemctl enable ly@tty2.service # systemctl enable ly@tty2.service
``` ```
**Important**: Because Ly runs in a TTY, you **must** disable the TTY service > [!IMPORTANT]
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: > 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 # systemctl disable getty@tty2.service
``` ```
You can change the TTY Ly will run on by editing the corresponding 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).
service file for your platform, or on systemd, by enabling the service on
different TTYs, as is done above. 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 ### OpenRC
@ -134,8 +150,8 @@ different TTYs, as is done above.
# rc-update del agetty.tty2 # rc-update del agetty.tty2
``` ```
**Note**: On Gentoo specifically, you also **must** comment out the appropriate > [!NOTE]
line for the TTY in /etc/inittab. > On Gentoo specifically, you also **must** comment out the appropriate line for the TTY in /etc/inittab.
### runit ### runit
@ -166,8 +182,7 @@ To disable TTY 2, edit `/etc/s6/config/tty2.conf` and set `SPAWN="no"`.
# dinitctl enable ly # dinitctl enable ly
``` ```
To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify `ACTIVE_CONSOLES`.
`ACTIVE_CONSOLES`.
### sysvinit ### sysvinit
@ -194,8 +209,7 @@ Ly:\
:al=root: :al=root:
``` ```
Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys` Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys` (TTYs in FreeBSD start at 0):
(TTYs in FreeBSD start at 0):
``` ```
ttyv1 "/usr/libexec/getty Ly" xterm on secure ttyv1 "/usr/libexec/getty Ly" xterm on secure
@ -203,37 +217,33 @@ ttyv1 "/usr/libexec/getty Ly" xterm on secure
### Updating ### Updating
You can also install Ly without overrding the current configuration file. This You can also install Ly without overrding the current configuration file. This is called **updating**. To update, simply run:
is called **updating**. To update, simply run:
``` ```
# zig build installnoconf # zig build installnoconf
``` ```
You can, of course, still select the init system of your choice when using this You can, of course, still select the init system of your choice when using this command.
command.
## Configuration ## Configuration
You can find all the configuration in `/etc/ly/config.ini`. The file is fully You can find all the configuration in `/etc/ly/config.ini`. The file is fully commented, and includes the default values.
commented, and includes the default values.
You may also check the validity of your configuration file (i.e. if there are any errors in it) with the following command:
```
$ ly --validate-config /etc/ly/config.ini
```
## Controls ## Controls
Use the Up/Down arrow keys to change the current field, and the Left/Right 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.
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.
## A note on .xinitrc ## A note on .xinitrc
If your `.xinitrc` file doesn't work ,make sure it is executable and includes a 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:
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 > 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.
> 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: A typical shebang for a shell script looks like this:
@ -244,47 +254,18 @@ A typical shebang for a shell script looks like this:
## Tips ## Tips
- The numlock and capslock state is printed in the top-right corner. - The numlock and capslock state is printed in the top-right corner.
- Use the F1 and F2 keys to respectively shutdown and reboot. - 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).
## Supported Wayland environments - 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).
- 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 ## A final note
The name "Ly" is a tribute to the fairy from the game Rayman. Ly was tested by The name "Ly" is a tribute to the fairy from the game Rayman. Ly was tested by oxodao, who is some seriously awesome dude.
oxodao, who is some seriously awesome dude.
Also, Ly wouldn't be there today without [ashametrine](https://github.com/ashametrine), who has done significant contributions to the project for the Zig rewrite, which lead to the release of Ly v1.0.0. Massive thanks, and sorry for not crediting you enough beforehand!
### Donate
If you like Ly and wish to support my work further, feel free to donate via my
[Liberapay link](https://liberapay.com/ShiningLea)!

View File

@ -27,6 +27,9 @@ allow_empty_password = true
# dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master) # dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master)
animation = none animation = none
# Delay between each animation frame in milliseconds
animation_frame_delay = 5
# Stop the animation after some time # Stop the animation after some time
# 0 -> Run forever # 0 -> Run forever
# 1..2e12 -> Stop the animation after this many seconds # 1..2e12 -> Stop the animation after this many seconds
@ -62,7 +65,7 @@ auto_login_service = ly-autologin
# To find available session names, check the .desktop files in: # To find available session names, check the .desktop files in:
# - /usr/share/xsessions/ (for X11 sessions) # - /usr/share/xsessions/ (for X11 sessions)
# - /usr/share/wayland-sessions/ (for Wayland sessions) # - /usr/share/wayland-sessions/ (for Wayland sessions)
# Use the filename without .desktop extension, or the value of DesktopNames field # Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field
# Examples: "i3", "sway", "gnome", "plasma", "xfce" # Examples: "i3", "sway", "gnome", "plasma", "xfce"
# If null, automatic login is disabled # If null, automatic login is disabled
auto_login_session = null auto_login_session = null
@ -94,6 +97,14 @@ blank_box = true
# Border foreground color id # Border foreground color id
border_fg = 0x00FFFFFF border_fg = 0x00FFFFFF
# Relative horizontal position from the end of the screen
# default: 0.5
box_position_h = 0.5
# Relative vertical position from the bottom of the screen
# default: 0.4
box_position_v = 0.4
# Title to show at the top of the main box # Title to show at the top of the main box
# If set to null, none will be shown # If set to null, none will be shown
box_title = null box_title = null
@ -101,13 +112,13 @@ box_title = null
# Brightness decrease command # Brightness decrease command
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%- brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%-
# Brightness decrease key, or null to disable # Brightness decrease key combination, or null to disable
brightness_down_key = F5 brightness_down_key = F5
# Brightness increase command # Brightness increase command
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10% brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%
# Brightness increase key, or null to disable # Brightness increase key combination, or null to disable
brightness_up_key = F6 brightness_up_key = F6
# Erase password input on failure # Erase password input on failure
@ -140,6 +151,11 @@ colormix_col2 = 0x000000FF
# Color mixing animation third color id # Color mixing animation third color id
colormix_col3 = 0x20000000 colormix_col3 = 0x20000000
# For custom binds: the horizontal limit in characters for each
# line of custom binds before moving on to the next.
# If null, defaults to the width of the terminal instead.
custom_bind_width = null
# Custom sessions directory # Custom sessions directory
# You can specify multiple directories, # You can specify multiple directories,
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions # e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
@ -167,10 +183,15 @@ doom_bottom_color = 0x00FFFFFF
# Dur file path # Dur file path
dur_file_path = $CONFIG_DIRECTORY/ly/example.dur dur_file_path = $CONFIG_DIRECTORY/ly/example.dur
# Dur offset x direction # Dur file alignment
# The dur file can be aligned with a direction and centered easily with the flags below
# Available inputs: topleft, topcenter, topright, centerleft, center, centerright, bottomleft, bottomcenter, bottomright
dur_offset_alignment = center
# Dur offset x direction (value is added to the current position determined by alignment, negatives are supported)
dur_x_offset = 0 dur_x_offset = 0
# Dur offset y direction # Dur offset y direction (value is added to the current position determined by alignment, negatives are supported)
dur_y_offset = 0 dur_y_offset = 0
# Set margin to the edges of the DM (useful for curved monitors) # Set margin to the edges of the DM (useful for curved monitors)
@ -228,7 +249,7 @@ gameoflife_initial_density = 0.4
# Command executed when pressing hibernate key (can be null) # Command executed when pressing hibernate key (can be null)
hibernate_cmd = null hibernate_cmd = null
# Specifies the key used for hibernate (F1-F12) # Specifies the key combination used for hibernate
hibernate_key = F4 hibernate_key = F4
# Remove main box borders # Remove main box borders
@ -278,6 +299,7 @@ login_defs_path = /etc/login.defs
logout_cmd = null logout_cmd = null
# General log file path # General log file path
# If null, syslog will be used instead
ly_log = /var/log/ly.log ly_log = /var/log/ly.log
# Main box horizontal margin # Main box horizontal margin
@ -286,9 +308,6 @@ margin_box_h = 2
# Main box vertical margin # Main box vertical margin
margin_box_v = 1 margin_box_v = 1
# Event timeout in milliseconds
min_refresh_delta = 5
# Set numlock on/off at startup # Set numlock on/off at startup
numlock = false numlock = false
@ -299,7 +318,7 @@ path = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Command executed when pressing restart_key # Command executed when pressing restart_key
restart_cmd = /sbin/shutdown -r now restart_cmd = /sbin/shutdown -r now
# Specifies the key used for restart (F1-F12) # Specifies the key combination used for restart
restart_key = F2 restart_key = F2
# Save the current desktop and login as defaults, and load them on startup # Save the current desktop and login as defaults, and load them on startup
@ -311,29 +330,42 @@ service_name = ly
# Session log file path # Session log file path
# This will contain stdout and stderr of Wayland sessions # This will contain stdout and stderr of Wayland sessions
# By default it's saved in the user's home directory # By default it's saved in the user's home directory
# Important: due to technical limitations, X11 and shell sessions aren't supported, which # Important: due to technical limitations, X11, shell sessions as well as
# means you won't get any logs from those sessions. # launching session via KMSCON aren't supported, which means you won't get any
# logs from those sessions.
# If null, no session log will be created # If null, no session log will be created
session_log = .local/state/ly-session.log session_log = .local/state/ly-session.log
# Setup command # Setup command
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
# Show the shell session in the session list
# If false, the shell session will be hidden
shell = true
# Specifies the key combination used for showing the password
show_password_key = F7
# Display the active TTY number (e.g. tty3) to the right of the clock in the top right corner
# If the clock is disabled, the TTY label occupies the top right corner on its own
# If false, the TTY number will not be shown
show_tty = false
# Command executed when pressing shutdown_key # Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown $PLATFORM_SHUTDOWN_ARG now shutdown_cmd = /sbin/shutdown $PLATFORM_SHUTDOWN_ARG now
# Specifies the key used for shutdown (F1-F12) # Specifies the key combination used for shutdown
shutdown_key = F1 shutdown_key = F1
# Command executed when pressing sleep key (can be null) # Command executed when pressing sleep key (can be null)
sleep_cmd = null sleep_cmd = null
# Specifies the key used for sleep (F1-F12) # Specifies the key combination used for sleep
sleep_key = F3 sleep_key = F3
# Command executed when starting Ly (before the TTY is taken control of) # Command executed when starting Ly (before the TTY is taken control of)
# If null, no command will be executed # See file at path below for an example of changing the default TTY colors
start_cmd = null start_cmd = $CONFIG_DIRECTORY/ly/startup.sh
# Center the session name. # Center the session name.
text_in_center = false text_in_center = false
@ -349,11 +381,18 @@ vi_mode = false
# Wayland desktop environments # Wayland desktop environments
# You can specify multiple directories, # You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions # e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions
# If null, Wayland sessions will not be shown
waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
# Xorg server command # Xorg server command
# Add the -quiet argument to hide startup logs from the server
x_cmd = $PREFIX_DIRECTORY/bin/X x_cmd = $PREFIX_DIRECTORY/bin/X
# Xorg virtual terminal number
# Mostly useful for FreeBSD where choosing the current TTY causes issues
# If null, the current TTY will be chosen
x_vt = null
# Xorg xauthority edition tool # Xorg xauthority edition tool
xauth_cmd = $PREFIX_DIRECTORY/bin/xauth xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
@ -364,4 +403,29 @@ xinitrc = ~/.xinitrc
# Xorg desktop environments # Xorg desktop environments
# You can specify multiple directories, # You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions # e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
# If null, X11 sessions will not be shown
xsessions = $PREFIX_DIRECTORY/share/xsessions xsessions = $PREFIX_DIRECTORY/share/xsessions
# Custom Commands and Labels:
# The following examples below give an outline for setting up custom commands and labels.
# Unless specified as optional, an option is mandatory.
# Comments preceding with '##' are for documentation.
# Comments preceding with '#' comment out the example INI.
## Declare a command with the F8 binding.
#[cmd:F8]
## The name of the command to show up in Ly.
## Note: "$" in "$brightness_up" fetches the appropriate string from the specified locale file
## and is replaced with the value representing "brightness_up".
## You can see the list of keys in any locale file in $CONFIG_DIRECTORY/ly/lang.
#cmd = touch /tmp/ly.gaming
#name = custom command $brightness_up
## Declare a label with an ID. This ID should be unique across all labels.
#[lbl:kernel]
#cmd = uname -srn
## Optional, defaulting to 0.
## In frames, the time to re-run the command and update the label.
## If 0, only run once and do not refresh afterwards
#refresh = 0

View File

@ -17,7 +17,7 @@ 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 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. redirect standard output & error. It is optional and defaults to false.
Finally, do note that, if the Terminal value is set to true, the 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 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 be set to "unspecified" (without quotes), which is behavior that at least
systemd recognizes (see pam_systemd's man page). systemd recognizes (see pam_systemd's man page).

BIN
res/example.dur Normal file

Binary file not shown.

View File

@ -2,26 +2,29 @@ authenticating = جاري المصادقة...
brightness_down = خفض السطوع brightness_down = خفض السطوع
brightness_up = رفع السطوع brightness_up = رفع السطوع
capslock = capslock capslock = capslock
custom = مخصص
custom_info_err_output_long = الإخراج طويل جداً
custom_info_err_no_output = لا يوجد إخراج
custom_info_err_no_output_error = ، خطأ محتمل
err_alloc = فشل في تخصيص الذاكرة err_alloc = فشل في تخصيص الذاكرة
err_args = تعذر تحليل وسيطات سطر الأوامر
err_autologin_session = لم يتم العثور على جلسة تسجيل الدخول التلقائي
err_bounds = out-of-bounds index err_bounds = out-of-bounds index
err_brightness_change = فشل في تغيير سطوع الشاشة err_brightness_change = فشل في تغيير سطوع الشاشة
err_chdir = فشل في فتح مجلد المنزل err_chdir = فشل في فتح مجلد المنزل
err_clock_too_long = نص الساعة طويل جداً
err_config = فشل في تفسير ملف الإعدادات err_config = فشل في تفسير ملف الإعدادات
err_crawl = فشل الزحف في أدلة الجلسة
err_dgn_oob = رسالة سجل (Log) err_dgn_oob = رسالة سجل (Log)
err_domain = اسم نطاق غير صالح err_domain = اسم نطاق غير صالح
err_empty_password = لا يُسمح بكلمة مرور فارغة err_empty_password = لا يُسمح بكلمة مرور فارغة
err_envlist = فشل في جلب قائمة المتغيرات البيئية err_envlist = فشل في جلب قائمة المتغيرات البيئية
err_get_active_tty = فشل الحصول على tty النشط
err_hibernate = فشل تنفيذ أمر الإسبات
err_hostname = فشل في جلب اسم المضيف (Hostname) err_hostname = فشل في جلب اسم المضيف (Hostname)
err_inactivity = فشل تنفيذ أمر عدم النشاط
err_lock_state = فشل الحصول على حالة القفل
err_log = فشل فتح ملف السجل
err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock) err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock)
err_null = مؤشر فارغ (Null pointer) err_null = مؤشر فارغ (Null pointer)
err_numlock = فشل في ضبط Num Lock err_numlock = فشل في ضبط Num Lock
@ -47,12 +50,12 @@ err_perm_group = فشل في تخفيض صلاحيات المجموعة (Group p
err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions) err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions)
err_pwnam = فشل في جلب معلومات المستخدم err_pwnam = فشل في جلب معلومات المستخدم
err_sleep = فشل في تنفيذ أمر sleep err_sleep = فشل في تنفيذ أمر sleep
err_start = فشل تنفيذ أمر البدء
err_battery = فشل تحميل حالة البطارية
err_switch_tty = فشل تبديل tty
err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY) err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY)
err_no_users = لم يتم العثور على مستخدمين
err_uid_range = فشل الحصول الديناميكي على نطاق uid
err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم
err_user_init = فشل في تهيئة بيانات المستخدم err_user_init = فشل في تهيئة بيانات المستخدم
err_user_uid = فشل في تعيين معرّف المستخدم (UID) err_user_uid = فشل في تعيين معرّف المستخدم (UID)
@ -60,11 +63,11 @@ err_xauth = فشل في تنفيذ أمر xauth
err_xcb_conn = فشل في الاتصال بمكتبة XCB err_xcb_conn = فشل في الاتصال بمكتبة XCB
err_xsessions_dir = فشل في العثور على مجلد Xsessions err_xsessions_dir = فشل في العثور على مجلد Xsessions
err_xsessions_open = فشل في فتح مجلد Xsessions err_xsessions_open = فشل في فتح مجلد Xsessions
hibernate = إسبات
insert = ادخال insert = ادخال
login = تسجيل الدخول login = تسجيل الدخول
logout = تم تسجيل خروجك logout = تم تسجيل خروجك
no_x11_support = تم تعطيل دعم x11 اثناء وقت الـ compile no_x11_support = دعم x11 معطّل في وقت الترجمة
normal = عادي normal = عادي
numlock = numlock numlock = numlock
other = اخر other = اخر
@ -73,6 +76,7 @@ restart = اعادة التشغيل
shell = shell shell = shell
shutdown = ايقاف التشغيل shutdown = ايقاف التشغيل
sleep = وضع السكون sleep = وضع السكون
toggle_password = إظهار/إخفاء كلمة المرور
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -3,6 +3,9 @@ brightness_down = намаляване на яркостта
brightness_up = увеличаване на яркостта brightness_up = увеличаване на яркостта
capslock = caps lock capslock = caps lock
custom = персонализирано custom = персонализирано
custom_info_err_output_long = резултатът е твърде дълъг
custom_info_err_no_output = няма резултат
custom_info_err_no_output_error = , възможна грешка
err_alloc = неуспешно заделяне на памет err_alloc = неуспешно заделяне на памет
err_args = неуспешен анализ на аргументите от командния ред err_args = неуспешен анализ на аргументите от командния ред
err_autologin_session = сесията за автоматично влизане не е намерена err_autologin_session = сесията за автоматично влизане не е намерена
@ -73,6 +76,7 @@ restart = рестартиране
shell = обвивка shell = обвивка
shutdown = изключване shutdown = изключване
sleep = заспиване sleep = заспиване
toggle_password = превключване на паролата
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -2,26 +2,29 @@ authenticating = autenticant...
brightness_down = abaixar brillantor brightness_down = abaixar brillantor
brightness_up = apujar brillantor brightness_up = apujar brillantor
capslock = Bloq Majús capslock = Bloq Majús
custom = personalitzat
custom_info_err_output_long = sortida massa llarga
custom_info_err_no_output = sense sortida
custom_info_err_no_output_error = , possible error
err_alloc = assignació de memòria fallida err_alloc = assignació de memòria fallida
err_args = no s'han pogut analitzar els arguments de la línia d'ordres
err_autologin_session = no s'ha trobat la sessió d'inici de sessió automàtic
err_bounds = índex fora de límits err_bounds = índex fora de límits
err_brightness_change = error en canviar la brillantor err_brightness_change = error en canviar la brillantor
err_chdir = error en obrir la carpeta home err_chdir = error en obrir la carpeta home
err_clock_too_long = la cadena del rellotge és massa llarga
err_config = no s'ha pogut analitzar el fitxer de configuració
err_crawl = no s'han pogut explorar els directoris de sessió
err_dgn_oob = missatge de registre err_dgn_oob = missatge de registre
err_domain = domini invàlid err_domain = domini invàlid
err_empty_password = no es permet la contrasenya buida
err_envlist = error en obtenir l'envlist err_envlist = error en obtenir l'envlist
err_get_active_tty = no s'ha pogut obtenir el tty actiu
err_hibernate = no s'ha pogut executar l'ordre d'hibernació
err_hostname = error en obtenir el nom de l'amfitrió err_hostname = error en obtenir el nom de l'amfitrió
err_inactivity = no s'ha pogut executar l'ordre d'inactivitat
err_lock_state = no s'ha pogut obtenir l'estat de bloqueig
err_log = no s'ha pogut obrir el fitxer de registre
err_mlock = error en bloquejar la memòria de clau err_mlock = error en bloquejar la memòria de clau
err_null = punter nul err_null = punter nul
err_numlock = error en establir el Bloq num err_numlock = error en establir el Bloq num
@ -46,13 +49,13 @@ err_perm_dir = error en canviar el directori actual
err_perm_group = error en degradar els permisos de grup err_perm_group = error en degradar els permisos de grup
err_perm_user = error en degradar els permisos de l'usuari err_perm_user = error en degradar els permisos de l'usuari
err_pwnam = error en obtenir la informació de l'usuari err_pwnam = error en obtenir la informació de l'usuari
err_sleep = no s'ha pogut executar l'ordre de suspensió
err_start = no s'ha pogut executar l'ordre d'inici
err_battery = no s'ha pogut carregar l'estat de la bateria
err_switch_tty = no s'ha pogut canviar de tty
err_tty_ctrl = ha fallat la transferència del control tty
err_no_users = no s'han trobat usuaris
err_uid_range = no s'ha pogut obtenir dinàmicament el rang d'uid
err_user_gid = error en establir el GID de l'usuari err_user_gid = error en establir el GID de l'usuari
err_user_init = error en inicialitzar usuari err_user_init = error en inicialitzar usuari
err_user_uid = error en establir l'UID de l'usuari err_user_uid = error en establir l'UID de l'usuari
@ -60,19 +63,20 @@ err_xauth = error en la comanda xauth
err_xcb_conn = error en la connexió xcb err_xcb_conn = error en la connexió xcb
err_xsessions_dir = error en trobar la carpeta de sessions err_xsessions_dir = error en trobar la carpeta de sessions
err_xsessions_open = error en obrir la carpeta de sessions err_xsessions_open = error en obrir la carpeta de sessions
hibernate = hibernar
insert = inserir insert = inserir
login = iniciar sessió login = iniciar sessió
logout = sessió tancada logout = sessió tancada
no_x11_support = el suport per x11 ha estat desactivat en la compilació no_x11_support = suport x11 desactivat en temps de compilació
normal = normal normal = normal
numlock = Bloq Num numlock = Bloq Num
other = altres
password = Clau password = Clau
restart = reiniciar restart = reiniciar
shell = shell shell = shell
shutdown = aturar shutdown = aturar
sleep = suspendre sleep = suspendre
toggle_password = mostrar/amagar contrasenya
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = ověřování...
brightness_down = snížit jas
brightness_up = zvýšit jas
capslock = capslock capslock = capslock
custom = vlastní
custom_info_err_output_long = výstup je příliš dlouhý
custom_info_err_no_output = žádný výstup
custom_info_err_no_output_error = , možná chyba
err_alloc = alokace paměti selhala err_alloc = alokace paměti selhala
err_args = nelze analyzovat argumenty příkazového řádku
err_autologin_session = relace automatického přihlášení nebyla nalezena
err_bounds = index je mimo hranice pole err_bounds = index je mimo hranice pole
err_brightness_change = nepodařilo se změnit jas
err_chdir = nelze otevřít domovský adresář err_chdir = nelze otevřít domovský adresář
err_clock_too_long = řetězec hodin je příliš dlouhý
err_config = nelze analyzovat konfigurační soubor
err_crawl = nepodařilo se prohledat adresáře relací
err_dgn_oob = zpráva protokolu err_dgn_oob = zpráva protokolu
err_domain = neplatná doména err_domain = neplatná doména
err_empty_password = prázdné heslo není povoleno
err_envlist = nepodařilo se získat seznam proměnných prostředí
err_get_active_tty = nepodařilo se získat aktivní tty
err_hibernate = nepodařilo se spustit příkaz hibernace
err_hostname = nelze získat název hostitele err_hostname = nelze získat název hostitele
err_inactivity = nepodařilo se spustit příkaz nečinnosti
err_lock_state = nepodařilo se získat stav zámku
err_log = nepodařilo se otevřít soubor protokolu
err_mlock = uzamčení paměti hesel selhalo err_mlock = uzamčení paměti hesel selhalo
err_null = nulový ukazatel err_null = nulový ukazatel
err_numlock = nepodařilo se nastavit numlock
err_pam = pam transakce selhala err_pam = pam transakce selhala
err_pam_abort = pam transakce přerušena err_pam_abort = pam transakce přerušena
err_pam_acct_expired = platnost účtu vypršela err_pam_acct_expired = platnost účtu vypršela
@ -46,33 +49,34 @@ err_perm_dir = nepodařilo se změnit adresář
err_perm_group = nepodařilo se snížit skupinová oprávnění 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_perm_user = nepodařilo se snížit uživatelská oprávnění
err_pwnam = nelze získat informace o uživateli err_pwnam = nelze získat informace o uživateli
err_sleep = nepodařilo se spustit příkaz spánku
err_start = nepodařilo se spustit příkaz spuštění
err_battery = nepodařilo se načíst stav baterie
err_switch_tty = nepodařilo se přepnout tty
err_tty_ctrl = přenos řízení tty selhal
err_no_users = nebyli nalezeni žádní uživatelé
err_uid_range = nepodařilo se dynamicky získat rozsah uid
err_user_gid = nastavení GID uživatele selhalo err_user_gid = nastavení GID uživatele selhalo
err_user_init = inicializace uživatele selhala err_user_init = inicializace uživatele selhala
err_user_uid = nastavení UID uživateli selhalo err_user_uid = nastavení UID uživateli selhalo
err_xauth = příkaz xauth selhal
err_xcb_conn = připojení xcb selhalo
err_xsessions_dir = nepodařilo se najít složku relací err_xsessions_dir = nepodařilo se najít složku relací
err_xsessions_open = nepodařilo se otevřít složku relací err_xsessions_open = nepodařilo se otevřít složku relací
hibernate = hibernace
insert = vložit
login = uživatel login = uživatel
logout = odhlášen logout = odhlášen
no_x11_support = podpora x11 zakázána při kompilaci
normal = normální
numlock = numlock numlock = numlock
other = jiné
password = heslo password = heslo
restart = restartovat restart = restartovat
shell = příkazový řádek shell = příkazový řádek
shutdown = vypnout shutdown = vypnout
sleep = uspat
toggle_password = zobrazit/skrýt heslo
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -2,26 +2,29 @@ authenticating = authentifizieren...
brightness_down = Helligkeit- brightness_down = Helligkeit-
brightness_up = Helligkeit+ brightness_up = Helligkeit+
capslock = Feststelltaste capslock = Feststelltaste
custom = benutzerdefiniert
custom_info_err_output_long = Ausgabe zu lang
custom_info_err_no_output = keine Ausgabe
custom_info_err_no_output_error = , möglicher Fehler
err_alloc = Speicherzuweisung fehlgeschlagen err_alloc = Speicherzuweisung fehlgeschlagen
err_args = Kommandozeilenargumente konnten nicht verarbeitet werden
err_autologin_session = Autologin-Sitzung nicht gefunden
err_bounds = Index ausserhalb des Bereichs err_bounds = Index ausserhalb des Bereichs
err_brightness_change = Helligkeitsänderung fehlgeschlagen err_brightness_change = Helligkeitsänderung fehlgeschlagen
err_chdir = Fehler beim Oeffnen des Home-Ordners err_chdir = Fehler beim Oeffnen des Home-Ordners
err_clock_too_long = Uhrzeitzeichenkette zu lang
err_config = Fehler beim Verarbeiten der Konfigurationsdatei err_config = Fehler beim Verarbeiten der Konfigurationsdatei
err_crawl = Sitzungsverzeichnisse konnten nicht durchsucht werden
err_dgn_oob = Diagnose-Nachricht err_dgn_oob = Diagnose-Nachricht
err_domain = Ungueltige Domain err_domain = Ungueltige Domain
err_empty_password = Leeres Passwort nicht zugelassen err_empty_password = Leeres Passwort nicht zugelassen
err_envlist = Fehler beim Abrufen der Umgebungs-Variablen err_envlist = Fehler beim Abrufen der Umgebungs-Variablen
err_get_active_tty = Aktives tty konnte nicht ermittelt werden
err_hibernate = Ruhezustand-Befehl konnte nicht ausgeführt werden
err_hostname = Abrufen des Hostnames fehlgeschlagen err_hostname = Abrufen des Hostnames fehlgeschlagen
err_inactivity = Inaktivitätsbefehl konnte nicht ausgeführt werden
err_lock_state = Sperrstatus konnte nicht ermittelt werden
err_log = Protokolldatei konnte nicht geöffnet werden
err_mlock = Sperren des Passwortspeichers fehlgeschlagen err_mlock = Sperren des Passwortspeichers fehlgeschlagen
err_null = Null Pointer err_null = Null Pointer
err_numlock = Numlock konnte nicht aktiviert werden err_numlock = Numlock konnte nicht aktiviert werden
@ -47,12 +50,12 @@ err_perm_group = Fehler beim Heruntersetzen der Gruppenberechtigungen
err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen
err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen
err_sleep = Sleep-Befehl fehlgeschlagen err_sleep = Sleep-Befehl fehlgeschlagen
err_start = Startbefehl konnte nicht ausgeführt werden
err_battery = Akkustand konnte nicht geladen werden
err_switch_tty = tty konnte nicht gewechselt werden
err_tty_ctrl = Fehler bei der TTY-Uebergabe err_tty_ctrl = Fehler bei der TTY-Uebergabe
err_no_users = Keine Benutzer gefunden
err_uid_range = uid-Bereich konnte nicht dynamisch ermittelt werden
err_user_gid = Fehler beim Setzen der Gruppen-ID err_user_gid = Fehler beim Setzen der Gruppen-ID
err_user_init = Nutzer-Initialisierung fehlgeschlagen err_user_init = Nutzer-Initialisierung fehlgeschlagen
err_user_uid = Setzen der Benutzer-ID fehlgeschlagen err_user_uid = Setzen der Benutzer-ID fehlgeschlagen
@ -60,11 +63,11 @@ err_xauth = Xauth-Befehl fehlgeschlagen
err_xcb_conn = xcb-Verbindung fehlgeschlagen err_xcb_conn = xcb-Verbindung fehlgeschlagen
err_xsessions_dir = Fehler beim Finden des Sitzungsordners err_xsessions_dir = Fehler beim Finden des Sitzungsordners
err_xsessions_open = Fehler beim Oeffnen des Sitzungsordners err_xsessions_open = Fehler beim Oeffnen des Sitzungsordners
hibernate = Ruhezustand
insert = Einfügen insert = Einfügen
login = Nutzer login = Nutzer
logout = Abmelden logout = Abmelden
no_x11_support = X11-Support bei Kompilierung deaktiviert no_x11_support = x11-Unterstützung zur Kompilierzeit deaktiviert
normal = Normal normal = Normal
numlock = Numlock numlock = Numlock
other = Andere other = Andere
@ -73,6 +76,7 @@ restart = Neustarten
shell = Shell shell = Shell
shutdown = Herunterfahren shutdown = Herunterfahren
sleep = Sleep sleep = Sleep
toggle_password = Passwort anzeigen/verbergen
wayland = wayland wayland = wayland
x11 = X11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -3,6 +3,9 @@ brightness_down = decrease brightness
brightness_up = increase brightness brightness_up = increase brightness
capslock = capslock capslock = capslock
custom = custom custom = custom
custom_info_err_output_long = output too long
custom_info_err_no_output = no output
custom_info_err_no_output_error = , possible error
err_alloc = failed memory allocation err_alloc = failed memory allocation
err_args = unable to parse command line arguments err_args = unable to parse command line arguments
err_autologin_session = autologin session not found err_autologin_session = autologin session not found
@ -73,6 +76,7 @@ restart = reboot
shell = shell shell = shell
shutdown = shutdown shutdown = shutdown
sleep = sleep sleep = sleep
toggle_password = toggle password
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

82
res/lang/eo.ini Normal file
View File

@ -0,0 +1,82 @@
authenticating = aŭtentigado...
brightness_down = malpliigi helecon
brightness_up = pliigi helecon
capslock = majuskla baskulo
custom = propra
custom_info_err_output_long = eligo tro longa
custom_info_err_no_output = neniu eligo
custom_info_err_no_output_error = , ebla eraro
err_alloc = malsukcesis memorasignon
err_args = ne povas analizi argumentojn de komanda linio
err_autologin_session = aŭtomatan ensalutan seancon ne trovis
err_bounds = indico estas ekster-intervala
err_brightness_change = malsukcesis ŝanĝi la helecon
err_chdir = malsukcesis malfermi hejman dosierujon
err_clock_too_long = horloĝa ĉeno estas tro longa
err_config = ne povas analizi agordan dosieron
err_crawl = malsukcesis dum serĉado de seancaj dosierujoj
err_dgn_oob = protokola mesaĝo
err_domain = malvalida domajno
err_empty_password = ne akceptas malplenan pasvorton
err_envlist = malsukcesis preni la medivariablojn
err_get_active_tty = malsukcesis preni la aktivan TTY-on
err_hibernate = malsukcesis ruli la komandon por diskodormo
err_hostname = malsukcesis preni la sistemnomon
err_inactivity = malsukcesis ruli la agorditan komandon por malaktiveco
err_lock_state = malsukcesis preni la ŝlosan staton
err_log = malsukcesis malfermi la protokolan dosieron
err_mlock = malsukcesis ŝlosi pasvortan memoron
err_null = nula memorloko
err_numlock = malsukcesis agordi numeran baskulon
err_pam = PAM-a transakcio malsukcesis
err_pam_abort = PAM-a transakcio malsukcesis
err_pam_acct_expired = konto eksvalidiĝis
err_pam_auth = aŭtentiga eraro
err_pam_authinfo_unavail = malsukcesis preni uzantajn informojn
err_pam_authok_reqd = memorsigno eksvalidiĝis
err_pam_buf = bufra eraro
err_pam_cred_err = malsukcesis agordi akreditaĵon
err_pam_cred_expired = akreditaĵo eksvalidiĝis
err_pam_cred_insufficient = nesufiĉa akreditaĵo
err_pam_cred_unavail = malsukcesis preni akreditaĵon
err_pam_maxtries = atingis maksimuman kvanton da provoj
err_pam_perm_denied = permeso negis
err_pam_session = seancan eraron
err_pam_sys = sisteman eraron
err_pam_user_unknown = ne konas uzanton
err_path = malsukcesis agordi la median dosierindikon
err_perm_dir = malsukcesis ŝanĝi la nunan dosierujon
err_perm_group = malsukcesis redukti grupajn permesojn
err_perm_user = malsukcesis redukti uzantajn permesojn
err_pwnam = malsukcesis preni uzantajn informojn
err_sleep = malsukcesis ruli memordorman komandon
err_start = malsukcesis ruli startan komandon
err_battery = malsukcesis ŝargi baterian staton
err_switch_tty = malsukcesis ŝanĝi TTY-on
err_tty_ctrl = TTY-an stiran transigon malsukcesis
err_no_users = nul uzantojn trovas
err_uid_range = malsukcesis dinamike preni UID-an intervalon
err_user_gid = malsukcesis agordi uzantan GID-on
err_user_init = malsukcesis iniciĝi uzanto
err_user_uid = malsukcesis agordi uzantan UID-on
err_xauth = malsukcesis plenumi je xauth
err_xcb_conn = malsukcesis dum konectado al xcb
err_xsessions_dir = malsukcesis trovi seancan dosierujon
err_xsessions_open = malsukcesis malfermi seancan dosierujon
hibernate = diskodormi
insert = enmeti
login = uzanto
logout = elsalutis
no_x11_support = x11 estas foriĝita de kompil-tempo
normal = normala
numlock = numera baskulo
other = alia
password = pasvorto
restart = restartigi
shell = ŝelo
shutdown = malŝalti
sleep = memordormi
toggle_password = montri/kaŝi pasvorton
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@ -2,29 +2,32 @@ authenticating = autenticando...
brightness_down = bajar brillo brightness_down = bajar brillo
brightness_up = subir brillo brightness_up = subir brillo
capslock = Bloq Mayús capslock = Bloq Mayús
custom = personalizado
custom_info_err_output_long = salida demasiado larga
custom_info_err_no_output = sin salida
custom_info_err_no_output_error = , posible error
err_alloc = asignación de memoria fallida err_alloc = asignación de memoria fallida
err_args = no se pudieron analizar los argumentos de la línea de comandos
err_autologin_session = no se encontró la sesión de inicio de sesión automático
err_bounds = índice fuera de límites err_bounds = índice fuera de límites
err_brightness_change = no se pudo cambiar el brillo
err_chdir = error al abrir la carpeta home err_chdir = error al abrir la carpeta home
err_clock_too_long = la cadena del reloj es demasiado larga
err_config = no se pudo analizar el archivo de configuración
err_crawl = no se pudieron explorar los directorios de sesión
err_dgn_oob = mensaje de registro err_dgn_oob = mensaje de registro
err_domain = dominio inválido err_domain = dominio inválido
err_empty_password = no se permite contraseña vacía
err_envlist = no se pudo obtener la lista de variables de entorno
err_get_active_tty = no se pudo obtener el tty activo
err_hibernate = no se pudo ejecutar el comando de hibernación
err_hostname = error al obtener el nombre de host err_hostname = error al obtener el nombre de host
err_inactivity = no se pudo ejecutar el comando de inactividad
err_lock_state = no se pudo obtener el estado de bloqueo
err_log = no se pudo abrir el archivo de registro
err_mlock = error al bloquear la contraseña de memoria err_mlock = error al bloquear la contraseña de memoria
err_null = puntero nulo err_null = puntero nulo
err_numlock = no se pudo configurar numlock
err_pam = error en la transacción pam err_pam = error en la transacción pam
err_pam_abort = transacción pam abortada err_pam_abort = transacción pam abortada
err_pam_acct_expired = cuenta expirada err_pam_acct_expired = cuenta expirada
@ -46,25 +49,25 @@ err_perm_dir = error al cambiar el directorio actual
err_perm_group = error al degradar los permisos del grupo err_perm_group = error al degradar los permisos del grupo
err_perm_user = error al degradar los permisos del usuario err_perm_user = error al degradar los permisos del usuario
err_pwnam = error al obtener la información del usuario err_pwnam = error al obtener la información del usuario
err_sleep = no se pudo ejecutar el comando de suspensión
err_start = no se pudo ejecutar el comando de inicio
err_battery = no se pudo cargar el estado de la batería
err_switch_tty = no se pudo cambiar de tty
err_tty_ctrl = falló la transferencia de control tty
err_no_users = no se encontraron usuarios
err_uid_range = no se pudo obtener dinámicamente el rango de uid
err_user_gid = error al establecer el GID del usuario err_user_gid = error al establecer el GID del usuario
err_user_init = error al inicializar usuario err_user_init = error al inicializar usuario
err_user_uid = error al establecer el UID del usuario err_user_uid = error al establecer el UID del usuario
err_xauth = falló el comando xauth
err_xcb_conn = falló la conexión xcb
err_xsessions_dir = error al buscar la carpeta de sesiones err_xsessions_dir = error al buscar la carpeta de sesiones
err_xsessions_open = error al abrir la carpeta de sesiones err_xsessions_open = error al abrir la carpeta de sesiones
hibernate = hibernar
insert = insertar insert = insertar
login = usuario login = usuario
logout = cerrar sesión logout = cerrar sesión
no_x11_support = soporte para x11 deshabilitado en tiempo de compilación no_x11_support = soporte x11 desactivado en tiempo de compilación
normal = normal normal = normal
numlock = Bloq Num numlock = Bloq Num
other = otro other = otro
@ -73,6 +76,7 @@ restart = reiniciar
shell = shell shell = shell
shutdown = apagar shutdown = apagar
sleep = suspender sleep = suspender
toggle_password = mostrar/ocultar contraseña
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -3,6 +3,9 @@ brightness_down = diminuer la luminosité
brightness_up = augmenter la luminosité brightness_up = augmenter la luminosité
capslock = verr.maj capslock = verr.maj
custom = customisé custom = customisé
custom_info_err_output_long = sortie trop longue
custom_info_err_no_output = pas de sortie
custom_info_err_no_output_error = , erreur possible
err_alloc = échec d'allocation mémoire err_alloc = échec d'allocation mémoire
err_args = échec de l'analyse des arguments en lignes de commande err_args = échec de l'analyse des arguments en lignes de commande
err_autologin_session = session de connexion automatique introuvable err_autologin_session = session de connexion automatique introuvable
@ -73,6 +76,7 @@ restart = redémarrer
shell = shell shell = shell
shutdown = éteindre shutdown = éteindre
sleep = veille sleep = veille
toggle_password = afficher le mot de passe
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = autenticazione in corso...
brightness_down = diminuisci luminosità
brightness_up = aumenta luminosità
capslock = capslock capslock = capslock
custom = personalizzato
custom_info_err_output_long = output troppo lungo
custom_info_err_no_output = nessun output
custom_info_err_no_output_error = , possibile errore
err_alloc = impossibile allocare memoria err_alloc = impossibile allocare memoria
err_args = impossibile analizzare gli argomenti della riga di comando
err_autologin_session = sessione di accesso automatico non trovata
err_bounds = indice fuori limite err_bounds = indice fuori limite
err_brightness_change = impossibile modificare la luminosità
err_chdir = impossibile aprire home directory err_chdir = impossibile aprire home directory
err_clock_too_long = stringa dell'orologio troppo lunga
err_config = impossibile analizzare il file di configurazione
err_crawl = impossibile esplorare le directory delle sessioni
err_dgn_oob = messaggio log err_dgn_oob = messaggio log
err_domain = dominio non valido err_domain = dominio non valido
err_empty_password = password vuota non consentita
err_envlist = impossibile ottenere la lista delle variabili d'ambiente
err_get_active_tty = impossibile ottenere il tty attivo
err_hibernate = impossibile eseguire il comando di ibernazione
err_hostname = impossibile ottenere hostname err_hostname = impossibile ottenere hostname
err_inactivity = impossibile eseguire il comando di inattività
err_lock_state = impossibile ottenere lo stato di blocco
err_log = impossibile aprire il file di log
err_mlock = impossibile ottenere lock per la password in memoria err_mlock = impossibile ottenere lock per la password in memoria
err_null = puntatore nullo err_null = puntatore nullo
err_numlock = impossibile impostare il numlock
err_pam = transazione PAM fallita err_pam = transazione PAM fallita
err_pam_abort = transazione PAM interrotta err_pam_abort = transazione PAM interrotta
err_pam_acct_expired = account scaduto err_pam_acct_expired = account scaduto
@ -46,33 +49,34 @@ err_perm_dir = impossibile cambiare directory corrente
err_perm_group = impossibile ridurre permessi gruppo err_perm_group = impossibile ridurre permessi gruppo
err_perm_user = impossibile ridurre permessi utente err_perm_user = impossibile ridurre permessi utente
err_pwnam = impossibile ottenere dati utente err_pwnam = impossibile ottenere dati utente
err_sleep = impossibile eseguire il comando di sospensione
err_start = impossibile eseguire il comando di avvio
err_battery = impossibile caricare lo stato della batteria
err_switch_tty = impossibile cambiare tty
err_tty_ctrl = trasferimento del controllo tty fallito
err_no_users = nessun utente trovato
err_uid_range = impossibile ottenere dinamicamente l'intervallo uid
err_user_gid = impossibile impostare GID utente err_user_gid = impossibile impostare GID utente
err_user_init = impossibile inizializzare utente err_user_init = impossibile inizializzare utente
err_user_uid = impossible impostare UID utente err_user_uid = impossible impostare UID utente
err_xauth = comando xauth fallito
err_xcb_conn = connessione xcb fallita
err_xsessions_dir = impossibile localizzare cartella sessioni err_xsessions_dir = impossibile localizzare cartella sessioni
err_xsessions_open = impossibile aprire cartella sessioni err_xsessions_open = impossibile aprire cartella sessioni
hibernate = ibernazione
insert = inserisci
login = username login = username
logout = scollegato logout = scollegato
no_x11_support = supporto x11 disabilitato in fase di compilazione
normal = normale
numlock = numlock numlock = numlock
other = altro
password = password password = password
restart = riavvio restart = riavvio
shell = shell shell = shell
shutdown = arresto shutdown = arresto
sleep = sospendi
toggle_password = mostra/nascondi password
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -2,26 +2,29 @@ authenticating = 認証中...
brightness_down = 明るさを下げる brightness_down = 明るさを下げる
brightness_up = 明るさを上げる brightness_up = 明るさを上げる
capslock = CapsLock capslock = CapsLock
custom = カスタム
custom_info_err_output_long = 出力が長すぎます
custom_info_err_no_output = 出力なし
custom_info_err_no_output_error = 、エラーの可能性あり
err_alloc = メモリ割り当て失敗 err_alloc = メモリ割り当て失敗
err_args = コマンドライン引数を解析できません
err_autologin_session = 自動ログインセッションが見つかりません
err_bounds = 境界外インデックス err_bounds = 境界外インデックス
err_brightness_change = 明るさの変更に失敗しました err_brightness_change = 明るさの変更に失敗しました
err_chdir = ホームフォルダを開けませんでした err_chdir = ホームフォルダを開けませんでした
err_clock_too_long = 時計の文字列が長すぎます
err_config = 設定ファイルを解析できません err_config = 設定ファイルを解析できません
err_crawl = セッションディレクトリのクロールに失敗しました
err_dgn_oob = ログメッセージ err_dgn_oob = ログメッセージ
err_domain = 無効なドメイン err_domain = 無効なドメイン
err_empty_password = 空のパスワードは許可されていません err_empty_password = 空のパスワードは許可されていません
err_envlist = 環境変数リストの取得に失敗しました err_envlist = 環境変数リストの取得に失敗しました
err_get_active_tty = アクティブなttyの取得に失敗しました
err_hibernate = 休止状態コマンドの実行に失敗しました
err_hostname = ホスト名の取得に失敗しました err_hostname = ホスト名の取得に失敗しました
err_inactivity = 無操作コマンドの実行に失敗しました
err_lock_state = ロック状態の取得に失敗しました
err_log = ログファイルを開けませんでした
err_mlock = パスワードメモリのロックに失敗しました err_mlock = パスワードメモリのロックに失敗しました
err_null = ヌルポインタ err_null = ヌルポインタ
err_numlock = NumLockの設定に失敗しました err_numlock = NumLockの設定に失敗しました
@ -47,12 +50,12 @@ err_perm_group = グループ権限のダウングレードに失敗しました
err_perm_user = ユーザー権限のダウングレードに失敗しました err_perm_user = ユーザー権限のダウングレードに失敗しました
err_pwnam = ユーザー情報の取得に失敗しました err_pwnam = ユーザー情報の取得に失敗しました
err_sleep = スリープコマンドの実行に失敗しました err_sleep = スリープコマンドの実行に失敗しました
err_start = 起動コマンドの実行に失敗しました
err_battery = バッテリー状態の読み込みに失敗しました
err_switch_tty = ttyの切り替えに失敗しました
err_tty_ctrl = TTY制御の転送に失敗しました err_tty_ctrl = TTY制御の転送に失敗しました
err_no_users = ユーザーが見つかりません
err_uid_range = uidの範囲を動的に取得できませんでした
err_user_gid = ユーザーGIDの設定に失敗しました err_user_gid = ユーザーGIDの設定に失敗しました
err_user_init = ユーザーの初期化に失敗しました err_user_init = ユーザーの初期化に失敗しました
err_user_uid = ユーザーUIDの設定に失敗しました err_user_uid = ユーザーUIDの設定に失敗しました
@ -60,11 +63,11 @@ err_xauth = xauthコマンドの実行に失敗しました
err_xcb_conn = XCB接続に失敗しました err_xcb_conn = XCB接続に失敗しました
err_xsessions_dir = セッションフォルダが見つかりませんでした err_xsessions_dir = セッションフォルダが見つかりませんでした
err_xsessions_open = セッションフォルダを開けませんでした err_xsessions_open = セッションフォルダを開けませんでした
hibernate = 休止状態
insert = 挿入 insert = 挿入
login = ログイン login = ログイン
logout = ログアウト済み logout = ログアウト済み
no_x11_support = X11サポートはコンパイル時に無効化されています no_x11_support = x11サポートはコンパイル時に無効化されています
normal = 通常 normal = 通常
numlock = NumLock numlock = NumLock
other = その他 other = その他
@ -73,6 +76,7 @@ restart = 再起動
shell = シェル shell = シェル
shutdown = シャットダウン shutdown = シャットダウン
sleep = スリープ sleep = スリープ
toggle_password = パスワードの表示/非表示
wayland = Wayland wayland = Wayland
x11 = X11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

82
res/lang/ku.ini Normal file
View File

@ -0,0 +1,82 @@
authenticating = tê piştrastkirin...
brightness_down = ronahiyê kêm bike
brightness_up = ronahiyê bilind bike
capslock = tîpên girdek (capslock)
custom = kesane
custom_info_err_output_long = encam pir dirêj e
custom_info_err_no_output = encam tune
custom_info_err_no_output_error = , xeletiya mimkun
err_alloc = veqetandina bîrê têk çû
err_args = argumanên rêzika fermanê nehatin analîzkirin
err_autologin_session = danişîna têketina xweber nehate dîtin
err_bounds = îndeksa derveyî sînor
err_brightness_change = guherandina ronahiyê têk çû
err_chdir = vekirina peldanka malê têk çû
err_clock_too_long = rêzika demjimêrê pir dirêj e
err_config = pela rêkxistinê nehat analîzkirin
err_crawl = gerandina pelrêçên danişînê têk çû
err_dgn_oob = peyama têketinê
err_domain = navpara nederbasdar
err_empty_password = borînpeyv nabe ku vala be
err_envlist = girtina lîsteya jîngehê (envlist) têk çû
err_get_active_tty = girtina tty ya çalak têk çû
err_hibernate = fermana cemidaninê nehat xebitandin
err_hostname = girtina navê mêvandar têk çû
err_inactivity = fermana neçalaktiyê nehat xebitandin
err_lock_state = girtina rewşa kilîtkirinê têk çû
err_log = vekirina pelê têkeinê têk çû
err_mlock = kilîtkirina bîra borînpeyvê têk çû
err_null = nîşandera null
err_numlock = sazkirina numlock têk çû
err_pam = danûstendina pam têk çû
err_pam_abort = danûstendina pam hate têkbirin
err_pam_acct_expired = dema jimarê derbas bûye
err_pam_auth = şaşetiya piştrastkirinê
err_pam_authinfo_unavail = zanyariyên bikarhêner nehatin girtin
err_pam_authok_reqd = dema nîşandanê derbas bûye
err_pam_buf = şaşetiya bîra demkî
err_pam_cred_err = sazkirina rastkitinê têk çû
err_pam_cred_expired = dema rastkitinê derbas bûye
err_pam_cred_insufficient = rastkitinê kêm
err_pam_cred_unavail = girtina rastkitinê têk çû
err_pam_maxtries = sînorê hewldanên herî bilind hat gihîştin
err_pam_perm_denied = mafdayîn hat paşguhkirin
err_pam_session = şaşetiya danişînê
err_pam_sys = şaşetiya pergalê
err_pam_user_unknown = bikarhênerê nenas
err_path = sazkirina rêgehê têk çû
err_perm_dir = guhertina pelrêçê heyî têk çû
err_perm_group = kêmkirina mafdayînên komê têk çû
err_perm_user = kêmkirina mafdayînên bikarhêner têk çû
err_pwnam = girtina zanyariyên bikarhêner têk çû
err_sleep = fermana cemidaninê nehat xebitandin
err_start = fermana destpêkirinê nehat xebitandin
err_battery = barkirina rewşa betariyê têk çû
err_switch_tty = guhertina tty têk çû
err_tty_ctrl = guhertina kontrola tty têk çû
err_no_users = tu bikarhêner nehatin dîtin
err_uid_range = girtina rêjeya dînamîk a sînorê uid têk çû
err_user_gid = sazkirina GID a bikarhêner têk çû
err_user_init = destpêkirina bikarhêner têk çû
err_user_uid = sazkirina UID a bikarhêner têk çû
err_xauth = fermana xauth têk çû
err_xcb_conn = girêdana xcb têk çû
err_xsessions_dir = dîtina peldanka danişînan têk çû
err_xsessions_open = vekirina peldanka danişînan têk çû
hibernate = bicemidîne
insert = têxîne
login = têketin
logout = derkeve
no_x11_support = piştgiriya x11 di dema berhevkirinê de hatiye girtin
normal = normal
numlock = numlock
other = ên din
password = borînpeyv
restart = ji nû ve bide destpêkirin
shell = shell
shutdown = vemirîne
sleep = têxîne xewê
toggle_password = şîfre nîşan bide/veşêre
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@ -3,23 +3,26 @@ brightness_down = samazināt spilgtumu
brightness_up = palielināt spilgtumu brightness_up = palielināt spilgtumu
capslock = caps lock capslock = caps lock
custom = pielāgots custom = pielāgots
custom_info_err_output_long = izvade pārāk gara
custom_info_err_no_output = nav izvades
custom_info_err_no_output_error = , iespējama kļūda
err_alloc = neizdevās atmiņas piešķiršana err_alloc = neizdevās atmiņas piešķiršana
err_args = nevar parsēt komandrindas argumentus
err_autologin_session = automātiskās pieteikšanās sesija nav atrasta
err_bounds = indekss ārpus robežām err_bounds = indekss ārpus robežām
err_brightness_change = neizdevās mainīt spilgtumu err_brightness_change = neizdevās mainīt spilgtumu
err_chdir = neizdevās atvērt mājas mapi err_chdir = neizdevās atvērt mājas mapi
err_clock_too_long = pulksteņa virkne pārāk gara err_clock_too_long = pulksteņa virkne pārāk gara
err_config = neizdevās parsēt konfigurācijas failu err_config = neizdevās parsēt konfigurācijas failu
err_crawl = neizdevās pārlūkot sesiju direktorijus
err_dgn_oob = žurnāla ziņojums err_dgn_oob = žurnāla ziņojums
err_domain = nederīgs domēns err_domain = nederīgs domēns
err_empty_password = tukša parole nav atļauta err_empty_password = tukša parole nav atļauta
err_envlist = neizdevās iegūt vides mainīgo sarakstu err_envlist = neizdevās iegūt vides mainīgo sarakstu
err_get_active_tty = neizdevās iegūt aktīvo tty err_get_active_tty = neizdevās iegūt aktīvo tty
err_hibernate = neizdevās izpildīt hibernācijas komandu
err_hostname = neizdevās iegūt hostname err_hostname = neizdevās iegūt hostname
err_inactivity = neizdevās izpildīt neaktivitātes komandu
err_lock_state = neizdevās iegūt bloķēšanas stāvokli err_lock_state = neizdevās iegūt bloķēšanas stāvokli
err_log = neizdevās atvērt žurnāla failu err_log = neizdevās atvērt žurnāla failu
err_mlock = neizdevās bloķēt paroles atmiņu err_mlock = neizdevās bloķēt paroles atmiņu
@ -47,12 +50,12 @@ err_perm_group = neizdevās pazemināt grupas atļaujas
err_perm_user = neizdevās pazemināt lietotāja atļaujas err_perm_user = neizdevās pazemināt lietotāja atļaujas
err_pwnam = neizdevās iegūt lietotāja informāciju err_pwnam = neizdevās iegūt lietotāja informāciju
err_sleep = neizdevās izpildīt miega komandu err_sleep = neizdevās izpildīt miega komandu
err_start = neizdevās izpildīt startēšanas komandu
err_battery = neizdevās ielādēt akumulatora stāvokli err_battery = neizdevās ielādēt akumulatora stāvokli
err_switch_tty = neizdevās pārslēgt tty err_switch_tty = neizdevās pārslēgt tty
err_tty_ctrl = tty vadības nodošana neizdevās err_tty_ctrl = tty vadības nodošana neizdevās
err_no_users = lietotāji nav atrasti err_no_users = lietotāji nav atrasti
err_uid_range = neizdevās dinamiski iegūt uid diapazonu
err_user_gid = neizdevās iestatīt lietotāja GID err_user_gid = neizdevās iestatīt lietotāja GID
err_user_init = neizdevās inicializēt lietotāju err_user_init = neizdevās inicializēt lietotāju
err_user_uid = neizdevās iestatīt lietotāja UID err_user_uid = neizdevās iestatīt lietotāja UID
@ -60,7 +63,7 @@ err_xauth = xauth komanda neizdevās
err_xcb_conn = xcb savienojums neizdevās err_xcb_conn = xcb savienojums neizdevās
err_xsessions_dir = neizdevās atrast sesiju mapi err_xsessions_dir = neizdevās atrast sesiju mapi
err_xsessions_open = neizdevās atvērt sesiju mapi err_xsessions_open = neizdevās atvērt sesiju mapi
hibernate = hibernācija
insert = ievietot insert = ievietot
login = lietotājs login = lietotājs
logout = iziet logout = iziet
@ -73,6 +76,7 @@ restart = restartēt
shell = terminālis shell = terminālis
shutdown = izslēgt shutdown = izslēgt
sleep = snauda sleep = snauda
toggle_password = rādīt/slēpt paroli
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -3,23 +3,26 @@ brightness_down = zmniejsz jasność
brightness_up = zwiększ jasność brightness_up = zwiększ jasność
capslock = capslock capslock = capslock
custom = własny custom = własny
custom_info_err_output_long = wyjście zbyt długie
custom_info_err_no_output = brak wyjścia
custom_info_err_no_output_error = , możliwy błąd
err_alloc = nieudana alokacja pamięci err_alloc = nieudana alokacja pamięci
err_args = nie można przetworzyć argumentów wiersza poleceń
err_autologin_session = nie znaleziono sesji autologowania err_autologin_session = nie znaleziono sesji autologowania
err_bounds = indeks poza zakresem err_bounds = indeks poza zakresem
err_brightness_change = nie udało się zmienić jasności err_brightness_change = nie udało się zmienić jasności
err_chdir = nie udało się otworzyć folderu domowego err_chdir = nie udało się otworzyć folderu domowego
err_clock_too_long = ciąg znaków zegara jest za długi err_clock_too_long = ciąg znaków zegara jest za długi
err_config = nie można przetworzyć pliku konfiguracyjnego err_config = nie można przetworzyć pliku konfiguracyjnego
err_crawl = nie udało się przeszukać katalogów sesji
err_dgn_oob = wiadomość loga err_dgn_oob = wiadomość loga
err_domain = niepoprawna domena err_domain = niepoprawna domena
err_empty_password = puste hasło jest niedozwolone err_empty_password = puste hasło jest niedozwolone
err_envlist = nie udało się pobrać listy zmiennych środowiskowych err_envlist = nie udało się pobrać listy zmiennych środowiskowych
err_get_active_tty = nie udało się uzyskać aktywnego tty err_get_active_tty = nie udało się uzyskać aktywnego tty
err_hibernate = nie udało się wykonać polecenia hibernacji
err_hostname = nie udało się uzyskać nazwy hosta err_hostname = nie udało się uzyskać nazwy hosta
err_inactivity = nie udało się wykonać polecenia nieaktywności
err_lock_state = nie udało się uzyskać stanu blokady err_lock_state = nie udało się uzyskać stanu blokady
err_log = nie udało się otworzyć pliku logu err_log = nie udało się otworzyć pliku logu
err_mlock = nie udało się zablokować pamięci haseł err_mlock = nie udało się zablokować pamięci haseł
@ -47,12 +50,12 @@ err_perm_group = nie udało się obniżyć uprawnień grupy
err_perm_user = nie udało się obniżyć uprawnień użytkownika err_perm_user = nie udało się obniżyć uprawnień użytkownika
err_pwnam = nie udało się uzyskać informacji o użytkowniku err_pwnam = nie udało się uzyskać informacji o użytkowniku
err_sleep = nie udało się wykonać polecenia sleep err_sleep = nie udało się wykonać polecenia sleep
err_start = nie udało się wykonać polecenia startowego
err_battery = nie udało się sprawdzić statusu baterii err_battery = nie udało się sprawdzić statusu baterii
err_switch_tty = nie można przełączyć tty err_switch_tty = nie można przełączyć tty
err_tty_ctrl = nie udało się przekazać kontroli tty err_tty_ctrl = nie udało się przekazać kontroli tty
err_no_users = nie znaleziono żadnego użytkownika err_no_users = nie znaleziono żadnego użytkownika
err_uid_range = nie udało się dynamicznie pobrać zakresu uid
err_user_gid = nie udało się ustawić GID 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_user_uid = nie udało się ustawić UID użytkownika
@ -60,11 +63,11 @@ err_xauth = polecenie xauth nie powiodło się
err_xcb_conn = połączenie xcb 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_dir = nie udało się znaleźć folderu sesji
err_xsessions_open = nie udało się otworzyć folderu sesji err_xsessions_open = nie udało się otworzyć folderu sesji
hibernate = hibernuj
insert = wstaw insert = wstaw
login = login login = login
logout = wylogowano logout = wylogowano
no_x11_support = wsparcie X11 wyłączone podczas kompilacji no_x11_support = obsługa x11 wyłączona podczas kompilacji
normal = normalny normal = normalny
numlock = numlock numlock = numlock
other = inny other = inny
@ -73,6 +76,7 @@ restart = uruchom ponownie
shell = powłoka shell = powłoka
shutdown = wyłącz shutdown = wyłącz
sleep = uśpij sleep = uśpij
toggle_password = Pokaż/ukryj hasło
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = a autenticar...
brightness_down = diminuir brilho
brightness_up = aumentar brilho
capslock = capslock capslock = capslock
custom = personalizado
custom_info_err_output_long = saída demasiado longa
custom_info_err_no_output = sem saída
custom_info_err_no_output_error = , possível erro
err_alloc = erro na atribuição de memória err_alloc = erro na atribuição de memória
err_args = não foi possível analisar os argumentos da linha de comandos
err_autologin_session = sessão de início de sessão automático não encontrada
err_bounds = índice fora de limites err_bounds = índice fora de limites
err_brightness_change = não foi possível alterar o brilho
err_chdir = erro ao abrir a pasta home err_chdir = erro ao abrir a pasta home
err_clock_too_long = a cadeia de caracteres do relógio é demasiado longa
err_config = não foi possível analisar o ficheiro de configuração
err_crawl = não foi possível explorar os diretórios de sessão
err_dgn_oob = mensagem de registo err_dgn_oob = mensagem de registo
err_domain = domínio inválido err_domain = domínio inválido
err_empty_password = palavra-passe vazia não é permitida
err_envlist = não foi possível obter a lista de variáveis de ambiente
err_get_active_tty = não foi possível obter o tty ativo
err_hibernate = não foi possível executar o comando de hibernação
err_hostname = erro ao obter o nome do host err_hostname = erro ao obter o nome do host
err_inactivity = não foi possível executar o comando de inatividade
err_lock_state = não foi possível obter o estado de bloqueio
err_log = não foi possível abrir o ficheiro de registo
err_mlock = erro de bloqueio de memória err_mlock = erro de bloqueio de memória
err_null = ponteiro nulo err_null = ponteiro nulo
err_numlock = não foi possível definir o numlock
err_pam = erro na transação pam err_pam = erro na transação pam
err_pam_abort = transação pam abortada err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada err_pam_acct_expired = conta expirada
@ -46,33 +49,34 @@ err_perm_dir = erro ao alterar o diretório atual
err_perm_group = erro ao reduzir as permissões do grupo err_perm_group = erro ao reduzir as permissões do grupo
err_perm_user = erro ao reduzir as permissões do utilizador err_perm_user = erro ao reduzir as permissões do utilizador
err_pwnam = erro ao obter informação do utilizador err_pwnam = erro ao obter informação do utilizador
err_sleep = não foi possível executar o comando de suspensão
err_start = não foi possível executar o comando de início
err_battery = não foi possível carregar o estado da bateria
err_switch_tty = não foi possível mudar de tty
err_tty_ctrl = falhou a transferência de controlo do tty
err_no_users = nenhum utilizador encontrado
err_uid_range = não foi possível obter dinamicamente o intervalo de uid
err_user_gid = erro ao definir o GID do utilizador err_user_gid = erro ao definir o GID do utilizador
err_user_init = erro ao iniciar o utilizador err_user_init = erro ao iniciar o utilizador
err_user_uid = erro ao definir o UID do utilizador err_user_uid = erro ao definir o UID do utilizador
err_xauth = o comando xauth falhou
err_xcb_conn = a ligação xcb falhou
err_xsessions_dir = erro ao localizar a pasta das sessões err_xsessions_dir = erro ao localizar a pasta das sessões
err_xsessions_open = erro ao abrir a pasta das sessões err_xsessions_open = erro ao abrir a pasta das sessões
hibernate = hibernar
insert = inserir
login = iniciar sessão login = iniciar sessão
logout = terminar sessão logout = terminar sessão
no_x11_support = suporte a x11 desativado em tempo de compilação
normal = normal
numlock = numlock numlock = numlock
other = outro
password = palavra-passe password = palavra-passe
restart = reiniciar restart = reiniciar
shell = shell shell = shell
shutdown = encerrar shutdown = encerrar
sleep = suspender
toggle_password = mostrar/ocultar palavra-passe
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = autenticando...
brightness_down = diminuir brilho
brightness_up = aumentar brilho
capslock = caixa alta capslock = caixa alta
custom = personalizado
custom_info_err_output_long = saída muito longa
custom_info_err_no_output = sem saída
custom_info_err_no_output_error = , possível erro
err_alloc = alocação de memória malsucedida err_alloc = alocação de memória malsucedida
err_args = não foi possível analisar os argumentos da linha de comando
err_autologin_session = sessão de login automático não encontrada
err_bounds = índice fora de limites err_bounds = índice fora de limites
err_brightness_change = não foi possível alterar o brilho
err_chdir = não foi possível abrir o diretório home err_chdir = não foi possível abrir o diretório home
err_clock_too_long = a string do relógio é muito longa
err_config = não foi possível analisar o arquivo de configuração
err_crawl = não foi possível explorar os diretórios de sessão
err_dgn_oob = mensagem de log err_dgn_oob = mensagem de log
err_domain = domínio inválido err_domain = domínio inválido
err_empty_password = senha vazia não é permitida
err_envlist = não foi possível obter a lista de variáveis de ambiente
err_get_active_tty = não foi possível obter o tty ativo
err_hibernate = não foi possível executar o comando de hibernação
err_hostname = não foi possível obter o nome do host err_hostname = não foi possível obter o nome do host
err_inactivity = não foi possível executar o comando de inatividade
err_lock_state = não foi possível obter o estado de bloqueio
err_log = não foi possível abrir o arquivo de log
err_mlock = bloqueio da memória de senha malsucedido err_mlock = bloqueio da memória de senha malsucedido
err_null = ponteiro nulo err_null = ponteiro nulo
err_numlock = não foi possível definir o numlock
err_pam = transação pam malsucedida err_pam = transação pam malsucedida
err_pam_abort = transação pam abortada err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada err_pam_acct_expired = conta expirada
@ -46,33 +49,34 @@ 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_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_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_pwnam = não foi possível obter informações do usuário
err_sleep = não foi possível executar o comando de suspensão
err_start = não foi possível executar o comando de início
err_battery = não foi possível carregar o status da bateria
err_switch_tty = não foi possível mudar de tty
err_tty_ctrl = falhou a transferência de controle do tty
err_no_users = nenhum usuário encontrado
err_uid_range = não foi possível obter dinamicamente o intervalo de uid
err_user_gid = não foi possível definir o GID 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_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_user_uid = não foi possível definir o UID do usuário
err_xauth = o comando xauth falhou
err_xcb_conn = a conexão xcb falhou
err_xsessions_dir = não foi possível encontrar a pasta das sessões 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 err_xsessions_open = não foi possível abrir a pasta das sessões
hibernate = hibernar
insert = inserir
login = conectar login = conectar
logout = desconectado logout = desconectado
no_x11_support = suporte a x11 desativado em tempo de compilação
normal = normal
numlock = numlock numlock = numlock
other = outro
password = senha password = senha
restart = reiniciar restart = reiniciar
shell = shell shell = shell
shutdown = desligar shutdown = desligar
sleep = suspender
toggle_password = mostrar/ocultar senha
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,31 +1,34 @@
authenticating = autentificare...
brightness_down = scade luminozitatea
brightness_up = crește luminozitatea
capslock = capslock capslock = capslock
custom = personalizat
custom_info_err_output_long = ieșire prea lungă
custom_info_err_no_output = fără ieșire
custom_info_err_no_output_error = , posibil eroare
err_alloc = alocare de memorie eșuată
err_args = imposibil de analizat argumentele liniei de comandă
err_autologin_session = sesiunea de autentificare automată nu a fost găsită
err_bounds = index în afara limitelor
err_brightness_change = imposibil de schimbat luminozitatea
err_chdir = imposibil de deschis folderul de acasă
err_clock_too_long = șirul de ceas este prea lung
err_config = imposibil de analizat fișierul de configurare
err_crawl = imposibil de explorat directoarele de sesiune
err_dgn_oob = mesaj jurnal
err_domain = domeniu invalid
err_empty_password = parola goală nu este permisă
err_envlist = imposibil de obținut lista de variabile de mediu
err_get_active_tty = imposibil de obținut tty-ul activ
err_hibernate = imposibil de executat comanda de hibernare
err_hostname = imposibil de obținut numele gazdei
err_inactivity = imposibil de executat comanda de inactivitate
err_lock_state = imposibil de obținut starea de blocare
err_log = imposibil de deschis fișierul jurnal
err_mlock = imposibil de blocat memoria parolei
err_null = pointer nul
err_numlock = imposibil de setat numlock
err_pam = tranzacție pam eșuată
err_pam_abort = tranzacţie pam anulată err_pam_abort = tranzacţie pam anulată
err_pam_acct_expired = cont expirat err_pam_acct_expired = cont expirat
err_pam_auth = eroare de autentificare err_pam_auth = eroare de autentificare
@ -41,38 +44,39 @@ err_pam_perm_denied = acces interzis
err_pam_session = eroare de sesiune err_pam_session = eroare de sesiune
err_pam_sys = eroare de sistem err_pam_sys = eroare de sistem
err_pam_user_unknown = utilizator necunoscut err_pam_user_unknown = utilizator necunoscut
err_path = imposibil de setat calea
err_perm_dir = nu s-a putut schimba dosarul (folder-ul) curent err_perm_dir = nu s-a putut schimba dosarul (folder-ul) curent
err_perm_group = nu s-a putut face downgrade permisiunilor de grup err_perm_group = nu s-a putut face downgrade permisiunilor de grup
err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator
err_pwnam = imposibil de obținut informații despre utilizator
err_sleep = imposibil de executat comanda de repaus
err_start = imposibil de executat comanda de pornire
err_battery = imposibil de încărcat starea bateriei
err_switch_tty = imposibil de comutat tty
err_tty_ctrl = transferul controlului tty a eșuat
err_no_users = niciun utilizator găsit
err_uid_range = imposibil de obținut dinamic intervalul uid
err_user_gid = imposibil de setat GID-ul utilizatorului
err_user_init = imposibil de inițializa utilizatorul
err_user_uid = imposibil de setat UID-ul utilizatorului
err_xauth = comanda xauth a eșuat
err_xcb_conn = conexiunea xcb a eșuat
err_xsessions_dir = imposibil de găsit folderul de sesiuni
err_xsessions_open = imposibil de deschis folderul de sesiuni
hibernate = hibernare
insert = inserare
login = utilizator login = utilizator
logout = opreşte sesiunea logout = opreşte sesiunea
no_x11_support = suportul x11 dezactivat la compilare
normal = normal
numlock = numlock numlock = numlock
other = altul
password = parolă password = parolă
restart = resetează restart = resetează
shell = shell shell = shell
shutdown = opreşte sistemul shutdown = opreşte sistemul
sleep = repaus
toggle_password = afișare/ascundere parolă
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -3,23 +3,26 @@ brightness_down = уменьшить яркость
brightness_up = увеличить яркость brightness_up = увеличить яркость
capslock = capslock capslock = capslock
custom = пользовательский custom = пользовательский
custom_info_err_output_long = вывод слишком длинный
custom_info_err_no_output = нет вывода
custom_info_err_no_output_error = , возможная ошибка
err_alloc = не удалось выделить память err_alloc = не удалось выделить память
err_args = не удалось разобрать аргументы командной строки
err_autologin_session = не найдена сессия с автологином err_autologin_session = не найдена сессия с автологином
err_bounds = за пределами индекса err_bounds = за пределами индекса
err_brightness_change = не удалось изменить яркость err_brightness_change = не удалось изменить яркость
err_chdir = не удалось открыть домашнюю папку err_chdir = не удалось открыть домашнюю папку
err_clock_too_long = строка часов слишком длинная err_clock_too_long = строка часов слишком длинная
err_config = не удалось разобрать файл конфигурации err_config = не удалось разобрать файл конфигурации
err_crawl = не удалось просканировать каталоги сессии
err_dgn_oob = отладочное сообщение (log) err_dgn_oob = отладочное сообщение (log)
err_domain = неверный домен err_domain = неверный домен
err_empty_password = пустой пароль не допустим err_empty_password = пустой пароль не допустим
err_envlist = не удалось получить список переменных среды err_envlist = не удалось получить список переменных среды
err_get_active_tty = не удалось получить активный tty err_get_active_tty = не удалось получить активный tty
err_hibernate = не удалось выполнить команду гибернации
err_hostname = не удалось получить имя хоста err_hostname = не удалось получить имя хоста
err_inactivity = не удалось выполнить команду бездействия
err_lock_state = не удалось получить состояние lock err_lock_state = не удалось получить состояние lock
err_log = не удалось открыть файл log err_log = не удалось открыть файл log
err_mlock = сбой блокировки памяти err_mlock = сбой блокировки памяти
@ -47,12 +50,12 @@ err_perm_group = не удалось понизить права доступа
err_perm_user = не удалось понизить права доступа пользователя err_perm_user = не удалось понизить права доступа пользователя
err_pwnam = не удалось получить информацию о пользователе err_pwnam = не удалось получить информацию о пользователе
err_sleep = не удалось выполнить команду sleep err_sleep = не удалось выполнить команду sleep
err_start = не удалось выполнить команду запуска
err_battery = не удалось получить статус батареи err_battery = не удалось получить статус батареи
err_switch_tty = не удалось переключить tty err_switch_tty = не удалось переключить tty
err_tty_ctrl = передача управления tty не удалась err_tty_ctrl = передача управления tty не удалась
err_no_users = пользователи не найдены err_no_users = пользователи не найдены
err_uid_range = не удалось динамически получить диапазон uid
err_user_gid = не удалось установить GID пользователя err_user_gid = не удалось установить GID пользователя
err_user_init = не удалось инициализировать пользователя err_user_init = не удалось инициализировать пользователя
err_user_uid = не удалось установить UID пользователя err_user_uid = не удалось установить UID пользователя
@ -60,11 +63,11 @@ err_xauth = команда xauth не выполнена
err_xcb_conn = ошибка подключения xcb err_xcb_conn = ошибка подключения xcb
err_xsessions_dir = не удалось найти сессионную папку err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку err_xsessions_open = не удалось открыть сессионную папку
hibernate = гибернация
insert = вставка insert = вставка
login = логин login = логин
logout = вышел из системы logout = вышел из системы
no_x11_support = поддержка x11 отключена во время компиляции no_x11_support = поддержка x11 отключена при компиляции
normal = обычный normal = обычный
numlock = numlock numlock = numlock
other = прочие other = прочие
@ -73,6 +76,7 @@ restart = перезагрузить
shell = оболочка shell = оболочка
shutdown = выключить shutdown = выключить
sleep = сон sleep = сон
toggle_password = показать/скрыть пароль
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,78 +1,82 @@
authenticating = autentifikacija...
brightness_down = smanji osvetljenost
brightness_up = povećaj osvetljenost
capslock = capslock capslock = capslock
custom = prilagođeno
err_alloc = neuspijesna alokacija memorije custom_info_err_output_long = izlaz predugačak
custom_info_err_no_output = nema izlaza
custom_info_err_no_output_error = , moguća greška
err_alloc = neuspješna alokacija memorije
err_args = nije moguće raščlaniti argumente komandne linije
err_autologin_session = sesija automatske prijave nije pronađena
err_bounds = izvan granica indeksa err_bounds = izvan granica indeksa
err_brightness_change = nije uspela promena osvetljenosti
err_chdir = neuspijesno otvaranje home foldera err_chdir = neuspješno otvaranje home foldera
err_clock_too_long = niska sata je preduga
err_config = nije moguće raščlaniti konfiguracioni fajl
err_crawl = nije uspelo skeniranje direktorijuma sesija
err_dgn_oob = log poruka err_dgn_oob = log poruka
err_domain = nevazeci domen err_domain = nevažeći domen
err_empty_password = prazna lozinka nije dozvoljena
err_envlist = nije uspelo dobijanje liste promenljivih okruženja
err_get_active_tty = nije uspelo dobijanje aktivnog tty
err_hibernate = nije uspelo izvršavanje komande hibernacije
err_hostname = neuspijesno trazenje hostname-a err_hostname = neuspješno traženje hostname-a
err_inactivity = nije uspelo izvršavanje komande neaktivnosti
err_lock_state = nije uspelo dobijanje stanja zaključavanja
err_log = nije uspelo otvaranje fajla dnevnika
err_mlock = neuspijesno zakljucavanje memorije lozinke err_mlock = neuspješno zaključavanje memorije lozinke
err_null = null pokazivac err_null = null pokazivač
err_numlock = nije uspelo postavljanje numlock
err_pam = pam transakcija neuspijesna err_pam = pam transakcija neuspješna
err_pam_abort = pam transakcija prekinuta err_pam_abort = pam transakcija prekinuta
err_pam_acct_expired = nalog istekao err_pam_acct_expired = nalog istekao
err_pam_auth = greska pri autentikaciji err_pam_auth = greška pri autentikaciji
err_pam_authinfo_unavail = neuspjelo uzimanje informacija o korisniku err_pam_authinfo_unavail = neuspješno uzimanje informacija o korisniku
err_pam_authok_reqd = token istekao err_pam_authok_reqd = token istekao
err_pam_buf = greska bafera memorije err_pam_buf = greška bafera memorije
err_pam_cred_err = neuspjelo postavljanje kredencijala err_pam_cred_err = neuspješno postavljanje kredencijala
err_pam_cred_expired = kredencijali istekli err_pam_cred_expired = kredencijali istekli
err_pam_cred_insufficient = nedovoljni kredencijali err_pam_cred_insufficient = nedovoljni kredencijali
err_pam_cred_unavail = neuspjelo uzimanje kredencijala err_pam_cred_unavail = neuspješno uzimanje kredencijala
err_pam_maxtries = dostignut maksimalan broj pokusaja err_pam_maxtries = dostignut maksimalan broj pokušaja
err_pam_perm_denied = nedozovoljeno err_pam_perm_denied = nedozvoljeno
err_pam_session = greska sesije err_pam_session = greška sesije
err_pam_sys = greska sistema err_pam_sys = greška sistema
err_pam_user_unknown = nepoznat korisnik err_pam_user_unknown = nepoznat korisnik
err_path = neuspjelo postavljanje path-a err_path = neuspješno postavljanje path-a
err_perm_dir = neuspjelo mijenjanje foldera err_perm_dir = neuspješno mijenjanje foldera
err_perm_group = neuspjesno snizavanje dozvola grupe err_perm_group = neuspješno snižavanje dozvola grupe
err_perm_user = neuspijesno snizavanje dozvola korisnika err_perm_user = neuspješno snižavanje dozvola korisnika
err_pwnam = neuspijesno skupljanje informacija o korisniku err_pwnam = neuspješno skupljanje informacija o korisniku
err_sleep = nije uspelo izvršavanje komande spavanja
err_start = nije uspelo izvršavanje komande pokretanja
err_battery = nije uspelo učitavanje statusa baterije
err_switch_tty = nije uspelo prebacivanje tty
err_tty_ctrl = prenos kontrole tty nije uspeo
err_no_users = nisu pronađeni korisnici
err_uid_range = nije uspelo dinamičko dobijanje opsega uid
err_user_gid = neuspijesno postavljanje korisničkog GID-a err_user_gid = neuspješno postavljanje korisničkog GID-a
err_user_init = neuspijensa inicijalizacija korisnika err_user_init = neuspješna inicijalizacija korisnika
err_user_uid = neuspijesno postavljanje UID-a korisnika err_user_uid = neuspješno postavljanje UID-a korisnika
err_xauth = komanda xauth nije uspela
err_xcb_conn = xcb veza nije uspela
err_xsessions_dir = neuspijesno pronalazenje foldera sesija err_xsessions_dir = neuspješno pronalaženje foldera sesija
err_xsessions_open = neuspijesno otvaranje foldera sesija err_xsessions_open = neuspješno otvaranje foldera sesija
hibernate = hibernacija
insert = umetni
login = korisnik login = korisnik
logout = izlogovan logout = izlogovan
no_x11_support = x11 podrška onemogućena tokom prevođenja
normal = normalno
numlock = numlock numlock = numlock
other = ostalo
password = lozinka password = lozinka
restart = ponovo pokreni restart = ponovo pokreni
shell = shell shell = shell
shutdown = ugasi shutdown = ugasi
sleep = uspavaj
toggle_password = prikaži/sakrij lozinku
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

82
res/lang/sr_Cyrl.ini Normal file
View File

@ -0,0 +1,82 @@
authenticating = аутентификација...
brightness_down = смањи осветљеност
brightness_up = повећај осветљеност
capslock = capslock
custom = прилагођено
custom_info_err_output_long = излаз предугачак
custom_info_err_no_output = нема излаза
custom_info_err_no_output_error = , могућа грешка
err_alloc = неуспешна алокација меморије
err_args = није могуће рашчланити аргументе командне линије
err_autologin_session = сесија аутоматске пријаве није пронађена
err_bounds = изван граница индекса
err_brightness_change = није успела промена осветљености
err_chdir = неуспешно отварање home фолдера
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 = неуспешно тражење hostname-а
err_inactivity = није успело извршавање команде неактивности
err_lock_state = није успело добијање стања закључавања
err_log = није успело отварање фајла дневника
err_mlock = неуспешно закључавање меморије лозинке
err_null = 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_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 = numlock
other = остало
password = лозинка
restart = поново покрени
shell = shell
shutdown = угаси
sleep = успавај
toggle_password = прикажи/сакриј лозинку
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@ -1,78 +1,82 @@
authenticating = autentiserar...
brightness_down = minska ljusstyrka
brightness_up = öka ljusstyrka
capslock = capslock capslock = capslock
custom = anpassad
err_alloc = misslyckad minnesallokering custom_info_err_output_long = utdata för lång
custom_info_err_no_output = ingen utdata
custom_info_err_no_output_error = , möjligt fel
err_bounds = utanför banan index err_alloc = minnesallokering misslyckades
err_args = tolkning av kommandoargument misslyckades
err_autologin_session = autologin-session hittades inte
err_bounds = index-värde utanför intervallet
err_brightness_change = ändring av ljusstyrka misslyckades
err_chdir = misslyckades att öppna hemkatalog err_chdir = misslyckades att öppna hemkatalog
err_clock_too_long = klocksträng för lång
err_config = tolkning av konfigfil misslyckades
err_crawl = genomsökning av sessionskataloger misslyckades
err_dgn_oob = loggmeddelande err_dgn_oob = loggmeddelande
err_domain = okänd domän err_domain = ogitlig domän
err_empty_password = tomt lösenord godtas ej
err_envlist = hämtning av env-lista misslyckades
err_get_active_tty = hämtning av aktiv tty misslyckades
err_hibernate = vilolägets kommando misslyckades
err_hostname = misslyckades att hämta värdnamn err_hostname = hämtning av hostname misslyckades
err_inactivity = inaktivitetslägets kommando misslyckades
err_lock_state = hämtning av låsningsstatus misslyckades
err_log = öppning av loggfil misslyckades
err_mlock = misslyckades att låsa lösenordsminne err_mlock = låsning av lösenordsminne misslyckades
err_null = nullpekare err_null = nullpekare
err_numlock = inställning av numlock misslyckades
err_pam = pam-transaktion misslyckades err_pam = pam-transaktion misslyckades
err_pam_abort = pam-transaktion avbröts err_pam_abort = pam-transaktion avbröts
err_pam_acct_expired = konto upphört err_pam_acct_expired = kontot har löpt ut
err_pam_auth = autentiseringsfel err_pam_auth = autentisering misslyckades
err_pam_authinfo_unavail = misslyckades att hämta användarinfo err_pam_authinfo_unavail = hämtning av användarinformation misslyckades
err_pam_authok_reqd = token utgången err_pam_authok_reqd = token har löpt ut
err_pam_buf = minnesbuffer fel err_pam_buf = minnesbufferfel
err_pam_cred_err = misslyckades att ställa in inloggningsuppgifter err_pam_cred_err = inställning av inloggningsuppgifter misslyckades
err_pam_cred_expired = inloggningsuppgifter upphörda err_pam_cred_expired = inloggningsuppgifterna har löpt ut
err_pam_cred_insufficient = otillräckliga inloggningsuppgifter err_pam_cred_insufficient = otillräckliga inloggningsuppgifter
err_pam_cred_unavail = misslyckades att hämta inloggningsuppgifter err_pam_cred_unavail = hämtning av inloggningsuppgifter misslyckades
err_pam_maxtries = nådde maximal försöksgräns err_pam_maxtries = gränsen för antal försök nådd
err_pam_perm_denied = åtkomst nekad err_pam_perm_denied = tillstånd nekas
err_pam_session = sessionsfel err_pam_session = sessionsfel
err_pam_sys = systemfel err_pam_sys = systemfel
err_pam_user_unknown = okänd användare err_pam_user_unknown = okänd användare
err_path = misslyckades att ställa in sökväg err_path = inställning av sökväg misslyckades
err_perm_dir = misslyckades att ändra aktuell katalog err_perm_dir = byte av nuvarande katalog misslyckades
err_perm_group = misslyckades att nergradera gruppbehörigheter err_perm_group = nedgradering av grupptillstånd misslyckades
err_perm_user = misslyckades att nergradera användarbehörigheter err_perm_user = nedgradering av användartillstånd misslyckades
err_pwnam = misslyckades att hämta användarinfo err_pwnam = hämtning av användarinformation misslyckades
err_sleep = strömsparlägets kommando misslyckades
err_start = startkommando misslyckades
err_battery = hämtning av batteristatus misslyckades
err_switch_tty = byte av tty misslyckades
err_tty_ctrl = överföring av tty-kontroll misslyckades
err_no_users = inga användare hittades
err_uid_range = dynamisk hämtning av uid-intervall misslyckades
err_user_gid = misslyckades att ställa in användar-GID err_user_gid = inställning av användarens GID misslyckades
err_user_init = misslyckades att initialisera användaren err_user_init = initiering av användare misslyckades
err_user_uid = misslyckades att ställa in användar-UID err_user_uid = inställning av användarens UID misslyckades
err_xauth = xauth-kommando misslyckades
err_xcb_conn = xcb-anslutning misslyckades
err_xsessions_dir = misslyckades att hitta sessionskatalog err_xsessions_dir = sessionskatalog hittades inte
err_xsessions_open = misslyckades att öppna sessionskatalog err_xsessions_open = öppning av sessionskatalog misslyckades
hibernate = viloläge
insert = infoga
login = inloggning login = inloggning
logout = utloggad logout = utloggad
no_x11_support = x11-stöd inaktiverat vid kompilering
normal = normal
numlock = numlock numlock = numlock
other = övrig
password = lösenord password = lösenord
restart = starta om restart = starta om
shell = skal shell = shell
shutdown = stäng av shutdown = stäng av
sleep = viloläge
toggle_password = visa/dölj lösenord
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = kimlik doğrulanıyor...
brightness_down = parlakligi azalt
brightness_up = parlakligi arttir
capslock = capslock capslock = capslock
custom = özel
custom_info_err_output_long = çıktı çok uzun
custom_info_err_no_output = çıktı yok
custom_info_err_no_output_error = , olası hata
err_alloc = basarisiz bellek ayirma err_alloc = basarisiz bellek ayirma
err_args = komut satırı argümanları ayrıştırılamıyor
err_autologin_session = otomatik oturum açma oturumu bulunamadı
err_bounds = sinirlarin disinda dizin err_bounds = sinirlarin disinda dizin
err_brightness_change = parlaklık değiştirilemedi
err_chdir = ev klasoru acilamadi err_chdir = ev klasoru acilamadi
err_clock_too_long = saat dizesi çok uzun
err_config = yapılandırma dosyası ayrıştırılamıyor
err_crawl = oturum dizinleri taranamadı
err_dgn_oob = log mesaji err_dgn_oob = log mesaji
err_domain = gecersiz etki alani err_domain = gecersiz etki alani
err_empty_password = boş parola kullanılamaz
err_envlist = ortam değişkenleri listesi alınamadı
err_get_active_tty = aktif tty alınamadı
err_hibernate = hazırda bekletme komutu çalıştırılamadı
err_hostname = ana bilgisayar adi alinamadi err_hostname = ana bilgisayar adi alinamadi
err_inactivity = hareketsizlik komutu çalıştırılamadı
err_lock_state = kilit durumu alınamadı
err_log = günlük dosyasıılamadı
err_mlock = parola bellegi kilitlenemedi err_mlock = parola bellegi kilitlenemedi
err_null = bos isaretci hatasi err_null = bos isaretci hatasi
err_numlock = numlock ayarlanamadı
err_pam = pam islemi basarisiz oldu err_pam = pam islemi basarisiz oldu
err_pam_abort = pam islemi durduruldu err_pam_abort = pam islemi durduruldu
err_pam_acct_expired = hesabin suresi dolmus err_pam_acct_expired = hesabin suresi dolmus
@ -46,33 +49,34 @@ err_perm_dir = gecerli dizin degistirilemedi
err_perm_group = grup izinleri dusurulemedi err_perm_group = grup izinleri dusurulemedi
err_perm_user = kullanici izinleri dusurulemedi err_perm_user = kullanici izinleri dusurulemedi
err_pwnam = kullanici bilgileri alinamadi err_pwnam = kullanici bilgileri alinamadi
err_sleep = uyku komutu çalıştırılamadı
err_start = başlatma komutu çalıştırılamadı
err_battery = pil durumu yüklenemedi
err_switch_tty = tty değiştirilemedi
err_tty_ctrl = tty kontrol aktarımı başarısız oldu
err_no_users = kullanıcı bulunamadı
err_uid_range = uid aralığı dinamik olarak alınamadı
err_user_gid = kullanici icin GID ayarlanamadi err_user_gid = kullanici icin GID ayarlanamadi
err_user_init = kullanici oturumu baslatilamadi err_user_init = kullanici oturumu baslatilamadi
err_user_uid = kullanici icin UID ayarlanamadi err_user_uid = kullanici icin UID ayarlanamadi
err_xauth = xauth komutu başarısız oldu
err_xcb_conn = xcb bağlantısı başarısız oldu
err_xsessions_dir = oturumlar klasoru bulunamadi err_xsessions_dir = oturumlar klasoru bulunamadi
err_xsessions_open = oturumlar klasoru acilamadi err_xsessions_open = oturumlar klasoru acilamadi
hibernate = askiya al
insert = ekle
login = kullanici login = kullanici
logout = oturumdan cikis yapildi logout = oturumdan cikis yapildi
no_x11_support = x11 desteği derleme zamanında devre dışı bırakıldı
normal = normal
numlock = numlock numlock = numlock
other = baska
password = sifre password = sifre
restart = yeniden baslat restart = yeniden baslat
shell = shell shell = shell
shutdown = makineyi kapat shutdown = makineyi kapat
sleep = uykuya al
toggle_password = parolayı göster/gizle
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = автентифікація...
brightness_down = зменшити яскравість
brightness_up = збільшити яскравість
capslock = capslock capslock = capslock
custom = власний
custom_info_err_output_long = вивід занадто довгий
custom_info_err_no_output = немає виводу
custom_info_err_no_output_error = , можлива помилка
err_alloc = невдале виділення пам'яті err_alloc = невдале виділення пам'яті
err_args = не вдалося розібрати аргументи командного рядка
err_autologin_session = сеанс автоматичного входу не знайдено
err_bounds = поза межами індексу err_bounds = поза межами індексу
err_brightness_change = не вдалося змінити яскравість
err_chdir = не вдалося відкрити домашній каталог err_chdir = не вдалося відкрити домашній каталог
err_clock_too_long = рядок годинника занадто довгий
err_config = не вдалося розібрати файл конфігурації
err_crawl = не вдалося сканувати каталоги сесій
err_dgn_oob = повідомлення журналу (log) err_dgn_oob = повідомлення журналу (log)
err_domain = недійсний домен err_domain = недійсний домен
err_empty_password = порожній пароль не дозволено
err_envlist = не вдалося отримати список змінних середовища
err_get_active_tty = не вдалося отримати активний tty
err_hibernate = не вдалося виконати команду гібернації
err_hostname = не вдалося отримати ім'я хосту err_hostname = не вдалося отримати ім'я хосту
err_inactivity = не вдалося виконати команду неактивності
err_lock_state = не вдалося отримати стан блокування
err_log = не вдалося відкрити файл журналу
err_mlock = збій блокування пам'яті err_mlock = збій блокування пам'яті
err_null = нульовий вказівник err_null = нульовий вказівник
err_numlock = не вдалося встановити numlock
err_pam = невдала pam транзакція err_pam = невдала pam транзакція
err_pam_abort = pam транзакція перервана err_pam_abort = pam транзакція перервана
err_pam_acct_expired = термін дії акаунту вичерпано err_pam_acct_expired = термін дії акаунту вичерпано
@ -46,33 +49,34 @@ err_perm_dir = не вдалося змінити поточний катало
err_perm_group = не вдалося понизити права доступу групи err_perm_group = не вдалося понизити права доступу групи
err_perm_user = не вдалося понизити права доступу користувача err_perm_user = не вдалося понизити права доступу користувача
err_pwnam = не вдалося отримати дані користувача 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_gid = не вдалося змінити GID користувача
err_user_init = не вдалося ініціалізувати користувача err_user_init = не вдалося ініціалізувати користувача
err_user_uid = не вдалося змінити UID користувача err_user_uid = не вдалося змінити UID користувача
err_xauth = команда xauth не вдалася
err_xcb_conn = з'єднання xcb не вдалося
err_xsessions_dir = не вдалося знайти каталог сесій err_xsessions_dir = не вдалося знайти каталог сесій
err_xsessions_open = не вдалося відкрити каталог сесій err_xsessions_open = не вдалося відкрити каталог сесій
hibernate = гібернація
insert = вставити
login = логін login = логін
logout = вийти logout = вийти
no_x11_support = підтримку x11 вимкнено під час компіляції
normal = нормальний
numlock = numlock numlock = numlock
other = інший
password = пароль password = пароль
restart = перезавантажити restart = перезавантажити
shell = оболонка shell = оболонка
shutdown = вимкнути shutdown = вимкнути
sleep = сплячий режим
toggle_password = показати/приховати пароль
wayland = wayland wayland = wayland
x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

View File

@ -1,30 +1,33 @@
authenticating = 正在认证...
brightness_down = 降低亮度
brightness_up = 提高亮度
capslock = 大写锁定 capslock = 大写锁定
custom = 自定义
custom_info_err_output_long = 输出过长
custom_info_err_no_output = 无输出
custom_info_err_no_output_error = ,可能有错误
err_alloc = 内存分配失败 err_alloc = 内存分配失败
err_args = 无法解析命令行参数
err_autologin_session = 未找到自动登录会话
err_bounds = 索引越界 err_bounds = 索引越界
err_brightness_change = 无法更改亮度
err_chdir = 无法打开home文件夹 err_chdir = 无法打开home文件夹
err_clock_too_long = 时钟字符串过长
err_config = 无法解析配置文件
err_crawl = 无法扫描会话目录
err_dgn_oob = 日志消息 err_dgn_oob = 日志消息
err_domain = 无效的域 err_domain = 无效的域
err_empty_password = 不允许空密码
err_envlist = 无法获取环境变量列表
err_get_active_tty = 无法获取当前活动的tty
err_hibernate = 无法执行休眠命令
err_hostname = 获取主机名失败 err_hostname = 获取主机名失败
err_inactivity = 无法执行非活动命令
err_lock_state = 无法获取锁定状态
err_log = 无法打开日志文件
err_mlock = 锁定密码存储器失败 err_mlock = 锁定密码存储器失败
err_null = 空指针 err_null = 空指针
err_numlock = 无法设置numlock
err_pam = PAM事件失败 err_pam = PAM事件失败
err_pam_abort = PAM事务已中止 err_pam_abort = PAM事务已中止
err_pam_acct_expired = 帐户已过期 err_pam_acct_expired = 帐户已过期
@ -46,33 +49,34 @@ err_perm_dir = 更改当前目录失败
err_perm_group = 组权限降级失败 err_perm_group = 组权限降级失败
err_perm_user = 用户权限降级失败 err_perm_user = 用户权限降级失败
err_pwnam = 获取用户信息失败 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_gid = 设置用户GID失败
err_user_init = 初始化用户失败 err_user_init = 初始化用户失败
err_user_uid = 设置用户UID失败 err_user_uid = 设置用户UID失败
err_xauth = xauth命令失败
err_xcb_conn = xcb连接失败
err_xsessions_dir = 找不到会话文件夹 err_xsessions_dir = 找不到会话文件夹
err_xsessions_open = 无法打开会话文件夹 err_xsessions_open = 无法打开会话文件夹
hibernate = 休眠
insert = 插入
login = 登录 login = 登录
logout = 注销 logout = 注销
no_x11_support = x11支持在编译时被禁用
normal = 正常
numlock = 数字锁定 numlock = 数字锁定
other = 其他
password = 密码 password = 密码
restart = 重启
shell = shell shell = shell
shutdown = 关机
sleep = 睡眠
toggle_password = 显示/隐藏密码
wayland = wayland wayland = wayland
x11 = x11 x11 = x11
xinitrc = xinitrc xinitrc = xinitrc

82
res/lang/zh_TW.ini Normal file
View File

@ -0,0 +1,82 @@
authenticating = 正在驗證...
brightness_down = 降低亮度
brightness_up = 提高亮度
capslock = 大寫鎖定
custom = 自訂
custom_info_err_output_long = 輸出過長
custom_info_err_no_output = 無輸出
custom_info_err_no_output_error = ,可能有錯誤
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 = 無法設定 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_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 = 數字鎖定
other = 其他
password = 密碼
restart = 重新啟動
shell = shell
shutdown = 關機
sleep = 睡眠
toggle_password = 顯示/隱藏密碼
wayland = wayland
x11 = x11
xinitrc = xinitrc

17
res/ly-kmsconvt@.service Normal file
View File

@ -0,0 +1,17 @@
[Unit]
Description=TUI display manager using KMSCON
After=systemd-user-sessions.service plymouth-quit-wait.service
After=kmsconvt@%i.service
Conflicts=kmsconvt@%i.service
[Service]
ExecStart=$PREFIX_DIRECTORY/bin/kmscon --term=linux --font-engine unifont --vt=%I --login -- $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME --use-kmscon-vt
StandardInput=tty
UtmpIdentifier=%I
TTYPath=/dev/%I
TTYReset=yes
TTYVHangup=yes
TTYVTDisallocate=yes
[Install]
WantedBy=multi-user.target

View File

@ -1,8 +1,8 @@
[Unit] [Unit]
Description=TUI display manager Description=TUI display manager
After=systemd-user-sessions.service plymouth-quit-wait.service After=systemd-user-sessions.service plymouth-quit-wait.service
After=getty@%I.service After=getty@%i.service
Conflicts=getty@%I.service Conflicts=getty@%i.service
[Service] [Service]
Type=idle Type=idle

37
res/startup.sh Executable file
View File

@ -0,0 +1,37 @@
#!/bin/sh
# This file is executed when starting Ly (before the TTY is taken control of)
# Custom startup code can be placed in this file or the start_cmd var can be pointed to a different file
# Uncomment the example below for an example of changing the default TTY colors to an alternitive palette on linux
# Colors are in red/green/blue hex (the current colors are a brighter palette than default)
#
# if [ "$TERM" = "linux" ]; then
# BLACK="232323"
# DARK_RED="D75F5F"
# DARK_GREEN="87AF5F"
# DARK_YELLOW="D7AF87"
# DARK_BLUE="8787AF"
# DARK_MAGENTA="BD53A5"
# DARK_CYAN="5FAFAF"
# LIGHT_GRAY="E5E5E5"
# DARK_GRAY="2B2B2B"
# RED="E33636"
# GREEN="98E34D"
# YELLOW="FFD75F"
# BLUE="7373C9"
# MAGENTA="D633B2"
# CYAN="44C9C9"
# WHITE="FFFFFF"
# COLORS="${BLACK} ${DARK_RED} ${DARK_GREEN} ${DARK_YELLOW} ${DARK_BLUE} ${DARK_MAGENTA} ${DARK_CYAN} ${LIGHT_GRAY} ${DARK_GRAY} ${RED} ${GREEN} ${YELLOW} ${BLUE} ${MAGENTA} ${CYAN} ${WHITE}"
# i=0
# while [ $i -lt 16 ]; do
# printf "\033]P%x%s" ${i} "$(echo "$COLORS" | cut -d ' ' -f$(( i + 1)))"
# i=$(( i + 1 ))
# done
# clear # for fixing background artifacting after changing color
# fi

View File

@ -1,9 +1,9 @@
const enums = @import("enums.zig"); const ini = @import("ly-ui").ly_core.ini;
const ini = @import("zigini");
const DisplayServer = enums.DisplayServer;
const Ini = ini.Ini; const Ini = ini.Ini;
const enums = @import("enums.zig");
const DisplayServer = enums.DisplayServer;
pub const DesktopEntry = struct { pub const DesktopEntry = struct {
Exec: []const u8 = "", Exec: []const u8 = "",
Name: []const u8 = "", Name: []const u8 = "",
@ -14,9 +14,9 @@ pub const DesktopEntry = struct {
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} }; pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
entry_ini: ?Ini(Entry) = null, entry_ini: ?Ini(Entry) = null,
file_name: []const u8 = "",
name: []const u8 = "", name: []const u8 = "",
xdg_session_desktop: ?[]const u8 = null, xdg_session_desktop: ?[]const u8 = null,
xdg_session_desktop_owned: bool = false,
xdg_desktop_names: ?[]const u8 = null, xdg_desktop_names: ?[]const u8 = null,
cmd: ?[]const u8 = null, cmd: ?[]const u8 = null,
specifier: []const u8 = "", specifier: []const u8 = "",

View File

@ -1,51 +0,0 @@
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

@ -1,39 +0,0 @@
const std = @import("std");
const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror));
const ErrorHandler = packed struct {
has_error: bool = false,
err_int: ErrInt = 0,
};
const SharedError = @This();
data: []align(std.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);
return .{ .data = data };
}
pub fn deinit(self: *SharedError) void {
std.posix.munmap(self.data);
}
pub fn writeError(self: SharedError, err: anyerror) void {
var buf_stream = std.io.fixedBufferStream(self.data);
const writer = buf_stream.writer();
writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }) catch {};
}
pub fn readError(self: SharedError) ?anyerror {
var buf_stream = std.io.fixedBufferStream(self.data);
const reader = buf_stream.reader();
const err_handler = try reader.readStruct(ErrorHandler);
if (err_handler.has_error)
return @errorFromInt(err_handler.err_int);
return null;
}

View File

@ -0,0 +1,92 @@
const std = @import("std");
const math = std.math;
const ly_ui = @import("ly-ui");
const Cell = ly_ui.Cell;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const Cascade = @This();
io: std.Io,
instance: ?Widget = null,
buffer: *TerminalBuffer,
current_auth_fails: *u64,
max_auth_fails: u64,
pub fn init(
io: std.Io,
buffer: *TerminalBuffer,
current_auth_fails: *u64,
max_auth_fails: u64,
) Cascade {
return .{
.io = io,
.instance = null,
.buffer = buffer,
.current_auth_fails = current_auth_fails,
.max_auth_fails = max_auth_fails,
};
}
pub fn widget(self: *Cascade) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Cascade",
null,
self,
null,
null,
draw,
null,
null,
null,
);
return &self.instance.?;
}
fn draw(self: *Cascade) void {
while (self.current_auth_fails.* >= self.max_auth_fails) {
self.io.sleep(.fromMilliseconds(10), .real) catch {};
var changed = false;
var y = self.buffer.height - 2;
while (y > 0) : (y -= 1) {
for (0..self.buffer.width) |x| {
const cell = TerminalBuffer.getCell(x, y - 1);
const cell_under = TerminalBuffer.getCell(x, y);
// 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.buffer.random.int(u16) % 10) > 7) continue;
cell.?.put(x, y) catch {};
var space = Cell.init(
' ',
cell_under.?.fg,
cell_under.?.bg,
);
space.put(x, y - 1) catch {};
}
}
if (!changed) {
self.io.sleep(.fromSeconds(7), .real) catch {};
self.current_auth_fails.* = 0;
}
TerminalBuffer.presentBuffer() catch {};
}
}

View File

@ -1,11 +1,17 @@
const std = @import("std"); const std = @import("std");
const Animation = @import("../tui/Animation.zig"); const math = std.math;
const Cell = @import("../tui/Cell.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const ly_ui = @import("ly-ui");
const Cell = ly_ui.Cell;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const ly_core = ly_ui.ly_core;
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const ColorMix = @This(); const ColorMix = @This();
const math = std.math;
const Vec2 = @Vector(2, f32); const Vec2 = @Vector(2, f32);
const time_scale: f32 = 0.01; const time_scale: f32 = 0.01;
@ -15,15 +21,33 @@ fn length(vec: Vec2) f32 {
return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]); return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
} }
instance: ?Widget = null,
start_time: TimeOfDay,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
frames: u64, frames: u64,
pattern_cos_mod: f32, pattern_cos_mod: f32,
pattern_sin_mod: f32, pattern_sin_mod: f32,
palette: [palette_len]Cell, palette: [palette_len]Cell,
pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) ColorMix { pub fn init(
terminal_buffer: *TerminalBuffer,
col1: u32,
col2: u32,
col3: u32,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
) !ColorMix {
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(),
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.animate = animate,
.timeout_sec = timeout_sec,
.frame_delay = frame_delay,
.frames = 0, .frames = 0,
.pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.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, .pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
@ -44,15 +68,25 @@ pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) C
}; };
} }
pub fn animation(self: *ColorMix) Animation { pub fn widget(self: *ColorMix) *Widget {
return Animation.init(self, deinit, realloc, draw); if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"ColorMix",
null,
self,
null,
null,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
} }
fn deinit(_: *ColorMix) void {}
fn realloc(_: *ColorMix) anyerror!void {}
fn draw(self: *ColorMix) void { fn draw(self: *ColorMix) void {
if (!self.animate.*) return;
self.frames +%= 1; self.frames +%= 1;
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale; const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;
@ -79,8 +113,20 @@ fn draw(self: *ColorMix) void {
uv -= @splat(1.0 * math.cos(uv[0] + uv[1]) - math.sin(uv[0] * 0.7 - uv[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]; const cell = self.palette[@as(usize, @trunc(math.floor(length(uv) * 5.0))) % palette_len];
cell.put(x, y); cell.put(x, y) catch {};
} }
} }
} }
fn update(self: *ColorMix, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn calculateTimeout(self: *ColorMix, _: *anyopaque) !?usize {
return self.frame_delay;
}

View File

@ -1,8 +1,14 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const Animation = @import("../tui/Animation.zig");
const Cell = @import("../tui/Cell.zig"); const ly_ui = @import("ly-ui");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const Cell = ly_ui.Cell;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const ly_core = ly_ui.ly_core;
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const Doom = @This(); const Doom = @This();
@ -10,14 +16,30 @@ pub const STEPS = 12;
pub const HEIGHT_MAX = 9; pub const HEIGHT_MAX = 9;
pub const SPREAD_MAX = 4; pub const SPREAD_MAX = 4;
instance: ?Widget = null,
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
buffer: []u8, buffer: []u8,
height: u8, height: u8,
spread: u8, spread: u8,
fire: [STEPS + 1]Cell, fire: [STEPS + 1]Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32, fire_height: u8, fire_spread: u8) !Doom { pub fn init(
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
top_color: u32,
middle_color: u32,
bottom_color: u32,
fire_height: u8,
fire_spread: u8,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height); const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width); initBuffer(buffer, terminal_buffer.width);
@ -39,8 +61,13 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
}; };
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.animate = animate,
.timeout_sec = timeout_sec,
.frame_delay = frame_delay,
.buffer = buffer, .buffer = buffer,
.height = @min(HEIGHT_MAX, fire_height), .height = @min(HEIGHT_MAX, fire_height),
.spread = @min(SPREAD_MAX, fire_spread), .spread = @min(SPREAD_MAX, fire_spread),
@ -48,21 +75,35 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
}; };
} }
pub fn animation(self: *Doom) Animation { pub fn widget(self: *Doom) *Widget {
return Animation.init(self, deinit, realloc, draw); if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Doom",
null,
self,
deinit,
realloc,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
} }
fn deinit(self: *Doom) void { fn deinit(self: *Doom) void {
self.allocator.free(self.buffer); self.allocator.free(self.buffer);
} }
fn realloc(self: *Doom) anyerror!void { fn realloc(self: *Doom) !void {
const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height); const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height);
initBuffer(buffer, self.terminal_buffer.width); initBuffer(buffer, self.terminal_buffer.width);
self.buffer = buffer; self.buffer = buffer;
} }
fn draw(self: *Doom) void { fn draw(self: *Doom) void {
if (!self.animate.*) return;
for (0..self.terminal_buffer.width) |x| { for (0..self.terminal_buffer.width) |x| {
// We start from 1 so that we always have the topmost line when spreading fire // We start from 1 so that we always have the topmost line when spreading fire
for (1..self.terminal_buffer.height) |y| { for (1..self.terminal_buffer.height) |y| {
@ -88,13 +129,13 @@ fn draw(self: *Doom) void {
// Send known fire levels to terminal buffer // Send known fire levels to terminal buffer
const from_cell = self.fire[level_buf_from]; const from_cell = self.fire[level_buf_from];
const to_cell = self.fire[level_buf_to]; const to_cell = self.fire[level_buf_to];
from_cell.put(x, y); from_cell.put(x, y) catch {};
to_cell.put(to_x, to_y); to_cell.put(to_x, to_y) catch {};
} }
// Draw bottom line (fire source) // Draw bottom line (fire source)
const src_cell = self.fire[STEPS]; const src_cell = self.fire[STEPS];
src_cell.put(x, self.terminal_buffer.height - 1); src_cell.put(x, self.terminal_buffer.height - 1) catch {};
} }
} }
@ -108,3 +149,15 @@ fn initBuffer(buffer: []u8, width: usize) void {
@memset(slice_start, 0); @memset(slice_start, 0);
@memset(slice_end, STEPS); @memset(slice_end, STEPS);
} }
fn update(self: *Doom, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn calculateTimeout(self: *Doom, _: *anyopaque) !?usize {
return self.frame_delay;
}

View File

@ -1,14 +0,0 @@
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 {}

View File

@ -1,32 +1,23 @@
const std = @import("std"); 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 Allocator = std.mem.Allocator;
const Json = std.json; const Json = std.json;
const eql = std.mem.eql; const eql = std.mem.eql;
const flate = std.compress.flate; const flate = std.compress.flate;
fn read_decompress_file(allocator: Allocator, file_path: []const u8) ![]u8 { const ly_ui = @import("ly-ui");
const file_buffer = std.fs.cwd().openFile(file_path, .{}) catch { const Cell = ly_ui.Cell;
return error.FileNotFound; const TerminalBuffer = ly_ui.TerminalBuffer;
}; const Color = TerminalBuffer.Color;
defer file_buffer.close(); const Styling = TerminalBuffer.Styling;
const Widget = ly_ui.Widget;
var file_reader_buffer: [4096]u8 = undefined; const ly_core = ly_ui.ly_core;
var decompress_buffer: [flate.max_window_len]u8 = undefined; const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const LogFile = ly_core.LogFile;
var file_reader = file_buffer.reader(&file_reader_buffer); const enums = @import("../enums.zig");
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer); const DurOffsetAlignment = enums.DurOffsetAlignment;
const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch {
return error.NotValidFile;
};
return file_decompressed;
}
const Frame = struct { const Frame = struct {
frameNumber: i32, frameNumber: i32,
@ -52,7 +43,7 @@ const Frame = struct {
}; };
// https://github.com/cmang/durdraw/blob/0.29.0/durformat.md // https://github.com/cmang/durdraw/blob/0.29.0/durformat.md
const DurFormat = struct { const DurFormatRaw = struct {
allocator: Allocator, allocator: Allocator,
formatVersion: ?i64 = null, formatVersion: ?i64 = null,
colorFormat: ?[]const u8 = null, colorFormat: ?[]const u8 = null,
@ -62,38 +53,48 @@ const DurFormat = struct {
lines: ?i64 = null, lines: ?i64 = null,
frames: std.ArrayList(Frame) = undefined, frames: std.ArrayList(Frame) = undefined,
pub fn valid(self: *DurFormat) bool { // Validate data and return a valid DurFormat
if (self.formatVersion != null and // Consumes `self`, making it unusable after
self.colorFormat != null and pub fn validate(self: *DurFormatRaw) !DurFormat {
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 // v8 may have breaking changes like changing the colormap xy direction
// (https://github.com/cmang/durdraw/issues/24) // (https://github.com/cmang/durdraw/issues/24)
if (self.formatVersion.? != 7) return false; const format_version = self.formatVersion orelse return error.MissingFieldVersion;
if (format_version != 7) return error.UnsupportedVersion;
const color_format_str = self.colorFormat orelse return error.MissingFieldColorFormat;
// Code currently only supports 16 and 256 color format only // Code currently only supports 16 and 256 color format only
if (!(eql(u8, "16", self.colorFormat.?) or eql(u8, "256", self.colorFormat.?))) const color_format: DurColorFormat =
return false; if (eql(u8, color_format_str, "16")) .@"16" else if (eql(u8, color_format_str, "256")) .@"256" else return error.UnsupportedColorFormat;
const encoding_str = self.encoding orelse return error.MissingFieldEncoding;
// Code currently supports only utf-8 encoding // Code currently supports only utf-8 encoding
if (!eql(u8, self.encoding.?, "utf-8")) return false; const encoding: DurEncoding = if (eql(u8, encoding_str, "utf-8")) .utf_8 else return error.UnsupportedEncoding;
if (self.framerate == null) return error.MissingFieldFramerate;
if (self.framerate.? <= 0) return error.InvalidFramerate;
const framerate: f64 = self.framerate.?;
// Sanity check on file // Sanity check on file
if (self.columns.? <= 0) return false; if (self.columns == null or self.lines == null) return error.MissingDimensions;
if (self.lines.? <= 0) return false; const columns = std.math.cast(u32, self.columns.?) orelse return error.InvalidColumnCount;
if (self.framerate.? < 0) return false; const lines = std.math.cast(u32, self.lines.?) orelse return error.InvalidLineCount;
return true; if (self.frames.items.len == 0) return error.NoFrames;
const frames = self.frames;
return .{
.allocator = self.allocator,
.formatVersion = format_version,
.colorFormat = color_format,
.encoding = encoding,
.framerate = framerate,
.columns = columns,
.lines = lines,
.frames = frames,
};
} }
return false; fn parseFromJson(self: *DurFormatRaw, allocator: Allocator, dur_json_root: Json.Value) !void {
}
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; 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) // Depending on the version, a dur file can have different json object names (ie: columns vs sizeX)
@ -140,28 +141,53 @@ const DurFormat = struct {
} }
} }
pub fn create_from_file(self: *DurFormat, allocator: Allocator, file_path: []const u8) !void { pub fn createFromFile(self: *DurFormatRaw, allocator: Allocator, io: std.Io, file_path: []const u8) !void {
const file_decompressed = try read_decompress_file(allocator, file_path); const file = try std.Io.Dir.cwd().openFile(io, file_path, .{});
defer allocator.free(file_decompressed); defer file.close(io);
const parsed = try Json.parseFromSlice(Json.Value, allocator, file_decompressed, .{}); var reader_buffer: [4096]u8 = undefined;
defer parsed.deinit(); var decompress_buffer: [flate.max_window_len]u8 = undefined;
try parse_dur_from_json(self, allocator, parsed.value); var file_reader = file.reader(io, &reader_buffer);
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer);
if (!self.valid()) { var json_reader = Json.Reader.init(allocator, &decompress.reader);
return error.NotValidFile; defer json_reader.deinit();
}
const json = try Json.parseFromTokenSource(Json.Value, allocator, &json_reader, .{});
defer json.deinit();
try parseFromJson(self, allocator, json.value);
} }
pub fn init(allocator: Allocator) DurFormat { pub fn init(allocator: Allocator) DurFormatRaw {
return .{ .allocator = allocator }; return .{ .allocator = allocator };
} }
pub fn deinit(self: *DurFormat) void { pub fn deinit(self: *DurFormatRaw) void {
if (self.colorFormat) |str| self.allocator.free(str); if (self.colorFormat) |str| self.allocator.free(str);
if (self.encoding) |str| self.allocator.free(str); if (self.encoding) |str| self.allocator.free(str);
}
};
const DurColorFormat = enum {
@"16",
@"256",
};
const DurEncoding = enum { utf_8 };
const DurFormat = struct {
allocator: Allocator,
formatVersion: i64,
colorFormat: DurColorFormat,
encoding: DurEncoding,
framerate: f64,
columns: u32,
lines: u32,
frames: std.ArrayList(Frame),
pub fn deinit(self: *DurFormat) void {
for (self.frames.items) |frame| { for (self.frames.items) |frame| {
frame.deinit(self.allocator); frame.deinit(self.allocator);
} }
@ -230,7 +256,7 @@ const durcolor_table_to_color16 = [17]u32{
15, // 16 bright white 15, // 16 bright white
}; };
fn sixcube_to_channel(sixcube: u32) u32 { fn sixCubeToChannel(sixcube: u32) u32 {
// Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF, // Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF,
// so we use 0xF0 instead with a scaler // so we use 0xF0 instead with a scaler
const equal_divisions = 0xF0 / 6; const equal_divisions = 0xF0 / 6;
@ -241,7 +267,7 @@ fn sixcube_to_channel(sixcube: u32) u32 {
return if (sixcube > 0) (sixcube * equal_divisions) + scaler else 0; return if (sixcube > 0) (sixcube * equal_divisions) + scaler else 0;
} }
fn convert_256_to_rgb(color_256: u32) u32 { fn convert256ToRgb(color_256: u32) u32 {
var rgb_color: u32 = 0; var rgb_color: u32 = 0;
// 0 - 15 is the standard color range, map to array table // 0 - 15 is the standard color range, map to array table
@ -257,9 +283,9 @@ fn convert_256_to_rgb(color_256: u32) u32 {
// divide by 1 gets the height of the cube (divide 1 for clarity for what we are doing) // 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 // 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) // 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 |= sixCubeToChannel(((color_256 - 16) / 36) % 6) << 16;
rgb_color |= sixcube_to_channel(((color_256 - 16) / 6) % 6) << 8; rgb_color |= sixCubeToChannel(((color_256 - 16) / 6) % 6) << 8;
rgb_color |= sixcube_to_channel(((color_256 - 16) / 1) % 6); rgb_color |= sixCubeToChannel(((color_256 - 16) / 1) % 6);
} }
// 232 - 255 is the grayscale range // 232 - 255 is the grayscale range
else { else {
@ -286,94 +312,213 @@ fn convert_256_to_rgb(color_256: u32) u32 {
return rgb_color; return rgb_color;
} }
const UVec2 = @Vector(2, u32);
const IVec2 = @Vector(2, i64);
const VEC_X = 0;
const VEC_Y = 1;
const DurFile = @This(); const DurFile = @This();
instance: ?Widget = null,
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
io: std.Io,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
frames: u64,
time_previous: i64,
x_offset: u32,
y_offset: u32,
full_color: bool,
dur_movie: DurFormat, dur_movie: DurFormat,
frame_width: u32, frames: usize,
frame_height: u32, start_pos: IVec2,
full_color: bool,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
frame_time: u32, frame_time: u32,
time_previous: i64,
is_color_format_16: bool, is_color_format_16: bool,
offset_alignment: DurOffsetAlignment,
offset: IVec2,
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 { // if the user has an even number of columns or rows, we will default to the left or higher position (e.g. 4 columns center = .x..)
var dur_movie: DurFormat = .init(allocator); fn center(v: i64) i64 {
return @intCast(@divTrunc(v, 2) + @mod(v, 2));
}
// error state is recoverable when thrown to main and results in no background with Dummy in main fn calculateStartPos(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat, offset_alignment: DurOffsetAlignment, offset: IVec2) IVec2 {
dur_movie.create_from_file(allocator, file_path) catch |err| switch (err) { const buf_width: i64 = @intCast(terminal_buffer.width);
const buf_height: i64 = @intCast(terminal_buffer.height);
const movie_width: i64 = @intCast(dur_movie.columns);
const movie_height: i64 = @intCast(dur_movie.lines);
const start_pos: IVec2 = switch (offset_alignment) {
DurOffsetAlignment.center => .{ center(buf_width) - center(movie_width), center(buf_height) - center(movie_height) },
DurOffsetAlignment.topleft => .{ 0, 0 },
DurOffsetAlignment.topcenter => .{ center(buf_width) - center(movie_width), 0 },
DurOffsetAlignment.topright => .{ buf_width - movie_width, 0 },
DurOffsetAlignment.centerleft => .{ 0, center(buf_height) - center(movie_height) },
DurOffsetAlignment.centerright => .{ buf_width - movie_width, center(buf_height) - center(movie_height) },
DurOffsetAlignment.bottomleft => .{ 0, buf_height - movie_height },
DurOffsetAlignment.bottomcenter => .{ center(buf_width) - center(movie_width), buf_height - movie_height },
DurOffsetAlignment.bottomright => .{ buf_width - movie_width, buf_height - movie_height },
};
return start_pos + offset;
}
pub fn init(
allocator: Allocator,
io: std.Io,
terminal_buffer: *TerminalBuffer,
log_file: *LogFile,
file_path: []const u8,
offset_alignment: DurOffsetAlignment,
x_offset: i32,
y_offset: i32,
full_color: bool,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
) !DurFile {
var dur_movie_raw: DurFormatRaw = .init(allocator);
defer dur_movie_raw.deinit();
dur_movie_raw.createFromFile(allocator, io, file_path) catch |err| switch (err) {
error.FileNotFound => { error.FileNotFound => {
try log_writer.print("error: dur_file was not found at: {s}\n", .{file_path}); try log_file.err(io, "tui", "dur_file was not found at: {s}", .{file_path});
return err; return err;
}, },
error.NotValidFile => { error.NotValidFile => {
try log_writer.print("error: dur_file loaded was invalid or not a dur file!\n", .{}); try log_file.err(io, "tui", "dur_file loaded was invalid or not a dur file!", .{});
return err; return err;
}, },
else => return err, else => return err,
}; };
var dur_movie = dur_movie_raw.validate() catch |err| switch (err) {
error.MissingFieldVersion => {
try log_file.err(io, "tui", "dur_file loaded was invalid: missing field formatVersion!", .{});
return err;
},
error.UnsupportedVersion => {
try log_file.err(io, "tui", "dur_file loaded was invalid: unsupported version ({d})!", .{dur_movie_raw.formatVersion.?});
return err;
},
error.MissingFieldColorFormat => {
try log_file.err(io, "tui", "dur_file loaded was invalid: missing field colorFormat!", .{});
return err;
},
error.UnsupportedColorFormat => {
try log_file.err(io, "tui", "dur_file loaded was invalid: unsupported colorFormat ({s})!", .{dur_movie_raw.colorFormat.?});
return err;
},
error.MissingFieldEncoding => {
try log_file.err(io, "tui", "dur_file loaded was invalid: missing field encoding!", .{});
return err;
},
error.UnsupportedEncoding => {
try log_file.err(io, "tui", "dur_file loaded was invalid: unsupported encoding ({s})!", .{dur_movie_raw.encoding.?});
return err;
},
error.MissingFieldFramerate => {
try log_file.err(io, "tui", "dur_file loaded was invalid: missing field framerate!", .{});
return err;
},
error.InvalidFramerate => {
try log_file.err(io, "tui", "dur_file loaded was invalid: negative framerate value found!", .{});
return err;
},
error.MissingDimensions => {
try log_file.err(io, "tui", "dur_file loaded was invalid: missing field(s) lines and/or columns!", .{});
return err;
},
error.InvalidColumnCount => {
try log_file.err(io, "tui", "dur_file loaded was invalid: columns value falls outside of supported range ({d})!", .{dur_movie_raw.columns.?});
return err;
},
error.InvalidLineCount => {
try log_file.err(io, "tui", "dur_file loaded was invalid: lines value falls outside of supported range ({d})!", .{dur_movie_raw.lines.?});
return err;
},
error.NoFrames => {
try log_file.err(io, "tui", "dur_file loaded was invalid: animation has no frames!", .{});
return err;
},
};
// 4 bit mode with 256 color is unsupported // 4 bit mode with 256 color is unsupported
if (!full_color and eql(u8, dur_movie.colorFormat.?, "256")) { if (!full_color and dur_movie.colorFormat == .@"256") {
try log_writer.print("error: dur_file can not be 256 color encoded when not using full_color option!\n", .{}); try log_file.err(io, "tui", "dur_file can not be 256 color encoded when not using full_color option!", .{});
dur_movie.deinit(); dur_movie.deinit();
return error.InvalidColorFormat; return error.NotFullColor;
} }
const buf_width: u32 = @intCast(terminal_buffer.width); const offset: IVec2 = .{ x_offset, y_offset };
const buf_height: u32 = @intCast(terminal_buffer.height);
const movie_width: u32 = @intCast(dur_movie.columns.?); const start_pos = calculateStartPos(terminal_buffer, &dur_movie, offset_alignment, offset);
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 // Convert dur fps to frames per ms
const frame_time: u32 = @intFromFloat(1000 / dur_movie.framerate.?); const frame_time: u32 = @trunc(1000 / dur_movie.framerate);
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.io = io,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.frames = 0, .frames = 0,
.time_previous = std.time.milliTimestamp(), .time_previous = std.Io.Timestamp.now(io, .real).toMilliseconds(),
.x_offset = x_offset_clamped, .start_pos = start_pos,
.y_offset = y_offset_clamped,
.full_color = full_color, .full_color = full_color,
.animate = animate,
.timeout_sec = timeout_sec,
.frame_delay = frame_delay,
.dur_movie = dur_movie, .dur_movie = dur_movie,
.frame_width = frame_width,
.frame_height = frame_height,
.frame_time = frame_time, .frame_time = frame_time,
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"), .is_color_format_16 = dur_movie.colorFormat == .@"16",
.offset_alignment = offset_alignment,
.offset = offset,
}; };
} }
pub fn animation(self: *DurFile) Animation { pub fn widget(self: *DurFile) *Widget {
return Animation.init(self, deinit, realloc, draw); if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"DurFile",
null,
self,
deinit,
realloc,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
} }
fn deinit(self: *DurFile) void { fn deinit(self: *DurFile) void {
self.dur_movie.deinit(); self.dur_movie.deinit();
} }
fn realloc(_: *DurFile) anyerror!void {} fn realloc(self: *DurFile) !void {
// when terminal size changes, we need to recalculate the start_pos based on the new size
self.start_pos = calculateStartPos(self.terminal_buffer, &self.dur_movie, self.offset_alignment, self.offset);
}
fn draw(self: *DurFile) void { fn draw(self: *DurFile) void {
if (!self.animate.*) return;
const current_frame = self.dur_movie.frames.items[self.frames]; const current_frame = self.dur_movie.frames.items[self.frames];
for (0..self.frame_height) |y| { // y is used as an iterator in the durformat, while cell_y gives us the correct placement for the cell (same for x)
for (0..@intCast(self.dur_movie.lines)) |y| {
const cell_y = @as(i32, @intCast(y)) + self.start_pos[VEC_Y];
var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator(); var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator();
for (0..self.frame_width) |x| { for (0..@intCast(self.dur_movie.columns)) |x| {
const cell_x = @as(i32, @intCast(x)) + self.start_pos[VEC_X];
const codepoint: u21 = iter.nextCodepoint().?; const codepoint: u21 = iter.nextCodepoint().?;
const color_map = current_frame.colorMap[x][y]; const color_map = current_frame.colorMap[x][y];
@ -385,20 +530,20 @@ fn draw(self: *DurFile) void {
color_map_1 = durcolor_table_to_color16[color_map_1 + 1]; // Add 1, dur source stores it like this for some reason 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 fg_color = if (self.full_color) convert256ToRgb(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 bg_color = if (self.full_color) convert256ToRgb(color_map_1) else tb_color_16[color_map_1];
const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color }; const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color };
cell.put(x + self.x_offset, y + self.y_offset); self.terminal_buffer.setCellBoundsChecked(cell_x, cell_y, cell) catch {};
} }
} }
const time_current = std.time.milliTimestamp(); const time_current = std.Io.Timestamp.now(self.io, .real).toMilliseconds();
const delta_time = time_current - self.time_previous; const delta_time = time_current - self.time_previous;
// Convert delay from sec to ms // Convert delay from sec to ms
const delay_time: u32 = @intFromFloat(current_frame.delay * 1000); const delay_time: u32 = @trunc(current_frame.delay * 1000);
if (delta_time > (self.frame_time + delay_time)) { if (delta_time > (self.frame_time + delay_time)) {
self.time_previous = time_current; self.time_previous = time_current;
@ -406,3 +551,15 @@ fn draw(self: *DurFile) void {
self.frames = (self.frames + 1) % frame_count; self.frames = (self.frames + 1) % frame_count;
} }
} }
fn update(self: *DurFile, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn calculateTimeout(self: *DurFile, _: *anyopaque) !?usize {
return self.frame_delay;
}

View File

@ -1,10 +1,15 @@
const std = @import("std"); 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 Allocator = std.mem.Allocator;
const ly_ui = @import("ly-ui");
const Cell = ly_ui.Cell;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const ly_core = ly_ui.ly_core;
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
const GameOfLife = @This(); const GameOfLife = @This();
// Visual styles - using block characters like other animations // Visual styles - using block characters like other animations
@ -16,6 +21,8 @@ const NEIGHBOR_DIRS = [_][2]i8{
.{ 1, 0 }, .{ 1, 1 }, .{ 1, 0 }, .{ 1, 1 },
}; };
instance: ?Widget = null,
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
current_grid: []bool, current_grid: []bool,
@ -26,11 +33,24 @@ fg_color: u32,
entropy_interval: usize, entropy_interval: usize,
frame_delay: usize, frame_delay: usize,
initial_density: f32, initial_density: f32,
animate: *bool,
timeout_sec: u12,
animation_frame_delay: u16,
dead_cell: Cell, dead_cell: Cell,
width: usize, width: usize,
height: usize, height: usize,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u32, entropy_interval: usize, frame_delay: usize, initial_density: f32) !GameOfLife { pub fn init(
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
fg_color: u32,
entropy_interval: usize,
frame_delay: usize,
initial_density: f32,
animate: *bool,
timeout_sec: u12,
animation_frame_delay: u16,
) !GameOfLife {
const width = terminal_buffer.width; const width = terminal_buffer.width;
const height = terminal_buffer.height; const height = terminal_buffer.height;
const grid_size = width * height; const grid_size = width * height;
@ -39,6 +59,8 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3
const next_grid = try allocator.alloc(bool, grid_size); const next_grid = try allocator.alloc(bool, grid_size);
var game = GameOfLife{ var game = GameOfLife{
.instance = null,
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.current_grid = current_grid, .current_grid = current_grid,
@ -49,6 +71,9 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3
.entropy_interval = entropy_interval, .entropy_interval = entropy_interval,
.frame_delay = frame_delay, .frame_delay = frame_delay,
.initial_density = initial_density, .initial_density = initial_density,
.animate = animate,
.timeout_sec = timeout_sec,
.animation_frame_delay = animation_frame_delay,
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg }, .dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
.width = width, .width = width,
.height = height, .height = height,
@ -60,8 +85,20 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_color: u3
return game; return game;
} }
pub fn animation(self: *GameOfLife) Animation { pub fn widget(self: *GameOfLife) *Widget {
return Animation.init(self, deinit, realloc, draw); if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"GameOfLife",
null,
self,
deinit,
realloc,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
} }
fn deinit(self: *GameOfLife) void { fn deinit(self: *GameOfLife) void {
@ -69,7 +106,7 @@ fn deinit(self: *GameOfLife) void {
self.allocator.free(self.next_grid); self.allocator.free(self.next_grid);
} }
fn realloc(self: *GameOfLife) anyerror!void { fn realloc(self: *GameOfLife) !void {
const new_width = self.terminal_buffer.width; const new_width = self.terminal_buffer.width;
const new_height = self.terminal_buffer.height; const new_height = self.terminal_buffer.height;
const new_size = new_width * new_height; const new_size = new_width * new_height;
@ -87,6 +124,8 @@ fn realloc(self: *GameOfLife) anyerror!void {
} }
fn draw(self: *GameOfLife) void { fn draw(self: *GameOfLife) void {
if (!self.animate.*) return;
// Update game state at controlled frame rate // Update game state at controlled frame rate
self.frame_counter += 1; self.frame_counter += 1;
if (self.frame_counter >= self.frame_delay) { if (self.frame_counter >= self.frame_delay) {
@ -107,11 +146,23 @@ fn draw(self: *GameOfLife) void {
const row_offset = y * self.width; const row_offset = y * self.width;
for (0..self.width) |x| { for (0..self.width) |x| {
const cell = if (self.current_grid[row_offset + x]) alive_cell else self.dead_cell; const cell = if (self.current_grid[row_offset + x]) alive_cell else self.dead_cell;
cell.put(x, y); cell.put(x, y) catch {};
} }
} }
} }
fn update(self: *GameOfLife, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn calculateTimeout(self: *GameOfLife, _: *anyopaque) !?usize {
return self.animation_frame_delay;
}
fn updateGeneration(self: *GameOfLife) void { fn updateGeneration(self: *GameOfLife) void {
// Conway's Game of Life rules with optimized neighbor counting // Conway's Game of Life rules with optimized neighbor counting
for (0..self.height) |y| { for (0..self.height) |y| {

View File

@ -1,11 +1,16 @@
const std = @import("std"); 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 Allocator = std.mem.Allocator;
const Random = std.Random; const Random = std.Random;
const ly_ui = @import("ly-ui");
const Cell = ly_ui.Cell;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const ly_core = ly_ui.ly_core;
const interop = ly_core.interop;
const TimeOfDay = interop.TimeOfDay;
pub const FRAME_DELAY: usize = 8; pub const FRAME_DELAY: usize = 8;
// Characters change mid-scroll // Characters change mid-scroll
@ -24,6 +29,8 @@ pub const Line = struct {
update: usize, update: usize,
}; };
instance: ?Widget = null,
start_time: TimeOfDay,
allocator: Allocator, allocator: Allocator,
terminal_buffer: *TerminalBuffer, terminal_buffer: *TerminalBuffer,
dots: []Dot, dots: []Dot,
@ -34,15 +41,30 @@ fg: u32,
head_col: u32, head_col: u32,
min_codepoint: u16, min_codepoint: u16,
max_codepoint: u16, max_codepoint: u16,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
default_cell: Cell, default_cell: Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16) !Matrix { pub fn init(
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
fg: u32,
head_col: u32,
min_codepoint: u16,
max_codepoint: u16,
animate: *bool,
timeout_sec: u12,
frame_delay: u16,
) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width); const lines = try allocator.alloc(Line, terminal_buffer.width);
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random); initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
return .{ return .{
.instance = null,
.start_time = try interop.getTimeOfDay(),
.allocator = allocator, .allocator = allocator,
.terminal_buffer = terminal_buffer, .terminal_buffer = terminal_buffer,
.dots = dots, .dots = dots,
@ -53,12 +75,27 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, hea
.head_col = head_col, .head_col = head_col,
.min_codepoint = min_codepoint, .min_codepoint = min_codepoint,
.max_codepoint = max_codepoint - min_codepoint, .max_codepoint = max_codepoint - min_codepoint,
.animate = animate,
.timeout_sec = timeout_sec,
.frame_delay = frame_delay,
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg }, .default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
}; };
} }
pub fn animation(self: *Matrix) Animation { pub fn widget(self: *Matrix) *Widget {
return Animation.init(self, deinit, realloc, draw); if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Matrix",
null,
self,
deinit,
realloc,
draw,
update,
null,
calculateTimeout,
);
return &self.instance.?;
} }
fn deinit(self: *Matrix) void { fn deinit(self: *Matrix) void {
@ -66,7 +103,7 @@ fn deinit(self: *Matrix) void {
self.allocator.free(self.lines); self.allocator.free(self.lines);
} }
fn realloc(self: *Matrix) anyerror!void { fn realloc(self: *Matrix) !void {
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1)); 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); const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);
@ -77,6 +114,8 @@ fn realloc(self: *Matrix) anyerror!void {
} }
fn draw(self: *Matrix) void { fn draw(self: *Matrix) void {
if (!self.animate.*) return;
const buf_height = self.terminal_buffer.height; const buf_height = self.terminal_buffer.height;
const buf_width = self.terminal_buffer.width; const buf_width = self.terminal_buffer.width;
self.count += 1; self.count += 1;
@ -86,17 +125,17 @@ fn draw(self: *Matrix) void {
self.count = 0; self.count = 0;
var x: usize = 0; var x: usize = 0;
while (x < self.terminal_buffer.width) : (x += 2) { while (x < buf_width) : (x += 2) {
var tail: usize = 0; var tail: usize = 0;
var line = &self.lines[x]; var line = &self.lines[x];
if (self.frame <= line.update) continue; if (self.frame <= line.update) continue;
if (self.dots[x].value == null and self.dots[self.terminal_buffer.width + x].value == ' ') { if (self.dots[x].value == null and self.dots[buf_width + x].value == ' ') {
if (line.space > 0) { if (line.space > 0) {
line.space -= 1; line.space -= 1;
} else { } else {
const randint = self.terminal_buffer.random.int(u16); const randint = self.terminal_buffer.random.int(u16);
const h = self.terminal_buffer.height; const h = buf_height;
line.length = @mod(randint, h - 3) + 3; line.length = @mod(randint, h - 3) + 3;
self.dots[x].value = @mod(randint, self.max_codepoint) + self.min_codepoint; self.dots[x].value = @mod(randint, self.max_codepoint) + self.min_codepoint;
line.space = @mod(randint, h + 1); line.space = @mod(randint, h + 1);
@ -153,7 +192,7 @@ fn draw(self: *Matrix) void {
var x: usize = 0; var x: usize = 0;
while (x < buf_width) : (x += 2) { while (x < buf_width) : (x += 2) {
var y: usize = 1; var y: usize = 1;
while (y <= self.terminal_buffer.height) : (y += 1) { while (y <= buf_height) : (y += 1) {
const dot = self.dots[buf_width * y + x]; const dot = self.dots[buf_width * y + x];
const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{ const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{
.ch = @intCast(dot.value.?), .ch = @intCast(dot.value.?),
@ -161,13 +200,25 @@ fn draw(self: *Matrix) void {
.bg = self.terminal_buffer.bg, .bg = self.terminal_buffer.bg,
}; };
cell.put(x, y - 1); cell.put(x, y - 1) catch {};
// Fill background in between columns // Fill background in between columns
self.default_cell.put(x + 1, y - 1); self.default_cell.put(x + 1, y - 1) catch {};
} }
} }
} }
fn update(self: *Matrix, _: *anyopaque) !void {
const time = try interop.getTimeOfDay();
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
self.animate.* = false;
}
}
fn calculateTimeout(self: *Matrix, _: *anyopaque) !?usize {
return self.frame_delay;
}
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void { fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
var y: usize = 0; var y: usize = 0;
while (y <= height) : (y += 1) { while (y <= height) : (y += 1) {

View File

@ -1,16 +1,17 @@
const std = @import("std"); 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 SharedError = @import("SharedError.zig");
const LogFile = @import("LogFile.zig");
const Md5 = std.crypto.hash.Md5; const Md5 = std.crypto.hash.Md5;
const builtin = @import("builtin");
const build_options = @import("build_options");
const ly_core = @import("ly-ui").ly_core;
const interop = ly_core.interop;
const SharedError = ly_core.SharedError;
const LogFile = ly_core.LogFile;
const utmp = interop.utmp; const utmp = interop.utmp;
const Utmp = utmp.utmpx; const Utmp = utmp.utmpx;
const Environment = @import("Environment.zig");
pub const AuthOptions = struct { pub const AuthOptions = struct {
tty: u8, tty: u8,
service_name: [:0]const u8, service_name: [:0]const u8,
@ -20,20 +21,22 @@ pub const AuthOptions = struct {
setup_cmd: []const u8, setup_cmd: []const u8,
login_cmd: ?[]const u8, login_cmd: ?[]const u8,
x_cmd: []const u8, x_cmd: []const u8,
x_vt: ?u8,
session_pid: std.posix.pid_t, session_pid: std.posix.pid_t,
use_kmscon_vt: bool,
}; };
var xorg_pid: std.posix.pid_t = 0; var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(i: c_int) callconv(.c) void { pub fn xorgSignalHandler(sig: std.posix.SIG) callconv(.c) void {
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i); if (xorg_pid > 0) _ = std.c.kill(xorg_pid, sig);
} }
var child_pid: std.posix.pid_t = 0; var child_pid: std.posix.pid_t = 0;
pub fn sessionSignalHandler(i: c_int) callconv(.c) void { pub fn sessionSignalHandler(sig: std.posix.SIG) callconv(.c) void {
if (child_pid > 0) _ = std.c.kill(child_pid, i); if (child_pid > 0) _ = std.c.kill(child_pid, sig);
} }
pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void { pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void {
var tty_buffer: [3]u8 = undefined; var tty_buffer: [3]u8 = undefined;
const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty}); const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty});
@ -41,9 +44,11 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty}); const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty});
// Set the XDG environment variables // Set the XDG environment variables
try log_file.info(io, "auth/env", "setting xdg environment variables", .{});
try setXdgEnv(allocator, tty_str, current_environment); try setXdgEnv(allocator, tty_str, current_environment);
// Open the PAM session // Open the PAM session
try log_file.info(io, "auth/pam", "encoding credentials", .{});
const login_z = try allocator.dupeZ(u8, login); const login_z = try allocator.dupeZ(u8, login);
defer allocator.free(login_z); defer allocator.free(login_z);
@ -58,37 +63,36 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
}; };
var handle: ?*interop.pam.pam_handle = undefined; var handle: ?*interop.pam.pam_handle = undefined;
var log_writer = &log_file.file_writer.interface; try log_file.info(io, "auth/pam", "starting session", .{});
try log_writer.writeAll("[pam] starting session\n");
var status = interop.pam.pam_start(options.service_name, null, &conv, &handle); var status = interop.pam.pam_start(options.service_name, null, &conv, &handle);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer _ = interop.pam.pam_end(handle, 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 // 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"); try log_file.info(io, "auth/pam", "setting tty", .{});
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr); status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
// Do the PAM routine // Do the PAM routine
try log_writer.writeAll("[pam] authenticating\n"); try log_file.info(io, "auth/pam", "authenticating", .{});
status = interop.pam.pam_authenticate(handle, 0); status = interop.pam.pam_authenticate(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_writer.writeAll("[pam] validating account\n"); try log_file.info(io, "auth/pam", "validating account", .{});
status = interop.pam.pam_acct_mgmt(handle, 0); status = interop.pam.pam_acct_mgmt(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_writer.writeAll("[pam] setting credentials\n"); try log_file.info(io, "auth/pam", "setting credentials", .{});
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED); status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED); defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
try log_writer.writeAll("[pam] opening session\n"); try log_file.info(io, "auth/pam", "opening session", .{});
status = interop.pam.pam_open_session(handle, 0); status = interop.pam.pam_open_session(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status); if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_close_session(handle, 0); defer status = interop.pam.pam_close_session(handle, 0);
try log_file.info(io, "auth/passwd", "getting struct", .{});
var user_entry: interop.UsernameEntry = undefined; var user_entry: interop.UsernameEntry = undefined;
{ {
defer interop.closePasswordDatabase(); defer interop.closePasswordDatabase();
@ -98,28 +102,27 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
} }
// Set user shell if it hasn't already been set // Set user shell if it hasn't already been set
try log_file.info(io, "auth/passwd", "setting user shell", .{});
if (user_entry.shell == null) interop.setUserShell(&user_entry); if (user_entry.shell == null) interop.setUserShell(&user_entry);
var shared_err = try SharedError.init(); var shared_err = try SharedError.init(null, null);
defer shared_err.deinit(); defer shared_err.deinit();
log_file.deinit(); log_file.deinit(io);
child_pid = try std.posix.fork(); child_pid = std.posix.system.fork();
if (child_pid == 0) { if (child_pid == 0) {
try log_file.reinit(); try log_file.reinit(io);
log_writer = &log_file.file_writer.interface; try log_file.info(io, "auth/sys", "starting session", .{});
try log_writer.writeAll("starting session\n"); startSession(log_file, allocator, io, options, tty_str, user_entry, handle, current_environment) catch |e| {
startSession(log_file, allocator, options, tty_str, user_entry, handle, current_environment) catch |e| {
shared_err.writeError(e); shared_err.writeError(e);
log_file.deinit(); log_file.deinit(io);
std.process.exit(1); std.process.exit(1);
}; };
log_file.deinit(); log_file.deinit(io);
std.process.exit(0); std.process.exit(0);
} }
@ -129,7 +132,8 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
// If an error occurs here, we can send SIGTERM to the session // If an error occurs here, we can send SIGTERM to the session
errdefer cleanup: { 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); var child_status: c_int = undefined;
_ = std.posix.system.waitpid(child_pid, &child_status, 0);
} }
// If we receive SIGTERM, forward it to child_pid // If we receive SIGTERM, forward it to child_pid
@ -140,13 +144,15 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
}; };
std.posix.sigaction(std.posix.SIG.TERM, &act, null); std.posix.sigaction(std.posix.SIG.TERM, &act, null);
try addUtmpEntry(&entry, user_entry.username.?, child_pid); try addUtmpEntry(io, &entry, user_entry.username.?, child_pid);
} }
// Wait for the session to stop // Wait for the session to stop
_ = std.posix.waitpid(child_pid, 0); var child_status: c_int = undefined;
_ = std.posix.system.waitpid(child_pid, &child_status, 0);
try log_file.reinit(); try log_file.reinit(io);
try log_file.info(io, "auth/utmp", "removing utmp entry", .{});
removeUtmpEntry(&entry); removeUtmpEntry(&entry);
if (shared_err.readError()) |err| return err; if (shared_err.readError()) |err| return err;
@ -155,6 +161,7 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
fn startSession( fn startSession(
log_file: *LogFile, log_file: *LogFile,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
io: std.Io,
options: AuthOptions, options: AuthOptions,
tty_str: []u8, tty_str: []u8,
user_entry: interop.UsernameEntry, user_entry: interop.UsernameEntry,
@ -162,34 +169,49 @@ fn startSession(
current_environment: Environment, current_environment: Environment,
) !void { ) !void {
// Set the user's GID & PID // Set the user's GID & PID
try log_file.info(io, "auth/passwd", "setting user context", .{});
try interop.setUserContext(allocator, user_entry); try interop.setUserContext(allocator, user_entry);
// Set up the environment // Set up the environment
try log_file.info(io, "auth/env", "setting environment variables", .{});
try initEnv(allocator, user_entry, options.path); try initEnv(allocator, user_entry, options.path);
// Reset the XDG environment variables // Reset the XDG environment variables
try log_file.info(io, "auth/env", "resetting xdg environment variables", .{});
try setXdgEnv(allocator, tty_str, current_environment); try setXdgEnv(allocator, tty_str, current_environment);
try setXdgRuntimeDir(allocator);
// Set the PAM variables // Set the PAM variables
const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle); const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle);
if (pam_env_vars == null) return error.GetEnvListFailed; if (pam_env_vars == null) return error.GetEnvListFailed;
const env_list = std.mem.span(pam_env_vars.?); const env_list = std.mem.span(pam_env_vars.?);
for (env_list) |env_var| try interop.putEnvironmentVariable(env_var); for (env_list) |env_var| {
if (env_var == null) continue;
try log_file.info(io, "auth/env", "setting pam environment variable: {s}", .{std.mem.span(env_var.?)});
try interop.putEnvironmentVariable(env_var);
}
const home_z = try allocator.dupeZ(u8, user_entry.home.?);
defer allocator.free(home_z);
// Change to the user's home directory // Change to the user's home directory
std.posix.chdir(user_entry.home.?) catch return error.ChangeDirectoryFailed; try log_file.info(io, "auth/sys", "changing cwd to user home", .{});
if (std.posix.system.chdir(home_z.ptr) < 0) return error.ChangeDirectoryFailed;
// Signal to the session process to give up control on the TTY // 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; try log_file.info(io, "auth/sys", "releasing tty", .{});
std.posix.kill(options.session_pid, std.posix.SIG.INT) catch return error.TtyControlTransferFailed;
// Execute what the user requested // Execute what the user requested
switch (current_environment.display_server) { switch (current_environment.display_server) {
.wayland, .shell, .custom => try executeCmd(log_file, allocator, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd), .wayland, .shell, .custom => try executeCmd(log_file, allocator, io, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd),
.xinitrc, .x11 => if (build_options.enable_x11_support) { .xinitrc, .x11 => if (build_options.enable_x11_support) {
var vt_buf: [5]u8 = undefined; var vt_buf: [5]u8 = undefined;
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty}); const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.x_vt orelse options.tty});
try executeX11Cmd(log_file, allocator, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt);
try log_file.info(io, "auth/x11", "setting vt to {s}", .{vt});
try executeX11Cmd(log_file, allocator, io, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt);
}, },
} }
} }
@ -217,18 +239,6 @@ fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environme
.custom => if (environment.is_terminal) "tty" else "unspecified", .custom => if (environment.is_terminal) "tty" else "unspecified",
}, false); }, false);
// 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});
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); 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_CLASS", "user", false);
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false); try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false);
@ -237,6 +247,20 @@ fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environme
try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false); try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false);
} }
fn setXdgRuntimeDir(allocator: std.mem.Allocator) !void {
// The "/run/user/%d" directory is not available on FreeBSD. It is much
// better to stick to the defaults and let applications using
// XDG_RUNTIME_DIR to fall back to directories inside user's home
// directory.
if (builtin.os.tag != .freebsd) {
const uid = std.posix.system.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});
try interop.setEnvironmentVariable(allocator, "XDG_RUNTIME_DIR", uid_str, false);
}
}
fn loginConv( fn loginConv(
num_msg: c_int, num_msg: c_int,
msg: ?[*]?*const interop.pam.pam_message, msg: ?[*]?*const interop.pam.pam_message,
@ -299,20 +323,20 @@ fn getFreeDisplay() !u8 {
var buf: [15]u8 = undefined; var buf: [15]u8 = undefined;
var i: u8 = 0; var i: u8 = 0;
while (i < 200) : (i += 1) { while (i < 200) : (i += 1) {
const xlock = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{i}); const xlock = try std.fmt.bufPrintZ(&buf, "/tmp/.X{d}-lock", .{i});
std.posix.access(xlock, std.posix.F_OK) catch break; if (interop.isError(std.posix.system.access(xlock.ptr, std.posix.F_OK))) break;
} }
return i; return i;
} }
fn getXPid(display_num: u8) !i32 { fn getXPid(io: std.Io, display_num: u8) !i32 {
var buf: [15]u8 = undefined; var buf: [15]u8 = undefined;
const file_name = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{display_num}); const file_name = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{display_num});
const file = try std.fs.openFileAbsolute(file_name, .{}); const file = try std.Io.Dir.openFileAbsolute(io, file_name, .{});
defer file.close(); defer file.close(io);
var file_buffer: [32]u8 = undefined; var file_buffer: [32]u8 = undefined;
var file_reader = file.reader(&file_buffer); var file_reader = file.reader(io, &file_buffer);
var reader = &file_reader.interface; var reader = &file_reader.interface;
var buffer: [20]u8 = undefined; var buffer: [20]u8 = undefined;
@ -322,41 +346,41 @@ fn getXPid(display_num: u8) !i32 {
return std.fmt.parseInt(i32, std.mem.trim(u8, buffer[0..written], " "), 10); return std.fmt.parseInt(i32, std.mem.trim(u8, buffer[0..written], " "), 10);
} }
fn createXauthFile(pwd: []const u8, buffer: []u8) ![]const u8 { fn createXauthFile(log_file: *LogFile, io: std.Io, pwd: []const u8, buffer: []u8) ![]const u8 {
var xauth_buf: [100]u8 = undefined; var xauth_buf: [100]u8 = undefined;
var xauth_dir: []const u8 = undefined; var xauth_dir: []const u8 = undefined;
const xdg_rt_dir = std.posix.getenv("XDG_RUNTIME_DIR"); const xdg_rt_dir = std.posix.system.getenv("XDG_RUNTIME_DIR");
var xauth_file: []const u8 = "lyxauth"; var xauth_file: []const u8 = "lyxauth";
if (xdg_rt_dir == null) no_rt_dir: { if (xdg_rt_dir == null) no_rt_dir: {
const xdg_cfg_home = std.posix.getenv("XDG_CONFIG_HOME"); const xdg_cfg_home = std.posix.system.getenv("XDG_CONFIG_HOME");
if (xdg_cfg_home == null) no_cfg_home: { if (xdg_cfg_home == null) no_cfg_home: {
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config", .{pwd}); xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config", .{pwd});
var dir = std.fs.cwd().openDir(xauth_dir, .{}) catch { var dir = std.Io.Dir.cwd().openDir(io, xauth_dir, .{}) catch {
// xauth_dir isn't a directory // xauth_dir isn't a directory
xauth_dir = pwd; xauth_dir = pwd;
xauth_file = ".lyxauth"; xauth_file = ".lyxauth";
break :no_cfg_home; break :no_cfg_home;
}; };
dir.close(); dir.close(io);
// xauth_dir is a directory, use it to store Xauthority // xauth_dir is a directory, use it to store Xauthority
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config/ly", .{pwd}); xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config/ly", .{pwd});
} else { } else {
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?}); xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{std.mem.span(xdg_cfg_home.?)});
} }
const file = std.fs.cwd().openFile(xauth_dir, .{}) catch break :no_rt_dir; const file = std.Io.Dir.cwd().openFile(io, xauth_dir, .{}) catch break :no_rt_dir;
file.close(); file.close(io);
// xauth_dir is a file, create the parent directory // xauth_dir is a file, create the parent directory
std.posix.mkdir(xauth_dir, 777) catch { std.Io.Dir.createDirAbsolute(io, xauth_dir, .fromMode(777)) catch {
xauth_dir = pwd; xauth_dir = pwd;
xauth_file = ".lyxauth"; xauth_file = ".lyxauth";
}; };
} else { } else {
xauth_dir = xdg_rt_dir.?; xauth_dir = std.mem.span(xdg_rt_dir.?);
} }
// Trim trailing slashes // Trim trailing slashes
@ -366,17 +390,19 @@ fn createXauthFile(pwd: []const u8, buffer: []u8) ![]const u8 {
const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file }); const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file });
std.fs.makeDirAbsolute(trimmed_xauth_dir) catch {}; std.Io.Dir.cwd().createDirPath(io, trimmed_xauth_dir) catch {};
const file = try std.fs.createFileAbsolute(xauthority, .{}); try log_file.info(io, "auth/x11", "creating xauth file: {s}", .{xauthority});
file.close();
const file = try std.Io.Dir.createFileAbsolute(io, xauthority, .{});
file.close(io);
return xauthority; return xauthority;
} }
fn mcookie() [Md5.digest_length * 2]u8 { fn mcookie(io: std.Io) [Md5.digest_length * 2]u8 {
var buf: [4096]u8 = undefined; var buf: [4096]u8 = undefined;
std.crypto.random.bytes(&buf); io.random(&buf);
var out: [Md5.digest_length]u8 = undefined; var out: [Md5.digest_length]u8 = undefined;
Md5.hash(&buf, &out, .{}); Md5.hash(&buf, &out, .{});
@ -384,79 +410,100 @@ fn mcookie() [Md5.digest_length * 2]u8 {
return std.fmt.bytesToHex(&out, .lower); return std.fmt.bytesToHex(&out, .lower);
} }
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 { fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, display_name: []u8, shell: [*:0]const u8, home: []const u8, xauth_buffer: []u8, options: AuthOptions) ![]const u8 {
const xauthority = try createXauthFile(home, xauth_buffer); const xauthority = try createXauthFile(log_file, io, home, xauth_buffer);
try interop.setEnvironmentVariable(allocator, "XAUTHORITY", xauthority, true); try interop.setEnvironmentVariable(allocator, "XAUTHORITY", xauthority, true);
try interop.setEnvironmentVariable(allocator, "DISPLAY", display_name, true); try interop.setEnvironmentVariable(allocator, "DISPLAY", display_name, true);
const magic_cookie = mcookie(); const magic_cookie = mcookie(io);
const pid = try std.posix.fork(); log_file.deinit(io);
const pid = std.posix.system.fork();
if (pid == 0) { if (pid == 0) {
try log_file.reinit(io);
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
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 cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ options.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1);
try log_file.info(io, "auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {}; _ = std.posix.system.execve(shell, &args, std.c.environ);
log_file.deinit(io);
std.process.exit(1); std.process.exit(1);
} }
const status = std.posix.waitpid(pid, 0); var status: c_int = undefined;
if (status.status != 0) { const result = std.posix.system.waitpid(pid, &status, 0);
try log_file.file_writer.interface.print("xauth command failed with status {d}\n", .{status.status});
try log_file.reinit(io);
if (interop.isError(result) or status != 0) {
try log_file.err(
io,
"auth/x11",
"xauth command failed with status: {d}",
.{status},
);
return error.XauthFailed; return error.XauthFailed;
} }
return xauthority;
} }
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 { fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, 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; var xauth_buffer: [256]u8 = undefined;
try log_writer.writeAll("[x11] getting free display\n"); try log_file.info(io, "auth/x11", "getting free display", .{});
const display_num = try getFreeDisplay(); const display_num = try getFreeDisplay();
var buf: [4]u8 = undefined; var buf: [4]u8 = undefined;
const display_name = try std.fmt.bufPrint(&buf, ":{d}", .{display_num}); const display_name = try std.fmt.bufPrint(&buf, ":{d}", .{display_num});
try log_file.info(io, "auth/x11", "got free display: {d}", .{display_num});
const shell_z = try allocator.dupeZ(u8, shell); const shell_z = try allocator.dupeZ(u8, shell);
defer allocator.free(shell_z); defer allocator.free(shell_z);
try log_writer.writeAll("[x11] creating xauth file\n"); try log_file.info(io, "auth/x11", "creating xauth file", .{});
try xauth(log_file, allocator, display_name, shell_z, home, &xauth_buffer, options); const xauthority = try xauth(log_file, allocator, io, display_name, shell_z, home, &xauth_buffer, options);
try log_writer.writeAll("[x11] starting x server\n"); try log_file.info(io, "auth/x11", "starting x server", .{});
const pid = try std.posix.fork(); const pid = std.posix.system.fork();
if (pid == 0) { if (pid == 0) {
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.x_cmd, display_name, vt }) catch std.process.exit(1); const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} -auth {s}", .{ options.x_cmd, display_name, vt, xauthority }) catch std.process.exit(1);
try log_file.info(io, "auth/x11", "executing: {s} -c {s} -auth {s}", .{ shell, cmd_str, xauthority });
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
std.posix.execveZ(shell_z, &args, std.c.environ) catch {}; _ = std.posix.system.execve(shell_z, &args, std.c.environ);
std.process.exit(1); std.process.exit(1);
} }
var ok: c_int = undefined; try log_file.info(io, "auth/x11", "waiting for xcb connection", .{});
var ok: c_int = -1;
var xcb: ?*interop.xcb.xcb_connection_t = null; var xcb: ?*interop.xcb.xcb_connection_t = null;
while (ok != 0) { while (ok != 0) {
xcb = interop.xcb.xcb_connect(null, null); xcb = interop.xcb.xcb_connect(null, null);
ok = interop.xcb.xcb_connection_has_error(xcb); ok = interop.xcb.xcb_connection_has_error(xcb);
std.posix.kill(pid, 0) catch |e| { std.posix.kill(pid, @enumFromInt(0)) catch |e| {
if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed; if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed;
}; };
} }
// X Server detaches from the process. // X Server detaches from the process.
// PID can be fetched from /tmp/X{d}.lock // PID can be fetched from /tmp/X{d}.lock
try log_writer.writeAll("[x11] getting x server pid\n"); try log_file.info(io, "auth/x11", "getting x server pid", .{});
const x_pid = try getXPid(display_num); const x_pid = try getXPid(io, display_num);
try log_file.info(io, "auth/x11", "got x server pid: {d}", .{x_pid});
try log_writer.writeAll("[x11] launching environment\n"); try log_file.info(io, "auth/x11", "launching environment", .{});
xorg_pid = try std.posix.fork(); xorg_pid = std.posix.system.fork();
if (xorg_pid == 0) { if (xorg_pid == 0) {
var cmd_buffer: [1024]u8 = undefined; var cmd_buffer: [1024]u8 = undefined;
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 cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} {s}", .{ if (options.use_kmscon_vt) "kmscon-launch-gui" else "", options.setup_cmd, options.login_cmd orelse "", desktop_cmd }) catch std.process.exit(1);
try log_file.info(io, "auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str });
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
std.posix.execveZ(shell_z, &args, std.c.environ) catch {}; _ = std.posix.system.execve(shell_z, &args, std.c.environ);
std.process.exit(1); std.process.exit(1);
} }
@ -468,60 +515,83 @@ fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []cons
}; };
std.posix.sigaction(std.posix.SIG.TERM, &act, null); std.posix.sigaction(std.posix.SIG.TERM, &act, null);
_ = std.posix.waitpid(xorg_pid, 0); var xorg_status: c_int = undefined;
_ = std.posix.system.waitpid(xorg_pid, &xorg_status, 0);
try log_file.info(io, "auth/x11", "disconnecting xcb", .{});
interop.xcb.xcb_disconnect(xcb); interop.xcb.xcb_disconnect(xcb);
// TODO: Find a more robust way to ensure that X has been terminated (pidfds?) // 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.posix.kill(x_pid, std.posix.SIG.TERM) catch {};
std.Thread.sleep(std.time.ns_per_s * 1); // Wait 1 second before sending SIGKILL io.sleep(.fromSeconds(1), .real) catch {}; // Wait 1 second before sending SIGKILL
std.posix.kill(x_pid, std.posix.SIG.KILL) catch return; std.posix.kill(x_pid, std.posix.SIG.KILL) catch return;
_ = std.posix.waitpid(x_pid, 0); var x_status: c_int = undefined;
_ = std.posix.system.waitpid(x_pid, &x_status, 0);
} }
fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void { fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void {
var maybe_log_file: ?std.fs.File = null; try global_log_file.info(io, "auth/sys", "launching wayland/shell/custom session", .{});
if (!is_terminal) {
var maybe_log_file: ?std.Io.File = null;
if (!is_terminal) redirect_streams: {
if (options.use_kmscon_vt) {
try global_log_file.err(io, "auth/sys", "cannot redirect stdio & stderr with kmscon", .{});
break :redirect_streams;
}
// For custom desktop entries, the "Terminal" value here determines if // For custom desktop entries, the "Terminal" value here determines if
// we redirect standard output & error or not. That is, we redirect only // 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 it's equal to false (so if it's not running in a TTY).
if (options.session_log) |log_path| { if (options.session_log) |log_path| {
maybe_log_file = try redirectStandardStreams(global_log_file, log_path, true); try global_log_file.info(io, "auth/sys", "setting up stdio & stderr redirection", .{});
maybe_log_file = try redirectStandardStreams(global_log_file, io, log_path, true);
} }
} }
defer if (maybe_log_file) |log_file| log_file.close(); defer if (maybe_log_file) |log_file| log_file.close(io);
const shell_z = try allocator.dupeZ(u8, shell); const shell_z = try allocator.dupeZ(u8, shell);
defer allocator.free(shell_z); defer allocator.free(shell_z);
var cmd_buffer: [1024]u8 = undefined; 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 cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} {s}", .{ if (!is_terminal and options.use_kmscon_vt) "kmscon-launch-gui" else "", options.setup_cmd, options.login_cmd orelse "", exec_cmd orelse shell });
try global_log_file.info(io, "auth/sys", "executing: {s} -c {s}", .{ shell, cmd_str });
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str }; const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
return std.posix.execveZ(shell_z, &args, std.c.environ); _ = std.posix.system.execve(shell_z, &args, std.c.environ);
return error.CmdExecveFailed;
} }
fn redirectStandardStreams(global_log_file: *LogFile, session_log: []const u8, create: bool) !std.fs.File { fn redirectStandardStreams(global_log_file: *LogFile, io: std.Io, session_log: []const u8, create: bool) !std.Io.File {
const log_file = if (create) (std.fs.cwd().createFile(session_log, .{ .mode = 0o666 }) catch |err| { create_session_log_dir: {
try global_log_file.file_writer.interface.print("failed to create new session log file: {s}\n", .{@errorName(err)}); const session_log_dir = std.Io.Dir.path.dirname(session_log) orelse break :create_session_log_dir;
std.Io.Dir.cwd().createDirPath(io, session_log_dir) catch |err| {
try global_log_file.err(io, "auth/sys", "failed to create session log file directory: {s}", .{@errorName(err)});
return 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)}); }
const log_file = if (create) (std.Io.Dir.cwd().createFile(io, session_log, .{ .permissions = .fromMode(0o666) }) catch |err| {
try global_log_file.err(io, "auth/sys", "failed to create new session log file: {s}", .{@errorName(err)});
return err;
}) else (std.Io.Dir.cwd().openFile(io, session_log, .{ .mode = .read_write }) catch |err| {
try global_log_file.err(io, "auth/sys", "failed to open existing session log file: {s}", .{@errorName(err)});
return err; return err;
}); });
try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO); if (interop.isError(std.posix.system.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO))) return error.StdoutDup2Failed;
try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO); if (interop.isError(std.posix.system.dup2(log_file.handle, std.posix.STDOUT_FILENO))) return error.LogFileDup2Failed;
return log_file; return log_file;
} }
fn addUtmpEntry(entry: *Utmp, username: []const u8, pid: c_int) !void { fn addUtmpEntry(io: std.Io, entry: *Utmp, username: []const u8, pid: c_int) !void {
entry.ut_type = utmp.USER_PROCESS; entry.ut_type = utmp.USER_PROCESS;
entry.ut_pid = pid; entry.ut_pid = pid;
var buf: [std.fs.max_path_bytes]u8 = undefined; var buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
const tty_path = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf); const length = try std.Io.File.stdin().realPath(io, &buf);
const tty_path = buf[0..length];
// Get the TTY name (i.e. without the /dev/ prefix) // Get the TTY name (i.e. without the /dev/ prefix)
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined; var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;

View File

@ -1,58 +0,0 @@
const std = @import("std");
const interop = @import("interop.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 Bigclock = enums.Bigclock;
pub const WIDTH = Lang.WIDTH;
pub const HEIGHT = Lang.HEIGHT;
pub const SIZE = Lang.SIZE;
pub fn clockCell(animate: bool, char: u8, fg: u32, bg: u32, bigclock: Bigclock) ![SIZE]Cell {
var cells: [SIZE]Cell = undefined;
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(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];
cell.put(x + xx, y + yy);
}
}
}
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' => 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,
};
}

View File

@ -1,28 +0,0 @@
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

118
src/components/InfoLine.zig Normal file
View File

@ -0,0 +1,118 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ly_ui = @import("ly-ui");
const keyboard = ly_ui.keyboard;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const CyclableLabel = ly_ui.CyclableLabel;
const MessageLabel = CyclableLabel(Message, Message);
const InfoLine = @This();
const Message = struct {
width: usize,
text: []const u8,
bg: u32,
fg: u32,
};
instance: ?Widget = null,
label: *MessageLabel,
pub fn init(
allocator: Allocator,
io: std.Io,
buffer: *TerminalBuffer,
width: usize,
arrow_fg: u32,
arrow_bg: u32,
) !InfoLine {
return .{
.instance = null,
.label = try MessageLabel.init(
allocator,
io,
buffer,
drawItem,
null,
null,
width,
true,
arrow_fg,
arrow_bg,
),
};
}
pub fn deinit(self: *InfoLine) void {
self.label.deinit();
}
pub fn widget(self: *InfoLine) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"InfoLine",
self.label.keybinds,
self,
deinit,
null,
draw,
null,
handle,
null,
);
return &self.instance.?;
}
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
if (text.len == 0) return;
try self.label.addItem(.{
.width = TerminalBuffer.strWidth(text),
.text = text,
.bg = bg,
.fg = fg,
});
}
pub fn clearRendered(self: InfoLine, allocator: Allocator) !void {
// Draw over the area
const spaces = try allocator.alloc(u8, self.label.width - 2);
defer allocator.free(spaces);
@memset(spaces, ' ');
try TerminalBuffer.drawText(
spaces,
self.label.component_pos.x + 2,
self.label.component_pos.y,
TerminalBuffer.Color.DEFAULT,
TerminalBuffer.Color.DEFAULT,
);
}
fn draw(self: *InfoLine) void {
self.label.draw();
}
fn handle(self: *InfoLine, maybe_key: ?keyboard.Key) !void {
try self.label.handle(maybe_key);
}
fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void {
if (message.width == 0) return;
const x_offset = if (label.text_in_center and width >= message.width) (width - message.width) / 2 else 0;
label.cursor = message.width + x_offset;
TerminalBuffer.drawConfinedText(
message.text,
x + x_offset,
y,
width,
message.fg,
message.bg,
) catch {};
}

123
src/components/Session.zig Normal file
View File

@ -0,0 +1,123 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ly_ui = @import("ly-ui");
const keyboard = ly_ui.keyboard;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const CyclableLabel = ly_ui.CyclableLabel;
const UserList = @import("UserList.zig");
const Environment = @import("../Environment.zig");
const Env = struct {
environment: Environment,
index: usize,
};
const EnvironmentLabel = CyclableLabel(Env, *UserList);
const Session = @This();
instance: ?Widget = null,
label: *EnvironmentLabel,
user_list: *UserList,
pub fn init(
allocator: Allocator,
io: std.Io,
buffer: *TerminalBuffer,
user_list: *UserList,
width: usize,
text_in_center: bool,
fg: u32,
bg: u32,
) !Session {
return .{
.instance = null,
.label = try EnvironmentLabel.init(
allocator,
io,
buffer,
drawItem,
sessionChanged,
user_list,
width,
text_in_center,
fg,
bg,
),
.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();
self.label.allocator.free(env.environment.file_name);
}
self.label.deinit();
}
pub fn widget(self: *Session) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"Session",
self.label.keybinds,
self,
deinit,
null,
draw,
null,
handle,
null,
);
return &self.instance.?;
}
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 draw(self: *Session) void {
self.label.draw();
}
fn handle(self: *Session, maybe_key: ?keyboard.Key) !void {
try self.label.handle(maybe_key);
}
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, width: usize) void {
if (width < 3) return;
const length = @min(TerminalBuffer.strWidth(env.environment.name), width - 3);
if (length == 0) return;
const x_offset = if (label.text_in_center and width >= length) (width - length) / 2 else 0;
label.cursor = length + x_offset;
TerminalBuffer.drawConfinedText(
env.environment.name,
x + x_offset,
y,
width,
label.fg,
label.bg,
) catch {};
}

147
src/components/UserList.zig Normal file
View File

@ -0,0 +1,147 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const ly_ui = @import("ly-ui");
const keyboard = ly_ui.keyboard;
const TerminalBuffer = ly_ui.TerminalBuffer;
const Widget = ly_ui.Widget;
const CyclableLabel = ly_ui.CyclableLabel;
const Session = @import("Session.zig");
const SavedUsers = @import("../config/SavedUsers.zig");
const StringList = std.ArrayListUnmanaged([]const u8);
pub const User = struct {
name: []const u8,
session_index: *usize,
allocated_index: bool,
first_run: bool,
};
const UserLabel = CyclableLabel(User, *Session);
const UserList = @This();
instance: ?Widget = null,
label: *UserLabel,
pub fn init(
allocator: Allocator,
io: std.Io,
buffer: *TerminalBuffer,
usernames: StringList,
saved_users: *SavedUsers,
session: *Session,
width: usize,
text_in_center: bool,
fg: u32,
bg: u32,
) !UserList {
var user_list = UserList{
.instance = null,
.label = try UserLabel.init(
allocator,
io,
buffer,
drawItem,
usernameChanged,
session,
width,
text_in_center,
fg,
bg,
),
};
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 user_list.label.addItem(.{
.name = username,
.session_index = maybe_session_index.?,
.allocated_index = allocated_index,
.first_run = first_run,
});
}
return user_list;
}
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 widget(self: *UserList) *Widget {
if (self.instance) |*instance| return instance;
self.instance = Widget.init(
"UserList",
self.label.keybinds,
self,
deinit,
null,
draw,
null,
handle,
null,
);
return &self.instance.?;
}
pub fn getCurrentUsername(self: UserList) []const u8 {
return self.label.list.items[self.label.current].name;
}
fn draw(self: *UserList) void {
self.label.draw();
}
fn handle(self: *UserList, maybe_key: ?keyboard.Key) !void {
try self.label.handle(maybe_key);
}
fn usernameChanged(user: User, maybe_session: ?*Session) void {
if (maybe_session) |session| {
session.label.current = @min(user.session_index.*, session.label.list.items.len - 1);
}
}
fn drawItem(label: *UserLabel, user: User, x: usize, y: usize, width: usize) void {
if (width < 3) return;
const length = @min(TerminalBuffer.strWidth(user.name), width - 3);
if (length == 0) return;
const x_offset = if (label.text_in_center and width >= length) (width - length) / 2 else 0;
label.cursor = length + x_offset;
TerminalBuffer.drawConfinedText(
user.name,
x + x_offset,
y,
width,
label.fg,
label.bg,
) catch {};
}

View File

@ -1,13 +1,15 @@
const build_options = @import("build_options"); const build_options = @import("build_options");
const enums = @import("../enums.zig");
const enums = @import("../enums.zig");
const Animation = enums.Animation; const Animation = enums.Animation;
const Input = enums.Input; const Input = enums.Input;
const ViMode = enums.ViMode; const ViMode = enums.ViMode;
const Bigclock = enums.Bigclock; const Bigclock = enums.Bigclock;
const DurOffsetAlignment = enums.DurOffsetAlignment;
allow_empty_password: bool = true, allow_empty_password: bool = true,
animation: Animation = .none, animation: Animation = .none,
animation_frame_delay: u16 = 5,
animation_timeout_sec: u12 = 0, animation_timeout_sec: u12 = 0,
asterisk: ?u32 = '*', asterisk: ?u32 = '*',
auth_fails: u64 = 10, auth_fails: u64 = 10,
@ -21,6 +23,8 @@ bigclock_12hr: bool = false,
bigclock_seconds: bool = false, bigclock_seconds: bool = false,
blank_box: bool = true, blank_box: bool = true,
border_fg: u32 = 0x00FFFFFF, border_fg: u32 = 0x00FFFFFF,
box_position_h: f32 = 0.5,
box_position_v: f32 = 0.4,
box_title: ?[]const u8 = null, box_title: ?[]const u8 = null,
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s 10%-", brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s 10%-",
brightness_down_key: ?[]const u8 = "F5", brightness_down_key: ?[]const u8 = "F5",
@ -35,6 +39,7 @@ cmatrix_max_codepoint: u16 = 0x7B,
colormix_col1: u32 = 0x00FF0000, colormix_col1: u32 = 0x00FF0000,
colormix_col2: u32 = 0x000000FF, colormix_col2: u32 = 0x000000FF,
colormix_col3: u32 = 0x20000000, colormix_col3: u32 = 0x20000000,
custom_bind_width: ?u32 = null,
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions", custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
default_input: Input = .login, default_input: Input = .login,
doom_fire_height: u8 = 6, doom_fire_height: u8 = 6,
@ -43,8 +48,9 @@ doom_top_color: u32 = 0x00FF0000,
doom_middle_color: u32 = 0x00FFFF00, doom_middle_color: u32 = 0x00FFFF00,
doom_bottom_color: u32 = 0x00FFFFFF, doom_bottom_color: u32 = 0x00FFFFFF,
dur_file_path: []const u8 = build_options.config_directory ++ "/ly/example.dur", dur_file_path: []const u8 = build_options.config_directory ++ "/ly/example.dur",
dur_x_offset: u32 = 0, dur_offset_alignment: DurOffsetAlignment = .center,
dur_y_offset: u32 = 0, dur_x_offset: i32 = 0,
dur_y_offset: i32 = 0,
edge_margin: u8 = 0, edge_margin: u8 = 0,
error_bg: u32 = 0x00000000, error_bg: u32 = 0x00000000,
error_fg: u32 = 0x01FF0000, error_fg: u32 = 0x01FF0000,
@ -68,10 +74,9 @@ lang: []const u8 = "en",
login_cmd: ?[]const u8 = null, login_cmd: ?[]const u8 = null,
login_defs_path: []const u8 = "/etc/login.defs", login_defs_path: []const u8 = "/etc/login.defs",
logout_cmd: ?[]const u8 = null, logout_cmd: ?[]const u8 = null,
ly_log: []const u8 = "/var/log/ly.log", ly_log: ?[]const u8 = "/var/log/ly.log",
margin_box_h: u8 = 2, margin_box_h: u8 = 2,
margin_box_v: u8 = 1, margin_box_v: u8 = 1,
min_refresh_delta: u16 = 5,
numlock: bool = false, numlock: bool = false,
path: ?[]const u8 = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", path: ?[]const u8 = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
restart_cmd: []const u8 = "/sbin/shutdown -r now", restart_cmd: []const u8 = "/sbin/shutdown -r now",
@ -80,6 +85,9 @@ save: bool = true,
service_name: [:0]const u8 = "ly", service_name: [:0]const u8 = "ly",
session_log: ?[]const u8 = "ly-session.log", session_log: ?[]const u8 = "ly-session.log",
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh", setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
shell: bool = true,
show_password_key: []const u8 = "F7",
show_tty: bool = false,
shutdown_cmd: []const u8 = "/sbin/shutdown -a now", shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
shutdown_key: []const u8 = "F1", shutdown_key: []const u8 = "F1",
sleep_cmd: ?[]const u8 = null, sleep_cmd: ?[]const u8 = null,
@ -88,8 +96,9 @@ start_cmd: ?[]const u8 = null,
text_in_center: bool = false, text_in_center: bool = false,
vi_default_mode: ViMode = .normal, vi_default_mode: ViMode = .normal,
vi_mode: bool = false, vi_mode: bool = false,
waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions", waylandsessions: ?[]const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",
x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X", x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X",
x_vt: ?u8 = null,
xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth", xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth",
xinitrc: ?[]const u8 = "~/.xinitrc", xinitrc: ?[]const u8 = "~/.xinitrc",
xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions", xsessions: ?[]const u8 = build_options.prefix_directory ++ "/share/xsessions",

View File

@ -8,6 +8,9 @@ brightness_down: []const u8 = "decrease brightness",
brightness_up: []const u8 = "increase brightness", brightness_up: []const u8 = "increase brightness",
capslock: []const u8 = "capslock", capslock: []const u8 = "capslock",
custom: []const u8 = "custom", custom: []const u8 = "custom",
custom_info_err_output_long: []const u8 = "output too long",
custom_info_err_no_output: []const u8 = "no output",
custom_info_err_no_output_error: []const u8 = ", possible error",
err_alloc: []const u8 = "failed memory allocation", err_alloc: []const u8 = "failed memory allocation",
err_args: []const u8 = "unable to parse command line arguments", err_args: []const u8 = "unable to parse command line arguments",
err_autologin_session: []const u8 = "autologin session not found", err_autologin_session: []const u8 = "autologin session not found",
@ -78,6 +81,7 @@ restart: []const u8 = "reboot",
shell: [:0]const u8 = "shell", shell: [:0]const u8 = "shell",
shutdown: []const u8 = "shutdown", shutdown: []const u8 = "shutdown",
sleep: []const u8 = "sleep", sleep: []const u8 = "sleep",
toggle_password: []const u8 = "toggle password",
wayland: []const u8 = "wayland", wayland: []const u8 = "wayland",
x11: []const u8 = "x11", x11: []const u8 = "x11",
xinitrc: [:0]const u8 = "xinitrc", xinitrc: [:0]const u8 = "xinitrc",

25
src/config/custom.zig Normal file
View File

@ -0,0 +1,25 @@
const std = @import("std");
const custom = @This();
pub const CustomCommandBind = struct {
name: []const u8 = "",
cmd: []const u8 = "",
};
pub const UNDEFINED_CMD: []const u8 = "echo \"You forgot to define 'cmd'!\"";
pub const CustomCommandInfo = struct {
name: []const u8 = "",
cmd: ?[]const u8 = null,
/// To be set to the label's widget ID
id: u64 = 0,
/// In frames, the refresh rate for the `cmd` to run again
/// If 0, only run once.
refresh: u32 = 0,
counter: u32 = 0,
};
pub var binds: std.array_hash_map.String(CustomCommandBind) = undefined;
pub var labels: std.array_hash_map.String(CustomCommandInfo) = undefined;

View File

@ -3,14 +3,20 @@
// Color codes interpreted differently since 1.1.0 // Color codes interpreted differently since 1.1.0
const std = @import("std"); const std = @import("std");
const ini = @import("zigini"); var temporary_allocator = std.heap.page_allocator;
const ly_ui = @import("ly-ui");
const TerminalBuffer = ly_ui.TerminalBuffer;
const Color = TerminalBuffer.Color;
const Styling = TerminalBuffer.Styling;
const ly_core = ly_ui.ly_core;
const IniParser = ly_core.IniParser;
const ini = ly_core.ini;
const Config = @import("Config.zig"); const Config = @import("Config.zig");
const OldSave = @import("OldSave.zig"); const OldSave = @import("OldSave.zig");
const SavedUsers = @import("SavedUsers.zig"); const SavedUsers = @import("SavedUsers.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const custom = @import("custom.zig");
const Color = TerminalBuffer.Color;
const Styling = TerminalBuffer.Styling;
const color_properties = [_][]const u8{ const color_properties = [_][]const u8{
"bg", "bg",
@ -41,8 +47,6 @@ const removed_properties = [_][]const u8{
"load", "load",
}; };
var temporary_allocator = std.heap.page_allocator;
pub var auto_eight_colors: bool = true; pub var auto_eight_colors: bool = true;
pub var maybe_animate: ?bool = null; pub var maybe_animate: ?bool = null;
@ -151,6 +155,69 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
return field; return field;
} }
if (std.mem.eql(u8, field.key, "min_refresh_delta")) {
// The option has simply been renamed
var mapped_field = field;
mapped_field.key = "animation_frame_delay";
return mapped_field;
}
// TODO: Dearest Melpert,
// I pray this message finds you well, as daylight dwindles and the witching hour
// approaches, I find it more and more imperative as time continues that I place
// this reminder here in such a format that you cannot ignore.
// Do you know how long I have been waiting for this petition to be authorized
// in regards to this particular segment of computerized instructions?
// It has been many a moon since this particular audit has been
// posted regarding the position of handling configurable literature
// apparatuses and plans for a new feature to the configuration
// interface and as time continues onwards I grow more restless
// on the progress of said interface, only to find out afterwards
// that you have PROCRASTINATED on the efforts meant to enhance
// configuration. Thus the requirement for this reminder larger
// compared to the two reminders regarding better methods of
// X termination detection and new usernames with existing
// save files.
//
// Thus is my que to leave this TODO at thy request,
//
// Forever Sullied,
//
// Ly Contributor.
//
if (std.mem.startsWith(u8, field.header, "cmd:")) {
const key = field.header["cmd:".len..];
const keyZ = temporary_allocator.dupe(u8, key) catch "";
if (!custom.binds.contains(key)) {
custom.binds.put(temporary_allocator, keyZ, .{}) catch {};
}
if (custom.binds.getPtr(keyZ)) |command| {
if (std.mem.eql(u8, field.key, "name")) {
command.name = temporary_allocator.dupe(u8, field.value) catch "";
}
if (std.mem.eql(u8, field.key, "cmd")) {
command.cmd = temporary_allocator.dupe(u8, field.value) catch "";
}
}
}
if (std.mem.startsWith(u8, field.header, "lbl:")) {
const key = field.header["lbl:".len..];
const keyZ = temporary_allocator.dupe(u8, key) catch "";
if (!custom.labels.contains(keyZ)) {
custom.labels.put(temporary_allocator, keyZ, .{ .name = keyZ }) catch {};
}
if (custom.labels.getPtr(keyZ)) |label| {
if (std.mem.eql(u8, field.key, "cmd")) {
label.cmd = temporary_allocator.dupe(u8, field.value) catch "";
}
if (std.mem.eql(u8, field.key, "refresh")) {
label.refresh = std.fmt.parseInt(u32, field.value, 10) catch 0;
}
}
}
return field; return field;
} }
@ -187,51 +254,14 @@ pub fn lateConfigFieldHandler(config: *Config) void {
} }
} }
pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave { pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !?IniParser(OldSave) {
var save = OldSave{}; var save_parser = try IniParser(OldSave).init(allocator, io, path, null);
errdefer save_parser.deinit();
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;
}
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; var user_buf: [32]u8 = undefined;
const save = save_ini.readFileToStruct(path, .{ const maybe_save = if (save_parser.maybe_load_error == null) save_parser.structure else tryMigrateFirstSaveFile(io, &user_buf);
.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;
if (maybe_save) |save| {
// Add all other users to the list // Add all other users to the list
for (usernames, 0..) |username, i| { for (usernames, 0..) |username, i| {
if (save.user) |user| { if (save.user) |user| {
@ -246,5 +276,40 @@ pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, save_ini: *ini.Ini(Ol
}); });
} }
return true; return save_parser;
}
return null;
}
fn tryMigrateFirstSaveFile(io: std.Io, user_buf: *[32]u8) ?OldSave {
if (maybe_save_file) |path| {
defer temporary_allocator.free(path);
var save = OldSave{};
var file = std.Io.Dir.openFileAbsolute(io, path, .{}) catch return null;
defer file.close(io);
var file_buffer: [64]u8 = undefined;
var file_reader = file.reader(io, &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 null;
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 null;
var session_index: ?usize = null;
if (written > 0) {
session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return null;
}
save.session_index = session_index;
return save;
}
return null;
} }

View File

@ -1,4 +1,5 @@
const std = @import("std"); const std = @import("std");
pub const Animation = enum { pub const Animation = enum {
none, none,
doom, doom,
@ -53,3 +54,15 @@ pub const Bigclock = enum {
en, en,
fa, fa,
}; };
pub const DurOffsetAlignment = enum {
topleft,
topcenter,
topright,
centerleft,
center,
centerright,
bottomleft,
bottomcenter,
bottomright,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,61 +0,0 @@
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});
}

View File

@ -1,260 +0,0 @@
const std = @import("std");
const interop = @import("../interop.zig");
const Cell = @import("Cell.zig");
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: usize,
height: usize,
fg: u32,
bg: u32,
border_fg: u32,
box_chars: struct {
left_up: u32,
left_down: u32,
right_up: u32,
right_down: u32,
top: u32,
bottom: u32,
left: u32,
right: u32,
},
labels_max_length: usize,
box_x: usize,
box_y: usize,
box_width: usize,
box_height: usize,
margin_box_v: u8,
margin_box_h: u8,
blank_cell: Cell,
pub fn init(options: InitOptions, labels_max_length: usize, random: Random) TerminalBuffer {
return .{
.random = random,
.width = @intCast(termbox.tb_width()),
.height = @intCast(termbox.tb_height()),
.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,
.right_down = 0x2518,
.top = 0x2500,
.bottom = 0x2500,
.left = 0x2502,
.right = 0x2502,
} else .{
.left_up = '+',
.left_down = '+',
.right_up = '+',
.right_down = '+',
.top = '-',
.bottom = '-',
.left = '|',
.right = '|',
},
.labels_max_length = labels_max_length,
.box_x = 0,
.box_y = 0,
.box_width = (2 * 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 changed = false;
var y = self.height - 2;
while (y > 0) : (y -= 1) {
for (0..self.width) |x| {
var cell: ?*termbox.tb_cell = undefined;
var cell_under: ?*termbox.tb_cell = undefined;
_ = termbox.tb_get_cell(@intCast(x), @intCast(y - 1), 1, &cell);
_ = termbox.tb_get_cell(@intCast(x), @intCast(y), 1, &cell_under);
// 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;
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.?.ch, cell.?.fg, cell.?.bg);
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.?.fg, cell_under.?.bg);
}
}
return changed;
}
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
if (self.width < 2 or self.height < 2) return;
const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2;
const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2;
const x2 = (self.width + @min(self.width, self.box_width)) / 2;
const y2 = (self.height + @min(self.height, self.box_height)) / 2;
self.box_x = x1;
self.box_y = y1;
if (show_borders) {
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg);
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg);
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg);
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg);
var c1 = 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| {
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| {
c1.put(x1 - 1, y1 + i);
c2.put(x2, y1 + i);
}
}
if (blank_box) {
for (0..self.box_height) |y| {
for (0..self.box_width) |x| {
self.blank_cell.put(x1 + x, y1 + y);
}
}
}
}
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
start_x: usize,
x: usize,
y: usize,
full_visible_length: usize,
visible_length: usize,
} {
const start_x = self.box_x + self.margin_box_h;
const x = start_x + self.labels_max_length + 1;
const y = self.box_y + self.margin_box_v;
const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x;
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
return .{
.start_x = start_x,
.x = x,
.y = y,
.full_visible_length = full_visible_length,
.visible_length = visible_length,
};
}
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
drawColorLabel(text, x, y, self.fg, self.bg);
}
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: 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: 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: 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: 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: 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,60 +0,0 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const generic = @import("generic.zig");
const Allocator = std.mem.Allocator;
const MessageLabel = generic.CyclableLabel(Message, Message);
const InfoLine = @This();
const Message = struct {
width: u8,
text: []const u8,
bg: u32,
fg: u32,
};
label: MessageLabel,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
return .{
.label = MessageLabel.init(allocator, buffer, drawItem, null, null),
};
}
pub fn deinit(self: *InfoLine) void {
self.label.deinit();
}
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);
@memset(spaces, ' ');
buffer.drawLabel(spaces, buffer.box_x, y);
}
fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool {
if (message.width == 0 or label.buffer.box_width <= message.width) return false;
const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2);
label.first_char_x = x + message.width;
TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg);
return true;
}

View File

@ -1,66 +0,0 @@
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);
sessionChanged(env, self.user_list);
}
fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void {
if (maybe_user_list) |user_list| {
const user = user_list.label.list.items[user_list.label.current];
if (!user.first_run) return;
user.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,160 +0,0 @@
const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Allocator = std.mem.Allocator;
const DynamicString = std.ArrayListUnmanaged(u8);
const termbox = interop.termbox;
const Text = @This();
allocator: Allocator,
buffer: *TerminalBuffer,
text: DynamicString,
end: usize,
cursor: usize,
visible_start: usize,
visible_length: usize,
x: usize,
y: usize,
masked: bool,
maybe_mask: ?u32,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u32) Text {
const text: DynamicString = .empty;
return .{
.allocator = allocator,
.buffer = buffer,
.text = text,
.end = 0,
.cursor = 0,
.visible_start = 0,
.visible_length = 0,
.x = 0,
.y = 0,
.masked = masked,
.maybe_mask = maybe_mask,
};
}
pub fn deinit(self: *Text) void {
self.text.deinit(self.allocator);
}
pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
self.x = x;
self.y = y;
self.visible_length = visible_length;
}
pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void {
if (maybe_event) |event| blk: {
if (event.type != termbox.TB_EVENT_KEY) break :blk;
switch (event.key) {
termbox.TB_KEY_ARROW_LEFT => self.goLeft(),
termbox.TB_KEY_ARROW_RIGHT => self.goRight(),
termbox.TB_KEY_DELETE => self.delete(),
termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => {
if (insert_mode) {
self.backspace();
} else {
self.goLeft();
}
},
termbox.TB_KEY_SPACE => try self.write(' '),
else => {
if (event.ch > 31 and event.ch < 127) {
if (insert_mode) {
try self.write(@intCast(event.ch));
} else {
switch (event.ch) {
'h' => self.goLeft(),
'l' => self.goRight(),
else => {},
}
}
}
},
}
}
if (self.masked and self.maybe_mask == null) {
_ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
return;
}
_ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
}
pub fn draw(self: Text) void {
if (self.masked) {
if (self.maybe_mask) |mask| {
const length = @min(self.text.items.len, self.visible_length - 1);
if (length == 0) return;
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
}
return;
}
const length = @min(self.text.items.len, self.visible_length);
if (length == 0) return;
const visible_slice = vs: {
if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) {
break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)];
} else {
break :vs self.text.items[self.visible_start..];
}
};
self.buffer.drawLabel(visible_slice, self.x, self.y);
}
pub fn clear(self: *Text) void {
self.text.clearRetainingCapacity();
self.end = 0;
self.cursor = 0;
self.visible_start = 0;
}
fn goLeft(self: *Text) void {
if (self.cursor == 0) return;
if (self.visible_start > 0) self.visible_start -= 1;
self.cursor -= 1;
}
fn goRight(self: *Text) void {
if (self.cursor >= self.end) return;
if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1;
self.cursor += 1;
}
fn delete(self: *Text) void {
if (self.cursor >= self.end) return;
_ = self.text.orderedRemove(self.cursor);
self.end -= 1;
}
fn backspace(self: *Text) void {
if (self.cursor == 0) return;
self.goLeft();
self.delete();
}
fn write(self: *Text, char: u8) !void {
if (char == 0) return;
try self.text.insert(self.allocator, self.cursor, char);
self.end += 1;
self.goRight();
}

View File

@ -1,88 +0,0 @@
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

@ -1,117 +0,0 @@
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 });
}
}
};
}