39 Commits
Author SHA1 Message Date
Cytops 747955ce3b Use %p instead of %P for strftime (#1070)
## What are the changes about?
%P is a GNU extension and returns an empty string on musl, changed to %p
_Replace this with a brief description of your changes_
literally just "sed -i '1964s/"%P"/"%p"/' src/main.zig"
No ai used its a one line fix
## What existing issue(s) does this resolve?
N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

I have tested on Musl alpine and Glibc artix they now render the same with the same config.lua

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1070
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-10-30 23:41:18 +01:00
AnErrupTion 3ab7f6d967 ly-core: Fix FreeBSD build
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-10-21 10:47:10 +02:00
Matheus Campos 4e0f23f084 Use zig 0.17 in the Nix devShell and update the readme (#1067)
## What are the changes about?

Update the nixpkgs input in flake.lock, since zig_0_17 is not available in the previous revision, use it in the devShell, bump the zig version in the readme to 0.17.x, and replace the nix build instructions (the flake has no packages output) with nix develop.

## What existing issue(s) does this resolve?

N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1067
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-10-04 18:27:23 +02:00
AnErrupTion 501964d012 readme: Add section about Nix flake
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-10-04 09:30:45 +02:00
AnErrupTion 2a2e0d738c config: Fix documentation & missing binds
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-10-04 09:11:23 +02:00
Matheus Campos 83903cf378 feat: add flake for development environment (#1066)
## What are the changes about?

This PR adds a flake.nix and flake.lock to provide a reproducible development environment for ly using Nix.

The development shell provides the dependencies required to build ly:

- zig
- pam
- xcb

## What existing issue(s) does this resolve?

N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1066
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-10-03 19:18:42 +02:00
AnErrupTion f1fc335bb8 Update to Zig 0.17.0 (#1028)
Now compiles!
(Tested with Zig 0.17.0-dev.2375+d8aab4878)

## What are the changes about?

Updates dependencies & code for Zig 0.17.0 compatibility.

## What existing issue(s) does this resolve?

N/A

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1028
2026-10-03 17:53:36 +02:00
matcxzandMatheus C. 55de3bc26c fix: prevent underflow when session list is empty (#1060)
## What are the changes about?
Fixes an integer underflow in `min_session_index`, which was computed by subtracting 1 from the session list length without checking for zero. Caused a panic (Debug/ReleaseSafe) or wraparound (ReleaseFast) on installs with no available sessions.

## What existing issue(s) does this resolve?
N/A

## Pre-requisites
- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Co-authored-by: Matheus C. <245040149+matheusc457@users.noreply.github.com>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1060
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-09-30 17:19:38 +02:00
koru f802d3bcb5 docs: Update formatting in readme and contributing.md (#1061)
Just some `readme.md` and `contributing.md` formatting update and tidy
up.

Signed-off-by: koru <koru@cock.li>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1061
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-09-29 18:13:29 +02:00
koru 44304503bd lang: Update Russian translation (#1062)
Signed-off-by: koru <koru@cock.li>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1062
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-09-29 08:36:56 +02:00
GalaxyShard 60be7ada9e Update & improve Esperanto translation (#1055)
This PR adds translations for new strings added since I initially created the translation. Additionally, some strings were rephrased for consistency and to use more colloquial terms and grammatical structures (based on other apps in Esperanto and komputeko, a dictionary of tech terminology), and I also fixed a few translation errors (sorry about those!).

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1055
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-09-08 10:20:31 +02:00
RacerBG 863162b5f7 Update Bulgarian Translation (#1054)
Signed-off-by: RacerBG <racerbg@noreply.codeberg.org>
Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1054
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-09-05 08:25:18 +02:00
AnErrupTion 809728e06a autrh: Check if faillock file exists, not just dir
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-27 10:54:16 +02:00
AnErrupTion 255af0f1d4 config: Don't error out on missing fields
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-26 15:53:20 +02:00
AnErrupTion a6e5f87e24 auth: Check faillock for locked account (closes #898)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-26 15:24:14 +02:00
AnErrupTion c90aec373e auth: Implement password reset after expiration (closes #992)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-26 09:36:02 +02:00
AnErrupTion 35fa91058b migrator: One TODO is gone! Yippee!
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-26 09:13:39 +02:00
AnErrupTion d10df78886 auth: Add password reset prerequisites
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-25 23:10:37 +02:00
AnErrupTion 95641008fe setup.sh: Set STARTUP, late load Xsession (closes #1049)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-25 20:32:36 +02:00
AnErrupTion 00440cca2e crawler: remove any trailing colon for XDG_CURRENT_DESKTOP
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-25 19:55:54 +02:00
AnErrupTion 39ea43c3cb ui: Let the info line render after empty password error
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-25 19:54:34 +02:00
urly3 432560a1cd fix: freebsd still requires 1-indexed tty (#1048)
## What are the changes about?

I thought it was working as intended but appears I just got lucky when testing.
VT_ACTIVATE still wants them 1-indexed, so increase the parsed value respectively.

My apologies!

## What existing issue(s) does this resolve?

N/A

## Pre-requisites

- [ x ] I have tested & confirmed the changes work locally
- [ x ] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1048
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-08-25 11:29:52 +02:00
AnErrupTion c7591a15eb auth: Simplify PAM appdata
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 22:47:10 +02:00
AnErrupTion 32dd0967ba config: Add grab_focus_tty (closes #1039)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 21:32:11 +02:00
AnErrupTion 95a1bca7de config: Fix dur file comment
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 17:58:21 +02:00
AnErrupTion 0a0d6704c8 Delete config.ini
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 16:51:01 +02:00
AnErrupTion 1117ef5a3b Reference Lua config + fix install bug
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 16:50:29 +02:00
AnErrupTion 6ef332d73e config: Add getRandomColor(), fix crash (closes #791)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 16:41:28 +02:00
AnErrupTion b68a41b1ea sysvinit: Use $PREFIX_DIRECTORY
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 16:12:48 +02:00
AnErrupTion 5618446377 systemd: Use agetty from sbin (closes #1045)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 16:11:57 +02:00
AnErrupTion 76a5f73ee5 config: Update config.lua
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 16:09:10 +02:00
AnErrupTion 4e7a599c8a config: Rename dur_file to dur (closes #1032)
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 15:44:21 +02:00
Titanium Brain b9742203ee Use Lua as a config file language (#1010)
## What are the changes about?

Adds zlua for running Lua code as the configuration file.
Missing as of this time:
- ~~Parsing custom binds and labels~~
- ~~Fix memory leaks~~
- ~~Investigate issue with animation colours~~
- ~~Add examples and default config~~

## What existing issue does this resolve?

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

## Pre-requisites

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

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1010
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-08-24 15:35:01 +02:00
urly3 882815343f feat: per tty last user caching (closes #1040) (#1044)
An update to #1042

## What are the changes about?

This no longer creates a new file and instead appends the new save data to the existing file in a backwards compatible way.
It can differentiate by using a prefix to the string (which would usually be the username) of "ly/tty".
Usernames on supported systems aren't able to include a forward slash, and if they could, the chances of those colliding..

The rest follows the last PR:
Fix using saved user list where it shouldn't
Now saves users that weren't previously in the save file (fixes TODO)
Since we save only valid state, the save file is self-cleaning of bad/outdated data

## What existing issue(s) does this resolve?

#1040

## Pre-requisites

- [ x ] I have tested & confirmed the changes work locally (freebsd, void linux)
- [ x ] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1044
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-08-24 15:32:41 +02:00
RadsammyT 0a29dc2fef feat(gameoflife): allow for variations to game of life (closes #1023) (#1041)
## What are the changes about?

Allows the game of life animation to be ran with configurable rules, specifically: the neighbors required for birth and survival.
### Example: [B3/S12345 (Maze)](https://conwaylife.com/wiki/OCA:Maze)
(where numbers after B are neighbors required for birth, and S for survival)
<video src="/attachments/49120c5e-ce21-4025-852e-924befbc2621" title="Screencast_20260814_233338" controls></video>

Introduces two keys to the config:
```ini
# Sets the multiple amounts of neighbors needed for a cell to survive.
# Each numerical digit from 0 to 8 inclusive in this string becomes
# one of the targets required for this cell to survive.
# Example: a string of "23" makes the cell survive on 2 or 3 neighbors
gameoflife_param_survival = "23"

# Sets the multiple amounts of neighbors needed for a cell to be born.
# Each numerical digit from 0 to 8 inclusive in this string becomes
# one of the targets required for this cell to be born.
# Example: a string of "3" makes the cell be born on 3 neighbors
gameoflife_param_birth = "3"
```

## What existing issue(s) does this resolve?

Solves !1023

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1041
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-08-24 15:32:16 +02:00
jR4dh3y bd28c0e679 Add optional bigclock digit outline (closes #1030) (#1031)
## What are the changes about?

Add optional `bigclock_outline_fg`. When set, each big-clock digit gets a 1-cell outline drawn under the glyph so it stays readable over busy animations. Default is `null` (previous behavior).

When the outline is enabled, the gap under the clock is slightly larger so the stroke is not glued to the login box.

## What existing issue(s) does this resolve?

Fixes #1030

## Pre-requisites

- [x] I have tested & confirmed the changes work locally
- [x] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

### Testing

- `zig build -Doptimize=ReleaseSafe` (zig 0.16.0)
- Greeter with `bigclock = en` and `bigclock_outline_fg = 0x20000000` over a `dur_file` animation
- Default `null` leaves stock look unchanged

### AI usage (per contributing.md)

Assisted by an LLM (xAI Grok) for exploration and drafting. I directed the design (optional outline only, no panel/box, stock layout otherwise), reviewed the Zig, fixed an `i2` overflow in the outline loop, verified the build/local greeter, and wrote this PR text.

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1031
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-08-24 15:31:52 +02:00
urly3 fa8332dddd feature: getActiveTerminal for FreeBSD (#1047)
## What are the changes about?

Implements getActiveTerminal for FreeBSD via fstat

## What existing issue(s) does this resolve?

#1046

## Pre-requisites

- [ x ] I have tested & confirmed the changes work locally
- [ x ] I have read and fully adhere to the rules set in the contributing guidelines found in `CONTRIBUTING.md`

Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1047
Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
2026-08-24 15:30:46 +02:00
AnErrupTion f17ccf9ea7 build.zig: Support rc build descriptions
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 15:29:17 +02:00
AnErrupTion 11da43d7d9 Start Ly v1.6.0 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2026-08-24 15:20:13 +02:00
53 changed files with 1719 additions and 781 deletions

No files matched your search

+1 -1
View File
@@ -73,7 +73,7 @@ body:
attributes:
label: Relevant logs
description: |
Please copy and paste (or attach) any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you. The log files (located as specified by `/etc/ly/config.ini`) usually contain relevant information about the problem:
Please copy and paste (or attach) any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you. The log files (located as specified by `/etc/ly/config.lua` or `/etc/ly/config.ini`) usually contain relevant information about the problem:
- The session log is located at `~/.local/state/ly-session.log` by default.
- The system log is located at `/var/log/ly.log` by default.
render: shell
+38 -8
View File
@@ -28,7 +28,7 @@ comptime {
}
}
const ly_version = std.SemanticVersion{ .major = 1, .minor = 5, .patch = 0 };
const ly_version = std.SemanticVersion{ .major = 1, .minor = 6, .patch = 0 };
fn InstallStep(
b: *std.Build,
@@ -102,13 +102,6 @@ pub fn build(b: *std.Build) !void {
}),
});
const zlua = b.dependency("zlua", .{
.target = target,
.optimize = optimize,
.lang = .luajit,
});
exe.root_module.addImport("zlua", zlua.module("zlua"));
const ly_ui = b.dependency("ly_ui", .{
.target = target,
.optimize = optimize,
@@ -214,6 +207,20 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
}
return version_str;
},
1 => {
// Release candidate build (e.g. v1.5.0-rc1)
var it = std.mem.splitScalar(u8, git_describe, '-');
const tagged_ancestor = std.mem.trimStart(u8, it.first(), "v");
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
if (version.order(ancestor_ver) != .gt) {
std.debug.print("{s} version '{f}' must be greater than tagged ancestor '{f}'\n", .{ name, version, ancestor_ver });
std.process.exit(1);
}
// The version is reformatted in accordance with the https://semver.org specification.
return version_str;
},
2 => {
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
var it = std.mem.splitScalar(u8, git_describe, '-');
@@ -236,6 +243,29 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
// The version is reformatted in accordance with the https://semver.org specification.
return b.fmt("{s}-dev.{s}+{s}", .{ version_str, commit_height, commit_id[1..] });
},
3 => {
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
var it = std.mem.splitScalar(u8, git_describe, '-');
const tagged_ancestor = std.mem.trimStart(u8, it.first(), "v");
_ = it.next().?;
const commit_height = it.next().?;
const commit_id = it.next().?;
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
if (version.order(ancestor_ver) != .gt) {
std.debug.print("{s} version '{f}' must be greater than tagged ancestor '{f}'\n", .{ name, version, ancestor_ver });
std.process.exit(1);
}
// Check that the commit hash is prefixed with a 'g' (a Git convention).
if (commit_id.len < 1 or commit_id[0] != 'g') {
std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
return version_str;
}
// The version is reformatted in accordance with the https://semver.org specification.
return b.fmt("{s}-dev.{s}+{s}", .{ version_str, commit_height, commit_id[1..] });
},
else => {
std.debug.print("Unexpected `git describe` output: {s}\n", .{git_describe});
return version_str;
+1 -5
View File
@@ -1,6 +1,6 @@
.{
.name = .ly,
.version = "1.5.0",
.version = "1.6.0",
.fingerprint = 0xa148ffcc5dc2cb59,
.minimum_zig_version = "0.17.0",
.dependencies = .{
@@ -11,10 +11,6 @@
.url = "git+https://github.com/Hejsil/zig-clap?ref=master#05faf3905e8548f5cc269a8836e154065e70128d",
.hash = "clap-0.12.0-oBajB4XpAQCF1WRKnD5KOKPsnwwT_NNdAJ7Ww3irYJac",
},
.zlua = .{
.url = "git+https://github.com/AnErrupTion/ziglua?ref=almost-there#5ff4bcc7b56661125dd93ad81addd473cebbcd14",
.hash = "zlua-0.1.0-hGRpC9eYBQDYOfd1aKlHG8hy8eTgKiaAdIyQEQgLREpI",
},
},
.paths = .{
"build.zig",
+61 -13
View File
@@ -1,29 +1,77 @@
# Contributing to Ly
# Contributing
To contribute to Ly, you have to open a pull request on [Codeberg](https://codeberg.org/fairyglade/ly), as [GitHub](https://github.com/fairyglade/ly) is just a mirror. However, you also have to respect the following rules, otherwise your PR may end up being rejected:
To contribute to Ly, you have to open a pull request on
[Codeberg](https://codeberg.org/fairyglade/ly), as
[GitHub](https://github.com/fairyglade/ly) is just a mirror. However, you also
have to respect the following rules, otherwise your PR may end up being
rejected:
## AI usage
While we cannot control your usage of any LLM whatsoever, we do heavily discourage their use for environmental, ethical & moral reasons that you can learn more about on the Internet. However, if you do end up still using them, here are a couple rules you **must absolutely** respect for your PR not to get instantly shot down. Of course, it goes without saying that **all** the other rules in the other sections below **must** be adhered to, even more so when AI is used.
While we cannot control your usage of any LLM whatsoever, we do heavily
discourage their use for environmental, ethical & moral reasons that you can
learn more about on the Internet. However, if you do end up still using them,
here are a couple rules you **must absolutely** respect for your PR not to get
instantly shot down. Of course, it goes without saying that **all** the other
rules in the other sections below **must** be adhered to, even more so when AI
is used.
1. **Communicate through you**, not the AI; as in, responses to reviews or other comments in the PR must be written by you and not an LLM. If English isn't your native language and you have a lot of trouble speaking it, you _may_ use AI, but responses must not be your typical, alienating AI-generated text: https://github.com/realrossmanngroup/no_ai_slop_writing_rules
2. Control the code, as in, **don't vibecode**, especially if you don't know Zig. Don't waste the maintainers's time, and **heavily test your code**, as AI-generated code is typically more error-prone in subtle ways (even if they can be better than humans in some other aspects). Finally, don't generate any "summary" or whatever the AI may come up with during the process. **If you cannot understand the generated code, do not contribute it**. Maintainers are busy and are often simple volunteers, so the less work they have to do, the better.
3. **Be transparent about AI usage**. Precise what AI model was used in the process, and preferably, how you went about creating your changes (i.e. how did you use AI to make the pull request). The more honest you are about it, the more likely your PR will be accepted and merged into the code base.
1. **Communicate through you**, not the AI; as in, responses to reviews or other
comments in the PR must be written by you and not an LLM. If English isn't
your native language and you have a lot of trouble speaking it, you _may_ use
AI, but responses must not be your typical, alienating AI-generated text:
<https://github.com/realrossmanngroup/no_ai_slop_writing_rules>.
2. Control the code, as in, **don't vibecode**, especially if you don't know
Zig. Don't waste the maintainers's time, and **heavily test your code**, as
AI-generated code is typically more error-prone in subtle ways (even if they
can be better than humans in some other aspects). Finally, don't generate any
"summary" or whatever the AI may come up with during the process. **If you
cannot understand the generated code, do not contribute it**. Maintainers are
busy and are often simple volunteers, so the less work they have to do, the
better.
3. **Be transparent about AI usage**. Precise what AI model was used in the
process, and preferably, how you went about creating your changes (i.e. how
did you use AI to make the pull request). The more honest you are about it,
the more likely your PR will be accepted and merged into the code base.
If all the above rules are respected, your changes have much more likely to be accepted into the code base.
If all the above rules are respected, your changes have much more likely to be
accepted into the code base.
## Code style
You must follow Zig's [style guide](https://ziglang.org/documentation/master/#Style-Guide). In most cases, all you'll have to do is run `zig fmt` after you have completed your changes, though it does not fix everything, notably variable, function & field naming, as well as the maximum length of aline.
You must follow Zig's
[style guide](https://ziglang.org/documentation/master/#Style-Guide). In most
cases, all you'll have to do is run `zig fmt` after you have completed your
changes, though it does not fix everything, notably variable, function & field
naming, as well as the maximum length of aline.
For the former, please refer to the aforementioned style guide in that case. For the latter, you must respect a maximum line length of **80 characters**. For function calls with many parameters or anything similar that may overflow this limit, consider adding a trailing comma to the list so that `zig fmt` can split it into multiple lines. Ideally, few or no lines end up having to get soft wrapped by an editor having this limit set.
For the former, please refer to the aforementioned style guide in that case. For
the latter, you must respect a maximum line length of **80 characters**. For
function calls with many parameters or anything similar that may overflow this
limit, consider adding a trailing comma to the list so that `zig fmt` can split
it into multiple lines. Ideally, few or no lines end up having to get soft
wrapped by an editor having this limit set.
## Commit names & descriptions
1. **Commit names must be descriptive**, resorting to the commit description if they are too long. In such cases, the commit name **must** be shortened while still making sense. With that said, no particular convention (emojis, prefixes & suffixes, etc.) is mandated for them, and you are free to adopt any one you think is best or that you are used to.
2. **Do not force push**, as PRs will get squashed into a single commit anyway. This erases history and makes it impossible to see the changes done over time and by a particular commit.
3. While not a requirement per se, **consider signing your commits** with an SSH or GPG key for security purposes. This ensures to a lesser extent that you are the person who you claim to be, and reduces the potential amount of malicious contributions that could infiltrate the code base.
1. **Commit names must be descriptive**, resorting to the commit description if
they are too long. In such cases, the commit name **must** be shortened while
still making sense. With that said, no particular convention (emojis,
prefixes & suffixes, etc.) is mandated for them, and you are free to adopt
any one you think is best or that you are used to.
2. **Do not force push**, as PRs will get squashed into a single commit anyway.
This erases history and makes it impossible to see the changes done over time
and by a particular commit.
3. While not a requirement per se, **consider signing your commits** with an SSH
or GPG key for security purposes. This ensures to a lesser extent that you
are the person who you claim to be, and reduces the potential amount of
malicious contributions that could infiltrate the code base.
## Additional requirements
Other requirements (such as testing your code) for merging the changes will be available when you open your pull request via the template (which you **must** use), however, these are not stated here because they may not be mandatory in some cases (e.g. if the changes are still work-in-progress), so they may temporarily be ommitted for a certain period of time until the changes are fully ready to be reviewed, for instance.
Other requirements (such as testing your code) for merging the changes will be
available when you open your pull request via the template (which you **must**
use), however, these are not stated here because they may not be mandatory in
some cases (e.g. if the changes are still work-in-progress), so they may
temporarily be ommitted for a certain period of time until the changes are fully
ready to be reviewed, for instance.
Generated
+27
View File
@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1791048980,
"narHash": "sha256-KgItSKML8Xvte0B7/uGnBDsYzSnnKHcOaiUbgWBXLXw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a7868a727837f3c09cee2ce0ca671c76b1589fed",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
+32
View File
@@ -0,0 +1,32 @@
{
description = "Ly - A lightweight TUI (ncurses-like) display manager for Linux and BSD";
inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
outputs = { nixpkgs, ... }:
let
systems = [
"x86_64-linux"
"aarch64-linux"
"i686-linux"
];
forAllSystems = nixpkgs.lib.genAttrs systems;
in
{
devShells = forAllSystems (system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
default = pkgs.mkShell {
packages = with pkgs; [
zig_0_17
pam
libxcb
];
};
}
);
};
}
+5 -8
View File
@@ -126,19 +126,16 @@ fn installLy(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap, inst
var config_dir = std.Io.Dir.cwd().openDir(io, ly_config_directory, .{}) catch unreachable;
defer config_dir.close(io);
if (install_config) {
const patched_config = try patchFile(allocator, io, "res/config.ini", patch_map);
defer allocator.free(patched_config);
const patched_config = try patchFile(allocator, io, "res/config.lua", patch_map);
defer allocator.free(patched_config);
try installText(io, patched_config, config_dir, ly_config_directory, "config.ini", .{});
if (install_config) {
try installText(io, patched_config, config_dir, ly_config_directory, "config.lua", .{});
try installFile(io, "res/startup.sh", config_dir, ly_config_directory, "startup.sh", .{ .permissions = .fromMode(0o755) });
}
const patched_example_config = try patchFile(allocator, io, "res/config.ini", patch_map);
defer allocator.free(patched_example_config);
try installText(io, patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{});
try installText(io, patched_config, config_dir, ly_config_directory, "config.lua.example", .{});
const patched_setup = try patchFile(allocator, io, "res/setup.sh", patch_map);
defer allocator.free(patched_setup);
+7
View File
@@ -21,6 +21,13 @@ pub fn build(b: *std.Build) void {
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
mod.addImport("zigini", zigini.module("zigini"));
const zlua = b.dependency("zlua", .{
.target = target,
.optimize = optimize,
.lang = .luajit,
});
mod.addImport("zlua", zlua.module("zlua"));
const translate_c = b.dependency("translate_c", .{});
addCImport(b, mod, translate_c, target, optimize, "pam", "#include <security/pam_appl.h>");
+5 -1
View File
@@ -1,6 +1,6 @@
.{
.name = .ly_core,
.version = "1.1.0",
.version = "1.2.0",
.fingerprint = 0xddda7afda795472,
.minimum_zig_version = "0.17.0",
.dependencies = .{
@@ -12,6 +12,10 @@
.url = "git+https://codeberg.org/ziglang/translate-c?ref=master#38d8339d9050eafe5dcf85b4fc9368576ca967e1",
.hash = "translate_c-0.0.0-Q_BUWoROBwAyokT5SJSw4YIrwdLd1w53i-pEYwBDqXZt",
},
.zlua = .{
.url = "git+https://github.com/natecraddock/ziglua?ref=master#ce59a325718d00c4b24364d482f9efa370a9bf09",
.hash = "zlua-0.1.0-hGRpCwmZBQDixqie-LjxX4vsguYaVT77Hd17csjHvQ5Y",
},
},
.paths = .{
"build.zig",
File renamed without changes.
+27 -3
View File
@@ -246,8 +246,29 @@ fn PlatformStruct() type {
if (result != 0) return error.SetUserUidFailed;
}
// FreeBSD implementation using sysctlbyname
// https://man.freebsd.org/cgi/man.cgi?sysctlbyname
pub fn getActiveTtyImpl(_: std.mem.Allocator, _: std.Io, _: bool) !u8 {
return error.FeatureUnimplemented;
const tty_fd = std.posix.system.open("/dev/tty", .{});
if (tty_fd < 0) return error.NoTtyFound;
defer _ = std.posix.system.close(tty_fd);
var tty_stat: std.posix.Stat = undefined;
if (std.posix.system.fstat(tty_fd, &tty_stat) < 0) return error.FstatTty;
var buf: ["ttyvx".len:0]u8 = undefined;
var len: usize = buf.len + 1;
if (std.posix.system.sysctlbyname("kern.devname", buf[0..].ptr, &len, &tty_stat.rdev, @sizeOf(u64)) < 0)
return error.SysctlTty;
const dev_name = buf[0 .. len - 1];
if (std.mem.startsWith(u8, dev_name, "ttyv")) {
return try std.fmt.parseInt(u8, dev_name[dev_name.len - 1 ..], 16) + 1;
}
return error.NoTtyFound;
}
pub fn getUserIdRange(_: std.mem.Allocator, _: std.Io, _: []const u8) !UidRange {
@@ -400,8 +421,11 @@ pub fn getNextUsernameEntry() ?UsernameEntry {
};
}
pub fn getUsernameEntry(username: [:0]const u8) ?UsernameEntry {
const entry = pwd.getpwnam(username);
pub fn getUsernameEntry(allocator: std.mem.Allocator, username: []const u8) ?UsernameEntry {
const username_z = allocator.dupeSentinel(u8, username, 0) catch return null;
defer allocator.free(username_z);
const entry = pwd.getpwnam(username_z);
if (entry == null) return null;
return .{
+335 -6
View File
@@ -1,23 +1,57 @@
const std = @import("std");
pub const ini = @import("zigini");
pub const zlua = @import("zlua");
pub const Lua = zlua.Lua;
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 const custom = @import("custom.zig");
pub fn Parser(comptime T: type) type {
return union(enum) {
ini: IniParser(T),
lua: LuaParser(T),
pub fn errors(self: *const @This()) std.ArrayList(Error) {
return switch (self.*) {
inline else => |p| p.errors,
};
}
pub fn maybe_load_error(self: *const @This()) ?anyerror {
return switch (self.*) {
inline else => |p| p.maybe_load_error,
};
}
pub fn structure(self: *const @This()) T {
return switch (self.*) {
inline else => |p| p.structure,
};
}
pub fn deinit(self: *@This()) void {
switch (self.*) {
inline else => |*p| p.deinit(),
}
}
};
}
pub const Error = struct {
type_name: []const u8,
key: []const u8,
value: []const u8,
error_name: []const u8,
};
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),
@@ -76,3 +110,298 @@ pub fn IniParser(comptime Struct: type) type {
}
};
}
pub fn LuaParser(comptime Struct: type) type {
return struct {
const Self = @This();
const temporary_allocator = std.heap.page_allocator;
pub var global_errors: std.ArrayList(Error) = .empty;
structure: Struct,
errors: std.ArrayList(Error),
maybe_load_error: ?anyerror,
allocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
pub fn init(
allocator: std.mem.Allocator,
path: []const u8,
) !Self {
var arena = std.heap.ArenaAllocator.init(allocator);
const arena_alloc = arena.allocator();
const data = parseLua(arena_alloc, path) catch load_error: {
break :load_error Struct{};
};
var maybe_load_error: ?anyerror = null;
if (global_errors.items.len != 0) {
maybe_load_error = error.InvalidConfig;
}
return .{
.structure = data,
.errors = global_errors,
.maybe_load_error = maybe_load_error,
.allocator = allocator,
.arena = arena,
};
}
fn parseLua(
allocator: std.mem.Allocator,
path: []const u8,
) !Struct {
var lua: *Lua = try .init(allocator);
defer lua.deinit();
lua.openBase();
lua.openBit();
lua.openMath();
lua.openString();
lua.openTable();
// convert to sentinel terminated slice
const spath: [:0]const u8 = try allocator.dupeSentinel(u8, path, 0);
defer allocator.free(spath);
lua.doFile(spath) catch return error.LuaError;
var data: Struct = .{};
switch (@typeInfo(Struct)) {
.@"struct" => |struc| {
const ly_type = lua.getGlobal("ly");
defer lua.pop(1); // pop ly table
if (ly_type == .nil) return error.MissingLyTable;
inline for (struc.field_names, struc.field_types) |name, ftype| {
try setField(allocator, lua, name, ftype, &data);
}
},
else => @compileError("Expected a struct."),
}
// Parse custom binds and labels
try parseCustom(lua);
return data;
}
pub fn setField(
allocator: std.mem.Allocator,
lua: *Lua,
comptime field_name: [:0]const u8,
field_type: type,
data: *Struct,
) !void {
return setFieldInner(
allocator,
lua,
field_name,
field_type,
data,
) catch |err| {
const value = lua.toString(-1) catch "";
const duped = allocator.dupe(u8, value) catch "";
errorHandler(@typeName(field_type), field_name, duped, err);
};
}
fn setFieldInner(
allocator: std.mem.Allocator,
lua: *Lua,
comptime field_name: [:0]const u8,
field_type: type,
data: *Struct,
) !void {
const type_info = @typeInfo(field_type);
const actual_type, const is_optional = blk: {
if (type_info == .optional) {
break :blk .{ type_info.optional.child, true };
}
break :blk .{ field_type, false };
};
// push value to top of stack
_ = lua.getField(-1, field_name);
defer lua.pop(1);
// handle null, i.e. undefined fields
if (is_optional and lua.isNil(-1)) {
@field(data, field_name) = null;
return;
}
// handle missing required fields
if (lua.isNil(-1)) return;
const actual_type_info = @typeInfo(actual_type);
// dispatch depending on type
if (actual_type_info == .int and is_optional) {
if (lua.isNumber(-1)) {
const value = try lua.toNumber(-1);
@field(data, field_name) = @trunc(value);
} else {
const str = try lua.toString(-1);
var view = try std.unicode.Utf8View.init(str);
var iter = view.iterator();
const codepoint = iter.nextCodepoint();
if (iter.nextCodepoint() != null) return error.ExpectedSingleCharacter;
@field(data, field_name) = if (codepoint) |cp| @intCast(cp) else null;
}
// non null integer
} else if (actual_type_info == .int) {
if (lua.isNumber(-1)) {
const value = try lua.toNumber(-1);
@field(data, field_name) = @trunc(value);
} else {
const str = try lua.toString(-1);
var view = try std.unicode.Utf8View.init(str);
var iter = view.iterator();
const codepoint = iter.nextCodepoint() orelse return error.EmptyString;
if (iter.nextCodepoint() != null) return error.ExpectedSingleCharacter;
@field(data, field_name) = @intCast(codepoint);
}
} else if (actual_type_info == .float) { // all floats
const value = try lua.toNumber(-1);
@field(data, field_name) = @floatCast(value);
} else if (actual_type_info == .bool) {
if (!lua.isBoolean(-1)) return error.ExpectedBoolean;
const value = lua.toBoolean(-1);
@field(data, field_name) = value;
} else if (actual_type == []const u8) {
const value = try lua.toString(-1);
const duped = try allocator.dupe(u8, value);
@field(data, field_name) = duped;
} else if (actual_type == [:0]const u8) {
const value = try lua.toString(-1);
const duped = try allocator.dupeSentinel(u8, value, 0);
@field(data, field_name) = duped;
} else if (actual_type_info == .@"enum") {
const value = try lua.toString(-1);
const variant = std.meta.stringToEnum(actual_type, value) orelse return error.InvalidVariant;
@field(data, field_name) = variant;
} else unreachable;
}
pub fn parseCustom(lua: *Lua) !void {
_ = lua.getGlobal("ly");
defer lua.pop(1); // pop ly table
if (!lua.isTable(-1)) return error.MissingLyTable;
_ = lua.getField(-1, "custom_commands");
// custom_commands can be omitted or empty, so we just skip instead of erroring
if (lua.isTable(-1)) binds: {
const len: usize = @intCast(lua.objectLen(-1));
if (len == 0) break :binds;
for (1..len + 1) |i| {
// push i-th table to stack
lua.pushInteger(@intCast(i));
const ith_table_type = lua.getTable(-2);
defer lua.pop(1); // i-th table in custom_commands
if (ith_table_type != .table) continue;
// skip command if binding isn't set or not a string
const binding_type = lua.getField(-1, "binding");
if (binding_type != .string) continue;
const binding = lua.toString(-1) catch continue;
const bindingZ = temporary_allocator.dupe(u8, binding) catch "";
lua.pop(1); // binding value
if (!custom.binds.contains(bindingZ)) {
custom.binds.put(temporary_allocator, bindingZ, .{}) catch {};
}
if (custom.binds.getPtr(bindingZ)) |command| {
// binding name
const name_type = lua.getField(-1, "name");
if (name_type != .string) continue;
const binding_name = lua.toString(-1) catch "";
command.name = temporary_allocator.dupe(u8, binding_name) catch "";
lua.pop(1); // name value
// binding command
const cmd_type = lua.getField(-1, "cmd");
if (cmd_type != .string) continue;
const binding_cmd = lua.toString(-1) catch "";
command.cmd = temporary_allocator.dupe(u8, binding_cmd) catch "";
lua.pop(1); // cmd value
}
}
}
lua.pop(1);
_ = lua.getField(-1, "custom_labels");
// custom_labels can be omitted, so we just skip instead of erroring
if (lua.isTable(-1)) labels: {
const len: usize = @intCast(lua.objectLen(-1));
if (len == 0) break :labels;
for (1..len + 1) |i| {
// push i-th table to stack
lua.pushInteger(@intCast(i));
const ith_table_type = lua.getTable(-2);
defer lua.pop(1); // i-th table in custom_labels
if (ith_table_type != .table) continue;
// skip command if binding isn't set or not a string
const label_type = lua.getField(-1, "label");
if (label_type != .string) continue;
const label = lua.toString(-1) catch continue;
const labelZ = temporary_allocator.dupe(u8, label) catch "";
lua.pop(1); // label value
if (!custom.labels.contains(labelZ)) {
custom.labels.put(temporary_allocator, labelZ, .{ .name = labelZ }) catch {};
}
if (custom.labels.getPtr(labelZ)) |label_ptr| {
// label command
const cmd_type = lua.getField(-1, "cmd");
if (cmd_type != .string) continue;
const label_cmd = lua.toString(-1) catch "";
label_ptr.cmd = temporary_allocator.dupe(u8, label_cmd) catch "";
lua.pop(1); // cmd value
// label refresh
const name_type = lua.getField(-1, "refresh");
if (name_type != .number) continue;
const label_refresh: u32 = @intCast(lua.toInteger(-1) catch 0);
label_ptr.refresh = label_refresh;
lua.pop(1); // name value
}
}
}
lua.pop(1);
}
pub fn deinit(self: *Self) void {
self.arena.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;
}
};
}
+1 -1
View File
@@ -1,6 +1,6 @@
.{
.name = .ly_ui,
.version = "1.1.0",
.version = "1.2.0",
.fingerprint = 0x8d11bf85a74ec803,
.minimum_zig_version = "0.17.0",
.dependencies = .{
+66 -9
View File
@@ -19,6 +19,12 @@ pub const CHAR_SIZE = CHAR_WIDTH * CHAR_HEIGHT;
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
pub const O: u32 = 0;
const OUTLINE_DIRS = [_][2]i8{
.{ -1, -1 }, .{ -1, 0 }, .{ -1, 1 },
.{ 0, -1 }, .{ 0, 1 }, .{ 1, -1 },
.{ 1, 0 }, .{ 1, 1 },
};
// zig fmt: off
pub const LocaleChars = struct {
ZERO: [CHAR_SIZE]u21,
@@ -51,6 +57,7 @@ text: []const u8,
max_width: ?usize,
fg: u32,
bg: u32,
outline_fg: ?u32 = null,
locale: BigLabelLocale,
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
@@ -63,6 +70,7 @@ pub fn init(
max_width: ?usize,
fg: u32,
bg: u32,
outline_fg: ?u32,
locale: BigLabelLocale,
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
@@ -75,6 +83,7 @@ pub fn init(
.max_width = max_width,
.fg = fg,
.bg = bg,
.outline_fg = outline_fg,
.locale = locale,
.update_fn = update_fn,
.calculate_timeout_fn = calculate_timeout_fn,
@@ -152,23 +161,71 @@ pub fn childrenPosition(self: BigLabel) Position {
fn draw(self: *BigLabel) void {
for (self.text, 0..) |c, i| {
const clock_cell = clockCell(
c,
self.fg,
self.bg,
self.locale,
);
const x = self.component_pos.x + i * (CHAR_WIDTH + 1);
const y = self.component_pos.y;
if (self.outline_fg) |outline_fg| {
drawDigitOutline(self, c, x, y, outline_fg);
}
alphaBlit(
self.component_pos.x + i * (CHAR_WIDTH + 1),
self.component_pos.y,
x,
y,
self.buffer.width,
self.buffer.height,
clock_cell,
clockCell(c, self.fg, self.bg, self.locale),
);
}
}
fn drawDigitOutline(
self: *BigLabel,
char: u8,
base_x: usize,
base_y: usize,
outline_fg: u32,
) void {
const pattern = toBigNumber(char, self.locale);
const stroke = Cell.init(X, outline_fg, TerminalBuffer.Color.DEFAULT);
for (0..CHAR_HEIGHT) |y| {
for (0..CHAR_WIDTH) |x| {
if (pattern[y * CHAR_WIDTH + x] == O) continue;
for (OUTLINE_DIRS) |dir| {
if (offsetBy(x, dir[0])) |nx| {
if (offsetBy(y, dir[1])) |ny| {
if (nx < CHAR_WIDTH and ny < CHAR_HEIGHT and
pattern[ny * CHAR_WIDTH + nx] != O)
{
continue;
}
}
}
const sx = offsetBy(base_x + x, dir[0]) orelse continue;
const sy = offsetBy(base_y + y, dir[1]) orelse continue;
if (sx >= self.buffer.width or
sy >= self.buffer.height)
{
continue;
}
stroke.put(sx, sy) catch {};
}
}
}
}
fn offsetBy(pos: usize, delta: i8) ?usize {
if (delta < 0) {
const abs: usize = @intCast(-delta);
if (pos < abs) return null;
return pos - abs;
}
return pos + @as(usize, @intCast(delta));
}
fn update(self: *BigLabel, context: *anyopaque) !void {
if (self.update_fn) |update_fn| {
return @call(
+2
View File
@@ -140,6 +140,7 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
fn goLeft(ptr: *anyopaque) !bool {
var self: *Self = @ptrCast(@alignCast(ptr));
if (self.list.items.len == 0) return false;
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
if (self.change_item_fn) |change_item_fn| {
@@ -156,6 +157,7 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
fn goRight(ptr: *anyopaque) !bool {
var self: *Self = @ptrCast(@alignCast(ptr));
if (self.list.items.len == 0) return false;
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
if (self.change_item_fn) |change_item_fn| {
+148 -77
View File
@@ -1,87 +1,116 @@
# The Ly display manager
# Ly
![Ly screenshot](.github/screenshot.png "Ly screenshot")
![Ly screenshot](.github/screenshot.png)
_Note: the above animation can be found [here](https://codeberg.org/fairyglade/ly-community/src/branch/main/animations/dur/blackhole-smooth-240x67.dur)!_
_Note: the above animation can be found
[here](https://codeberg.org/fairyglade/ly-community/src/branch/main/animations/dur/blackhole-smooth-240x67.dur)!_
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.
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.
Join us on Matrix over at [#ly-dm:matrix.org](https://matrix.to/#/#ly-dm:matrix.org)!
Join us on Matrix over at
[#ly-dm:matrix.org](https://matrix.to/#/#ly-dm:matrix.org)!
> [!NOTE]
> Development happens on [Codeberg](https://codeberg.org/fairyglade/ly) with a mirror on [GitHub](https://github.com/fairyglade/ly).
>
> Development happens on [Codeberg](https://codeberg.org/fairyglade/ly) with a
> mirror on [GitHub](https://github.com/fairyglade/ly).
## Dependencies
- Compile-time:
- zig 0.16.x (you must use a **release version** of zig; check that `zig version` does not have a `-dev*` suffix)
### Compile-time
- libc
- zig 0.17.x (you must use a **release version** of zig; check that `zig
version` does not have a `-dev*` suffix)
- pam
- libc
- xcb (optional, required by default; needed for X11 support)
- pam
- Runtime (with default config):
- xorg
- xcb (optional, required by default; needed for X11 support)
- xorg-xauth
### Runtime (default config)
- brightnessctl
- xorg
### Debian
- xorg-xauth
```
- brightnessctl
### Distro-specific
#### Debian
```console
# apt install build-essential libpam0g-dev libxcb-xkb-dev xauth xserver-xorg brightnessctl
```
### Fedora
#### Fedora
```
```console
# dnf install kernel-devel pam-devel libxcb-devel zig xorg-x11-xauth xorg-x11-server brightnessctl
```
> [!WARNING]
> Distributions using SELinux such as Fedora and openSUSE Tumbleweed may encounter issues. If you encounter such issues, such as session launch failures, proceed with the following steps:
>
> ```
> Distributions using SELinux such as Fedora and openSUSE Tumbleweed may
> encounter issues. If you encounter such issues, such as session launch
> failures, proceed with the following steps:
>
> ```console
> # ausearch -m avc -ts recent
> ```
>
> If SELinux is denying process context transition, you will see this output:
>
> ```
> ```ini
> denied { transition } for pid=XXXX comm="ly" path="/usr/bin/bash"
> scontext=system_u:system_r:unconfined_service_t:s0
> tcontext=unconfined_u:unconfined_r:unconfined_t:s0
> tclass=process permissive=0
> ```
>
> Pipe this output into `audit2allow` to generate a security module package. This will persist regardless of changes to filesystem permissions:
> Pipe this output into `audit2allow` to generate a security module package.
> This will persist regardless of changes to filesystem permissions:
>
> ```
> ```console
> # ausearch -m avc -ts recent | audit2allow -M ly-local
> ```
>
> ```
> ```console
> # semodule -i ly-local.pp
> ```
>
> _This fix has been confirmed on Fedora 39 and 44 and openSUSE Tumbleweed. (#494)_
> _This fix has been confirmed on Fedora 39 and 44 and openSUSE Tumbleweed.
> (#494)_
### FreeBSD
#### FreeBSD
```
```console
# pkg install ca_root_nss libxcb git xorg xauth
```
#### Nix
Enter a development shell with all dependencies:
```console
$ git clone https://codeberg.org/fairyglade/ly.git && cd ly
$ nix develop
```
Then follow [Manually building](#manually-building) from the `zig build` step.
## Availability
[![Packaging status](https://repology.org/badge/vertical-allrepos/ly-display-manager.svg?exclude_unsupported=1)](https://repology.org/project/ly-display-manager/versions)
## Custom animations & user scripts
If you want to pimp your Ly installation even further than what the traditional configuration file allows you to, a [community repository](https://codeberg.org/fairyglade/ly-community) exists containing custom animations & scripts, so go check it out!
If you want to pimp your Ly installation even further than what the traditional
configuration file allows you to, a
[community repository](https://codeberg.org/fairyglade/ly-community) exists
containing custom animations & scripts, so go check it out!
## Support
@@ -89,15 +118,22 @@ Every environment that works on other login managers also should work on Ly.
- Unlike most login managers Ly has an xinitrc and shell entry.
- 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 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 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 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.lua` or `/etc/ly/config.ini`:
- The session log is located at `~/.local/state/ly-session.log` by default.
@@ -107,64 +143,78 @@ Logs are defined by `/etc/ly/config.ini`:
The procedure for manually building Ly is pretty standard:
```
$ git clone https://codeberg.org/fairyglade/ly.git
$ cd ly
```console
$ git clone https://codeberg.org/fairyglade/ly.git && cd ly
$ zig build
```
After building, you can (optionally) test Ly in a terminal emulator, although authentication will **not** work:
After building, you can (optionally) test Ly in a terminal emulator, although
authentication will **not** work:
```
```console
$ zig build run
```
> [!IMPORTANT]
> 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.
>
> 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.
> [!NOTE]
> You can, however, test your configuration file changes like that. Note that you must do Ctrl+C in order to exit Ly.
>
> You can, however, test your configuration file changes like that. Note that
> you must do Ctrl+C in order to exit Ly.
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.
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.
> [!NOTE]
>
> All following sections will assume you are using LightDM for convenience sake.
### systemd
Now, you can install Ly on your system:
```
```console
# zig build installexe -Dinit_system=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, if you are using LightDM, you can execute the following command:
Note that you also need to disable your current display manager. For example, if
you are using LightDM, you can execute the following command:
```
```console
# systemctl disable lightdm.service
```
Then, similarly to the previous command, you need to enable the Ly service:
```
```console
# systemctl enable ly@tty2.service
```
> [!IMPORTANT]
> Because Ly runs in a TTY, you **must** disable the TTY service that Ly will run on, otherwise bad things will happen. For example, to disable `getty` spawning on TTY 2, you need to execute the following command:
>
> 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:
```
```console
# systemctl disable getty@tty2.service
```
On non-systemd systems, you can change the TTY Ly will run on by editing the corresponding service file for your platform.
On non-systemd systems, you can change the TTY Ly will run on by editing the
corresponding service file for your platform.
### OpenRC
```
```console
# zig build installexe -Dinit_system=openrc
# rc-update del lightdm
# rc-update add ly
@@ -172,11 +222,12 @@ On non-systemd systems, you can change the TTY Ly will run on by editing the cor
```
> [!NOTE]
> On Gentoo, Alpine and potentially others, you also **must** comment out the appropriate line for the TTY in /etc/inittab.
> On Gentoo, Alpine and potentially others, you also **must** comment out the
> appropriate line for the TTY in /etc/inittab.
### runit
```
```console
# zig build installexe -Dinit_system=runit
# unlink /var/service/lightdm
# ln -s /etc/sv/ly /var/service/
@@ -186,7 +237,7 @@ On non-systemd systems, you can change the TTY Ly will run on by editing the cor
### s6
```
```console
# zig build installexe -Dinit_system=s6
# s6-rc -d change lightdm
# s6-service add default ly-srv
@@ -198,96 +249,116 @@ To disable TTY 2, edit `/etc/s6/config/tty2.conf` and set `SPAWN="no"`.
### dinit
```
```console
# zig build installexe -Dinit_system=dinit
# dinitctl disable lightdm
# dinitctl enable ly
```
To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify `ACTIVE_CONSOLES`.
To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify
`ACTIVE_CONSOLES`.
### sysvinit
```
```console
# zig build installexe -Dinit_system=sysvinit
# update-rc.d lightdm disable
# update-rc.d ly defaults
```
To disable TTY 2, go to `/etc/inittab` and comment out the line containing `tty2`.
To disable TTY 2, go to `/etc/inittab` and comment out the line containing
`tty2`.
### FreeBSD
```
```console
# zig build installexe -Dprefix_directory=/usr/local -Dconfig_directory=/usr/local/etc -Dinit_system=freebsd
# sysrc lightdm_enable="NO"
```
To enable Ly, add the following entry to `/etc/gettytab`:
```
```bash
Ly:\
:lo=/usr/local/bin/ly_wrapper:\
:al=root:
```
Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys` (TTYs in FreeBSD start at 0):
Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys`
(TTYs in FreeBSD start at 0):
```
```bash
ttyv1 "/usr/libexec/getty Ly" xterm on secure
```
### Updating
You can also install Ly without overriding the current configuration file. This is called **updating**. To update, simply run:
You can also install Ly without overriding the current configuration file. This
is called **updating**. To update, simply run:
```
```console
# zig build installnoconf
```
You can, of course, still select the init system of your choice when using this command.
You can, of course, still select the init system of your choice when using this
command.
## Configuration
You can find all the configuration in `/etc/ly/config.ini`. The file is fully commented, and includes the default values.
You can find all the configuration in `/etc/ly/config.lua`. The file is fully
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:
It uses the Lua language, which means you can make the configuration dynamic.
You could, for example, choose a random animation each time Ly starts up.
```
$ ly --validate-config /etc/ly/config.ini
You may also check the validity of your configuration file (i.e. if there are
any errors in it) with the following command:
```console
$ ly --validate-config /etc/ly/config.lua
```
## Controls
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.
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.
## A note on .xinitrc
If your `.xinitrc` file doesn't work, make sure it is executable and includes a shebang. This file is supposed to be a shell script! Quoting from `xinit`'s man page:
If your `.xinitrc` file doesn't work, make sure it is executable and includes a
shebang. This file is supposed to be a shell script! Quoting from `xinit`'s man
page:
> If no specific client program is given on the command line, xinit will look for a file in the user's home directory called .xinitrc to run as a shell script to start up client programs.
> If no specific client program is given on the command line, xinit will look
> for a file in the user's home directory called .xinitrc to run as a shell
> script to start up client programs.
A typical shebang for a shell script looks like this:
```
```bash
#!/bin/sh
```
## Tips
- The numlock and capslock states are printed in the top-right corner.
- Use the F1 and F2 keys to respectively shutdown and reboot.
- Take a look at your `.xsession` file if X doesn't start, as it can interfere (this file is launched with X to configure the display properly).
- 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).
## A final note
The name "Ly" is a tribute to the fairy from the game Rayman. Ly was tested by oxodao, who is some seriously awesome dude.
The name "Ly" is a tribute to the fairy from the game Rayman. Ly was tested by
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!
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)!
[Liberapay](https://liberapay.com/ShiningLea)!
-451
View File
@@ -1,451 +0,0 @@
# Ly supports 24-bit true color with styling, which means each color is a 32-bit value.
# The format is 0xSSRRGGBB, where SS is the styling, RR is red, GG is green, and BB is blue.
# Here are the possible styling options:
# TB_BOLD 0x01000000
# TB_UNDERLINE 0x02000000
# TB_REVERSE 0x04000000
# TB_ITALIC 0x08000000
# TB_BLINK 0x10000000
# TB_HI_BLACK 0x20000000
# TB_BRIGHT 0x40000000
# TB_DIM 0x80000000
# Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's
# configuration doesn't support using it, you have to manually compute the color value.
# Note that, if you want to use the default color value of the terminal, you can use the
# special value 0x00000000. This means that, if you want to use black, you *must* use
# the styling option TB_HI_BLACK (the RGB values are ignored when using this option).
# Allow empty password or not when authenticating
allow_empty_password = true
# The active animation
# none -> Nothing
# doom -> PSX DOOM fire
# matrix -> CMatrix
# colormix -> Color mixing shader
# gameoflife -> John Conway's Game of Life
# dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master)
# lua -> user-made animation written in LuaJIT
animation = none
# Delay between each animation frame in milliseconds
animation_frame_delay = 5
# Stop the animation after some time
# 0 -> Run forever
# 1..2e12 -> Stop the animation after this many seconds
animation_timeout_sec = 0
# The character used to mask the password
# You can either type it directly as a UTF-8 character (like *), or use a UTF-32
# codepoint (for example 0x2022 for a bullet point)
# If null, the password will be hidden
# Note: you can use a # by escaping it like so: \#
asterisk = *
# The number of failed authentications before a special animation is played... ;)
# If set to 0, the animation will never be played
auth_fails = 10
# Automatic login configuration
# This feature allows Ly to automatically log in a user without password prompt.
# IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work.
# Autologin only happens once at startup - it won't re-trigger after logout.
# PAM service name to use for automatic login
# The default service (ly-autologin) uses pam_permit to allow login without password
# The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically
auto_login_service = ly-autologin
# Session name to launch automatically
# To find available session names, check the .desktop files in:
# - /usr/share/xsessions/ (for X11 sessions)
# - /usr/share/wayland-sessions/ (for Wayland sessions)
# Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field
# Examples: "i3", "sway", "gnome", "plasma", "xfce"
# If null, automatic login is disabled
auto_login_session = null
# Username to automatically log in
# Must be a valid user on the system
# If null, automatic login is disabled
auto_login_user = null
# Identifier for battery whose charge to display at top left
# Primary battery is usually BAT0 or BAT1
# If set to null, battery status won't be shown
# Unused on FreeBSD (a sysctl is used there)
battery_id = null
# Background color id
bg = 0x00000000
# Change the state and language of the big clock
# none -> Disabled (default)
# en -> English
# fa -> Farsi
bigclock = none
# Set bigclock to 12-hour notation.
bigclock_12hr = false
# Set bigclock to show the seconds.
bigclock_seconds = false
# Blank main box background
# Setting to false will make it transparent
blank_box = true
# Border foreground color id
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.5
box_position_v = 0.5
# Title to show at the top of the main box
# If set to null, none will be shown
box_title = null
# Brightness decrease command
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%-
# Brightness decrease key combination
# If null, the keybind is disabled and isn't shown
brightness_down_key = F5
# Brightness increase command
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%
# Brightness increase key combination
# If null, the keybind is disabled and isn't shown
brightness_up_key = F6
# Erase password input on failure
clear_password = false
# Format string for clock in top right corner (see strftime specification). Example: %c
# If null, the clock won't be shown
clock = null
# CMatrix animation foreground color id
cmatrix_fg = 0x0000FF00
# CMatrix animation character string head color id
cmatrix_head_col = 0x01FFFFFF
# CMatrix animation minimum codepoint. It uses a 16-bit integer
# For Japanese characters for example, you can use 0x3000 here
cmatrix_min_codepoint = 0x21
# CMatrix animation maximum codepoint. It uses a 16-bit integer
# For Japanese characters for example, you can use 0x30FF here
cmatrix_max_codepoint = 0x7B
# Color mixing animation first color id
colormix_col1 = 0x00FF0000
# Color mixing animation second color id
colormix_col2 = 0x000000FF
# Color mixing animation third color id
colormix_col3 = 0x20000000
# Screen corners customization
# Keywords:
# shutdown -> Shutdown key
# restart -> Restart key
# britup -> Brightness up key
# britdown -> Brightness down key
# password -> Toggle password key
# clock -> Clock (format defined by 'clock' option)
# tty -> Active TTY number
# battery -> Battery percentage
# version -> Ly version string
# numlock -> Numlock state
# capslock -> Capslock state
# labels -> All custom info labels (lbl:)
# binds -> All custom keybind hints (cmd:)
# lbl:name -> Specific custom info label
# cmd:key -> Specific custom keybind hint
#
# If using a keyword that groups multiple labels into one (e.g. labels, binds),
# they'll be placed horizontally
#
# Also, the order defines the vertical stack (first item is at the edge)
# If items are separted by commas, they'll be placed horizontally
# It is possible to have both horizontal and vertical items on the same corner
# Bottom left
corner_bottom_left = version
# Bottom right
corner_bottom_right = labels
# Top left
corner_top_left = shutdown,restart,britup,britdown,password battery binds
# Top right
corner_top_right = clock numlock,capslock
# 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
# You can specify multiple directories,
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
custom_sessions = $CONFIG_DIRECTORY/ly/custom-sessions
# Input box active by default on startup
# Available inputs: info_line, session, login, password
default_input = login
# DOOM animation fire height (1 thru 9)
doom_fire_height = 6
# DOOM animation fire spread (0 thru 4)
doom_fire_spread = 2
# DOOM animation custom top color (low intensity flames)
doom_top_color = 0x009F2707
# DOOM animation custom middle color (medium intensity flames)
doom_middle_color = 0x00C78F17
# DOOM animation custom bottom color (high intensity flames)
doom_bottom_color = 0x00FFFFFF
# Dur file path
dur_file_path = $CONFIG_DIRECTORY/ly/example.dur
# Dur 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 offset y direction (value is added to the current position determined by alignment, negatives are supported)
dur_y_offset = 0
# Set margin to the edges of the DM (useful for curved monitors)
edge_margin = 0
# Error background color id
error_bg = 0x00000000
# Error foreground color id
# Default is red and bold
error_fg = 0x01FF0000
# Foreground color id
fg = 0x00FFFFFF
# Render true colors (if supported)
# If false, output will be in eight-color mode
# All eight-color mode color codes:
# TB_DEFAULT 0x0000
# TB_BLACK 0x0001
# TB_RED 0x0002
# TB_GREEN 0x0003
# TB_YELLOW 0x0004
# TB_BLUE 0x0005
# TB_MAGENTA 0x0006
# TB_CYAN 0x0007
# TB_WHITE 0x0008
# If full color is off, the styling options still work. The colors are
# always 32-bit values with the styling in the most significant byte.
# Note: If using the dur_file animation option and the dur file's color range
# is saved as 256 with this option disabled, the file will not be drawn.
full_color = true
# Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations)
# 0 -> Pure Conway's Game of Life (will eventually stabilize)
# 10 -> Add entropy every 10 generations (recommended for continuous activity)
# 50+ -> Less frequent entropy for more natural evolution
gameoflife_entropy_interval = 10
# Game of Life animation foreground color id
gameoflife_fg = 0x0000FF00
# Game of Life frame delay (lower = faster animation, higher = slower)
# 1-3 -> Very fast animation
# 6 -> Default smooth animation speed
# 10+ -> Slower, more contemplative speed
gameoflife_frame_delay = 6
# Game of Life initial cell density (0.0 to 1.0)
# 0.1 -> Sparse, minimal activity
# 0.4 -> Balanced activity (recommended)
# 0.7+ -> Dense, chaotic patterns
gameoflife_initial_density = 0.4
# Remove main box borders
hide_borders = false
# Command executed when no input is detected for a certain time
# If null, no command will be executed
inactivity_cmd = null
# Executes a command after a certain amount of seconds
inactivity_delay = 0
# Initial text to show on the info line
# If set to null, the info line defaults to the hostname
initial_info_text = null
# Input boxes length
input_len = 34
# Active language
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
lang = en
# Command executed when logging in
# If null, no command will be executed
# Important: the code itself must end with `exec "$@"` in order to launch the session!
# You can also set environment variables in there, they'll persist until logout
login_cmd = null
# Path for login.defs file (used for listing all local users on the system on
# Linux)
login_defs_path = /etc/login.defs
# Command executed when logging out
# If null, no command will be executed
# Important: the session will already be terminated when this command is executed, so
# no need to add `exec "$@"` at the end
logout_cmd = null
# The file pointing to the Lua file to be used when using the Lua animation option
lua_animation_file = $CONFIG_DIRECTORY/ly/example.lua
# General log file path
# If null, syslog will be used instead
ly_log = /var/log/ly.log
# Main box horizontal margin
margin_box_h = 2
# Main box vertical margin
margin_box_v = 1
# Set numlock on/off at startup
numlock = false
# Default path
# If null, ly doesn't set a path
path = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Specifies the key combination used for restart
# If null, the keybind is disabled and isn't shown
restart_key = F2
# Absolute directory for the save file
# If null, current desktop & login won't be saved nor loaded
save_file_dir = $CONFIG_DIRECTORY/ly
# Service name (set to ly to use the provided pam config file)
service_name = ly
# Session log file path
# This will contain stdout and stderr of Wayland sessions
# By default it's saved in the user's home directory
# Important: due to technical limitations, X11, shell sessions as well as
# 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
session_log = .local/state/ly-session.log
# Setup command
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
# If null, the keybind is disabled and isn't shown
show_password_key = F7
# Specifies the key combination used for shutdown
# If null, the keybind is disabled and isn't shown
shutdown_key = F1
# Command executed when starting Ly (before the TTY is taken control of)
# See file at path below for an example of changing the default TTY colors
start_cmd = $CONFIG_DIRECTORY/ly/startup.sh
# Center the session name.
text_in_center = false
# If true, user will need to manually type username instead of selecting from the list
# of discovered users
type_username = false
# Default vi mode
# normal -> normal mode
# insert -> insert mode
vi_default_mode = normal
# Enable vi keybindings
vi_mode = false
# Wayland desktop environments
# You can specify multiple directories,
# 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
# Xorg server command
# Add the -quiet argument to hide startup logs from the server
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
xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
# xinitrc
# If null, the xinitrc session will be hidden
xinitrc = ~/.xinitrc
# Xorg desktop environments
# You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
# If null, X11 sessions will not be shown
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
+503
View File
@@ -0,0 +1,503 @@
-- This is an example function you can use on any option supporting colors
-- to use a random color instead.
function getRandomColor()
math.randomseed()
local r = math.random(0, 255)
local g = math.random(0, 255)
local b = math.random(0, 255)
local col = b
col = bit.bor(col, bit.lshift(g, 8))
col = bit.bor(col, bit.lshift(r, 16))
return col
end
ly = {
-- Ly supports 24-bit true color with styling, which means each color is a 32-bit value.
-- The format is 0xSSRRGGBB, where SS is the styling, RR is red, GG is green, and BB is blue.
-- Here are the possible styling options:
-- TB_BOLD 0x01000000
-- TB_UNDERLINE 0x02000000
-- TB_REVERSE 0x04000000
-- TB_ITALIC 0x08000000
-- TB_BLINK 0x10000000
-- TB_HI_BLACK 0x20000000
-- TB_BRIGHT 0x40000000
-- TB_DIM 0x80000000
-- Programmatically, you'd apply them using the bitwise OR operator (|), and
-- in Lua, you can use the bit.bor(x, y) function to compute x | y.
-- Note that, if you want to use the default color value of the terminal, you can use the
-- special value 0x00000000. This means that, if you want to use black, you *must* use
-- the styling option TB_HI_BLACK (the RGB values are ignored when using this option).
-- Allow empty password or not when authenticating
allow_empty_password = true,
-- The active animation
-- none -> Nothing
-- doom -> PSX DOOM fire
-- matrix -> CMatrix
-- colormix -> Color mixing shader
-- gameoflife -> John Conway's Game of Life
-- dur -> .dur file format (https://github.com/cmang/durdraw/tree/master)
-- lua -> user-made animation written in LuaJIT
animation = "none",
-- Delay between each animation frame in milliseconds
animation_frame_delay = 5,
-- Stop the animation after some time
-- 0 -> Run forever
-- 1..2e12 -> Stop the animation after this many seconds
animation_timeout_sec = 0,
-- The character used to mask the password
-- You can either type it directly as a UTF-8 character (like *), or use a UTF-32
-- codepoint (for example 0x2022 for a bullet point)
-- If null, the password will be hidden
-- Note: you can use a # by escaping it like so: \#
asterisk = '*',
-- The number of failed authentications before a special animation is played... ;)
-- If set to 0, the animation will never be played
auth_fails = 10,
-- Automatic login configuration
-- This feature allows Ly to automatically log in a user without password prompt.
-- IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work.
-- Autologin only happens once at startup - it won't re-trigger after logout.
-- PAM service name to use for automatic login
-- The default service (ly-autologin) uses pam_permit to allow login without password
-- The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically
auto_login_service = "ly-autologin",
-- Session name to launch automatically
-- To find available session names, check the .desktop files in:
-- - /usr/share/xsessions/ (for X11 sessions)
-- - /usr/share/wayland-sessions/ (for Wayland sessions)
-- Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field
-- Examples: "i3", "sway", "gnome", "plasma", "xfce"
-- If null, automatic login is disabled
auto_login_session = nil,
-- Username to automatically log in
-- Must be a valid user on the system
-- If null, automatic login is disabled
auto_login_user = nil,
-- Identifier for battery whose charge to display at top left
-- Primary battery is usually BAT0 or BAT1
-- If set to null, battery status won't be shown
-- Unused on FreeBSD (a sysctl is used there)
battery_id = nil,
-- Background color id
bg = 0x00000000,
-- Change the state and language of the big clock
-- none -> Disabled (default)
-- en -> English
-- fa -> Farsi
bigclock = "none",
-- Set bigclock to 12-hour notation.
bigclock_12hr = false,
-- Set bigclock to show the seconds.
bigclock_seconds = false,
-- Blank main box background
-- Setting to false will make it transparent
blank_box = true,
-- Border foreground color id
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.5
box_position_v = 0.5,
-- Title to show at the top of the main box
-- If set to null, none will be shown
box_title = nil,
-- Brightness decrease command
brightness_down_cmd = "$PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%-",
-- Brightness decrease key combination
-- If null, the keybind is disabled and isn't shown
brightness_down_key = "F5",
-- Brightness increase command
brightness_up_cmd = "$PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%",
-- Brightness increase key combination
-- If null, the keybind is disabled and isn't shown
brightness_up_key = "F6",
-- Erase password input on failure
clear_password = false,
-- Format string for clock in top right corner (see strftime specification). Example: %c
-- If null, the clock won't be shown
clock = nil,
-- CMatrix animation foreground color id
cmatrix_fg = 0x0000FF00,
-- CMatrix animation character string head color id
cmatrix_head_col = 0x01FFFFFF,
-- CMatrix animation minimum codepoint. It uses a 16-bit integer
-- For Japanese characters for example, you can use 0x3000 here
cmatrix_min_codepoint = 0x21,
-- CMatrix animation maximum codepoint. It uses a 16-bit integer
-- For Japanese characters for example, you can use 0x30FF here
cmatrix_max_codepoint = 0x7B,
-- Color mixing animation first color id
colormix_col1 = 0x00FF0000,
-- Color mixing animation second color id
colormix_col2 = 0x000000FF,
-- Color mixing animation third color id
colormix_col3 = 0x20000000,
-- Screen corners customization
-- Keywords:
-- shutdown -> Shutdown key
-- restart -> Restart key
-- britup -> Brightness up key
-- britdown -> Brightness down key
-- password -> Toggle password key
-- clock -> Clock (format defined by 'clock' option)
-- tty -> Active TTY number
-- battery -> Battery percentage
-- version -> Ly version string
-- numlock -> Numlock state
-- capslock -> Capslock state
-- labels -> All custom info labels (lbl:)
-- binds -> All custom keybind hints (cmd:)
-- lbl:name -> Specific custom info label
-- cmd:key -> Specific custom keybind hint
--
-- If using a keyword that groups multiple labels into one (e.g. labels, binds),
-- they'll be placed horizontally
--
-- Also, the order defines the vertical stack (first item is at the edge)
-- If items are separted by commas, they'll be placed horizontally
-- It is possible to have both horizontal and vertical items on the same corner
-- Bottom left
corner_bottom_left = "version",
-- Bottom right
corner_bottom_right = "labels",
-- Top left
corner_top_left = "shutdown,restart,britup,britdown,password battery binds",
-- Top right
corner_top_right = "clock numlock,capslock",
-- 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 = nil,
-- Custom sessions directory
-- You can specify multiple directories,
-- e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
custom_sessions = "$CONFIG_DIRECTORY/ly/custom-sessions",
-- Input box active by default on startup
-- Available inputs: info_line, session, login, password
default_input = "login",
-- DOOM animation fire height (1 thru 9)
doom_fire_height = 6,
-- DOOM animation fire spread (0 thru 4)
doom_fire_spread = 2,
-- DOOM animation custom top color (low intensity flames)
doom_top_color = 0x009F2707,
-- DOOM animation custom middle color (medium intensity flames)
doom_middle_color = 0x00C78F17,
-- DOOM animation custom bottom color (high intensity flames)
doom_bottom_color = 0x00FFFFFF,
-- Dur file path
dur_file_path = "$CONFIG_DIRECTORY/ly/example.dur",
-- Dur 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 offset y direction (value is added to the current position determined by alignment, negatives are supported)
dur_y_offset = 0,
-- Set margin to the edges of the DM (useful for curved monitors)
edge_margin = 0,
-- Error background color id
error_bg = 0x00000000,
-- Error foreground color id
-- Default is red and bold
error_fg = 0x01FF0000,
-- Tally directory for the pam_faillock module (if present)
-- Used for getting if an account is locked or not after too many failed
-- login attempts
-- If directory doesn't exist, lock status won't be checked when
-- authenticating
faillock_tally_dir = "/var/run/faillock",
-- Foreground color id
fg = 0x00FFFFFF,
-- Render true colors (if supported)
-- If false, output will be in eight-color mode
-- All eight-color mode color codes:
-- TB_DEFAULT 0x0000
-- TB_BLACK 0x0001
-- TB_RED 0x0002
-- TB_GREEN 0x0003
-- TB_YELLOW 0x0004
-- TB_BLUE 0x0005
-- TB_MAGENTA 0x0006
-- TB_CYAN 0x0007
-- TB_WHITE 0x0008
-- If full color is off, the styling options still work. The colors are
-- always 32-bit values with the styling in the most significant byte.
-- Note: If using the dur_file animation option and the dur file's color range
-- is saved as 256 with this option disabled, the file will not be drawn.
full_color = true,
-- Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations),
-- 0 -> Pure Conway's Game of Life (will eventually stabilize)
-- 10 -> Add entropy every 10 generations (recommended for continuous activity)
-- 50+ -> Less frequent entropy for more natural evolution
gameoflife_entropy_interval = 10,
-- Game of Life animation foreground color id
gameoflife_fg = 0x0000FF00,
-- Game of Life frame delay (lower = faster animation, higher = slower),
-- 1-3 -> Very fast animation
-- 6 -> Default smooth animation speed
-- 10+ -> Slower, more contemplative speed
gameoflife_frame_delay = 6,
-- Game of Life initial cell density (0.0 to 1.0)
-- 0.1 -> Sparse, minimal activity
-- 0.4 -> Balanced activity (recommended)
-- 0.7+ -> Dense, chaotic patterns
gameoflife_initial_density = 0.4,
-- Sets the multiple amounts of neighbors needed for a cell to be born.
-- Each numerical digit from 0 to 8 inclusive in this string becomes
-- one of the targets required for this cell to be born.
-- Example: a string of "3" makes the cell be born on 3 neighbors
gameoflife_param_birth = "3",
-- Sets the multiple amounts of neighbors needed for a cell to survive.
-- Each numerical digit from 0 to 8 inclusive in this string becomes
-- one of the targets required for this cell to survive.
-- Example: a string of "23" makes the cell survive on 2 or 3 neighbors
gameoflife_param_survival = "23",
-- Sets the TTY on which to always grab focus on.
-- This is useful if you want to have deterministic behavior when starting
-- Ly on multiple TTYs simultaneously.
-- If null, Ly will always try to grab focus on the TTY it is launched on,
-- even if multiple instances are launched.
grab_focus_tty = nil,
-- Remove main box borders
hide_borders = false,
-- Command executed when no input is detected for a certain time
-- If null, no command will be executed
inactivity_cmd = nil,
-- Executes a command after a certain amount of seconds
inactivity_delay = 0,
-- Initial text to show on the info line
-- If set to null, the info line defaults to the hostname
initial_info_text = nil,
-- Input boxes length
input_len = 34,
-- Active language
-- Available languages are found in $CONFIG_DIRECTORY/ly/lang/
lang = "en",
-- Command executed when logging in
-- If null, no command will be executed
-- Important: the code itself must end with `exec "$@"` in order to launch the session!
-- You can also set environment variables in there, they'll persist until logout
login_cmd = nil,
-- Path for login.defs file (used for listing all local users on the system on
-- Linux)
login_defs_path = "/etc/login.defs",
-- Command executed when logging out
-- If null, no command will be executed
-- Important: the session will already be terminated when this command is executed, so
-- no need to add `exec "$@"` at the end
logout_cmd = nil,
-- The file pointing to the Lua file to be used when using the Lua animation option
lua_animation_file = "$CONFIG_DIRECTORY/ly/example.lua",
-- General log file path
-- If null, syslog will be used instead
ly_log = "/var/log/ly.log",
-- Main box horizontal margin
margin_box_h = 2,
-- Main box vertical margin
margin_box_v = 1,
-- Set numlock on/off at startup
numlock = false,
-- Default path
-- If null, ly doesn't set a path
path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
-- Specifies the key combination used for restart
-- If null, the keybind is disabled and isn't shown
restart_key = "F2",
-- Absolute directory for the save file
-- If null, current desktop & login won't be saved nor loaded
save_file_dir = "$CONFIG_DIRECTORY/ly",
-- Service name (set to ly to use the provided pam config file)
service_name = "ly",
-- Session log file path
-- This will contain stdout and stderr of Wayland sessions
-- By default it's saved in the user's home directory
-- Important: due to technical limitations, X11, shell sessions as well as
-- 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
session_log = ".local/state/ly-session.log",
-- Setup command
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
-- If null, the keybind is disabled and isn't shown
show_password_key = "F7",
-- Specifies the key combination used for shutdown
-- If null, the keybind is disabled and isn't shown
shutdown_key = "F1",
-- Command executed when starting Ly (before the TTY is taken control of)
-- See file at path below for an example of changing the default TTY colors
start_cmd = "$CONFIG_DIRECTORY/ly/startup.sh",
-- Center the session name.
text_in_center = false,
-- If true, user will need to manually type username instead of selecting from the list
-- of discovered users
type_username = false,
-- Default vi mode
-- normal -> normal mode
-- insert -> insert mode
vi_default_mode = "normal",
-- Enable vi keybindings
vi_mode = false,
-- Wayland desktop environments
-- You can specify multiple directories,
-- 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",
-- Xorg server command
-- Add the -quiet argument to hide startup logs from the server
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 = nil,
-- Xorg xauthority edition tool
xauth_cmd = "$PREFIX_DIRECTORY/bin/xauth",
-- xinitrc
-- If null, the xinitrc session will be hidden
xinitrc = "~/.xinitrc",
-- Xorg desktop environments
-- You can specify multiple directories,
-- e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
-- If null, X11 sessions will not be shown
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 code.
custom_commands = {
{
-- Declare a command with the F8 binding.
binding = "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.
name = "custom command $brightness_up",
cmd = "touch /tmp/ly.gaming",
},
},
-- custom_labels = {
-- {
-- -- Declare a label with an ID. This ID should be unique across all labels.
-- label = "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,
-- },
-- }
}
+1 -1
View File
@@ -18,7 +18,7 @@
--
-- For arguments fg and bg: they are colors in the format
-- 0xSSRRGGBB, where SS is for styling. See your
-- config.ini for more details.
-- config.ini or config.lua for more details.
--
-- For the byte argument, you may use string.byte to fill this argument.
--
+2 -1
View File
@@ -6,6 +6,7 @@ custom = مخصص
custom_info_err_output_long = الإخراج طويل جداً
custom_info_err_no_output = لا يوجد إخراج
custom_info_err_no_output_error = ، خطأ محتمل
err_alloc = فشل في تخصيص الذاكرة
err_args = تعذر تحليل وسيطات سطر الأوامر
err_autologin_session = لم يتم العثور على جلسة تسجيل الدخول التلقائي
@@ -33,7 +34,6 @@ err_pam_abort = تم إلغاء معاملة PAM
err_pam_acct_expired = الحساب منتهي الصلاحية
err_pam_auth = خطأ في المصادقة (Authentication error)
err_pam_authinfo_unavail = فشل في الحصول على معلومات المستخدم
err_pam_authok_reqd = انتهت صلاحية رمز المصادقة (Token)
err_pam_buf = خطأ في ذاكرة التخزين المؤقت (Buffer)
err_pam_cred_err = فشل في تعيين بيانات الاعتماد (Credentials)
err_pam_cred_expired = بيانات الاعتماد منتهية الصلاحية
@@ -77,6 +77,7 @@ shell = shell
shutdown = ايقاف التشغيل
sleep = وضع السكون
toggle_password = إظهار/إخفاء كلمة المرور
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = персонализирано
custom_info_err_output_long = резултатът е твърде дълъг
custom_info_err_no_output = няма резултат
custom_info_err_no_output_error = , възможна грешка
err_acc_locked = профилът е заключен, твърде много неуспешни опити
err_alloc = неуспешно заделяне на памет
err_args = неуспешен анализ на аргументите от командния ред
err_autologin_session = сесията за автоматично влизане не е намерена
@@ -33,7 +34,6 @@ err_pam_abort = прекратена транзакция
err_pam_acct_expired = изтекъл профил
err_pam_auth = грешка при удостоверяването
err_pam_authinfo_unavail = неуспешно получаване на информация за потребителя
err_pam_authok_reqd = изтекъл жетон
err_pam_buf = грешка в буфера на паметта
err_pam_cred_err = неуспешно задаване на удостоверения
err_pam_cred_expired = изтекли удостоверения
@@ -77,6 +77,7 @@ shell = обвивка
shutdown = изключване
sleep = заспиване
toggle_password = превключване на паролата
token_expired = паролата е изтекла, моля, нулирайте паролата си
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_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
@@ -33,7 +34,6 @@ err_pam_abort = transacció pam avortada
err_pam_acct_expired = compte expirat
err_pam_auth = error d'autenticació
err_pam_authinfo_unavail = error en obtenir la informació de l'usuari
err_pam_authok_reqd = token expirat
err_pam_buf = error en la memòria intermèdia
err_pam_cred_err = error en establir les credencials
err_pam_cred_expired = credencials expirades
@@ -77,6 +77,7 @@ shell = shell
shutdown = aturar
sleep = suspendre
toggle_password = mostrar/amagar contrasenya
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_args = nelze analyzovat argumenty příkazového řádku
err_autologin_session = relace automatického přihlášení nebyla nalezena
@@ -33,7 +34,6 @@ err_pam_abort = pam transakce přerušena
err_pam_acct_expired = platnost účtu vypršela
err_pam_auth = chyba autentizace
err_pam_authinfo_unavail = nelze získat informace o uživateli
err_pam_authok_reqd = platnost tokenu vypršela
err_pam_buf = chyba vyrovnávací paměti
err_pam_cred_err = nelze nastavit pověření
err_pam_cred_expired = platnost pověření vypršela
@@ -77,6 +77,7 @@ shell = příkazový řádek
shutdown = vypnout
sleep = uspat
toggle_password = zobrazit/skrýt heslo
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_args = Kommandozeilenargumente konnten nicht verarbeitet werden
err_autologin_session = Autologin-Sitzung nicht gefunden
@@ -33,7 +34,6 @@ err_pam_abort = PAM-Transaktion abgebrochen
err_pam_acct_expired = Benutzerkonto abgelaufen
err_pam_auth = Authentifizierungsfehler
err_pam_authinfo_unavail = Abrufen der Benutzerinformationen fehlgeschlagen
err_pam_authok_reqd = Passwort abgelaufen
err_pam_buf = Speicherpufferfehler
err_pam_cred_err = Fehler beim Setzen der Anmeldedaten
err_pam_cred_expired = Anmeldedaten abgelaufen
@@ -77,6 +77,7 @@ shell = Shell
shutdown = Herunterfahren
sleep = Sleep
toggle_password = Passwort anzeigen/verbergen
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_acc_locked = account locked, too many attempts
err_alloc = failed memory allocation
err_args = unable to parse command line arguments
err_autologin_session = autologin session not found
@@ -33,7 +34,6 @@ err_pam_abort = pam transaction aborted
err_pam_acct_expired = account expired
err_pam_auth = authentication error
err_pam_authinfo_unavail = failed to get user info
err_pam_authok_reqd = token expired
err_pam_buf = memory buffer error
err_pam_cred_err = failed to set credentials
err_pam_cred_expired = credentials expired
@@ -77,6 +77,7 @@ shell = shell
shutdown = shutdown
sleep = sleep
toggle_password = toggle password
token_expired = password expired, please reset
wayland = wayland
x11 = x11
xinitrc = xinitrc
+33 -32
View File
@@ -1,15 +1,16 @@
authenticating = aŭtentigado...
authenticating = aŭtentigante...
brightness_down = malpliigi helecon
brightness_up = pliigi helecon
capslock = majuskla baskulo
custom = propra
custom_info_err_output_long = eligo tro longa
custom_info_err_output_long = eligo estas tro longa
custom_info_err_no_output = neniu eligo
custom_info_err_no_output_error = , ebla eraro
err_alloc = malsukcesis memorasignon
err_acc_locked = konto ŝlosiĝis, tro multaj provoj
err_alloc = malsukcesis memorasigno
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_autologin_session = aŭtomata ensaluta seanco ne troviĝis
err_bounds = indico estas ekster la limoj
err_brightness_change = malsukcesis ŝanĝi la helecon
err_chdir = malsukcesis malfermi hejman dosierujon
err_clock_too_long = horloĝa ĉeno estas tro longa
@@ -17,33 +18,32 @@ 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_empty_password = malplena pasvorto malpermesiĝas
err_envlist = malsukcesis preni la medivariablojn
err_get_active_tty = malsukcesis preni la aktivan TTY-on
err_get_active_tty = malsukcesis preni la aktivan TTY
err_hibernate = malsukcesis ruli la komandon por diskodormo
err_hostname = malsukcesis preni la sistemnomon
err_inactivity = malsukcesis ruli la agorditan komandon por malaktiveco
err_inactivity = malsukcesis ruli la komandon de 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_null = nula referenco
err_numlock = malsukcesis agordi nombran baskulon
err_pam = PAM transakcio malsukcesis
err_pam_abort = PAM 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_buf = memorbufra eraro
err_pam_cred_err = malsukcesis agordi akreditilojn
err_pam_cred_expired = akreditiloj eksvalidiĝis
err_pam_cred_insufficient = nesufiĉaj akreditiloj
err_pam_cred_unavail = malsukcesis preni akreditilojn
err_pam_maxtries = atingis limon 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_pam_session = seanca eraro
err_pam_sys = sistema eraro
err_pam_user_unknown = nekonita uzanto
err_path = malsukcesis agordi la median dosierindikon
err_perm_dir = malsukcesis ŝanĝi la nunan dosierujon
err_perm_group = malsukcesis redukti grupajn permesojn
@@ -52,22 +52,22 @@ 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_switch_tty = malsukcesis ŝanĝi TTY
err_tty_ctrl = stira transigo de TTY malsukcesis
err_no_users = neniu uzanto troviĝas
err_uid_range = malsukcesis dinamike preni intervalon de UID
err_user_gid = malsukcesis agordi uzantan GID
err_user_init = malsukcesis pravalorizi uzanton
err_user_uid = malsukcesis agordi uzantan UID
err_xauth = malsukcesis plenumi xauth
err_xcb_conn = malsukcesis konekti 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
no_x11_support = x11 estis forigita dum kompilado
normal = normala
numlock = numera baskulo
other = alia
@@ -77,6 +77,7 @@ shell = ŝelo
shutdown = malŝalti
sleep = memordormi
toggle_password = montri/kaŝi pasvorton
token_expired = pasvorto eksvalidiĝis, bonvolu reagordi ĝin
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_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
@@ -33,7 +34,6 @@ err_pam_abort = transacción pam abortada
err_pam_acct_expired = cuenta expirada
err_pam_auth = error de autenticación
err_pam_authinfo_unavail = error al obtener información del usuario
err_pam_authok_reqd = token expirado
err_pam_buf = error de la memoria intermedia
err_pam_cred_err = error al establecer las credenciales
err_pam_cred_expired = credenciales expiradas
@@ -77,6 +77,7 @@ shell = shell
shutdown = apagar
sleep = suspender
toggle_password = mostrar/ocultar contraseña
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_acc_locked = compte bloqué, trop de tentatives
err_alloc = échec d'allocation mémoire
err_args = échec de l'analyse des arguments en lignes de commande
err_autologin_session = session de connexion automatique introuvable
@@ -33,7 +34,6 @@ err_pam_abort = transaction pam avortée
err_pam_acct_expired = compte expiré
err_pam_auth = erreur d'authentification
err_pam_authinfo_unavail = échec de l'obtention des infos utilisateur
err_pam_authok_reqd = tiquet expiré
err_pam_buf = erreur de mémoire tampon
err_pam_cred_err = échec de la modification des identifiants
err_pam_cred_expired = identifiants expirés
@@ -77,6 +77,7 @@ shell = shell
shutdown = éteindre
sleep = veille
toggle_password = afficher le mot de passe
token_expired = mot de passe expiré, veuillez le changer
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_args = impossibile analizzare gli argomenti della riga di comando
err_autologin_session = sessione di accesso automatico non trovata
@@ -33,7 +34,6 @@ err_pam_abort = transazione PAM interrotta
err_pam_acct_expired = account scaduto
err_pam_auth = errore di autenticazione
err_pam_authinfo_unavail = impossibile ottenere informazioni utente
err_pam_authok_reqd = token scaduto
err_pam_buf = errore buffer memoria
err_pam_cred_err = impossibile impostare credenziali
err_pam_cred_expired = credenziali scadute
@@ -77,6 +77,7 @@ shell = shell
shutdown = arresto
sleep = sospendi
toggle_password = mostra/nascondi password
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = カスタム
custom_info_err_output_long = 出力が長すぎます
custom_info_err_no_output = 出力なし
custom_info_err_no_output_error = 、エラーの可能性あり
err_alloc = メモリ割り当て失敗
err_args = コマンドライン引数を解析できません
err_autologin_session = 自動ログインセッションが見つかりません
@@ -33,7 +34,6 @@ 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 = 認証情報の有効期限が切れています
@@ -77,6 +77,7 @@ shell = シェル
shutdown = シャットダウン
sleep = スリープ
toggle_password = パスワードの表示/非表示
wayland = Wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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
@@ -33,7 +34,6 @@ 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
@@ -77,6 +77,7 @@ shell = shell
shutdown = vemirîne
sleep = têxîne xewê
toggle_password = şîfre nîşan bide/veşêre
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_args = nevar parsēt komandrindas argumentus
err_autologin_session = automātiskās pieteikšanās sesija nav atrasta
@@ -33,7 +34,6 @@ err_pam_abort = pam transakcija pārtraukta
err_pam_acct_expired = konts novecojis
err_pam_auth = autentifikācijas kļūda
err_pam_authinfo_unavail = neizdevās iegūt lietotāja informāciju
err_pam_authok_reqd = žetons beidzies
err_pam_buf = atmiņas bufera kļūda
err_pam_cred_err = neizdevās iestatīt akreditācijas datus
err_pam_cred_expired = akreditācijas dati novecojuši
@@ -77,6 +77,7 @@ shell = terminālis
shutdown = izslēgt
sleep = snauda
toggle_password = rādīt/slēpt paroli
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_args = nie można przetworzyć argumentów wiersza poleceń
err_autologin_session = nie znaleziono sesji autologowania
@@ -33,7 +34,6 @@ err_pam_abort = transakcja pam przerwana
err_pam_acct_expired = konto wygasło
err_pam_auth = błąd uwierzytelniania
err_pam_authinfo_unavail = nie udało się zdobyć informacji o użytkowniku
err_pam_authok_reqd = token wygasł
err_pam_buf = błąd bufora pamięci
err_pam_cred_err = nie udało się ustawić uwierzytelnienia
err_pam_cred_expired = uwierzytelnienie wygasło
@@ -77,6 +77,7 @@ shell = powłoka
shutdown = wyłącz
sleep = uśpij
toggle_password = Pokaż/ukryj hasło
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_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
@@ -33,7 +34,6 @@ err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada
err_pam_auth = erro de autenticação
err_pam_authinfo_unavail = erro ao obter informação do utilizador
err_pam_authok_reqd = token expirado
err_pam_buf = erro de buffer de memória
err_pam_cred_err = erro ao definir credenciais
err_pam_cred_expired = credenciais expiradas
@@ -77,6 +77,7 @@ shell = shell
shutdown = encerrar
sleep = suspender
toggle_password = mostrar/ocultar palavra-passe
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_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
@@ -33,7 +34,6 @@ err_pam_abort = transação pam abortada
err_pam_acct_expired = conta expirada
err_pam_auth = erro de autenticação
err_pam_authinfo_unavail = não foi possível obter informações do usuário
err_pam_authok_reqd = token expirado
err_pam_buf = erro de buffer de memória
err_pam_cred_err = erro para definir credenciais
err_pam_cred_expired = credenciais expiradas
@@ -77,6 +77,7 @@ shell = shell
shutdown = desligar
sleep = suspender
toggle_password = mostrar/ocultar senha
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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ă
@@ -33,7 +34,6 @@ err_pam_abort = tranzacţie pam anulată
err_pam_acct_expired = cont expirat
err_pam_auth = eroare de autentificare
err_pam_authinfo_unavail = nu s-au putut obţine informaţii despre utilizator
err_pam_authok_reqd = token expirat
err_pam_buf = eroare de memorie (buffer)
err_pam_cred_err = nu s-au putut seta date de identificare (credentials)
err_pam_cred_expired = datele de identificare (credentials) au expirat
@@ -77,6 +77,7 @@ shell = shell
shutdown = opreşte sistemul
sleep = repaus
toggle_password = afișare/ascundere parolă
wayland = wayland
x11 = x11
xinitrc = xinitrc
+24 -23
View File
@@ -6,39 +6,39 @@ custom = пользовательский
custom_info_err_output_long = вывод слишком длинный
custom_info_err_no_output = нет вывода
custom_info_err_no_output_error = , возможная ошибка
err_acc_locked = аккаунт заблокирован, слишком много попыток
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_crawl = не удалось просканировать каталоги сессий
err_dgn_oob = отладочное сообщение
err_domain = неверный домен
err_empty_password = пустой пароль не допустим
err_empty_password = пустой пароль недопустим
err_envlist = не удалось получить список переменных среды
err_get_active_tty = не удалось получить активный tty
err_hibernate = не удалось выполнить команду гибернации
err_hostname = не удалось получить имя хоста
err_inactivity = не удалось выполнить команду бездействия
err_lock_state = не удалось получить состояние lock
err_log = не удалось открыть файл log
err_inactivity = не удалось выполнить команду перехода в режим бездействия
err_lock_state = не удалось получить состояние блокировки
err_log = не удалось открыть файл журнала
err_mlock = сбой блокировки памяти
err_null = нулевой указатель
err_numlock = не удалось установить numlock
err_pam = pam транзакция не удалась
err_pam_abort = pam транзакция прервана
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_cred_err = не удалось установить учётные данные
err_pam_cred_expired = срок действия учётных данных истёк
err_pam_cred_insufficient = недостаточно учётных данных
err_pam_cred_unavail = не удалось получить учётные данные
err_pam_maxtries = лимит попыток исчерпан
err_pam_perm_denied = доступ запрещён
err_pam_session = ошибка сессии
@@ -49,7 +49,7 @@ err_perm_dir = не удалось изменить текущий катало
err_perm_group = не удалось понизить права доступа группы
err_perm_user = не удалось понизить права доступа пользователя
err_pwnam = не удалось получить информацию о пользователе
err_sleep = не удалось выполнить команду sleep
err_sleep = не удалось выполнить команду перехода в спящий режим
err_start = не удалось выполнить команду запуска
err_battery = не удалось получить статус батареи
err_switch_tty = не удалось переключить tty
@@ -59,24 +59,25 @@ err_uid_range = не удалось динамически получить ди
err_user_gid = не удалось установить GID пользователя
err_user_init = не удалось инициализировать пользователя
err_user_uid = не удалось установить UID пользователя
err_xauth = команда xauth не выполнена
err_xauth = ошибка выполнения команды xauth
err_xcb_conn = ошибка подключения xcb
err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку
err_xsessions_dir = не удалось найти папку сессий
err_xsessions_open = не удалось открыть папку сессий
hibernate = гибернация
insert = вставка
login = логин
logout = вышел из системы
login = вход
logout = выход из системы
no_x11_support = поддержка x11 отключена при компиляции
normal = обычный
numlock = numlock
other = прочие
other = другое
password = пароль
restart = перезагрузить
shell = оболочка
shutdown = выключить
sleep = сон
sleep = спящий режим
toggle_password = показать/скрыть пароль
token_expired = срок действия пароля истёк, выполните сброс
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = prilagođeno
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
@@ -33,7 +34,6 @@ err_pam_abort = pam transakcija prekinuta
err_pam_acct_expired = nalog istekao
err_pam_auth = greška pri autentikaciji
err_pam_authinfo_unavail = neuspješno uzimanje informacija o korisniku
err_pam_authok_reqd = token istekao
err_pam_buf = greška bafera memorije
err_pam_cred_err = neuspješno postavljanje kredencijala
err_pam_cred_expired = kredencijali istekli
@@ -77,6 +77,7 @@ shell = shell
shutdown = ugasi
sleep = uspavaj
toggle_password = prikaži/sakrij lozinku
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = прилагођено
custom_info_err_output_long = излаз предугачак
custom_info_err_no_output = нема излаза
custom_info_err_no_output_error = , могућа грешка
err_alloc = неуспешна алокација меморије
err_args = није могуће рашчланити аргументе командне линије
err_autologin_session = сесија аутоматске пријаве није пронађена
@@ -33,7 +34,6 @@ 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 = акредитиви истекли
@@ -77,6 +77,7 @@ shell = shell
shutdown = угаси
sleep = успавај
toggle_password = прикажи/сакриј лозинку
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = anpassad
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_alloc = minnesallokering misslyckades
err_args = tolkning av kommandoargument misslyckades
err_autologin_session = autologin-session hittades inte
@@ -33,7 +34,6 @@ err_pam_abort = pam-transaktion avbröts
err_pam_acct_expired = kontot har löpt ut
err_pam_auth = autentisering misslyckades
err_pam_authinfo_unavail = hämtning av användarinformation misslyckades
err_pam_authok_reqd = token har löpt ut
err_pam_buf = minnesbufferfel
err_pam_cred_err = inställning av inloggningsuppgifter misslyckades
err_pam_cred_expired = inloggningsuppgifterna har löpt ut
@@ -77,6 +77,7 @@ shell = shell
shutdown = stäng av
sleep = viloläge
toggle_password = visa/dölj lösenord
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ 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_args = komut satırı argümanları ayrıştırılamıyor
err_autologin_session = otomatik oturum açma oturumu bulunamadı
@@ -33,7 +34,6 @@ err_pam_abort = pam islemi durduruldu
err_pam_acct_expired = hesabin suresi dolmus
err_pam_auth = kimlik dogrulama hatasi
err_pam_authinfo_unavail = kullanici bilgileri getirilirken hata olustu
err_pam_authok_reqd = suresi dolmus token
err_pam_buf = bellek arabellegi hatasi
err_pam_cred_err = kimlik bilgileri ayarlanamadi
err_pam_cred_expired = kimlik bilgilerinin suresi dolmus
@@ -77,6 +77,7 @@ shell = shell
shutdown = makineyi kapat
sleep = uykuya al
toggle_password = parolayı göster/gizle
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = власний
custom_info_err_output_long = вивід занадто довгий
custom_info_err_no_output = немає виводу
custom_info_err_no_output_error = , можлива помилка
err_alloc = невдале виділення пам'яті
err_args = не вдалося розібрати аргументи командного рядка
err_autologin_session = сеанс автоматичного входу не знайдено
@@ -33,7 +34,6 @@ 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 = термін дії повноважень вичерпано
@@ -77,6 +77,7 @@ shell = оболонка
shutdown = вимкнути
sleep = сплячий режим
toggle_password = показати/приховати пароль
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = 自定义
custom_info_err_output_long = 输出过长
custom_info_err_no_output = 无输出
custom_info_err_no_output_error = ,可能有错误
err_alloc = 内存分配失败
err_args = 无法解析命令行参数
err_autologin_session = 未找到自动登录会话
@@ -33,7 +34,6 @@ 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 = 凭据已过期
@@ -77,6 +77,7 @@ shell = shell
shutdown = 关机
sleep = 睡眠
toggle_password = 显示/隐藏密码
wayland = wayland
x11 = x11
xinitrc = xinitrc
+2 -1
View File
@@ -6,6 +6,7 @@ custom = 自訂
custom_info_err_output_long = 輸出過長
custom_info_err_no_output = 無輸出
custom_info_err_no_output_error = ,可能有錯誤
err_alloc = 記憶體配置失敗
err_args = 無法解析命令列參數
err_autologin_session = 找不到自動登入工作階段
@@ -33,7 +34,6 @@ 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 = 憑證已過期
@@ -77,6 +77,7 @@ shell = shell
shutdown = 關機
sleep = 睡眠
toggle_password = 顯示/隱藏密碼
wayland = wayland
x11 = x11
xinitrc = xinitrc
+4 -4
View File
@@ -70,10 +70,6 @@ if [ "$XDG_SESSION_TYPE" = "x11" ]; then
done
fi
if [ -f "$USERXSESSION" ]; then
. "$USERXSESSION"
fi
if [ -d "$CONFIG_DIRECTORY"/X11/Xresources ]; then
for i in "$CONFIG_DIRECTORY"/X11/Xresources/*; do
[ -f "$i" ] && xrdb -merge "$i"
@@ -105,6 +101,10 @@ if [ "$XDG_SESSION_TYPE" = "x11" ]; then
fi
done
fi
if [ -f "$USERXSESSION" ]; then
. "$USERXSESSION"
fi
fi
exec "$@"
+21 -5
View File
@@ -39,6 +39,11 @@ animation_frame_delay: u16,
dead_cell: Cell,
width: usize,
height: usize,
// Where N is the amount of neighbors, determines if the cell can survive/be born
// by checking if the Nth bit (where if N == 0, it is the least significant bit,
// or 2 to the power of 0) is set to 1.
cell_survival: u9,
cell_birth: u9,
pub fn init(
allocator: Allocator,
@@ -50,6 +55,8 @@ pub fn init(
animate: *bool,
timeout_sec: u12,
animation_frame_delay: u16,
string_cell_survival: []const u8,
string_cell_birth: []const u8,
) !GameOfLife {
const width = terminal_buffer.width;
const height = terminal_buffer.height;
@@ -77,6 +84,16 @@ pub fn init(
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
.width = width,
.height = height,
.cell_birth = 0,
.cell_survival = 0,
};
for (string_cell_survival) |c| switch (c) {
'0'...'8' => game.cell_survival |= (@as(u9, 1) << @truncate(@as(u9, c - '0'))),
else => {},
};
for (string_cell_birth) |c| switch (c) {
'0'...'8' => game.cell_birth |= (@as(u9, 1) << @truncate(@as(u9, c - '0'))),
else => {},
};
// Initialize grid
@@ -173,11 +190,10 @@ fn updateGeneration(self: *GameOfLife) void {
const is_alive = self.current_grid[index];
// Optimized rule application
self.next_grid[index] = switch (neighbors) {
2 => is_alive,
3 => true,
else => false,
};
self.next_grid[index] = if (is_alive)
self.cell_survival & (@as(u9, 1) << @truncate(neighbors)) != 0
else
self.cell_birth & (@as(u9, 1) << @truncate(neighbors)) != 0;
}
}
+1 -1
View File
@@ -8,7 +8,7 @@ const Allocator = std.mem.Allocator;
const InfoLine = @import("../components/InfoLine.zig");
const Lang = @import("../config/Lang.zig");
const zlua = @import("zlua");
const zlua = ly_ui.ly_core.zlua;
const ly_lua = @embedFile("ly.lua");
+114 -21
View File
@@ -20,12 +20,33 @@ pub const AuthOptions = struct {
xauth_cmd: []const u8,
setup_cmd: []const u8,
login_cmd: ?[]const u8,
faillock_tally_dir: []const u8,
x_cmd: []const u8,
x_vt: ?u8,
session_pid: std.posix.pid_t,
use_kmscon_vt: bool,
};
const PamAppdata = struct {
username: []const u8,
password: []const u8,
new_authtok_requested: bool,
authreq_responded: bool,
new_password: []const u8,
};
//https://github.com/linux-pam/linux-pam/blob/master/modules/pam_faillock/faillock.h#L55
const PamFaillockEntry = extern struct {
pub const STATUS_VALID: usize = 0x1;
pub const STATUS_RHOST: usize = 0x2;
pub const STATUS_TTY: usize = 0x4;
source: [52]u8,
reserved: u16,
status: u16,
time: u64,
};
var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(sig: std.posix.SIG) callconv(.c) void {
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, sig);
@@ -36,7 +57,24 @@ pub fn sessionSignalHandler(sig: std.posix.SIG) callconv(.c) void {
if (child_pid > 0) _ = std.c.kill(child_pid, sig);
}
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 {
pub fn authenticate(
allocator: std.mem.Allocator,
io: std.Io,
log_file: *LogFile,
options: AuthOptions,
current_environment: Environment,
login: []const u8,
password: []const u8,
maybe_new_password: ?[]const u8,
) !void {
const lock_path = try std.fs.path.join(allocator, &.{ options.faillock_tally_dir, login });
defer allocator.free(lock_path);
var faillock_entries: usize = 0;
if (try fileExists(io, lock_path)) {
faillock_entries = try getFaillockEntries(io, lock_path);
}
var tty_buffer: [3]u8 = undefined;
const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty});
@@ -49,13 +87,14 @@ pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile
// Open the PAM session
try log_file.info(io, "auth/pam", "encoding credentials", .{});
const login_z = try allocator.dupeSentinel(u8, login, 0);
defer allocator.free(login_z);
const password_z = try allocator.dupeSentinel(u8, password, 0);
defer allocator.free(password_z);
var credentials = [_:null]?[*:0]const u8{ login_z, password_z };
var credentials: PamAppdata = .{
.username = login,
.password = password,
.new_authtok_requested = false,
.authreq_responded = false,
.new_password = "",
};
const conv = interop.pam.pam_conv{
.conv = loginConv,
@@ -76,10 +115,24 @@ pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile
// Do the PAM routine
try log_file.info(io, "auth/pam", "authenticating", .{});
status = interop.pam.pam_authenticate(handle, 0);
if (status == interop.pam.PAM_AUTH_ERR and try fileExists(io, lock_path)) {
const new_faillock_entries = try getFaillockEntries(io, lock_path);
if (faillock_entries == new_faillock_entries) {
return error.AccountLocked;
}
}
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_file.info(io, "auth/pam", "validating account", .{});
status = interop.pam.pam_acct_mgmt(handle, 0);
if (status == interop.pam.PAM_NEW_AUTHTOK_REQD) {
if (maybe_new_password) |new_passwsord| {
credentials.new_authtok_requested = true;
credentials.new_password = new_passwsord;
status = interop.pam.pam_chauthtok(handle, interop.pam.PAM_CHANGE_EXPIRED_AUTHTOK);
}
}
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_file.info(io, "auth/pam", "setting credentials", .{});
@@ -98,7 +151,7 @@ pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile
defer interop.closePasswordDatabase();
// Get password structure from username
user_entry = interop.getUsernameEntry(login_z) orelse return error.GetPasswordNameFailed;
user_entry = interop.getUsernameEntry(allocator, login) orelse return error.GetPasswordNameFailed;
}
// Set user shell if it hasn't already been set
@@ -163,6 +216,38 @@ pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile
if (shared_err.readError()) |err| return err;
}
fn fileExists(io: std.Io, path: []const u8) !bool {
var file = std.Io.Dir.openFileAbsolute(io, path, .{}) catch |err| {
if (err == error.FileNotFound) return false;
return err;
};
defer file.close(io);
return true;
}
fn getFaillockEntries(io: std.Io, path: []const u8) !usize {
var file = try std.Io.Dir.openFileAbsolute(io, path, .{});
defer file.close(io);
var buffer: [1024]u8 = undefined;
var reader = file.reader(io, &buffer);
var count: usize = 0;
while (!reader.atEnd()) {
const entry = reader.interface.takeStruct(PamFaillockEntry, .little) catch |err| {
if (err == error.EndOfStream) break;
return err;
};
if (entry.status & PamFaillockEntry.STATUS_VALID != 0) {
count += 1;
}
}
return count;
}
fn startSession(
log_file: *LogFile,
allocator: std.mem.Allocator,
@@ -276,6 +361,7 @@ fn loginConv(
resp: ?*?[*]interop.pam.pam_response,
appdata_ptr: ?*anyopaque,
) callconv(.c) c_int {
const data: *PamAppdata = @ptrCast(@alignCast(appdata_ptr));
const message_count: u32 = @intCast(num_msg);
const messages = msg.?;
@@ -289,20 +375,36 @@ fn loginConv(
var username: ?[:0]u8 = null;
var password: ?[:0]u8 = null;
var status: c_int = interop.pam.PAM_SUCCESS;
defer {
if (status != interop.pam.PAM_SUCCESS) {
// Memory is freed by pam otherwise
allocator.free(response);
if (username) |str| allocator.free(str);
if (password) |str| allocator.free(str);
} else {
resp.?.* = response.ptr;
}
}
for (0..message_count) |i| set_credentials: {
switch (messages[i].?.msg_style) {
interop.pam.PAM_PROMPT_ECHO_ON => {
const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr));
username = allocator.dupeSentinel(u8, std.mem.span(data[0]), 0) catch {
username = allocator.dupeSentinel(u8, data.username, 0) catch {
status = interop.pam.PAM_BUF_ERR;
break :set_credentials;
};
response[i].resp = username.?;
},
interop.pam.PAM_PROMPT_ECHO_OFF => {
const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr));
password = allocator.dupeSentinel(u8, std.mem.span(data[1]), 0) catch {
var pass = data.password;
if (data.new_authtok_requested) {
if (data.authreq_responded) {
pass = data.new_password;
}
data.authreq_responded = true;
}
password = allocator.dupeSentinel(u8, pass, 0) catch {
status = interop.pam.PAM_BUF_ERR;
break :set_credentials;
};
@@ -316,15 +418,6 @@ fn loginConv(
}
}
if (status != interop.pam.PAM_SUCCESS) {
// Memory is freed by pam otherwise
allocator.free(response);
if (username) |str| allocator.free(str);
if (password) |str| allocator.free(str);
} else {
resp.?.* = response.ptr;
}
return status;
}
+5
View File
@@ -21,6 +21,7 @@ bg: u32 = 0x00000000,
bigclock: Bigclock = .none,
bigclock_12hr: bool = false,
bigclock_seconds: bool = false,
bigclock_outline_fg: ?u32 = null,
blank_box: bool = true,
border_fg: u32 = 0x00FFFFFF,
box_position_h: f32 = 0.5,
@@ -58,12 +59,16 @@ dur_y_offset: i32 = 0,
edge_margin: u8 = 0,
error_bg: u32 = 0x00000000,
error_fg: u32 = 0x01FF0000,
faillock_tally_dir: []const u8 = "/var/run/faillock",
fg: u32 = 0x00FFFFFF,
full_color: bool = true,
gameoflife_fg: u32 = 0x0000FF00,
gameoflife_entropy_interval: usize = 10,
gameoflife_frame_delay: usize = 6,
gameoflife_initial_density: f32 = 0.4,
gameoflife_param_birth: []const u8 = "3",
gameoflife_param_survival: []const u8 = "23",
grab_focus_tty: ?u8 = null,
hide_borders: bool = false,
inactivity_cmd: ?[]const u8 = null,
inactivity_delay: u16 = 0,
+3 -2
View File
@@ -1,5 +1,5 @@
//
// NOTE: After editing this file, please run `/res/lang/normalize_lang_files.py`
// NOTE: After editing this file, please run `res/lang/normalize_lang_files.py`
// to update all the language files accordingly.
//
@@ -11,6 +11,7 @@ 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_acc_locked: []const u8 = "account locked, too many attempts",
err_alloc: []const u8 = "failed memory allocation",
err_args: []const u8 = "unable to parse command line arguments",
err_autologin_session: []const u8 = "autologin session not found",
@@ -38,7 +39,6 @@ err_pam_abort: []const u8 = "pam transaction aborted",
err_pam_acct_expired: []const u8 = "account expired",
err_pam_auth: []const u8 = "authentication error",
err_pam_authinfo_unavail: []const u8 = "failed to get user info",
err_pam_authok_reqd: []const u8 = "token expired",
err_pam_buf: []const u8 = "memory buffer error",
err_pam_cred_err: []const u8 = "failed to set credentials",
err_pam_cred_expired: []const u8 = "credentials expired",
@@ -82,6 +82,7 @@ shell: [:0]const u8 = "shell",
shutdown: []const u8 = "shutdown",
sleep: []const u8 = "sleep",
toggle_password: []const u8 = "toggle password",
token_expired: []const u8 = "password expired, please reset",
wayland: []const u8 = "wayland",
x11: []const u8 = "x11",
xinitrc: [:0]const u8 = "xinitrc",
+21 -12
View File
@@ -18,7 +18,7 @@ const Config = @import("Config.zig");
const Lang = @import("Lang.zig");
const OldSave = @import("OldSave.zig");
const SavedUsers = @import("SavedUsers.zig");
const custom = @import("custom.zig");
const custom = ly_core.custom;
const color_properties = [_][]const u8{
"bg",
@@ -74,16 +74,27 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
}
if (std.mem.eql(u8, field.key, "animation")) {
// The option now uses a string (which then gets converted into an enum) instead of an integer
// It also combines the previous "animate" and "animation" options
const animation = std.fmt.parseInt(u8, field.value, 10) catch return field;
string_conversion: {
// The option now uses a string (which then gets converted into an enum) instead of an integer
// It also combines the previous "animate" and "animation" options
const animation = std.fmt.parseInt(u8, field.value, 10) catch break :string_conversion;
var mapped_field = field;
mapped_field.value = switch (animation) {
0 => "doom",
1 => "matrix",
else => "none",
};
return mapped_field;
}
// 'dur_file' was renamed to 'dur'
var mapped_field = field;
mapped_field.value = switch (animation) {
0 => "doom",
1 => "matrix",
else => "none",
};
if (std.mem.eql(u8, mapped_field.value, "dur_file")) {
mapped_field.value = "dur";
}
return mapped_field;
}
@@ -221,9 +232,7 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
// 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.
// compared to the one regarding better methods of X termination.
//
// Thus is my que to leave this TODO at thy request,
//
+1 -1
View File
@@ -6,7 +6,7 @@ pub const Animation = enum {
matrix,
colormix,
gameoflife,
dur_file,
dur,
lua,
};
+186 -73
View File
@@ -19,9 +19,12 @@ const interop = ly_core.interop;
const UidRange = ly_core.UidRange;
const LogFile = ly_core.LogFile;
const SharedError = ly_core.SharedError;
const Parser = ly_core.Parser;
const IniParser = ly_core.IniParser;
const LuaParser = ly_core.LuaParser;
const ini = ly_core.ini;
const Ini = ini.Ini;
const custom = ly_core.custom;
const Cascade = @import("animations/Cascade.zig");
const ColorMix = @import("animations/ColorMix.zig");
@@ -39,7 +42,6 @@ const Lang = @import("config/Lang.zig");
const migrator = @import("config/migrator.zig");
const OldSave = @import("config/OldSave.zig");
const SavedUsers = @import("config/SavedUsers.zig");
const custom = @import("config/custom.zig");
const DisplayServer = @import("enums.zig").DisplayServer;
const Environment = @import("Environment.zig");
const Entry = Environment.Entry;
@@ -111,6 +113,7 @@ const UiState = struct {
login_text: ?*Text,
password: *Text,
password_widget: *Widget,
maybe_old_password: ?[]const u8,
insert_mode: bool,
edge_margin: Position,
config: Config,
@@ -126,6 +129,7 @@ const UiState = struct {
bigclock_buf: [32:0]u8,
custom_binds: std.ArrayList(CustomBindLabel),
custom_info: std.ArrayList(CustomInfoLabel),
tty_cache: [std.math.maxInt(u8)]?u8,
};
var shutdown = false;
@@ -134,6 +138,7 @@ var restart = false;
pub fn main(init: std.process.Init) !void {
var state: UiState = undefined;
state.tty_cache = @splat(null);
state.io = init.io;
var stderr_buffer: [128]u8 = undefined;
@@ -191,7 +196,7 @@ pub fn main(init: std.process.Init) !void {
if (res.args.help != 0) {
try clap.help(stderr, clap.Help, &params, .{});
std.log.info("note: if you want to configure Ly, please check the config file, which is located at " ++ build_options.config_directory ++ "/ly/config.ini.", .{});
std.log.info("note: if you want to configure Ly, please check the config file, which is located at " ++ build_options.config_directory ++ "/ly/config.lua or " ++ build_options.config_directory ++ "/ly/config.ini.", .{});
std.process.exit(0);
}
if (res.args.version != 0) {
@@ -201,22 +206,28 @@ pub fn main(init: std.process.Init) !void {
if (res.args.config) |path| config_parent_path = path;
if (res.args.@"use-kmscon-vt" != 0) state.use_kmscon_vt = true;
if (res.args.@"validate-config") |path| {
var parser = try IniParser(Config).init(
state.allocator,
state.io,
path,
migrator.configFieldHandler,
);
var parser: Parser(Config) = blk: {
if (std.mem.endsWith(u8, path, ".ini")) {
break :blk .{ .ini = try IniParser(Config).init(
state.allocator,
state.io,
path,
migrator.configFieldHandler,
) };
} else {
break :blk .{ .lua = try LuaParser(Config).init(state.allocator, path) };
}
};
defer parser.deinit();
for (parser.errors.items) |err| {
for (parser.errors().items) |err| {
std.log.err(
"failed to convert value '{s}' of option '{s}' to type '{s}': {s}",
.{ err.value, err.key, err.type_name, err.error_name },
);
}
if (parser.maybe_load_error) |err| {
if (parser.maybe_load_error()) |err| {
std.log.err("failed to load config file: {s}", .{@errorName(err)});
std.process.exit(1);
}
@@ -232,12 +243,29 @@ pub fn main(init: std.process.Init) !void {
state.allocator.free(state.old_save_path);
};
const config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
// Test for presence of Lua config file first
// If it fails, fall back to ini
var config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.lua" });
std.Io.Dir.accessAbsolute(state.io, config_path, .{}) catch {
state.allocator.free(config_path);
config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
};
defer state.allocator.free(config_path);
custom.binds = .empty;
custom.labels = .empty;
var config_parser = try IniParser(Config).init(state.allocator, state.io, config_path, migrator.configFieldHandler);
var config_parser: Parser(Config) = blk: {
if (std.mem.endsWith(u8, config_path, ".ini")) {
break :blk .{ .ini = try IniParser(Config).init(
state.allocator,
state.io,
config_path,
migrator.configFieldHandler,
) };
} else {
break :blk .{ .lua = try LuaParser(Config).init(state.allocator, config_path) };
}
};
defer config_parser.deinit();
defer if (!shutdown or !restart) {
var iter = custom.binds.iterator();
@@ -256,7 +284,7 @@ pub fn main(init: std.process.Init) !void {
custom.labels.deinit(temporary_allocator);
};
state.config = config_parser.structure;
state.config = config_parser.structure();
var lang_buffer: [16]u8 = undefined;
const lang_file = try std.fmt.bufPrint(&lang_buffer, "{s}.ini", .{state.config.lang});
@@ -274,7 +302,7 @@ pub fn main(init: std.process.Init) !void {
state.old_save_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.ini" });
}
if (config_parser.maybe_load_error == null) {
if (config_parser.maybe_load_error() == null and config_parser == .ini) {
migrator.lateConfigFieldHandler(&state.config, state.lang);
}
@@ -319,7 +347,8 @@ pub fn main(init: std.process.Init) !void {
}
while (reader.seek < reader.buffer.len) {
const line = reader.takeDelimiterInclusive('\n') catch break;
var line = reader.takeDelimiterInclusive('\n') catch break;
if (std.mem.startsWith(u8, line, "ly/tty")) continue;
var user = std.mem.splitScalar(u8, line[0..(line.len - 1)], ':');
const username = user.next() orelse continue;
@@ -334,19 +363,8 @@ pub fn main(init: std.process.Init) !void {
.allocated_username = true,
});
}
}
// If no save file previously existed, fill it up with all usernames
// TODO: Add new username with existing save file
if (state.config.save_file_dir != null and state.saved_users.user_list.items.len == 0) {
for (usernames.items) |user| {
try state.saved_users.user_list.append(state.allocator, .{
.username = user,
.session_index = 0,
.first_run = true,
.allocated_username = false,
});
}
updateTtyCache(&state, .{ .usernames = usernames.items }) catch break :read_save_file;
}
var log_file_buffer: [1024]u8 = undefined;
@@ -559,6 +577,7 @@ pub fn main(init: std.process.Init) !void {
null,
state.buffer.fg,
state.buffer.bg,
state.config.bigclock_outline_fg,
switch (state.config.bigclock) {
.none, .en => .en,
.fa => .fa,
@@ -643,7 +662,7 @@ pub fn main(init: std.process.Init) !void {
);
}
if (config_parser.maybe_load_error) |load_error| {
if (config_parser.maybe_load_error()) |load_error| {
// We can't localize this since the config failed to load so we'd fallback to the default language anyway
try state.info_line.addMessage(
"unable to parse config file",
@@ -657,7 +676,7 @@ pub fn main(init: std.process.Init) !void {
.{@errorName(load_error)},
);
for (config_parser.errors.items) |err| {
for (config_parser.errors().items) |err| {
try state.log_file.err(
state.io,
"conf",
@@ -871,6 +890,9 @@ pub fn main(init: std.process.Init) !void {
);
defer state.password_label.deinit();
state.maybe_old_password = null;
defer if (state.maybe_old_password) |pass| state.allocator.free(pass);
state.insert_mode = !state.config.vi_mode or state.config.vi_default_mode == .insert;
state.password = try Text.init(
@@ -989,7 +1011,10 @@ pub fn main(init: std.process.Init) !void {
);
break :no_tty_found build_options.fallback_tty;
};
if (!state.use_kmscon_vt) {
if (!state.use_kmscon_vt) switch_tty: {
if (state.config.grab_focus_tty) |tty| {
if (tty != state.active_tty) break :switch_tty;
}
interop.switchTty(state.active_tty) catch |err| {
try state.info_line.addMessage(
state.lang.err_switch_tty,
@@ -1063,10 +1088,12 @@ pub fn main(init: std.process.Init) !void {
&state.animate,
state.config.animation_timeout_sec,
state.config.animation_frame_delay,
state.config.gameoflife_param_survival,
state.config.gameoflife_param_birth,
);
animation = game_of_life.widget();
},
.dur_file => {
.dur => {
var dur = try DurFile.init(
state.allocator,
state.io,
@@ -1121,6 +1148,8 @@ pub fn main(init: std.process.Init) !void {
// Skip if autologin is active to prevent overriding autologin session
var default_input = state.config.default_input;
const min_session_index = if (state.session.label.list.items.len == 0) 0 else state.session.label.list.items.len - 1;
if (state.config.save_file_dir != null and !state.is_autologin) {
if (state.login_text) |box| {
if (state.saved_username) |username| {
@@ -1132,16 +1161,23 @@ pub fn main(init: std.process.Init) !void {
for (state.saved_users.user_list.items) |user| {
if (std.mem.eql(u8, username, user.username)) {
state.session.label.current = @min(user.session_index, state.session.label.list.items.len - 1);
state.session.label.current = @min(user.session_index, min_session_index);
break;
}
}
}
} else if (state.saved_users.last_username_index) |index| load_last_user: {
// If the saved index isn't valid, bail out
if (index >= state.saved_users.user_list.items.len) break :load_last_user;
} else if (state.tty_cache[state.active_tty]) |user_index| {
const user_session_index = state.login.?.label.list.items[user_index].session_index.*;
const user = state.saved_users.user_list.items[index];
state.login.?.label.current = user_index;
state.session.label.current = @min(user_session_index, min_session_index);
} else if (state.saved_users.last_username_index) |last_user_index| load_last_user: {
const saved_users = state.saved_users.user_list.items;
// If the saved index isn't valid, bail out
if (last_user_index >= saved_users.len) break :load_last_user;
const user = saved_users[last_user_index];
// Find user with saved name, and switch over to it
// If it doesn't exist (anymore), we don't change the value
@@ -1154,7 +1190,7 @@ pub fn main(init: std.process.Init) !void {
default_input = .password;
state.session.label.current = @min(user.session_index, state.session.label.list.items.len - 1);
state.session.label.current = @min(user.session_index, min_session_index);
}
}
@@ -1462,23 +1498,6 @@ fn authenticate(ptr: *anyopaque) !bool {
state.config.error_bg,
state.config.error_fg,
);
if (!state.is_autologin) {
state.info_line.clearRendered(state.allocator) catch |err| {
try state.info_line.addMessage(
state.lang.err_alloc,
state.config.error_bg,
state.config.error_fg,
);
try state.log_file.err(
state.io,
"tui",
"failed to clear info line: {s}",
.{@errorName(err)},
);
};
state.info_line.label.draw();
try TerminalBuffer.presentBuffer();
}
return false;
}
@@ -1516,28 +1535,51 @@ fn authenticate(ptr: *anyopaque) !bool {
.{},
) catch {};
var file = std.Io.Dir.cwd().createFile(state.io, state.save_path, .{}) catch |err| {
const current: u8 = @intCast(state.login.?.label.current);
const login_users = state.login.?.label.list.items;
// Try to update the local tty cache before overwriting,
// since multiple instances can be running
updateTtyCache(state, .{ .user_list = login_users }) catch |err| {
state.log_file.err(
state.io,
"sys",
"failed to create save file: {s}",
"failed to update cache: {s}",
.{@errorName(err)},
) catch {};
};
var save_file = std.Io.Dir.cwd().createFile(state.io, state.save_path, .{}) catch |err| {
state.log_file.err(
state.io,
"sys",
"failed to create save file: {s} {s}",
.{ @errorName(err), state.save_path },
) catch break :save_last_settings;
break :save_last_settings;
};
defer file.close(state.io);
defer save_file.close(state.io);
var file_buffer: [256]u8 = undefined;
var file_writer = file.writer(state.io, &file_buffer);
var file_writer = save_file.writer(state.io, &file_buffer);
var writer = &file_writer.interface;
if (state.login_text) |box| {
try writer.print("0-{s}\n", .{box.text.items});
} else {
try writer.print("{d}\n", .{state.login.?.label.current});
try writer.print("{d}\n", .{current});
}
for (state.saved_users.user_list.items) |user| {
try writer.print("{s}:{d}\n", .{ user.username, user.session_index });
for (login_users) |user| {
try writer.print("{s}:{d}\n", .{ user.name, user.session_index.* });
}
state.tty_cache[state.active_tty] = current;
for (state.tty_cache, 0..) |maybe_user_index, tty_num| {
if (maybe_user_index) |user_index| {
// Posix usernames can't contain a '/'
// And, well, if your username is this string...
try writer.print("ly/tty{d}:{s}\n", .{ tty_num, login_users[user_index].name });
}
}
try writer.flush();
@@ -1572,6 +1614,7 @@ fn authenticate(ptr: *anyopaque) !bool {
.xauth_cmd = state.config.xauth_cmd,
.setup_cmd = state.config.setup_cmd,
.login_cmd = state.config.login_cmd,
.faillock_tally_dir = state.config.faillock_tally_dir,
.x_cmd = state.config.x_cmd,
.x_vt = state.config.x_vt,
.session_pid = session_pid,
@@ -1595,7 +1638,8 @@ fn authenticate(ptr: *anyopaque) !bool {
auth_options,
current_environment,
if (state.login_text) |box| box.text.items else state.login.?.getCurrentUsername(),
password_text,
if (state.maybe_old_password) |pass| pass else password_text,
if (state.maybe_old_password != null) password_text else null,
) catch |err| {
shared_err.writeError(err);
@@ -1607,12 +1651,19 @@ fn authenticate(ptr: *anyopaque) !bool {
std.process.exit(0);
}
var session_status: c_int = undefined;
_ = std.posix.system.waitpid(session_pid, &session_status, 0);
// HACK: It seems like the session process is not exiting immediately after the waitpid call.
// This is a workaround to ensure the session process has exited before re-initializing the TTY.
state.io.sleep(.fromSeconds(1), .real) catch {};
session_pid = -1;
if (state.maybe_old_password) |pass| {
state.allocator.free(pass);
state.maybe_old_password = null;
}
if (session_pid != -1) {
var session_status: c_int = undefined;
_ = std.posix.system.waitpid(session_pid, &session_status, 0);
// HACK: It seems like the session process is not exiting immediately after the waitpid call.
// This is a workaround to ensure the session process has exited before re-initializing the TTY.
state.io.sleep(.fromSeconds(1), .real) catch {};
session_pid = -1;
}
try state.log_file.reinit(state.io);
}
@@ -1620,7 +1671,20 @@ fn authenticate(ptr: *anyopaque) !bool {
try state.buffer.reclaim();
const auth_err = shared_err.readError();
if (auth_err) |err| {
if (auth_err) |err| handle_error: {
if (err == error.PamNewAuthTokenRequired) {
try state.info_line.addMessage(
state.lang.token_expired,
state.config.bg,
state.config.fg,
);
state.maybe_old_password = try state.allocator.dupe(u8, state.password.text.items);
state.password.clear();
state.is_autologin = false;
break :handle_error;
}
state.auth_fails += 1;
state.buffer.setActiveWidget(state.password_widget);
@@ -1897,7 +1961,7 @@ fn updateBigClock(self: *BigLabel, ptr: *anyopaque) !void {
"%M",
if (state.config.bigclock_seconds) separator else "",
if (state.config.bigclock_seconds) "%S" else "",
if (state.config.bigclock_12hr) "%P" else "",
if (state.config.bigclock_12hr) "%p" else "",
},
0,
);
@@ -2259,7 +2323,8 @@ fn positionWidgets(ptr: *anyopaque) !void {
const clock_text_len = TerminalBuffer.strWidth(state.bigclock_label.text) * (BigLabel.CHAR_WIDTH + 1);
if (state.config.bigclock != .none) {
bb_height += BigLabel.CHAR_HEIGHT + 2;
const gap: usize = if (state.config.bigclock_outline_fg != null) 3 else 2;
bb_height += BigLabel.CHAR_HEIGHT + gap;
bb_width = @max(bb_width, clock_text_len);
}
@@ -2409,7 +2474,12 @@ fn crawl(session: *Session, io: std.Io, lang: Lang, path: []const u8, display_se
for (desktop_names) |*c| {
if (c.* == ';') c.* = ':';
}
maybe_xdg_desktop_names = desktop_names;
if (desktop_names[desktop_names.len - 1] == ':') {
maybe_xdg_desktop_names = desktop_names[0 .. desktop_names.len - 1];
} else {
maybe_xdg_desktop_names = desktop_names;
}
} else if (display_server != .custom) {
// If DesktopNames is empty, and this isn't a custom session entry,
// we'll take the name of the session file
@@ -2568,12 +2638,55 @@ fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {
error.PamCredentialsInsufficient => lang.err_pam_cred_insufficient,
error.PamCredentialsUnavailable => lang.err_pam_cred_unavail,
error.PamMaximumTries => lang.err_pam_maxtries,
error.PamNewAuthTokenRequired => lang.err_pam_authok_reqd,
error.PamPermissionDenied => lang.err_pam_perm_denied,
error.PamSessionError => lang.err_pam_session,
error.PamSystemError => lang.err_pam_sys,
error.PamUserUnknown => lang.err_pam_user_unknown,
error.PamAbort => lang.err_pam_abort,
error.AccountLocked => lang.err_acc_locked,
else => @errorName(err),
};
}
// Updates the UiState's tty_cache using the save file.
// Matched against the current truthful user list to ensure it's a valid index.
fn updateTtyCache(state: *UiState, real_users: union(enum) { user_list: []UserList.User, usernames: [][]const u8 }) !void {
var save_file = try std.Io.Dir.cwd().openFile(state.io, state.save_path, .{});
defer save_file.close(state.io);
var file_buffer: [256]u8 = undefined;
var file_reader = save_file.reader(state.io, &file_buffer);
var reader = &file_reader.interface;
while (reader.seek < reader.buffer.len) {
var line = reader.takeDelimiterInclusive('\n') catch break;
if (!std.mem.startsWith(u8, line, "ly/tty")) continue;
line = line["ly/tty".len..];
var entry = std.mem.splitScalar(u8, line[0..(line.len - 1)], ':');
const tty_num_str = entry.next() orelse continue;
const saved_username = entry.next() orelse continue;
const tty_num = std.fmt.parseInt(usize, tty_num_str, 10) catch continue;
if (tty_num >= std.math.maxInt(u8)) continue;
switch (real_users) {
.usernames => |usernames| {
for (usernames, 0..) |username, u_index| {
if (std.mem.eql(u8, username, saved_username)) {
state.tty_cache[tty_num] = @intCast(u_index);
break;
}
}
},
.user_list => |user_list| {
for (user_list, 0..) |user, u_index| {
if (std.mem.eql(u8, user.name, saved_username)) {
state.tty_cache[tty_num] = @intCast(u_index);
break;
}
}
},
}
}
}