11 Commits

Author SHA1 Message Date
AnErrupTion
a9b6bfc62a Backport: Prevent Ly from zombifying when X.org is terminated
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-05 21:49:38 +02:00
AnErrupTion
32d0de3d2d Backport: Workaround for session process not exiting immediately
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-04 00:04:55 +02:00
AnErrupTion
a4dd5b3c44 Backport: Fix clock string length issues
Co-authored-by: Plash <plash@noreply.codeberg.org>
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 13:16:35 +02:00
AnErrupTion
b7d81a9b51 Backport: Fix possible overflow with 5-digit+ UIDs
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 01:00:56 +02:00
AnErrupTion
3359127a06 Backport: Reorder default PATH to prioritize /usr/local directories to solve
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-08-02 01:00:10 +02:00
AnErrupTion
faa2636c99 Start Ly v1.1.2 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 16:37:48 +02:00
AnErrupTion
9d23209ca3 Backport: Fix character width calculation
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-08 16:36:02 +02:00
AnErrupTion
170e523823 Backport: Fix XDG_RUNTIME_DIR not being set properly
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-07-06 09:31:56 +02:00
AnErrupTion
f983c6b19b Backport: Fix big clock UB in ReleaseSafe
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-31 10:39:15 +02:00
AnErrupTion
d12850889c Start v1.1.1 development cycle
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-31 10:39:03 +02:00
AnErrupTion
42393f46d1 Only support Ly v1.1.x in bug reports
Signed-off-by: AnErrupTion <anerruption@disroot.org>
2025-05-30 19:14:25 +02:00
60 changed files with 5293 additions and 2550 deletions

View File

@@ -58,8 +58,8 @@ 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.
Moreover, it is almost always a good idea to include your session log and your general log files (found at ~/ly-session.log and /var/log/ly.log respectively by default) as it usually contains relevant information about the problem.
Please copy and paste any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you.
If you're using the latest code on master (for v1.1.0), including your session log (found at /var/log/ly-session.log unless modified) is a good idea. (But make sure it's relevant!)
render: shell
- type: textarea
id: moreinfo

BIN
.github/screenshot.png vendored

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 43 KiB

125
build.zig
View File

@@ -8,11 +8,9 @@ const InitSystem = enum {
runit,
s6,
dinit,
sysvinit,
freebsd,
};
const min_zig_string = "0.15.0";
const min_zig_string = "0.14.0";
const current_zig = builtin.zig_version;
// Implementing zig version detection through compile time
@@ -23,7 +21,7 @@ comptime {
}
}
const ly_version = std.SemanticVersion{ .major = 1, .minor = 2, .patch = 0 };
const ly_version = std.SemanticVersion{ .major = 1, .minor = 1, .patch = 2 };
var dest_directory: []const u8 = undefined;
var config_directory: []const u8 = undefined;
@@ -43,7 +41,6 @@ pub fn build(b: *std.Build) !void {
const version_str = try getVersionStr(b, "ly", ly_version);
const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support (default is on)") orelse true;
const default_tty = b.option(u8, "default_tty", "Set the TTY (default is 2)") orelse 2;
const fallback_tty = b.option(u8, "fallback_tty", "Set the fallback TTY (default is 2). This value gets embedded into the binary") orelse 2;
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
@@ -51,7 +48,6 @@ pub fn build(b: *std.Build) !void {
build_options.addOption([]const u8, "prefix_directory", prefix_directory);
build_options.addOption([]const u8, "version", version_str);
build_options.addOption(u8, "tty", default_tty);
build_options.addOption(u8, "fallback_tty", fallback_tty);
build_options.addOption(bool, "enable_x11_support", enable_x11_support);
const target = b.standardTargetOptions(.{});
@@ -59,13 +55,9 @@ pub fn build(b: *std.Build) !void {
const exe = b.addExecutable(.{
.name = "ly",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
// Here until the native backend matures in terms of performance
.use_llvm = true,
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
@@ -76,17 +68,13 @@ pub fn build(b: *std.Build) !void {
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("clap", clap.module("clap"));
const termbox_dep = b.dependency("termbox2", .{
.target = target,
.optimize = optimize,
});
exe.addIncludePath(b.path("include"));
exe.linkSystemLibrary("pam");
if (enable_x11_support) exe.linkSystemLibrary("xcb");
exe.linkLibC();
const translate_c = b.addTranslateC(.{
.root_source_file = termbox_dep.path("termbox2.h"),
.root_source_file = b.path("include/termbox2.h"),
.target = target,
.optimize = optimize,
});
@@ -134,10 +122,6 @@ pub fn Installer(install_config: bool) type {
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
try patch_map.put("$EXECUTABLE_NAME", executable_name);
// The "-a" argument doesn't exist on FreeBSD, so we use "-p"
// instead to shutdown the system.
try patch_map.put("$PLATFORM_SHUTDOWN_ARG", if (init_system == .freebsd) "-p" else "-a");
try install_ly(allocator, patch_map, install_config);
try install_service(allocator, patch_map);
}
@@ -151,12 +135,6 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config:
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
};
const ly_custom_sessions_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" });
std.fs.cwd().makePath(ly_custom_sessions_directory) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory});
};
const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" });
std.fs.cwd().makePath(ly_lang_path) catch {
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path});
@@ -185,51 +163,30 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config:
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{});
}
const patched_example_config = try patchFile(allocator, "res/config.ini", patch_map);
try installText(patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{});
const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map);
try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 });
}
{
var custom_sessions_dir = std.fs.cwd().openDir(ly_custom_sessions_directory, .{}) catch unreachable;
defer custom_sessions_dir.close();
const patched_readme = try patchFile(allocator, "res/custom-sessions/README", patch_map);
try installText(patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{});
}
{
var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
defer lang_dir.close();
const languages = [_][]const u8{
"ar.ini",
"cat.ini",
"cs.ini",
"de.ini",
"en.ini",
"es.ini",
"fr.ini",
"it.ini",
"ja_JP.ini",
"lv.ini",
"pl.ini",
"pt.ini",
"pt_BR.ini",
"ro.ini",
"ru.ini",
"sr.ini",
"sv.ini",
"tr.ini",
"uk.ini",
"zh_CN.ini",
};
inline for (languages) |language| {
try installFile("res/lang/" ++ language, lang_dir, ly_lang_path, language, .{});
}
try installFile("res/lang/cat.ini", lang_dir, ly_lang_path, "cat.ini", .{});
try installFile("res/lang/cs.ini", lang_dir, ly_lang_path, "cs.ini", .{});
try installFile("res/lang/de.ini", lang_dir, ly_lang_path, "de.ini", .{});
try installFile("res/lang/en.ini", lang_dir, ly_lang_path, "en.ini", .{});
try installFile("res/lang/es.ini", lang_dir, ly_lang_path, "es.ini", .{});
try installFile("res/lang/fr.ini", lang_dir, ly_lang_path, "fr.ini", .{});
try installFile("res/lang/it.ini", lang_dir, ly_lang_path, "it.ini", .{});
try installFile("res/lang/pl.ini", lang_dir, ly_lang_path, "pl.ini", .{});
try installFile("res/lang/pt.ini", lang_dir, ly_lang_path, "pt.ini", .{});
try installFile("res/lang/pt_BR.ini", lang_dir, ly_lang_path, "pt_BR.ini", .{});
try installFile("res/lang/ro.ini", lang_dir, ly_lang_path, "ro.ini", .{});
try installFile("res/lang/ru.ini", lang_dir, ly_lang_path, "ru.ini", .{});
try installFile("res/lang/sr.ini", lang_dir, ly_lang_path, "sr.ini", .{});
try installFile("res/lang/sv.ini", lang_dir, ly_lang_path, "sv.ini", .{});
try installFile("res/lang/tr.ini", lang_dir, ly_lang_path, "tr.ini", .{});
try installFile("res/lang/uk.ini", lang_dir, ly_lang_path, "uk.ini", .{});
}
{
@@ -243,7 +200,7 @@ fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config:
var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable;
defer pam_dir.close();
try installFile(if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
try installFile("res/pam.d/ly", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
}
}
@@ -314,23 +271,6 @@ fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void {
const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{});
},
.sysvinit => {
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
std.fs.cwd().makePath(service_path) catch {};
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
defer service_dir.close();
const patched_service = try patchFile(allocator, "res/ly-sysvinit", patch_map);
try installText(patched_service, service_dir, service_path, "ly", .{ .mode = 0o755 });
},
.freebsd => {
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable;
defer executable_dir.close();
const patched_wrapper = try patchFile(allocator, "res/ly-freebsd-wrapper", patch_map);
try installText(patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .mode = 0o755 });
},
}
}
@@ -362,8 +302,6 @@ pub fn Uninstaller(uninstall_config: bool) type {
try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found");
},
.dinit => try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found"),
.sysvinit => try deleteFile(allocator, config_directory, "/init.d/ly", "sysvinit service not found"),
.freebsd => try deleteFile(allocator, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"),
}
}
};
@@ -404,7 +342,7 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
if (version.order(ancestor_ver) != .gt) {
std.debug.print("{s} version '{f}' must be greater than tagged ancestor '{f}'\n", .{ name, version, ancestor_ver });
std.debug.print("{s} version '{}' must be greater than tagged ancestor '{}'\n", .{ name, version, ancestor_ver });
std.process.exit(1);
}
@@ -439,11 +377,8 @@ fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: P
var file = try std.fs.cwd().openFile(source_file, .{});
defer file.close();
const stat = try file.stat();
var buffer: [4096]u8 = undefined;
var reader = file.reader(&buffer);
var text = try reader.interface.readAlloc(allocator, stat.size);
const reader = file.reader();
var text = try reader.readAllAlloc(allocator, std.math.maxInt(u16));
var iterator = patch_map.iterator();
while (iterator.next()) |kv| {
@@ -465,10 +400,8 @@ fn installText(
var file = try destination_directory.createFile(destination_file, options);
defer file.close();
var buffer: [1024]u8 = undefined;
var writer = file.writer(&buffer);
try writer.interface.writeAll(text);
try writer.interface.flush();
const writer = file.writer();
try writer.writeAll(text);
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
}

View File

@@ -1,20 +1,16 @@
.{
.name = .ly,
.version = "1.2.0",
.version = "1.1.2",
.fingerprint = 0xa148ffcc5dc2cb59,
.minimum_zig_version = "0.15.0",
.minimum_zig_version = "0.14.0",
.dependencies = .{
.clap = .{
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.11.0.tar.gz",
.hash = "clap-0.11.0-oBajB-HnAQDPCKYzwF7rO3qDFwRcD39Q0DALlTSz5H7e",
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz",
.hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0",
},
.zigini = .{
.url = "https://github.com/AnErrupTion/zigini/archive/96ca1d9f1a7ec741f07ceb104dae2b3a7bdfd48a.tar.gz",
.hash = "zigini-0.3.2-BSkB7WJJAADybd5DGd9MLCp6ikGGUq9wicxsjv0HF1Qc",
},
.termbox2 = .{
.url = "git+https://github.com/AnErrupTion/termbox2?ref=master#290ac6b8225aacfd16851224682b851b65fcb918",
.hash = "N-V-__8AAGcUBQAa5vov1Yi_9AXEffFQ1e2KsXaK4dgygRKq",
.url = "https://github.com/Kawaii-Ash/zigini/archive/2ed3d417f17fab5b0ee8cad8a63c6d62d7ac1042.tar.gz",
.hash = "zigini-0.3.1-BSkB7XJGAAB2E-sKyzhTaQCBlYBL8yqzE4E_jmSY99sC",
},
},
.paths = .{""},

4321
include/termbox2.h Normal file

File diff suppressed because it is too large Load Diff

334
readme.md
View File

@@ -1,19 +1,14 @@
# The Ly display manager
# Ly - a TUI display manager
## Development is now continuing on [Codeberg](https://codeberg.org/AnErrupTion/ly), with the [GitHub](https://github.com/fairyglade/ly) repository becoming a mirror. Issues & pull requests on GitHub will be ignored from now on.
![Ly screenshot](.github/screenshot.png "Ly screenshot")
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD,
designed with portability in mind (e.g. it does not require systemd to run).
Join us on Matrix over at [#ly:envs.net](https://matrix.to/#/#ly:envs.net)!
**Note**: Development happens on [Codeberg](https://codeberg.org/fairyglade/ly)
with a mirror on [GitHub](https://github.com/fairyglade/ly).
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD.
## Dependencies
- Compile-time:
- zig 0.15.x
- zig 0.14.0
- libc
- pam
- xcb (optional, required by default; needed for X11 support)
@@ -24,263 +19,256 @@ with a mirror on [GitHub](https://github.com/fairyglade/ly).
- brightnessctl
### Debian
```
# apt install build-essential libpam0g-dev libxcb-xkb-dev xauth xserver-xorg brightnessctl
# apt install build-essential libpam0g-dev libxcb-xkb-dev
```
### Fedora
**Warning**: You may encounter issues with SELinux on Fedora.
It is recommended to add a rule for Ly as it currently does not ship one.
```
# dnf install kernel-devel pam-devel libxcb-devel zig xorg-x11-xauth xorg-x11-server brightnessctl
# dnf install kernel-devel pam-devel libxcb-devel zig
```
### FreeBSD
```
# pkg install ca_root_nss libxcb git xorg xauth
```
## Packaging status
[![Packaging status](https://repology.org/badge/vertical-allrepos/ly.svg?exclude_unsupported=1)](https://repology.org/project/ly/versions)
## Support
The following desktop environments were tested with success:
Ly has been tested with a wide variety of desktop environments and window
managers, all of which you can find in the sections below:
[Wayland Environments](#supported-wayland-environments)
[Wayland environments](#supported-wayland-environments)
[X11 Environments](#supported-x11-environments)
[X11 environments](#supported-x11-environments)
Ly should work with any X desktop environment, and provides
basic wayland support (sway works very well, for example).
## Manually building
The procedure for manually building Ly is pretty standard:
## systemd?
Unlike what you may have heard, Ly does not require `systemd`,
and was even specifically designed not to depend on `logind`.
You should be able to make it work easily with a better init,
changing the source code won't be necessary :)
## Cloning and Compiling
Clone the repository
```
$ git clone https://codeberg.org/AnErrupTion/ly
```
Change the directory to ly
```
$ git clone https://codeberg.org/fairyglade/ly.git
$ cd ly
```
Compile
```
$ zig build
```
After building, you can (optionally) test Ly in a terminal emulator, although
authentication will **not** work:
Test in the configured tty (tty2 by default)
or a terminal emulator (but authentication won't work)
```
$ zig build run
```
**Important**: While you can also run Ly in a terminal emulator as root, it is
**not** recommended either. If you want to properly test Ly, please enable its
service (as described below) and reboot your machine.
The following sections show how to install Ly for a particular init system.
Because the procedure is very similar for all of them, the commands will only
be detailed for the first section (which is about systemd).
**Note**: All following sections will assume you are using LightDM for
convenience sake.
### systemd
Now, you can install Ly on your system:
**Important**: Running Ly in a terminal emulator as root is *not* recommended. If you
want to properly test Ly, please enable its service (as described below) and reboot
your machine.
Install Ly for systemd-based systems (the default)
```
# zig build installexe -Dinit_system=systemd
# zig build installexe
```
**Note**: The `init_system` parameter is optional and defaults to `systemd`.
Note that you also need to disable your current display manager. For example,
if LightDM is the current display manager, you can execute the following
command:
Instead of DISPLAY_MANAGER you need to add your DM:
- gdm.service
- sddm.service
- lightdm.service
```
# systemctl disable lightdm.service
# systemctl disable DISPLAY_MANAGER
```
Then, similarly to the previous command, you need to enable the Ly service:
Enable the service
```
# systemctl enable ly.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 (the default TTY on which Ly spawns), you need to
execute the following command:
If you need to switch between ttys after Ly's start you also have to
disable getty on Ly's tty to prevent "login" from spawning on top of it
```
# systemctl disable getty@tty2.service
```
You can change the TTY Ly will run on by editing the `tty` option in the
configuration file **and** change which TTY is used in the corresponding
service file..
### OpenRC
**NOTE 1**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
Clone, compile and test.
Install Ly and the provided OpenRC service
```
# zig build installexe -Dinit_system=openrc
# rc-update del lightdm
```
Enable the service
```
# rc-update add ly
```
You can edit which tty Ly will start on by editing the `tty` option in the configuration file.
If you choose a tty that already has a login/getty running (has a basic login prompt),
then you have to disable getty, so it doesn't respawn on top of ly
```
# rc-update del agetty.tty2
```
**Note**: On Gentoo specifically, you also **must** comment out the appropriate
line for the TTY in /etc/inittab.
**NOTE 2**: To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).
### runit
```
# zig build installexe -Dinit_system=runit
# rm /var/service/lightdm
# ln -s /etc/sv/ly /var/service/
```
By default, ly will run on tty2. To change the tty it must be set in `/etc/ly/config.ini`
You should as well disable your existing display manager service if needed, e.g.:
```
# rm /var/service/lxdm
```
The agetty service for the tty console where you are running ly should be disabled.
For instance, if you are running ly on tty2 (that's the default, check your `/etc/ly/config.ini`)
you should disable the agetty-tty2 service like this:
```
# rm /var/service/agetty-tty2
```
### s6
```
# zig build installexe -Dinit_system=s6
# s6-rc -d change lightdm
```
Then, edit `/etc/s6/config/ttyX.conf` and set `SPAWN="no"`, where X is the TTY ID (e.g. `2`).
Finally, enable the service:
```
# s6-service add default ly-srv
# s6-db-reload
# s6-rc -u change ly-srv
```
To disable TTY 2, edit `/etc/s6/config/tty2.conf` and set `SPAWN="no"`.
### dinit
```
# zig build installexe -Dinit_system=dinit
# dinitctl disable lightdm
# dinitctl enable ly
```
To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify
`ACTIVE_CONSOLES`.
In addition to the steps above, you will also have to keep a TTY free within `/etc/dinit.d/config/console.conf`.
### sysvinit
```
# zig build installexe -Dinit_system=sysvinit
# update-rc.d lightdm disable
# update-rc.d ly defaults
```
To disable TTY 2, go to `/etc/inittab` and comment out the line containing `tty2`.
### FreeBSD
```
# zig build installexe -Dprefix_directory=/usr/local -Dconfig_directory=/usr/local/etc -Dinit_system=freebsd
# sysrc lightdm_enable="NO"
```
To enable Ly, add the following entry to `/etc/gettytab`:
```
Ly:\
:lo=/usr/local/bin/ly_wrapper:\
:al=root:
```
Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys`
(TTYs in FreeBSD start at 0):
```
ttyv1 "/usr/libexec/getty Ly" xterm on secure
```
To do that, change `ACTIVE_CONSOLES` so that the tty that ly should use in `/etc/ly/config.ini` is free.
### Updating
You can also install Ly without overrding the current configuration file. This
is called **updating**. To update, simply run:
You can also install Ly without overrding the current configuration file. That's called
*updating*. To update, simply run:
```
# 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.
## Arch Linux Installation
You can install ly from the [`[extra]` repos](https://archlinux.org/packages/extra/x86_64/ly/):
```
# pacman -S ly
```
## Gentoo Installation
You can install ly from the GURU repository:
Note: If the package is masked, you may need to unmask it using ~amd64 keyword:
```bash
# echo 'x11-misc/ly ~amd64' >> /etc/portage/package.accept_keywords
```
1. Enable the GURU repository:
```bash
# eselect repository enable guru
```
2. Sync the GURU repository:
```bash
# emaint sync -r guru
```
3. Install ly from source:
```bash
# emerge --ask x11-misc/ly
```
## Configuration
You can find all the configuration in `/etc/ly/config.ini`. The file is fully
commented, and includes the default values.
You can find all the configuration in `/etc/ly/config.ini`.
The file is commented, and includes the default values.
## Controls
Use the up and down arrow keys to change the current field, and the
left and right arrow keys to change the target desktop environment
while on the desktop field (above the login field).
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.
## .xinitrc
If your .xinitrc doesn't work make sure it is executable and includes a shebang.
This file is supposed to be a shell script! Quoting from xinit's man page:
## A note on .xinitrc
If your `.xinitrc` file doesn't work ,make sure it is executable and includes a
shebang. This file is supposed to be a shell script! Quoting from `xinit`'s man
page:
> If no specific client program is given on the command line, xinit will look
> for a file in the user's home directory called .xinitrc to run as a shell
> script to start up client programs.
A typical shebang for a shell script looks like this:
> If no specific client program is given on the command line, xinit will look for a file in the user's home directory called .xinitrc to run as a shell script to start up client programs.
On Arch Linux, the example .xinitrc (/etc/X11/xinit/xinitrc) starts like this:
```
#!/bin/sh
```
## Tips
- The numlock and capslock state is printed in the top-right corner.
- Use the F1 and F2 keys to respectively shutdown and reboot.
- Take a look at your `.xsession` file if X doesn't start, as it can interfere
- Take a look at your .xsession if X doesn't start, as it can interfere
(this file is launched with X to configure the display properly).
## Supported Wayland environments
## Supported Wayland Environments
- budgie
- cosmic
- deepin
- enlightenment
- gnome
- hyprland
- kde
- labwc
- niri
- pantheon
- sway
- weston
- budgie
- cosmic
- deepin
- enlightenment
- gnome
- hyprland
- kde
- labwc
- niri
- pantheon
- sway
- weston
## Supported X11 Environments
- awesome
- bspwm
- budgie
- cinnamon
- dwm
- enlightenment
- gnome
- kde
- leftwm
- lxde
- mate
- maxx
- pantheon
- qwm
- spectrwm
- windowmaker
- xfce
- xmonad
## Supported X11 environments
- awesome
- bspwm
- budgie
- cinnamon
- dwm
- enlightenment
- gnome
- kde
- leftwm
- lxde
- mate
- maxx
- pantheon
- qwm
- spectrwm
- windowmaker
- xfce
- xmonad
## A final note
The name "Ly" is a tribute to the fairy from the game Rayman. Ly was tested by
oxodao, who is some seriously awesome dude.
## Additional Information
The name "Ly" is a tribute to the fairy from the game Rayman.
Ly was tested by oxodao, who is some seriously awesome dude.

View File

@@ -1,14 +1,14 @@
# 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
#define TB_BOLD 0x01000000
#define TB_UNDERLINE 0x02000000
#define TB_REVERSE 0x04000000
#define TB_ITALIC 0x08000000
#define TB_BLINK 0x10000000
#define TB_HI_BLACK 0x20000000
#define TB_BRIGHT 0x40000000
#define 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
@@ -23,7 +23,6 @@ allow_empty_password = true
# doom -> PSX DOOM fire
# matrix -> CMatrix
# colormix -> Color mixing shader
# gameoflife -> John Conway's Game of Life
animation = none
# Stop the animation after some time
@@ -41,11 +40,6 @@ asterisk = *
# The number of failed authentications before a special animation is played... ;)
auth_fails = 10
# Identifier for battery whose charge to display at top left
# Primary battery is usually BAT0 or BAT1
# If set to null, battery status won't be shown
battery_id = null
# Background color id
bg = 0x00000000
@@ -55,12 +49,6 @@ bg = 0x00000000
# 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
@@ -72,14 +60,14 @@ border_fg = 0x00FFFFFF
# 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 increase command
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s 10%-
# Brightness decrease key, or null to disable
brightness_down_key = F5
# Brightness increase command
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s +10%
# Brightness increase key, or null to disable
brightness_up_key = F6
@@ -94,9 +82,6 @@ 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
@@ -114,28 +99,20 @@ colormix_col2 = 0x000000FF
# Color mixing animation third color id
colormix_col3 = 0x20000000
# Custom sessions directory
# You can specify multiple directories,
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
custom_sessions = $CONFIG_DIRECTORY/ly/custom-sessions
# Console path
console_dev = /dev/console
# 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 top color (low intensity flames)
doom_top_color = 0x00FF0000
# DOOM animation fire spread (0 thru 4)
doom_fire_spread = 2
# DOOM animation middle color (medium intensity flames)
doom_middle_color = 0x00FFFF00
# 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 animation bottom color (high intensity flames)
doom_bottom_color = 0x00FFFFFF
# Error background color id
@@ -148,52 +125,12 @@ 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.
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
# Remove power management command hints
hide_key_hints = false
# Remove version number from the top left corner
hide_version_string = false
# Initial text to show on the info line
# If set to null, the info line defaults to the hostname
initial_info_text = null
@@ -205,25 +142,21 @@ input_len = 34
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
lang = en
# Load the saved desktop and username
load = true
# Command executed when logging in
# If null, no command will be executed
# Important: the code itself must end with `exec "$@"` in order to launch the session!
# You can also set environment variables in there, they'll persist until logout
login_cmd = null
# 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
# General log file path
ly_log = /var/log/ly.log
# Main box horizontal margin
margin_box_h = 2
@@ -246,7 +179,7 @@ restart_cmd = /sbin/shutdown -r now
# Specifies the key used for restart (F1-F12)
restart_key = F2
# Save the current desktop and login as defaults, and load them on startup
# Save the current desktop and login as defaults
save = true
# Service name (set to ly to use the provided pam config file)
@@ -256,15 +189,14 @@ service_name = ly
# This will contain stdout and stderr of Wayland sessions
# By default it's saved in the user's home directory
# Important: due to technical limitations, X11 and shell sessions aren't supported, which
# means you won't get any logs from those sessions.
# If null, no session log will be created
# means you won't get any logs from those sessions
session_log = ly-session.log
# Setup command
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
# Command executed when pressing shutdown_key
shutdown_cmd = /sbin/shutdown $PLATFORM_SHUTDOWN_ARG now
shutdown_cmd = /sbin/shutdown -a now
# Specifies the key used for shutdown (F1-F12)
shutdown_key = F1
@@ -278,6 +210,9 @@ sleep_key = F3
# Center the session name.
text_in_center = false
# TTY in use
tty = $DEFAULT_TTY
# Default vi mode
# normal -> normal mode
# insert -> insert mode
@@ -288,7 +223,7 @@ vi_mode = false
# Wayland desktop environments
# You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions
# e.g. /usr/share/wayland-sessions:/usr/local/share/wayland-sessions
waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
# Xorg server command
@@ -303,5 +238,5 @@ xinitrc = ~/.xinitrc
# Xorg desktop environments
# You can specify multiple directories,
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
# e.g. /usr/share/xsessions:/usr/local/share/xsessions
xsessions = $PREFIX_DIRECTORY/share/xsessions

View File

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

View File

@@ -2,21 +2,17 @@ authenticating = جاري المصادقة...
brightness_down = خفض السطوع
brightness_up = رفع السطوع
capslock = capslock
err_alloc = فشل في تخصيص الذاكرة
err_bounds = out-of-bounds index
err_brightness_change = فشل في تغيير سطوع الشاشة
err_chdir = فشل في فتح مجلد المنزل
err_config = فشل في تفسير ملف الإعدادات
err_console_dev = فشل في الوصول إلى جهاز وحدة التحكم
err_dgn_oob = رسالة سجل (Log)
err_domain = اسم نطاق غير صالح
err_empty_password = لا يُسمح بكلمة مرور فارغة
err_envlist = فشل في جلب قائمة المتغيرات البيئية
err_hostname = فشل في جلب اسم المضيف (Hostname)
err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock)
err_null = مؤشر فارغ (Null pointer)
err_numlock = فشل في ضبط Num Lock
@@ -42,10 +38,8 @@ err_perm_group = فشل في تخفيض صلاحيات المجموعة (Group p
err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions)
err_pwnam = فشل في جلب معلومات المستخدم
err_sleep = فشل في تنفيذ أمر sleep
err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY)
err_unknown = حدث خطأ غير معروف
err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم
err_user_init = فشل في تهيئة بيانات المستخدم
err_user_uid = فشل في تعيين معرّف المستخدم (UID)

View File

@@ -2,21 +2,17 @@ authenticating = autenticant...
brightness_down = abaixar brillantor
brightness_up = apujar brillantor
capslock = Bloq Majús
err_alloc = assignació de memòria fallida
err_bounds = índex fora de límits
err_brightness_change = error en canviar la brillantor
err_chdir = error en obrir la carpeta home
err_console_dev = error en accedir a la consola
err_dgn_oob = missatge de registre
err_domain = domini invàlid
err_envlist = error en obtenir l'envlist
err_hostname = error en obtenir el nom de l'amfitrió
err_mlock = error en bloquejar la memòria de clau
err_null = punter nul
err_numlock = error en establir el Bloq num
@@ -43,9 +39,7 @@ err_perm_user = error en degradar els permisos de l'usuari
err_pwnam = error en obtenir la informació de l'usuari
err_unknown = ha ocorregut un error desconegut
err_user_gid = error en establir el GID de l'usuari
err_user_init = error en inicialitzar usuari
err_user_uid = error en establir l'UID de l'usuari

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = alokace paměti selhala
err_bounds = index je mimo hranice pole
err_chdir = nelze otevřít domovský adresář
err_console_dev = chyba při přístupu do konzole
err_dgn_oob = zpráva protokolu
err_domain = neplatná doména
err_hostname = nelze získat název hostitele
err_mlock = uzamčení paměti hesel selhalo
err_null = nulový ukazatel
@@ -44,8 +40,6 @@ err_pwnam = nelze získat informace o uživateli
err_user_gid = nastavení GID uživatele selhalo
err_user_init = inicializace uživatele selhala
err_user_uid = nastavení UID uživateli selhalo

View File

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

View File

@@ -2,21 +2,17 @@ authenticating = authenticating...
brightness_down = decrease brightness
brightness_up = increase brightness
capslock = capslock
custom = custom
err_alloc = failed memory allocation
err_bounds = out-of-bounds index
err_brightness_change = failed to change brightness
err_chdir = failed to open home folder
err_clock_too_long = clock string too long
err_config = unable to parse config file
err_console_dev = failed to access console
err_dgn_oob = log message
err_domain = invalid domain
err_empty_password = empty password not allowed
err_envlist = failed to get envlist
err_get_active_tty = failed to get active tty
err_hostname = failed to get hostname
err_lock_state = failed to get lock state
err_log = failed to open log file
err_mlock = failed to lock password memory
err_null = null pointer
err_numlock = failed to set numlock
@@ -42,10 +38,8 @@ err_perm_group = failed to downgrade group permissions
err_perm_user = failed to downgrade user permissions
err_pwnam = failed to get user info
err_sleep = failed to execute sleep command
err_battery = failed to load battery status
err_switch_tty = failed to switch tty
err_tty_ctrl = tty control transfer failed
err_no_users = no users found
err_unknown = an unknown error occurred
err_user_gid = failed to set user GID
err_user_init = failed to initialize user
err_user_uid = failed to set user UID

View File

@@ -2,21 +2,17 @@ authenticating = autenticando...
brightness_down = bajar brillo
brightness_up = subir brillo
capslock = Bloq Mayús
err_alloc = asignación de memoria fallida
err_bounds = índice fuera de límites
err_chdir = error al abrir la carpeta home
err_console_dev = error al acceder a la consola
err_dgn_oob = mensaje de registro
err_domain = dominio inválido
err_hostname = error al obtener el nombre de host
err_mlock = error al bloquear la contraseña de memoria
err_null = puntero nulo
@@ -44,8 +40,6 @@ err_pwnam = error al obtener la información del usuario
err_user_gid = error al establecer el GID del usuario
err_user_init = error al inicializar usuario
err_user_uid = error al establecer el UID del usuario

View File

@@ -2,21 +2,17 @@ authenticating = authentification...
brightness_down = diminuer la luminosité
brightness_up = augmenter la luminosité
capslock = verr.maj
custom = customisé
err_alloc = échec d'allocation mémoire
err_bounds = indice hors-limite
err_brightness_change = échec du changement de luminosité
err_chdir = échec de l'ouverture du répertoire home
err_clock_too_long = chaîne de formattage de l'horloge trop longue
err_config = échec de lecture du fichier de configuration
err_console_dev = échec d'accès à la console
err_dgn_oob = message
err_domain = domaine invalide
err_empty_password = mot de passe vide non autorisé
err_envlist = échec de lecture de la liste d'environnement
err_get_active_tty = échec de lecture du terminal actif
err_hostname = échec de lecture du nom d'hôte
err_lock_state = échec de lecture de l'état de verrouillage
err_log = échec de l'ouverture du fichier de journal
err_mlock = échec du verrouillage mémoire
err_null = pointeur null
err_numlock = échec de modification du verr.num
@@ -42,10 +38,8 @@ err_perm_group = échec du déclassement des permissions de groupe
err_perm_user = échec du déclassement des permissions utilisateur
err_pwnam = échec de lecture des infos utilisateur
err_sleep = échec de l'exécution de la commande de veille
err_battery = échec de lecture de l'état de la batterie
err_switch_tty = échec du changement de terminal
err_tty_ctrl = échec du transfert de contrôle du terminal
err_no_users = aucun utilisateur trouvé
err_unknown = une erreur inconnue est survenue
err_user_gid = échec de modification du GID
err_user_init = échec d'initialisation de l'utilisateur
err_user_uid = échec de modification du UID

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = impossibile allocare memoria
err_bounds = indice fuori limite
err_chdir = impossibile aprire home directory
err_console_dev = impossibile aprire console
err_dgn_oob = messaggio log
err_domain = dominio non valido
err_hostname = impossibile ottenere hostname
err_mlock = impossibile ottenere lock per la password in memoria
err_null = puntatore nullo
@@ -44,8 +40,6 @@ err_pwnam = impossibile ottenere dati utente
err_user_gid = impossibile impostare GID utente
err_user_init = impossibile inizializzare utente
err_user_uid = impossible impostare UID utente

View File

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

View File

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

View File

@@ -2,21 +2,17 @@ authenticating = uwierzytelnianie...
brightness_down = zmniejsz jasność
brightness_up = zwiększ jasność
capslock = capslock
err_alloc = nieudana alokacja pamięci
err_bounds = indeks poza zakresem
err_brightness_change = nie udało się zmienić jasności
err_chdir = nie udało się otworzyć folderu domowego
err_config = nie można przetworzyć pliku konfiguracyjnego
err_console_dev = nie udało się uzyskać dostępu do konsoli
err_dgn_oob = wiadomość loga
err_domain = niepoprawna domena
err_empty_password = puste hasło jest niedozwolone
err_envlist = nie udało się pobrać listy zmiennych środowiskowych
err_hostname = nie udało się uzyskać nazwy hosta
err_mlock = nie udało się zablokować pamięci haseł
err_null = pusty wskaźnik
err_numlock = nie udało się ustawić numlock
@@ -42,10 +38,8 @@ err_perm_group = nie udało się obniżyć uprawnień grupy
err_perm_user = nie udało się obniżyć uprawnień użytkownika
err_pwnam = nie udało się uzyskać informacji o użytkowniku
err_sleep = nie udało się wykonać polecenia sleep
err_tty_ctrl = nie udało się przekazać kontroli tty
err_unknown = wystąpił nieznany błąd
err_user_gid = nie udało się ustawić GID użytkownika
err_user_init = nie udało się zainicjalizować użytkownika
err_user_uid = nie udało się ustawić UID użytkownika

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = erro na atribuição de memória
err_bounds = índice fora de limites
err_chdir = erro ao abrir a pasta home
err_console_dev = erro ao aceder à consola
err_dgn_oob = mensagem de registo
err_domain = domínio inválido
err_hostname = erro ao obter o nome do host
err_mlock = erro de bloqueio de memória
err_null = ponteiro nulo
@@ -44,8 +40,6 @@ err_pwnam = erro ao obter informação do utilizador
err_user_gid = erro ao definir o GID do utilizador
err_user_init = erro ao iniciar o utilizador
err_user_uid = erro ao definir o UID do utilizador

View File

@@ -2,21 +2,17 @@
capslock = caixa alta
err_alloc = alocação de memória malsucedida
err_bounds = índice fora de limites
err_chdir = não foi possível abrir o diretório home
err_console_dev = não foi possível acessar o console
err_dgn_oob = mensagem de log
err_domain = domínio inválido
err_hostname = não foi possível obter o nome do host
err_mlock = bloqueio da memória de senha malsucedido
err_null = ponteiro nulo
@@ -44,8 +40,6 @@ err_pwnam = não foi possível obter informações do usuário
err_user_gid = não foi possível definir o GID do usuário
err_user_init = não foi possível iniciar o usuário
err_user_uid = não foi possível definir o UID do usuário

View File

@@ -7,11 +7,7 @@ capslock = capslock
err_console_dev = nu s-a putut accesa consola
@@ -52,8 +48,6 @@ err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator
login = utilizator
logout = opreşte sesiunea

View File

@@ -1,22 +1,18 @@
authenticating = аутентификация...
brightness_down = уменьшить яркость
brightness_up = увеличить яркость
capslock = capslock
capslock = capslock
err_alloc = не удалось выделить память
err_bounds = за пределами индекса
err_brightness_change = не удалось изменить яркость
err_chdir = не удалось открыть домашнюю папку
err_config = не удалось разобрать файл конфигурации
err_console_dev = не удалось получить доступ к консоли
err_dgn_oob = отладочное сообщение (log)
err_domain = неверный домен
err_empty_password = пустой пароль не допустим
err_envlist = не удалось получить список переменных среды
err_hostname = не удалось получить имя хоста
err_mlock = сбой блокировки памяти
err_null = нулевой указатель
@@ -41,30 +37,28 @@ err_perm_dir = не удалось изменить текущий катало
err_perm_group = не удалось понизить права доступа группы
err_perm_user = не удалось понизить права доступа пользователя
err_pwnam = не удалось получить информацию о пользователе
err_sleep = не удалось выполнить команду sleep
err_switch_tty = не удалось переключить tty
err_tty_ctrl = передача управления tty не удалась
err_no_users = пользователи не найдены
err_user_gid = не удалось установить GID пользователя
err_user_init = не удалось инициализировать пользователя
err_user_uid = не удалось установить UID пользователя
err_xauth = команда xauth не выполнена
err_xcb_conn = ошибка подключения xcb
err_xsessions_dir = не удалось найти сессионную папку
err_xsessions_open = не удалось открыть сессионную папку
login = логин
logout = вышел из системы
no_x11_support = поддержка x11 отключена во время компиляции
logout = logged out
numlock = numlock
other = прочие
password = пароль
restart = перезагрузить
shell = оболочка
shell = shell
shutdown = выключить
sleep = сон
wayland = wayland
x11 = x11
xinitrc = xinitrc

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = neuspijesna alokacija memorije
err_bounds = izvan granica indeksa
err_chdir = neuspijesno otvaranje home foldera
err_console_dev = neuspijesno pristupanje konzoli
err_dgn_oob = log poruka
err_domain = nevazeci domen
err_hostname = neuspijesno trazenje hostname-a
err_mlock = neuspijesno zakljucavanje memorije lozinke
err_null = null pokazivac
@@ -44,8 +40,6 @@ err_pwnam = neuspijesno skupljanje informacija o korisniku
err_user_gid = neuspijesno postavljanje korisničkog GID-a
err_user_init = neuspijensa inicijalizacija korisnika
err_user_uid = neuspijesno postavljanje UID-a korisnika

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = misslyckad minnesallokering
err_bounds = utanför banan index
err_chdir = misslyckades att öppna hemkatalog
err_console_dev = misslyckades att komma åt konsol
err_dgn_oob = loggmeddelande
err_domain = okänd domän
err_hostname = misslyckades att hämta värdnamn
err_mlock = misslyckades att låsa lösenordsminne
err_null = nullpekare
@@ -44,8 +40,6 @@ err_pwnam = misslyckades att hämta användarinfo
err_user_gid = misslyckades att ställa in användar-GID
err_user_init = misslyckades att initialisera användaren
err_user_uid = misslyckades att ställa in användar-UID

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = basarisiz bellek ayirma
err_bounds = sinirlarin disinda dizin
err_chdir = ev klasoru acilamadi
err_console_dev = konsola erisilemedi
err_dgn_oob = log mesaji
err_domain = gecersiz etki alani
err_hostname = ana bilgisayar adi alinamadi
err_mlock = parola bellegi kilitlenemedi
err_null = bos isaretci hatasi
@@ -44,8 +40,6 @@ err_pwnam = kullanici bilgileri alinamadi
err_user_gid = kullanici icin GID ayarlanamadi
err_user_init = kullanici oturumu baslatilamadi
err_user_uid = kullanici icin UID ayarlanamadi

View File

@@ -2,21 +2,17 @@
capslock = capslock
err_alloc = невдале виділення пам'яті
err_bounds = поза межами індексу
err_chdir = не вдалося відкрити домашній каталог
err_console_dev = невдалий доступ до консолі
err_dgn_oob = повідомлення журналу (log)
err_domain = недійсний домен
err_hostname = не вдалося отримати ім'я хосту
err_mlock = збій блокування пам'яті
err_null = нульовий вказівник
@@ -44,8 +40,6 @@ err_pwnam = не вдалося отримати дані користувача
err_user_gid = не вдалося змінити GID користувача
err_user_init = не вдалося ініціалізувати користувача
err_user_uid = не вдалося змінити UID користувача

View File

@@ -2,21 +2,17 @@
capslock = 大写锁定
err_alloc = 内存分配失败
err_bounds = 索引越界
err_chdir = 无法打开home文件夹
err_console_dev = 无法访问控制台
err_dgn_oob = 日志消息
err_domain = 无效的域
err_hostname = 获取主机名失败
err_mlock = 锁定密码存储器失败
err_null = 空指针
@@ -44,8 +40,6 @@ err_pwnam = 获取用户信息失败
err_user_gid = 设置用户GID失败
err_user_init = 初始化用户失败
err_user_uid = 设置用户UID失败

View File

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

View File

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

View File

@@ -19,8 +19,12 @@ then
commandUL="/sbin/agetty"
fi
## Get the tty from the conf file
CONFTTY=$(cat $CONFIG_DIRECTORY/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
## The execution vars
TTY="tty$DEFAULT_TTY"
# If CONFTTY is empty then default to $DEFAULT_TTY
TTY="tty${CONFTTY:-$DEFAULT_TTY}"
TERM=linux
BAUD=38400
# If we don't have getty then we should have agetty

View File

@@ -7,4 +7,6 @@ fi
BAUD_RATE=38400
TERM_NAME=linux
TTY=tty$DEFAULT_TTY
auxtty=$(/bin/cat $CONFIG_DIRECTORY/ly/config.ini 2>/dev/null 1| /bin/sed -n 's/\(^[[:space:]]*tty[[:space:]]*=[[:space:]]*\)\([[:digit:]][[:digit:]]*\)\(.*\)/\2/p')
TTY=tty${auxtty:-$DEFAULT_TTY}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,27 +7,26 @@ const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Doom = @This();
pub const STEPS = 12;
pub const HEIGHT_MAX = 9;
pub const SPREAD_MAX = 4;
allocator: Allocator,
terminal_buffer: *TerminalBuffer,
buffer: []u8,
height: u8,
spread: u8,
fire: [STEPS + 1]Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32, fire_height: u8, fire_spread: u8) !Doom {
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32) !Doom {
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
initBuffer(buffer, terminal_buffer.width);
const levels =
[_]Cell{
Cell.init(' ', terminal_buffer.bg, terminal_buffer.bg),
Cell.init(0x2591, top_color, terminal_buffer.bg),
Cell.init(0x2592, top_color, terminal_buffer.bg),
Cell.init(0x2593, top_color, terminal_buffer.bg),
Cell.init(0x2588, top_color, terminal_buffer.bg),
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.buffer = buffer,
.fire = [_]Cell{
Cell.init(' ', TerminalBuffer.Color.DEFAULT, TerminalBuffer.Color.DEFAULT),
Cell.init(0x2591, top_color, TerminalBuffer.Color.DEFAULT),
Cell.init(0x2592, top_color, TerminalBuffer.Color.DEFAULT),
Cell.init(0x2593, top_color, TerminalBuffer.Color.DEFAULT),
Cell.init(0x2588, top_color, TerminalBuffer.Color.DEFAULT),
Cell.init(0x2591, middle_color, top_color),
Cell.init(0x2592, middle_color, top_color),
Cell.init(0x2593, middle_color, top_color),
@@ -36,15 +35,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
Cell.init(0x2592, bottom_color, middle_color),
Cell.init(0x2593, bottom_color, middle_color),
Cell.init(0x2588, bottom_color, middle_color),
};
return .{
.allocator = allocator,
.terminal_buffer = terminal_buffer,
.buffer = buffer,
.height = @min(HEIGHT_MAX, fire_height),
.spread = @min(SPREAD_MAX, fire_spread),
.fire = levels,
},
};
}
@@ -66,35 +57,20 @@ fn draw(self: *Doom) void {
for (0..self.terminal_buffer.width) |x| {
// We start from 1 so that we always have the topmost line when spreading fire
for (1..self.terminal_buffer.height) |y| {
// Get index of current cell in fire level buffer
// Get current cell
const from = y * self.terminal_buffer.width + x;
const cell_index = self.buffer[from];
// Generate random data for fire propagation
const rand_loss = self.terminal_buffer.random.intRangeAtMost(u8, 0, HEIGHT_MAX);
const rand_spread = self.terminal_buffer.random.intRangeAtMost(u8, 0, self.spread * 2);
// Spread fire
const propagate = self.terminal_buffer.random.int(u1);
const to = from - self.terminal_buffer.width; // Get the line above
// Select semi-random target cell
const to = from -| self.terminal_buffer.width + self.spread -| rand_spread;
const to_x = to % self.terminal_buffer.width;
const to_y = to / self.terminal_buffer.width;
self.buffer[to] = if (cell_index > 0) cell_index - propagate else cell_index;
// Get fire level of current cell
const level_buf_from = self.buffer[from];
// Choose new fire level and store in level buffer
const level_buf_to = level_buf_from -| @intFromBool(rand_loss >= self.height);
self.buffer[to] = level_buf_to;
// Send known fire levels to terminal buffer
const from_cell = self.fire[level_buf_from];
const to_cell = self.fire[level_buf_to];
from_cell.put(x, y);
to_cell.put(to_x, to_y);
// Put the cell
const cell = self.fire[cell_index];
cell.put(x, y);
}
// Draw bottom line (fire source)
const src_cell = self.fire[STEPS];
src_cell.put(x, self.terminal_buffer.height - 1);
}
}

View File

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

View File

@@ -11,6 +11,8 @@ pub const FRAME_DELAY: usize = 8;
// Characters change mid-scroll
pub const MID_SCROLL_CHANGE = true;
const DOT_HEAD_COLOR: u32 = @intCast(TerminalBuffer.Color.WHITE | TerminalBuffer.Styling.BOLD);
const Matrix = @This();
pub const Dot = struct {
@@ -31,12 +33,11 @@ lines: []Line,
frame: usize,
count: usize,
fg: u32,
head_col: u32,
min_codepoint: u16,
max_codepoint: u16,
default_cell: Cell,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16) !Matrix {
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min_codepoint: u16, max_codepoint: u16) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width);
@@ -50,7 +51,6 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, hea
.frame = 3,
.count = 0,
.fg = fg,
.head_col = head_col,
.min_codepoint = min_codepoint,
.max_codepoint = max_codepoint - min_codepoint,
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
@@ -157,13 +157,11 @@ fn draw(self: *Matrix) void {
const dot = self.dots[buf_width * y + x];
const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{
.ch = @intCast(dot.value.?),
.fg = if (dot.is_head) self.head_col else self.fg,
.fg = if (dot.is_head) DOT_HEAD_COLOR else self.fg,
.bg = self.terminal_buffer.bg,
};
cell.put(x, y - 1);
// Fill background in between columns
self.default_cell.put(x + 1, y - 1);
}
}
}

View File

@@ -5,8 +5,8 @@ const enums = @import("enums.zig");
const Environment = @import("Environment.zig");
const interop = @import("interop.zig");
const SharedError = @import("SharedError.zig");
const LogFile = @import("LogFile.zig");
const Allocator = std.mem.Allocator;
const Md5 = std.crypto.hash.Md5;
const utmp = interop.utmp;
const Utmp = utmp.utmpx;
@@ -14,8 +14,8 @@ const Utmp = utmp.utmpx;
pub const AuthOptions = struct {
tty: u8,
service_name: [:0]const u8,
path: ?[]const u8,
session_log: ?[]const u8,
path: ?[:0]const u8,
session_log: []const u8,
xauth_cmd: []const u8,
setup_cmd: []const u8,
login_cmd: ?[]const u8,
@@ -24,33 +24,28 @@ pub const AuthOptions = struct {
};
var xorg_pid: std.posix.pid_t = 0;
pub fn xorgSignalHandler(i: c_int) callconv(.c) void {
pub fn xorgSignalHandler(i: c_int) callconv(.C) void {
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i);
}
var child_pid: std.posix.pid_t = 0;
pub fn sessionSignalHandler(i: c_int) callconv(.c) void {
pub fn sessionSignalHandler(i: c_int) callconv(.C) void {
if (child_pid > 0) _ = std.c.kill(child_pid, i);
}
pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void {
pub fn authenticate(options: AuthOptions, current_environment: Environment, login: [:0]const u8, password: [:0]const u8) !void {
var tty_buffer: [3]u8 = undefined;
const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty});
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{options.tty});
var pam_tty_buffer: [6]u8 = undefined;
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty});
// Set the XDG environment variables
try setXdgEnv(allocator, tty_str, current_environment);
setXdgSessionEnv(current_environment.display_server);
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names);
// Open the PAM session
const login_z = try allocator.dupeZ(u8, login);
defer allocator.free(login_z);
const password_z = try allocator.dupeZ(u8, password);
defer allocator.free(password_z);
var credentials = [_:null]?[*:0]const u8{ login_z, password_z };
var credentials = [_:null]?[*:0]const u8{ login, password };
const conv = interop.pam.pam_conv{
.conv = loginConv,
@@ -58,68 +53,53 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
};
var handle: ?*interop.pam.pam_handle = undefined;
var log_writer = &log_file.file_writer.interface;
try log_writer.writeAll("[pam] starting session\n");
var status = interop.pam.pam_start(options.service_name, null, &conv, &handle);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer _ = interop.pam.pam_end(handle, status);
// Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module
try log_writer.writeAll("[pam] setting tty\n");
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
// Do the PAM routine
try log_writer.writeAll("[pam] authenticating\n");
status = interop.pam.pam_authenticate(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_writer.writeAll("[pam] validating account\n");
status = interop.pam.pam_acct_mgmt(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
try log_writer.writeAll("[pam] setting credentials\n");
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
try log_writer.writeAll("[pam] opening session\n");
status = interop.pam.pam_open_session(handle, 0);
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
defer status = interop.pam.pam_close_session(handle, 0);
var user_entry: interop.UsernameEntry = undefined;
var pwd: *interop.pwd.passwd = undefined;
{
defer interop.closePasswordDatabase();
defer interop.pwd.endpwent();
// Get password structure from username
user_entry = interop.getUsernameEntry(login_z) orelse return error.GetPasswordNameFailed;
pwd = interop.pwd.getpwnam(login) orelse return error.GetPasswordNameFailed;
}
// Set user shell if it hasn't already been set
if (user_entry.shell == null) interop.setUserShell(&user_entry);
if (pwd.pw_shell == null) {
interop.unistd.setusershell();
pwd.pw_shell = interop.unistd.getusershell();
interop.unistd.endusershell();
}
var shared_err = try SharedError.init();
defer shared_err.deinit();
log_file.deinit();
child_pid = try std.posix.fork();
if (child_pid == 0) {
try log_file.reinit();
log_writer = &log_file.file_writer.interface;
try log_writer.writeAll("starting session\n");
startSession(log_file, allocator, options, tty_str, user_entry, handle, current_environment) catch |e| {
startSession(options, tty_str, pwd, handle, current_environment) catch |e| {
shared_err.writeError(e);
log_file.deinit();
std.process.exit(1);
};
log_file.deinit();
std.process.exit(0);
}
@@ -128,113 +108,123 @@ pub fn authenticate(allocator: std.mem.Allocator, log_file: *LogFile, options: A
{
// If an error occurs here, we can send SIGTERM to the session
errdefer cleanup: {
std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup;
_ = std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup;
_ = std.posix.waitpid(child_pid, 0);
}
// If we receive SIGTERM, forward it to child_pid
const act = std.posix.Sigaction{
.handler = .{ .handler = &sessionSignalHandler },
.mask = std.posix.sigemptyset(),
.mask = std.posix.empty_sigset,
.flags = 0,
};
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
try addUtmpEntry(&entry, user_entry.username.?, child_pid);
try addUtmpEntry(&entry, pwd.pw_name.?, child_pid);
}
// Wait for the session to stop
_ = std.posix.waitpid(child_pid, 0);
try log_file.reinit();
removeUtmpEntry(&entry);
if (shared_err.readError()) |err| return err;
}
fn startSession(
log_file: *LogFile,
allocator: std.mem.Allocator,
options: AuthOptions,
tty_str: []u8,
user_entry: interop.UsernameEntry,
tty_str: [:0]u8,
pwd: *interop.pwd.passwd,
handle: ?*interop.pam.pam_handle,
current_environment: Environment,
) !void {
// Set the user's GID & PID
try interop.setUserContext(allocator, user_entry);
if (builtin.os.tag == .freebsd) {
// FreeBSD has initgroups() in unistd
const status = interop.unistd.initgroups(pwd.pw_name, pwd.pw_gid);
if (status != 0) return error.GroupInitializationFailed;
// FreeBSD sets the GID and UID with setusercontext()
const result = interop.pwd.setusercontext(null, pwd, pwd.pw_uid, interop.pwd.LOGIN_SETALL);
if (result != 0) return error.SetUserUidFailed;
} else {
const status = interop.grp.initgroups(pwd.pw_name, pwd.pw_gid);
if (status != 0) return error.GroupInitializationFailed;
std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed;
std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed;
}
// Set up the environment
try initEnv(allocator, user_entry, options.path);
try initEnv(pwd, options.path);
// Reset the XDG environment variables
try setXdgEnv(allocator, tty_str, current_environment);
setXdgSessionEnv(current_environment.display_server);
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names);
// Set the PAM variables
const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle);
if (pam_env_vars == null) return error.GetEnvListFailed;
const env_list = std.mem.span(pam_env_vars.?);
for (env_list) |env_var| try interop.putEnvironmentVariable(env_var);
for (env_list) |env_var| _ = interop.stdlib.putenv(env_var);
// Change to the user's home directory
std.posix.chdir(user_entry.home.?) catch return error.ChangeDirectoryFailed;
std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed;
// Signal to the session process to give up control on the TTY
std.posix.kill(options.session_pid, std.posix.SIG.CHLD) catch return error.TtyControlTransferFailed;
_ = std.posix.kill(options.session_pid, std.posix.SIG.CHLD) catch return error.TtyControlTransferFailed;
// Execute what the user requested
switch (current_environment.display_server) {
.wayland, .shell, .custom => try executeCmd(log_file, allocator, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd),
.wayland => try executeWaylandCmd(pwd.pw_shell.?, options, current_environment.cmd),
.shell => try executeShellCmd(pwd.pw_shell.?, options),
.xinitrc, .x11 => if (build_options.enable_x11_support) {
var vt_buf: [5]u8 = undefined;
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty});
try executeX11Cmd(log_file, allocator, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt);
try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, options, current_environment.cmd, vt);
},
}
}
fn initEnv(allocator: std.mem.Allocator, entry: interop.UsernameEntry, path_env: ?[]const u8) !void {
if (entry.home) |home| {
try interop.setEnvironmentVariable(allocator, "HOME", home, true);
try interop.setEnvironmentVariable(allocator, "PWD", home, true);
} else return error.NoHomeDirectory;
try interop.setEnvironmentVariable(allocator, "SHELL", entry.shell.?, true);
try interop.setEnvironmentVariable(allocator, "USER", entry.username.?, true);
try interop.setEnvironmentVariable(allocator, "LOGNAME", entry.username.?, true);
fn initEnv(pwd: *interop.pwd.passwd, path_env: ?[:0]const u8) !void {
_ = interop.stdlib.setenv("HOME", pwd.pw_dir, 1);
_ = interop.stdlib.setenv("PWD", pwd.pw_dir, 1);
_ = interop.stdlib.setenv("SHELL", pwd.pw_shell, 1);
_ = interop.stdlib.setenv("USER", pwd.pw_name, 1);
_ = interop.stdlib.setenv("LOGNAME", pwd.pw_name, 1);
if (path_env) |path| {
interop.setEnvironmentVariable(allocator, "PATH", path, true) catch return error.SetPathFailed;
const status = interop.stdlib.setenv("PATH", path, 1);
if (status != 0) return error.SetPathFailed;
}
}
fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environment) !void {
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_TYPE", switch (environment.display_server) {
fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
_ = interop.stdlib.setenv("XDG_SESSION_TYPE", switch (display_server) {
.wayland => "wayland",
.shell => "tty",
.xinitrc, .x11 => "x11",
.custom => if (environment.is_terminal) "tty" else "unspecified",
}, false);
}, 0);
}
fn setXdgEnv(tty_str: [:0]u8, maybe_desktop_name: ?[:0]const u8, maybe_xdg_desktop_names: ?[:0]const u8) !void {
// The "/run/user/%d" directory is not available on FreeBSD. It is much
// better to stick to the defaults and let applications using
// XDG_RUNTIME_DIR to fall back to directories inside user's home
// directory.
if (builtin.os.tag != .freebsd) {
const uid = std.posix.getuid();
const uid = interop.unistd.getuid();
var uid_buffer: [32]u8 = undefined; // No UID can be larger than this
const uid_str = try std.fmt.bufPrint(&uid_buffer, "/run/user/{d}", .{uid});
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
try interop.setEnvironmentVariable(allocator, "XDG_RUNTIME_DIR", uid_str, false);
_ = interop.stdlib.setenv("XDG_RUNTIME_DIR", uid_str, 0);
}
if (environment.xdg_desktop_names) |xdg_desktop_names| try interop.setEnvironmentVariable(allocator, "XDG_CURRENT_DESKTOP", xdg_desktop_names, false);
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_CLASS", "user", false);
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false);
if (environment.xdg_session_desktop) |desktop_name| try interop.setEnvironmentVariable(allocator, "XDG_SESSION_DESKTOP", desktop_name, false);
try interop.setEnvironmentVariable(allocator, "XDG_SEAT", "seat0", false);
try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false);
if (maybe_xdg_desktop_names) |xdg_desktop_names| _ = interop.stdlib.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names, 0);
_ = interop.stdlib.setenv("XDG_SESSION_CLASS", "user", 0);
_ = interop.stdlib.setenv("XDG_SESSION_ID", "1", 0);
if (maybe_desktop_name) |desktop_name| _ = interop.stdlib.setenv("XDG_SESSION_DESKTOP", desktop_name, 0);
_ = interop.stdlib.setenv("XDG_SEAT", "seat0", 0);
_ = interop.stdlib.setenv("XDG_VTNR", tty_str, 0);
}
fn loginConv(
@@ -242,7 +232,7 @@ fn loginConv(
msg: ?[*]?*const interop.pam.pam_message,
resp: ?*?[*]interop.pam.pam_response,
appdata_ptr: ?*anyopaque,
) callconv(.c) c_int {
) callconv(.C) c_int {
const message_count: u32 = @intCast(num_msg);
const messages = msg.?;
@@ -286,8 +276,8 @@ 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);
if (username != null) allocator.free(username.?);
if (password != null) allocator.free(password.?);
} else {
resp.?.* = response.ptr;
}
@@ -311,50 +301,46 @@ fn getXPid(display_num: u8) !i32 {
const file = try std.fs.openFileAbsolute(file_name, .{});
defer file.close();
var file_buffer: [32]u8 = undefined;
var file_reader = file.reader(&file_buffer);
var reader = &file_reader.interface;
var file_buf: [20]u8 = undefined;
var fbs = std.io.fixedBufferStream(&file_buf);
var buffer: [20]u8 = undefined;
var writer = std.Io.Writer.fixed(&buffer);
_ = try file.reader().streamUntilDelimiter(fbs.writer(), '\n', 20);
const line = fbs.getWritten();
const written = try reader.streamDelimiter(&writer, '\n');
return std.fmt.parseInt(i32, std.mem.trim(u8, buffer[0..written], " "), 10);
return std.fmt.parseInt(i32, std.mem.trim(u8, line, " "), 10);
}
fn createXauthFile(pwd: []const u8, buffer: []u8) ![]const u8 {
fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
var xauth_buf: [100]u8 = undefined;
var xauth_dir: []const u8 = undefined;
var xauth_dir: [:0]const u8 = undefined;
const xdg_rt_dir = std.posix.getenv("XDG_RUNTIME_DIR");
var xauth_file: []const u8 = "lyxauth";
if (xdg_rt_dir == null) no_rt_dir: {
if (xdg_rt_dir == null) {
const xdg_cfg_home = std.posix.getenv("XDG_CONFIG_HOME");
if (xdg_cfg_home == null) no_cfg_home: {
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config", .{pwd});
var dir = std.fs.cwd().openDir(xauth_dir, .{}) catch {
// xauth_dir isn't a directory
var sb: std.c.Stat = undefined;
if (xdg_cfg_home == null) {
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/.config", .{pwd});
_ = std.c.stat(xauth_dir, &sb);
const mode = sb.mode & std.posix.S.IFMT;
if (mode == std.posix.S.IFDIR) {
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xauth_dir});
} else {
xauth_dir = pwd;
xauth_file = ".lyxauth";
break :no_cfg_home;
};
dir.close();
// xauth_dir is a directory, use it to store Xauthority
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{xauth_dir});
}
} else {
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?});
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?});
}
const file = std.fs.cwd().openFile(xauth_dir, .{}) catch break :no_rt_dir;
file.close();
// xauth_dir is a file, create the parent directory
std.posix.mkdir(xauth_dir, 777) catch {
xauth_dir = pwd;
xauth_file = ".lyxauth";
};
_ = std.c.stat(xauth_dir, &sb);
const mode = sb.mode & std.posix.S.IFMT;
if (mode != std.posix.S.IFDIR) {
std.posix.mkdir(xauth_dir, 777) catch {
xauth_dir = pwd;
xauth_file = ".lyxauth";
};
}
} else {
xauth_dir = xdg_rt_dir.?;
}
@@ -364,8 +350,9 @@ fn createXauthFile(pwd: []const u8, buffer: []u8) ![]const u8 {
while (xauth_dir[i] == '/') i -= 1;
const trimmed_xauth_dir = xauth_dir[0 .. i + 1];
const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file });
const file = try std.fs.createFileAbsolute(xauthority, .{});
var buf: [256]u8 = undefined;
const xauthority: [:0]u8 = try std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file });
const file = try std.fs.createFileAbsoluteZ(xauthority, .{});
file.close();
return xauthority;
@@ -381,10 +368,13 @@ fn mcookie() [Md5.digest_length * 2]u8 {
return std.fmt.bytesToHex(&out, .lower);
}
fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, display_name: []u8, shell: [*:0]const u8, home: []const u8, xauth_buffer: []u8, options: AuthOptions) !void {
const xauthority = try createXauthFile(home, xauth_buffer);
try interop.setEnvironmentVariable(allocator, "XAUTHORITY", xauthority, true);
try interop.setEnvironmentVariable(allocator, "DISPLAY", display_name, true);
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, options: AuthOptions) !void {
var pwd_buf: [100]u8 = undefined;
const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir});
const xauthority = try createXauthFile(pwd);
_ = interop.stdlib.setenv("XAUTHORITY", xauthority, 1);
_ = interop.stdlib.setenv("DISPLAY", display_name, 1);
const magic_cookie = mcookie();
@@ -392,42 +382,46 @@ fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, display_name: []u8, s
if (pid == 0) {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ options.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1);
}
const status = std.posix.waitpid(pid, 0);
if (status.status != 0) {
try log_file.file_writer.interface.print("xauth command failed with status {d}\n", .{status.status});
return error.XauthFailed;
}
if (status.status != 0) return error.XauthFailed;
}
fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, home: []const u8, options: AuthOptions, desktop_cmd: []const u8, vt: []const u8) !void {
var log_writer = &log_file.file_writer.interface;
var xauth_buffer: [256]u8 = undefined;
fn executeShellCmd(shell: [*:0]const u8, options: AuthOptions) !void {
// We don't want to redirect stdout and stderr in a shell session
try log_writer.writeAll("[x11] getting free display\n");
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", shell });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
return std.posix.execveZ(shell, &args, std.c.environ);
}
fn executeWaylandCmd(shell: [*:0]const u8, options: AuthOptions, desktop_cmd: []const u8) !void {
const log_file = try redirectStandardStreams(options.session_log, true);
defer log_file.close();
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", desktop_cmd });
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
return std.posix.execveZ(shell, &args, std.c.environ);
}
fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, options: AuthOptions, desktop_cmd: []const u8, vt: []const u8) !void {
const display_num = try getFreeDisplay();
var buf: [4]u8 = undefined;
const display_name = try std.fmt.bufPrint(&buf, ":{d}", .{display_num});
var buf: [5]u8 = undefined;
const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num});
try xauth(display_name, shell, pw_dir, options);
const shell_z = try allocator.dupeZ(u8, shell);
defer allocator.free(shell_z);
try log_writer.writeAll("[x11] creating xauth file\n");
try xauth(log_file, allocator, display_name, shell_z, home, &xauth_buffer, options);
try log_writer.writeAll("[x11] starting x server\n");
const pid = try std.posix.fork();
if (pid == 0) {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.x_cmd, display_name, vt }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
std.posix.execveZ(shell_z, &args, std.c.environ) catch {};
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1);
}
@@ -443,24 +437,21 @@ fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []cons
// X Server detaches from the process.
// PID can be fetched from /tmp/X{d}.lock
try log_writer.writeAll("[x11] getting x server pid\n");
const x_pid = try getXPid(display_num);
try log_writer.writeAll("[x11] launching environment\n");
xorg_pid = try std.posix.fork();
if (xorg_pid == 0) {
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", desktop_cmd }) catch std.process.exit(1);
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
std.posix.execveZ(shell_z, &args, std.c.environ) catch {};
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
std.posix.execveZ(shell, &args, std.c.environ) catch {};
std.process.exit(1);
}
// If we receive SIGTERM, clean up by killing the xorg_pid process
const act = std.posix.Sigaction{
.handler = .{ .handler = &xorgSignalHandler },
.mask = std.posix.sigemptyset(),
.mask = std.posix.empty_sigset,
.flags = 0,
};
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
@@ -468,44 +459,16 @@ fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, shell: []cons
_ = std.posix.waitpid(xorg_pid, 0);
interop.xcb.xcb_disconnect(xcb);
// TODO: Find a more robust way to ensure that X has been terminated (pidfds?)
std.posix.kill(x_pid, std.posix.SIG.TERM) catch {};
std.Thread.sleep(std.time.ns_per_s * 1); // Wait 1 second before sending SIGKILL
std.posix.kill(x_pid, std.posix.SIG.KILL) catch return;
_ = std.posix.waitpid(x_pid, 0);
var status: c_int = 0;
_ = std.c.waitpid(x_pid, &status, 0);
}
fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void {
var maybe_log_file: ?std.fs.File = null;
if (!is_terminal) {
// For custom desktop entries, the "Terminal" value here determines if
// we redirect standard output & error or not. That is, we redirect only
// if it's equal to false (so if it's not running in a TTY).
if (options.session_log) |log_path| {
maybe_log_file = try redirectStandardStreams(global_log_file, log_path, true);
}
}
defer if (maybe_log_file) |log_file| log_file.close();
const shell_z = try allocator.dupeZ(u8, shell);
defer allocator.free(shell_z);
var cmd_buffer: [1024]u8 = undefined;
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", exec_cmd orelse shell });
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
return std.posix.execveZ(shell_z, &args, std.c.environ);
}
fn redirectStandardStreams(global_log_file: *LogFile, session_log: []const u8, create: bool) !std.fs.File {
const log_file = if (create) (std.fs.cwd().createFile(session_log, .{ .mode = 0o666 }) catch |err| {
try global_log_file.file_writer.interface.print("failed to create new session log file: {s}\n", .{@errorName(err)});
return err;
}) else (std.fs.cwd().openFile(session_log, .{ .mode = .read_write }) catch |err| {
try global_log_file.file_writer.interface.print("failed to open existing session log file: {s}\n", .{@errorName(err)});
return err;
});
fn redirectStandardStreams(session_log: []const u8, create: bool) !std.fs.File {
const log_file = if (create) (try std.fs.cwd().createFile(session_log, .{ .mode = 0o666 })) else (try std.fs.cwd().openFile(session_log, .{ .mode = .read_write }));
try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO);
try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO);
@@ -513,21 +476,18 @@ fn redirectStandardStreams(global_log_file: *LogFile, session_log: []const u8, c
return log_file;
}
fn addUtmpEntry(entry: *Utmp, username: []const u8, pid: c_int) !void {
fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
entry.ut_type = utmp.USER_PROCESS;
entry.ut_pid = pid;
var buf: [std.fs.max_path_bytes]u8 = undefined;
const tty_path = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf);
var buf: [4096]u8 = undefined;
const ttyname = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf);
// Get the TTY name (i.e. without the /dev/ prefix)
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{tty_path["/dev/".len..]});
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]});
entry.ut_line = ttyname_buf;
// Get the TTY ID (i.e. without the tty prefix) and truncate it to the size
// of ut_id if necessary
entry.ut_id = ttyname_buf["tty".len..(@sizeOf(@TypeOf(entry.ut_id)) + "tty".len)].*;
entry.ut_id = ttyname_buf["tty".len..7].*;
var username_buf: [@sizeOf(@TypeOf(entry.ut_user))]u8 = undefined;
_ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username});
@@ -538,17 +498,14 @@ fn addUtmpEntry(entry: *Utmp, username: []const u8, pid: c_int) !void {
host[0] = 0;
entry.ut_host = host;
const time = try interop.getTimeOfDay();
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.gettimeofday(&tv, null);
entry.ut_tv = .{
.tv_sec = @intCast(time.seconds),
.tv_usec = @intCast(time.microseconds),
.tv_sec = @intCast(tv.tv_sec),
.tv_usec = @intCast(tv.tv_usec),
};
// FreeBSD doesn't have this field
if (builtin.os.tag == .linux) {
entry.ut_addr_v6[0] = 0;
}
entry.ut_addr_v6[0] = 0;
utmp.setutxent();
_ = utmp.pututxline(entry);

View File

@@ -11,11 +11,13 @@ pub const WIDTH = Lang.WIDTH;
pub const HEIGHT = Lang.HEIGHT;
pub const SIZE = Lang.SIZE;
pub fn clockCell(animate: bool, char: u8, fg: u32, bg: u32, bigclock: Bigclock) ![SIZE]Cell {
pub fn clockCell(animate: bool, char: u8, fg: u32, bg: u32, bigclock: Bigclock) [SIZE]Cell {
var cells: [SIZE]Cell = undefined;
const time = try interop.getTimeOfDay();
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(time.microseconds, 500000) != 0) ' ' else char, bigclock);
var tv: interop.system_time.timeval = undefined;
_ = interop.system_time.gettimeofday(&tv, null);
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char, bigclock);
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
return cells;
@@ -49,9 +51,6 @@ fn toBigNumber(char: u8, bigclock: Bigclock) [SIZE]u21 {
'7' => locale_chars.SEVEN,
'8' => locale_chars.EIGHT,
'9' => locale_chars.NINE,
'p', 'P' => locale_chars.P,
'a', 'A' => locale_chars.A,
'm', 'M' => locale_chars.M,
':' => locale_chars.S,
else => locale_chars.E,
};

View File

@@ -1,13 +1,12 @@
const interop = @import("../interop.zig");
const builtin = @import("builtin");
pub const WIDTH = 5;
pub const HEIGHT = 5;
pub const SIZE = WIDTH * HEIGHT;
pub const X: u32 = if (interop.supportsUnicode()) 0x2593 else '#';
pub const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
pub const O: u32 = 0;
// zig fmt: off
pub const LocaleChars = struct {
ZERO: [SIZE]u21,
ONE: [SIZE]u21,
@@ -21,8 +20,4 @@ pub const LocaleChars = struct {
NINE: [SIZE]u21,
S: [SIZE]u21,
E: [SIZE]u21,
P: [SIZE]u21,
A: [SIZE]u21,
M: [SIZE]u21,
};
// zig fmt: on
};

View File

@@ -90,26 +90,5 @@ pub const locale_chars = LocaleChars{
O,O,O,O,O,
O,O,O,O,O,
},
.P = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
X,X,O,O,O,
X,X,O,O,O,
},
.A = [_]u21{
X,X,X,X,X,
X,X,O,X,X,
X,X,X,X,X,
X,X,O,X,X,
X,X,O,X,X,
},
.M = [_]u21{
X,X,X,X,X,
X,O,X,O,X,
X,O,X,O,X,
X,O,O,O,X,
X,O,O,O,X,
},
};
// zig fmt: on
// zig fmt: on

View File

@@ -76,27 +76,6 @@ pub const locale_chars = LocaleChars{
O,O,O,X,O,
O,O,O,X,O,
},
.P = [_]u21{
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
},
.A = [_]u21{
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
},
.M = [_]u21{
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
O,O,O,O,O,
},
.S = [_]u21{
O,O,O,O,O,
O,O,X,O,O,
@@ -112,4 +91,4 @@ pub const locale_chars = LocaleChars{
O,O,O,O,O,
},
};
// zig fmt: on
// zig fmt: on

View File

@@ -11,68 +11,56 @@ animation: Animation = .none,
animation_timeout_sec: u12 = 0,
asterisk: ?u32 = '*',
auth_fails: u64 = 10,
battery_id: ?[]const u8 = null,
bg: u32 = 0x00000000,
bigclock: Bigclock = .none,
bigclock_12hr: bool = false,
bigclock_seconds: bool = false,
blank_box: bool = true,
border_fg: u32 = 0x00FFFFFF,
box_title: ?[]const u8 = null,
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s 10%-",
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s 10%-",
brightness_down_key: ?[]const u8 = "F5",
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s +10%",
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s +10%",
brightness_up_key: ?[]const u8 = "F6",
clear_password: bool = false,
clock: ?[:0]const u8 = null,
cmatrix_fg: u32 = 0x0000FF00,
cmatrix_head_col: u32 = 0x01FFFFFF,
cmatrix_min_codepoint: u16 = 0x21,
cmatrix_max_codepoint: u16 = 0x7B,
colormix_col1: u32 = 0x00FF0000,
colormix_col2: u32 = 0x000000FF,
colormix_col3: u32 = 0x20000000,
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
console_dev: []const u8 = "/dev/console",
default_input: Input = .login,
doom_fire_height: u8 = 6,
doom_fire_spread: u8 = 2,
doom_top_color: u32 = 0x00FF0000,
doom_middle_color: u32 = 0x00FFFF00,
doom_bottom_color: u32 = 0x00FFFFFF,
error_bg: u32 = 0x00000000,
error_fg: u32 = 0x01FF0000,
fg: u32 = 0x00FFFFFF,
full_color: bool = true,
gameoflife_fg: u32 = 0x0000FF00,
gameoflife_entropy_interval: usize = 10,
gameoflife_frame_delay: usize = 6,
gameoflife_initial_density: f32 = 0.4,
hide_borders: bool = false,
hide_key_hints: bool = false,
hide_version_string: bool = false,
initial_info_text: ?[]const u8 = null,
input_len: u8 = 34,
lang: []const u8 = "en",
load: bool = true,
login_cmd: ?[]const u8 = null,
login_defs_path: []const u8 = "/etc/login.defs",
logout_cmd: ?[]const u8 = null,
ly_log: []const u8 = "/var/log/ly.log",
margin_box_h: u8 = 2,
margin_box_v: u8 = 1,
min_refresh_delta: u16 = 5,
numlock: bool = false,
path: ?[]const u8 = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
path: ?[:0]const u8 = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
restart_cmd: []const u8 = "/sbin/shutdown -r now",
restart_key: []const u8 = "F2",
save: bool = true,
service_name: [:0]const u8 = "ly",
session_log: ?[]const u8 = "ly-session.log",
session_log: []const u8 = "ly-session.log",
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
shutdown_key: []const u8 = "F1",
sleep_cmd: ?[]const u8 = null,
sleep_key: []const u8 = "F3",
text_in_center: bool = false,
tty: u8 = build_options.tty,
vi_default_mode: ViMode = .normal,
vi_mode: bool = false,
waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",

View File

@@ -7,21 +7,17 @@ authenticating: []const u8 = "authenticating...",
brightness_down: []const u8 = "decrease brightness",
brightness_up: []const u8 = "increase brightness",
capslock: []const u8 = "capslock",
custom: []const u8 = "custom",
err_alloc: []const u8 = "failed memory allocation",
err_bounds: []const u8 = "out-of-bounds index",
err_brightness_change: []const u8 = "failed to change brightness",
err_chdir: []const u8 = "failed to open home folder",
err_clock_too_long: []const u8 = "clock string too long",
err_config: []const u8 = "unable to parse config file",
err_console_dev: []const u8 = "failed to access console",
err_dgn_oob: []const u8 = "log message",
err_domain: []const u8 = "invalid domain",
err_empty_password: []const u8 = "empty password not allowed",
err_envlist: []const u8 = "failed to get envlist",
err_get_active_tty: []const u8 = "failed to get active tty",
err_hostname: []const u8 = "failed to get hostname",
err_lock_state: []const u8 = "failed to get lock state",
err_log: []const u8 = "failed to open log file",
err_mlock: []const u8 = "failed to lock password memory",
err_null: []const u8 = "null pointer",
err_numlock: []const u8 = "failed to set numlock",
@@ -47,10 +43,8 @@ err_perm_group: []const u8 = "failed to downgrade group permissions",
err_perm_user: []const u8 = "failed to downgrade user permissions",
err_pwnam: []const u8 = "failed to get user info",
err_sleep: []const u8 = "failed to execute sleep command",
err_battery: []const u8 = "failed to load battery status",
err_switch_tty: []const u8 = "failed to switch tty",
err_tty_ctrl: []const u8 = "tty control transfer failed",
err_no_users: []const u8 = "no users found",
err_unknown: []const u8 = "an unknown error occurred",
err_user_gid: []const u8 = "failed to set user GID",
err_user_init: []const u8 = "failed to initialize user",
err_user_uid: []const u8 = "failed to set user UID",
@@ -59,13 +53,13 @@ err_xcb_conn: []const u8 = "xcb connection failed",
err_xsessions_dir: []const u8 = "failed to find sessions folder",
err_xsessions_open: []const u8 = "failed to open sessions folder",
insert: []const u8 = "insert",
login: []const u8 = "login",
login: []const u8 = "login:",
logout: []const u8 = "logged out",
no_x11_support: []const u8 = "x11 support disabled at compile-time",
normal: []const u8 = "normal",
numlock: []const u8 = "numlock",
other: []const u8 = "other",
password: []const u8 = "password",
password: []const u8 = "password:",
restart: []const u8 = "reboot",
shell: [:0]const u8 = "shell",
shutdown: []const u8 = "shutdown",

View File

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

View File

@@ -1,16 +1,9 @@
// The migrator ensures compatibility with older configuration files
// Properties removed or changed since 0.6.0
// Color codes interpreted differently since 1.1.0
// The migrator ensures compatibility with <=0.6.0 configuration files
const std = @import("std");
const ini = @import("zigini");
const Config = @import("Config.zig");
const OldSave = @import("OldSave.zig");
const SavedUsers = @import("SavedUsers.zig");
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
const Color = TerminalBuffer.Color;
const Styling = TerminalBuffer.Styling;
const Save = @import("Save.zig");
const enums = @import("../enums.zig");
const color_properties = [_][]const u8{
"bg",
@@ -23,10 +16,6 @@ const color_properties = [_][]const u8{
"error_fg",
"fg",
};
var set_color_properties =
[_]bool{ false, false, false, false, false, false, false, false, false };
const removed_properties = [_][]const u8{
"wayland_specifier",
"max_desktop_len",
@@ -37,22 +26,22 @@ const removed_properties = [_][]const u8{
"term_restore_cursor_cmd",
"x_cmd_setup",
"wayland_cmd",
"console_dev",
"load",
};
var temporary_allocator = std.heap.page_allocator;
pub var auto_eight_colors: bool = true;
var buffer = std.mem.zeroes([10 * color_properties.len]u8);
pub var maybe_animate: ?bool = null;
pub var maybe_save_file: ?[]const u8 = null;
pub var mapped_config_fields = false;
pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniField {
if (std.mem.eql(u8, field.key, "animate")) {
// The option doesn't exist anymore, but we save its value for "animation"
maybe_animate = std.mem.eql(u8, field.value, "true");
mapped_config_fields = true;
return null;
}
@@ -68,29 +57,19 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
else => "none",
};
mapped_config_fields = true;
return mapped_field;
}
inline for (color_properties, &set_color_properties) |property, *status| {
inline for (color_properties) |property| {
if (std.mem.eql(u8, field.key, property)) {
// Color has been set; it won't be overwritten if we default to eight-color output
status.* = true;
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
// If they're all using eight-color codes, we start in eight-color mode
const color = std.fmt.parseInt(u16, field.value, 0) catch {
auto_eight_colors = false;
return field;
};
const color = std.fmt.parseInt(u16, field.value, 0) catch return field;
var mapped_field = field;
const color_no_styling = color & 0x00FF;
const styling_only = color & 0xFF00;
// If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM,
// we have an invalid color, so do not use eight-color mode
if (color_no_styling > 0x0008 or styling_only > 0x8000) auto_eight_colors = false;
return field;
mapped_field.value = mapColor(color) catch return field;
mapped_config_fields = true;
return mapped_field;
}
}
@@ -99,6 +78,7 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
var mapped_field = field;
mapped_field.key = "clear_password";
mapped_config_fields = true;
return mapped_field;
}
@@ -114,6 +94,7 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
else => "login",
};
mapped_config_fields = true;
return mapped_field;
}
@@ -121,12 +102,14 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
// The option doesn't exist anymore, but we save its value for migration later on
maybe_save_file = temporary_allocator.dupe(u8, field.value) catch return null;
mapped_config_fields = true;
return null;
}
inline for (removed_properties) |property| {
if (std.mem.eql(u8, field.key, property)) {
// The options don't exist anymore
mapped_config_fields = true;
return null;
}
}
@@ -138,57 +121,28 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
if (std.mem.eql(u8, field.value, "true")) {
mapped_field.value = "en";
mapped_config_fields = true;
} else if (std.mem.eql(u8, field.value, "false")) {
mapped_field.value = "none";
mapped_config_fields = true;
}
return mapped_field;
}
if (std.mem.eql(u8, field.key, "full_color")) {
// If color mode is defined, definitely don't set it automatically
auto_eight_colors = false;
return field;
}
return field;
}
// This is the stuff we only handle after reading the config.
// For example, the "animate" field could come after "animation"
pub fn lateConfigFieldHandler(config: *Config) void {
pub fn lateConfigFieldHandler(animation: *enums.Animation) void {
if (maybe_animate) |animate| {
if (!animate) config.*.animation = .none;
}
if (auto_eight_colors) {
// Valid config file predates true-color mode
// Will use eight-color output instead
config.full_color = false;
// We cannot rely on Config defaults when in eight-color mode,
// because they will appear as undesired colors.
// Instead set color properties to matching eight-color codes
config.doom_top_color = Color.ECOL_RED;
config.doom_middle_color = Color.ECOL_YELLOW;
config.doom_bottom_color = Color.ECOL_WHITE;
config.cmatrix_head_col = Styling.BOLD | Color.ECOL_WHITE;
// These may be in the config, so only change those which were not set
if (!set_color_properties[0]) config.bg = Color.DEFAULT;
if (!set_color_properties[1]) config.border_fg = Color.ECOL_WHITE;
if (!set_color_properties[2]) config.cmatrix_fg = Color.ECOL_GREEN;
if (!set_color_properties[3]) config.colormix_col1 = Color.ECOL_RED;
if (!set_color_properties[4]) config.colormix_col2 = Color.ECOL_BLUE;
if (!set_color_properties[5]) config.colormix_col3 = Color.ECOL_BLACK;
if (!set_color_properties[6]) config.error_bg = Color.DEFAULT;
if (!set_color_properties[7]) config.error_fg = Styling.BOLD | Color.ECOL_RED;
if (!set_color_properties[8]) config.fg = Color.ECOL_WHITE;
if (!animate) animation.* = .none;
}
}
pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave {
var save = OldSave{};
pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
var save = Save{};
if (maybe_save_file) |path| {
defer temporary_allocator.free(path);
@@ -196,21 +150,21 @@ pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave {
var file = std.fs.openFileAbsolute(path, .{}) catch return save;
defer file.close();
var file_buffer: [64]u8 = undefined;
var file_reader = file.reader(&file_buffer);
var reader = &file_reader.interface;
const reader = file.reader();
var user_writer = std.Io.Writer.fixed(user_buf);
var written = reader.streamDelimiter(&user_writer, '\n') catch return save;
if (written > 0) save.user = user_buf[0..written];
var user_fbs = std.io.fixedBufferStream(user_buf);
reader.streamUntilDelimiter(user_fbs.writer(), '\n', user_buf.len) catch return save;
const user = user_fbs.getWritten();
if (user.len > 0) save.user = user;
var session_buf: [20]u8 = undefined;
var session_writer = std.Io.Writer.fixed(&session_buf);
written = reader.streamDelimiter(&session_writer, '\n') catch return save;
var session_fbs = std.io.fixedBufferStream(&session_buf);
reader.streamUntilDelimiter(session_fbs.writer(), '\n', session_buf.len) catch return save;
const session_index_str = session_fbs.getWritten();
var session_index: ?usize = null;
if (written > 0) {
session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return save;
if (session_index_str.len > 0) {
session_index = std.fmt.parseUnsigned(usize, session_index_str, 10) catch return save;
}
save.session_index = session_index;
}
@@ -218,28 +172,32 @@ pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave {
return save;
}
pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, save_ini: *ini.Ini(OldSave), path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !bool {
var old_save_file_exists = true;
fn mapColor(color: u16) ![]const u8 {
const color_no_styling = color & 0x00FF;
const styling_only = color & 0xFF00;
var user_buf: [32]u8 = undefined;
const save = save_ini.readFileToStruct(path, .{
.fieldHandler = null,
.comment_characters = "#",
}) catch no_save_file: {
old_save_file_exists = false;
break :no_save_file tryMigrateFirstSaveFile(&user_buf);
// If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM,
// we have an invalid color, so return an error
if (color_no_styling > 0x0008 or styling_only > 0x8000) return error.InvalidColor;
var new_color: u32 = switch (color_no_styling) {
0x0000 => 0x00000000, // Default
0x0001 => 0x20000000, // "Hi-black" styling
0x0002 => 0x00FF0000, // Red
0x0003 => 0x0000FF00, // Green
0x0004 => 0x00FFFF00, // Yellow
0x0005 => 0x000000FF, // Blue
0x0006 => 0x00FF00FF, // Magenta
0x0007 => 0x0000FFFF, // Cyan
0x0008 => 0x00FFFFFF, // White
else => unreachable,
};
if (!old_save_file_exists) return false;
// Add all other users to the list
for (usernames, 0..) |username, i| {
if (save.user) |user| {
if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i;
}
try saved_users.user_list.append(allocator, .{ .username = username, .session_index = save.session_index orelse 0 });
// Only applying styling if color isn't black and styling isn't also black
if (!(new_color == 0x20000000 and styling_only == 0x20000000)) {
// Shift styling by 16 to the left to apply it to the new 32-bit color
new_color |= @as(u32, @intCast(styling_only)) << 16;
}
return true;
return try std.fmt.bufPrint(&buffer, "0x{X}", .{new_color});
}

View File

@@ -3,7 +3,6 @@ pub const Animation = enum {
doom,
matrix,
colormix,
gameoflife,
};
pub const DisplayServer = enum {
@@ -11,7 +10,6 @@ pub const DisplayServer = enum {
shell,
xinitrc,
x11,
custom,
};
pub const Input = enum {

View File

@@ -1,6 +1,6 @@
const std = @import("std");
const builtin = @import("builtin");
const UidRange = @import("UidRange.zig");
const Allocator = std.mem.Allocator;
pub const termbox = @import("termbox2");
@@ -17,247 +17,53 @@ pub const xcb = @cImport({
@cInclude("xcb/xcb.h");
});
const pwd = @cImport({
@cInclude("pwd.h");
// We include a FreeBSD-specific header here since login_cap.h references
// the passwd struct directly, so we can't import it separately
if (builtin.os.tag == .freebsd) {
@cInclude("sys/types.h");
@cInclude("login_cap.h");
}
});
const stdlib = @cImport({
@cInclude("stdlib.h");
});
const unistd = @cImport({
pub const unistd = @cImport({
@cInclude("unistd.h");
});
const grp = @cImport({
@cInclude("grp.h");
});
const system_time = @cImport({
@cInclude("sys/time.h");
});
const time = @cImport({
pub const time = @cImport({
@cInclude("time.h");
});
pub const TimeOfDay = struct {
seconds: i64,
microseconds: i64,
};
pub const system_time = @cImport({
@cInclude("sys/time.h");
});
pub const UsernameEntry = struct {
username: ?[]const u8,
uid: std.posix.uid_t,
gid: std.posix.gid_t,
home: ?[]const u8,
shell: ?[]const u8,
passwd_struct: [*c]pwd.passwd,
};
pub const stdlib = @cImport({
@cInclude("stdlib.h");
});
// Contains the platform-specific code
fn PlatformStruct() type {
return switch (builtin.os.tag) {
.linux => struct {
pub const kd = @cImport({
@cInclude("sys/kd.h");
});
pub const pwd = @cImport({
@cInclude("pwd.h");
// We include a FreeBSD-specific header here since login_cap.h references
// the passwd struct directly, so we can't import it separately'
if (builtin.os.tag == .freebsd) @cInclude("login_cap.h");
});
pub const vt = @cImport({
@cInclude("sys/vt.h");
});
pub const grp = @cImport({
@cInclude("grp.h");
});
pub const LedState = c_char;
pub const get_led_state = kd.KDGKBLED;
pub const set_led_state = kd.KDSKBLED;
pub const numlock_led = kd.K_NUMLOCK;
pub const capslock_led = kd.K_CAPSLOCK;
pub const vt_activate = vt.VT_ACTIVATE;
pub const vt_waitactive = vt.VT_WAITACTIVE;
// BSD-specific headers
pub const kbio = @cImport({
@cInclude("sys/kbio.h");
});
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
const status = grp.initgroups(username, @intCast(entry.gid));
if (status != 0) return error.GroupInitializationFailed;
// Linux-specific headers
pub const kd = @cImport({
@cInclude("sys/kd.h");
});
std.posix.setgid(@intCast(entry.gid)) catch return error.SetUserGidFailed;
std.posix.setuid(@intCast(entry.uid)) catch return error.SetUserUidFailed;
}
pub const vt = @cImport({
@cInclude("sys/vt.h");
});
// Procedure:
// 1. Open /proc/self/stat to retrieve the tty_nr field
// 2. Parse the tty_nr field to extract the major and minor device
// numbers
// 3. Then, read every /sys/class/tty/[dir]/dev, where [dir] is
// every sub-directory
// 4. Finally, compare the major and minor device numbers with the
// extracted values. If they correspond, parse [dir] to get the
// TTY ID
pub fn getActiveTtyImpl(allocator: std.mem.Allocator) !u8 {
var file_buffer: [256]u8 = undefined;
var tty_major: u16 = undefined;
var tty_minor: u16 = undefined;
{
var file = try std.fs.openFileAbsolute("/proc/self/stat", .{});
defer file.close();
var reader = file.reader(&file_buffer);
var buffer: [1024]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer);
var iterator = std.mem.splitScalar(u8, buffer[0..read], ' ');
var fields: [52][]const u8 = undefined;
var index: usize = 0;
while (iterator.next()) |field| {
fields[index] = field;
index += 1;
}
const tty_nr = try std.fmt.parseInt(u16, fields[6], 10);
tty_major = tty_nr / 256;
tty_minor = tty_nr % 256;
}
var directory = try std.fs.openDirAbsolute("/sys/class/tty", .{ .iterate = true });
defer directory.close();
var iterator = directory.iterate();
while (try iterator.next()) |entry| {
const path = try std.fmt.allocPrint(allocator, "/sys/class/tty/{s}/dev", .{entry.name});
defer allocator.free(path);
var file = try std.fs.openFileAbsolute(path, .{});
defer file.close();
var reader = file.reader(&file_buffer);
var buffer: [16]u8 = undefined;
const read = try readBuffer(&reader.interface, &buffer);
var device_iterator = std.mem.splitScalar(u8, buffer[0..(read - 1)], ':');
const device_major_str = device_iterator.next() orelse continue;
const device_minor_str = device_iterator.next() orelse continue;
const device_major = try std.fmt.parseInt(u8, device_major_str, 10);
const device_minor = try std.fmt.parseInt(u8, device_minor_str, 10);
if (device_major == tty_major and device_minor == tty_minor) {
const tty_id_str = entry.name["tty".len..];
return try std.fmt.parseInt(u8, tty_id_str, 10);
}
}
return error.NoTtyFound;
}
// This is very bad parsing, but we only need to get 2 values..
// and the format of the file seems to be standard? So this should
// be fine...
pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange {
const login_defs_file = try std.fs.cwd().openFile(file_path, .{});
defer login_defs_file.close();
const login_defs_buffer = try login_defs_file.readToEndAlloc(allocator, std.math.maxInt(u16));
defer allocator.free(login_defs_buffer);
var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n');
var uid_range = UidRange{};
while (iterator.next()) |line| {
const trimmed_line = std.mem.trim(u8, line, " \n\r\t");
if (std.mem.startsWith(u8, trimmed_line, "UID_MIN")) {
uid_range.uid_min = try parseValue(std.posix.uid_t, "UID_MIN", trimmed_line);
} else if (std.mem.startsWith(u8, trimmed_line, "UID_MAX")) {
uid_range.uid_max = try parseValue(std.posix.uid_t, "UID_MAX", trimmed_line);
}
}
return uid_range;
}
fn parseValue(comptime T: type, name: []const u8, buffer: []const u8) !T {
var iterator = std.mem.splitAny(u8, buffer, " \t");
var maybe_value: ?T = null;
while (iterator.next()) |slice| {
// Skip the slice if it's empty (whitespace) or is the name of the
// property (e.g. UID_MIN or UID_MAX)
if (slice.len == 0 or std.mem.eql(u8, slice, name)) continue;
maybe_value = std.fmt.parseInt(T, slice, 10) catch continue;
}
return maybe_value orelse error.ValueNotFound;
}
fn readBuffer(reader: *std.Io.Reader, buffer: []u8) !usize {
var bytes_read: usize = 0;
var byte: u8 = try reader.takeByte();
while (byte != 0 and bytes_read < buffer.len) {
buffer[bytes_read] = byte;
bytes_read += 1;
byte = reader.takeByte() catch break;
}
return bytes_read;
}
},
.freebsd => struct {
pub const kbio = @cImport({
@cInclude("sys/kbio.h");
});
pub const consio = @cImport({
@cInclude("sys/consio.h");
});
pub const LedState = c_int;
pub const get_led_state = kbio.KDGETLED;
pub const set_led_state = kbio.KDSETLED;
pub const numlock_led = kbio.LED_NUM;
pub const capslock_led = kbio.LED_CAP;
pub const vt_activate = consio.VT_ACTIVATE;
pub const vt_waitactive = consio.VT_WAITACTIVE;
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
// FreeBSD has initgroups() in unistd
const status = unistd.initgroups(username, @intCast(entry.gid));
if (status != 0) return error.GroupInitializationFailed;
// FreeBSD sets the GID and UID with setusercontext()
const result = pwd.setusercontext(null, entry.passwd_struct, @intCast(entry.uid), pwd.LOGIN_SETALL);
if (result != 0) return error.SetUserUidFailed;
}
pub fn getActiveTtyImpl(_: std.mem.Allocator) !u8 {
return error.FeatureUnimplemented;
}
pub fn getUserIdRange(_: std.mem.Allocator, _: []const u8) !UidRange {
return .{
// Hardcoded default values chosen from
// /usr/src/usr.sbin/pw/pw_conf.c
.uid_min = 1000,
.uid_max = 32000,
};
}
},
else => @compileError("Unsupported target: " ++ builtin.os.tag),
};
}
const platform_struct = PlatformStruct();
pub fn supportsUnicode() bool {
return builtin.os.tag == .linux or builtin.os.tag == .freebsd;
}
// Used for getting & setting the lock state
const LedState = if (builtin.os.tag.isBSD()) c_int else c_char;
const get_led_state = if (builtin.os.tag.isBSD()) kbio.KDGETLED else kd.KDGKBLED;
const set_led_state = if (builtin.os.tag.isBSD()) kbio.KDSETLED else kd.KDSKBLED;
const numlock_led = if (builtin.os.tag.isBSD()) kbio.LED_NUM else kd.K_NUMLOCK;
const capslock_led = if (builtin.os.tag.isBSD()) kbio.LED_CAP else kd.K_CAPSLOCK;
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) []u8 {
const timer = std.time.timestamp();
@@ -267,122 +73,37 @@ pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) []u8 {
return buf[0..len];
}
pub fn getTimeOfDay() !TimeOfDay {
var tv: system_time.timeval = undefined;
const status = system_time.gettimeofday(&tv, null);
pub fn switchTty(console_dev: []const u8, tty: u8) !void {
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .WRONLY }, 0);
defer std.posix.close(fd);
if (status != 0) return error.FailedToGetTimeOfDay;
return .{
.seconds = @intCast(tv.tv_sec),
.microseconds = @intCast(tv.tv_usec),
};
_ = std.c.ioctl(fd, vt.VT_ACTIVATE, tty);
_ = std.c.ioctl(fd, vt.VT_WAITACTIVE, tty);
}
pub fn getActiveTty(allocator: std.mem.Allocator) !u8 {
return platform_struct.getActiveTtyImpl(allocator);
}
pub fn switchTty(tty: u8) !void {
var status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.vt_activate, tty);
if (status != 0) return error.FailedToActivateTty;
status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.vt_waitactive, tty);
if (status != 0) return error.FailedToWaitForActiveTty;
}
pub fn getLockState() !struct {
pub fn getLockState(console_dev: []const u8) !struct {
numlock: bool,
capslock: bool,
} {
var led: platform_struct.LedState = undefined;
const status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.get_led_state, &led);
if (status != 0) return error.FailedToGetLockState;
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .RDONLY }, 0);
defer std.posix.close(fd);
var led: LedState = undefined;
_ = std.c.ioctl(fd, get_led_state, &led);
return .{
.numlock = (led & platform_struct.numlock_led) != 0,
.capslock = (led & platform_struct.capslock_led) != 0,
.numlock = (led & numlock_led) != 0,
.capslock = (led & capslock_led) != 0,
};
}
pub fn setNumlock(val: bool) !void {
var led: platform_struct.LedState = undefined;
var status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.get_led_state, &led);
if (status != 0) return error.FailedToGetNumlock;
var led: LedState = undefined;
_ = std.c.ioctl(0, get_led_state, &led);
const numlock = (led & platform_struct.numlock_led) != 0;
const numlock = (led & numlock_led) != 0;
if (numlock != val) {
status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.set_led_state, led ^ platform_struct.numlock_led);
const status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led);
if (status != 0) return error.FailedToSetNumlock;
}
}
pub fn setUserContext(allocator: std.mem.Allocator, entry: UsernameEntry) !void {
const username_z = try allocator.dupeZ(u8, entry.username.?);
defer allocator.free(username_z);
return platform_struct.setUserContextImpl(username_z.ptr, entry);
}
pub fn setUserShell(entry: *UsernameEntry) void {
unistd.setusershell();
const shell = unistd.getusershell();
entry.shell = std.mem.span(shell);
unistd.endusershell();
}
pub fn setEnvironmentVariable(allocator: std.mem.Allocator, name: []const u8, value: []const u8, replace: bool) !void {
const name_z = try allocator.dupeZ(u8, name);
defer allocator.free(name_z);
const value_z = try allocator.dupeZ(u8, value);
defer allocator.free(value_z);
const status = stdlib.setenv(name_z.ptr, value_z.ptr, @intFromBool(replace));
if (status != 0) return error.SetEnvironmentVariableFailed;
}
pub fn putEnvironmentVariable(name_and_value: [*c]u8) !void {
const status = stdlib.putenv(name_and_value);
if (status != 0) return error.PutEnvironmentVariableFailed;
}
pub fn getNextUsernameEntry() ?UsernameEntry {
const entry = pwd.getpwent();
if (entry == null) return null;
return .{
.username = if (entry.*.pw_name) |name| std.mem.span(name) else null,
.uid = @intCast(entry.*.pw_uid),
.gid = @intCast(entry.*.pw_gid),
.home = if (entry.*.pw_dir) |dir| std.mem.span(dir) else null,
.shell = if (entry.*.pw_shell) |shell| std.mem.span(shell) else null,
.passwd_struct = entry,
};
}
pub fn getUsernameEntry(username: [:0]const u8) ?UsernameEntry {
const entry = pwd.getpwnam(username);
if (entry == null) return null;
return .{
.username = if (entry.*.pw_name) |name| std.mem.span(name) else null,
.uid = @intCast(entry.*.pw_uid),
.gid = @intCast(entry.*.pw_gid),
.home = if (entry.*.pw_dir) |dir| std.mem.span(dir) else null,
.shell = if (entry.*.pw_shell) |shell| std.mem.span(shell) else null,
.passwd_struct = entry,
};
}
pub fn closePasswordDatabase() void {
pwd.endpwent();
}
// This is very bad parsing, but we only need to get 2 values... and the format
// of the file doesn't seem to be standard? So this should be fine...
pub fn getUserIdRange(allocator: std.mem.Allocator, file_path: []const u8) !UidRange {
return platform_struct.getUserIdRange(allocator, file_path);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const interop = @import("../interop.zig");
const Cell = @import("Cell.zig");
@@ -30,22 +31,14 @@ pub const Styling = struct {
pub const Color = struct {
pub const DEFAULT = 0x00000000;
pub const TRUE_BLACK = Styling.HI_BLACK;
pub const TRUE_RED = 0x00FF0000;
pub const TRUE_GREEN = 0x0000FF00;
pub const TRUE_YELLOW = 0x00FFFF00;
pub const TRUE_BLUE = 0x000000FF;
pub const TRUE_MAGENTA = 0x00FF00FF;
pub const TRUE_CYAN = 0x0000FFFF;
pub const TRUE_WHITE = 0x00FFFFFF;
pub const ECOL_BLACK = 1;
pub const ECOL_RED = 2;
pub const ECOL_GREEN = 3;
pub const ECOL_YELLOW = 4;
pub const ECOL_BLUE = 5;
pub const ECOL_MAGENTA = 6;
pub const ECOL_CYAN = 7;
pub const ECOL_WHITE = 8;
pub const BLACK = Styling.HI_BLACK;
pub const RED = 0x00FF0000;
pub const GREEN = 0x0000FF00;
pub const YELLOW = 0x00FFFF00;
pub const BLUE = 0x000000FF;
pub const MAGENTA = 0x00FF00FF;
pub const CYAN = 0x0000FFFF;
pub const WHITE = 0x00FFFFFF;
};
random: Random,
@@ -81,7 +74,7 @@ pub fn init(options: InitOptions, labels_max_length: usize, random: Random) Term
.fg = options.fg,
.bg = options.bg,
.border_fg = options.border_fg,
.box_chars = if (interop.supportsUnicode()) .{
.box_chars = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) .{
.left_up = 0x250C,
.left_down = 0x2514,
.right_up = 0x2510,
@@ -117,28 +110,24 @@ pub fn cascade(self: TerminalBuffer) bool {
while (y > 0) : (y -= 1) {
for (0..self.width) |x| {
var cell: ?*termbox.tb_cell = undefined;
var cell_under: ?*termbox.tb_cell = undefined;
var cell: termbox.tb_cell = undefined;
var cell_under: termbox.tb_cell = undefined;
_ = termbox.tb_get_cell(@intCast(x), @intCast(y - 1), 1, &cell);
_ = termbox.tb_get_cell(@intCast(x), @intCast(y), 1, &cell_under);
// This shouldn't happen under normal circumstances, but because
// this is a *secret* animation, there's no need to care that much
if (cell == null or cell_under == null) continue;
const char: u8 = @truncate(cell.?.ch);
const char: u8 = @truncate(cell.ch);
if (std.ascii.isWhitespace(char)) continue;
const char_under: u8 = @truncate(cell_under.?.ch);
const char_under: u8 = @truncate(cell_under.ch);
if (!std.ascii.isWhitespace(char_under)) continue;
changed = true;
if ((self.random.int(u16) % 10) > 7) continue;
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.?.ch, cell.?.fg, cell.?.bg);
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.?.fg, cell_under.?.bg);
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.fg, cell_under.bg);
}
}
@@ -231,7 +220,7 @@ pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: usize, y: us
var i: c_int = @intCast(x);
while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) {
if (i - @as(c_int, @intCast(x)) >= max_length) break;
if (i >= max_length) break;
_ = termbox.tb_set_cell(i, yc, codepoint, self.fg, self.bg);
}
}
@@ -248,6 +237,5 @@ pub fn strWidth(str: []const u8) !u8 {
var utf8 = utf8view.iterator();
var i: c_int = 0;
while (utf8.nextCodepoint()) |codepoint| i += termbox.tb_wcwidth(codepoint);
return @intCast(i);
}

View File

@@ -4,7 +4,7 @@ const generic = @import("generic.zig");
const Allocator = std.mem.Allocator;
const MessageLabel = generic.CyclableLabel(Message, Message);
const MessageLabel = generic.CyclableLabel(Message);
const InfoLine = @This();
@@ -19,7 +19,7 @@ label: MessageLabel,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
return .{
.label = MessageLabel.init(allocator, buffer, drawItem, null, null),
.label = MessageLabel.init(allocator, buffer, drawItem),
};
}

View File

@@ -1,55 +1,46 @@
const std = @import("std");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const enums = @import("../../enums.zig");
const ini = @import("zigini");
const Environment = @import("../../Environment.zig");
const generic = @import("generic.zig");
const UserList = @import("UserList.zig");
const Allocator = std.mem.Allocator;
const DisplayServer = enums.DisplayServer;
const Env = struct {
environment: Environment,
index: usize,
};
const EnvironmentLabel = generic.CyclableLabel(Env, *UserList);
const Ini = ini.Ini;
const EnvironmentLabel = generic.CyclableLabel(Environment);
const Session = @This();
label: EnvironmentLabel,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, user_list: *UserList) Session {
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) Session {
return .{
.label = EnvironmentLabel.init(allocator, buffer, drawItem, sessionChanged, user_list),
.label = EnvironmentLabel.init(allocator, buffer, drawItem),
};
}
pub fn deinit(self: *Session) void {
for (self.label.list.items) |*env| {
if (env.environment.entry_ini) |*entry_ini| entry_ini.deinit();
for (self.label.list.items) |*environment| {
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
if (environment.xdg_session_desktop) |session_desktop| self.label.allocator.free(session_desktop);
}
self.label.deinit();
}
pub fn addEnvironment(self: *Session, environment: Environment) !void {
try self.label.addItem(.{ .environment = environment, .index = self.label.list.items.len });
try self.label.addItem(environment);
}
fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void {
if (maybe_user_list) |user_list| {
user_list.label.list.items[user_list.label.current].session_index.* = env.index;
}
}
fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize) bool {
const length = @min(env.environment.name.len, label.visible_length - 3);
fn drawItem(label: *EnvironmentLabel, environment: Environment, x: usize, y: usize) bool {
const length = @min(environment.name.len, label.visible_length - 3);
if (length == 0) return false;
const nx = if (label.text_in_center) (label.x + (label.visible_length - env.environment.name.len) / 2) else (label.x + 2);
label.first_char_x = nx + env.environment.name.len;
const nx = if (label.text_in_center) (label.x + (label.visible_length - environment.name.len) / 2) else (label.x + 2);
label.first_char_x = nx + environment.name.len;
label.buffer.drawLabel(env.environment.specifier, x, y);
label.buffer.drawLabel(env.environment.name, nx, label.y);
label.buffer.drawLabel(environment.specifier, x, y);
label.buffer.drawLabel(environment.name, nx, label.y);
return true;
}

View File

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

View File

@@ -2,12 +2,11 @@ const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
pub fn CyclableLabel(comptime ItemType: type) type {
return struct {
const Allocator = std.mem.Allocator;
const ItemList = std.ArrayListUnmanaged(ItemType);
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
const termbox = interop.termbox;
@@ -23,10 +22,8 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
first_char_x: usize,
text_in_center: bool,
draw_item_fn: DrawItemFn,
change_item_fn: ?ChangeItemFn,
change_item_arg: ?ChangeItemType,
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn, change_item_fn: ?ChangeItemFn, change_item_arg: ?ChangeItemType) Self {
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn) Self {
return .{
.allocator = allocator,
.buffer = buffer,
@@ -38,8 +35,6 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
.first_char_x = 0,
.text_in_center = false,
.draw_item_fn = draw_item_fn,
.change_item_fn = change_item_fn,
.change_item_arg = change_item_arg,
};
}
@@ -99,19 +94,21 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
}
fn goLeft(self: *Self) void {
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
if (self.change_item_fn) |change_item_fn| {
@call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg });
if (self.current == 0) {
self.current = self.list.items.len - 1;
return;
}
self.current -= 1;
}
fn goRight(self: *Self) void {
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
if (self.change_item_fn) |change_item_fn| {
@call(.auto, change_item_fn, .{ self.list.items[self.current], self.change_item_arg });
if (self.current == self.list.items.len - 1) {
self.current = 0;
return;
}
self.current += 1;
}
};
}