mirror of
https://github.com/fairyglade/ly.git
synced 2026-05-06 23:30:37 +00:00
Compare commits
286 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
807f6d249a | ||
|
|
15cd0c4779 | ||
|
|
51c5c3ee0b | ||
|
|
59c07aa3ba | ||
|
|
80d4b114f3 | ||
|
|
4f45d92ea8 | ||
|
|
5edf5251f6 | ||
|
|
eec83179b9 | ||
|
|
b8048234d9 | ||
|
|
10a873acb9 | ||
|
|
142476041d | ||
|
|
fad683e035 | ||
|
|
e882eea22a | ||
|
|
fe6942d406 | ||
|
|
5b7c7dfdf5 | ||
|
|
074bb0a68a | ||
|
|
e0a3364169 | ||
|
|
7a8d913531 | ||
|
|
984ac596af | ||
|
|
ed486c29d2 | ||
|
|
a6fc5d67e8 | ||
|
|
549576aa3e | ||
|
|
3758b5da1b | ||
|
|
5e1c681385 | ||
|
|
ac78ccc398 | ||
|
|
e548333473 | ||
|
|
60e3380375 | ||
|
|
aa392837bc | ||
|
|
abe72c74ff | ||
|
|
dda56eab37 | ||
|
|
9c50297059 | ||
|
|
acac884cfe | ||
|
|
a89c918c5d | ||
|
|
64539f4342 | ||
|
|
93696a6b30 | ||
|
|
4f26eeada0 | ||
|
|
83e98a185f | ||
|
|
128dcb16f8 | ||
|
|
3a4109eb2d | ||
|
|
7cefff4570 | ||
|
|
f31c55b562 | ||
|
|
9cde291ac7 | ||
|
|
03d976171a | ||
|
|
b01e4afc79 | ||
|
|
5a4605ffb6 | ||
|
|
01dcfa207e | ||
|
|
32d5330efb | ||
|
|
5564fed664 | ||
|
|
7c7aed9cb2 | ||
|
|
57c96a3478 | ||
|
|
6773f74788 | ||
|
|
b389e379fa | ||
|
|
4a72e41e44 | ||
|
|
d268d5bb45 | ||
|
|
f320d3f666 | ||
|
|
70e95f094a | ||
|
|
207b352888 | ||
|
|
99dba44e46 | ||
|
|
d1810d8c98 | ||
|
|
852a602032 | ||
|
|
cf5f62661c | ||
|
|
1db780c7a7 | ||
|
|
941b7e0dae | ||
|
|
e9e2d51261 | ||
|
|
769aefd6e9 | ||
|
|
f678e3bb28 | ||
|
|
f22593f828 | ||
|
|
7bbdebe58b | ||
|
|
bca38856b1 | ||
|
|
8c08359e51 | ||
|
|
fd81da7cbd | ||
|
|
950eeed3ee | ||
|
|
ce0d00771d | ||
|
|
b032c9b296 | ||
|
|
7744745f09 | ||
|
|
11735290b8 | ||
|
|
21fca058e7 | ||
|
|
2b46a81796 | ||
|
|
72f43fbc56 | ||
|
|
b00d6899e5 | ||
|
|
7ce8ff61fe | ||
|
|
1a04a608a1 | ||
|
|
b0dcc12785 | ||
|
|
b672d04dc6 | ||
|
|
2e04ea4d79 | ||
|
|
0c12008327 | ||
|
|
7934060d3b | ||
|
|
a158098df0 | ||
|
|
5bfa1670cc | ||
|
|
a4076b83da | ||
|
|
2eea683078 | ||
|
|
d7f64676ee | ||
|
|
456916f059 | ||
|
|
2b1e4dc6c9 | ||
|
|
94c306758a | ||
|
|
82d24d7725 | ||
|
|
5a51d5ced5 | ||
|
|
135d1e40f6 | ||
|
|
c0c400e0b6 | ||
|
|
9e4147bfb4 | ||
|
|
8e893932f2 | ||
|
|
26e7585b0b | ||
|
|
b1cb576f67 | ||
|
|
add7f25f0d | ||
|
|
2e7bb3eb58 | ||
|
|
e57de5172e | ||
|
|
04d4447273 | ||
|
|
ced8f9bee3 | ||
|
|
c6446db3e2 | ||
|
|
e0692885c5 | ||
|
|
b6d3aa0e1c | ||
|
|
e6966a628c | ||
|
|
cab3f1bfb5 | ||
|
|
a9ff0a6d07 | ||
|
|
92beb24c80 | ||
|
|
7e18d906c4 | ||
|
|
3bfdc75a70 | ||
|
|
6cce221cbf | ||
|
|
1c99574f73 | ||
|
|
b249dba092 | ||
|
|
3365b33d6d | ||
|
|
6cb53b6e38 | ||
|
|
d82fa82a87 | ||
|
|
c2b3d794e8 | ||
|
|
e29bda3250 | ||
|
|
4df2382698 | ||
|
|
3dfb86fe69 | ||
|
|
f9a001b160 | ||
|
|
5235ca47c5 | ||
|
|
a94abf2e69 | ||
|
|
fe354a4809 | ||
|
|
8c964d9ce5 | ||
|
|
392ea6ea63 | ||
|
|
10854e643a | ||
|
|
1980b2e479 | ||
|
|
816be7449f | ||
|
|
f0758d812e | ||
|
|
3c0c84d067 | ||
|
|
cc07c4870a | ||
|
|
68ec85f412 | ||
|
|
1537addd67 | ||
|
|
2da3648179 | ||
|
|
0cf752f3b8 | ||
|
|
15c78c838a | ||
|
|
4a486bd876 | ||
|
|
38173d8557 | ||
|
|
95d1d9378c | ||
|
|
6f62106fcc | ||
|
|
4171e29995 | ||
|
|
80c27224e9 | ||
|
|
106f157a2c | ||
|
|
ec16ad5dfc | ||
|
|
5f22173b91 | ||
|
|
4bc405f239 | ||
|
|
c4b68364ef | ||
|
|
1c05664c85 | ||
|
|
1fbcb10110 | ||
|
|
bfb3f925d9 | ||
|
|
44c8acff16 | ||
|
|
541eae5311 | ||
|
|
3faf3dec42 | ||
|
|
52d29bbd47 | ||
|
|
a3a8f11575 | ||
|
|
02f5aa702d | ||
|
|
ed88458efd | ||
|
|
8df9603188 | ||
|
|
412994775b | ||
|
|
bb669c239c | ||
|
|
657daafec8 | ||
|
|
e36872baa6 | ||
|
|
4738ca81a7 | ||
|
|
1f2453f0fb | ||
|
|
76da16904f | ||
|
|
e577c454da | ||
|
|
09c2cfb74d | ||
|
|
b3f1e91cf6 | ||
|
|
aef1dd9c1a | ||
|
|
3d977d2ff7 | ||
|
|
a34a5a97bd | ||
|
|
1839e4cb44 | ||
|
|
b2f51e5bc8 | ||
|
|
bd335c8c91 | ||
|
|
4f4855b5e9 | ||
|
|
1e2faad0f8 | ||
|
|
cb4f1952cd | ||
|
|
81a17f2904 | ||
|
|
339e39d496 | ||
|
|
44faa263b1 | ||
|
|
7a0520687d | ||
|
|
cee0e0ca4b | ||
|
|
3edd1ff1be | ||
|
|
145ad5142c | ||
|
|
d8b0ae34f3 | ||
|
|
a6535b9152 | ||
|
|
ee97f3b5e1 | ||
|
|
5924db58e1 | ||
|
|
36e220e2ff | ||
|
|
0a9ceca822 | ||
|
|
f9553655a3 | ||
|
|
230874abd1 | ||
|
|
38c3ecd089 | ||
|
|
fec0815161 | ||
|
|
a7d6b06d21 | ||
|
|
1ee8010c24 | ||
|
|
f988bd334b | ||
|
|
aa0222948a | ||
|
|
69d39dc035 | ||
|
|
6d7dbb9f27 | ||
|
|
7cfb947187 | ||
|
|
336847d418 | ||
|
|
ff9b6279d3 | ||
|
|
c7dea3c178 | ||
|
|
d7a4535007 | ||
|
|
73ecac67bf | ||
|
|
7353e75d78 | ||
|
|
cd0accfb28 | ||
|
|
b71789912d | ||
|
|
5bacc8870b | ||
|
|
a7ff18aa16 | ||
|
|
c3d180c213 | ||
|
|
3f891d7f0d | ||
|
|
e404d5bdb3 | ||
|
|
dee055748c | ||
|
|
bd2d1142b2 | ||
|
|
b382d74969 | ||
|
|
4fbbb6f0f2 | ||
|
|
ef64029795 | ||
|
|
71c694e575 | ||
|
|
c37aa6957a | ||
|
|
8377f14509 | ||
|
|
b35c055e7b | ||
|
|
c05c32c5be | ||
|
|
1f0274e797 | ||
|
|
f97d0c27d3 | ||
|
|
d36147a376 | ||
|
|
8030cf5244 | ||
|
|
933e5bdd2d | ||
|
|
a9d85a6925 | ||
|
|
3d3cf84292 | ||
|
|
19c879a201 | ||
|
|
98f59a69cc | ||
|
|
5fb40899e5 | ||
|
|
6933c4db02 | ||
|
|
c3d0864e62 | ||
|
|
e8cb8970fd | ||
|
|
c11194332c | ||
|
|
35265f5e77 | ||
|
|
5c3da10386 | ||
|
|
1d4e32ba82 | ||
|
|
97efac0cd1 | ||
|
|
04920e1b1b | ||
|
|
48e5369f56 | ||
|
|
918e9ad5ac | ||
|
|
ce17d346e8 | ||
|
|
ce0b05fd6e | ||
|
|
99f3ab96ba | ||
|
|
2a8e221e80 | ||
|
|
1c5686ea54 | ||
|
|
ab23631e66 | ||
|
|
1bcbb08202 | ||
|
|
d08b9a916e | ||
|
|
e5eb8b7f6f | ||
|
|
41f4378bfe | ||
|
|
7b81336761 | ||
|
|
3ad0c00380 | ||
|
|
7182d91b37 | ||
|
|
de11ac8972 | ||
|
|
2df2c44cdf | ||
|
|
9a3d62333a | ||
|
|
2ae86a695b | ||
|
|
ef78ac28a4 | ||
|
|
e7aad8de88 | ||
|
|
9b00df4337 | ||
|
|
a5e38e2ce5 | ||
|
|
e156414062 | ||
|
|
14aae40fda | ||
|
|
3504180e95 | ||
|
|
b4952cdc51 | ||
|
|
5e8e0af59c | ||
|
|
a8b8292318 | ||
|
|
36a27f6167 | ||
|
|
9d4c4a3a59 | ||
|
|
78ed67b35f | ||
|
|
fa46155f72 | ||
|
|
063b3145ed | ||
|
|
98af3a98c8 |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
github: AnErrupTion
|
||||||
|
liberapay: ShiningLea
|
||||||
11
.github/ISSUE_TEMPLATE/bug.yml
vendored
11
.github/ISSUE_TEMPLATE/bug.yml
vendored
@@ -11,11 +11,15 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I have looked for any other duplicate issues
|
- label: I have looked for any other duplicate issues
|
||||||
required: true
|
required: true
|
||||||
|
- label: I have reproduced the issue on a fresh install of my OS & Ly with default settings, except ones I will mention
|
||||||
|
required: false
|
||||||
|
- label: I have confirmed this issue also occurs on the latest development version (found in the `master` branch)
|
||||||
|
required: true
|
||||||
- type: input
|
- type: input
|
||||||
id: version
|
id: version
|
||||||
attributes:
|
attributes:
|
||||||
label: Ly version
|
label: Ly version
|
||||||
description: The output of `ly --version`. Please note that only Ly v1.1.0 and above are supported.
|
description: The output of `ly --version`. Please note that only Ly v1.2.0 and above are supported.
|
||||||
placeholder: 1.1.0-dev.12+2b0301c
|
placeholder: 1.1.0-dev.12+2b0301c
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
@@ -58,8 +62,9 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Relevant logs
|
label: Relevant logs
|
||||||
description: |
|
description: |
|
||||||
Please copy and paste any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you.
|
Please copy and paste (or attach) any relevant logs, error messages or any other output. This will be automatically formatted into code, so no need for backticks. Screenshots are accepted if they make life easier for you. The log files (located as specified by `/etc/ly/config.ini`) usually contain relevant information about the problem:
|
||||||
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!)
|
- The session log is located at `~/.local/state/ly-session.log` by default.
|
||||||
|
- The system log is located at `/var/log/ly.log` by default.
|
||||||
render: shell
|
render: shell
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: moreinfo
|
id: moreinfo
|
||||||
|
|||||||
12
.github/pull_request_template.md
vendored
Normal file
12
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
## What are the changes about?
|
||||||
|
|
||||||
|
_Replace this with a brief description of your changes_
|
||||||
|
|
||||||
|
## What existing issue does this resolve?
|
||||||
|
|
||||||
|
_Replace this with a reference to an existing issue, or N/A if there is none_
|
||||||
|
|
||||||
|
## Pre-requisites
|
||||||
|
|
||||||
|
- [ ] I have tested & confirmed the changes work locally
|
||||||
|
- [ ] I have run `zig fmt` throughout my changes
|
||||||
BIN
.github/screenshot.png
vendored
BIN
.github/screenshot.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 52 KiB |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ zig-cache/
|
|||||||
zig-out/
|
zig-out/
|
||||||
valgrind.log
|
valgrind.log
|
||||||
.zig-cache
|
.zig-cache
|
||||||
|
zig-pkg
|
||||||
|
|||||||
349
build.zig
349
build.zig
@@ -8,9 +8,11 @@ const InitSystem = enum {
|
|||||||
runit,
|
runit,
|
||||||
s6,
|
s6,
|
||||||
dinit,
|
dinit,
|
||||||
|
sysvinit,
|
||||||
|
freebsd,
|
||||||
};
|
};
|
||||||
|
|
||||||
const min_zig_string = "0.14.0";
|
const min_zig_string = "0.16.0";
|
||||||
const current_zig = builtin.zig_version;
|
const current_zig = builtin.zig_version;
|
||||||
|
|
||||||
// Implementing zig version detection through compile time
|
// Implementing zig version detection through compile time
|
||||||
@@ -21,7 +23,7 @@ comptime {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const ly_version = std.SemanticVersion{ .major = 1, .minor = 1, .patch = 1 };
|
const ly_version = std.SemanticVersion{ .major = 1, .minor = 4, .patch = 0 };
|
||||||
|
|
||||||
var dest_directory: []const u8 = undefined;
|
var dest_directory: []const u8 = undefined;
|
||||||
var config_directory: []const u8 = undefined;
|
var config_directory: []const u8 = undefined;
|
||||||
@@ -41,6 +43,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
const version_str = try getVersionStr(b, "ly", ly_version);
|
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 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 default_tty = b.option(u8, "default_tty", "Set the TTY (default is 2)") orelse 2;
|
||||||
|
const fallback_tty = b.option(u8, "fallback_tty", "Set the fallback TTY (default is 2). This value gets embedded into the binary") orelse 2;
|
||||||
|
const fallback_uid_min = b.option(std.posix.uid_t, "fallback_uid_min", "Set the fallback minimum UID (default is 1000). This value gets embedded into the binary") orelse 1000;
|
||||||
|
const fallback_uid_max = b.option(std.posix.uid_t, "fallback_uid_max", "Set the fallback maximum UID (default is 60000). This value gets embedded into the binary") orelse 60000;
|
||||||
|
|
||||||
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
|
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
|
||||||
|
|
||||||
@@ -48,6 +53,9 @@ pub fn build(b: *std.Build) !void {
|
|||||||
build_options.addOption([]const u8, "prefix_directory", prefix_directory);
|
build_options.addOption([]const u8, "prefix_directory", prefix_directory);
|
||||||
build_options.addOption([]const u8, "version", version_str);
|
build_options.addOption([]const u8, "version", version_str);
|
||||||
build_options.addOption(u8, "tty", default_tty);
|
build_options.addOption(u8, "tty", default_tty);
|
||||||
|
build_options.addOption(u8, "fallback_tty", fallback_tty);
|
||||||
|
build_options.addOption(std.posix.uid_t, "fallback_uid_min", fallback_uid_min);
|
||||||
|
build_options.addOption(std.posix.uid_t, "fallback_uid_max", fallback_uid_max);
|
||||||
build_options.addOption(bool, "enable_x11_support", enable_x11_support);
|
build_options.addOption(bool, "enable_x11_support", enable_x11_support);
|
||||||
|
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
@@ -55,33 +63,25 @@ pub fn build(b: *std.Build) !void {
|
|||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "ly",
|
.name = "ly",
|
||||||
|
.root_module = b.createModule(.{
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.root_source_file = b.path("src/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
}),
|
||||||
|
.use_llvm = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
|
const ly_ui = b.dependency("ly_ui", .{ .target = target, .optimize = optimize });
|
||||||
exe.root_module.addImport("zigini", zigini.module("zigini"));
|
exe.root_module.addImport("ly-ui", ly_ui.module("ly-ui"));
|
||||||
|
|
||||||
exe.root_module.addOptions("build_options", build_options);
|
exe.root_module.addOptions("build_options", build_options);
|
||||||
|
|
||||||
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
|
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
|
||||||
exe.root_module.addImport("clap", clap.module("clap"));
|
exe.root_module.addImport("clap", clap.module("clap"));
|
||||||
|
|
||||||
exe.addIncludePath(b.path("include"));
|
exe.root_module.linkSystemLibrary("pam", .{});
|
||||||
exe.linkSystemLibrary("pam");
|
if (enable_x11_support) exe.root_module.linkSystemLibrary("xcb", .{});
|
||||||
if (enable_x11_support) exe.linkSystemLibrary("xcb");
|
|
||||||
exe.linkLibC();
|
|
||||||
|
|
||||||
const translate_c = b.addTranslateC(.{
|
|
||||||
.root_source_file = b.path("include/termbox2.h"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
translate_c.defineCMacroRaw("TB_IMPL");
|
|
||||||
translate_c.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit)
|
|
||||||
const termbox2 = translate_c.addModule("termbox2");
|
|
||||||
exe.root_module.addImport("termbox2", termbox2);
|
|
||||||
|
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|
||||||
@@ -112,6 +112,8 @@ pub fn build(b: *std.Build) !void {
|
|||||||
pub fn Installer(install_config: bool) type {
|
pub fn Installer(install_config: bool) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
|
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
|
||||||
|
var threaded: std.Io.Threaded = .init_single_threaded;
|
||||||
|
const io = threaded.io();
|
||||||
const allocator = step.owner.allocator;
|
const allocator = step.owner.allocator;
|
||||||
|
|
||||||
var patch_map = PatchMap.init(allocator);
|
var patch_map = PatchMap.init(allocator);
|
||||||
@@ -122,154 +124,216 @@ pub fn Installer(install_config: bool) type {
|
|||||||
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
|
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
|
||||||
try patch_map.put("$EXECUTABLE_NAME", executable_name);
|
try patch_map.put("$EXECUTABLE_NAME", executable_name);
|
||||||
|
|
||||||
try install_ly(allocator, patch_map, install_config);
|
// The "-a" argument doesn't exist on FreeBSD, so we use "-p"
|
||||||
try install_service(allocator, patch_map);
|
// instead to shutdown the system.
|
||||||
|
try patch_map.put("$PLATFORM_SHUTDOWN_ARG", if (init_system == .freebsd) "-p" else "-a");
|
||||||
|
|
||||||
|
try install_ly(allocator, io, patch_map, install_config);
|
||||||
|
try install_service(allocator, io, patch_map);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_ly(allocator: std.mem.Allocator, patch_map: PatchMap, install_config: bool) !void {
|
fn install_ly(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap, install_config: bool) !void {
|
||||||
const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly" });
|
const ly_config_directory = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly" });
|
||||||
|
|
||||||
std.fs.cwd().makePath(ly_config_directory) catch {
|
std.Io.Dir.cwd().createDirPath(io, ly_config_directory) catch {
|
||||||
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
|
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
|
||||||
};
|
};
|
||||||
|
|
||||||
const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" });
|
const ly_custom_sessions_directory = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/custom-sessions" });
|
||||||
std.fs.cwd().makePath(ly_lang_path) catch {
|
|
||||||
|
std.Io.Dir.cwd().createDirPath(io, ly_custom_sessions_directory) catch {
|
||||||
|
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_custom_sessions_directory});
|
||||||
|
};
|
||||||
|
|
||||||
|
const ly_lang_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/ly/lang" });
|
||||||
|
std.Io.Dir.cwd().createDirPath(io, ly_lang_path) catch {
|
||||||
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path});
|
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_lang_path});
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
|
const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
|
||||||
std.fs.cwd().makePath(exe_path) catch {
|
std.Io.Dir.cwd().createDirPath(io, exe_path) catch {
|
||||||
if (!std.mem.eql(u8, dest_directory, "")) {
|
if (!std.mem.eql(u8, dest_directory, "")) {
|
||||||
std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path});
|
std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable;
|
var executable_dir = std.Io.Dir.cwd().openDir(io, exe_path, .{}) catch unreachable;
|
||||||
defer executable_dir.close();
|
defer executable_dir.close(io);
|
||||||
|
|
||||||
try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
|
try installFile(io, "zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable;
|
var config_dir = std.Io.Dir.cwd().openDir(io, ly_config_directory, .{}) catch unreachable;
|
||||||
defer config_dir.close();
|
defer config_dir.close(io);
|
||||||
|
|
||||||
if (install_config) {
|
if (install_config) {
|
||||||
const patched_config = try patchFile(allocator, "res/config.ini", patch_map);
|
const patched_config = try patchFile(allocator, io, "res/config.ini", patch_map);
|
||||||
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{});
|
try installText(io, patched_config, config_dir, ly_config_directory, "config.ini", .{});
|
||||||
|
|
||||||
|
try installFile(io, "res/startup.sh", config_dir, ly_config_directory, "startup.sh", .{ .permissions = .fromMode(0o755) });
|
||||||
}
|
}
|
||||||
|
|
||||||
const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map);
|
const patched_example_config = try patchFile(allocator, io, "res/config.ini", patch_map);
|
||||||
try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 });
|
try installText(io, patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{});
|
||||||
|
|
||||||
|
const patched_setup = try patchFile(allocator, io, "res/setup.sh", patch_map);
|
||||||
|
try installText(io, patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .permissions = .fromMode(0o755) });
|
||||||
|
|
||||||
|
try installFile(io, "res/example.dur", config_dir, ly_config_directory, "example.dur", .{ .permissions = .fromMode(0o755) });
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
|
var custom_sessions_dir = std.Io.Dir.cwd().openDir(io, ly_custom_sessions_directory, .{}) catch unreachable;
|
||||||
defer lang_dir.close();
|
defer custom_sessions_dir.close(io);
|
||||||
|
|
||||||
try installFile("res/lang/cat.ini", lang_dir, ly_lang_path, "cat.ini", .{});
|
const patched_readme = try patchFile(allocator, io, "res/custom-sessions/README", patch_map);
|
||||||
try installFile("res/lang/cs.ini", lang_dir, ly_lang_path, "cs.ini", .{});
|
try installText(io, patched_readme, custom_sessions_dir, ly_custom_sessions_directory, "README", .{});
|
||||||
try installFile("res/lang/de.ini", lang_dir, ly_lang_path, "de.ini", .{});
|
|
||||||
try installFile("res/lang/en.ini", lang_dir, ly_lang_path, "en.ini", .{});
|
|
||||||
try installFile("res/lang/es.ini", lang_dir, ly_lang_path, "es.ini", .{});
|
|
||||||
try installFile("res/lang/fr.ini", lang_dir, ly_lang_path, "fr.ini", .{});
|
|
||||||
try installFile("res/lang/it.ini", lang_dir, ly_lang_path, "it.ini", .{});
|
|
||||||
try installFile("res/lang/pl.ini", lang_dir, ly_lang_path, "pl.ini", .{});
|
|
||||||
try installFile("res/lang/pt.ini", lang_dir, ly_lang_path, "pt.ini", .{});
|
|
||||||
try installFile("res/lang/pt_BR.ini", lang_dir, ly_lang_path, "pt_BR.ini", .{});
|
|
||||||
try installFile("res/lang/ro.ini", lang_dir, ly_lang_path, "ro.ini", .{});
|
|
||||||
try installFile("res/lang/ru.ini", lang_dir, ly_lang_path, "ru.ini", .{});
|
|
||||||
try installFile("res/lang/sr.ini", lang_dir, ly_lang_path, "sr.ini", .{});
|
|
||||||
try installFile("res/lang/sv.ini", lang_dir, ly_lang_path, "sv.ini", .{});
|
|
||||||
try installFile("res/lang/tr.ini", lang_dir, ly_lang_path, "tr.ini", .{});
|
|
||||||
try installFile("res/lang/uk.ini", lang_dir, ly_lang_path, "uk.ini", .{});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
|
var lang_dir = std.Io.Dir.cwd().openDir(io, ly_lang_path, .{}) catch unreachable;
|
||||||
std.fs.cwd().makePath(pam_path) catch {
|
defer lang_dir.close(io);
|
||||||
|
|
||||||
|
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(io, "res/lang/" ++ language, lang_dir, ly_lang_path, language, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const pam_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
|
||||||
|
std.Io.Dir.cwd().createDirPath(io, pam_path) catch {
|
||||||
if (!std.mem.eql(u8, dest_directory, "")) {
|
if (!std.mem.eql(u8, dest_directory, "")) {
|
||||||
std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path});
|
std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable;
|
var pam_dir = std.Io.Dir.cwd().openDir(io, pam_path, .{}) catch unreachable;
|
||||||
defer pam_dir.close();
|
defer pam_dir.close(io);
|
||||||
|
|
||||||
try installFile("res/pam.d/ly", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
|
try installFile(io, if (init_system == .freebsd) "res/pam.d/ly-freebsd" else "res/pam.d/ly-linux", pam_dir, pam_path, "ly", .{ .permissions = .fromMode(0o644) });
|
||||||
|
try installFile(io, if (init_system == .freebsd) "res/pam.d/ly-freebsd-autologin" else "res/pam.d/ly-linux-autologin", pam_dir, pam_path, "ly-autologin", .{ .permissions = .fromMode(0o644) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void {
|
fn install_service(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap) !void {
|
||||||
switch (init_system) {
|
switch (init_system) {
|
||||||
.systemd => {
|
.systemd => {
|
||||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" });
|
const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" });
|
||||||
std.fs.cwd().makePath(service_path) catch {};
|
std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
|
||||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
|
||||||
defer service_dir.close();
|
defer service_dir.close(io);
|
||||||
|
|
||||||
const patched_service = try patchFile(allocator, "res/ly.service", patch_map);
|
const patched_service = try patchFile(allocator, io, "res/ly@.service", patch_map);
|
||||||
try installText(patched_service, service_dir, service_path, "ly.service", .{ .mode = 0o644 });
|
try installText(io, patched_service, service_dir, service_path, "ly@.service", .{ .permissions = .fromMode(0o644) });
|
||||||
|
|
||||||
|
const patched_kmsconvt_service = try patchFile(allocator, io, "res/ly-kmsconvt@.service", patch_map);
|
||||||
|
try installText(io, patched_kmsconvt_service, service_dir, service_path, "ly-kmsconvt@.service", .{ .permissions = .fromMode(0o644) });
|
||||||
},
|
},
|
||||||
.openrc => {
|
.openrc => {
|
||||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
|
const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
|
||||||
std.fs.cwd().makePath(service_path) catch {};
|
std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
|
||||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
|
||||||
defer service_dir.close();
|
defer service_dir.close(io);
|
||||||
|
|
||||||
const patched_service = try patchFile(allocator, "res/ly-openrc", patch_map);
|
const patched_service = try patchFile(allocator, io, "res/ly-openrc", patch_map);
|
||||||
try installText(patched_service, service_dir, service_path, executable_name, .{ .mode = 0o755 });
|
try installText(io, patched_service, service_dir, service_path, executable_name, .{ .permissions = .fromMode(0o755) });
|
||||||
},
|
},
|
||||||
.runit => {
|
.runit => {
|
||||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" });
|
const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" });
|
||||||
std.fs.cwd().makePath(service_path) catch {};
|
std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
|
||||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
|
||||||
defer service_dir.close();
|
defer service_dir.close(io);
|
||||||
|
|
||||||
const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
|
const supervise_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
|
||||||
|
|
||||||
const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map);
|
const patched_conf = try patchFile(allocator, io, "res/ly-runit-service/conf", patch_map);
|
||||||
try installText(patched_conf, service_dir, service_path, "conf", .{});
|
try installText(io, patched_conf, service_dir, service_path, "conf", .{});
|
||||||
|
|
||||||
try installFile("res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .override_mode = 0o755 });
|
try installFile(io, "res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .permissions = .fromMode(0o755) });
|
||||||
|
|
||||||
const patched_run = try patchFile(allocator, "res/ly-runit-service/run", patch_map);
|
const patched_run = try patchFile(allocator, io, "res/ly-runit-service/run", patch_map);
|
||||||
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
|
try installText(io, patched_run, service_dir, service_path, "run", .{ .permissions = .fromMode(0o755) });
|
||||||
|
|
||||||
try std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{});
|
std.Io.Dir.cwd().symLink(io, "/run/runit/supervise.ly", supervise_path, .{}) catch |err| {
|
||||||
|
if (err == error.PathAlreadyExists) {
|
||||||
|
std.debug.print("warn: /run/runit/supervise.ly already exists as a symbolic link.\n", .{});
|
||||||
|
} else {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
};
|
||||||
std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{});
|
std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{});
|
||||||
},
|
},
|
||||||
.s6 => {
|
.s6 => {
|
||||||
const admin_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" });
|
const admin_service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" });
|
||||||
std.fs.cwd().makePath(admin_service_path) catch {};
|
std.Io.Dir.cwd().createDirPath(io, admin_service_path) catch {};
|
||||||
var admin_service_dir = std.fs.cwd().openDir(admin_service_path, .{}) catch unreachable;
|
var admin_service_dir = std.Io.Dir.cwd().openDir(io, admin_service_path, .{}) catch unreachable;
|
||||||
defer admin_service_dir.close();
|
defer admin_service_dir.close(io);
|
||||||
|
|
||||||
const file = try admin_service_dir.createFile("ly-srv", .{});
|
const file = try admin_service_dir.createFile(io, "ly-srv", .{});
|
||||||
file.close();
|
file.close(io);
|
||||||
|
|
||||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" });
|
const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" });
|
||||||
std.fs.cwd().makePath(service_path) catch {};
|
std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
|
||||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
|
||||||
defer service_dir.close();
|
defer service_dir.close(io);
|
||||||
|
|
||||||
const patched_run = try patchFile(allocator, "res/ly-s6/run", patch_map);
|
const patched_run = try patchFile(allocator, io, "res/ly-s6/run", patch_map);
|
||||||
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
|
try installText(io, patched_run, service_dir, service_path, "run", .{ .permissions = .fromMode(0o755) });
|
||||||
|
|
||||||
try installFile("res/ly-s6/type", service_dir, service_path, "type", .{});
|
try installFile(io, "res/ly-s6/type", service_dir, service_path, "type", .{});
|
||||||
},
|
},
|
||||||
.dinit => {
|
.dinit => {
|
||||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" });
|
const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" });
|
||||||
std.fs.cwd().makePath(service_path) catch {};
|
std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
|
||||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
|
||||||
defer service_dir.close();
|
defer service_dir.close(io);
|
||||||
|
|
||||||
const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map);
|
const patched_service = try patchFile(allocator, io, "res/ly-dinit", patch_map);
|
||||||
try installText(patched_service, service_dir, service_path, "ly", .{});
|
try installText(io, patched_service, service_dir, service_path, "ly", .{});
|
||||||
|
},
|
||||||
|
.sysvinit => {
|
||||||
|
const service_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
|
||||||
|
std.Io.Dir.cwd().createDirPath(io, service_path) catch {};
|
||||||
|
var service_dir = std.Io.Dir.cwd().openDir(io, service_path, .{}) catch unreachable;
|
||||||
|
defer service_dir.close(io);
|
||||||
|
|
||||||
|
const patched_service = try patchFile(allocator, io, "res/ly-sysvinit", patch_map);
|
||||||
|
try installText(io, patched_service, service_dir, service_path, "ly", .{ .permissions = .fromMode(0o755) });
|
||||||
|
},
|
||||||
|
.freebsd => {
|
||||||
|
const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
|
||||||
|
var executable_dir = std.Io.Dir.cwd().openDir(io, exe_path, .{}) catch unreachable;
|
||||||
|
defer executable_dir.close(io);
|
||||||
|
|
||||||
|
const patched_wrapper = try patchFile(allocator, io, "res/ly-freebsd-wrapper", patch_map);
|
||||||
|
try installText(io, patched_wrapper, executable_dir, exe_path, "ly_wrapper", .{ .permissions = .fromMode(0o755) });
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,31 +341,35 @@ fn install_service(allocator: std.mem.Allocator, patch_map: PatchMap) !void {
|
|||||||
pub fn Uninstaller(uninstall_config: bool) type {
|
pub fn Uninstaller(uninstall_config: bool) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
|
pub fn make(step: *std.Build.Step, _: std.Build.Step.MakeOptions) !void {
|
||||||
|
var threaded: std.Io.Threaded = .init_single_threaded;
|
||||||
|
const io = threaded.io();
|
||||||
const allocator = step.owner.allocator;
|
const allocator = step.owner.allocator;
|
||||||
|
|
||||||
if (uninstall_config) {
|
if (uninstall_config) {
|
||||||
try deleteTree(allocator, config_directory, "/ly", "ly config directory not found");
|
try deleteTree(allocator, io, config_directory, "/ly", "ly config directory not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ prefix_directory, "/bin/", executable_name });
|
const exe_path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ prefix_directory, "/bin/", executable_name });
|
||||||
var success = true;
|
var success = true;
|
||||||
std.fs.cwd().deleteFile(exe_path) catch {
|
std.Io.Dir.cwd().deleteFile(io, exe_path) catch {
|
||||||
std.debug.print("warn: ly executable not found\n", .{});
|
std.debug.print("warn: ly executable not found\n", .{});
|
||||||
success = false;
|
success = false;
|
||||||
};
|
};
|
||||||
if (success) std.debug.print("info: deleted {s}\n", .{exe_path});
|
if (success) std.debug.print("info: deleted {s}\n", .{exe_path});
|
||||||
|
|
||||||
try deleteFile(allocator, config_directory, "/pam.d/ly", "ly pam file not found");
|
try deleteFile(allocator, io, config_directory, "/pam.d/ly", "ly pam file not found");
|
||||||
|
|
||||||
switch (init_system) {
|
switch (init_system) {
|
||||||
.systemd => try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly.service", "systemd service not found"),
|
.systemd => try deleteFile(allocator, io, prefix_directory, "/lib/systemd/system/ly@.service", "systemd service not found"),
|
||||||
.openrc => try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found"),
|
.openrc => try deleteFile(allocator, io, config_directory, "/init.d/ly", "openrc service not found"),
|
||||||
.runit => try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found"),
|
.runit => try deleteTree(allocator, io, config_directory, "/sv/ly", "runit service not found"),
|
||||||
.s6 => {
|
.s6 => {
|
||||||
try deleteTree(allocator, config_directory, "/s6/sv/ly-srv", "s6 service not found");
|
try deleteTree(allocator, io, config_directory, "/s6/sv/ly-srv", "s6 service not found");
|
||||||
try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found");
|
try deleteFile(allocator, io, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found");
|
||||||
},
|
},
|
||||||
.dinit => try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found"),
|
.dinit => try deleteFile(allocator, io, config_directory, "/dinit.d/ly", "dinit service not found"),
|
||||||
|
.sysvinit => try deleteFile(allocator, io, config_directory, "/init.d/ly", "sysvinit service not found"),
|
||||||
|
.freebsd => try deleteFile(allocator, io, prefix_directory, "/bin/ly_wrapper", "freebsd wrapper not found"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -319,11 +387,11 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
|
|||||||
"--match",
|
"--match",
|
||||||
"*.*.*",
|
"*.*.*",
|
||||||
"--tags",
|
"--tags",
|
||||||
}, &status, .Ignore) catch {
|
}, &status, .ignore) catch {
|
||||||
return version_str;
|
return version_str;
|
||||||
};
|
};
|
||||||
var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r");
|
var git_describe = std.mem.trim(u8, git_describe_raw, " \n\r");
|
||||||
git_describe = std.mem.trimLeft(u8, git_describe, "v");
|
git_describe = std.mem.trimStart(u8, git_describe, "v");
|
||||||
|
|
||||||
switch (std.mem.count(u8, git_describe, "-")) {
|
switch (std.mem.count(u8, git_describe, "-")) {
|
||||||
0 => {
|
0 => {
|
||||||
@@ -336,13 +404,13 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
|
|||||||
2 => {
|
2 => {
|
||||||
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
|
// Untagged development build (e.g. 0.10.0-dev.2025+ecf0050a9).
|
||||||
var it = std.mem.splitScalar(u8, git_describe, '-');
|
var it = std.mem.splitScalar(u8, git_describe, '-');
|
||||||
const tagged_ancestor = std.mem.trimLeft(u8, it.first(), "v");
|
const tagged_ancestor = std.mem.trimStart(u8, it.first(), "v");
|
||||||
const commit_height = it.next().?;
|
const commit_height = it.next().?;
|
||||||
const commit_id = it.next().?;
|
const commit_id = it.next().?;
|
||||||
|
|
||||||
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
|
const ancestor_ver = try std.SemanticVersion.parse(tagged_ancestor);
|
||||||
if (version.order(ancestor_ver) != .gt) {
|
if (version.order(ancestor_ver) != .gt) {
|
||||||
std.debug.print("{s} version '{}' must be greater than tagged ancestor '{}'\n", .{ name, version, ancestor_ver });
|
std.debug.print("{s} version '{f}' must be greater than tagged ancestor '{f}'\n", .{ name, version, ancestor_ver });
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,22 +431,26 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn installFile(
|
fn installFile(
|
||||||
|
io: std.Io,
|
||||||
source_file: []const u8,
|
source_file: []const u8,
|
||||||
destination_directory: std.fs.Dir,
|
destination_directory: std.Io.Dir,
|
||||||
destination_directory_path: []const u8,
|
destination_directory_path: []const u8,
|
||||||
destination_file: []const u8,
|
destination_file: []const u8,
|
||||||
options: std.fs.Dir.CopyFileOptions,
|
options: std.Io.Dir.CopyFileOptions,
|
||||||
) !void {
|
) !void {
|
||||||
try std.fs.cwd().copyFile(source_file, destination_directory, destination_file, options);
|
try std.Io.Dir.cwd().copyFile(source_file, destination_directory, destination_file, io, options);
|
||||||
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
|
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: PatchMap) ![]const u8 {
|
fn patchFile(allocator: std.mem.Allocator, io: std.Io, source_file: []const u8, patch_map: PatchMap) ![]const u8 {
|
||||||
var file = try std.fs.cwd().openFile(source_file, .{});
|
var file = try std.Io.Dir.cwd().openFile(io, source_file, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
const reader = file.reader();
|
const stat = try file.stat(io);
|
||||||
var text = try reader.readAllAlloc(allocator, std.math.maxInt(u16));
|
|
||||||
|
var buffer: [4096]u8 = undefined;
|
||||||
|
var reader = file.reader(io, &buffer);
|
||||||
|
var text = try reader.interface.readAlloc(allocator, @intCast(stat.size));
|
||||||
|
|
||||||
var iterator = patch_map.iterator();
|
var iterator = patch_map.iterator();
|
||||||
while (iterator.next()) |kv| {
|
while (iterator.next()) |kv| {
|
||||||
@@ -391,30 +463,34 @@ fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: P
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn installText(
|
fn installText(
|
||||||
|
io: std.Io,
|
||||||
text: []const u8,
|
text: []const u8,
|
||||||
destination_directory: std.fs.Dir,
|
destination_directory: std.Io.Dir,
|
||||||
destination_directory_path: []const u8,
|
destination_directory_path: []const u8,
|
||||||
destination_file: []const u8,
|
destination_file: []const u8,
|
||||||
options: std.fs.File.CreateFlags,
|
options: std.Io.File.CreateFlags,
|
||||||
) !void {
|
) !void {
|
||||||
var file = try destination_directory.createFile(destination_file, options);
|
var file = try destination_directory.createFile(io, destination_file, options);
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
const writer = file.writer();
|
var buffer: [1024]u8 = undefined;
|
||||||
try writer.writeAll(text);
|
var writer = file.writer(io, &buffer);
|
||||||
|
try writer.interface.writeAll(text);
|
||||||
|
try writer.interface.flush();
|
||||||
|
|
||||||
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
|
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deleteFile(
|
fn deleteFile(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
io: std.Io,
|
||||||
prefix: []const u8,
|
prefix: []const u8,
|
||||||
file: []const u8,
|
file: []const u8,
|
||||||
warning: []const u8,
|
warning: []const u8,
|
||||||
) !void {
|
) !void {
|
||||||
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file });
|
const path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file });
|
||||||
|
|
||||||
std.fs.cwd().deleteFile(path) catch |err| {
|
std.Io.Dir.cwd().deleteFile(io, path) catch |err| {
|
||||||
if (err == error.FileNotFound) {
|
if (err == error.FileNotFound) {
|
||||||
std.debug.print("warn: {s}\n", .{warning});
|
std.debug.print("warn: {s}\n", .{warning});
|
||||||
return;
|
return;
|
||||||
@@ -428,13 +504,14 @@ fn deleteFile(
|
|||||||
|
|
||||||
fn deleteTree(
|
fn deleteTree(
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
|
io: std.Io,
|
||||||
prefix: []const u8,
|
prefix: []const u8,
|
||||||
directory: []const u8,
|
directory: []const u8,
|
||||||
warning: []const u8,
|
warning: []const u8,
|
||||||
) !void {
|
) !void {
|
||||||
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory });
|
const path = try std.Io.Dir.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory });
|
||||||
|
|
||||||
var dir = std.fs.cwd().openDir(path, .{}) catch |err| {
|
var dir = std.Io.Dir.cwd().openDir(io, path, .{}) catch |err| {
|
||||||
if (err == error.FileNotFound) {
|
if (err == error.FileNotFound) {
|
||||||
std.debug.print("warn: {s}\n", .{warning});
|
std.debug.print("warn: {s}\n", .{warning});
|
||||||
return;
|
return;
|
||||||
@@ -442,9 +519,9 @@ fn deleteTree(
|
|||||||
|
|
||||||
return err;
|
return err;
|
||||||
};
|
};
|
||||||
dir.close();
|
dir.close(io);
|
||||||
|
|
||||||
try std.fs.cwd().deleteTree(path);
|
try std.Io.Dir.cwd().deleteTree(io, path);
|
||||||
|
|
||||||
std.debug.print("info: deleted {s}\n", .{path});
|
std.debug.print("info: deleted {s}\n", .{path});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
.{
|
.{
|
||||||
.name = .ly,
|
.name = .ly,
|
||||||
.version = "1.1.1",
|
.version = "1.4.0",
|
||||||
.fingerprint = 0xa148ffcc5dc2cb59,
|
.fingerprint = 0xa148ffcc5dc2cb59,
|
||||||
.minimum_zig_version = "0.14.0",
|
.minimum_zig_version = "0.16.0",
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
|
.ly_ui = .{
|
||||||
|
.path = "ly-ui",
|
||||||
|
},
|
||||||
.clap = .{
|
.clap = .{
|
||||||
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz",
|
.url = "git+https://github.com/Hejsil/zig-clap#fc1e5cc3f6d9d3001112385ee6256d694e959d2f",
|
||||||
.hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0",
|
.hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1",
|
||||||
},
|
|
||||||
.zigini = .{
|
|
||||||
.url = "https://github.com/Kawaii-Ash/zigini/archive/2ed3d417f17fab5b0ee8cad8a63c6d62d7ac1042.tar.gz",
|
|
||||||
.hash = "zigini-0.3.1-BSkB7XJGAAB2E-sKyzhTaQCBlYBL8yqzE4E_jmSY99sC",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
.paths = .{""},
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
2
create_vendor_tarball.sh
Executable file
2
create_vendor_tarball.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
tar --zstd -cvf vendor.tar.zst zig-pkg ly-ui/zig-pkg ly-core/zig-pkg
|
||||||
4321
include/termbox2.h
4321
include/termbox2.h
File diff suppressed because it is too large
Load Diff
71
ly-core/build.zig
Normal file
71
ly-core/build.zig
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Translator = @import("translate_c").Translator;
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
const mod = b.addModule("ly-core", .{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
|
||||||
|
mod.addImport("zigini", zigini.module("zigini"));
|
||||||
|
|
||||||
|
const translate_c = b.dependency("translate_c", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "pam", "#include <security/pam_appl.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "utmp", "#include <utmpx.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "xcb", "#include <xcb/xcb.h>");
|
||||||
|
if (target.result.os.tag == .freebsd) {
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "pwd",
|
||||||
|
\\#include <pwd.h>
|
||||||
|
\\#include <sys/types.h>
|
||||||
|
\\#include <login_cap.h>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "pwd", "#include <pwd.h>");
|
||||||
|
}
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "stdlib", "#include <stdlib.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "unistd", "#include <unistd.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "grp", "#include <grp.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "system_time", "#include <sys/time.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "time", "#include <time.h>");
|
||||||
|
|
||||||
|
if (target.result.os.tag == .linux) {
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "kd", "#include <sys/kd.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "vt", "#include <sys/vt.h>");
|
||||||
|
} else if (target.result.os.tag == .freebsd) {
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "kbio", "#include <sys/kbio.h>");
|
||||||
|
addCImport(b, mod, translate_c, target, optimize, "consio", "#include <sys/consio.h>");
|
||||||
|
}
|
||||||
|
|
||||||
|
const mod_tests = b.addTest(.{
|
||||||
|
.root_module = mod,
|
||||||
|
});
|
||||||
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addCImport(
|
||||||
|
b: *std.Build,
|
||||||
|
mod: *std.Build.Module,
|
||||||
|
translate_c: *std.Build.Dependency,
|
||||||
|
target: std.Build.ResolvedTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
comptime name: []const u8,
|
||||||
|
comptime bytes: []const u8,
|
||||||
|
) void {
|
||||||
|
const pam: Translator = .init(translate_c, .{
|
||||||
|
.c_source_file = b.addWriteFiles().add(name ++ ".h", bytes),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
mod.addImport(name, pam.mod);
|
||||||
|
}
|
||||||
21
ly-core/build.zig.zon
Normal file
21
ly-core/build.zig.zon
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.{
|
||||||
|
.name = .ly_core,
|
||||||
|
.version = "1.0.0",
|
||||||
|
.fingerprint = 0xddda7afda795472,
|
||||||
|
.minimum_zig_version = "0.16.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.zigini = .{
|
||||||
|
.url = "git+https://github.com/AshAmetrine/zigini?ref=master#a665d081dda42664a96da2840ea09c5ccf9d0692",
|
||||||
|
.hash = "zigini-0.5.0-BSkB7e9WAACfyCBABNZiWL3gFMw18GKn3qBcPs8L1Ec1",
|
||||||
|
},
|
||||||
|
.translate_c = .{
|
||||||
|
.url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7",
|
||||||
|
.hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
||||||
71
ly-core/src/LogFile.zig
Normal file
71
ly-core/src/LogFile.zig
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const interop = @import("interop.zig");
|
||||||
|
|
||||||
|
const LogFile = @This();
|
||||||
|
|
||||||
|
path: []const u8,
|
||||||
|
could_open_log_file: bool = undefined,
|
||||||
|
file: std.Io.File = undefined,
|
||||||
|
buffer: []u8,
|
||||||
|
file_writer: std.Io.File.Writer = undefined,
|
||||||
|
|
||||||
|
pub fn init(io: std.Io, path: []const u8, buffer: []u8) !LogFile {
|
||||||
|
var log_file = LogFile{ .path = path, .buffer = buffer };
|
||||||
|
log_file.could_open_log_file = try openLogFile(io, path, &log_file);
|
||||||
|
return log_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reinit(self: *LogFile, io: std.Io) !void {
|
||||||
|
self.could_open_log_file = try openLogFile(io, self.path, self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *LogFile, io: std.Io) void {
|
||||||
|
self.file.close(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(self: *LogFile, io: std.Io, category: []const u8, comptime message: []const u8, args: anytype) !void {
|
||||||
|
var buffer: [128:0]u8 = undefined;
|
||||||
|
const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
try self.file_writer.interface.print("{s} [info/{s}] ", .{ time, category });
|
||||||
|
try self.file_writer.interface.print(message, args);
|
||||||
|
try self.file_writer.interface.writeByte('\n');
|
||||||
|
try self.file_writer.interface.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn err(self: *LogFile, io: std.Io, category: []const u8, comptime message: []const u8, args: anytype) !void {
|
||||||
|
var buffer: [128:0]u8 = undefined;
|
||||||
|
const time = interop.timeAsString(io, &buffer, "%Y-%m-%d %H:%M:%S");
|
||||||
|
|
||||||
|
try self.file_writer.interface.print("{s} [err/{s}] ", .{ time, category });
|
||||||
|
try self.file_writer.interface.print(message, args);
|
||||||
|
try self.file_writer.interface.writeByte('\n');
|
||||||
|
try self.file_writer.interface.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn openLogFile(io: std.Io, path: []const u8, log_file: *LogFile) !bool {
|
||||||
|
var could_open_log_file = true;
|
||||||
|
open_log_file: {
|
||||||
|
log_file.file = std.Io.Dir.cwd().openFile(io, path, .{ .mode = .write_only }) catch std.Io.Dir.cwd().createFile(io, path, .{ .permissions = .fromMode(0o666) }) catch {
|
||||||
|
// If we could neither open an existing log file nor create a new
|
||||||
|
// one, abort.
|
||||||
|
could_open_log_file = false;
|
||||||
|
break :open_log_file;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!could_open_log_file) {
|
||||||
|
log_file.file = try std.Io.Dir.openFileAbsolute(io, "/dev/null", .{ .mode = .write_only });
|
||||||
|
}
|
||||||
|
|
||||||
|
var log_file_writer = log_file.file.writer(io, log_file.buffer);
|
||||||
|
|
||||||
|
// Seek to the end of the log file
|
||||||
|
if (could_open_log_file) {
|
||||||
|
const stat = try log_file.file.stat(io);
|
||||||
|
try log_file_writer.seekTo(stat.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_file.file_writer = log_file_writer;
|
||||||
|
return could_open_log_file;
|
||||||
|
}
|
||||||
52
ly-core/src/SharedError.zig
Normal file
52
ly-core/src/SharedError.zig
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror));
|
||||||
|
const PaddingInt = std.meta.Int(.unsigned, 8 - (@bitSizeOf(ErrInt) + @bitSizeOf(bool)) % 8);
|
||||||
|
|
||||||
|
const ErrorHandler = packed struct {
|
||||||
|
has_error: bool = false,
|
||||||
|
err_int: ErrInt = 0,
|
||||||
|
padding: PaddingInt = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SharedError = @This();
|
||||||
|
|
||||||
|
data: []align(std.heap.page_size_min) u8,
|
||||||
|
write_error_event_fn: ?*const fn (anyerror, *anyopaque) anyerror!void,
|
||||||
|
ctx: ?*anyopaque,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
write_error_event_fn: ?*const fn (anyerror, *anyopaque) anyerror!void,
|
||||||
|
ctx: ?*anyopaque,
|
||||||
|
) !SharedError {
|
||||||
|
const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), .{ .READ = true, .WRITE = true }, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.data = data,
|
||||||
|
.write_error_event_fn = write_error_event_fn,
|
||||||
|
.ctx = ctx,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *SharedError) void {
|
||||||
|
std.posix.munmap(self.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeError(self: SharedError, err: anyerror) void {
|
||||||
|
var writer: std.Io.Writer = .fixed(self.data);
|
||||||
|
writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }, .native) catch {};
|
||||||
|
|
||||||
|
if (self.write_error_event_fn) |write_error_event_fn| {
|
||||||
|
@call(.auto, write_error_event_fn, .{ err, self.ctx.? }) catch {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn readError(self: SharedError) ?anyerror {
|
||||||
|
var reader: std.Io.Reader = .fixed(self.data);
|
||||||
|
const err_handler = try reader.takeStruct(ErrorHandler, .native);
|
||||||
|
|
||||||
|
if (err_handler.has_error)
|
||||||
|
return @errorFromInt(err_handler.err_int);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
6
ly-core/src/UidRange.zig
Normal file
6
ly-core/src/UidRange.zig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
// We set both values to 0 by default so that, in case they aren't present in
|
||||||
|
// the login.defs for some reason, then only the root username will be shown
|
||||||
|
uid_min: std.posix.uid_t = 0,
|
||||||
|
uid_max: std.posix.uid_t = 0,
|
||||||
384
ly-core/src/interop.zig
Normal file
384
ly-core/src/interop.zig
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const UidRange = @import("UidRange.zig");
|
||||||
|
const pwd = @import("pwd");
|
||||||
|
const stdlib = @import("stdlib");
|
||||||
|
const unistd = @import("unistd");
|
||||||
|
const grp = @import("grp");
|
||||||
|
const system_time = @import("system_time");
|
||||||
|
const time = @import("time");
|
||||||
|
|
||||||
|
pub const pam = @import("pam");
|
||||||
|
pub const utmp = @import("utmp");
|
||||||
|
// Exists for X11 support only
|
||||||
|
pub const xcb = @import("xcb");
|
||||||
|
|
||||||
|
pub const TimeOfDay = struct {
|
||||||
|
seconds: i64,
|
||||||
|
microseconds: i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Contains the platform-specific code
|
||||||
|
fn PlatformStruct() type {
|
||||||
|
return switch (builtin.os.tag) {
|
||||||
|
.linux => struct {
|
||||||
|
pub const kd = @import("kd");
|
||||||
|
pub const vt = @import("vt");
|
||||||
|
|
||||||
|
pub const LedState = c_char;
|
||||||
|
pub const get_led_state = kd.KDGKBLED;
|
||||||
|
pub const set_led_state = kd.KDSKBLED;
|
||||||
|
pub const numlock_led = kd.K_NUMLOCK;
|
||||||
|
pub const capslock_led = kd.K_CAPSLOCK;
|
||||||
|
pub const vt_activate = vt.VT_ACTIVATE;
|
||||||
|
pub const vt_waitactive = vt.VT_WAITACTIVE;
|
||||||
|
|
||||||
|
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
|
||||||
|
const status = grp.initgroups(username, @intCast(entry.gid));
|
||||||
|
if (status != 0) return error.GroupInitializationFailed;
|
||||||
|
|
||||||
|
if (isError(std.posix.system.setgid(@intCast(entry.gid)))) return error.SetUserGidFailed;
|
||||||
|
if (isError(std.posix.system.setuid(@intCast(entry.uid)))) return error.SetUserUidFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Procedure:
|
||||||
|
// 1. Open /proc/self/stat to retrieve the tty_nr field
|
||||||
|
// 2. Parse the tty_nr field to extract the major and minor device
|
||||||
|
// numbers
|
||||||
|
// 3. Then, read every /sys/class/tty/[dir]/dev, where [dir] is
|
||||||
|
// every sub-directory
|
||||||
|
// 4. Finally, compare the major and minor device numbers with the
|
||||||
|
// extracted values. If they correspond, parse [dir] to get the
|
||||||
|
// TTY ID
|
||||||
|
pub fn getActiveTtyImpl(allocator: std.mem.Allocator, io: std.Io, use_kmscon_vt: bool) !u8 {
|
||||||
|
var file_buffer: [256]u8 = undefined;
|
||||||
|
|
||||||
|
if (use_kmscon_vt) {
|
||||||
|
var file = try std.Io.Dir.openFileAbsolute(io, "/sys/class/tty/tty0/active", .{});
|
||||||
|
defer file.close(io);
|
||||||
|
|
||||||
|
var reader = file.reader(io, &file_buffer);
|
||||||
|
var buffer: [16]u8 = undefined;
|
||||||
|
const read = try readBuffer(&reader.interface, &buffer);
|
||||||
|
|
||||||
|
const tty = buffer[0..(read - 1)];
|
||||||
|
return std.fmt.parseInt(u8, tty["tty".len..], 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tty_major: u16 = undefined;
|
||||||
|
var tty_minor: u16 = undefined;
|
||||||
|
|
||||||
|
{
|
||||||
|
var file = try std.Io.Dir.openFileAbsolute(io, "/proc/self/stat", .{});
|
||||||
|
defer file.close(io);
|
||||||
|
|
||||||
|
var reader = file.reader(io, &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.Io.Dir.openDirAbsolute(io, "/sys/class/tty", .{ .iterate = true });
|
||||||
|
defer directory.close(io);
|
||||||
|
|
||||||
|
var iterator = directory.iterate();
|
||||||
|
while (try iterator.next(io)) |entry| {
|
||||||
|
const path = try std.fmt.allocPrint(allocator, "/sys/class/tty/{s}/dev", .{entry.name});
|
||||||
|
defer allocator.free(path);
|
||||||
|
|
||||||
|
var file = try std.Io.Dir.openFileAbsolute(io, path, .{});
|
||||||
|
defer file.close(io);
|
||||||
|
|
||||||
|
var reader = file.reader(io, &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, io: std.Io, file_path: []const u8) !UidRange {
|
||||||
|
const login_defs_file = try std.Io.Dir.cwd().openFile(io, file_path, .{});
|
||||||
|
defer login_defs_file.close(io);
|
||||||
|
|
||||||
|
var buffer: [4096]u8 = undefined;
|
||||||
|
var reader = login_defs_file.reader(io, &buffer);
|
||||||
|
|
||||||
|
const login_defs_buffer = try reader.interface.allocRemaining(allocator, .unlimited);
|
||||||
|
defer allocator.free(login_defs_buffer);
|
||||||
|
|
||||||
|
var iterator = std.mem.splitScalar(u8, login_defs_buffer, '\n');
|
||||||
|
var uid_range = UidRange{};
|
||||||
|
var nameFound = false;
|
||||||
|
|
||||||
|
while (iterator.next()) |line| {
|
||||||
|
const trimmed_line = std.mem.trim(u8, line, " \n\r\t");
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, trimmed_line, "UID_MIN")) {
|
||||||
|
uid_range.uid_min = try parseValue(std.posix.uid_t, "UID_MIN", trimmed_line);
|
||||||
|
nameFound = true;
|
||||||
|
} else if (std.mem.startsWith(u8, trimmed_line, "UID_MAX")) {
|
||||||
|
uid_range.uid_max = try parseValue(std.posix.uid_t, "UID_MAX", trimmed_line);
|
||||||
|
nameFound = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nameFound) return error.UidNameNotFound;
|
||||||
|
|
||||||
|
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 = @import("kbio");
|
||||||
|
pub const consio = @import("consio");
|
||||||
|
|
||||||
|
pub const LedState = c_int;
|
||||||
|
pub const get_led_state = kbio.KDGETLED;
|
||||||
|
pub const set_led_state = kbio.KDSETLED;
|
||||||
|
pub const numlock_led = kbio.LED_NUM;
|
||||||
|
pub const capslock_led = kbio.LED_CAP;
|
||||||
|
pub const vt_activate = consio.VT_ACTIVATE;
|
||||||
|
pub const vt_waitactive = consio.VT_WAITACTIVE;
|
||||||
|
|
||||||
|
const FREEBSD_UID_MIN = 1000;
|
||||||
|
const FREEBSD_UID_MAX = 32000;
|
||||||
|
|
||||||
|
pub fn setUserContextImpl(username: [*:0]const u8, entry: UsernameEntry) !void {
|
||||||
|
// FreeBSD has initgroups() in unistd
|
||||||
|
const status = unistd.initgroups(username, @intCast(entry.gid));
|
||||||
|
if (status != 0) return error.GroupInitializationFailed;
|
||||||
|
|
||||||
|
// FreeBSD sets the GID and UID with setusercontext()
|
||||||
|
const result = pwd.setusercontext(null, entry.passwd_struct, @intCast(entry.uid), pwd.LOGIN_SETALL);
|
||||||
|
if (result != 0) return error.SetUserUidFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getActiveTtyImpl(_: std.mem.Allocator, _: std.Io, _: bool) !u8 {
|
||||||
|
return error.FeatureUnimplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getUserIdRange(_: std.mem.Allocator, _: std.Io, _: []const u8) !UidRange {
|
||||||
|
return .{
|
||||||
|
// Hardcoded default values chosen from
|
||||||
|
// /usr/src/usr.sbin/pw/pw_conf.c
|
||||||
|
.uid_min = FREEBSD_UID_MIN,
|
||||||
|
.uid_max = FREEBSD_UID_MAX,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => @compileError("Unsupported target: " ++ builtin.os.tag),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const platform_struct = PlatformStruct();
|
||||||
|
|
||||||
|
// TODO 0.16.0: Can we get away with this?
|
||||||
|
pub fn isError(result: anytype) bool {
|
||||||
|
if (@typeInfo(@TypeOf(result)).int.signedness == .signed) {
|
||||||
|
return result < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@typeInfo(@TypeOf(result)).int.signedness == .unsigned) {
|
||||||
|
return switch (builtin.os.tag) {
|
||||||
|
.linux => std.os.linux.errno(result) != .SUCCESS,
|
||||||
|
else => @compileError("interop.isError() not implemented for current target!"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn supportsUnicode() bool {
|
||||||
|
return builtin.os.tag == .linux or builtin.os.tag == .freebsd;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn timeAsString(io: std.Io, buf: [:0]u8, format: [:0]const u8) []u8 {
|
||||||
|
const timer: isize = @intCast(std.Io.Timestamp.now(io, .real).toSeconds());
|
||||||
|
const tm_info = time.localtime(&timer);
|
||||||
|
const len = time.strftime(buf, buf.len, format, tm_info);
|
||||||
|
|
||||||
|
return buf[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTimeOfDay() !TimeOfDay {
|
||||||
|
var tv: system_time.timeval = undefined;
|
||||||
|
const status = system_time.gettimeofday(&tv, null);
|
||||||
|
|
||||||
|
if (status != 0) return error.FailedToGetTimeOfDay;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.seconds = @intCast(tv.tv_sec),
|
||||||
|
.microseconds = @intCast(tv.tv_usec),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getActiveTty(allocator: std.mem.Allocator, io: std.Io, use_kmscon_vt: bool) !u8 {
|
||||||
|
return platform_struct.getActiveTtyImpl(allocator, io, use_kmscon_vt);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn switchTty(tty: u8) !void {
|
||||||
|
var status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.vt_activate, tty);
|
||||||
|
if (status != 0) return error.FailedToActivateTty;
|
||||||
|
|
||||||
|
status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.vt_waitactive, tty);
|
||||||
|
if (status != 0) return error.FailedToWaitForActiveTty;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getLockState() !struct {
|
||||||
|
numlock: bool,
|
||||||
|
capslock: bool,
|
||||||
|
} {
|
||||||
|
var led: platform_struct.LedState = undefined;
|
||||||
|
const status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.get_led_state, &led);
|
||||||
|
if (status != 0) return error.FailedToGetLockState;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.numlock = (led & platform_struct.numlock_led) != 0,
|
||||||
|
.capslock = (led & platform_struct.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;
|
||||||
|
|
||||||
|
const numlock = (led & platform_struct.numlock_led) != 0;
|
||||||
|
if (numlock != val) {
|
||||||
|
status = std.c.ioctl(std.posix.STDIN_FILENO, platform_struct.set_led_state, led ^ platform_struct.numlock_led);
|
||||||
|
if (status != 0) return error.FailedToSetNumlock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setUserContext(allocator: std.mem.Allocator, entry: UsernameEntry) !void {
|
||||||
|
const username_z = try allocator.dupeZ(u8, entry.username.?);
|
||||||
|
defer allocator.free(username_z);
|
||||||
|
|
||||||
|
return platform_struct.setUserContextImpl(username_z.ptr, entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setUserShell(entry: *UsernameEntry) void {
|
||||||
|
unistd.setusershell();
|
||||||
|
|
||||||
|
const shell = unistd.getusershell();
|
||||||
|
entry.shell = std.mem.span(shell);
|
||||||
|
|
||||||
|
unistd.endusershell();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setEnvironmentVariable(allocator: std.mem.Allocator, name: []const u8, value: []const u8, replace: bool) !void {
|
||||||
|
const name_z = try allocator.dupeZ(u8, name);
|
||||||
|
defer allocator.free(name_z);
|
||||||
|
|
||||||
|
const value_z = try allocator.dupeZ(u8, value);
|
||||||
|
defer allocator.free(value_z);
|
||||||
|
|
||||||
|
const status = stdlib.setenv(name_z.ptr, value_z.ptr, @intFromBool(replace));
|
||||||
|
if (status != 0) return error.SetEnvironmentVariableFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn putEnvironmentVariable(name_and_value: [*c]u8) !void {
|
||||||
|
const status = stdlib.putenv(name_and_value);
|
||||||
|
if (status != 0) return error.PutEnvironmentVariableFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getNextUsernameEntry() ?UsernameEntry {
|
||||||
|
const entry = pwd.getpwent();
|
||||||
|
if (entry == null) return null;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.username = if (entry.*.pw_name) |name| std.mem.span(name) else null,
|
||||||
|
.uid = @intCast(entry.*.pw_uid),
|
||||||
|
.gid = @intCast(entry.*.pw_gid),
|
||||||
|
.home = if (entry.*.pw_dir) |dir| std.mem.span(dir) else null,
|
||||||
|
.shell = if (entry.*.pw_shell) |shell| std.mem.span(shell) else null,
|
||||||
|
.passwd_struct = entry,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getUsernameEntry(username: [:0]const u8) ?UsernameEntry {
|
||||||
|
const entry = pwd.getpwnam(username);
|
||||||
|
if (entry == null) return null;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.username = if (entry.*.pw_name) |name| std.mem.span(name) else null,
|
||||||
|
.uid = @intCast(entry.*.pw_uid),
|
||||||
|
.gid = @intCast(entry.*.pw_gid),
|
||||||
|
.home = if (entry.*.pw_dir) |dir| std.mem.span(dir) else null,
|
||||||
|
.shell = if (entry.*.pw_shell) |shell| std.mem.span(shell) else null,
|
||||||
|
.passwd_struct = entry,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn closePasswordDatabase() void {
|
||||||
|
pwd.endpwent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is very bad parsing, but we only need to get 2 values... and the format
|
||||||
|
// of the file doesn't seem to be standard? So this should be fine...
|
||||||
|
pub fn getUserIdRange(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8) !UidRange {
|
||||||
|
return platform_struct.getUserIdRange(allocator, io, file_path);
|
||||||
|
}
|
||||||
78
ly-core/src/root.zig
Normal file
78
ly-core/src/root.zig
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const ini = @import("zigini");
|
||||||
|
|
||||||
|
pub const interop = @import("interop.zig");
|
||||||
|
pub const UidRange = @import("UidRange.zig");
|
||||||
|
pub const LogFile = @import("LogFile.zig");
|
||||||
|
pub const SharedError = @import("SharedError.zig");
|
||||||
|
|
||||||
|
pub fn IniParser(comptime Struct: type) type {
|
||||||
|
return struct {
|
||||||
|
const Self = @This();
|
||||||
|
const temporary_allocator = std.heap.page_allocator;
|
||||||
|
|
||||||
|
pub const Error = struct {
|
||||||
|
type_name: []const u8,
|
||||||
|
key: []const u8,
|
||||||
|
value: []const u8,
|
||||||
|
error_name: []const u8,
|
||||||
|
};
|
||||||
|
pub var global_errors: std.ArrayList(Error) = .empty;
|
||||||
|
|
||||||
|
ini_struct: ini.Ini(Struct),
|
||||||
|
structure: Struct,
|
||||||
|
maybe_load_error: ?anyerror,
|
||||||
|
errors: std.ArrayList(Error),
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
path: []const u8,
|
||||||
|
field_handler: ?fn (allocator: std.mem.Allocator, field: ini.IniField) ?ini.IniField,
|
||||||
|
) !Self {
|
||||||
|
var ini_struct = ini.Ini(Struct).init(allocator);
|
||||||
|
errdefer ini_struct.deinit();
|
||||||
|
|
||||||
|
var maybe_load_error: ?anyerror = null;
|
||||||
|
|
||||||
|
const structure = ini_struct.readFileToStruct(io, path, .{
|
||||||
|
.fieldHandler = field_handler,
|
||||||
|
.errorHandler = errorHandler,
|
||||||
|
.comment_characters = "#",
|
||||||
|
}) catch |err| load_error: {
|
||||||
|
maybe_load_error = err;
|
||||||
|
break :load_error Struct{};
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.ini_struct = ini_struct,
|
||||||
|
.structure = structure,
|
||||||
|
.maybe_load_error = maybe_load_error,
|
||||||
|
.errors = global_errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.ini_struct.deinit();
|
||||||
|
|
||||||
|
for (0..global_errors.items.len) |i| {
|
||||||
|
const err = global_errors.items[i];
|
||||||
|
temporary_allocator.free(err.type_name);
|
||||||
|
temporary_allocator.free(err.key);
|
||||||
|
temporary_allocator.free(err.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
global_errors.deinit(temporary_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn errorHandler(type_name: []const u8, key: []const u8, value: []const u8, err: anyerror) void {
|
||||||
|
global_errors.append(temporary_allocator, .{
|
||||||
|
.type_name = temporary_allocator.dupe(u8, type_name) catch return,
|
||||||
|
.key = temporary_allocator.dupe(u8, key) catch return,
|
||||||
|
.value = temporary_allocator.dupe(u8, value) catch return,
|
||||||
|
.error_name = @errorName(err),
|
||||||
|
}) catch return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
53
ly-ui/build.zig
Normal file
53
ly-ui/build.zig
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Translator = @import("translate_c").Translator;
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
const mod = b.addModule("ly-ui", .{
|
||||||
|
.root_source_file = b.path("src/root.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ly_core = b.dependency("ly_core", .{ .target = target, .optimize = optimize });
|
||||||
|
mod.addImport("ly-core", ly_core.module("ly-core"));
|
||||||
|
|
||||||
|
const termbox_dep = b.dependency("termbox2", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const translate_c_dep = b.dependency("translate_c", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
const termbox2: Translator = .init(translate_c_dep, .{
|
||||||
|
.c_source_file = termbox_dep.path("termbox2.h"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
termbox2.defineCMacro("TB_IMPL", null);
|
||||||
|
// TODO 0.16.0: Workaround until Aro gets better...
|
||||||
|
// https://codeberg.org/ziglang/translate-c/issues/319
|
||||||
|
termbox2.defineCMacro("_XOPEN_SOURCE", "700");
|
||||||
|
termbox2.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit)
|
||||||
|
// TODO 0.16.0: Including <fcntl.h> with -OReleaseSafe causes
|
||||||
|
// __attribute__(__error__()) to be called. Below
|
||||||
|
// is the workaround.
|
||||||
|
termbox2.defineCMacro("_FORTIFY_SOURCE", "0");
|
||||||
|
// TODO 0.16.0: Needed for now
|
||||||
|
if (target.result.os.tag == .freebsd) {
|
||||||
|
termbox2.defineCMacro("__BSD_VISIBLE", "1");
|
||||||
|
}
|
||||||
|
mod.addImport("termbox2", termbox2.mod);
|
||||||
|
|
||||||
|
const mod_tests = b.addTest(.{
|
||||||
|
.root_module = mod,
|
||||||
|
});
|
||||||
|
const run_mod_tests = b.addRunArtifact(mod_tests);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run tests");
|
||||||
|
test_step.dependOn(&run_mod_tests.step);
|
||||||
|
}
|
||||||
24
ly-ui/build.zig.zon
Normal file
24
ly-ui/build.zig.zon
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
.{
|
||||||
|
.name = .ly_ui,
|
||||||
|
.version = "1.0.0",
|
||||||
|
.fingerprint = 0x8d11bf85a74ec803,
|
||||||
|
.minimum_zig_version = "0.16.0",
|
||||||
|
.dependencies = .{
|
||||||
|
.ly_core = .{
|
||||||
|
.path = "../ly-core",
|
||||||
|
},
|
||||||
|
.termbox2 = .{
|
||||||
|
.url = "git+https://github.com/AnErrupTion/termbox2?ref=master#c7f241e8888ce243e1748b05c26a42fcfaaad936",
|
||||||
|
.hash = "N-V-__8AAAUXBQD6Fwpi9m0MBqWXFFaqW5l1lVrJC2Ynj7a-",
|
||||||
|
},
|
||||||
|
.translate_c = .{
|
||||||
|
.url = "git+https://codeberg.org/ziglang/translate-c#7a1a9fdc4ab00835748a6657ecbb835e3d5d45f7",
|
||||||
|
.hash = "translate_c-0.0.0-Q_BUWvP1BgCjAk6PWv5286tOlvzD9-X-NkuTzh0KxY0Q",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.paths = .{
|
||||||
|
"build.zig",
|
||||||
|
"build.zig.zon",
|
||||||
|
"src",
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
const interop = @import("../interop.zig");
|
const TerminalBuffer = @import("TerminalBuffer.zig");
|
||||||
|
|
||||||
const termbox = interop.termbox;
|
|
||||||
|
|
||||||
const Cell = @This();
|
const Cell = @This();
|
||||||
|
|
||||||
@@ -19,5 +17,5 @@ pub fn init(ch: u32, fg: u32, bg: u32) Cell {
|
|||||||
pub fn put(self: Cell, x: usize, y: usize) void {
|
pub fn put(self: Cell, x: usize, y: usize) void {
|
||||||
if (self.ch == 0) return;
|
if (self.ch == 0) return;
|
||||||
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), self.ch, self.fg, self.bg);
|
TerminalBuffer.setCell(x, y, self);
|
||||||
}
|
}
|
||||||
221
ly-ui/src/Position.zig
Normal file
221
ly-ui/src/Position.zig
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
const Position = @This();
|
||||||
|
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
|
||||||
|
pub fn init(x: usize, y: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = x,
|
||||||
|
.y = y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + other.x,
|
||||||
|
.y = self.y + other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + if (condition) other.x else 0,
|
||||||
|
.y = self.y + if (condition) other.y else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addX(self: Position, x: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addY(self: Position, y: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y + y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addXIf(self: Position, x: usize, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + if (condition) x else 0,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addYIf(self: Position, y: usize, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y + if (condition) y else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addXFrom(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + other.x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addYFrom(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y + other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addXFromIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x + if (condition) other.x else 0,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addYFromIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y + if (condition) other.y else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x - other.x,
|
||||||
|
.y = self.y - other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x - if (condition) other.x else 0,
|
||||||
|
.y = self.y - if (condition) other.y else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeX(self: Position, x: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x - x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeY(self: Position, y: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y - y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeXIf(self: Position, x: usize, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x - if (condition) x else 0,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeYIf(self: Position, y: usize, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y - if (condition) y else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeXFrom(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x - other.x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeYFrom(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y - other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeXFromIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x - if (condition) other.x else 0,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn removeYFromIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = self.y - if (condition) other.y else 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = other.x - self.x,
|
||||||
|
.y = other.y - self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invertIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = if (condition) other.x - self.x else self.x,
|
||||||
|
.y = if (condition) other.y - self.y else self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invertX(self: Position, width: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = width - self.x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invertY(self: Position, height: usize) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = height - self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invertXIf(self: Position, width: usize, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = if (condition) width - self.x else self.x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invertYIf(self: Position, height: usize, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = if (condition) height - self.y else self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resetXFrom(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = other.x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resetYFrom(self: Position, other: Position) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = other.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resetXFromIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = if (condition) other.x else self.x,
|
||||||
|
.y = self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resetYFromIf(self: Position, other: Position, condition: bool) Position {
|
||||||
|
return .{
|
||||||
|
.x = self.x,
|
||||||
|
.y = if (condition) other.y else self.y,
|
||||||
|
};
|
||||||
|
}
|
||||||
632
ly-ui/src/TerminalBuffer.zig
Normal file
632
ly-ui/src/TerminalBuffer.zig
Normal file
@@ -0,0 +1,632 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Random = std.Random;
|
||||||
|
|
||||||
|
const ly_core = @import("ly-core");
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const LogFile = ly_core.LogFile;
|
||||||
|
const SharedError = ly_core.SharedError;
|
||||||
|
pub const termbox = @import("termbox2");
|
||||||
|
|
||||||
|
const Cell = @import("Cell.zig");
|
||||||
|
const keyboard = @import("keyboard.zig");
|
||||||
|
const Position = @import("Position.zig");
|
||||||
|
const Widget = @import("Widget.zig");
|
||||||
|
|
||||||
|
const TerminalBuffer = @This();
|
||||||
|
|
||||||
|
pub const KeybindCallbackFn = *const fn (*anyopaque) anyerror!bool;
|
||||||
|
pub const KeybindMap = std.AutoHashMap(keyboard.Key, struct {
|
||||||
|
callback: KeybindCallbackFn,
|
||||||
|
context: *anyopaque,
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const InitOptions = struct {
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
border_fg: u32,
|
||||||
|
full_color: bool,
|
||||||
|
is_tty: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Styling = struct {
|
||||||
|
pub const BOLD = termbox.TB_BOLD;
|
||||||
|
pub const UNDERLINE = termbox.TB_UNDERLINE;
|
||||||
|
pub const REVERSE = termbox.TB_REVERSE;
|
||||||
|
pub const ITALIC = termbox.TB_ITALIC;
|
||||||
|
pub const BLINK = termbox.TB_BLINK;
|
||||||
|
pub const HI_BLACK = termbox.TB_HI_BLACK;
|
||||||
|
pub const BRIGHT = termbox.TB_BRIGHT;
|
||||||
|
pub const DIM = termbox.TB_DIM;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Color = struct {
|
||||||
|
pub const DEFAULT = 0x00000000;
|
||||||
|
pub const TRUE_BLACK = Styling.HI_BLACK;
|
||||||
|
pub const TRUE_RED = 0x00FF0000;
|
||||||
|
pub const TRUE_GREEN = 0x0000FF00;
|
||||||
|
pub const TRUE_YELLOW = 0x00FFFF00;
|
||||||
|
pub const TRUE_BLUE = 0x000000FF;
|
||||||
|
pub const TRUE_MAGENTA = 0x00FF00FF;
|
||||||
|
pub const TRUE_CYAN = 0x0000FFFF;
|
||||||
|
pub const TRUE_WHITE = 0x00FFFFFF;
|
||||||
|
pub const TRUE_DIM_RED = 0x00800000;
|
||||||
|
pub const TRUE_DIM_GREEN = 0x00008000;
|
||||||
|
pub const TRUE_DIM_YELLOW = 0x00808000;
|
||||||
|
pub const TRUE_DIM_BLUE = 0x00000080;
|
||||||
|
pub const TRUE_DIM_MAGENTA = 0x00800080;
|
||||||
|
pub const TRUE_DIM_CYAN = 0x00008080;
|
||||||
|
pub const TRUE_DIM_WHITE = 0x00C0C0C0;
|
||||||
|
pub const ECOL_BLACK = 1;
|
||||||
|
pub const ECOL_RED = 2;
|
||||||
|
pub const ECOL_GREEN = 3;
|
||||||
|
pub const ECOL_YELLOW = 4;
|
||||||
|
pub const ECOL_BLUE = 5;
|
||||||
|
pub const ECOL_MAGENTA = 6;
|
||||||
|
pub const ECOL_CYAN = 7;
|
||||||
|
pub const ECOL_WHITE = 8;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const START_POSITION = Position.init(0, 0);
|
||||||
|
|
||||||
|
log_file: *LogFile,
|
||||||
|
random: Random,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
border_fg: u32,
|
||||||
|
box_chars: struct {
|
||||||
|
left_up: u32,
|
||||||
|
left_down: u32,
|
||||||
|
right_up: u32,
|
||||||
|
right_down: u32,
|
||||||
|
top: u32,
|
||||||
|
bottom: u32,
|
||||||
|
left: u32,
|
||||||
|
right: u32,
|
||||||
|
},
|
||||||
|
blank_cell: Cell,
|
||||||
|
full_color: bool,
|
||||||
|
termios: ?std.posix.termios,
|
||||||
|
keybinds: KeybindMap,
|
||||||
|
handlable_widgets: std.ArrayList(*Widget),
|
||||||
|
run: bool,
|
||||||
|
update: bool,
|
||||||
|
active_widget_index: usize,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
options: InitOptions,
|
||||||
|
log_file: *LogFile,
|
||||||
|
random: Random,
|
||||||
|
) !TerminalBuffer {
|
||||||
|
// Initialize termbox
|
||||||
|
_ = termbox.tb_init();
|
||||||
|
|
||||||
|
if (options.full_color) {
|
||||||
|
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
|
||||||
|
try log_file.info(io, "tui", "termbox2 set to 24-bit color output mode", .{});
|
||||||
|
} else {
|
||||||
|
try log_file.info(io, "tui", "termbox2 set to eight-color output mode", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = termbox.tb_clear();
|
||||||
|
|
||||||
|
// Let's take some precautions here and clear the back buffer as well
|
||||||
|
try clearBackBuffer();
|
||||||
|
|
||||||
|
const width: usize = @intCast(termbox.tb_width());
|
||||||
|
const height: usize = @intCast(termbox.tb_height());
|
||||||
|
|
||||||
|
try log_file.info(io, "tui", "screen resolution is {d}x{d}", .{ width, height });
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.log_file = log_file,
|
||||||
|
.random = random,
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.fg = options.fg,
|
||||||
|
.bg = options.bg,
|
||||||
|
.border_fg = options.border_fg,
|
||||||
|
.box_chars = if (interop.supportsUnicode()) .{
|
||||||
|
.left_up = 0x250C,
|
||||||
|
.left_down = 0x2514,
|
||||||
|
.right_up = 0x2510,
|
||||||
|
.right_down = 0x2518,
|
||||||
|
.top = 0x2500,
|
||||||
|
.bottom = 0x2500,
|
||||||
|
.left = 0x2502,
|
||||||
|
.right = 0x2502,
|
||||||
|
} else .{
|
||||||
|
.left_up = '+',
|
||||||
|
.left_down = '+',
|
||||||
|
.right_up = '+',
|
||||||
|
.right_down = '+',
|
||||||
|
.top = '-',
|
||||||
|
.bottom = '-',
|
||||||
|
.left = '|',
|
||||||
|
.right = '|',
|
||||||
|
},
|
||||||
|
.blank_cell = Cell.init(' ', options.fg, options.bg),
|
||||||
|
.full_color = options.full_color,
|
||||||
|
// Needed to reclaim the TTY after giving up its control
|
||||||
|
.termios = try std.posix.tcgetattr(std.posix.STDIN_FILENO),
|
||||||
|
.keybinds = KeybindMap.init(allocator),
|
||||||
|
.handlable_widgets = .empty,
|
||||||
|
.run = true,
|
||||||
|
.update = true,
|
||||||
|
.active_widget_index = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *TerminalBuffer) void {
|
||||||
|
self.keybinds.deinit();
|
||||||
|
TerminalBuffer.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runEventLoop(
|
||||||
|
self: *TerminalBuffer,
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
shared_error: SharedError,
|
||||||
|
layers: [][]*Widget,
|
||||||
|
active_widget: *Widget,
|
||||||
|
inactivity_delay: u16,
|
||||||
|
position_widgets_fn: *const fn (*anyopaque) anyerror!void,
|
||||||
|
inactivity_event_fn: ?*const fn (*anyopaque) anyerror!void,
|
||||||
|
context: *anyopaque,
|
||||||
|
) !void {
|
||||||
|
try self.registerGlobalKeybind(io, "Ctrl+K", &moveCursorUp, self);
|
||||||
|
try self.registerGlobalKeybind(io, "Up", &moveCursorUp, self);
|
||||||
|
|
||||||
|
try self.registerGlobalKeybind(io, "Ctrl+J", &moveCursorDown, self);
|
||||||
|
try self.registerGlobalKeybind(io, "Down", &moveCursorDown, self);
|
||||||
|
|
||||||
|
try self.registerGlobalKeybind(io, "Tab", &wrapCursor, self);
|
||||||
|
try self.registerGlobalKeybind(io, "Shift+Tab", &wrapCursorReverse, self);
|
||||||
|
|
||||||
|
defer self.handlable_widgets.deinit(allocator);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
for (layers) |layer| {
|
||||||
|
for (layer) |widget| {
|
||||||
|
try widget.update(context);
|
||||||
|
|
||||||
|
if (widget.vtable.handle_fn != null) {
|
||||||
|
try self.handlable_widgets.append(allocator, widget);
|
||||||
|
|
||||||
|
if (widget.id == active_widget.id) self.active_widget_index = i;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try @call(.auto, position_widgets_fn, .{context});
|
||||||
|
|
||||||
|
var event: termbox.tb_event = undefined;
|
||||||
|
var inactivity_cmd_ran = false;
|
||||||
|
var inactivity_time_start = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
while (self.run) {
|
||||||
|
var maybe_timeout: ?usize = null;
|
||||||
|
|
||||||
|
if (self.update) {
|
||||||
|
try TerminalBuffer.clearScreen(false);
|
||||||
|
|
||||||
|
// Reset cursor
|
||||||
|
const current_widget = self.getActiveWidget();
|
||||||
|
current_widget.handle(null) catch |err| {
|
||||||
|
shared_error.writeError(error.SetCursorFailed);
|
||||||
|
try self.log_file.err(
|
||||||
|
io,
|
||||||
|
"tui",
|
||||||
|
"failed to set cursor in active widget '{s}': {s}",
|
||||||
|
.{ current_widget.display_name, @errorName(err) },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (layers) |layer| {
|
||||||
|
for (layer) |widget| {
|
||||||
|
try widget.update(context);
|
||||||
|
widget.draw();
|
||||||
|
|
||||||
|
if (try widget.calculateTimeout(context)) |widget_timeout| {
|
||||||
|
if (maybe_timeout == null or widget_timeout < maybe_timeout.?) maybe_timeout = widget_timeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalBuffer.presentBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inactivity_event_fn) |inactivity_fn| {
|
||||||
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (!inactivity_cmd_ran and time.seconds - inactivity_time_start.seconds > inactivity_delay) {
|
||||||
|
try @call(.auto, inactivity_fn, .{context});
|
||||||
|
inactivity_cmd_ran = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const event_error = if (maybe_timeout) |timeout| termbox.tb_peek_event(&event, @intCast(timeout)) else termbox.tb_poll_event(&event);
|
||||||
|
|
||||||
|
self.update = maybe_timeout != null or event_error >= 0;
|
||||||
|
|
||||||
|
if (event_error < 0) continue;
|
||||||
|
|
||||||
|
// Input of some kind was detected, so reset the inactivity timer
|
||||||
|
inactivity_time_start = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (event.type == termbox.TB_EVENT_RESIZE) {
|
||||||
|
self.width = TerminalBuffer.getWidth();
|
||||||
|
self.height = TerminalBuffer.getHeight();
|
||||||
|
|
||||||
|
try self.log_file.info(
|
||||||
|
io,
|
||||||
|
"tui",
|
||||||
|
"screen resolution updated to {d}x{d}",
|
||||||
|
.{ self.width, self.height },
|
||||||
|
);
|
||||||
|
|
||||||
|
for (layers) |layer| {
|
||||||
|
for (layer) |widget| {
|
||||||
|
widget.realloc() catch |err| {
|
||||||
|
shared_error.writeError(error.WidgetReallocationFailed);
|
||||||
|
try self.log_file.err(
|
||||||
|
io,
|
||||||
|
"tui",
|
||||||
|
"failed to reallocate widget '{s}': {s}",
|
||||||
|
.{ widget.display_name, @errorName(err) },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try @call(.auto, position_widgets_fn, .{context});
|
||||||
|
|
||||||
|
self.update = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var maybe_keys = try self.handleKeybind(allocator, event);
|
||||||
|
if (maybe_keys) |*keys| {
|
||||||
|
defer keys.deinit(allocator);
|
||||||
|
|
||||||
|
const current_widget = self.getActiveWidget();
|
||||||
|
for (keys.items) |key| {
|
||||||
|
current_widget.handle(key) catch |err| {
|
||||||
|
shared_error.writeError(error.CurrentWidgetHandlingFailed);
|
||||||
|
try self.log_file.err(
|
||||||
|
io,
|
||||||
|
"tui",
|
||||||
|
"failed to handle active widget '{s}': {s}",
|
||||||
|
.{ current_widget.display_name, @errorName(err) },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stopEventLoop(self: *TerminalBuffer) void {
|
||||||
|
self.run = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawNextFrame(self: *TerminalBuffer, value: bool) void {
|
||||||
|
self.update = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getActiveWidget(self: *TerminalBuffer) *Widget {
|
||||||
|
return self.handlable_widgets.items[self.active_widget_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setActiveWidget(self: *TerminalBuffer, widget: *Widget) void {
|
||||||
|
for (self.handlable_widgets.items, 0..) |widg, i| {
|
||||||
|
if (widg.id == widget.id) self.active_widget_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getWidth() usize {
|
||||||
|
return @intCast(termbox.tb_width());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeight() usize {
|
||||||
|
return @intCast(termbox.tb_height());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCursor(x: usize, y: usize) void {
|
||||||
|
_ = termbox.tb_set_cursor(@intCast(x), @intCast(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearScreen(clear_back_buffer: bool) !void {
|
||||||
|
_ = termbox.tb_clear();
|
||||||
|
if (clear_back_buffer) try clearBackBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn shutdown() void {
|
||||||
|
_ = termbox.tb_shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn presentBuffer() void {
|
||||||
|
_ = termbox.tb_present();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCell(x: usize, y: usize) ?Cell {
|
||||||
|
var maybe_cell: ?*termbox.tb_cell = undefined;
|
||||||
|
_ = termbox.tb_get_cell(
|
||||||
|
@intCast(x),
|
||||||
|
@intCast(y),
|
||||||
|
1,
|
||||||
|
&maybe_cell,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (maybe_cell) |cell| {
|
||||||
|
return Cell.init(cell.ch, cell.fg, cell.bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setCell(x: usize, y: usize, cell: Cell) void {
|
||||||
|
_ = termbox.tb_set_cell(
|
||||||
|
@intCast(x),
|
||||||
|
@intCast(y),
|
||||||
|
cell.ch,
|
||||||
|
cell.fg,
|
||||||
|
cell.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reclaim(self: TerminalBuffer) !void {
|
||||||
|
if (self.termios) |termios| {
|
||||||
|
// Take back control of the TTY
|
||||||
|
_ = termbox.tb_init();
|
||||||
|
|
||||||
|
if (self.full_color) {
|
||||||
|
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, termios);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerKeybind(
|
||||||
|
self: *TerminalBuffer,
|
||||||
|
io: std.Io,
|
||||||
|
keybinds: *KeybindMap,
|
||||||
|
keybind: []const u8,
|
||||||
|
callback: KeybindCallbackFn,
|
||||||
|
context: *anyopaque,
|
||||||
|
) !void {
|
||||||
|
const key = try self.parseKeybind(io, keybind);
|
||||||
|
|
||||||
|
keybinds.put(key, .{
|
||||||
|
.callback = callback,
|
||||||
|
.context = context,
|
||||||
|
}) catch |err| {
|
||||||
|
try self.log_file.err(
|
||||||
|
io,
|
||||||
|
"tui",
|
||||||
|
"failed to register keybind {s}: {s}",
|
||||||
|
.{ keybind, @errorName(err) },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn registerGlobalKeybind(
|
||||||
|
self: *TerminalBuffer,
|
||||||
|
io: std.Io,
|
||||||
|
keybind: []const u8,
|
||||||
|
callback: KeybindCallbackFn,
|
||||||
|
context: *anyopaque,
|
||||||
|
) !void {
|
||||||
|
try self.registerKeybind(io, &self.keybinds, keybind, callback, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simulateKeybind(self: *TerminalBuffer, io: std.Io, keybind: []const u8) !bool {
|
||||||
|
const key = try self.parseKeybind(io, keybind);
|
||||||
|
|
||||||
|
if (self.keybinds.get(key)) |binding| {
|
||||||
|
return try @call(
|
||||||
|
.auto,
|
||||||
|
binding.callback,
|
||||||
|
.{binding.context},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const current_widget = self.getActiveWidget();
|
||||||
|
if (current_widget.keybinds) |keybinds| {
|
||||||
|
if (keybinds.get(key)) |binding| {
|
||||||
|
return try @call(
|
||||||
|
.auto,
|
||||||
|
binding.callback,
|
||||||
|
.{binding.context},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawText(
|
||||||
|
text: []const u8,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) void {
|
||||||
|
const yc: c_int = @intCast(y);
|
||||||
|
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
||||||
|
var utf8 = utf8view.iterator();
|
||||||
|
|
||||||
|
var i: c_int = @intCast(x);
|
||||||
|
while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) {
|
||||||
|
_ = termbox.tb_set_cell(i, yc, codepoint, fg, bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawConfinedText(
|
||||||
|
text: []const u8,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
max_length: usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) void {
|
||||||
|
const yc: c_int = @intCast(y);
|
||||||
|
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
||||||
|
var utf8 = utf8view.iterator();
|
||||||
|
|
||||||
|
var i: c_int = @intCast(x);
|
||||||
|
while (utf8.nextCodepoint()) |codepoint| : (i += termbox.tb_wcwidth(codepoint)) {
|
||||||
|
if (i - @as(c_int, @intCast(x)) >= max_length) break;
|
||||||
|
_ = termbox.tb_set_cell(i, yc, codepoint, fg, bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawCharMultiple(
|
||||||
|
char: u32,
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
length: usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) void {
|
||||||
|
const cell = Cell.init(char, fg, bg);
|
||||||
|
for (0..length) |xx| cell.put(x + xx, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every codepoint is assumed to have a width of 1.
|
||||||
|
// Since Ly is normally running in a TTY, this should be fine.
|
||||||
|
pub fn strWidth(str: []const u8) usize {
|
||||||
|
const utf8view = std.unicode.Utf8View.init(str) catch return str.len;
|
||||||
|
var utf8 = utf8view.iterator();
|
||||||
|
var length: c_int = 0;
|
||||||
|
|
||||||
|
while (utf8.nextCodepoint()) |codepoint| {
|
||||||
|
length += termbox.tb_wcwidth(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
return @intCast(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clearBackBuffer() !void {
|
||||||
|
// Clear the TTY because termbox2 doesn't seem to do it properly
|
||||||
|
const capability = termbox.global.caps[termbox.TB_CAP_CLEAR_SCREEN];
|
||||||
|
const capability_slice = std.mem.span(capability);
|
||||||
|
const result = std.posix.system.write(termbox.global.ttyfd, capability_slice.ptr, capability_slice.len);
|
||||||
|
|
||||||
|
if (result != capability_slice.len) return error.PartialClearBackBuffer;
|
||||||
|
if (result < 0) return error.ClearBackBufferFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseKeybind(self: *TerminalBuffer, io: std.Io, keybind: []const u8) !keyboard.Key {
|
||||||
|
var key = std.mem.zeroes(keyboard.Key);
|
||||||
|
var iterator = std.mem.splitScalar(u8, keybind, '+');
|
||||||
|
|
||||||
|
while (iterator.next()) |item| {
|
||||||
|
var found = false;
|
||||||
|
|
||||||
|
inline for (std.meta.fields(keyboard.Key)) |field| {
|
||||||
|
if (std.ascii.eqlIgnoreCase(field.name, item)) {
|
||||||
|
@field(key, field.name) = true;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
try self.log_file.err(
|
||||||
|
io,
|
||||||
|
"tui",
|
||||||
|
"failed to parse key {s} of keybind {s}",
|
||||||
|
.{ item, keybind },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handleKeybind(
|
||||||
|
self: *TerminalBuffer,
|
||||||
|
allocator: Allocator,
|
||||||
|
tb_event: termbox.tb_event,
|
||||||
|
) !?std.ArrayList(keyboard.Key) {
|
||||||
|
var keys = try keyboard.getKeyList(allocator, tb_event);
|
||||||
|
|
||||||
|
for (keys.items) |key| {
|
||||||
|
if (self.keybinds.get(key)) |binding| {
|
||||||
|
const passthrough_event = try @call(
|
||||||
|
.auto,
|
||||||
|
binding.callback,
|
||||||
|
.{binding.context},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!passthrough_event) {
|
||||||
|
keys.deinit(allocator);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
const current_widget = self.getActiveWidget();
|
||||||
|
if (current_widget.keybinds) |keybinds| {
|
||||||
|
if (keybinds.get(key)) |binding| {
|
||||||
|
const passthrough_event = try @call(
|
||||||
|
.auto,
|
||||||
|
binding.callback,
|
||||||
|
.{binding.context},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!passthrough_event) {
|
||||||
|
keys.deinit(allocator);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moveCursorUp(ptr: *anyopaque) !bool {
|
||||||
|
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
|
||||||
|
if (state.active_widget_index == 0) return false;
|
||||||
|
|
||||||
|
state.active_widget_index -= 1;
|
||||||
|
state.update = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn moveCursorDown(ptr: *anyopaque) !bool {
|
||||||
|
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
|
||||||
|
if (state.active_widget_index == state.handlable_widgets.items.len - 1) return false;
|
||||||
|
|
||||||
|
state.active_widget_index += 1;
|
||||||
|
state.update = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrapCursor(ptr: *anyopaque) !bool {
|
||||||
|
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
state.active_widget_index = (state.active_widget_index + 1) % state.handlable_widgets.items.len;
|
||||||
|
state.update = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wrapCursorReverse(ptr: *anyopaque) !bool {
|
||||||
|
var state: *TerminalBuffer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
state.active_widget_index = if (state.active_widget_index == 0) state.handlable_widgets.items.len - 1 else state.active_widget_index - 1;
|
||||||
|
state.update = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
186
ly-ui/src/Widget.zig
Normal file
186
ly-ui/src/Widget.zig
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
const Widget = @This();
|
||||||
|
|
||||||
|
const keyboard = @import("keyboard.zig");
|
||||||
|
const TerminalBuffer = @import("TerminalBuffer.zig");
|
||||||
|
|
||||||
|
const VTable = struct {
|
||||||
|
deinit_fn: ?*const fn (ptr: *anyopaque) void,
|
||||||
|
realloc_fn: ?*const fn (ptr: *anyopaque) anyerror!void,
|
||||||
|
draw_fn: *const fn (ptr: *anyopaque) void,
|
||||||
|
update_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!void,
|
||||||
|
handle_fn: ?*const fn (ptr: *anyopaque, maybe_key: ?keyboard.Key) anyerror!void,
|
||||||
|
calculate_timeout_fn: ?*const fn (ptr: *anyopaque, ctx: *anyopaque) anyerror!?usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var idCounter: u64 = 0;
|
||||||
|
|
||||||
|
id: u64,
|
||||||
|
display_name: []const u8,
|
||||||
|
keybinds: ?TerminalBuffer.KeybindMap,
|
||||||
|
pointer: *anyopaque,
|
||||||
|
vtable: VTable,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
display_name: []const u8,
|
||||||
|
keybinds: ?TerminalBuffer.KeybindMap,
|
||||||
|
pointer: anytype,
|
||||||
|
comptime deinit_fn: ?fn (ptr: @TypeOf(pointer)) void,
|
||||||
|
comptime realloc_fn: ?fn (ptr: @TypeOf(pointer)) anyerror!void,
|
||||||
|
comptime draw_fn: fn (ptr: @TypeOf(pointer)) void,
|
||||||
|
comptime update_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!void,
|
||||||
|
comptime handle_fn: ?fn (ptr: @TypeOf(pointer), maybe_key: ?keyboard.Key) anyerror!void,
|
||||||
|
comptime calculate_timeout_fn: ?fn (ptr: @TypeOf(pointer), ctx: *anyopaque) anyerror!?usize,
|
||||||
|
) Widget {
|
||||||
|
const Pointer = @TypeOf(pointer);
|
||||||
|
const Impl = struct {
|
||||||
|
pub fn deinitImpl(ptr: *anyopaque) void {
|
||||||
|
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
return @call(
|
||||||
|
.always_inline,
|
||||||
|
deinit_fn.?,
|
||||||
|
.{impl},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reallocImpl(ptr: *anyopaque) !void {
|
||||||
|
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
return @call(
|
||||||
|
.always_inline,
|
||||||
|
realloc_fn.?,
|
||||||
|
.{impl},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawImpl(ptr: *anyopaque) void {
|
||||||
|
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
return @call(
|
||||||
|
.always_inline,
|
||||||
|
draw_fn,
|
||||||
|
.{impl},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateImpl(ptr: *anyopaque, ctx: *anyopaque) !void {
|
||||||
|
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
return @call(
|
||||||
|
.always_inline,
|
||||||
|
update_fn.?,
|
||||||
|
.{ impl, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handleImpl(ptr: *anyopaque, maybe_key: ?keyboard.Key) !void {
|
||||||
|
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
return @call(
|
||||||
|
.always_inline,
|
||||||
|
handle_fn.?,
|
||||||
|
.{ impl, maybe_key },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculateTimeoutImpl(ptr: *anyopaque, ctx: *anyopaque) !?usize {
|
||||||
|
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
return @call(
|
||||||
|
.always_inline,
|
||||||
|
calculate_timeout_fn.?,
|
||||||
|
.{ impl, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const vtable = VTable{
|
||||||
|
.deinit_fn = if (deinit_fn != null) deinitImpl else null,
|
||||||
|
.realloc_fn = if (realloc_fn != null) reallocImpl else null,
|
||||||
|
.draw_fn = drawImpl,
|
||||||
|
.update_fn = if (update_fn != null) updateImpl else null,
|
||||||
|
.handle_fn = if (handle_fn != null) handleImpl else null,
|
||||||
|
.calculate_timeout_fn = if (calculate_timeout_fn != null) calculateTimeoutImpl else null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
idCounter += 1;
|
||||||
|
return .{
|
||||||
|
.id = idCounter,
|
||||||
|
.display_name = display_name,
|
||||||
|
.keybinds = keybinds,
|
||||||
|
.pointer = pointer,
|
||||||
|
.vtable = Impl.vtable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Widget) void {
|
||||||
|
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
||||||
|
|
||||||
|
if (self.vtable.deinit_fn) |deinit_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
deinit_fn,
|
||||||
|
.{impl},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn realloc(self: *Widget) !void {
|
||||||
|
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
||||||
|
|
||||||
|
if (self.vtable.realloc_fn) |realloc_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
realloc_fn,
|
||||||
|
.{impl},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *Widget) void {
|
||||||
|
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
||||||
|
|
||||||
|
@call(
|
||||||
|
.auto,
|
||||||
|
self.vtable.draw_fn,
|
||||||
|
.{impl},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(self: *Widget, ctx: *anyopaque) !void {
|
||||||
|
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
||||||
|
|
||||||
|
if (self.vtable.update_fn) |update_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
update_fn,
|
||||||
|
.{ impl, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(self: *Widget, maybe_key: ?keyboard.Key) !void {
|
||||||
|
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
||||||
|
|
||||||
|
if (self.vtable.handle_fn) |handle_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
handle_fn,
|
||||||
|
.{ impl, maybe_key },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculateTimeout(self: *Widget, ctx: *anyopaque) !?usize {
|
||||||
|
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
||||||
|
|
||||||
|
if (self.vtable.calculate_timeout_fn) |calculate_timeout_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
calculate_timeout_fn,
|
||||||
|
.{ impl, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
237
ly-ui/src/components/BigLabel.zig
Normal file
237
ly-ui/src/components/BigLabel.zig
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
const BigLabel = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const ly_core = @import("ly-core");
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
|
||||||
|
const en = @import("bigLabelLocales/en.zig");
|
||||||
|
const fa = @import("bigLabelLocales/fa.zig");
|
||||||
|
const Cell = @import("../Cell.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Widget = @import("../Widget.zig");
|
||||||
|
|
||||||
|
pub const CHAR_WIDTH = 5;
|
||||||
|
pub const CHAR_HEIGHT = 5;
|
||||||
|
pub const CHAR_SIZE = CHAR_WIDTH * CHAR_HEIGHT;
|
||||||
|
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
|
||||||
|
pub const O: u32 = 0;
|
||||||
|
|
||||||
|
// zig fmt: off
|
||||||
|
pub const LocaleChars = struct {
|
||||||
|
ZERO: [CHAR_SIZE]u21,
|
||||||
|
ONE: [CHAR_SIZE]u21,
|
||||||
|
TWO: [CHAR_SIZE]u21,
|
||||||
|
THREE: [CHAR_SIZE]u21,
|
||||||
|
FOUR: [CHAR_SIZE]u21,
|
||||||
|
FIVE: [CHAR_SIZE]u21,
|
||||||
|
SIX: [CHAR_SIZE]u21,
|
||||||
|
SEVEN: [CHAR_SIZE]u21,
|
||||||
|
EIGHT: [CHAR_SIZE]u21,
|
||||||
|
NINE: [CHAR_SIZE]u21,
|
||||||
|
S: [CHAR_SIZE]u21,
|
||||||
|
E: [CHAR_SIZE]u21,
|
||||||
|
P: [CHAR_SIZE]u21,
|
||||||
|
A: [CHAR_SIZE]u21,
|
||||||
|
M: [CHAR_SIZE]u21,
|
||||||
|
};
|
||||||
|
// zig fmt: on
|
||||||
|
|
||||||
|
pub const BigLabelLocale = enum {
|
||||||
|
en,
|
||||||
|
fa,
|
||||||
|
};
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
allocator: ?Allocator = null,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
text: []const u8,
|
||||||
|
max_width: ?usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
locale: BigLabelLocale,
|
||||||
|
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
|
||||||
|
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
|
||||||
|
component_pos: Position,
|
||||||
|
children_pos: Position,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
text: []const u8,
|
||||||
|
max_width: ?usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
locale: BigLabelLocale,
|
||||||
|
update_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!void,
|
||||||
|
calculate_timeout_fn: ?*const fn (*BigLabel, *anyopaque) anyerror!?usize,
|
||||||
|
) BigLabel {
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.allocator = null,
|
||||||
|
.buffer = buffer,
|
||||||
|
.text = text,
|
||||||
|
.max_width = max_width,
|
||||||
|
.fg = fg,
|
||||||
|
.bg = bg,
|
||||||
|
.locale = locale,
|
||||||
|
.update_fn = update_fn,
|
||||||
|
.calculate_timeout_fn = calculate_timeout_fn,
|
||||||
|
.component_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *BigLabel) void {
|
||||||
|
if (self.allocator) |allocator| allocator.free(self.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *BigLabel) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"BigLabel",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTextAlloc(
|
||||||
|
self: *BigLabel,
|
||||||
|
allocator: Allocator,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) !void {
|
||||||
|
self.text = try std.fmt.allocPrint(allocator, fmt, args);
|
||||||
|
self.allocator = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTextBuf(
|
||||||
|
self: *BigLabel,
|
||||||
|
buffer: []u8,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) !void {
|
||||||
|
self.text = try std.fmt.bufPrint(buffer, fmt, args);
|
||||||
|
self.allocator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setText(self: *BigLabel, text: []const u8) void {
|
||||||
|
self.text = text;
|
||||||
|
self.allocator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionX(self: *BigLabel, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text) * CHAR_WIDTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionY(self: *BigLabel, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = original_pos.addY(CHAR_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionXY(self: *BigLabel, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = Position.init(
|
||||||
|
TerminalBuffer.strWidth(self.text) * CHAR_WIDTH,
|
||||||
|
CHAR_HEIGHT,
|
||||||
|
).add(original_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn childrenPosition(self: BigLabel) Position {
|
||||||
|
return self.children_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *BigLabel) void {
|
||||||
|
for (self.text, 0..) |c, i| {
|
||||||
|
const clock_cell = clockCell(
|
||||||
|
c,
|
||||||
|
self.fg,
|
||||||
|
self.bg,
|
||||||
|
self.locale,
|
||||||
|
);
|
||||||
|
|
||||||
|
alphaBlit(
|
||||||
|
self.component_pos.x + i * (CHAR_WIDTH + 1),
|
||||||
|
self.component_pos.y,
|
||||||
|
self.buffer.width,
|
||||||
|
self.buffer.height,
|
||||||
|
clock_cell,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(self: *BigLabel, context: *anyopaque) !void {
|
||||||
|
if (self.update_fn) |update_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
update_fn,
|
||||||
|
.{ self, context },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *BigLabel, ctx: *anyopaque) !?usize {
|
||||||
|
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
calculate_timeout_fn,
|
||||||
|
.{ self, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clockCell(char: u8, fg: u32, bg: u32, locale: BigLabelLocale) [CHAR_SIZE]Cell {
|
||||||
|
var cells: [CHAR_SIZE]Cell = undefined;
|
||||||
|
|
||||||
|
//@divTrunc(time.microseconds, 500000) != 0)
|
||||||
|
const clock_chars = toBigNumber(char, locale);
|
||||||
|
for (0..cells.len) |i| cells[i] = Cell.init(clock_chars[i], fg, bg);
|
||||||
|
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [CHAR_SIZE]Cell) void {
|
||||||
|
if (x + CHAR_WIDTH >= tb_width or y + CHAR_HEIGHT >= tb_height) return;
|
||||||
|
|
||||||
|
for (0..CHAR_HEIGHT) |yy| {
|
||||||
|
for (0..CHAR_WIDTH) |xx| {
|
||||||
|
const cell = cells[yy * CHAR_WIDTH + xx];
|
||||||
|
cell.put(x + xx, y + yy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn toBigNumber(char: u8, locale: BigLabelLocale) [CHAR_SIZE]u21 {
|
||||||
|
const locale_chars = switch (locale) {
|
||||||
|
.fa => fa.locale_chars,
|
||||||
|
.en => en.locale_chars,
|
||||||
|
};
|
||||||
|
return switch (char) {
|
||||||
|
'0' => locale_chars.ZERO,
|
||||||
|
'1' => locale_chars.ONE,
|
||||||
|
'2' => locale_chars.TWO,
|
||||||
|
'3' => locale_chars.THREE,
|
||||||
|
'4' => locale_chars.FOUR,
|
||||||
|
'5' => locale_chars.FIVE,
|
||||||
|
'6' => locale_chars.SIX,
|
||||||
|
'7' => locale_chars.SEVEN,
|
||||||
|
'8' => locale_chars.EIGHT,
|
||||||
|
'9' => locale_chars.NINE,
|
||||||
|
'p', 'P' => locale_chars.P,
|
||||||
|
'a', 'A' => locale_chars.A,
|
||||||
|
'm', 'M' => locale_chars.M,
|
||||||
|
':' => locale_chars.S,
|
||||||
|
else => locale_chars.E,
|
||||||
|
};
|
||||||
|
}
|
||||||
190
ly-ui/src/components/Box.zig
Normal file
190
ly-ui/src/components/Box.zig
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Cell = @import("../Cell.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Widget = @import("../Widget.zig");
|
||||||
|
|
||||||
|
const Box = @This();
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
horizontal_margin: usize,
|
||||||
|
vertical_margin: usize,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
show_borders: bool,
|
||||||
|
blank_box: bool,
|
||||||
|
top_title: ?[]const u8,
|
||||||
|
bottom_title: ?[]const u8,
|
||||||
|
border_fg: u32,
|
||||||
|
title_fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
update_fn: ?*const fn (*Box, *anyopaque) anyerror!void,
|
||||||
|
left_pos: Position,
|
||||||
|
right_pos: Position,
|
||||||
|
children_pos: Position,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
horizontal_margin: usize,
|
||||||
|
vertical_margin: usize,
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
show_borders: bool,
|
||||||
|
blank_box: bool,
|
||||||
|
top_title: ?[]const u8,
|
||||||
|
bottom_title: ?[]const u8,
|
||||||
|
border_fg: u32,
|
||||||
|
title_fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
update_fn: ?*const fn (*Box, *anyopaque) anyerror!void,
|
||||||
|
) Box {
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.buffer = buffer,
|
||||||
|
.horizontal_margin = horizontal_margin,
|
||||||
|
.vertical_margin = vertical_margin,
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.show_borders = show_borders,
|
||||||
|
.blank_box = blank_box,
|
||||||
|
.top_title = top_title,
|
||||||
|
.bottom_title = bottom_title,
|
||||||
|
.border_fg = border_fg,
|
||||||
|
.title_fg = title_fg,
|
||||||
|
.bg = bg,
|
||||||
|
.update_fn = update_fn,
|
||||||
|
.left_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.right_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *Box) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Box",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionXY(self: *Box, original_pos: Position) void {
|
||||||
|
if (self.buffer.width < 2 or self.buffer.height < 2) return;
|
||||||
|
|
||||||
|
self.left_pos = original_pos;
|
||||||
|
self.right_pos = Position.init(
|
||||||
|
@min(self.buffer.width, self.width),
|
||||||
|
@min(self.buffer.height, self.height),
|
||||||
|
).add(self.left_pos);
|
||||||
|
|
||||||
|
self.children_pos = Position.init(
|
||||||
|
self.horizontal_margin,
|
||||||
|
self.vertical_margin,
|
||||||
|
).add(self.left_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn childrenPosition(self: Box) Position {
|
||||||
|
return self.children_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *Box) void {
|
||||||
|
if (self.show_borders) {
|
||||||
|
var left_up = Cell.init(
|
||||||
|
self.buffer.box_chars.left_up,
|
||||||
|
self.border_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
var right_up = Cell.init(
|
||||||
|
self.buffer.box_chars.right_up,
|
||||||
|
self.border_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
var left_down = Cell.init(
|
||||||
|
self.buffer.box_chars.left_down,
|
||||||
|
self.border_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
var right_down = Cell.init(
|
||||||
|
self.buffer.box_chars.right_down,
|
||||||
|
self.border_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
var top = Cell.init(
|
||||||
|
self.buffer.box_chars.top,
|
||||||
|
self.border_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
var bottom = Cell.init(
|
||||||
|
self.buffer.box_chars.bottom,
|
||||||
|
self.border_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
|
||||||
|
left_up.put(self.left_pos.x - 1, self.left_pos.y - 1);
|
||||||
|
right_up.put(self.right_pos.x, self.left_pos.y - 1);
|
||||||
|
left_down.put(self.left_pos.x - 1, self.right_pos.y);
|
||||||
|
right_down.put(self.right_pos.x, self.right_pos.y);
|
||||||
|
|
||||||
|
for (0..self.width) |i| {
|
||||||
|
top.put(self.left_pos.x + i, self.left_pos.y - 1);
|
||||||
|
bottom.put(self.left_pos.x + i, self.right_pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
top.ch = self.buffer.box_chars.left;
|
||||||
|
bottom.ch = self.buffer.box_chars.right;
|
||||||
|
|
||||||
|
for (0..self.height) |i| {
|
||||||
|
top.put(self.left_pos.x - 1, self.left_pos.y + i);
|
||||||
|
bottom.put(self.right_pos.x, self.left_pos.y + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.blank_box) {
|
||||||
|
for (0..self.height) |y| {
|
||||||
|
for (0..self.width) |x| {
|
||||||
|
self.buffer.blank_cell.put(self.left_pos.x + x, self.left_pos.y + y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.top_title) |title| {
|
||||||
|
TerminalBuffer.drawConfinedText(
|
||||||
|
title,
|
||||||
|
self.left_pos.x,
|
||||||
|
self.left_pos.y - 1,
|
||||||
|
self.width,
|
||||||
|
self.title_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.bottom_title) |title| {
|
||||||
|
TerminalBuffer.drawConfinedText(
|
||||||
|
title,
|
||||||
|
self.left_pos.x,
|
||||||
|
self.left_pos.y + self.height,
|
||||||
|
self.width,
|
||||||
|
self.title_fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(self: *Box, ctx: *anyopaque) !void {
|
||||||
|
if (self.update_fn) |update_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
update_fn,
|
||||||
|
.{ self, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
153
ly-ui/src/components/Label.zig
Normal file
153
ly-ui/src/components/Label.zig
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
const Label = @This();
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const Cell = @import("../Cell.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Widget = @import("../Widget.zig");
|
||||||
|
|
||||||
|
instance: ?Widget,
|
||||||
|
allocator: ?Allocator,
|
||||||
|
text: []const u8,
|
||||||
|
max_width: ?usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
update_fn: ?*const fn (*Label, *anyopaque) anyerror!void,
|
||||||
|
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
|
||||||
|
component_pos: Position,
|
||||||
|
children_pos: Position,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
text: []const u8,
|
||||||
|
max_width: ?usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
update_fn: ?*const fn (*Label, *anyopaque) anyerror!void,
|
||||||
|
calculate_timeout_fn: ?*const fn (*Label, *anyopaque) anyerror!?usize,
|
||||||
|
) Label {
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.allocator = null,
|
||||||
|
.text = text,
|
||||||
|
.max_width = max_width,
|
||||||
|
.fg = fg,
|
||||||
|
.bg = bg,
|
||||||
|
.update_fn = update_fn,
|
||||||
|
.calculate_timeout_fn = calculate_timeout_fn,
|
||||||
|
.component_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Label) void {
|
||||||
|
if (self.allocator) |allocator| allocator.free(self.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *Label) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Label",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTextAlloc(
|
||||||
|
self: *Label,
|
||||||
|
allocator: Allocator,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) !void {
|
||||||
|
self.text = try std.fmt.allocPrint(allocator, fmt, args);
|
||||||
|
self.allocator = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setTextBuf(
|
||||||
|
self: *Label,
|
||||||
|
buffer: []u8,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
args: anytype,
|
||||||
|
) !void {
|
||||||
|
self.text = try std.fmt.bufPrint(buffer, fmt, args);
|
||||||
|
self.allocator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setText(self: *Label, text: []const u8) void {
|
||||||
|
self.text = text;
|
||||||
|
self.allocator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionX(self: *Label, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = original_pos.addX(TerminalBuffer.strWidth(self.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionY(self: *Label, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = original_pos.addY(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionXY(self: *Label, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = Position.init(
|
||||||
|
TerminalBuffer.strWidth(self.text),
|
||||||
|
1,
|
||||||
|
).add(original_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn childrenPosition(self: Label) Position {
|
||||||
|
return self.children_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *Label) void {
|
||||||
|
if (self.max_width) |width| {
|
||||||
|
TerminalBuffer.drawConfinedText(
|
||||||
|
self.text,
|
||||||
|
self.component_pos.x,
|
||||||
|
self.component_pos.y,
|
||||||
|
width,
|
||||||
|
self.fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalBuffer.drawText(
|
||||||
|
self.text,
|
||||||
|
self.component_pos.x,
|
||||||
|
self.component_pos.y,
|
||||||
|
self.fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(self: *Label, ctx: *anyopaque) !void {
|
||||||
|
if (self.update_fn) |update_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
update_fn,
|
||||||
|
.{ self, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *Label, ctx: *anyopaque) !?usize {
|
||||||
|
if (self.calculate_timeout_fn) |calculate_timeout_fn| {
|
||||||
|
return @call(
|
||||||
|
.auto,
|
||||||
|
calculate_timeout_fn,
|
||||||
|
.{ self, ctx },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
246
ly-ui/src/components/Text.zig
Normal file
246
ly-ui/src/components/Text.zig
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const keyboard = @import("../keyboard.zig");
|
||||||
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
const Widget = @import("../Widget.zig");
|
||||||
|
|
||||||
|
const DynamicString = std.ArrayListUnmanaged(u8);
|
||||||
|
|
||||||
|
const Text = @This();
|
||||||
|
|
||||||
|
instance: ?Widget,
|
||||||
|
allocator: Allocator,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
text: DynamicString,
|
||||||
|
end: usize,
|
||||||
|
cursor: usize,
|
||||||
|
visible_start: usize,
|
||||||
|
width: usize,
|
||||||
|
component_pos: Position,
|
||||||
|
children_pos: Position,
|
||||||
|
should_insert: bool,
|
||||||
|
masked: bool,
|
||||||
|
maybe_mask: ?u32,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
keybinds: TerminalBuffer.KeybindMap,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
should_insert: bool,
|
||||||
|
masked: bool,
|
||||||
|
maybe_mask: ?u32,
|
||||||
|
width: usize,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) !*Text {
|
||||||
|
var self = try allocator.create(Text);
|
||||||
|
self.* = Text{
|
||||||
|
.instance = null,
|
||||||
|
.allocator = allocator,
|
||||||
|
.buffer = buffer,
|
||||||
|
.text = .empty,
|
||||||
|
.end = 0,
|
||||||
|
.cursor = 0,
|
||||||
|
.visible_start = 0,
|
||||||
|
.width = width,
|
||||||
|
.component_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.should_insert = should_insert,
|
||||||
|
.masked = masked,
|
||||||
|
.maybe_mask = maybe_mask,
|
||||||
|
.fg = fg,
|
||||||
|
.bg = bg,
|
||||||
|
.keybinds = .init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Delete", &delete, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Backspace", &backspace, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Ctrl+U", &clearTextEntry, self);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Text) void {
|
||||||
|
self.text.deinit(self.allocator);
|
||||||
|
self.keybinds.deinit();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *Text) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Text",
|
||||||
|
self.keybinds,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
null,
|
||||||
|
handle,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionX(self: *Text, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = original_pos.addX(self.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionY(self: *Text, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = original_pos.addY(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionXY(self: *Text, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.children_pos = Position.init(
|
||||||
|
self.width,
|
||||||
|
1,
|
||||||
|
).add(original_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn childrenPosition(self: Text) Position {
|
||||||
|
return self.children_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *Text) void {
|
||||||
|
self.text.clearRetainingCapacity();
|
||||||
|
self.end = 0;
|
||||||
|
self.cursor = 0;
|
||||||
|
self.visible_start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggleMask(self: *Text) void {
|
||||||
|
self.masked = !self.masked;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(self: *Text, maybe_key: ?keyboard.Key) !void {
|
||||||
|
if (maybe_key) |key| {
|
||||||
|
if (self.should_insert) {
|
||||||
|
const maybe_character = key.getEnabledPrintableAscii();
|
||||||
|
if (maybe_character) |character| try self.write(character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.masked and self.maybe_mask == null) {
|
||||||
|
TerminalBuffer.setCursor(
|
||||||
|
self.component_pos.x,
|
||||||
|
self.component_pos.y,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalBuffer.setCursor(
|
||||||
|
self.component_pos.x + (self.cursor - self.visible_start),
|
||||||
|
self.component_pos.y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *Text) void {
|
||||||
|
if (self.masked) {
|
||||||
|
if (self.maybe_mask) |mask| {
|
||||||
|
if (self.width < 1) return;
|
||||||
|
|
||||||
|
const length = @min(TerminalBuffer.strWidth(self.text.items), self.width - 1);
|
||||||
|
if (length == 0) return;
|
||||||
|
|
||||||
|
TerminalBuffer.drawCharMultiple(
|
||||||
|
mask,
|
||||||
|
self.component_pos.x,
|
||||||
|
self.component_pos.y,
|
||||||
|
length,
|
||||||
|
self.fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const str_length = TerminalBuffer.strWidth(self.text.items);
|
||||||
|
const length = @min(str_length, self.width);
|
||||||
|
if (length == 0) return;
|
||||||
|
|
||||||
|
const visible_slice = vs: {
|
||||||
|
if (str_length > self.width and self.cursor < str_length) {
|
||||||
|
break :vs self.text.items[self.visible_start..(self.width + self.visible_start)];
|
||||||
|
} else {
|
||||||
|
break :vs self.text.items[self.visible_start..];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TerminalBuffer.drawText(
|
||||||
|
visible_slice,
|
||||||
|
self.component_pos.x,
|
||||||
|
self.component_pos.y,
|
||||||
|
self.fg,
|
||||||
|
self.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn goLeft(ptr: *anyopaque) !bool {
|
||||||
|
var self: *Text = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
if (self.cursor == 0) return false;
|
||||||
|
if (self.visible_start > 0) self.visible_start -= 1;
|
||||||
|
|
||||||
|
self.cursor -= 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn goRight(ptr: *anyopaque) !bool {
|
||||||
|
var self: *Text = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
if (self.cursor >= self.end) return false;
|
||||||
|
if (self.cursor - self.visible_start == self.width - 1) self.visible_start += 1;
|
||||||
|
|
||||||
|
self.cursor += 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn delete(ptr: *anyopaque) !bool {
|
||||||
|
var self: *Text = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
if (self.cursor >= self.end or !self.should_insert) return false;
|
||||||
|
|
||||||
|
_ = self.text.orderedRemove(self.cursor);
|
||||||
|
|
||||||
|
self.end -= 1;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn backspace(ptr: *anyopaque) !bool {
|
||||||
|
const self: *Text = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
if (self.cursor == 0 or !self.should_insert) return false;
|
||||||
|
|
||||||
|
_ = try goLeft(ptr);
|
||||||
|
_ = try delete(ptr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: *Text, char: u8) !void {
|
||||||
|
if (char == 0) return;
|
||||||
|
|
||||||
|
try self.text.insert(self.allocator, self.cursor, char);
|
||||||
|
|
||||||
|
self.end += 1;
|
||||||
|
_ = try goRight(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clearTextEntry(ptr: *anyopaque) !bool {
|
||||||
|
var self: *Text = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
if (!self.should_insert) return false;
|
||||||
|
|
||||||
|
self.clear();
|
||||||
|
self.buffer.drawNextFrame(true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
const Lang = @import("Lang.zig");
|
const BigLabel = @import("../BigLabel.zig");
|
||||||
|
const LocaleChars = BigLabel.LocaleChars;
|
||||||
const LocaleChars = Lang.LocaleChars;
|
const X = BigLabel.X;
|
||||||
const X = Lang.X;
|
const O = BigLabel.O;
|
||||||
const O = Lang.O;
|
|
||||||
|
|
||||||
// zig fmt: off
|
// zig fmt: off
|
||||||
pub const locale_chars = LocaleChars{
|
pub const locale_chars = LocaleChars{
|
||||||
@@ -90,5 +89,26 @@ pub const locale_chars = LocaleChars{
|
|||||||
O,O,O,O,O,
|
O,O,O,O,O,
|
||||||
O,O,O,O,O,
|
O,O,O,O,O,
|
||||||
},
|
},
|
||||||
|
.P = [_]u21{
|
||||||
|
X,X,X,X,X,
|
||||||
|
X,X,O,X,X,
|
||||||
|
X,X,X,X,X,
|
||||||
|
X,X,O,O,O,
|
||||||
|
X,X,O,O,O,
|
||||||
|
},
|
||||||
|
.A = [_]u21{
|
||||||
|
X,X,X,X,X,
|
||||||
|
X,X,O,X,X,
|
||||||
|
X,X,X,X,X,
|
||||||
|
X,X,O,X,X,
|
||||||
|
X,X,O,X,X,
|
||||||
|
},
|
||||||
|
.M = [_]u21{
|
||||||
|
X,X,X,X,X,
|
||||||
|
X,O,X,O,X,
|
||||||
|
X,O,X,O,X,
|
||||||
|
X,O,O,O,X,
|
||||||
|
X,O,O,O,X,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// zig fmt: on
|
// zig fmt: on
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
const Lang = @import("Lang.zig");
|
const BigLabel = @import("../BigLabel.zig");
|
||||||
|
const LocaleChars = BigLabel.LocaleChars;
|
||||||
const LocaleChars = Lang.LocaleChars;
|
const X = BigLabel.X;
|
||||||
const X = Lang.X;
|
const O = BigLabel.O;
|
||||||
const O = Lang.O;
|
|
||||||
|
|
||||||
// zig fmt: off
|
// zig fmt: off
|
||||||
pub const locale_chars = LocaleChars{
|
pub const locale_chars = LocaleChars{
|
||||||
@@ -76,6 +75,27 @@ pub const locale_chars = LocaleChars{
|
|||||||
O,O,O,X,O,
|
O,O,O,X,O,
|
||||||
O,O,O,X,O,
|
O,O,O,X,O,
|
||||||
},
|
},
|
||||||
|
.P = [_]u21{
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
},
|
||||||
|
.A = [_]u21{
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
},
|
||||||
|
.M = [_]u21{
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
O,O,O,O,O,
|
||||||
|
},
|
||||||
.S = [_]u21{
|
.S = [_]u21{
|
||||||
O,O,O,O,O,
|
O,O,O,O,O,
|
||||||
O,O,X,O,O,
|
O,O,X,O,O,
|
||||||
172
ly-ui/src/components/generic.zig
Normal file
172
ly-ui/src/components/generic.zig
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Cell = @import("../Cell.zig");
|
||||||
|
const keyboard = @import("../keyboard.zig");
|
||||||
|
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||||
|
const Position = @import("../Position.zig");
|
||||||
|
|
||||||
|
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
|
||||||
|
return struct {
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const ItemList = std.ArrayListUnmanaged(ItemType);
|
||||||
|
const DrawItemFn = *const fn (*Self, ItemType, usize, usize, usize) void;
|
||||||
|
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
allocator: Allocator,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
list: ItemList,
|
||||||
|
current: usize,
|
||||||
|
width: usize,
|
||||||
|
component_pos: Position,
|
||||||
|
children_pos: Position,
|
||||||
|
text_in_center: bool,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
cursor: usize,
|
||||||
|
draw_item_fn: DrawItemFn,
|
||||||
|
change_item_fn: ?ChangeItemFn,
|
||||||
|
change_item_arg: ?ChangeItemType,
|
||||||
|
keybinds: TerminalBuffer.KeybindMap,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
draw_item_fn: DrawItemFn,
|
||||||
|
change_item_fn: ?ChangeItemFn,
|
||||||
|
change_item_arg: ?ChangeItemType,
|
||||||
|
width: usize,
|
||||||
|
text_in_center: bool,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) !*Self {
|
||||||
|
var self = try allocator.create(Self);
|
||||||
|
self.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.buffer = buffer,
|
||||||
|
.list = .empty,
|
||||||
|
.current = 0,
|
||||||
|
.width = width,
|
||||||
|
.component_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.children_pos = TerminalBuffer.START_POSITION,
|
||||||
|
.text_in_center = text_in_center,
|
||||||
|
.fg = fg,
|
||||||
|
.bg = bg,
|
||||||
|
.cursor = 0,
|
||||||
|
.draw_item_fn = draw_item_fn,
|
||||||
|
.change_item_fn = change_item_fn,
|
||||||
|
.change_item_arg = change_item_arg,
|
||||||
|
.keybinds = .init(allocator),
|
||||||
|
};
|
||||||
|
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Left", &goLeft, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Ctrl+H", &goLeft, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Right", &goRight, self);
|
||||||
|
try buffer.registerKeybind(io, &self.keybinds, "Ctrl+L", &goRight, self);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.list.deinit(self.allocator);
|
||||||
|
self.keybinds.deinit();
|
||||||
|
self.allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionX(self: *Self, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.cursor = self.component_pos.x + 2;
|
||||||
|
self.children_pos = original_pos.addX(self.width);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionY(self: *Self, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.cursor = self.component_pos.x + 2;
|
||||||
|
self.children_pos = original_pos.addY(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positionXY(self: *Self, original_pos: Position) void {
|
||||||
|
self.component_pos = original_pos;
|
||||||
|
self.cursor = self.component_pos.x + 2;
|
||||||
|
self.children_pos = Position.init(
|
||||||
|
self.width,
|
||||||
|
1,
|
||||||
|
).add(original_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn childrenPosition(self: Self) Position {
|
||||||
|
return self.children_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addItem(self: *Self, item: ItemType) !void {
|
||||||
|
try self.list.append(self.allocator, item);
|
||||||
|
self.current = self.list.items.len - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle(self: *Self, _: ?keyboard.Key) void {
|
||||||
|
TerminalBuffer.setCursor(
|
||||||
|
self.component_pos.x + self.cursor + 2,
|
||||||
|
self.component_pos.y,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *Self) void {
|
||||||
|
if (self.list.items.len == 0) return;
|
||||||
|
if (self.width < 2) return;
|
||||||
|
|
||||||
|
var left_arrow = Cell.init('<', self.fg, self.bg);
|
||||||
|
var right_arrow = Cell.init('>', self.fg, self.bg);
|
||||||
|
|
||||||
|
left_arrow.put(self.component_pos.x, self.component_pos.y);
|
||||||
|
right_arrow.put(
|
||||||
|
self.component_pos.x + self.width - 1,
|
||||||
|
self.component_pos.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
const current_item = self.list.items[self.current];
|
||||||
|
const x = self.component_pos.x + 2;
|
||||||
|
const y = self.component_pos.y;
|
||||||
|
const width = self.width - 2;
|
||||||
|
|
||||||
|
@call(
|
||||||
|
.auto,
|
||||||
|
self.draw_item_fn,
|
||||||
|
.{ self, current_item, x, y, width },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn goLeft(ptr: *anyopaque) !bool {
|
||||||
|
var self: *Self = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
self.current = if (self.current == 0) self.list.items.len - 1 else self.current - 1;
|
||||||
|
|
||||||
|
if (self.change_item_fn) |change_item_fn| {
|
||||||
|
@call(
|
||||||
|
.auto,
|
||||||
|
change_item_fn,
|
||||||
|
.{ self.list.items[self.current], self.change_item_arg },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn goRight(ptr: *anyopaque) !bool {
|
||||||
|
var self: *Self = @ptrCast(@alignCast(ptr));
|
||||||
|
|
||||||
|
self.current = if (self.current == self.list.items.len - 1) 0 else self.current + 1;
|
||||||
|
|
||||||
|
if (self.change_item_fn) |change_item_fn| {
|
||||||
|
@call(
|
||||||
|
.auto,
|
||||||
|
change_item_fn,
|
||||||
|
.{ self.list.items[self.current], self.change_item_arg },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
708
ly-ui/src/keyboard.zig
Normal file
708
ly-ui/src/keyboard.zig
Normal file
@@ -0,0 +1,708 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const KeyList = std.ArrayList(Key);
|
||||||
|
|
||||||
|
const TerminalBuffer = @import("TerminalBuffer.zig");
|
||||||
|
const termbox = TerminalBuffer.termbox;
|
||||||
|
|
||||||
|
pub const Key = packed struct {
|
||||||
|
ctrl: bool,
|
||||||
|
shift: bool,
|
||||||
|
alt: bool,
|
||||||
|
|
||||||
|
f1: bool,
|
||||||
|
f2: bool,
|
||||||
|
f3: bool,
|
||||||
|
f4: bool,
|
||||||
|
f5: bool,
|
||||||
|
f6: bool,
|
||||||
|
f7: bool,
|
||||||
|
f8: bool,
|
||||||
|
f9: bool,
|
||||||
|
f10: bool,
|
||||||
|
f11: bool,
|
||||||
|
f12: bool,
|
||||||
|
|
||||||
|
insert: bool,
|
||||||
|
delete: bool,
|
||||||
|
home: bool,
|
||||||
|
end: bool,
|
||||||
|
pageup: bool,
|
||||||
|
pagedown: bool,
|
||||||
|
up: bool,
|
||||||
|
down: bool,
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
tab: bool,
|
||||||
|
backspace: bool,
|
||||||
|
enter: bool,
|
||||||
|
|
||||||
|
@" ": bool,
|
||||||
|
@"!": bool,
|
||||||
|
@"`": bool,
|
||||||
|
esc: bool,
|
||||||
|
@"[": bool,
|
||||||
|
@"\\": bool,
|
||||||
|
@"]": bool,
|
||||||
|
@"/": bool,
|
||||||
|
_: bool,
|
||||||
|
@"'": bool,
|
||||||
|
@"\"": bool,
|
||||||
|
@",": bool,
|
||||||
|
@"-": bool,
|
||||||
|
@".": bool,
|
||||||
|
@"#": bool,
|
||||||
|
@"$": bool,
|
||||||
|
@"%": bool,
|
||||||
|
@"&": bool,
|
||||||
|
@"*": bool,
|
||||||
|
@"(": bool,
|
||||||
|
@")": bool,
|
||||||
|
@"+": bool,
|
||||||
|
@"=": bool,
|
||||||
|
@":": bool,
|
||||||
|
@";": bool,
|
||||||
|
@"<": bool,
|
||||||
|
@">": bool,
|
||||||
|
@"?": bool,
|
||||||
|
@"@": bool,
|
||||||
|
@"^": bool,
|
||||||
|
@"~": bool,
|
||||||
|
@"{": bool,
|
||||||
|
@"}": bool,
|
||||||
|
@"|": bool,
|
||||||
|
|
||||||
|
@"0": bool,
|
||||||
|
@"1": bool,
|
||||||
|
@"2": bool,
|
||||||
|
@"3": bool,
|
||||||
|
@"4": bool,
|
||||||
|
@"5": bool,
|
||||||
|
@"6": bool,
|
||||||
|
@"7": bool,
|
||||||
|
@"8": bool,
|
||||||
|
@"9": bool,
|
||||||
|
|
||||||
|
a: bool,
|
||||||
|
b: bool,
|
||||||
|
c: bool,
|
||||||
|
d: bool,
|
||||||
|
e: bool,
|
||||||
|
f: bool,
|
||||||
|
g: bool,
|
||||||
|
h: bool,
|
||||||
|
i: bool,
|
||||||
|
j: bool,
|
||||||
|
k: bool,
|
||||||
|
l: bool,
|
||||||
|
m: bool,
|
||||||
|
n: bool,
|
||||||
|
o: bool,
|
||||||
|
p: bool,
|
||||||
|
q: bool,
|
||||||
|
r: bool,
|
||||||
|
s: bool,
|
||||||
|
t: bool,
|
||||||
|
u: bool,
|
||||||
|
v: bool,
|
||||||
|
w: bool,
|
||||||
|
x: bool,
|
||||||
|
y: bool,
|
||||||
|
z: bool,
|
||||||
|
|
||||||
|
pub fn getEnabledPrintableAscii(self: Key) ?u8 {
|
||||||
|
if (self.ctrl or self.alt) return null;
|
||||||
|
|
||||||
|
inline for (std.meta.fields(Key)) |field| {
|
||||||
|
if (field.name.len == 1 and std.ascii.isPrint(field.name[0]) and @field(self, field.name)) {
|
||||||
|
if (self.shift) {
|
||||||
|
if (!std.ascii.isAlphanumeric(field.name[0])) return null;
|
||||||
|
return std.ascii.toUpper(field.name[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return field.name[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn getKeyList(allocator: Allocator, tb_event: termbox.tb_event) !KeyList {
|
||||||
|
var keys: KeyList = .empty;
|
||||||
|
var key = std.mem.zeroes(Key);
|
||||||
|
|
||||||
|
if (tb_event.mod & termbox.TB_MOD_CTRL != 0) key.ctrl = true;
|
||||||
|
if (tb_event.mod & termbox.TB_MOD_SHIFT != 0) key.shift = true;
|
||||||
|
if (tb_event.mod & termbox.TB_MOD_ALT != 0) key.alt = true;
|
||||||
|
|
||||||
|
if (tb_event.key == termbox.TB_KEY_BACK_TAB) {
|
||||||
|
key.shift = true;
|
||||||
|
key.tab = true;
|
||||||
|
} else if (tb_event.key > termbox.TB_KEY_BACK_TAB) {
|
||||||
|
const code = 0xFFFF - tb_event.key;
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
0 => key.f1 = true,
|
||||||
|
1 => key.f2 = true,
|
||||||
|
2 => key.f3 = true,
|
||||||
|
3 => key.f4 = true,
|
||||||
|
4 => key.f5 = true,
|
||||||
|
5 => key.f6 = true,
|
||||||
|
6 => key.f7 = true,
|
||||||
|
7 => key.f8 = true,
|
||||||
|
8 => key.f9 = true,
|
||||||
|
9 => key.f10 = true,
|
||||||
|
10 => key.f11 = true,
|
||||||
|
11 => key.f12 = true,
|
||||||
|
12 => key.insert = true,
|
||||||
|
13 => key.delete = true,
|
||||||
|
14 => key.home = true,
|
||||||
|
15 => key.end = true,
|
||||||
|
16 => key.pageup = true,
|
||||||
|
17 => key.pagedown = true,
|
||||||
|
18 => key.up = true,
|
||||||
|
19 => key.down = true,
|
||||||
|
20 => key.left = true,
|
||||||
|
21 => key.right = true,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
} else if (tb_event.ch < 128) {
|
||||||
|
const code = if (tb_event.ch == 0 and tb_event.key < 128) tb_event.key else tb_event.ch;
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
0 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.@"2" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"`" = true;
|
||||||
|
},
|
||||||
|
1 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.a = true;
|
||||||
|
},
|
||||||
|
2 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.b = true;
|
||||||
|
},
|
||||||
|
3 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.c = true;
|
||||||
|
},
|
||||||
|
4 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.d = true;
|
||||||
|
},
|
||||||
|
5 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.e = true;
|
||||||
|
},
|
||||||
|
6 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.f = true;
|
||||||
|
},
|
||||||
|
7 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.g = true;
|
||||||
|
},
|
||||||
|
8 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.h = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.backspace = true;
|
||||||
|
},
|
||||||
|
9 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.i = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.tab = true;
|
||||||
|
},
|
||||||
|
10 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.j = true;
|
||||||
|
},
|
||||||
|
11 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.k = true;
|
||||||
|
},
|
||||||
|
12 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.l = true;
|
||||||
|
},
|
||||||
|
13 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.m = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.enter = true;
|
||||||
|
},
|
||||||
|
14 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.n = true;
|
||||||
|
},
|
||||||
|
15 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.o = true;
|
||||||
|
},
|
||||||
|
16 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.p = true;
|
||||||
|
},
|
||||||
|
17 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.q = true;
|
||||||
|
},
|
||||||
|
18 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.r = true;
|
||||||
|
},
|
||||||
|
19 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.s = true;
|
||||||
|
},
|
||||||
|
20 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.t = true;
|
||||||
|
},
|
||||||
|
21 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.u = true;
|
||||||
|
},
|
||||||
|
22 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.v = true;
|
||||||
|
},
|
||||||
|
23 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.w = true;
|
||||||
|
},
|
||||||
|
24 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.x = true;
|
||||||
|
},
|
||||||
|
25 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.y = true;
|
||||||
|
},
|
||||||
|
26 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.z = true;
|
||||||
|
},
|
||||||
|
27 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.@"3" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.esc = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"[" = true;
|
||||||
|
},
|
||||||
|
28 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.@"4" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"\\" = true;
|
||||||
|
},
|
||||||
|
29 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.@"5" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"]" = true;
|
||||||
|
},
|
||||||
|
30 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"6" = true;
|
||||||
|
},
|
||||||
|
31 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.@"7" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"/" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key._ = true;
|
||||||
|
},
|
||||||
|
32 => {
|
||||||
|
key.@" " = true;
|
||||||
|
},
|
||||||
|
33 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"!" = true;
|
||||||
|
},
|
||||||
|
34 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"\"" = true;
|
||||||
|
},
|
||||||
|
35 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"#" = true;
|
||||||
|
},
|
||||||
|
36 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"$" = true;
|
||||||
|
},
|
||||||
|
37 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"%" = true;
|
||||||
|
},
|
||||||
|
38 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"&" = true;
|
||||||
|
},
|
||||||
|
39 => {
|
||||||
|
key.@"'" = true;
|
||||||
|
},
|
||||||
|
40 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"(" = true;
|
||||||
|
},
|
||||||
|
41 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@")" = true;
|
||||||
|
},
|
||||||
|
42 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"*" = true;
|
||||||
|
},
|
||||||
|
43 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"+" = true;
|
||||||
|
},
|
||||||
|
44 => {
|
||||||
|
key.@"," = true;
|
||||||
|
},
|
||||||
|
45 => {
|
||||||
|
key.@"-" = true;
|
||||||
|
},
|
||||||
|
46 => {
|
||||||
|
key.@"." = true;
|
||||||
|
},
|
||||||
|
47 => {
|
||||||
|
key.@"/" = true;
|
||||||
|
},
|
||||||
|
48 => {
|
||||||
|
key.@"0" = true;
|
||||||
|
},
|
||||||
|
49 => {
|
||||||
|
key.@"1" = true;
|
||||||
|
},
|
||||||
|
50 => {
|
||||||
|
key.@"2" = true;
|
||||||
|
},
|
||||||
|
51 => {
|
||||||
|
key.@"3" = true;
|
||||||
|
},
|
||||||
|
52 => {
|
||||||
|
key.@"4" = true;
|
||||||
|
},
|
||||||
|
53 => {
|
||||||
|
key.@"5" = true;
|
||||||
|
},
|
||||||
|
54 => {
|
||||||
|
key.@"6" = true;
|
||||||
|
},
|
||||||
|
55 => {
|
||||||
|
key.@"7" = true;
|
||||||
|
},
|
||||||
|
56 => {
|
||||||
|
key.@"8" = true;
|
||||||
|
},
|
||||||
|
57 => {
|
||||||
|
key.@"9" = true;
|
||||||
|
},
|
||||||
|
58 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@":" = true;
|
||||||
|
},
|
||||||
|
59 => {
|
||||||
|
key.@";" = true;
|
||||||
|
},
|
||||||
|
60 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"<" = true;
|
||||||
|
},
|
||||||
|
61 => {
|
||||||
|
key.@"=" = true;
|
||||||
|
},
|
||||||
|
62 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@">" = true;
|
||||||
|
},
|
||||||
|
63 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"?" = true;
|
||||||
|
},
|
||||||
|
64 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"2" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"@" = true;
|
||||||
|
},
|
||||||
|
65 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.a = true;
|
||||||
|
},
|
||||||
|
66 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.b = true;
|
||||||
|
},
|
||||||
|
67 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.c = true;
|
||||||
|
},
|
||||||
|
68 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.d = true;
|
||||||
|
},
|
||||||
|
69 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.e = true;
|
||||||
|
},
|
||||||
|
70 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.f = true;
|
||||||
|
},
|
||||||
|
71 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.g = true;
|
||||||
|
},
|
||||||
|
72 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.h = true;
|
||||||
|
},
|
||||||
|
73 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.i = true;
|
||||||
|
},
|
||||||
|
74 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.j = true;
|
||||||
|
},
|
||||||
|
75 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.k = true;
|
||||||
|
},
|
||||||
|
76 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.l = true;
|
||||||
|
},
|
||||||
|
77 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.m = true;
|
||||||
|
},
|
||||||
|
78 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.n = true;
|
||||||
|
},
|
||||||
|
79 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.o = true;
|
||||||
|
},
|
||||||
|
80 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.p = true;
|
||||||
|
},
|
||||||
|
81 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.q = true;
|
||||||
|
},
|
||||||
|
82 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.r = true;
|
||||||
|
},
|
||||||
|
83 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.s = true;
|
||||||
|
},
|
||||||
|
84 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.t = true;
|
||||||
|
},
|
||||||
|
85 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.u = true;
|
||||||
|
},
|
||||||
|
86 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.v = true;
|
||||||
|
},
|
||||||
|
87 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.w = true;
|
||||||
|
},
|
||||||
|
88 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.x = true;
|
||||||
|
},
|
||||||
|
89 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.y = true;
|
||||||
|
},
|
||||||
|
90 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.z = true;
|
||||||
|
},
|
||||||
|
91 => {
|
||||||
|
key.@"[" = true;
|
||||||
|
},
|
||||||
|
92 => {
|
||||||
|
key.@"\\" = true;
|
||||||
|
},
|
||||||
|
93 => {
|
||||||
|
key.@"]" = true;
|
||||||
|
},
|
||||||
|
94 => {
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"^" = true;
|
||||||
|
},
|
||||||
|
95 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"-" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key._ = true;
|
||||||
|
},
|
||||||
|
96 => {
|
||||||
|
key.@"`" = true;
|
||||||
|
},
|
||||||
|
97 => {
|
||||||
|
key.a = true;
|
||||||
|
},
|
||||||
|
98 => {
|
||||||
|
key.b = true;
|
||||||
|
},
|
||||||
|
99 => {
|
||||||
|
key.c = true;
|
||||||
|
},
|
||||||
|
100 => {
|
||||||
|
key.d = true;
|
||||||
|
},
|
||||||
|
101 => {
|
||||||
|
key.e = true;
|
||||||
|
},
|
||||||
|
102 => {
|
||||||
|
key.f = true;
|
||||||
|
},
|
||||||
|
103 => {
|
||||||
|
key.g = true;
|
||||||
|
},
|
||||||
|
104 => {
|
||||||
|
key.h = true;
|
||||||
|
},
|
||||||
|
105 => {
|
||||||
|
key.i = true;
|
||||||
|
},
|
||||||
|
106 => {
|
||||||
|
key.j = true;
|
||||||
|
},
|
||||||
|
107 => {
|
||||||
|
key.k = true;
|
||||||
|
},
|
||||||
|
108 => {
|
||||||
|
key.l = true;
|
||||||
|
},
|
||||||
|
109 => {
|
||||||
|
key.m = true;
|
||||||
|
},
|
||||||
|
110 => {
|
||||||
|
key.n = true;
|
||||||
|
},
|
||||||
|
111 => {
|
||||||
|
key.o = true;
|
||||||
|
},
|
||||||
|
112 => {
|
||||||
|
key.p = true;
|
||||||
|
},
|
||||||
|
113 => {
|
||||||
|
key.q = true;
|
||||||
|
},
|
||||||
|
114 => {
|
||||||
|
key.r = true;
|
||||||
|
},
|
||||||
|
115 => {
|
||||||
|
key.s = true;
|
||||||
|
},
|
||||||
|
116 => {
|
||||||
|
key.t = true;
|
||||||
|
},
|
||||||
|
117 => {
|
||||||
|
key.u = true;
|
||||||
|
},
|
||||||
|
118 => {
|
||||||
|
key.v = true;
|
||||||
|
},
|
||||||
|
119 => {
|
||||||
|
key.w = true;
|
||||||
|
},
|
||||||
|
120 => {
|
||||||
|
key.x = true;
|
||||||
|
},
|
||||||
|
121 => {
|
||||||
|
key.y = true;
|
||||||
|
},
|
||||||
|
122 => {
|
||||||
|
key.z = true;
|
||||||
|
},
|
||||||
|
123 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"{" = true;
|
||||||
|
},
|
||||||
|
124 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"\\" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"|" = true;
|
||||||
|
},
|
||||||
|
125 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"}" = true;
|
||||||
|
},
|
||||||
|
126 => {
|
||||||
|
key.shift = true;
|
||||||
|
key.@"`" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.@"~" = true;
|
||||||
|
},
|
||||||
|
127 => {
|
||||||
|
key.ctrl = true;
|
||||||
|
key.@"8" = true;
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
key = std.mem.zeroes(Key);
|
||||||
|
key.backspace = true;
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try keys.append(allocator, key);
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
13
ly-ui/src/root.zig
Normal file
13
ly-ui/src/root.zig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
pub const ly_core = @import("ly-core");
|
||||||
|
|
||||||
|
pub const Cell = @import("Cell.zig");
|
||||||
|
pub const keyboard = @import("keyboard.zig");
|
||||||
|
pub const Position = @import("Position.zig");
|
||||||
|
pub const TerminalBuffer = @import("TerminalBuffer.zig");
|
||||||
|
pub const Widget = @import("Widget.zig");
|
||||||
|
|
||||||
|
pub const BigLabel = @import("components/BigLabel.zig");
|
||||||
|
pub const Box = @import("components/Box.zig");
|
||||||
|
pub const CyclableLabel = @import("components/generic.zig").CyclableLabel;
|
||||||
|
pub const Label = @import("components/Label.zig");
|
||||||
|
pub const Text = @import("components/Text.zig");
|
||||||
314
readme.md
314
readme.md
@@ -1,177 +1,220 @@
|
|||||||
# Ly - a TUI display manager
|
# The Ly 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 is a lightweight TUI (ncurses-like) display manager for Linux and BSD.
|
_Note: the above animation can be found [here](https://codeberg.org/attachments/f336d6ac-8331-4323-91fc-0e4619803401)!_
|
||||||
|
|
||||||
|
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD, designed with portability in mind and doesn't require systemd to run.
|
||||||
|
|
||||||
|
Join us on Matrix over at [#ly-dm:matrix.org](https://matrix.to/#/#ly-dm:matrix.org)!
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Development happens on [Codeberg](https://codeberg.org/fairyglade/ly) with a mirror on [GitHub](https://github.com/fairyglade/ly).
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- Compile-time:
|
- Compile-time:
|
||||||
- zig 0.14.0
|
- zig 0.16.x
|
||||||
|
|
||||||
- libc
|
- libc
|
||||||
|
|
||||||
- pam
|
- pam
|
||||||
|
|
||||||
- xcb (optional, required by default; needed for X11 support)
|
- xcb (optional, required by default; needed for X11 support)
|
||||||
|
|
||||||
- Runtime (with default config):
|
- Runtime (with default config):
|
||||||
- xorg
|
- xorg
|
||||||
|
|
||||||
- xorg-xauth
|
- xorg-xauth
|
||||||
|
|
||||||
- shutdown
|
- shutdown
|
||||||
|
|
||||||
- brightnessctl
|
- brightnessctl
|
||||||
|
|
||||||
### Debian
|
### Debian
|
||||||
|
|
||||||
```
|
```
|
||||||
# apt install build-essential libpam0g-dev libxcb-xkb-dev
|
# apt install build-essential libpam0g-dev libxcb-xkb-dev xauth xserver-xorg brightnessctl
|
||||||
```
|
```
|
||||||
|
|
||||||
### Fedora
|
### 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.
|
> [!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
|
# dnf install kernel-devel pam-devel libxcb-devel zig xorg-x11-xauth xorg-x11-server brightnessctl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### FreeBSD
|
||||||
|
|
||||||
|
```
|
||||||
|
# pkg install ca_root_nss libxcb git xorg xauth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Availability
|
||||||
|
|
||||||
|
[](https://repology.org/project/ly-display-manager/versions)
|
||||||
|
|
||||||
## Support
|
## Support
|
||||||
The following desktop environments were tested with success:
|
|
||||||
|
|
||||||
[Wayland Environments](#supported-wayland-environments)
|
Every environment that works on other login managers also should work on Ly.
|
||||||
|
|
||||||
[X11 Environments](#supported-x11-environments)
|
- Unlike most login managers Ly has an xinitrc and shell entry.
|
||||||
|
|
||||||
Ly should work with any X desktop environment, and provides
|
- If you installed your favorite environment and you don't see it, that's because Ly doesn't automatically refresh itself. To fix this you should restart Ly service (depends on your init system) or the easy way is to reboot your system.
|
||||||
basic wayland support (sway works very well, for example).
|
|
||||||
|
|
||||||
## systemd?
|
- If your environment is still missing then check at `/usr/share/xsessions` or `/usr/share/wayland-sessions` to see if a .desktop file is present.
|
||||||
Unlike what you may have heard, Ly does not require `systemd`,
|
|
||||||
and was even specifically designed not to depend on `logind`.
|
- If there isn't a .desktop file then create a new one at `/etc/ly/custom-sessions` that launches your favorite environment. These .desktop files can be only seen by Ly and if you want them system-wide you also can create at those directories instead.
|
||||||
You should be able to make it work easily with a better init,
|
|
||||||
changing the source code won't be necessary :)
|
- If Xorg sessions don't work then check if your distro compiles Ly with Xorg.
|
||||||
|
|
||||||
|
Logs are defined by `/etc/ly/config.ini`:
|
||||||
|
|
||||||
|
- The session log is located at `~/.local/state/ly-session.log` by default.
|
||||||
|
|
||||||
|
- The system log is located at `/var/log/ly.log` by default.
|
||||||
|
|
||||||
|
## Manually building
|
||||||
|
|
||||||
|
The procedure for manually building Ly is pretty standard:
|
||||||
|
|
||||||
## Cloning and Compiling
|
|
||||||
Clone the repository
|
|
||||||
```
|
|
||||||
$ git clone https://codeberg.org/AnErrupTion/ly
|
|
||||||
```
|
|
||||||
|
|
||||||
Change the directory to ly
|
|
||||||
```
|
```
|
||||||
|
$ git clone https://codeberg.org/fairyglade/ly.git
|
||||||
$ cd ly
|
$ cd ly
|
||||||
```
|
|
||||||
|
|
||||||
Compile
|
|
||||||
```
|
|
||||||
$ zig build
|
$ zig build
|
||||||
```
|
```
|
||||||
|
|
||||||
Test in the configured tty (tty2 by default)
|
After building, you can (optionally) test Ly in a terminal emulator, although authentication will **not** work:
|
||||||
or a terminal emulator (but authentication won't work)
|
|
||||||
```
|
```
|
||||||
$ zig build run
|
$ zig build run
|
||||||
```
|
```
|
||||||
|
|
||||||
**Important**: Running Ly in a terminal emulator as root is *not* recommended. If you
|
> [!IMPORTANT]
|
||||||
want to properly test Ly, please enable its service (as described below) and reboot
|
> While you can run Ly in a terminal emulator as root, it is **not** recommended. If you want to test Ly, please enable its service (as described below) and reboot your machine.
|
||||||
your machine.
|
|
||||||
|
The next sections will explain how to use Ly with a variety of init systems. Detailed explanation is only given for systemd, but should be applicable for all.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> All following sections will assume you are using LightDM for convenience sake.
|
||||||
|
|
||||||
|
### systemd
|
||||||
|
|
||||||
|
Now, you can install Ly on your system:
|
||||||
|
|
||||||
Install Ly for systemd-based systems (the default)
|
|
||||||
```
|
```
|
||||||
# zig build installexe
|
# zig build installexe -Dinit_system=systemd
|
||||||
```
|
```
|
||||||
|
|
||||||
Instead of DISPLAY_MANAGER you need to add your DM:
|
> [!NOTE]
|
||||||
- gdm.service
|
> The `init_system` parameter is optional and defaults to `systemd`.
|
||||||
- sddm.service
|
|
||||||
- lightdm.service
|
Note that you also need to disable your current display manager. For example, if you are using LightDM, you can execute the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
# systemctl disable DISPLAY_MANAGER
|
# systemctl disable lightdm.service
|
||||||
```
|
```
|
||||||
|
|
||||||
Enable the service
|
Then, similarly to the previous command, you need to enable the Ly service:
|
||||||
|
|
||||||
```
|
```
|
||||||
# systemctl enable ly.service
|
# systemctl enable ly@tty2.service
|
||||||
```
|
```
|
||||||
|
|
||||||
If you need to switch between ttys after Ly's start you also have to
|
> [!IMPORTANT]
|
||||||
disable getty on Ly's tty to prevent "login" from spawning on top of it
|
> Because Ly runs in a TTY, you **must** disable the TTY service that Ly will run on, otherwise bad things will happen. For example, to disable `getty` spawning on TTY 2, you need to execute the following command:
|
||||||
|
|
||||||
```
|
```
|
||||||
# systemctl disable getty@tty2.service
|
# systemctl disable getty@tty2.service
|
||||||
```
|
```
|
||||||
|
|
||||||
|
On platforms that use systemd-logind to dynamically start `autovt@.service` instances when the switch to a new tty occurs, any ly instances for ttys _except the default tty_ need to be enabled using a different mechanism: To autostart ly on switch to `tty2`, do not enable any `ly` unit directly, instead symlink `autovt@tty2.service` to `ly@tty2.service` within `/usr/lib/systemd/system/` (analogous for every other tty you want to enable ly on).
|
||||||
|
|
||||||
|
The target of the symlink, `ly@ttyN.service`, does not actually exist, but systemd nevertheless recognizes that the instanciation of `autovt@.service` with `%I` equal to `ttyN` now points to an instanciation of `ly@.service` with `%I` set to `ttyN`.
|
||||||
|
|
||||||
|
Compare to `man 5 logind.conf`, especially regarding the `NAutoVTs=` and `ReserveVT=` parameters.
|
||||||
|
|
||||||
|
On non-systemd systems, you can change the TTY Ly will run on by editing the corresponding service file for your platform.
|
||||||
|
|
||||||
### OpenRC
|
### 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
|
# zig build installexe -Dinit_system=openrc
|
||||||
```
|
# rc-update del lightdm
|
||||||
|
|
||||||
Enable the service
|
|
||||||
```
|
|
||||||
# rc-update add ly
|
# 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
|
# rc-update del agetty.tty2
|
||||||
```
|
```
|
||||||
|
|
||||||
**NOTE 2**: To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).
|
> [!NOTE]
|
||||||
|
> On Gentoo specifically, you also **must** comment out the appropriate line for the TTY in /etc/inittab.
|
||||||
|
|
||||||
### runit
|
### runit
|
||||||
|
|
||||||
```
|
```
|
||||||
# zig build installexe -Dinit_system=runit
|
# zig build installexe -Dinit_system=runit
|
||||||
|
# rm /var/service/lightdm
|
||||||
# ln -s /etc/sv/ly /var/service/
|
# 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
|
# rm /var/service/agetty-tty2
|
||||||
```
|
```
|
||||||
|
|
||||||
### s6
|
### s6
|
||||||
|
|
||||||
```
|
```
|
||||||
# zig build installexe -Dinit_system=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-service add default ly-srv
|
||||||
# s6-db-reload
|
# s6-db-reload
|
||||||
# s6-rc -u change ly-srv
|
# s6-rc -u change ly-srv
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To disable TTY 2, edit `/etc/s6/config/tty2.conf` and set `SPAWN="no"`.
|
||||||
|
|
||||||
### dinit
|
### dinit
|
||||||
|
|
||||||
```
|
```
|
||||||
# zig build installexe -Dinit_system=dinit
|
# zig build installexe -Dinit_system=dinit
|
||||||
|
# dinitctl disable lightdm
|
||||||
# dinitctl enable ly
|
# dinitctl enable ly
|
||||||
```
|
```
|
||||||
|
|
||||||
In addition to the steps above, you will also have to keep a TTY free within `/etc/dinit.d/config/console.conf`.
|
To disable TTY 2, go to `/etc/dinit.d/config/console.conf` and modify `ACTIVE_CONSOLES`.
|
||||||
|
|
||||||
To do that, change `ACTIVE_CONSOLES` so that the tty that ly should use in `/etc/ly/config.ini` is free.
|
### sysvinit
|
||||||
|
|
||||||
|
```
|
||||||
|
# zig build installexe -Dinit_system=sysvinit
|
||||||
|
# update-rc.d lightdm disable
|
||||||
|
# update-rc.d ly defaults
|
||||||
|
```
|
||||||
|
|
||||||
|
To disable TTY 2, go to `/etc/inittab` and comment out the line containing `tty2`.
|
||||||
|
|
||||||
|
### FreeBSD
|
||||||
|
|
||||||
|
```
|
||||||
|
# zig build installexe -Dprefix_directory=/usr/local -Dconfig_directory=/usr/local/etc -Dinit_system=freebsd
|
||||||
|
# sysrc lightdm_enable="NO"
|
||||||
|
```
|
||||||
|
|
||||||
|
To enable Ly, add the following entry to `/etc/gettytab`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Ly:\
|
||||||
|
:lo=/usr/local/bin/ly_wrapper:\
|
||||||
|
:al=root:
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, modify the command field of the `ttyv1` terminal entry in `/etc/ttys` (TTYs in FreeBSD start at 0):
|
||||||
|
|
||||||
|
```
|
||||||
|
ttyv1 "/usr/libexec/getty Ly" xterm on secure
|
||||||
|
```
|
||||||
|
|
||||||
### Updating
|
### Updating
|
||||||
You can also install Ly without overrding the current configuration file. That's called
|
|
||||||
*updating*. To update, simply run:
|
You can also install Ly without overrding the current configuration file. This is called **updating**. To update, simply run:
|
||||||
|
|
||||||
```
|
```
|
||||||
# zig build installnoconf
|
# zig build installnoconf
|
||||||
@@ -179,96 +222,41 @@ You can also install Ly without overrding the current configuration file. That's
|
|||||||
|
|
||||||
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
|
## Configuration
|
||||||
You can find all the configuration in `/etc/ly/config.ini`.
|
|
||||||
The file is commented, and includes the default values.
|
You can find all the configuration in `/etc/ly/config.ini`. The file is fully commented, and includes the default values.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
Use the up and down arrow keys to change the current field, and the
|
|
||||||
left and right arrow keys to change the target desktop environment
|
|
||||||
while on the desktop field (above the login field).
|
|
||||||
|
|
||||||
## .xinitrc
|
Use the Up/Down arrow keys to change the current field, and the Left/Right arrow keys to scroll through the different fields (whether it be the info line, the desktop environment, or the username). The info line is where messages and errors are displayed.
|
||||||
If 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.
|
> 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:
|
A typical shebang for a shell script looks like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
- The numlock and capslock state is printed in the top-right corner.
|
- The numlock and capslock state is printed in the top-right corner.
|
||||||
|
|
||||||
- Use the F1 and F2 keys to respectively shutdown and reboot.
|
- Use the F1 and F2 keys to respectively shutdown and reboot.
|
||||||
- Take a look at your .xsession if X doesn't start, as it can interfere
|
|
||||||
(this file is launched with X to configure the display properly).
|
|
||||||
|
|
||||||
## Supported Wayland Environments
|
- Take a look at your `.xsession` file if X doesn't start, as it can interfere (this file is launched with X to configure the display properly).
|
||||||
- budgie
|
|
||||||
- cosmic
|
|
||||||
- deepin
|
|
||||||
- enlightenment
|
|
||||||
- gnome
|
|
||||||
- hyprland
|
|
||||||
- kde
|
|
||||||
- labwc
|
|
||||||
- niri
|
|
||||||
- pantheon
|
|
||||||
- sway
|
|
||||||
- weston
|
|
||||||
|
|
||||||
## Supported X11 Environments
|
## A final note
|
||||||
- awesome
|
|
||||||
- bspwm
|
|
||||||
- budgie
|
|
||||||
- cinnamon
|
|
||||||
- dwm
|
|
||||||
- enlightenment
|
|
||||||
- gnome
|
|
||||||
- kde
|
|
||||||
- leftwm
|
|
||||||
- lxde
|
|
||||||
- mate
|
|
||||||
- maxx
|
|
||||||
- pantheon
|
|
||||||
- qwm
|
|
||||||
- spectrwm
|
|
||||||
- windowmaker
|
|
||||||
- xfce
|
|
||||||
- xmonad
|
|
||||||
|
|
||||||
|
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
|
Also, Ly wouldn't be there today without [ashametrine](https://github.com/ashametrine), who has done significant contributions to the project for the Zig rewrite, which lead to the release of Ly v1.0.0. Massive thanks, and sorry for not crediting you enough beforehand!
|
||||||
The name "Ly" is a tribute to the fairy from the game Rayman.
|
|
||||||
Ly was tested by oxodao, who is some seriously awesome dude.
|
### Donate
|
||||||
|
|
||||||
|
If you like Ly and wish to support my work further, feel free to donate via my
|
||||||
|
[Liberapay link](https://liberapay.com/ShiningLea)!
|
||||||
|
|||||||
262
res/config.ini
262
res/config.ini
@@ -1,14 +1,14 @@
|
|||||||
# Ly supports 24-bit true color with styling, which means each color is a 32-bit value.
|
# 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.
|
# 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:
|
# Here are the possible styling options:
|
||||||
#define TB_BOLD 0x01000000
|
# TB_BOLD 0x01000000
|
||||||
#define TB_UNDERLINE 0x02000000
|
# TB_UNDERLINE 0x02000000
|
||||||
#define TB_REVERSE 0x04000000
|
# TB_REVERSE 0x04000000
|
||||||
#define TB_ITALIC 0x08000000
|
# TB_ITALIC 0x08000000
|
||||||
#define TB_BLINK 0x10000000
|
# TB_BLINK 0x10000000
|
||||||
#define TB_HI_BLACK 0x20000000
|
# TB_HI_BLACK 0x20000000
|
||||||
#define TB_BRIGHT 0x40000000
|
# TB_BRIGHT 0x40000000
|
||||||
#define TB_DIM 0x80000000
|
# TB_DIM 0x80000000
|
||||||
# Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's
|
# 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.
|
# 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
|
# Note that, if you want to use the default color value of the terminal, you can use the
|
||||||
@@ -23,8 +23,13 @@ allow_empty_password = true
|
|||||||
# doom -> PSX DOOM fire
|
# doom -> PSX DOOM fire
|
||||||
# matrix -> CMatrix
|
# matrix -> CMatrix
|
||||||
# colormix -> Color mixing shader
|
# colormix -> Color mixing shader
|
||||||
|
# gameoflife -> John Conway's Game of Life
|
||||||
|
# dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master)
|
||||||
animation = none
|
animation = none
|
||||||
|
|
||||||
|
# Delay between each animation frame in milliseconds
|
||||||
|
animation_frame_delay = 5
|
||||||
|
|
||||||
# Stop the animation after some time
|
# Stop the animation after some time
|
||||||
# 0 -> Run forever
|
# 0 -> Run forever
|
||||||
# 1..2e12 -> Stop the animation after this many seconds
|
# 1..2e12 -> Stop the animation after this many seconds
|
||||||
@@ -38,8 +43,38 @@ animation_timeout_sec = 0
|
|||||||
asterisk = *
|
asterisk = *
|
||||||
|
|
||||||
# The number of failed authentications before a special animation is played... ;)
|
# The number of failed authentications before a special animation is played... ;)
|
||||||
|
# If set to 0, the animation will never be played
|
||||||
auth_fails = 10
|
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
|
||||||
|
|
||||||
|
# Automatic login configuration
|
||||||
|
# This feature allows Ly to automatically log in a user without password prompt.
|
||||||
|
# IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work.
|
||||||
|
# Autologin only happens once at startup - it won't re-trigger after logout.
|
||||||
|
|
||||||
|
# PAM service name to use for automatic login
|
||||||
|
# The default service (ly-autologin) uses pam_permit to allow login without password
|
||||||
|
# The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically
|
||||||
|
auto_login_service = ly-autologin
|
||||||
|
|
||||||
|
# Session name to launch automatically
|
||||||
|
# To find available session names, check the .desktop files in:
|
||||||
|
# - /usr/share/xsessions/ (for X11 sessions)
|
||||||
|
# - /usr/share/wayland-sessions/ (for Wayland sessions)
|
||||||
|
# Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field
|
||||||
|
# Examples: "i3", "sway", "gnome", "plasma", "xfce"
|
||||||
|
# If null, automatic login is disabled
|
||||||
|
auto_login_session = null
|
||||||
|
|
||||||
|
# Username to automatically log in
|
||||||
|
# Must be a valid user on the system
|
||||||
|
# If null, automatic login is disabled
|
||||||
|
auto_login_user = null
|
||||||
|
|
||||||
# Background color id
|
# Background color id
|
||||||
bg = 0x00000000
|
bg = 0x00000000
|
||||||
|
|
||||||
@@ -49,6 +84,12 @@ bg = 0x00000000
|
|||||||
# fa -> Farsi
|
# fa -> Farsi
|
||||||
bigclock = none
|
bigclock = none
|
||||||
|
|
||||||
|
# Set bigclock to 12-hour notation.
|
||||||
|
bigclock_12hr = false
|
||||||
|
|
||||||
|
# Set bigclock to show the seconds.
|
||||||
|
bigclock_seconds = false
|
||||||
|
|
||||||
# Blank main box background
|
# Blank main box background
|
||||||
# Setting to false will make it transparent
|
# Setting to false will make it transparent
|
||||||
blank_box = true
|
blank_box = true
|
||||||
@@ -60,16 +101,16 @@ border_fg = 0x00FFFFFF
|
|||||||
# If set to null, none will be shown
|
# If set to null, none will be shown
|
||||||
box_title = null
|
box_title = null
|
||||||
|
|
||||||
# Brightness increase command
|
# Brightness decrease command
|
||||||
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s 10%-
|
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%-
|
||||||
|
|
||||||
# Brightness decrease key, or null to disable
|
# Brightness decrease key combination, or null to disable
|
||||||
brightness_down_key = F5
|
brightness_down_key = F5
|
||||||
|
|
||||||
# Brightness increase command
|
# Brightness increase command
|
||||||
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s +10%
|
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%
|
||||||
|
|
||||||
# Brightness increase key, or null to disable
|
# Brightness increase key combination, or null to disable
|
||||||
brightness_up_key = F6
|
brightness_up_key = F6
|
||||||
|
|
||||||
# Erase password input on failure
|
# Erase password input on failure
|
||||||
@@ -82,6 +123,9 @@ clock = null
|
|||||||
# CMatrix animation foreground color id
|
# CMatrix animation foreground color id
|
||||||
cmatrix_fg = 0x0000FF00
|
cmatrix_fg = 0x0000FF00
|
||||||
|
|
||||||
|
# CMatrix animation character string head color id
|
||||||
|
cmatrix_head_col = 0x01FFFFFF
|
||||||
|
|
||||||
# CMatrix animation minimum codepoint. It uses a 16-bit integer
|
# CMatrix animation minimum codepoint. It uses a 16-bit integer
|
||||||
# For Japanese characters for example, you can use 0x3000 here
|
# For Japanese characters for example, you can use 0x3000 here
|
||||||
cmatrix_min_codepoint = 0x21
|
cmatrix_min_codepoint = 0x21
|
||||||
@@ -99,22 +143,52 @@ colormix_col2 = 0x000000FF
|
|||||||
# Color mixing animation third color id
|
# Color mixing animation third color id
|
||||||
colormix_col3 = 0x20000000
|
colormix_col3 = 0x20000000
|
||||||
|
|
||||||
# Console path
|
# For custom binds: the horizontal limit in characters for each
|
||||||
console_dev = /dev/console
|
# line of custom binds before moving on to the next.
|
||||||
|
# If null, defaults to the width of the terminal instead.
|
||||||
|
custom_bind_width = null
|
||||||
|
|
||||||
|
# Custom sessions directory
|
||||||
|
# You can specify multiple directories,
|
||||||
|
# e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
|
||||||
|
custom_sessions = $CONFIG_DIRECTORY/ly/custom-sessions
|
||||||
|
|
||||||
# Input box active by default on startup
|
# Input box active by default on startup
|
||||||
# Available inputs: info_line, session, login, password
|
# Available inputs: info_line, session, login, password
|
||||||
default_input = login
|
default_input = login
|
||||||
|
|
||||||
# DOOM animation top color (low intensity flames)
|
# DOOM animation fire height (1 thru 9)
|
||||||
doom_top_color = 0x00FF0000
|
doom_fire_height = 6
|
||||||
|
|
||||||
# DOOM animation middle color (medium intensity flames)
|
# DOOM animation fire spread (0 thru 4)
|
||||||
doom_middle_color = 0x00FFFF00
|
doom_fire_spread = 2
|
||||||
|
|
||||||
# DOOM animation bottom color (high intensity flames)
|
# DOOM animation custom top color (low intensity flames)
|
||||||
|
doom_top_color = 0x009F2707
|
||||||
|
|
||||||
|
# DOOM animation custom middle color (medium intensity flames)
|
||||||
|
doom_middle_color = 0x00C78F17
|
||||||
|
|
||||||
|
# DOOM animation custom bottom color (high intensity flames)
|
||||||
doom_bottom_color = 0x00FFFFFF
|
doom_bottom_color = 0x00FFFFFF
|
||||||
|
|
||||||
|
# Dur file path
|
||||||
|
dur_file_path = $CONFIG_DIRECTORY/ly/example.dur
|
||||||
|
|
||||||
|
# Dur file alignment
|
||||||
|
# The dur file can be aligned with a direction and centered easily with the flags below
|
||||||
|
# Available inputs: topleft, topcenter, topright, centerleft, center, centerright, bottomleft, bottomcenter, bottomright
|
||||||
|
dur_offset_alignment = center
|
||||||
|
|
||||||
|
# Dur offset x direction (value is added to the current position determined by alignment, negatives are supported)
|
||||||
|
dur_x_offset = 0
|
||||||
|
|
||||||
|
# Dur offset y direction (value is added to the current position determined by alignment, negatives are supported)
|
||||||
|
dur_y_offset = 0
|
||||||
|
|
||||||
|
# Set margin to the edges of the DM (useful for curved monitors)
|
||||||
|
edge_margin = 0
|
||||||
|
|
||||||
# Error background color id
|
# Error background color id
|
||||||
error_bg = 0x00000000
|
error_bg = 0x00000000
|
||||||
|
|
||||||
@@ -125,12 +199,70 @@ error_fg = 0x01FF0000
|
|||||||
# Foreground color id
|
# Foreground color id
|
||||||
fg = 0x00FFFFFF
|
fg = 0x00FFFFFF
|
||||||
|
|
||||||
|
# Render true colors (if supported)
|
||||||
|
# If false, output will be in eight-color mode
|
||||||
|
# All eight-color mode color codes:
|
||||||
|
# TB_DEFAULT 0x0000
|
||||||
|
# TB_BLACK 0x0001
|
||||||
|
# TB_RED 0x0002
|
||||||
|
# TB_GREEN 0x0003
|
||||||
|
# TB_YELLOW 0x0004
|
||||||
|
# TB_BLUE 0x0005
|
||||||
|
# TB_MAGENTA 0x0006
|
||||||
|
# TB_CYAN 0x0007
|
||||||
|
# TB_WHITE 0x0008
|
||||||
|
# If full color is off, the styling options still work. The colors are
|
||||||
|
# always 32-bit values with the styling in the most significant byte.
|
||||||
|
# Note: If using the dur_file animation option and the dur file's color range
|
||||||
|
# is saved as 256 with this option disabled, the file will not be drawn.
|
||||||
|
full_color = true
|
||||||
|
|
||||||
|
# Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations)
|
||||||
|
# 0 -> Pure Conway's Game of Life (will eventually stabilize)
|
||||||
|
# 10 -> Add entropy every 10 generations (recommended for continuous activity)
|
||||||
|
# 50+ -> Less frequent entropy for more natural evolution
|
||||||
|
gameoflife_entropy_interval = 10
|
||||||
|
|
||||||
|
# Game of Life animation foreground color id
|
||||||
|
gameoflife_fg = 0x0000FF00
|
||||||
|
|
||||||
|
# Game of Life frame delay (lower = faster animation, higher = slower)
|
||||||
|
# 1-3 -> Very fast animation
|
||||||
|
# 6 -> Default smooth animation speed
|
||||||
|
# 10+ -> Slower, more contemplative speed
|
||||||
|
gameoflife_frame_delay = 6
|
||||||
|
|
||||||
|
# Game of Life initial cell density (0.0 to 1.0)
|
||||||
|
# 0.1 -> Sparse, minimal activity
|
||||||
|
# 0.4 -> Balanced activity (recommended)
|
||||||
|
# 0.7+ -> Dense, chaotic patterns
|
||||||
|
gameoflife_initial_density = 0.4
|
||||||
|
|
||||||
|
# Command executed when pressing hibernate key (can be null)
|
||||||
|
hibernate_cmd = null
|
||||||
|
|
||||||
|
# Specifies the key combination used for hibernate
|
||||||
|
hibernate_key = F4
|
||||||
|
|
||||||
# Remove main box borders
|
# Remove main box borders
|
||||||
hide_borders = false
|
hide_borders = false
|
||||||
|
|
||||||
# Remove power management command hints
|
# Remove power management command hints
|
||||||
hide_key_hints = false
|
hide_key_hints = false
|
||||||
|
|
||||||
|
# Remove keyboard lock states from the top right corner
|
||||||
|
hide_keyboard_locks = false
|
||||||
|
|
||||||
|
# Remove version number from the top left corner
|
||||||
|
hide_version_string = false
|
||||||
|
|
||||||
|
# Command executed when no input is detected for a certain time
|
||||||
|
# If null, no command will be executed
|
||||||
|
inactivity_cmd = null
|
||||||
|
|
||||||
|
# Executes a command after a certain amount of seconds
|
||||||
|
inactivity_delay = 0
|
||||||
|
|
||||||
# Initial text to show on the info line
|
# Initial text to show on the info line
|
||||||
# If set to null, the info line defaults to the hostname
|
# If set to null, the info line defaults to the hostname
|
||||||
initial_info_text = null
|
initial_info_text = null
|
||||||
@@ -142,44 +274,45 @@ input_len = 34
|
|||||||
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
|
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
|
||||||
lang = en
|
lang = en
|
||||||
|
|
||||||
# Load the saved desktop and username
|
|
||||||
load = true
|
|
||||||
|
|
||||||
# Command executed when logging in
|
# Command executed when logging in
|
||||||
# If null, no command will be executed
|
# If null, no command will be executed
|
||||||
# Important: the code itself must end with `exec "$@"` in order to launch the session!
|
# 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
|
# You can also set environment variables in there, they'll persist until logout
|
||||||
login_cmd = null
|
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
|
# Command executed when logging out
|
||||||
# If null, no command will be executed
|
# If null, no command will be executed
|
||||||
# Important: the session will already be terminated when this command is executed, so
|
# Important: the session will already be terminated when this command is executed, so
|
||||||
# no need to add `exec "$@"` at the end
|
# no need to add `exec "$@"` at the end
|
||||||
logout_cmd = null
|
logout_cmd = null
|
||||||
|
|
||||||
|
# General log file path
|
||||||
|
ly_log = /var/log/ly.log
|
||||||
|
|
||||||
# Main box horizontal margin
|
# Main box horizontal margin
|
||||||
margin_box_h = 2
|
margin_box_h = 2
|
||||||
|
|
||||||
# Main box vertical margin
|
# Main box vertical margin
|
||||||
margin_box_v = 1
|
margin_box_v = 1
|
||||||
|
|
||||||
# Event timeout in milliseconds
|
|
||||||
min_refresh_delta = 5
|
|
||||||
|
|
||||||
# Set numlock on/off at startup
|
# Set numlock on/off at startup
|
||||||
numlock = false
|
numlock = false
|
||||||
|
|
||||||
# Default path
|
# Default path
|
||||||
# If null, ly doesn't set a path
|
# If null, ly doesn't set a path
|
||||||
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
path = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
|
||||||
# Command executed when pressing restart_key
|
# Command executed when pressing restart_key
|
||||||
restart_cmd = /sbin/shutdown -r now
|
restart_cmd = /sbin/shutdown -r now
|
||||||
|
|
||||||
# Specifies the key used for restart (F1-F12)
|
# Specifies the key combination used for restart
|
||||||
restart_key = F2
|
restart_key = F2
|
||||||
|
|
||||||
# Save the current desktop and login as defaults
|
# Save the current desktop and login as defaults, and load them on startup
|
||||||
save = true
|
save = true
|
||||||
|
|
||||||
# Service name (set to ly to use the provided pam config file)
|
# Service name (set to ly to use the provided pam config file)
|
||||||
@@ -188,31 +321,46 @@ service_name = ly
|
|||||||
# Session log file path
|
# Session log file path
|
||||||
# This will contain stdout and stderr of Wayland sessions
|
# This will contain stdout and stderr of Wayland sessions
|
||||||
# By default it's saved in the user's home directory
|
# By default it's saved in the user's home directory
|
||||||
# Important: due to technical limitations, X11 and shell sessions aren't supported, which
|
# Important: due to technical limitations, X11, shell sessions as well as
|
||||||
# means you won't get any logs from those sessions
|
# launching session via KMSCON aren't supported, which means you won't get any
|
||||||
session_log = ly-session.log
|
# logs from those sessions.
|
||||||
|
# If null, no session log will be created
|
||||||
|
session_log = .local/state/ly-session.log
|
||||||
|
|
||||||
# Setup command
|
# Setup command
|
||||||
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
|
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
|
||||||
|
|
||||||
# Command executed when pressing shutdown_key
|
# Show the shell session in the session list
|
||||||
shutdown_cmd = /sbin/shutdown -a now
|
# If false, the shell session will be hidden
|
||||||
|
shell = true
|
||||||
|
|
||||||
# Specifies the key used for shutdown (F1-F12)
|
# Specifies the key combination used for showing the password
|
||||||
|
show_password_key = F7
|
||||||
|
|
||||||
|
# Display the active TTY number (e.g. tty3) to the right of the clock in the top right corner
|
||||||
|
# If the clock is disabled, the TTY label occupies the top right corner on its own
|
||||||
|
# If false, the TTY number will not be shown
|
||||||
|
show_tty = false
|
||||||
|
|
||||||
|
# Command executed when pressing shutdown_key
|
||||||
|
shutdown_cmd = /sbin/shutdown $PLATFORM_SHUTDOWN_ARG now
|
||||||
|
|
||||||
|
# Specifies the key combination used for shutdown
|
||||||
shutdown_key = F1
|
shutdown_key = F1
|
||||||
|
|
||||||
# Command executed when pressing sleep key (can be null)
|
# Command executed when pressing sleep key (can be null)
|
||||||
sleep_cmd = null
|
sleep_cmd = null
|
||||||
|
|
||||||
# Specifies the key used for sleep (F1-F12)
|
# Specifies the key combination used for sleep
|
||||||
sleep_key = F3
|
sleep_key = F3
|
||||||
|
|
||||||
|
# Command executed when starting Ly (before the TTY is taken control of)
|
||||||
|
# See file at path below for an example of changing the default TTY colors
|
||||||
|
start_cmd = $CONFIG_DIRECTORY/ly/startup.sh
|
||||||
|
|
||||||
# Center the session name.
|
# Center the session name.
|
||||||
text_in_center = false
|
text_in_center = false
|
||||||
|
|
||||||
# TTY in use
|
|
||||||
tty = $DEFAULT_TTY
|
|
||||||
|
|
||||||
# Default vi mode
|
# Default vi mode
|
||||||
# normal -> normal mode
|
# normal -> normal mode
|
||||||
# insert -> insert mode
|
# insert -> insert mode
|
||||||
@@ -223,12 +371,19 @@ vi_mode = false
|
|||||||
|
|
||||||
# Wayland desktop environments
|
# Wayland desktop environments
|
||||||
# You can specify multiple directories,
|
# You can specify multiple directories,
|
||||||
# e.g. /usr/share/wayland-sessions:/usr/local/share/wayland-sessions
|
# e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions
|
||||||
|
# If null, Wayland sessions will not be shown
|
||||||
waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
|
waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
|
||||||
|
|
||||||
# Xorg server command
|
# Xorg server command
|
||||||
|
# Add the -quiet argument to hide startup logs from the server
|
||||||
x_cmd = $PREFIX_DIRECTORY/bin/X
|
x_cmd = $PREFIX_DIRECTORY/bin/X
|
||||||
|
|
||||||
|
# Xorg virtual terminal number
|
||||||
|
# Mostly useful for FreeBSD where choosing the current TTY causes issues
|
||||||
|
# If null, the current TTY will be chosen
|
||||||
|
x_vt = null
|
||||||
|
|
||||||
# Xorg xauthority edition tool
|
# Xorg xauthority edition tool
|
||||||
xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
|
xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
|
||||||
|
|
||||||
@@ -238,5 +393,30 @@ xinitrc = ~/.xinitrc
|
|||||||
|
|
||||||
# Xorg desktop environments
|
# Xorg desktop environments
|
||||||
# You can specify multiple directories,
|
# You can specify multiple directories,
|
||||||
# e.g. /usr/share/xsessions:/usr/local/share/xsessions
|
# e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
|
||||||
|
# If null, X11 sessions will not be shown
|
||||||
xsessions = $PREFIX_DIRECTORY/share/xsessions
|
xsessions = $PREFIX_DIRECTORY/share/xsessions
|
||||||
|
|
||||||
|
# Custom Commands and Labels:
|
||||||
|
# The following examples below give an outline for setting up custom commands and labels.
|
||||||
|
# Unless specified as optional, an option is mandatory.
|
||||||
|
|
||||||
|
# Comments preceding with '##' are for documentation.
|
||||||
|
# Comments preceding with '#' comment out the example INI.
|
||||||
|
|
||||||
|
## Declare a command with the F8 binding.
|
||||||
|
#[cmd:F8]
|
||||||
|
## The name of the command to show up in Ly.
|
||||||
|
## Note: "$" in "$brightness_up" fetches the appropriate string from the specified locale file
|
||||||
|
## and is replaced with the value representing "brightness_up".
|
||||||
|
## You can see the list of keys in any locale file in $CONFIG_DIRECTORY/ly/lang.
|
||||||
|
#cmd = touch /tmp/ly.gaming
|
||||||
|
#name = custom command $brightness_up
|
||||||
|
|
||||||
|
## Declare a label with an ID. This ID should be unique across all labels.
|
||||||
|
#[lbl:kernel]
|
||||||
|
#cmd = uname -srn
|
||||||
|
## Optional, defaulting to 0.
|
||||||
|
## In frames, the time to re-run the command and update the label.
|
||||||
|
## If 0, only run once and do not refresh afterwards
|
||||||
|
#refresh = 0
|
||||||
|
|||||||
23
res/custom-sessions/README
Normal file
23
res/custom-sessions/README
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
A custom session is just a desktop entry file, like for X11 and Wayland
|
||||||
|
sessions. For example:
|
||||||
|
|
||||||
|
[Desktop Entry]
|
||||||
|
Name=Fish shell
|
||||||
|
Exec=$PREFIX_DIRECTORY/bin/fish
|
||||||
|
DesktopNames=null
|
||||||
|
Terminal=true
|
||||||
|
|
||||||
|
The DesktopNames value is optional and sets the XDG_SESSION_DESKTOP and
|
||||||
|
XDG_CURRENT_DESKTOP environment variables. If equal to null or if not present,
|
||||||
|
XDG_SESSION_DESKTOP and XDG_CURRENT_DESKTOP will not be set. Otherwise, the
|
||||||
|
syntax is the same as described in the Freedesktop Desktop Entry Specification.
|
||||||
|
|
||||||
|
The Terminal value specifies if standard output and standard error should be
|
||||||
|
redirected to the session log file found in Ly's configuration file. If set to
|
||||||
|
true, Ly will consider the program is going to run in a TTY, and thus will not
|
||||||
|
redirect standard output & error. It is optional and defaults to false.
|
||||||
|
|
||||||
|
Finally, do note that if the Terminal value is set to true, the
|
||||||
|
XDG_SESSION_TYPE environment variable will be set to "tty". Otherwise, it will
|
||||||
|
be set to "unspecified" (without quotes), which is behavior that at least
|
||||||
|
systemd recognizes (see pam_systemd's man page).
|
||||||
BIN
res/example.dur
Normal file
BIN
res/example.dur
Normal file
Binary file not shown.
@@ -2,17 +2,29 @@ authenticating = جاري المصادقة...
|
|||||||
brightness_down = خفض السطوع
|
brightness_down = خفض السطوع
|
||||||
brightness_up = رفع السطوع
|
brightness_up = رفع السطوع
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = فشل في تخصيص الذاكرة
|
err_alloc = فشل في تخصيص الذاكرة
|
||||||
|
|
||||||
|
|
||||||
err_bounds = out-of-bounds index
|
err_bounds = out-of-bounds index
|
||||||
err_brightness_change = فشل في تغيير سطوع الشاشة
|
err_brightness_change = فشل في تغيير سطوع الشاشة
|
||||||
err_chdir = فشل في فتح مجلد المنزل
|
err_chdir = فشل في فتح مجلد المنزل
|
||||||
|
|
||||||
err_config = فشل في تفسير ملف الإعدادات
|
err_config = فشل في تفسير ملف الإعدادات
|
||||||
err_console_dev = فشل في الوصول إلى جهاز وحدة التحكم
|
|
||||||
err_dgn_oob = رسالة سجل (Log)
|
err_dgn_oob = رسالة سجل (Log)
|
||||||
err_domain = اسم نطاق غير صالح
|
err_domain = اسم نطاق غير صالح
|
||||||
err_empty_password = لا يُسمح بكلمة مرور فارغة
|
err_empty_password = لا يُسمح بكلمة مرور فارغة
|
||||||
err_envlist = فشل في جلب قائمة المتغيرات البيئية
|
err_envlist = فشل في جلب قائمة المتغيرات البيئية
|
||||||
|
|
||||||
|
|
||||||
err_hostname = فشل في جلب اسم المضيف (Hostname)
|
err_hostname = فشل في جلب اسم المضيف (Hostname)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock)
|
err_mlock = فشل في تأمين ذاكرة كلمة المرور (mlock)
|
||||||
err_null = مؤشر فارغ (Null pointer)
|
err_null = مؤشر فارغ (Null pointer)
|
||||||
err_numlock = فشل في ضبط Num Lock
|
err_numlock = فشل في ضبط Num Lock
|
||||||
@@ -38,8 +50,12 @@ err_perm_group = فشل في تخفيض صلاحيات المجموعة (Group p
|
|||||||
err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions)
|
err_perm_user = فشل في تخفيض صلاحيات المستخدم (User permissions)
|
||||||
err_pwnam = فشل في جلب معلومات المستخدم
|
err_pwnam = فشل في جلب معلومات المستخدم
|
||||||
err_sleep = فشل في تنفيذ أمر sleep
|
err_sleep = فشل في تنفيذ أمر sleep
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY)
|
err_tty_ctrl = فشل في نقل تحكم الطرفية (TTY)
|
||||||
err_unknown = حدث خطأ غير معروف
|
|
||||||
|
|
||||||
err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم
|
err_user_gid = فشل في تعيين معرّف المجموعة (GID) للمستخدم
|
||||||
err_user_init = فشل في تهيئة بيانات المستخدم
|
err_user_init = فشل في تهيئة بيانات المستخدم
|
||||||
err_user_uid = فشل في تعيين معرّف المستخدم (UID)
|
err_user_uid = فشل في تعيين معرّف المستخدم (UID)
|
||||||
@@ -47,6 +63,7 @@ err_xauth = فشل في تنفيذ أمر xauth
|
|||||||
err_xcb_conn = فشل في الاتصال بمكتبة XCB
|
err_xcb_conn = فشل في الاتصال بمكتبة XCB
|
||||||
err_xsessions_dir = فشل في العثور على مجلد Xsessions
|
err_xsessions_dir = فشل في العثور على مجلد Xsessions
|
||||||
err_xsessions_open = فشل في فتح مجلد Xsessions
|
err_xsessions_open = فشل في فتح مجلد Xsessions
|
||||||
|
|
||||||
insert = ادخال
|
insert = ادخال
|
||||||
login = تسجيل الدخول
|
login = تسجيل الدخول
|
||||||
logout = تم تسجيل خروجك
|
logout = تم تسجيل خروجك
|
||||||
@@ -59,6 +76,7 @@ restart = اعادة التشغيل
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = ايقاف التشغيل
|
shutdown = ايقاف التشغيل
|
||||||
sleep = وضع السكون
|
sleep = وضع السكون
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
82
res/lang/bg.ini
Normal file
82
res/lang/bg.ini
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
authenticating = удостоверяване...
|
||||||
|
brightness_down = намаляване на яркостта
|
||||||
|
brightness_up = увеличаване на яркостта
|
||||||
|
capslock = caps lock
|
||||||
|
custom = персонализирано
|
||||||
|
custom_info_err_output_long = резултатът е твърде дълъг
|
||||||
|
custom_info_err_no_output = няма резултат
|
||||||
|
custom_info_err_no_output_error = , възможна грешка
|
||||||
|
err_alloc = неуспешно заделяне на памет
|
||||||
|
err_args = неуспешен анализ на аргументите от командния ред
|
||||||
|
err_autologin_session = сесията за автоматично влизане не е намерена
|
||||||
|
err_bounds = индексът е извън границите
|
||||||
|
err_brightness_change = неуспешна промяна на яркостта
|
||||||
|
err_chdir = неуспешно отваряне на домашната папка
|
||||||
|
err_clock_too_long = низът на часовника е твърде дълъг
|
||||||
|
err_config = неуспешен анализ на конфигурационния файл
|
||||||
|
err_crawl = неуспешно обхождане на папките със сесии
|
||||||
|
err_dgn_oob = съобщение в дневника
|
||||||
|
err_domain = невалиден домейн
|
||||||
|
err_empty_password = не е позволена празна парола
|
||||||
|
err_envlist = неуспешно получаване на списъка с променливи на средата
|
||||||
|
err_get_active_tty = неуспешно откриване на активния TTY
|
||||||
|
err_hibernate = неуспешно изпълнение на командата за хибернация
|
||||||
|
err_hostname = неуспешно получаване на името на хоста
|
||||||
|
err_inactivity = неуспешно изпълнение на командата за неактивност
|
||||||
|
err_lock_state = неуспешно получаване на състоянието на заключване
|
||||||
|
err_log = неуспешно отваряне на файла с дневника
|
||||||
|
err_mlock = неуспешно заключване на паметта за паролата
|
||||||
|
err_null = нулев указател
|
||||||
|
err_numlock = неуспешно задаване на num lock
|
||||||
|
err_pam = неуспешна транзакция
|
||||||
|
err_pam_abort = прекратена транзакция
|
||||||
|
err_pam_acct_expired = изтекъл профил
|
||||||
|
err_pam_auth = грешка при удостоверяването
|
||||||
|
err_pam_authinfo_unavail = неуспешно получаване на информация за потребителя
|
||||||
|
err_pam_authok_reqd = изтекъл жетон
|
||||||
|
err_pam_buf = грешка в буфера на паметта
|
||||||
|
err_pam_cred_err = неуспешно задаване на удостоверения
|
||||||
|
err_pam_cred_expired = изтекли удостоверения
|
||||||
|
err_pam_cred_insufficient = недостатъчни удостоверения
|
||||||
|
err_pam_cred_unavail = неуспешно получаване на удостоверения
|
||||||
|
err_pam_maxtries = достигнат е максималният лимит на опитите
|
||||||
|
err_pam_perm_denied = достъпът е отказан
|
||||||
|
err_pam_session = грешка в сесията
|
||||||
|
err_pam_sys = системна грешка
|
||||||
|
err_pam_user_unknown = непознат потребител
|
||||||
|
err_path = неуспешно задаване на пътя
|
||||||
|
err_perm_dir = неуспешна смяна на текущата папка
|
||||||
|
err_perm_group = неуспешно понижаване на правата на групата
|
||||||
|
err_perm_user = неуспешно понижаване на правата на потребителя
|
||||||
|
err_pwnam = неуспешно получаване на информация за потребителя
|
||||||
|
err_sleep = неуспешно изпълнение на командата за заспиване
|
||||||
|
err_start = неуспешно изпълнение на командата за стартиране
|
||||||
|
err_battery = неуспешно зареждане на състоянието на батерията
|
||||||
|
err_switch_tty = неуспешна смяна на TTY
|
||||||
|
err_tty_ctrl = неуспешно прехвърляне на контрола над TTY
|
||||||
|
err_no_users = не са намерени потребители
|
||||||
|
err_uid_range = неуспешно динамично получаване на UID обхват
|
||||||
|
err_user_gid = неуспешно задаване на потребителския GID
|
||||||
|
err_user_init = неуспешна стартиране на потребителя
|
||||||
|
err_user_uid = неуспешно задаване на потребителския UID
|
||||||
|
err_xauth = неуспешна команда xauth
|
||||||
|
err_xcb_conn = неуспешна xcb връзка
|
||||||
|
err_xsessions_dir = папката със сесии не е намерена
|
||||||
|
err_xsessions_open = неуспешно отваряне на папката със сесии
|
||||||
|
hibernate = хибернация
|
||||||
|
insert = вмъкване
|
||||||
|
login = вход
|
||||||
|
logout = излизане
|
||||||
|
no_x11_support = поддръжката на x11 е изключена при компилирането
|
||||||
|
normal = нормално
|
||||||
|
numlock = num lock
|
||||||
|
other = друго
|
||||||
|
password = парола
|
||||||
|
restart = рестартиране
|
||||||
|
shell = обвивка
|
||||||
|
shutdown = изключване
|
||||||
|
sleep = заспиване
|
||||||
|
toggle_password = превключване на паролата
|
||||||
|
wayland = wayland
|
||||||
|
x11 = x11
|
||||||
|
xinitrc = xinitrc
|
||||||
@@ -2,17 +2,29 @@ authenticating = autenticant...
|
|||||||
brightness_down = abaixar brillantor
|
brightness_down = abaixar brillantor
|
||||||
brightness_up = apujar brillantor
|
brightness_up = apujar brillantor
|
||||||
capslock = Bloq Majús
|
capslock = Bloq Majús
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = assignació de memòria fallida
|
err_alloc = assignació de memòria fallida
|
||||||
|
|
||||||
|
|
||||||
err_bounds = índex fora de límits
|
err_bounds = índex fora de límits
|
||||||
err_brightness_change = error en canviar la brillantor
|
err_brightness_change = error en canviar la brillantor
|
||||||
err_chdir = error en obrir la carpeta home
|
err_chdir = error en obrir la carpeta home
|
||||||
|
|
||||||
err_console_dev = error en accedir a la consola
|
|
||||||
|
|
||||||
err_dgn_oob = missatge de registre
|
err_dgn_oob = missatge de registre
|
||||||
err_domain = domini invàlid
|
err_domain = domini invàlid
|
||||||
|
|
||||||
err_envlist = error en obtenir l'envlist
|
err_envlist = error en obtenir l'envlist
|
||||||
|
|
||||||
|
|
||||||
err_hostname = error en obtenir el nom de l'amfitrió
|
err_hostname = error en obtenir el nom de l'amfitrió
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = error en bloquejar la memòria de clau
|
err_mlock = error en bloquejar la memòria de clau
|
||||||
err_null = punter nul
|
err_null = punter nul
|
||||||
err_numlock = error en establir el Bloq num
|
err_numlock = error en establir el Bloq num
|
||||||
@@ -39,7 +51,11 @@ err_perm_user = error en degradar els permisos de l'usuari
|
|||||||
err_pwnam = error en obtenir la informació de l'usuari
|
err_pwnam = error en obtenir la informació de l'usuari
|
||||||
|
|
||||||
|
|
||||||
err_unknown = ha ocorregut un error desconegut
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = error en establir el GID de l'usuari
|
err_user_gid = error en establir el GID de l'usuari
|
||||||
err_user_init = error en inicialitzar usuari
|
err_user_init = error en inicialitzar usuari
|
||||||
err_user_uid = error en establir l'UID de l'usuari
|
err_user_uid = error en establir l'UID de l'usuari
|
||||||
@@ -47,6 +63,7 @@ err_xauth = error en la comanda xauth
|
|||||||
err_xcb_conn = error en la connexió xcb
|
err_xcb_conn = error en la connexió xcb
|
||||||
err_xsessions_dir = error en trobar la carpeta de sessions
|
err_xsessions_dir = error en trobar la carpeta de sessions
|
||||||
err_xsessions_open = error en obrir la carpeta de sessions
|
err_xsessions_open = error en obrir la carpeta de sessions
|
||||||
|
|
||||||
insert = inserir
|
insert = inserir
|
||||||
login = iniciar sessió
|
login = iniciar sessió
|
||||||
logout = sessió tancada
|
logout = sessió tancada
|
||||||
@@ -59,6 +76,7 @@ restart = reiniciar
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = aturar
|
shutdown = aturar
|
||||||
sleep = suspendre
|
sleep = suspendre
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = alokace paměti selhala
|
err_alloc = alokace paměti selhala
|
||||||
|
|
||||||
|
|
||||||
err_bounds = index je mimo hranice pole
|
err_bounds = index je mimo hranice pole
|
||||||
|
|
||||||
err_chdir = nelze otevřít domovský adresář
|
err_chdir = nelze otevřít domovský adresář
|
||||||
|
|
||||||
err_console_dev = chyba při přístupu do konzole
|
|
||||||
|
|
||||||
err_dgn_oob = zpráva protokolu
|
err_dgn_oob = zpráva protokolu
|
||||||
err_domain = neplatná doména
|
err_domain = neplatná doména
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = nelze získat název hostitele
|
err_hostname = nelze získat název hostitele
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = uzamčení paměti hesel selhalo
|
err_mlock = uzamčení paměti hesel selhalo
|
||||||
err_null = nulový ukazatel
|
err_null = nulový ukazatel
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = nelze získat informace o uživateli
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = nastavení GID uživatele selhalo
|
err_user_gid = nastavení GID uživatele selhalo
|
||||||
err_user_init = inicializace uživatele selhala
|
err_user_init = inicializace uživatele selhala
|
||||||
err_user_uid = nastavení UID uživateli selhalo
|
err_user_uid = nastavení UID uživateli selhalo
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = nastavení UID uživateli selhalo
|
|||||||
err_xsessions_dir = nepodařilo se najít složku relací
|
err_xsessions_dir = nepodařilo se najít složku relací
|
||||||
err_xsessions_open = nepodařilo se otevřít složku relací
|
err_xsessions_open = nepodařilo se otevřít složku relací
|
||||||
|
|
||||||
|
|
||||||
login = uživatel
|
login = uživatel
|
||||||
logout = odhlášen
|
logout = odhlášen
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = restartovat
|
|||||||
shell = příkazový řádek
|
shell = příkazový řádek
|
||||||
shutdown = vypnout
|
shutdown = vypnout
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -1,64 +1,82 @@
|
|||||||
|
authenticating = authentifizieren...
|
||||||
|
brightness_down = Helligkeit-
|
||||||
|
brightness_up = Helligkeit+
|
||||||
capslock = Feststelltaste
|
capslock = Feststelltaste
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = Speicherzuweisung fehlgeschlagen
|
err_alloc = Speicherzuweisung fehlgeschlagen
|
||||||
err_bounds = Listenindex ist außerhalb des Bereichs
|
|
||||||
|
|
||||||
err_chdir = Fehler beim oeffnen des home-ordners
|
|
||||||
|
|
||||||
err_console_dev = Zugriff auf die Konsole fehlgeschlagen
|
|
||||||
err_dgn_oob = Protokoll Nachricht
|
|
||||||
err_domain = Unzulaessige domain
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = Holen des Hostnames fehlgeschlagen
|
err_bounds = Index ausserhalb des Bereichs
|
||||||
err_mlock = Abschließen des Passwortspeichers fehlgeschlagen
|
err_brightness_change = Helligkeitsänderung fehlgeschlagen
|
||||||
err_null = Null Zeiger
|
err_chdir = Fehler beim Oeffnen des Home-Ordners
|
||||||
|
|
||||||
err_pam = pam Transaktion fehlgeschlagen
|
err_config = Fehler beim Verarbeiten der Konfigurationsdatei
|
||||||
err_pam_abort = pam Transaktion abgebrochen
|
|
||||||
|
err_dgn_oob = Diagnose-Nachricht
|
||||||
|
err_domain = Ungueltige Domain
|
||||||
|
err_empty_password = Leeres Passwort nicht zugelassen
|
||||||
|
err_envlist = Fehler beim Abrufen der Umgebungs-Variablen
|
||||||
|
|
||||||
|
|
||||||
|
err_hostname = Abrufen des Hostnames fehlgeschlagen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
err_mlock = Sperren des Passwortspeichers fehlgeschlagen
|
||||||
|
err_null = Null Pointer
|
||||||
|
err_numlock = Numlock konnte nicht aktiviert werden
|
||||||
|
err_pam = PAM-Transaktion fehlgeschlagen
|
||||||
|
err_pam_abort = PAM-Transaktion abgebrochen
|
||||||
err_pam_acct_expired = Benutzerkonto abgelaufen
|
err_pam_acct_expired = Benutzerkonto abgelaufen
|
||||||
err_pam_auth = Authentifizierungs Fehler
|
err_pam_auth = Authentifizierungsfehler
|
||||||
err_pam_authinfo_unavail = holen der Benutzerinformationen fehlgeschlagen
|
err_pam_authinfo_unavail = Abrufen der Benutzerinformationen fehlgeschlagen
|
||||||
err_pam_authok_reqd = Schluessel abgelaufen
|
err_pam_authok_reqd = Passwort abgelaufen
|
||||||
err_pam_buf = Speicherpufferfehler
|
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_expired = Anmeldedaten abgelaufen
|
||||||
err_pam_cred_insufficient = Anmeldedaten unzureichend
|
err_pam_cred_insufficient = Anmeldedaten unzureichend
|
||||||
err_pam_cred_unavail = Fehler beim holen der Anmeldedaten
|
err_pam_cred_unavail = Fehler beim Abrufen der Anmeldedaten
|
||||||
err_pam_maxtries = Maximale Versuche erreicht
|
err_pam_maxtries = Maximale Versuchsanzahl erreicht
|
||||||
err_pam_perm_denied = Zugriff Verweigert
|
err_pam_perm_denied = Zugriff verweigert
|
||||||
err_pam_session = Sitzungsfehler
|
err_pam_session = Sitzungsfehler
|
||||||
err_pam_sys = Systemfehler
|
err_pam_sys = Systemfehler
|
||||||
err_pam_user_unknown = Unbekannter Nutzer
|
err_pam_user_unknown = Unbekannter Nutzer
|
||||||
err_path = Fehler beim setzen des Pfades
|
err_path = Fehler beim Setzen des Pfades
|
||||||
err_perm_dir = Fehler beim wechseln des Ordners
|
err_perm_dir = Ordnerwechsel fehlgeschlagen
|
||||||
err_perm_group = Fehler beim heruntersetzen der Gruppen Berechtigungen
|
err_perm_group = Fehler beim Heruntersetzen der Gruppenberechtigungen
|
||||||
err_perm_user = Fehler beim heruntersetzen der Nutzer Berechtigungen
|
err_perm_user = Fehler beim Heruntersetzen der Nutzerberechtigungen
|
||||||
err_pwnam = Holen der Benutzerinformationen fehlgeschlagen
|
err_pwnam = Abrufen der Benutzerinformationen fehlgeschlagen
|
||||||
|
err_sleep = Sleep-Befehl fehlgeschlagen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = Fehler beim setzen der Gruppen Id des Nutzers
|
err_tty_ctrl = Fehler bei der TTY-Uebergabe
|
||||||
err_user_init = Initialisierung des Nutzers fehlgeschlagen
|
|
||||||
err_user_uid = Setzen der Benutzer Id fehlgeschlagen
|
|
||||||
|
|
||||||
|
|
||||||
err_xsessions_dir = Fehler beim finden des Sitzungsordners
|
err_user_gid = Fehler beim Setzen der Gruppen-ID
|
||||||
err_xsessions_open = Fehler beim öffnen des Sitzungsordners
|
err_user_init = Nutzer-Initialisierung fehlgeschlagen
|
||||||
|
err_user_uid = Setzen der Benutzer-ID fehlgeschlagen
|
||||||
login = Anmelden
|
err_xauth = Xauth-Befehl fehlgeschlagen
|
||||||
logout = Abgemeldet
|
err_xcb_conn = xcb-Verbindung fehlgeschlagen
|
||||||
|
err_xsessions_dir = Fehler beim Finden des Sitzungsordners
|
||||||
|
err_xsessions_open = Fehler beim Oeffnen des Sitzungsordners
|
||||||
numlock = Numtaste
|
|
||||||
|
|
||||||
|
insert = Einfügen
|
||||||
|
login = Nutzer
|
||||||
|
logout = Abmelden
|
||||||
|
no_x11_support = X11-Support bei Kompilierung deaktiviert
|
||||||
|
normal = Normal
|
||||||
|
numlock = Numlock
|
||||||
|
other = Andere
|
||||||
password = Passwort
|
password = Passwort
|
||||||
restart = Neustarten
|
restart = Neustarten
|
||||||
shell = shell
|
shell = Shell
|
||||||
shutdown = Herunterfahren
|
shutdown = Herunterfahren
|
||||||
|
sleep = Sleep
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
x11 = X11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@ authenticating = authenticating...
|
|||||||
brightness_down = decrease brightness
|
brightness_down = decrease brightness
|
||||||
brightness_up = increase brightness
|
brightness_up = increase brightness
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
custom = custom
|
||||||
|
custom_info_err_output_long = output too long
|
||||||
|
custom_info_err_no_output = no output
|
||||||
|
custom_info_err_no_output_error = , possible error
|
||||||
err_alloc = failed memory allocation
|
err_alloc = failed memory allocation
|
||||||
|
err_args = unable to parse command line arguments
|
||||||
|
err_autologin_session = autologin session not found
|
||||||
err_bounds = out-of-bounds index
|
err_bounds = out-of-bounds index
|
||||||
err_brightness_change = failed to change brightness
|
err_brightness_change = failed to change brightness
|
||||||
err_chdir = failed to open home folder
|
err_chdir = failed to open home folder
|
||||||
|
err_clock_too_long = clock string too long
|
||||||
err_config = unable to parse config file
|
err_config = unable to parse config file
|
||||||
err_console_dev = failed to access console
|
err_crawl = failed to crawl session directories
|
||||||
err_dgn_oob = log message
|
err_dgn_oob = log message
|
||||||
err_domain = invalid domain
|
err_domain = invalid domain
|
||||||
err_empty_password = empty password not allowed
|
err_empty_password = empty password not allowed
|
||||||
err_envlist = failed to get envlist
|
err_envlist = failed to get envlist
|
||||||
|
err_get_active_tty = failed to get active tty
|
||||||
|
err_hibernate = failed to execute hibernate command
|
||||||
err_hostname = failed to get hostname
|
err_hostname = failed to get hostname
|
||||||
|
err_inactivity = failed to execute inactivity command
|
||||||
|
err_lock_state = failed to get lock state
|
||||||
|
err_log = failed to open log file
|
||||||
err_mlock = failed to lock password memory
|
err_mlock = failed to lock password memory
|
||||||
err_null = null pointer
|
err_null = null pointer
|
||||||
err_numlock = failed to set numlock
|
err_numlock = failed to set numlock
|
||||||
@@ -38,8 +50,12 @@ err_perm_group = failed to downgrade group permissions
|
|||||||
err_perm_user = failed to downgrade user permissions
|
err_perm_user = failed to downgrade user permissions
|
||||||
err_pwnam = failed to get user info
|
err_pwnam = failed to get user info
|
||||||
err_sleep = failed to execute sleep command
|
err_sleep = failed to execute sleep command
|
||||||
|
err_start = failed to execute start command
|
||||||
|
err_battery = failed to load battery status
|
||||||
|
err_switch_tty = failed to switch tty
|
||||||
err_tty_ctrl = tty control transfer failed
|
err_tty_ctrl = tty control transfer failed
|
||||||
err_unknown = an unknown error occurred
|
err_no_users = no users found
|
||||||
|
err_uid_range = failed to dynamically get uid range
|
||||||
err_user_gid = failed to set user GID
|
err_user_gid = failed to set user GID
|
||||||
err_user_init = failed to initialize user
|
err_user_init = failed to initialize user
|
||||||
err_user_uid = failed to set user UID
|
err_user_uid = failed to set user UID
|
||||||
@@ -47,6 +63,7 @@ err_xauth = xauth command failed
|
|||||||
err_xcb_conn = xcb connection failed
|
err_xcb_conn = xcb connection failed
|
||||||
err_xsessions_dir = failed to find sessions folder
|
err_xsessions_dir = failed to find sessions folder
|
||||||
err_xsessions_open = failed to open sessions folder
|
err_xsessions_open = failed to open sessions folder
|
||||||
|
hibernate = hibernate
|
||||||
insert = insert
|
insert = insert
|
||||||
login = login
|
login = login
|
||||||
logout = logged out
|
logout = logged out
|
||||||
@@ -59,6 +76,7 @@ restart = reboot
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = shutdown
|
shutdown = shutdown
|
||||||
sleep = sleep
|
sleep = sleep
|
||||||
|
toggle_password = toggle password
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
82
res/lang/eo.ini
Normal file
82
res/lang/eo.ini
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
authenticating = aŭtentigado...
|
||||||
|
brightness_down = malpliigi helecon
|
||||||
|
brightness_up = pliigi helecon
|
||||||
|
capslock = majuskla baskulo
|
||||||
|
custom = propra
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
err_alloc = malsukcesis memorasignon
|
||||||
|
err_args = ne povas analizi argumentojn de komanda linio
|
||||||
|
err_autologin_session = aŭtomatan ensalutan seancon ne trovis
|
||||||
|
err_bounds = indico estas ekster-intervala
|
||||||
|
err_brightness_change = malsukcesis ŝanĝi la helecon
|
||||||
|
err_chdir = malsukcesis malfermi hejman dosierujon
|
||||||
|
err_clock_too_long = horloĝa ĉeno estas tro longa
|
||||||
|
err_config = ne povas analizi agordan dosieron
|
||||||
|
err_crawl = malsukcesis dum serĉado de seancaj dosierujoj
|
||||||
|
err_dgn_oob = protokola mesaĝo
|
||||||
|
err_domain = malvalida domajno
|
||||||
|
err_empty_password = ne akceptas malplenan pasvorton
|
||||||
|
err_envlist = malsukcesis preni la medivariablojn
|
||||||
|
err_get_active_tty = malsukcesis preni la aktivan TTY-on
|
||||||
|
err_hibernate = malsukcesis ruli la komandon por diskodormo
|
||||||
|
err_hostname = malsukcesis preni la sistemnomon
|
||||||
|
err_inactivity = malsukcesis ruli la agorditan komandon por malaktiveco
|
||||||
|
err_lock_state = malsukcesis preni la ŝlosan staton
|
||||||
|
err_log = malsukcesis malfermi la protokolan dosieron
|
||||||
|
err_mlock = malsukcesis ŝlosi pasvortan memoron
|
||||||
|
err_null = nula memorloko
|
||||||
|
err_numlock = malsukcesis agordi numeran baskulon
|
||||||
|
err_pam = PAM-a transakcio malsukcesis
|
||||||
|
err_pam_abort = PAM-a transakcio malsukcesis
|
||||||
|
err_pam_acct_expired = konto eksvalidiĝis
|
||||||
|
err_pam_auth = aŭtentiga eraro
|
||||||
|
err_pam_authinfo_unavail = malsukcesis preni uzantajn informojn
|
||||||
|
err_pam_authok_reqd = memorsigno eksvalidiĝis
|
||||||
|
err_pam_buf = bufra eraro
|
||||||
|
err_pam_cred_err = malsukcesis agordi akreditaĵon
|
||||||
|
err_pam_cred_expired = akreditaĵo eksvalidiĝis
|
||||||
|
err_pam_cred_insufficient = nesufiĉa akreditaĵo
|
||||||
|
err_pam_cred_unavail = malsukcesis preni akreditaĵon
|
||||||
|
err_pam_maxtries = atingis maksimuman kvanton da provoj
|
||||||
|
err_pam_perm_denied = permeso negis
|
||||||
|
err_pam_session = seancan eraron
|
||||||
|
err_pam_sys = sisteman eraron
|
||||||
|
err_pam_user_unknown = ne konas uzanton
|
||||||
|
err_path = malsukcesis agordi la median dosierindikon
|
||||||
|
err_perm_dir = malsukcesis ŝanĝi la nunan dosierujon
|
||||||
|
err_perm_group = malsukcesis redukti grupajn permesojn
|
||||||
|
err_perm_user = malsukcesis redukti uzantajn permesojn
|
||||||
|
err_pwnam = malsukcesis preni uzantajn informojn
|
||||||
|
err_sleep = malsukcesis ruli memordorman komandon
|
||||||
|
err_start = malsukcesis ruli startan komandon
|
||||||
|
err_battery = malsukcesis ŝargi baterian staton
|
||||||
|
err_switch_tty = malsukcesis ŝanĝi TTY-on
|
||||||
|
err_tty_ctrl = TTY-an stiran transigon malsukcesis
|
||||||
|
err_no_users = nul uzantojn trovas
|
||||||
|
err_uid_range = malsukcesis dinamike preni UID-an intervalon
|
||||||
|
err_user_gid = malsukcesis agordi uzantan GID-on
|
||||||
|
err_user_init = malsukcesis iniciĝi uzanto
|
||||||
|
err_user_uid = malsukcesis agordi uzantan UID-on
|
||||||
|
err_xauth = malsukcesis plenumi je xauth
|
||||||
|
err_xcb_conn = malsukcesis dum konectado al xcb
|
||||||
|
err_xsessions_dir = malsukcesis trovi seancan dosierujon
|
||||||
|
err_xsessions_open = malsukcesis malfermi seancan dosierujon
|
||||||
|
hibernate = diskodormi
|
||||||
|
insert = enmeti
|
||||||
|
login = uzanto
|
||||||
|
logout = elsalutis
|
||||||
|
no_x11_support = x11 estas foriĝita de kompil-tempo
|
||||||
|
normal = normala
|
||||||
|
numlock = numera baskulo
|
||||||
|
other = alia
|
||||||
|
password = pasvorto
|
||||||
|
restart = restartigi
|
||||||
|
shell = ŝelo
|
||||||
|
shutdown = malŝalti
|
||||||
|
sleep = memordormi
|
||||||
|
|
||||||
|
wayland = wayland
|
||||||
|
x11 = x11
|
||||||
|
xinitrc = xinitrc
|
||||||
@@ -2,17 +2,29 @@ authenticating = autenticando...
|
|||||||
brightness_down = bajar brillo
|
brightness_down = bajar brillo
|
||||||
brightness_up = subir brillo
|
brightness_up = subir brillo
|
||||||
capslock = Bloq Mayús
|
capslock = Bloq Mayús
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = asignación de memoria fallida
|
err_alloc = asignación de memoria fallida
|
||||||
|
|
||||||
|
|
||||||
err_bounds = índice fuera de límites
|
err_bounds = índice fuera de límites
|
||||||
|
|
||||||
err_chdir = error al abrir la carpeta home
|
err_chdir = error al abrir la carpeta home
|
||||||
|
|
||||||
err_console_dev = error al acceder a la consola
|
|
||||||
|
|
||||||
err_dgn_oob = mensaje de registro
|
err_dgn_oob = mensaje de registro
|
||||||
err_domain = dominio inválido
|
err_domain = dominio inválido
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = error al obtener el nombre de host
|
err_hostname = error al obtener el nombre de host
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = error al bloquear la contraseña de memoria
|
err_mlock = error al bloquear la contraseña de memoria
|
||||||
err_null = puntero nulo
|
err_null = puntero nulo
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = error al obtener la información del usuario
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = error al establecer el GID del usuario
|
err_user_gid = error al establecer el GID del usuario
|
||||||
err_user_init = error al inicializar usuario
|
err_user_init = error al inicializar usuario
|
||||||
err_user_uid = error al establecer el UID del usuario
|
err_user_uid = error al establecer el UID del usuario
|
||||||
@@ -47,6 +63,7 @@ err_user_uid = error al establecer el UID del usuario
|
|||||||
|
|
||||||
err_xsessions_dir = error al buscar la carpeta de sesiones
|
err_xsessions_dir = error al buscar la carpeta de sesiones
|
||||||
err_xsessions_open = error al abrir la carpeta de sesiones
|
err_xsessions_open = error al abrir la carpeta de sesiones
|
||||||
|
|
||||||
insert = insertar
|
insert = insertar
|
||||||
login = usuario
|
login = usuario
|
||||||
logout = cerrar sesión
|
logout = cerrar sesión
|
||||||
@@ -59,6 +76,7 @@ restart = reiniciar
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = apagar
|
shutdown = apagar
|
||||||
sleep = suspender
|
sleep = suspender
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@ authenticating = authentification...
|
|||||||
brightness_down = diminuer la luminosité
|
brightness_down = diminuer la luminosité
|
||||||
brightness_up = augmenter la luminosité
|
brightness_up = augmenter la luminosité
|
||||||
capslock = verr.maj
|
capslock = verr.maj
|
||||||
|
custom = customisé
|
||||||
|
custom_info_err_output_long = sortie trop longue
|
||||||
|
custom_info_err_no_output = pas de sortie
|
||||||
|
custom_info_err_no_output_error = , erreur possible
|
||||||
err_alloc = échec d'allocation mémoire
|
err_alloc = échec d'allocation mémoire
|
||||||
|
err_args = échec de l'analyse des arguments en lignes de commande
|
||||||
|
err_autologin_session = session de connexion automatique introuvable
|
||||||
err_bounds = indice hors-limite
|
err_bounds = indice hors-limite
|
||||||
err_brightness_change = échec du changement de luminosité
|
err_brightness_change = échec du changement de luminosité
|
||||||
err_chdir = échec de l'ouverture du répertoire home
|
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_config = échec de lecture du fichier de configuration
|
||||||
err_console_dev = échec d'accès à la console
|
err_crawl = échec de la navigation des répertoires de session
|
||||||
err_dgn_oob = message
|
err_dgn_oob = message
|
||||||
err_domain = domaine invalide
|
err_domain = domaine invalide
|
||||||
err_empty_password = mot de passe vide non autorisé
|
err_empty_password = mot de passe vide non autorisé
|
||||||
err_envlist = échec de lecture de la liste d'environnement
|
err_envlist = échec de lecture de la liste d'environnement
|
||||||
|
err_get_active_tty = échec de lecture du terminal actif
|
||||||
|
err_hibernate = échec de l'exécution de la commande de veille prolongée
|
||||||
err_hostname = échec de lecture du nom d'hôte
|
err_hostname = échec de lecture du nom d'hôte
|
||||||
|
err_inactivity = échec de l'exécution de la commande d'inactivité
|
||||||
|
err_lock_state = échec de lecture de l'état de verrouillage
|
||||||
|
err_log = échec de l'ouverture du fichier de journal
|
||||||
err_mlock = échec du verrouillage mémoire
|
err_mlock = échec du verrouillage mémoire
|
||||||
err_null = pointeur null
|
err_null = pointeur null
|
||||||
err_numlock = échec de modification du verr.num
|
err_numlock = échec de modification du verr.num
|
||||||
@@ -38,8 +50,12 @@ err_perm_group = échec du déclassement des permissions de groupe
|
|||||||
err_perm_user = échec du déclassement des permissions utilisateur
|
err_perm_user = échec du déclassement des permissions utilisateur
|
||||||
err_pwnam = échec de lecture des infos utilisateur
|
err_pwnam = échec de lecture des infos utilisateur
|
||||||
err_sleep = échec de l'exécution de la commande de veille
|
err_sleep = échec de l'exécution de la commande de veille
|
||||||
|
err_start = échec de l'exécution de la commande de démarrage
|
||||||
|
err_battery = échec de lecture de l'état de la batterie
|
||||||
|
err_switch_tty = échec du changement de terminal
|
||||||
err_tty_ctrl = échec du transfert de contrôle du terminal
|
err_tty_ctrl = échec du transfert de contrôle du terminal
|
||||||
err_unknown = une erreur inconnue est survenue
|
err_no_users = aucun utilisateur trouvé
|
||||||
|
err_uid_range = échec de récupération dynamique de la plage d'UID
|
||||||
err_user_gid = échec de modification du GID
|
err_user_gid = échec de modification du GID
|
||||||
err_user_init = échec d'initialisation de l'utilisateur
|
err_user_init = échec d'initialisation de l'utilisateur
|
||||||
err_user_uid = échec de modification du UID
|
err_user_uid = échec de modification du UID
|
||||||
@@ -47,6 +63,7 @@ err_xauth = échec de la commande xauth
|
|||||||
err_xcb_conn = échec de la connexion xcb
|
err_xcb_conn = échec de la connexion xcb
|
||||||
err_xsessions_dir = échec de la recherche du dossier de sessions
|
err_xsessions_dir = échec de la recherche du dossier de sessions
|
||||||
err_xsessions_open = échec de l'ouverture du dossier de sessions
|
err_xsessions_open = échec de l'ouverture du dossier de sessions
|
||||||
|
hibernate = veille prolongée
|
||||||
insert = insertion
|
insert = insertion
|
||||||
login = identifiant
|
login = identifiant
|
||||||
logout = déconnecté
|
logout = déconnecté
|
||||||
@@ -59,6 +76,7 @@ restart = redémarrer
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = éteindre
|
shutdown = éteindre
|
||||||
sleep = veille
|
sleep = veille
|
||||||
|
toggle_password = afficher le mot de passe
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = impossibile allocare memoria
|
err_alloc = impossibile allocare memoria
|
||||||
|
|
||||||
|
|
||||||
err_bounds = indice fuori limite
|
err_bounds = indice fuori limite
|
||||||
|
|
||||||
err_chdir = impossibile aprire home directory
|
err_chdir = impossibile aprire home directory
|
||||||
|
|
||||||
err_console_dev = impossibile aprire console
|
|
||||||
|
|
||||||
err_dgn_oob = messaggio log
|
err_dgn_oob = messaggio log
|
||||||
err_domain = dominio non valido
|
err_domain = dominio non valido
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = impossibile ottenere hostname
|
err_hostname = impossibile ottenere hostname
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = impossibile ottenere lock per la password in memoria
|
err_mlock = impossibile ottenere lock per la password in memoria
|
||||||
err_null = puntatore nullo
|
err_null = puntatore nullo
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = impossibile ottenere dati utente
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = impossibile impostare GID utente
|
err_user_gid = impossibile impostare GID utente
|
||||||
err_user_init = impossibile inizializzare utente
|
err_user_init = impossibile inizializzare utente
|
||||||
err_user_uid = impossible impostare UID utente
|
err_user_uid = impossible impostare UID utente
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = impossible impostare UID utente
|
|||||||
err_xsessions_dir = impossibile localizzare cartella sessioni
|
err_xsessions_dir = impossibile localizzare cartella sessioni
|
||||||
err_xsessions_open = impossibile aprire cartella sessioni
|
err_xsessions_open = impossibile aprire cartella sessioni
|
||||||
|
|
||||||
|
|
||||||
login = username
|
login = username
|
||||||
logout = scollegato
|
logout = scollegato
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = riavvio
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = arresto
|
shutdown = arresto
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
82
res/lang/ja_JP.ini
Normal file
82
res/lang/ja_JP.ini
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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
|
||||||
82
res/lang/ku.ini
Normal file
82
res/lang/ku.ini
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
authenticating = tê piştrastkirin...
|
||||||
|
brightness_down = ronahiyê kêm bike
|
||||||
|
brightness_up = ronahiyê bilind bike
|
||||||
|
capslock = tîpên girdek (capslock)
|
||||||
|
custom = kesane
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
err_alloc = veqetandina bîrê têk çû
|
||||||
|
err_args = argumanên rêzika fermanê nehatin analîzkirin
|
||||||
|
err_autologin_session = danişîna têketina xweber nehate dîtin
|
||||||
|
err_bounds = îndeksa derveyî sînor
|
||||||
|
err_brightness_change = guherandina ronahiyê têk çû
|
||||||
|
err_chdir = vekirina peldanka malê têk çû
|
||||||
|
err_clock_too_long = rêzika demjimêrê pir dirêj e
|
||||||
|
err_config = pela rêkxistinê nehat analîzkirin
|
||||||
|
err_crawl = gerandina pelrêçên danişînê têk çû
|
||||||
|
err_dgn_oob = peyama têketinê
|
||||||
|
err_domain = navpara nederbasdar
|
||||||
|
err_empty_password = borînpeyv nabe ku vala be
|
||||||
|
err_envlist = girtina lîsteya jîngehê (envlist) têk çû
|
||||||
|
err_get_active_tty = girtina tty ya çalak têk çû
|
||||||
|
err_hibernate = fermana cemidaninê nehat xebitandin
|
||||||
|
err_hostname = girtina navê mêvandar têk çû
|
||||||
|
err_inactivity = fermana neçalaktiyê nehat xebitandin
|
||||||
|
err_lock_state = girtina rewşa kilîtkirinê têk çû
|
||||||
|
err_log = vekirina pelê têkeinê têk çû
|
||||||
|
err_mlock = kilîtkirina bîra borînpeyvê têk çû
|
||||||
|
err_null = nîşandera null
|
||||||
|
err_numlock = sazkirina numlock têk çû
|
||||||
|
err_pam = danûstendina pam têk çû
|
||||||
|
err_pam_abort = danûstendina pam hate têkbirin
|
||||||
|
err_pam_acct_expired = dema jimarê derbas bûye
|
||||||
|
err_pam_auth = şaşetiya piştrastkirinê
|
||||||
|
err_pam_authinfo_unavail = zanyariyên bikarhêner nehatin girtin
|
||||||
|
err_pam_authok_reqd = dema nîşandanê derbas bûye
|
||||||
|
err_pam_buf = şaşetiya bîra demkî
|
||||||
|
err_pam_cred_err = sazkirina rastkitinê têk çû
|
||||||
|
err_pam_cred_expired = dema rastkitinê derbas bûye
|
||||||
|
err_pam_cred_insufficient = rastkitinê kêm
|
||||||
|
err_pam_cred_unavail = girtina rastkitinê têk çû
|
||||||
|
err_pam_maxtries = sînorê hewldanên herî bilind hat gihîştin
|
||||||
|
err_pam_perm_denied = mafdayîn hat paşguhkirin
|
||||||
|
err_pam_session = şaşetiya danişînê
|
||||||
|
err_pam_sys = şaşetiya pergalê
|
||||||
|
err_pam_user_unknown = bikarhênerê nenas
|
||||||
|
err_path = sazkirina rêgehê têk çû
|
||||||
|
err_perm_dir = guhertina pelrêçê heyî têk çû
|
||||||
|
err_perm_group = kêmkirina mafdayînên komê têk çû
|
||||||
|
err_perm_user = kêmkirina mafdayînên bikarhêner têk çû
|
||||||
|
err_pwnam = girtina zanyariyên bikarhêner têk çû
|
||||||
|
err_sleep = fermana cemidaninê nehat xebitandin
|
||||||
|
err_start = fermana destpêkirinê nehat xebitandin
|
||||||
|
err_battery = barkirina rewşa betariyê têk çû
|
||||||
|
err_switch_tty = guhertina tty têk çû
|
||||||
|
err_tty_ctrl = guhertina kontrola tty têk çû
|
||||||
|
err_no_users = tu bikarhêner nehatin dîtin
|
||||||
|
err_uid_range = girtina rêjeya dînamîk a sînorê uid têk çû
|
||||||
|
err_user_gid = sazkirina GID a bikarhêner têk çû
|
||||||
|
err_user_init = destpêkirina bikarhêner têk çû
|
||||||
|
err_user_uid = sazkirina UID a bikarhêner têk çû
|
||||||
|
err_xauth = fermana xauth têk çû
|
||||||
|
err_xcb_conn = girêdana xcb têk çû
|
||||||
|
err_xsessions_dir = dîtina peldanka danişînan têk çû
|
||||||
|
err_xsessions_open = vekirina peldanka danişînan têk çû
|
||||||
|
hibernate = bicemidîne
|
||||||
|
insert = têxîne
|
||||||
|
login = têketin
|
||||||
|
logout = derkeve
|
||||||
|
no_x11_support = piştgiriya x11 di dema berhevkirinê de hatiye girtin
|
||||||
|
normal = normal
|
||||||
|
numlock = numlock
|
||||||
|
other = ên din
|
||||||
|
password = borînpeyv
|
||||||
|
restart = ji nû ve bide destpêkirin
|
||||||
|
shell = shell
|
||||||
|
shutdown = vemirîne
|
||||||
|
sleep = têxîne xewê
|
||||||
|
|
||||||
|
wayland = wayland
|
||||||
|
x11 = x11
|
||||||
|
xinitrc = xinitrc
|
||||||
82
res/lang/lv.ini
Normal file
82
res/lang/lv.ini
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
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
|
||||||
@@ -2,17 +2,29 @@ authenticating = uwierzytelnianie...
|
|||||||
brightness_down = zmniejsz jasność
|
brightness_down = zmniejsz jasność
|
||||||
brightness_up = zwiększ jasność
|
brightness_up = zwiększ jasność
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
custom = własny
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = nieudana alokacja pamięci
|
err_alloc = nieudana alokacja pamięci
|
||||||
|
|
||||||
|
err_autologin_session = nie znaleziono sesji autologowania
|
||||||
err_bounds = indeks poza zakresem
|
err_bounds = indeks poza zakresem
|
||||||
err_brightness_change = nie udało się zmienić jasności
|
err_brightness_change = nie udało się zmienić jasności
|
||||||
err_chdir = nie udało się otworzyć folderu domowego
|
err_chdir = nie udało się otworzyć folderu domowego
|
||||||
|
err_clock_too_long = ciąg znaków zegara jest za długi
|
||||||
err_config = nie można przetworzyć pliku konfiguracyjnego
|
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_dgn_oob = wiadomość loga
|
||||||
err_domain = niepoprawna domena
|
err_domain = niepoprawna domena
|
||||||
err_empty_password = puste hasło jest niedozwolone
|
err_empty_password = puste hasło jest niedozwolone
|
||||||
err_envlist = nie udało się pobrać listy zmiennych środowiskowych
|
err_envlist = nie udało się pobrać listy zmiennych środowiskowych
|
||||||
|
err_get_active_tty = nie udało się uzyskać aktywnego tty
|
||||||
|
|
||||||
err_hostname = nie udało się uzyskać nazwy hosta
|
err_hostname = nie udało się uzyskać nazwy hosta
|
||||||
|
|
||||||
|
err_lock_state = nie udało się uzyskać stanu blokady
|
||||||
|
err_log = nie udało się otworzyć pliku logu
|
||||||
err_mlock = nie udało się zablokować pamięci haseł
|
err_mlock = nie udało się zablokować pamięci haseł
|
||||||
err_null = pusty wskaźnik
|
err_null = pusty wskaźnik
|
||||||
err_numlock = nie udało się ustawić numlock
|
err_numlock = nie udało się ustawić numlock
|
||||||
@@ -38,8 +50,12 @@ err_perm_group = nie udało się obniżyć uprawnień grupy
|
|||||||
err_perm_user = nie udało się obniżyć uprawnień użytkownika
|
err_perm_user = nie udało się obniżyć uprawnień użytkownika
|
||||||
err_pwnam = nie udało się uzyskać informacji o użytkowniku
|
err_pwnam = nie udało się uzyskać informacji o użytkowniku
|
||||||
err_sleep = nie udało się wykonać polecenia sleep
|
err_sleep = nie udało się wykonać polecenia sleep
|
||||||
|
|
||||||
|
err_battery = nie udało się sprawdzić statusu baterii
|
||||||
|
err_switch_tty = nie można przełączyć tty
|
||||||
err_tty_ctrl = nie udało się przekazać kontroli tty
|
err_tty_ctrl = nie udało się przekazać kontroli tty
|
||||||
err_unknown = wystąpił nieznany błąd
|
err_no_users = nie znaleziono żadnego użytkownika
|
||||||
|
|
||||||
err_user_gid = nie udało się ustawić GID użytkownika
|
err_user_gid = nie udało się ustawić GID użytkownika
|
||||||
err_user_init = nie udało się zainicjalizować użytkownika
|
err_user_init = nie udało się zainicjalizować użytkownika
|
||||||
err_user_uid = nie udało się ustawić UID użytkownika
|
err_user_uid = nie udało się ustawić UID użytkownika
|
||||||
@@ -47,6 +63,7 @@ err_xauth = polecenie xauth nie powiodło się
|
|||||||
err_xcb_conn = połączenie xcb nie powiodło się
|
err_xcb_conn = połączenie xcb nie powiodło się
|
||||||
err_xsessions_dir = nie udało się znaleźć folderu sesji
|
err_xsessions_dir = nie udało się znaleźć folderu sesji
|
||||||
err_xsessions_open = nie udało się otworzyć folderu sesji
|
err_xsessions_open = nie udało się otworzyć folderu sesji
|
||||||
|
|
||||||
insert = wstaw
|
insert = wstaw
|
||||||
login = login
|
login = login
|
||||||
logout = wylogowano
|
logout = wylogowano
|
||||||
@@ -59,6 +76,7 @@ restart = uruchom ponownie
|
|||||||
shell = powłoka
|
shell = powłoka
|
||||||
shutdown = wyłącz
|
shutdown = wyłącz
|
||||||
sleep = uśpij
|
sleep = uśpij
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = erro na atribuição de memória
|
err_alloc = erro na atribuição de memória
|
||||||
|
|
||||||
|
|
||||||
err_bounds = índice fora de limites
|
err_bounds = índice fora de limites
|
||||||
|
|
||||||
err_chdir = erro ao abrir a pasta home
|
err_chdir = erro ao abrir a pasta home
|
||||||
|
|
||||||
err_console_dev = erro ao aceder à consola
|
|
||||||
|
|
||||||
err_dgn_oob = mensagem de registo
|
err_dgn_oob = mensagem de registo
|
||||||
err_domain = domínio inválido
|
err_domain = domínio inválido
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = erro ao obter o nome do host
|
err_hostname = erro ao obter o nome do host
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = erro de bloqueio de memória
|
err_mlock = erro de bloqueio de memória
|
||||||
err_null = ponteiro nulo
|
err_null = ponteiro nulo
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = erro ao obter informação do utilizador
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = erro ao definir o GID do utilizador
|
err_user_gid = erro ao definir o GID do utilizador
|
||||||
err_user_init = erro ao iniciar o utilizador
|
err_user_init = erro ao iniciar o utilizador
|
||||||
err_user_uid = erro ao definir o UID do utilizador
|
err_user_uid = erro ao definir o UID do utilizador
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = erro ao definir o UID do utilizador
|
|||||||
err_xsessions_dir = erro ao localizar a pasta das sessões
|
err_xsessions_dir = erro ao localizar a pasta das sessões
|
||||||
err_xsessions_open = erro ao abrir a pasta das sessões
|
err_xsessions_open = erro ao abrir a pasta das sessões
|
||||||
|
|
||||||
|
|
||||||
login = iniciar sessão
|
login = iniciar sessão
|
||||||
logout = terminar sessão
|
logout = terminar sessão
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = reiniciar
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = encerrar
|
shutdown = encerrar
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = caixa alta
|
capslock = caixa alta
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = alocação de memória malsucedida
|
err_alloc = alocação de memória malsucedida
|
||||||
|
|
||||||
|
|
||||||
err_bounds = índice fora de limites
|
err_bounds = índice fora de limites
|
||||||
|
|
||||||
err_chdir = não foi possível abrir o diretório home
|
err_chdir = não foi possível abrir o diretório home
|
||||||
|
|
||||||
err_console_dev = não foi possível acessar o console
|
|
||||||
|
|
||||||
err_dgn_oob = mensagem de log
|
err_dgn_oob = mensagem de log
|
||||||
err_domain = domínio inválido
|
err_domain = domínio inválido
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = não foi possível obter o nome do host
|
err_hostname = não foi possível obter o nome do host
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = bloqueio da memória de senha malsucedido
|
err_mlock = bloqueio da memória de senha malsucedido
|
||||||
err_null = ponteiro nulo
|
err_null = ponteiro nulo
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ 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_gid = não foi possível definir o GID do usuário
|
||||||
err_user_init = não foi possível iniciar o usuário
|
err_user_init = não foi possível iniciar o usuário
|
||||||
err_user_uid = não foi possível definir o UID do usuário
|
err_user_uid = não foi possível definir o UID do usuário
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = não foi possível definir o UID do usuário
|
|||||||
err_xsessions_dir = não foi possível encontrar a pasta das sessões
|
err_xsessions_dir = não foi possível encontrar a pasta das sessões
|
||||||
err_xsessions_open = não foi possível abrir a pasta das sessões
|
err_xsessions_open = não foi possível abrir a pasta das sessões
|
||||||
|
|
||||||
|
|
||||||
login = conectar
|
login = conectar
|
||||||
logout = desconectado
|
logout = desconectado
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = reiniciar
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = desligar
|
shutdown = desligar
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -7,7 +7,19 @@ capslock = capslock
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_console_dev = nu s-a putut accesa consola
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -48,6 +60,11 @@ err_perm_user = nu s-a putut face downgrade permisiunilor de utilizator
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
login = utilizator
|
login = utilizator
|
||||||
logout = opreşte sesiunea
|
logout = opreşte sesiunea
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = resetează
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = opreşte sistemul
|
shutdown = opreşte sistemul
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -1,21 +1,33 @@
|
|||||||
|
authenticating = аутентификация...
|
||||||
|
brightness_down = уменьшить яркость
|
||||||
|
brightness_up = увеличить яркость
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
custom = пользовательский
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = не удалось выделить память
|
err_alloc = не удалось выделить память
|
||||||
|
|
||||||
|
err_autologin_session = не найдена сессия с автологином
|
||||||
err_bounds = за пределами индекса
|
err_bounds = за пределами индекса
|
||||||
|
err_brightness_change = не удалось изменить яркость
|
||||||
err_chdir = не удалось открыть домашнюю папку
|
err_chdir = не удалось открыть домашнюю папку
|
||||||
|
err_clock_too_long = строка часов слишком длинная
|
||||||
|
err_config = не удалось разобрать файл конфигурации
|
||||||
|
|
||||||
err_console_dev = не удалось получить доступ к консоли
|
|
||||||
err_dgn_oob = отладочное сообщение (log)
|
err_dgn_oob = отладочное сообщение (log)
|
||||||
err_domain = неверный домен
|
err_domain = неверный домен
|
||||||
|
err_empty_password = пустой пароль не допустим
|
||||||
|
err_envlist = не удалось получить список переменных среды
|
||||||
|
err_get_active_tty = не удалось получить активный tty
|
||||||
|
|
||||||
err_hostname = не удалось получить имя хоста
|
err_hostname = не удалось получить имя хоста
|
||||||
|
|
||||||
|
err_lock_state = не удалось получить состояние lock
|
||||||
|
err_log = не удалось открыть файл log
|
||||||
err_mlock = сбой блокировки памяти
|
err_mlock = сбой блокировки памяти
|
||||||
err_null = нулевой указатель
|
err_null = нулевой указатель
|
||||||
|
err_numlock = не удалось установить numlock
|
||||||
err_pam = pam транзакция не удалась
|
err_pam = pam транзакция не удалась
|
||||||
err_pam_abort = pam транзакция прервана
|
err_pam_abort = pam транзакция прервана
|
||||||
err_pam_acct_expired = срок действия аккаунта истёк
|
err_pam_acct_expired = срок действия аккаунта истёк
|
||||||
@@ -37,28 +49,34 @@ err_perm_dir = не удалось изменить текущий катало
|
|||||||
err_perm_group = не удалось понизить права доступа группы
|
err_perm_group = не удалось понизить права доступа группы
|
||||||
err_perm_user = не удалось понизить права доступа пользователя
|
err_perm_user = не удалось понизить права доступа пользователя
|
||||||
err_pwnam = не удалось получить информацию о пользователе
|
err_pwnam = не удалось получить информацию о пользователе
|
||||||
|
err_sleep = не удалось выполнить команду sleep
|
||||||
|
|
||||||
|
err_battery = не удалось получить статус батареи
|
||||||
|
err_switch_tty = не удалось переключить tty
|
||||||
|
err_tty_ctrl = передача управления tty не удалась
|
||||||
|
err_no_users = пользователи не найдены
|
||||||
|
|
||||||
err_user_gid = не удалось установить GID пользователя
|
err_user_gid = не удалось установить GID пользователя
|
||||||
err_user_init = не удалось инициализировать пользователя
|
err_user_init = не удалось инициализировать пользователя
|
||||||
err_user_uid = не удалось установить UID пользователя
|
err_user_uid = не удалось установить UID пользователя
|
||||||
|
err_xauth = команда xauth не выполнена
|
||||||
|
err_xcb_conn = ошибка подключения xcb
|
||||||
err_xsessions_dir = не удалось найти сессионную папку
|
err_xsessions_dir = не удалось найти сессионную папку
|
||||||
err_xsessions_open = не удалось открыть сессионную папку
|
err_xsessions_open = не удалось открыть сессионную папку
|
||||||
|
|
||||||
|
insert = вставка
|
||||||
login = логин
|
login = логин
|
||||||
logout = logged out
|
logout = вышел из системы
|
||||||
|
no_x11_support = поддержка x11 отключена во время компиляции
|
||||||
|
normal = обычный
|
||||||
numlock = numlock
|
numlock = numlock
|
||||||
|
other = прочие
|
||||||
password = пароль
|
password = пароль
|
||||||
restart = перезагрузить
|
restart = перезагрузить
|
||||||
shell = shell
|
shell = оболочка
|
||||||
shutdown = выключить
|
shutdown = выключить
|
||||||
|
sleep = сон
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = neuspijesna alokacija memorije
|
err_alloc = neuspijesna alokacija memorije
|
||||||
|
|
||||||
|
|
||||||
err_bounds = izvan granica indeksa
|
err_bounds = izvan granica indeksa
|
||||||
|
|
||||||
err_chdir = neuspijesno otvaranje home foldera
|
err_chdir = neuspijesno otvaranje home foldera
|
||||||
|
|
||||||
err_console_dev = neuspijesno pristupanje konzoli
|
|
||||||
|
|
||||||
err_dgn_oob = log poruka
|
err_dgn_oob = log poruka
|
||||||
err_domain = nevazeci domen
|
err_domain = nevazeci domen
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = neuspijesno trazenje hostname-a
|
err_hostname = neuspijesno trazenje hostname-a
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = neuspijesno zakljucavanje memorije lozinke
|
err_mlock = neuspijesno zakljucavanje memorije lozinke
|
||||||
err_null = null pokazivac
|
err_null = null pokazivac
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = neuspijesno skupljanje informacija o korisniku
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = neuspijesno postavljanje korisničkog GID-a
|
err_user_gid = neuspijesno postavljanje korisničkog GID-a
|
||||||
err_user_init = neuspijensa inicijalizacija korisnika
|
err_user_init = neuspijensa inicijalizacija korisnika
|
||||||
err_user_uid = neuspijesno postavljanje UID-a korisnika
|
err_user_uid = neuspijesno postavljanje UID-a korisnika
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = neuspijesno postavljanje UID-a korisnika
|
|||||||
err_xsessions_dir = neuspijesno pronalazenje foldera sesija
|
err_xsessions_dir = neuspijesno pronalazenje foldera sesija
|
||||||
err_xsessions_open = neuspijesno otvaranje foldera sesija
|
err_xsessions_open = neuspijesno otvaranje foldera sesija
|
||||||
|
|
||||||
|
|
||||||
login = korisnik
|
login = korisnik
|
||||||
logout = izlogovan
|
logout = izlogovan
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = ponovo pokreni
|
|||||||
shell = shell
|
shell = shell
|
||||||
shutdown = ugasi
|
shutdown = ugasi
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
108
res/lang/sv.ini
108
res/lang/sv.ini
@@ -1,64 +1,82 @@
|
|||||||
|
authenticating = autentiserar...
|
||||||
|
brightness_down = minska ljusstyrka
|
||||||
|
brightness_up = öka ljusstyrka
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
err_alloc = misslyckad minnesallokering
|
custom = anpassad
|
||||||
err_bounds = utanför banan index
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
err_alloc = minnesallokering misslyckades
|
||||||
|
err_args = tolkning av kommandoargument misslyckades
|
||||||
|
err_autologin_session = autologin-session hittades inte
|
||||||
|
err_bounds = index-värde utanför intervallet
|
||||||
|
err_brightness_change = ändring av ljusstyrka misslyckades
|
||||||
err_chdir = misslyckades att öppna hemkatalog
|
err_chdir = misslyckades att öppna hemkatalog
|
||||||
|
err_clock_too_long = klocksträng för lång
|
||||||
err_console_dev = misslyckades att komma åt konsol
|
err_config = tolkning av konfigfil misslyckades
|
||||||
|
err_crawl = genomsökning av sessionskataloger misslyckades
|
||||||
err_dgn_oob = loggmeddelande
|
err_dgn_oob = loggmeddelande
|
||||||
err_domain = okänd domän
|
err_domain = ogitlig domän
|
||||||
|
err_empty_password = tomt lösenord godtas ej
|
||||||
|
err_envlist = hämtning av env-lista misslyckades
|
||||||
err_hostname = misslyckades att hämta värdnamn
|
err_get_active_tty = hämtning av aktiv tty misslyckades
|
||||||
err_mlock = misslyckades att låsa lösenordsminne
|
err_hibernate = vilolägets kommando misslyckades
|
||||||
err_null = nullpekare
|
err_hostname = hämtning av hostname misslyckades
|
||||||
|
err_inactivity = inaktivitetslägets kommando misslyckades
|
||||||
|
err_lock_state = hämtning av låsningsstatus misslyckades
|
||||||
|
err_log = öppning av loggfil misslyckades
|
||||||
|
err_mlock = låsning av lösenordsminne misslyckades
|
||||||
|
err_null = null pointer
|
||||||
|
err_numlock = inställning av numlock misslyckades
|
||||||
err_pam = pam-transaktion misslyckades
|
err_pam = pam-transaktion misslyckades
|
||||||
err_pam_abort = pam-transaktion avbröts
|
err_pam_abort = pam-transaktion avbröts
|
||||||
err_pam_acct_expired = konto upphört
|
err_pam_acct_expired = kontot har löpt ut
|
||||||
err_pam_auth = autentiseringsfel
|
err_pam_auth = autentisering misslyckades
|
||||||
err_pam_authinfo_unavail = misslyckades att hämta användarinfo
|
err_pam_authinfo_unavail = hämtning av användarinformation misslyckades
|
||||||
err_pam_authok_reqd = token utgången
|
err_pam_authok_reqd = token har löpt ut
|
||||||
err_pam_buf = minnesbuffer fel
|
err_pam_buf = minnesbufferfel
|
||||||
err_pam_cred_err = misslyckades att ställa in inloggningsuppgifter
|
err_pam_cred_err = inställning av inloggningsuppgifter misslyckades
|
||||||
err_pam_cred_expired = inloggningsuppgifter upphörda
|
err_pam_cred_expired = inloggningsuppgifterna har löpt ut
|
||||||
err_pam_cred_insufficient = otillräckliga inloggningsuppgifter
|
err_pam_cred_insufficient = otillräckliga inloggningsuppgifter
|
||||||
err_pam_cred_unavail = misslyckades att hämta inloggningsuppgifter
|
err_pam_cred_unavail = hämtning av inloggningsuppgifter misslyckades
|
||||||
err_pam_maxtries = nådde maximal försöksgräns
|
err_pam_maxtries = gränsen för antal försök nådd
|
||||||
err_pam_perm_denied = åtkomst nekad
|
err_pam_perm_denied = tillstånd nekas
|
||||||
err_pam_session = sessionsfel
|
err_pam_session = sessionsfel
|
||||||
err_pam_sys = systemfel
|
err_pam_sys = systemfel
|
||||||
err_pam_user_unknown = okänd användare
|
err_pam_user_unknown = okänd användare
|
||||||
err_path = misslyckades att ställa in sökväg
|
err_path = inställning av sökväg misslyckades
|
||||||
err_perm_dir = misslyckades att ändra aktuell katalog
|
err_perm_dir = byte av nuvarande katalog misslyckades
|
||||||
err_perm_group = misslyckades att nergradera gruppbehörigheter
|
err_perm_group = nedgradering av grupptillstånd misslyckades
|
||||||
err_perm_user = misslyckades att nergradera användarbehörigheter
|
err_perm_user = nedgradering av användartillstånd misslyckades
|
||||||
err_pwnam = misslyckades att hämta användarinfo
|
err_pwnam = hämtning av användarinformation misslyckades
|
||||||
|
err_sleep = strömsparlägets kommando misslyckades
|
||||||
|
err_start = startkommando misslyckades
|
||||||
|
err_battery = hämtning av batteristatus misslyckades
|
||||||
err_user_gid = misslyckades att ställa in användar-GID
|
err_switch_tty = byte av tty misslyckades
|
||||||
err_user_init = misslyckades att initialisera användaren
|
err_tty_ctrl = överföring av tty-kontroll misslyckades
|
||||||
err_user_uid = misslyckades att ställa in användar-UID
|
err_no_users = inga användare hittades
|
||||||
|
err_uid_range = dynamisk hämtning av uid-intervall misslyckades
|
||||||
|
err_user_gid = inställning av användarens GID misslyckades
|
||||||
err_xsessions_dir = misslyckades att hitta sessionskatalog
|
err_user_init = initiering av användare misslyckades
|
||||||
err_xsessions_open = misslyckades att öppna sessionskatalog
|
err_user_uid = inställning av användarens UID misslyckades
|
||||||
|
err_xauth = xauth-kommando misslyckades
|
||||||
|
err_xcb_conn = xcb-anslutning misslyckades
|
||||||
|
err_xsessions_dir = sessionskatalog hittades inte
|
||||||
|
err_xsessions_open = öppning av sessionskatalog misslyckades
|
||||||
|
hibernate = viloläge
|
||||||
|
insert = infoga
|
||||||
login = inloggning
|
login = inloggning
|
||||||
logout = utloggad
|
logout = utloggad
|
||||||
|
no_x11_support = x11-stöd inaktiverat vid kompilering
|
||||||
|
normal = normal
|
||||||
numlock = numlock
|
numlock = numlock
|
||||||
|
other = övrig
|
||||||
password = lösenord
|
password = lösenord
|
||||||
restart = starta om
|
restart = starta om
|
||||||
shell = skal
|
shell = shell
|
||||||
shutdown = stäng av
|
shutdown = stäng av
|
||||||
|
sleep = viloläge
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
|
|
||||||
|
brightness_down = parlakligi azalt
|
||||||
|
brightness_up = parlakligi arttir
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = basarisiz bellek ayirma
|
err_alloc = basarisiz bellek ayirma
|
||||||
|
|
||||||
|
|
||||||
err_bounds = sinirlarin disinda dizin
|
err_bounds = sinirlarin disinda dizin
|
||||||
|
|
||||||
err_chdir = ev klasoru acilamadi
|
err_chdir = ev klasoru acilamadi
|
||||||
|
|
||||||
err_console_dev = konsola erisilemedi
|
|
||||||
|
|
||||||
err_dgn_oob = log mesaji
|
err_dgn_oob = log mesaji
|
||||||
err_domain = gecersiz etki alani
|
err_domain = gecersiz etki alani
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = ana bilgisayar adi alinamadi
|
err_hostname = ana bilgisayar adi alinamadi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = parola bellegi kilitlenemedi
|
err_mlock = parola bellegi kilitlenemedi
|
||||||
err_null = bos isaretci hatasi
|
err_null = bos isaretci hatasi
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = kullanici bilgileri alinamadi
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = kullanici icin GID ayarlanamadi
|
err_user_gid = kullanici icin GID ayarlanamadi
|
||||||
err_user_init = kullanici oturumu baslatilamadi
|
err_user_init = kullanici oturumu baslatilamadi
|
||||||
err_user_uid = kullanici icin UID ayarlanamadi
|
err_user_uid = kullanici icin UID ayarlanamadi
|
||||||
@@ -47,17 +63,19 @@ err_user_uid = kullanici icin UID ayarlanamadi
|
|||||||
|
|
||||||
err_xsessions_dir = oturumlar klasoru bulunamadi
|
err_xsessions_dir = oturumlar klasoru bulunamadi
|
||||||
err_xsessions_open = oturumlar klasoru acilamadi
|
err_xsessions_open = oturumlar klasoru acilamadi
|
||||||
|
hibernate = askiya al
|
||||||
|
|
||||||
login = kullanici
|
login = kullanici
|
||||||
logout = oturumdan cikis yapildi
|
logout = oturumdan cikis yapildi
|
||||||
|
|
||||||
|
|
||||||
numlock = numlock
|
numlock = numlock
|
||||||
|
other = baska
|
||||||
password = sifre
|
password = sifre
|
||||||
restart = yeniden baslat
|
restart = yeniden baslat
|
||||||
shell = shell
|
shell = shell
|
||||||
shutdown = makineyi kapat
|
shutdown = makineyi kapat
|
||||||
|
sleep = uykuya al
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = capslock
|
capslock = capslock
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = невдале виділення пам'яті
|
err_alloc = невдале виділення пам'яті
|
||||||
|
|
||||||
|
|
||||||
err_bounds = поза межами індексу
|
err_bounds = поза межами індексу
|
||||||
|
|
||||||
err_chdir = не вдалося відкрити домашній каталог
|
err_chdir = не вдалося відкрити домашній каталог
|
||||||
|
|
||||||
err_console_dev = невдалий доступ до консолі
|
|
||||||
|
|
||||||
err_dgn_oob = повідомлення журналу (log)
|
err_dgn_oob = повідомлення журналу (log)
|
||||||
err_domain = недійсний домен
|
err_domain = недійсний домен
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = не вдалося отримати ім'я хосту
|
err_hostname = не вдалося отримати ім'я хосту
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = збій блокування пам'яті
|
err_mlock = збій блокування пам'яті
|
||||||
err_null = нульовий вказівник
|
err_null = нульовий вказівник
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = не вдалося отримати дані користувача
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = не вдалося змінити GID користувача
|
err_user_gid = не вдалося змінити GID користувача
|
||||||
err_user_init = не вдалося ініціалізувати користувача
|
err_user_init = не вдалося ініціалізувати користувача
|
||||||
err_user_uid = не вдалося змінити UID користувача
|
err_user_uid = не вдалося змінити UID користувача
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = не вдалося змінити UID користувача
|
|||||||
err_xsessions_dir = не вдалося знайти каталог сесій
|
err_xsessions_dir = не вдалося знайти каталог сесій
|
||||||
err_xsessions_open = не вдалося відкрити каталог сесій
|
err_xsessions_open = не вдалося відкрити каталог сесій
|
||||||
|
|
||||||
|
|
||||||
login = логін
|
login = логін
|
||||||
logout = вийти
|
logout = вийти
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ restart = перезавантажити
|
|||||||
shell = оболонка
|
shell = оболонка
|
||||||
shutdown = вимкнути
|
shutdown = вимкнути
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
|
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -2,17 +2,29 @@
|
|||||||
|
|
||||||
|
|
||||||
capslock = 大写锁定
|
capslock = 大写锁定
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_alloc = 内存分配失败
|
err_alloc = 内存分配失败
|
||||||
|
|
||||||
|
|
||||||
err_bounds = 索引越界
|
err_bounds = 索引越界
|
||||||
|
|
||||||
err_chdir = 无法打开home文件夹
|
err_chdir = 无法打开home文件夹
|
||||||
|
|
||||||
err_console_dev = 无法访问控制台
|
|
||||||
|
|
||||||
err_dgn_oob = 日志消息
|
err_dgn_oob = 日志消息
|
||||||
err_domain = 无效的域
|
err_domain = 无效的域
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_hostname = 获取主机名失败
|
err_hostname = 获取主机名失败
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_mlock = 锁定密码存储器失败
|
err_mlock = 锁定密码存储器失败
|
||||||
err_null = 空指针
|
err_null = 空指针
|
||||||
|
|
||||||
@@ -40,6 +52,10 @@ err_pwnam = 获取用户信息失败
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
err_user_gid = 设置用户GID失败
|
err_user_gid = 设置用户GID失败
|
||||||
err_user_init = 初始化用户失败
|
err_user_init = 初始化用户失败
|
||||||
err_user_uid = 设置用户UID失败
|
err_user_uid = 设置用户UID失败
|
||||||
@@ -48,6 +64,7 @@ err_user_uid = 设置用户UID失败
|
|||||||
err_xsessions_dir = 找不到会话文件夹
|
err_xsessions_dir = 找不到会话文件夹
|
||||||
err_xsessions_open = 无法打开会话文件夹
|
err_xsessions_open = 无法打开会话文件夹
|
||||||
|
|
||||||
|
|
||||||
login = 登录
|
login = 登录
|
||||||
logout = 注销
|
logout = 注销
|
||||||
|
|
||||||
@@ -59,6 +76,7 @@ password = 密码
|
|||||||
shell = shell
|
shell = shell
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
wayland = wayland
|
wayland = wayland
|
||||||
x11 = x11
|
x11 = x11
|
||||||
xinitrc = xinitrc
|
xinitrc = xinitrc
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
type = process
|
type = process
|
||||||
restart = true
|
restart = true
|
||||||
smooth-recovery = true
|
smooth-recovery = true
|
||||||
command = $PREFIX_DIRECTORY/bin/$EXE_NAME
|
command = $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
|
||||||
depends-on = loginready
|
depends-on = login.target
|
||||||
termsignal = HUP
|
termsignal = HUP
|
||||||
# ly needs access to the console while loginready already occupies it
|
# ly needs access to the console while login.target already occupies it
|
||||||
options = shares-console
|
options = shares-console
|
||||||
|
|||||||
7
res/ly-freebsd-wrapper
Normal file
7
res/ly-freebsd-wrapper
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# On FreeBSD, even if we override the default login program, getty will still
|
||||||
|
# try to append "login -fp root" as arguments to Ly, which is not supported.
|
||||||
|
# To avoid this, we use a wrapper script that ignores these arguments before
|
||||||
|
# actually executing Ly.
|
||||||
|
exec $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
|
||||||
17
res/ly-kmsconvt@.service
Normal file
17
res/ly-kmsconvt@.service
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=TUI display manager using KMSCON
|
||||||
|
After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||||
|
After=kmsconvt@%i.service
|
||||||
|
Conflicts=kmsconvt@%i.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=$PREFIX_DIRECTORY/bin/kmscon --font-engine unifont --vt=%I --seats=seat0 --login -- $PREFIX_DIRECTORY/bin/ly --use-kmscon-vt
|
||||||
|
StandardInput=tty
|
||||||
|
UtmpIdentifier=%I
|
||||||
|
TTYPath=/dev/%I
|
||||||
|
TTYReset=yes
|
||||||
|
TTYVHangup=yes
|
||||||
|
TTYVTDisallocate=yes
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
@@ -19,12 +19,8 @@ then
|
|||||||
commandUL="/sbin/agetty"
|
commandUL="/sbin/agetty"
|
||||||
fi
|
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
|
## The execution vars
|
||||||
# If CONFTTY is empty then default to $DEFAULT_TTY
|
TTY="tty$DEFAULT_TTY"
|
||||||
TTY="tty${CONFTTY:-$DEFAULT_TTY}"
|
|
||||||
TERM=linux
|
TERM=linux
|
||||||
BAUD=38400
|
BAUD=38400
|
||||||
# If we don't have getty then we should have agetty
|
# If we don't have getty then we should have agetty
|
||||||
|
|||||||
@@ -7,6 +7,4 @@ fi
|
|||||||
|
|
||||||
BAUD_RATE=38400
|
BAUD_RATE=38400
|
||||||
TERM_NAME=linux
|
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}
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/execlineb -P
|
#!/bin/execlineb -P
|
||||||
exec agetty -L -8 -n -l $PREFIX_DIRECTORY/bin/$EXE_NAME tty$DEFAULT_TTY 115200
|
exec agetty -L -8 -n -l $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME tty$DEFAULT_TTY 115200
|
||||||
|
|||||||
65
res/ly-sysvinit
Executable file
65
res/ly-sysvinit
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
### BEGIN INIT INFO
|
||||||
|
# Provides: ly
|
||||||
|
# Required-Start: $remote_fs $syslog
|
||||||
|
# Required-Stop: $remote_fs $syslog
|
||||||
|
# Default-Start: 2 3 4 5
|
||||||
|
# Default-Stop: 0 1 6
|
||||||
|
# Short-Description: Ly display manager
|
||||||
|
# Description: Starts and stops the Ly display manager
|
||||||
|
### END INIT INFO
|
||||||
|
#
|
||||||
|
# Author: AnErrupTion <anerruption@disroot.org>
|
||||||
|
#
|
||||||
|
|
||||||
|
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||||
|
DAEMON=/usr/bin/ly
|
||||||
|
TTY=/dev/tty$DEFAULT_TTY
|
||||||
|
PIDFILE=/var/run/ly.pid
|
||||||
|
NAME=ly
|
||||||
|
DESC="Ly display manager"
|
||||||
|
|
||||||
|
. /lib/lsb/init-functions
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
log_daemon_msg "Starting $DESC on $TTY..."
|
||||||
|
if [ -f "$PIDFILE" ]; then
|
||||||
|
log_progress_msg "$DESC is already running"
|
||||||
|
log_end_msg 0
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure TTY exists
|
||||||
|
[ -c "$TTY" ] || {
|
||||||
|
log_failure_msg "$TTY does not exist"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
start-stop-daemon --start --background --make-pidfile --pidfile $PIDFILE \
|
||||||
|
--chdir / --exec /bin/sh -- -c "exec setsid sh -c 'exec <$TTY >$TTY 2>&1 $DAEMON'"
|
||||||
|
log_end_msg $?
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
log_daemon_msg "Stopping $DESC..."
|
||||||
|
start-stop-daemon --stop --pidfile $PIDFILE --retry 5
|
||||||
|
RETVAL=$?
|
||||||
|
[ $RETVAL -eq 0 ] && rm -f "$PIDFILE"
|
||||||
|
log_end_msg $RETVAL
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
echo "Restarting $DESC..."
|
||||||
|
$0 stop
|
||||||
|
sleep 1
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: /etc/init.d/$NAME {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=TUI display manager
|
Description=TUI display manager
|
||||||
After=systemd-user-sessions.service plymouth-quit-wait.service
|
After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||||
After=getty@tty$DEFAULT_TTY.service
|
After=getty@%i.service
|
||||||
Conflicts=getty@tty$DEFAULT_TTY.service
|
Conflicts=getty@%i.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=idle
|
Type=idle
|
||||||
ExecStart=$PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
|
ExecStart=$PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
|
||||||
StandardInput=tty
|
StandardInput=tty
|
||||||
TTYPath=/dev/tty$DEFAULT_TTY
|
TTYPath=/dev/%I
|
||||||
TTYReset=yes
|
TTYReset=yes
|
||||||
TTYVHangup=yes
|
TTYVHangup=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
Alias=display-manager.service
|
WantedBy=multi-user.target
|
||||||
8
res/pam.d/ly-freebsd
Normal file
8
res/pam.d/ly-freebsd
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#%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
|
||||||
9
res/pam.d/ly-freebsd-autologin
Normal file
9
res/pam.d/ly-freebsd-autologin
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
|
||||||
|
# OpenPAM (used in FreeBSD) doesn't support prepending "-" for ignoring missing
|
||||||
|
# modules.
|
||||||
|
auth required pam_permit.so
|
||||||
|
auth include login
|
||||||
|
account include login
|
||||||
|
password include login
|
||||||
|
session include login
|
||||||
16
res/pam.d/ly-linux-autologin
Normal file
16
res/pam.d/ly-linux-autologin
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#%PAM-1.0
|
||||||
|
|
||||||
|
auth required pam_permit.so
|
||||||
|
-auth optional pam_gnome_keyring.so
|
||||||
|
-auth optional pam_kwallet5.so
|
||||||
|
|
||||||
|
account include login
|
||||||
|
|
||||||
|
password include login
|
||||||
|
-password optional pam_gnome_keyring.so use_authtok
|
||||||
|
|
||||||
|
-session optional pam_systemd.so class=greeter
|
||||||
|
-session optional pam_elogind.so
|
||||||
|
session include login
|
||||||
|
-session optional pam_gnome_keyring.so auto_start
|
||||||
|
-session optional pam_kwallet5.so auto_start
|
||||||
37
res/startup.sh
Executable file
37
res/startup.sh
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# This file is executed when starting Ly (before the TTY is taken control of)
|
||||||
|
# Custom startup code can be placed in this file or the start_cmd var can be pointed to a different file
|
||||||
|
|
||||||
|
|
||||||
|
# Uncomment the example below for an example of changing the default TTY colors to an alternitive palette on linux
|
||||||
|
# Colors are in red/green/blue hex (the current colors are a brighter palette than default)
|
||||||
|
#
|
||||||
|
# if [ "$TERM" = "linux" ]; then
|
||||||
|
# BLACK="232323"
|
||||||
|
# DARK_RED="D75F5F"
|
||||||
|
# DARK_GREEN="87AF5F"
|
||||||
|
# DARK_YELLOW="D7AF87"
|
||||||
|
# DARK_BLUE="8787AF"
|
||||||
|
# DARK_MAGENTA="BD53A5"
|
||||||
|
# DARK_CYAN="5FAFAF"
|
||||||
|
# LIGHT_GRAY="E5E5E5"
|
||||||
|
# DARK_GRAY="2B2B2B"
|
||||||
|
# RED="E33636"
|
||||||
|
# GREEN="98E34D"
|
||||||
|
# YELLOW="FFD75F"
|
||||||
|
# BLUE="7373C9"
|
||||||
|
# MAGENTA="D633B2"
|
||||||
|
# CYAN="44C9C9"
|
||||||
|
# WHITE="FFFFFF"
|
||||||
|
|
||||||
|
# COLORS="${BLACK} ${DARK_RED} ${DARK_GREEN} ${DARK_YELLOW} ${DARK_BLUE} ${DARK_MAGENTA} ${DARK_CYAN} ${LIGHT_GRAY} ${DARK_GRAY} ${RED} ${GREEN} ${YELLOW} ${BLUE} ${MAGENTA} ${CYAN} ${WHITE}"
|
||||||
|
|
||||||
|
# i=0
|
||||||
|
# while [ $i -lt 16 ]; do
|
||||||
|
# printf "\033]P%x%s" ${i} "$(echo "$COLORS" | cut -d ' ' -f$(( i + 1)))"
|
||||||
|
|
||||||
|
# i=$(( i + 1 ))
|
||||||
|
# done
|
||||||
|
|
||||||
|
# clear # for fixing background artifacting after changing color
|
||||||
|
# fi
|
||||||
@@ -1,21 +1,24 @@
|
|||||||
const enums = @import("enums.zig");
|
const ini = @import("ly-ui").ly_core.ini;
|
||||||
const ini = @import("zigini");
|
|
||||||
|
|
||||||
const DisplayServer = enums.DisplayServer;
|
|
||||||
const Ini = ini.Ini;
|
const Ini = ini.Ini;
|
||||||
|
|
||||||
|
const enums = @import("enums.zig");
|
||||||
|
const DisplayServer = enums.DisplayServer;
|
||||||
|
|
||||||
pub const DesktopEntry = struct {
|
pub const DesktopEntry = struct {
|
||||||
Exec: []const u8 = "",
|
Exec: []const u8 = "",
|
||||||
Name: [:0]const u8 = "",
|
Name: []const u8 = "",
|
||||||
DesktopNames: ?[:0]u8 = null,
|
DesktopNames: ?[]u8 = null,
|
||||||
|
Terminal: ?bool = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
|
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
|
||||||
|
|
||||||
entry_ini: ?Ini(Entry) = null,
|
entry_ini: ?Ini(Entry) = null,
|
||||||
name: [:0]const u8 = "",
|
file_name: []const u8 = "",
|
||||||
xdg_session_desktop: ?[:0]const u8 = null,
|
name: []const u8 = "",
|
||||||
xdg_desktop_names: ?[:0]const u8 = null,
|
xdg_session_desktop: ?[]const u8 = null,
|
||||||
cmd: []const u8 = "",
|
xdg_desktop_names: ?[]const u8 = null,
|
||||||
|
cmd: ?[]const u8 = null,
|
||||||
specifier: []const u8 = "",
|
specifier: []const u8 = "",
|
||||||
display_server: DisplayServer = .wayland,
|
display_server: DisplayServer = .wayland,
|
||||||
|
is_terminal: bool = false,
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror));
|
|
||||||
|
|
||||||
const ErrorHandler = packed struct {
|
|
||||||
has_error: bool = false,
|
|
||||||
err_int: ErrInt = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SharedError = @This();
|
|
||||||
|
|
||||||
data: []align(std.heap.page_size_min) u8,
|
|
||||||
|
|
||||||
pub fn init() !SharedError {
|
|
||||||
const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), std.posix.PROT.READ | std.posix.PROT.WRITE, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0);
|
|
||||||
|
|
||||||
return .{ .data = data };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *SharedError) void {
|
|
||||||
std.posix.munmap(self.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writeError(self: SharedError, err: anyerror) void {
|
|
||||||
var buf_stream = std.io.fixedBufferStream(self.data);
|
|
||||||
const writer = buf_stream.writer();
|
|
||||||
writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }) catch {};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn readError(self: SharedError) ?anyerror {
|
|
||||||
var buf_stream = std.io.fixedBufferStream(self.data);
|
|
||||||
const reader = buf_stream.reader();
|
|
||||||
const err_handler = try reader.readStruct(ErrorHandler);
|
|
||||||
|
|
||||||
if (err_handler.has_error)
|
|
||||||
return @errorFromInt(err_handler.err_int);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
92
src/animations/Cascade.zig
Normal file
92
src/animations/Cascade.zig
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const math = std.math;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const Cell = ly_ui.Cell;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
|
||||||
|
const Cascade = @This();
|
||||||
|
|
||||||
|
io: std.Io,
|
||||||
|
instance: ?Widget = null,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
current_auth_fails: *u64,
|
||||||
|
max_auth_fails: u64,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
io: std.Io,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
current_auth_fails: *u64,
|
||||||
|
max_auth_fails: u64,
|
||||||
|
) Cascade {
|
||||||
|
return .{
|
||||||
|
.io = io,
|
||||||
|
.instance = null,
|
||||||
|
.buffer = buffer,
|
||||||
|
.current_auth_fails = current_auth_fails,
|
||||||
|
.max_auth_fails = max_auth_fails,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *Cascade) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Cascade",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *Cascade) void {
|
||||||
|
while (self.current_auth_fails.* >= self.max_auth_fails) {
|
||||||
|
self.io.sleep(.fromMilliseconds(10), .real) catch {};
|
||||||
|
|
||||||
|
var changed = false;
|
||||||
|
var y = self.buffer.height - 2;
|
||||||
|
|
||||||
|
while (y > 0) : (y -= 1) {
|
||||||
|
for (0..self.buffer.width) |x| {
|
||||||
|
const cell = TerminalBuffer.getCell(x, y - 1);
|
||||||
|
const cell_under = TerminalBuffer.getCell(x, y);
|
||||||
|
|
||||||
|
// This shouldn't happen under normal circumstances, but because
|
||||||
|
// this is a *secret* animation, there's no need to care that much
|
||||||
|
if (cell == null or cell_under == null) continue;
|
||||||
|
|
||||||
|
const char: u8 = @truncate(cell.?.ch);
|
||||||
|
if (std.ascii.isWhitespace(char)) continue;
|
||||||
|
|
||||||
|
const char_under: u8 = @truncate(cell_under.?.ch);
|
||||||
|
if (!std.ascii.isWhitespace(char_under)) continue;
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
if ((self.buffer.random.int(u16) % 10) > 7) continue;
|
||||||
|
|
||||||
|
cell.?.put(x, y);
|
||||||
|
|
||||||
|
var space = Cell.init(
|
||||||
|
' ',
|
||||||
|
cell_under.?.fg,
|
||||||
|
cell_under.?.bg,
|
||||||
|
);
|
||||||
|
space.put(x, y - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed) {
|
||||||
|
self.io.sleep(.fromSeconds(7), .real) catch {};
|
||||||
|
self.current_auth_fails.* = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalBuffer.presentBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,17 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Animation = @import("../tui/Animation.zig");
|
const math = std.math;
|
||||||
const Cell = @import("../tui/Cell.zig");
|
|
||||||
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
|
const ly_ui = @import("ly-ui");
|
||||||
|
const Cell = ly_ui.Cell;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
|
||||||
|
const ly_core = ly_ui.ly_core;
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const TimeOfDay = interop.TimeOfDay;
|
||||||
|
|
||||||
const ColorMix = @This();
|
const ColorMix = @This();
|
||||||
|
|
||||||
const math = std.math;
|
|
||||||
const Vec2 = @Vector(2, f32);
|
const Vec2 = @Vector(2, f32);
|
||||||
|
|
||||||
const time_scale: f32 = 0.01;
|
const time_scale: f32 = 0.01;
|
||||||
@@ -15,15 +21,33 @@ fn length(vec: Vec2) f32 {
|
|||||||
return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
|
return math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
start_time: TimeOfDay,
|
||||||
terminal_buffer: *TerminalBuffer,
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
frames: u64,
|
frames: u64,
|
||||||
pattern_cos_mod: f32,
|
pattern_cos_mod: f32,
|
||||||
pattern_sin_mod: f32,
|
pattern_sin_mod: f32,
|
||||||
palette: [palette_len]Cell,
|
palette: [palette_len]Cell,
|
||||||
|
|
||||||
pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) ColorMix {
|
pub fn init(
|
||||||
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
col1: u32,
|
||||||
|
col2: u32,
|
||||||
|
col3: u32,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
|
) !ColorMix {
|
||||||
return .{
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.start_time = try interop.getTimeOfDay(),
|
||||||
.terminal_buffer = terminal_buffer,
|
.terminal_buffer = terminal_buffer,
|
||||||
|
.animate = animate,
|
||||||
|
.timeout_sec = timeout_sec,
|
||||||
|
.frame_delay = frame_delay,
|
||||||
.frames = 0,
|
.frames = 0,
|
||||||
.pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
|
.pattern_cos_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
|
||||||
.pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
|
.pattern_sin_mod = terminal_buffer.random.float(f32) * math.pi * 2.0,
|
||||||
@@ -44,15 +68,25 @@ pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) C
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn animation(self: *ColorMix) Animation {
|
pub fn widget(self: *ColorMix) *Widget {
|
||||||
return Animation.init(self, deinit, realloc, draw);
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"ColorMix",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(_: *ColorMix) void {}
|
|
||||||
|
|
||||||
fn realloc(_: *ColorMix) anyerror!void {}
|
|
||||||
|
|
||||||
fn draw(self: *ColorMix) void {
|
fn draw(self: *ColorMix) void {
|
||||||
|
if (!self.animate.*) return;
|
||||||
|
|
||||||
self.frames +%= 1;
|
self.frames +%= 1;
|
||||||
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;
|
const time: f32 = @as(f32, @floatFromInt(self.frames)) * time_scale;
|
||||||
|
|
||||||
@@ -79,8 +113,20 @@ fn draw(self: *ColorMix) void {
|
|||||||
uv -= @splat(1.0 * math.cos(uv[0] + uv[1]) - math.sin(uv[0] * 0.7 - uv[1]));
|
uv -= @splat(1.0 * math.cos(uv[0] + uv[1]) - math.sin(uv[0] * 0.7 - uv[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
const cell = self.palette[@as(usize, @intFromFloat(math.floor(length(uv) * 5.0))) % palette_len];
|
const cell = self.palette[@as(usize, @trunc(math.floor(length(uv) * 5.0))) % palette_len];
|
||||||
cell.put(x, y);
|
cell.put(x, y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(self: *ColorMix, _: *anyopaque) !void {
|
||||||
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
|
||||||
|
self.animate.* = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *ColorMix, _: *anyopaque) !?usize {
|
||||||
|
return self.frame_delay;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,32 +1,55 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Animation = @import("../tui/Animation.zig");
|
|
||||||
const Cell = @import("../tui/Cell.zig");
|
const ly_ui = @import("ly-ui");
|
||||||
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
|
const Cell = ly_ui.Cell;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
|
||||||
|
const ly_core = ly_ui.ly_core;
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const TimeOfDay = interop.TimeOfDay;
|
||||||
|
|
||||||
const Doom = @This();
|
const Doom = @This();
|
||||||
|
|
||||||
pub const STEPS = 12;
|
pub const STEPS = 12;
|
||||||
|
pub const HEIGHT_MAX = 9;
|
||||||
|
pub const SPREAD_MAX = 4;
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
start_time: TimeOfDay,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
terminal_buffer: *TerminalBuffer,
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
buffer: []u8,
|
buffer: []u8,
|
||||||
|
height: u8,
|
||||||
|
spread: u8,
|
||||||
fire: [STEPS + 1]Cell,
|
fire: [STEPS + 1]Cell,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u32, middle_color: u32, bottom_color: u32) !Doom {
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
top_color: u32,
|
||||||
|
middle_color: u32,
|
||||||
|
bottom_color: u32,
|
||||||
|
fire_height: u8,
|
||||||
|
fire_spread: u8,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
|
) !Doom {
|
||||||
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
|
const buffer = try allocator.alloc(u8, terminal_buffer.width * terminal_buffer.height);
|
||||||
initBuffer(buffer, terminal_buffer.width);
|
initBuffer(buffer, terminal_buffer.width);
|
||||||
|
|
||||||
return .{
|
const levels =
|
||||||
.allocator = allocator,
|
[_]Cell{
|
||||||
.terminal_buffer = terminal_buffer,
|
Cell.init(' ', terminal_buffer.bg, terminal_buffer.bg),
|
||||||
.buffer = buffer,
|
Cell.init(0x2591, top_color, terminal_buffer.bg),
|
||||||
.fire = [_]Cell{
|
Cell.init(0x2592, top_color, terminal_buffer.bg),
|
||||||
Cell.init(' ', TerminalBuffer.Color.DEFAULT, TerminalBuffer.Color.DEFAULT),
|
Cell.init(0x2593, top_color, terminal_buffer.bg),
|
||||||
Cell.init(0x2591, top_color, TerminalBuffer.Color.DEFAULT),
|
Cell.init(0x2588, top_color, terminal_buffer.bg),
|
||||||
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(0x2591, middle_color, top_color),
|
||||||
Cell.init(0x2592, middle_color, top_color),
|
Cell.init(0x2592, middle_color, top_color),
|
||||||
Cell.init(0x2593, middle_color, top_color),
|
Cell.init(0x2593, middle_color, top_color),
|
||||||
@@ -35,42 +58,84 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u
|
|||||||
Cell.init(0x2592, bottom_color, middle_color),
|
Cell.init(0x2592, bottom_color, middle_color),
|
||||||
Cell.init(0x2593, bottom_color, middle_color),
|
Cell.init(0x2593, bottom_color, middle_color),
|
||||||
Cell.init(0x2588, bottom_color, middle_color),
|
Cell.init(0x2588, bottom_color, middle_color),
|
||||||
},
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.start_time = try interop.getTimeOfDay(),
|
||||||
|
.allocator = allocator,
|
||||||
|
.terminal_buffer = terminal_buffer,
|
||||||
|
.animate = animate,
|
||||||
|
.timeout_sec = timeout_sec,
|
||||||
|
.frame_delay = frame_delay,
|
||||||
|
.buffer = buffer,
|
||||||
|
.height = @min(HEIGHT_MAX, fire_height),
|
||||||
|
.spread = @min(SPREAD_MAX, fire_spread),
|
||||||
|
.fire = levels,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn animation(self: *Doom) Animation {
|
pub fn widget(self: *Doom) *Widget {
|
||||||
return Animation.init(self, deinit, realloc, draw);
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Doom",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
realloc,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Doom) void {
|
fn deinit(self: *Doom) void {
|
||||||
self.allocator.free(self.buffer);
|
self.allocator.free(self.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realloc(self: *Doom) anyerror!void {
|
fn realloc(self: *Doom) !void {
|
||||||
const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height);
|
const buffer = try self.allocator.realloc(self.buffer, self.terminal_buffer.width * self.terminal_buffer.height);
|
||||||
initBuffer(buffer, self.terminal_buffer.width);
|
initBuffer(buffer, self.terminal_buffer.width);
|
||||||
self.buffer = buffer;
|
self.buffer = buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(self: *Doom) void {
|
fn draw(self: *Doom) void {
|
||||||
|
if (!self.animate.*) return;
|
||||||
|
|
||||||
for (0..self.terminal_buffer.width) |x| {
|
for (0..self.terminal_buffer.width) |x| {
|
||||||
// We start from 1 so that we always have the topmost line when spreading fire
|
// We start from 1 so that we always have the topmost line when spreading fire
|
||||||
for (1..self.terminal_buffer.height) |y| {
|
for (1..self.terminal_buffer.height) |y| {
|
||||||
// Get current cell
|
// Get index of current cell in fire level buffer
|
||||||
const from = y * self.terminal_buffer.width + x;
|
const from = y * self.terminal_buffer.width + x;
|
||||||
const cell_index = self.buffer[from];
|
|
||||||
|
|
||||||
// Spread fire
|
// Generate random data for fire propagation
|
||||||
const propagate = self.terminal_buffer.random.int(u1);
|
const rand_loss = self.terminal_buffer.random.intRangeAtMost(u8, 0, HEIGHT_MAX);
|
||||||
const to = from - self.terminal_buffer.width; // Get the line above
|
const rand_spread = self.terminal_buffer.random.intRangeAtMost(u8, 0, self.spread * 2);
|
||||||
|
|
||||||
self.buffer[to] = if (cell_index > 0) cell_index - propagate else cell_index;
|
// 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;
|
||||||
|
|
||||||
// Put the cell
|
// Get fire level of current cell
|
||||||
const cell = self.fire[cell_index];
|
const level_buf_from = self.buffer[from];
|
||||||
cell.put(x, y);
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw bottom line (fire source)
|
||||||
|
const src_cell = self.fire[STEPS];
|
||||||
|
src_cell.put(x, self.terminal_buffer.height - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,3 +149,15 @@ fn initBuffer(buffer: []u8, width: usize) void {
|
|||||||
@memset(slice_start, 0);
|
@memset(slice_start, 0);
|
||||||
@memset(slice_end, STEPS);
|
@memset(slice_end, STEPS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(self: *Doom, _: *anyopaque) !void {
|
||||||
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
|
||||||
|
self.animate.* = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *Doom, _: *anyopaque) !?usize {
|
||||||
|
return self.frame_delay;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Animation = @import("../tui/Animation.zig");
|
|
||||||
|
|
||||||
const Dummy = @This();
|
|
||||||
|
|
||||||
pub fn animation(self: *Dummy) Animation {
|
|
||||||
return Animation.init(self, deinit, realloc, draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(_: *Dummy) void {}
|
|
||||||
|
|
||||||
fn realloc(_: *Dummy) anyerror!void {}
|
|
||||||
|
|
||||||
fn draw(_: *Dummy) void {}
|
|
||||||
528
src/animations/DurFile.zig
Normal file
528
src/animations/DurFile.zig
Normal file
@@ -0,0 +1,528 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Json = std.json;
|
||||||
|
const eql = std.mem.eql;
|
||||||
|
const flate = std.compress.flate;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const Cell = ly_ui.Cell;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Color = TerminalBuffer.Color;
|
||||||
|
const Styling = TerminalBuffer.Styling;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
|
||||||
|
const ly_core = ly_ui.ly_core;
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const TimeOfDay = interop.TimeOfDay;
|
||||||
|
const LogFile = ly_core.LogFile;
|
||||||
|
|
||||||
|
const enums = @import("../enums.zig");
|
||||||
|
const DurOffsetAlignment = enums.DurOffsetAlignment;
|
||||||
|
|
||||||
|
fn read_decompress_file(allocator: Allocator, io: std.Io, file_path: []const u8) ![]u8 {
|
||||||
|
const file_buffer = std.Io.Dir.cwd().openFile(io, file_path, .{}) catch {
|
||||||
|
return error.FileNotFound;
|
||||||
|
};
|
||||||
|
defer file_buffer.close(io);
|
||||||
|
|
||||||
|
var file_reader_buffer: [4096]u8 = undefined;
|
||||||
|
var decompress_buffer: [flate.max_window_len]u8 = undefined;
|
||||||
|
|
||||||
|
var file_reader = file_buffer.reader(io, &file_reader_buffer);
|
||||||
|
var decompress: flate.Decompress = .init(&file_reader.interface, .gzip, &decompress_buffer);
|
||||||
|
|
||||||
|
const file_decompressed = decompress.reader.allocRemaining(allocator, .unlimited) catch {
|
||||||
|
return error.NotValidFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
return file_decompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Frame = struct {
|
||||||
|
frameNumber: i32,
|
||||||
|
delay: f32,
|
||||||
|
contents: [][]u8,
|
||||||
|
colorMap: [][][]i32,
|
||||||
|
|
||||||
|
// allocator must be outside of struct as it will fail the json parser
|
||||||
|
pub fn deinit(self: *const Frame, allocator: Allocator) void {
|
||||||
|
for (self.contents) |con| {
|
||||||
|
allocator.free(con);
|
||||||
|
}
|
||||||
|
allocator.free(self.contents);
|
||||||
|
|
||||||
|
for (self.colorMap) |cm| {
|
||||||
|
for (cm) |int2| {
|
||||||
|
allocator.free(int2);
|
||||||
|
}
|
||||||
|
allocator.free(cm);
|
||||||
|
}
|
||||||
|
allocator.free(self.colorMap);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://github.com/cmang/durdraw/blob/0.29.0/durformat.md
|
||||||
|
const DurFormat = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
formatVersion: ?i64 = null,
|
||||||
|
colorFormat: ?[]const u8 = null,
|
||||||
|
encoding: ?[]const u8 = null,
|
||||||
|
framerate: ?f64 = null,
|
||||||
|
columns: ?i64 = null,
|
||||||
|
lines: ?i64 = null,
|
||||||
|
frames: std.ArrayList(Frame) = undefined,
|
||||||
|
|
||||||
|
pub fn valid(self: *DurFormat) bool {
|
||||||
|
if (self.formatVersion != null and
|
||||||
|
self.colorFormat != null and
|
||||||
|
self.encoding != null and
|
||||||
|
self.framerate != null and
|
||||||
|
self.columns != null and
|
||||||
|
self.lines != null and
|
||||||
|
self.frames.items.len >= 1)
|
||||||
|
{
|
||||||
|
// v8 may have breaking changes like changing the colormap xy direction
|
||||||
|
// (https://github.com/cmang/durdraw/issues/24)
|
||||||
|
if (self.formatVersion.? != 7) return false;
|
||||||
|
|
||||||
|
// Code currently only supports 16 and 256 color format only
|
||||||
|
if (!(eql(u8, "16", self.colorFormat.?) or eql(u8, "256", self.colorFormat.?)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Code currently supports only utf-8 encoding
|
||||||
|
if (!eql(u8, self.encoding.?, "utf-8")) return false;
|
||||||
|
|
||||||
|
// Sanity check on file
|
||||||
|
if (self.columns.? <= 0) return false;
|
||||||
|
if (self.lines.? <= 0) return false;
|
||||||
|
if (self.framerate.? < 0) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_dur_from_json(self: *DurFormat, allocator: Allocator, dur_json_root: Json.Value) !void {
|
||||||
|
var dur_movie = if (dur_json_root.object.get("DurMovie")) |dm| dm.object else return error.NotValidFile;
|
||||||
|
|
||||||
|
// Depending on the version, a dur file can have different json object names (ie: columns vs sizeX)
|
||||||
|
self.formatVersion = if (dur_movie.get("formatVersion")) |x| x.integer else null;
|
||||||
|
self.colorFormat = if (dur_movie.get("colorFormat")) |x| try allocator.dupe(u8, x.string) else null;
|
||||||
|
self.encoding = if (dur_movie.get("encoding")) |x| try allocator.dupe(u8, x.string) else null;
|
||||||
|
self.framerate = if (dur_movie.get("framerate")) |x| x.float else null;
|
||||||
|
self.columns = if (dur_movie.get("columns")) |x| x.integer else if (dur_movie.get("sizeX")) |x| x.integer else null;
|
||||||
|
|
||||||
|
self.lines = if (dur_movie.get("lines")) |x| x.integer else if (dur_movie.get("sizeY")) |x| x.integer else null;
|
||||||
|
|
||||||
|
const frames = dur_movie.get("frames") orelse return error.NotValidFile;
|
||||||
|
|
||||||
|
self.frames = try .initCapacity(allocator, frames.array.items.len);
|
||||||
|
|
||||||
|
for (frames.array.items) |json_frame| {
|
||||||
|
var parsed_frame = try Json.parseFromValue(Frame, allocator, json_frame, .{});
|
||||||
|
defer parsed_frame.deinit();
|
||||||
|
|
||||||
|
const frame_val = parsed_frame.value;
|
||||||
|
|
||||||
|
// copy all fields to own the ptrs for deallocation, the parsed_frame has some other
|
||||||
|
// allocated memory making it difficult to deallocate without leaks
|
||||||
|
const frame: Frame = .{ .frameNumber = frame_val.frameNumber, .delay = frame_val.delay, .contents = try allocator.alloc([]u8, frame_val.contents.len), .colorMap = try allocator.alloc([][]i32, frame_val.colorMap.len) };
|
||||||
|
|
||||||
|
for (0..frame.contents.len) |i| {
|
||||||
|
frame.contents[i] = try allocator.dupe(u8, frame_val.contents[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// colorMap is stored as an 3d array where:
|
||||||
|
// the outer (i) most array is the horizontal position of the color
|
||||||
|
// the middle (j) is the vertical position of the color
|
||||||
|
// the inner (0/1) is the foreground/background color
|
||||||
|
for (0..frame.colorMap.len) |i| {
|
||||||
|
frame.colorMap[i] = try allocator.alloc([]i32, frame_val.colorMap[i].len);
|
||||||
|
for (0..frame.colorMap[i].len) |j| {
|
||||||
|
frame.colorMap[i][j] = try allocator.alloc(i32, 2);
|
||||||
|
frame.colorMap[i][j][0] = frame_val.colorMap[i][j][0];
|
||||||
|
frame.colorMap[i][j][1] = frame_val.colorMap[i][j][1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.frames.append(allocator, frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_from_file(self: *DurFormat, allocator: Allocator, io: std.Io, file_path: []const u8) !void {
|
||||||
|
const file_decompressed = try read_decompress_file(allocator, io, file_path);
|
||||||
|
defer allocator.free(file_decompressed);
|
||||||
|
|
||||||
|
const parsed = try Json.parseFromSlice(Json.Value, allocator, file_decompressed, .{});
|
||||||
|
defer parsed.deinit();
|
||||||
|
|
||||||
|
try parse_dur_from_json(self, allocator, parsed.value);
|
||||||
|
|
||||||
|
if (!self.valid()) {
|
||||||
|
return error.NotValidFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: Allocator) DurFormat {
|
||||||
|
return .{ .allocator = allocator };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *DurFormat) void {
|
||||||
|
if (self.colorFormat) |str| self.allocator.free(str);
|
||||||
|
if (self.encoding) |str| self.allocator.free(str);
|
||||||
|
|
||||||
|
for (self.frames.items) |frame| {
|
||||||
|
frame.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
self.frames.deinit(self.allocator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tb_color_16 = [16]u32{
|
||||||
|
Color.ECOL_BLACK,
|
||||||
|
Color.ECOL_RED,
|
||||||
|
Color.ECOL_GREEN,
|
||||||
|
Color.ECOL_YELLOW,
|
||||||
|
Color.ECOL_BLUE,
|
||||||
|
Color.ECOL_MAGENTA,
|
||||||
|
Color.ECOL_CYAN,
|
||||||
|
Color.ECOL_WHITE,
|
||||||
|
Color.ECOL_BLACK | Styling.BOLD,
|
||||||
|
Color.ECOL_RED | Styling.BOLD,
|
||||||
|
Color.ECOL_GREEN | Styling.BOLD,
|
||||||
|
Color.ECOL_YELLOW | Styling.BOLD,
|
||||||
|
Color.ECOL_BLUE | Styling.BOLD,
|
||||||
|
Color.ECOL_MAGENTA | Styling.BOLD,
|
||||||
|
Color.ECOL_CYAN | Styling.BOLD,
|
||||||
|
Color.ECOL_WHITE | Styling.BOLD,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Using bold for bright colors allows for all 16 colors to be rendered on tty term
|
||||||
|
const rgb_color_16 = [16]u32{
|
||||||
|
Color.DEFAULT, // DEFAULT instead of TRUE_BLACK to not break compositors (the latter ignores transparency)
|
||||||
|
Color.TRUE_DIM_RED,
|
||||||
|
Color.TRUE_DIM_GREEN,
|
||||||
|
Color.TRUE_DIM_YELLOW,
|
||||||
|
Color.TRUE_DIM_BLUE,
|
||||||
|
Color.TRUE_DIM_MAGENTA,
|
||||||
|
Color.TRUE_DIM_CYAN,
|
||||||
|
Color.TRUE_DIM_WHITE,
|
||||||
|
Color.DEFAULT | Styling.BOLD,
|
||||||
|
Color.TRUE_RED | Styling.BOLD,
|
||||||
|
Color.TRUE_GREEN | Styling.BOLD,
|
||||||
|
Color.TRUE_YELLOW | Styling.BOLD,
|
||||||
|
Color.TRUE_BLUE | Styling.BOLD,
|
||||||
|
Color.TRUE_MAGENTA | Styling.BOLD,
|
||||||
|
Color.TRUE_CYAN | Styling.BOLD,
|
||||||
|
Color.TRUE_WHITE | Styling.BOLD,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Made this table from looking at colormapping in dur source, not sure whats going on with the mapping logic
|
||||||
|
// Array indexes are dur colormappings which value maps to indexes in table above. Only needed for dur 16 color
|
||||||
|
const durcolor_table_to_color16 = [17]u32{
|
||||||
|
0, // 0 black
|
||||||
|
0, // 1 nothing?? dur source did not say why 1 is unused
|
||||||
|
4, // 2 blue
|
||||||
|
2, // 3 green
|
||||||
|
6, // 4 cyan
|
||||||
|
1, // 5 red
|
||||||
|
5, // 6 magenta
|
||||||
|
3, // 7 yellow
|
||||||
|
7, // 8 light gray
|
||||||
|
8, // 9 gray
|
||||||
|
12, // 10 bright blue
|
||||||
|
10, // 11 bright green
|
||||||
|
14, // 12 bright cyan
|
||||||
|
9, // 13 bright red
|
||||||
|
13, // 14 bright magenta
|
||||||
|
11, // 15 bright yellow
|
||||||
|
15, // 16 bright white
|
||||||
|
};
|
||||||
|
|
||||||
|
fn sixcube_to_channel(sixcube: u32) u32 {
|
||||||
|
// Although the range top for the extended range is 0xFF, 6 is not divisible into 0xFF,
|
||||||
|
// so we use 0xF0 instead with a scaler
|
||||||
|
const equal_divisions = 0xF0 / 6;
|
||||||
|
|
||||||
|
// Since the range is to 0xFF but 6 isn't divisible, we must add a scaler to get it to 0xFF at the last index (5)
|
||||||
|
const scaler = 0xFF - (equal_divisions * 5);
|
||||||
|
|
||||||
|
return if (sixcube > 0) (sixcube * equal_divisions) + scaler else 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_256_to_rgb(color_256: u32) u32 {
|
||||||
|
var rgb_color: u32 = 0;
|
||||||
|
|
||||||
|
// 0 - 15 is the standard color range, map to array table
|
||||||
|
if (color_256 < 16) {
|
||||||
|
rgb_color = rgb_color_16[color_256];
|
||||||
|
}
|
||||||
|
// 16 - 231 is the extended range
|
||||||
|
else if (color_256 < 232) {
|
||||||
|
|
||||||
|
// For extended term range we subtract by 16 to get it in a 0..(6x6x6) cube (range of 216)
|
||||||
|
// divide by 36 gets the depth of the cube (6x6x1)
|
||||||
|
// divide by 6 gets the width of the cube (6x1)
|
||||||
|
// divide by 1 gets the height of the cube (divide 1 for clarity for what we are doing)
|
||||||
|
// each channel can be 6 levels of brightness hence remander operation of 6
|
||||||
|
// finally bitshift to correct rgb channel (16 for red, 8 for green, 0 for blue)
|
||||||
|
rgb_color |= sixcube_to_channel(((color_256 - 16) / 36) % 6) << 16;
|
||||||
|
rgb_color |= sixcube_to_channel(((color_256 - 16) / 6) % 6) << 8;
|
||||||
|
rgb_color |= sixcube_to_channel(((color_256 - 16) / 1) % 6);
|
||||||
|
}
|
||||||
|
// 232 - 255 is the grayscale range
|
||||||
|
else {
|
||||||
|
|
||||||
|
// For grayscale we have a space of 232 - 255 (24)
|
||||||
|
// subtract by 232 to get it into the 0..23 range
|
||||||
|
// standard colors will contain white and black, so we do not use them in the grayscale range (0 is 0x08, 23 is 0xEE)
|
||||||
|
// this results in a skip of 0x08 for the first color and divisions of 0x0A
|
||||||
|
// example: term_col 232 = scaler + equal_divisions * (232 - 232) which becomes (scaler + 0x00) == 0x08
|
||||||
|
// example: term_col 255 = scaler + equal_divisions * (255 - 232) which becomes (scaler + 0xE6) == 0xEE
|
||||||
|
const scaler = 0x08;
|
||||||
|
|
||||||
|
// to get equal parts, the equation is:
|
||||||
|
// 0xEE = equal_divisions * 23 + scaler | top of range is 0xEE, 23 is last element value (255 minus 232)
|
||||||
|
// reordered to solve for equal_divisions:
|
||||||
|
const equal_divisions = (0xEE - scaler) / 23; // evals to 0x0A
|
||||||
|
|
||||||
|
const channel = scaler + equal_divisions * (color_256 - 232);
|
||||||
|
|
||||||
|
// gray is equal value of same channel color in rgb
|
||||||
|
rgb_color = channel | (channel << 8) | (channel << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rgb_color;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UVec2 = @Vector(2, u32);
|
||||||
|
const IVec2 = @Vector(2, i64);
|
||||||
|
|
||||||
|
const VEC_X = 0;
|
||||||
|
const VEC_Y = 1;
|
||||||
|
|
||||||
|
const DurFile = @This();
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
start_time: TimeOfDay,
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
dur_movie: DurFormat,
|
||||||
|
frames: usize,
|
||||||
|
frame_size: UVec2,
|
||||||
|
start_pos: IVec2,
|
||||||
|
full_color: bool,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
|
frame_time: u32,
|
||||||
|
time_previous: i64,
|
||||||
|
is_color_format_16: bool,
|
||||||
|
offset_alignment: DurOffsetAlignment,
|
||||||
|
offset: IVec2,
|
||||||
|
|
||||||
|
// if the user has an even number of columns or rows, we will default to the left or higher position (e.g. 4 columns center = .x..)
|
||||||
|
fn center(v: u32) i64 {
|
||||||
|
return @intCast((v / 2) + (v % 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_start_position(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat, offset_alignment: DurOffsetAlignment, offset: IVec2) IVec2 {
|
||||||
|
const buf_width: u32 = @intCast(terminal_buffer.width);
|
||||||
|
const buf_height: u32 = @intCast(terminal_buffer.height);
|
||||||
|
|
||||||
|
var movie_width: u32 = @intCast(dur_movie.columns.?);
|
||||||
|
var movie_height: u32 = @intCast(dur_movie.lines.?);
|
||||||
|
|
||||||
|
if (movie_width > buf_width) movie_width = buf_width;
|
||||||
|
if (movie_height > buf_height) movie_height = buf_height;
|
||||||
|
|
||||||
|
const start_pos: IVec2 = switch (offset_alignment) {
|
||||||
|
DurOffsetAlignment.center => .{ center(buf_width) - center(movie_width), center(buf_height) - center(movie_height) },
|
||||||
|
DurOffsetAlignment.topleft => .{ 0, 0 },
|
||||||
|
DurOffsetAlignment.topcenter => .{ center(buf_width) - center(movie_width), 0 },
|
||||||
|
DurOffsetAlignment.topright => .{ buf_width - movie_width, 0 },
|
||||||
|
DurOffsetAlignment.centerleft => .{ 0, center(buf_height) - center(movie_height) },
|
||||||
|
DurOffsetAlignment.centerright => .{ buf_width - movie_width, center(buf_height) - center(movie_height) },
|
||||||
|
DurOffsetAlignment.bottomleft => .{ 0, buf_height - movie_height },
|
||||||
|
DurOffsetAlignment.bottomcenter => .{ center(buf_width) - center(movie_width), buf_height - movie_height },
|
||||||
|
DurOffsetAlignment.bottomright => .{ buf_width - movie_width, buf_height - movie_height },
|
||||||
|
};
|
||||||
|
|
||||||
|
return start_pos + offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calc_frame_size(terminal_buffer: *TerminalBuffer, dur_movie: *DurFormat) UVec2 {
|
||||||
|
const buf_width: u32 = @intCast(terminal_buffer.width);
|
||||||
|
const buf_height: u32 = @intCast(terminal_buffer.height);
|
||||||
|
|
||||||
|
const movie_width: u32 = @intCast(dur_movie.columns.?);
|
||||||
|
const movie_height: u32 = @intCast(dur_movie.lines.?);
|
||||||
|
|
||||||
|
// Draw only the needed amount if movie smaller than screen. If movie is bigger, we will just draw entire screen
|
||||||
|
const frame_width = if (movie_width < buf_width) movie_width else buf_width;
|
||||||
|
const frame_height = if (movie_height < buf_height) movie_height else buf_height;
|
||||||
|
|
||||||
|
return .{ frame_width, frame_height };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
log_file: *LogFile,
|
||||||
|
file_path: []const u8,
|
||||||
|
offset_alignment: DurOffsetAlignment,
|
||||||
|
x_offset: i32,
|
||||||
|
y_offset: i32,
|
||||||
|
full_color: bool,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
|
) !DurFile {
|
||||||
|
var dur_movie: DurFormat = .init(allocator);
|
||||||
|
|
||||||
|
dur_movie.create_from_file(allocator, io, file_path) catch |err| switch (err) {
|
||||||
|
error.FileNotFound => {
|
||||||
|
try log_file.err(io, "tui", "dur_file was not found at: {s}", .{file_path});
|
||||||
|
return err;
|
||||||
|
},
|
||||||
|
error.NotValidFile => {
|
||||||
|
try log_file.err(io, "tui", "dur_file loaded was invalid or not a dur file!", .{});
|
||||||
|
return err;
|
||||||
|
},
|
||||||
|
else => return err,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4 bit mode with 256 color is unsupported
|
||||||
|
if (!full_color and eql(u8, dur_movie.colorFormat.?, "256")) {
|
||||||
|
try log_file.err(io, "tui", "dur_file can not be 256 color encoded when not using full_color option!", .{});
|
||||||
|
dur_movie.deinit();
|
||||||
|
return error.InvalidColorFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset: IVec2 = .{ x_offset, y_offset };
|
||||||
|
|
||||||
|
const start_pos = calc_start_position(terminal_buffer, &dur_movie, offset_alignment, offset);
|
||||||
|
const frame_size = calc_frame_size(terminal_buffer, &dur_movie);
|
||||||
|
|
||||||
|
// Convert dur fps to frames per ms
|
||||||
|
const frame_time: u32 = @trunc(1000 / dur_movie.framerate.?);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.start_time = try interop.getTimeOfDay(),
|
||||||
|
.allocator = allocator,
|
||||||
|
.io = io,
|
||||||
|
.terminal_buffer = terminal_buffer,
|
||||||
|
.frames = 0,
|
||||||
|
.time_previous = std.Io.Timestamp.now(io, .real).toMilliseconds(),
|
||||||
|
.frame_size = frame_size,
|
||||||
|
.start_pos = start_pos,
|
||||||
|
.full_color = full_color,
|
||||||
|
.animate = animate,
|
||||||
|
.timeout_sec = timeout_sec,
|
||||||
|
.frame_delay = frame_delay,
|
||||||
|
.dur_movie = dur_movie,
|
||||||
|
.frame_time = frame_time,
|
||||||
|
.is_color_format_16 = eql(u8, dur_movie.colorFormat.?, "16"),
|
||||||
|
.offset_alignment = offset_alignment,
|
||||||
|
.offset = offset,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *DurFile) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"DurFile",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
realloc,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *DurFile) void {
|
||||||
|
self.dur_movie.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realloc(self: *DurFile) !void {
|
||||||
|
// when terminal size changes, we need to recalculate the start_pos and frame_size based on the new size
|
||||||
|
self.start_pos = calc_start_position(self.terminal_buffer, &self.dur_movie, self.offset_alignment, self.offset);
|
||||||
|
self.frame_size = calc_frame_size(self.terminal_buffer, &self.dur_movie);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *DurFile) void {
|
||||||
|
if (!self.animate.*) return;
|
||||||
|
|
||||||
|
const current_frame = self.dur_movie.frames.items[self.frames];
|
||||||
|
|
||||||
|
const buf_width: u32 = @intCast(self.terminal_buffer.width);
|
||||||
|
const buf_height: u32 = @intCast(self.terminal_buffer.height);
|
||||||
|
|
||||||
|
// y is used as an iterator in the durformat, while cell_y gives us the correct placement for the cell (same for x)
|
||||||
|
for (0..self.frame_size[VEC_Y]) |y| {
|
||||||
|
const y_offset_i = @as(i32, @intCast(y)) + self.start_pos[VEC_Y];
|
||||||
|
// we skip the pass if it falls outside of the draw window (ensure no int underflow)
|
||||||
|
const cell_y: u32 = if (y_offset_i >= 0 and y_offset_i < buf_height) @intCast(y_offset_i) else continue;
|
||||||
|
|
||||||
|
var iter = std.unicode.Utf8View.initUnchecked(current_frame.contents[y]).iterator();
|
||||||
|
|
||||||
|
for (0..self.frame_size[VEC_X]) |x| {
|
||||||
|
const x_offset_i = @as(i32, @intCast(x)) + self.start_pos[VEC_X];
|
||||||
|
// skip pass, same as y but also increment the codepoint iter to fetch correct values in later passes
|
||||||
|
const cell_x: u32 = if (x_offset_i >= 0 and x_offset_i < buf_width) @intCast(x_offset_i) else {
|
||||||
|
_ = iter.nextCodepoint().?;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const codepoint: u21 = iter.nextCodepoint().?;
|
||||||
|
const color_map = current_frame.colorMap[x][y];
|
||||||
|
|
||||||
|
var color_map_0: u32 = @intCast(if (color_map[0] == -1) 0 else color_map[0]);
|
||||||
|
var color_map_1: u32 = @intCast(if (color_map[1] == -1) 0 else color_map[1]);
|
||||||
|
|
||||||
|
if (self.is_color_format_16) {
|
||||||
|
color_map_0 = durcolor_table_to_color16[color_map_0];
|
||||||
|
color_map_1 = durcolor_table_to_color16[color_map_1 + 1]; // Add 1, dur source stores it like this for some reason
|
||||||
|
}
|
||||||
|
|
||||||
|
const fg_color = if (self.full_color) convert_256_to_rgb(color_map_0) else tb_color_16[color_map_0];
|
||||||
|
const bg_color = if (self.full_color) convert_256_to_rgb(color_map_1) else tb_color_16[color_map_1];
|
||||||
|
|
||||||
|
const cell = Cell{ .ch = @intCast(codepoint), .fg = fg_color, .bg = bg_color };
|
||||||
|
|
||||||
|
cell.put(cell_x, cell_y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const time_current = std.Io.Timestamp.now(self.io, .real).toMilliseconds();
|
||||||
|
const delta_time = time_current - self.time_previous;
|
||||||
|
|
||||||
|
// Convert delay from sec to ms
|
||||||
|
const delay_time: u32 = @trunc(current_frame.delay * 1000);
|
||||||
|
if (delta_time > (self.frame_time + delay_time)) {
|
||||||
|
self.time_previous = time_current;
|
||||||
|
|
||||||
|
const frame_count = self.dur_movie.frames.items.len;
|
||||||
|
self.frames = (self.frames + 1) % frame_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(self: *DurFile, _: *anyopaque) !void {
|
||||||
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
|
||||||
|
self.animate.* = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *DurFile, _: *anyopaque) !?usize {
|
||||||
|
return self.frame_delay;
|
||||||
|
}
|
||||||
240
src/animations/GameOfLife.zig
Normal file
240
src/animations/GameOfLife.zig
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const Cell = ly_ui.Cell;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
|
||||||
|
const ly_core = ly_ui.ly_core;
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const TimeOfDay = interop.TimeOfDay;
|
||||||
|
|
||||||
|
const GameOfLife = @This();
|
||||||
|
|
||||||
|
// 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 },
|
||||||
|
};
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
start_time: TimeOfDay,
|
||||||
|
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,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
animation_frame_delay: u16,
|
||||||
|
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,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
animation_frame_delay: u16,
|
||||||
|
) !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{
|
||||||
|
.instance = null,
|
||||||
|
.start_time = try interop.getTimeOfDay(),
|
||||||
|
.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,
|
||||||
|
.animate = animate,
|
||||||
|
.timeout_sec = timeout_sec,
|
||||||
|
.animation_frame_delay = animation_frame_delay,
|
||||||
|
.dead_cell = .{ .ch = DEAD_CHAR, .fg = @intCast(TerminalBuffer.Color.DEFAULT), .bg = terminal_buffer.bg },
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize grid
|
||||||
|
game.initializeGrid();
|
||||||
|
|
||||||
|
return game;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *GameOfLife) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"GameOfLife",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
realloc,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *GameOfLife) void {
|
||||||
|
self.allocator.free(self.current_grid);
|
||||||
|
self.allocator.free(self.next_grid);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realloc(self: *GameOfLife) !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 {
|
||||||
|
if (!self.animate.*) return;
|
||||||
|
|
||||||
|
// 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 update(self: *GameOfLife, _: *anyopaque) !void {
|
||||||
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
|
||||||
|
self.animate.* = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *GameOfLife, _: *anyopaque) !?usize {
|
||||||
|
return self.animation_frame_delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateGeneration(self: *GameOfLife) void {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Animation = @import("../tui/Animation.zig");
|
|
||||||
const Cell = @import("../tui/Cell.zig");
|
|
||||||
const TerminalBuffer = @import("../tui/TerminalBuffer.zig");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const Random = std.Random;
|
const Random = std.Random;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const Cell = ly_ui.Cell;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
|
||||||
|
const ly_core = ly_ui.ly_core;
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const TimeOfDay = interop.TimeOfDay;
|
||||||
|
|
||||||
pub const FRAME_DELAY: usize = 8;
|
pub const FRAME_DELAY: usize = 8;
|
||||||
|
|
||||||
// Characters change mid-scroll
|
// Characters change mid-scroll
|
||||||
pub const MID_SCROLL_CHANGE = true;
|
pub const MID_SCROLL_CHANGE = true;
|
||||||
|
|
||||||
const DOT_HEAD_COLOR: u32 = @intCast(TerminalBuffer.Color.WHITE | TerminalBuffer.Styling.BOLD);
|
|
||||||
|
|
||||||
const Matrix = @This();
|
const Matrix = @This();
|
||||||
|
|
||||||
pub const Dot = struct {
|
pub const Dot = struct {
|
||||||
@@ -26,6 +29,8 @@ pub const Line = struct {
|
|||||||
update: usize,
|
update: usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
start_time: TimeOfDay,
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
terminal_buffer: *TerminalBuffer,
|
terminal_buffer: *TerminalBuffer,
|
||||||
dots: []Dot,
|
dots: []Dot,
|
||||||
@@ -33,17 +38,33 @@ lines: []Line,
|
|||||||
frame: usize,
|
frame: usize,
|
||||||
count: usize,
|
count: usize,
|
||||||
fg: u32,
|
fg: u32,
|
||||||
|
head_col: u32,
|
||||||
min_codepoint: u16,
|
min_codepoint: u16,
|
||||||
max_codepoint: u16,
|
max_codepoint: u16,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
default_cell: Cell,
|
default_cell: Cell,
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min_codepoint: u16, max_codepoint: u16) !Matrix {
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
terminal_buffer: *TerminalBuffer,
|
||||||
|
fg: u32,
|
||||||
|
head_col: u32,
|
||||||
|
min_codepoint: u16,
|
||||||
|
max_codepoint: u16,
|
||||||
|
animate: *bool,
|
||||||
|
timeout_sec: u12,
|
||||||
|
frame_delay: u16,
|
||||||
|
) !Matrix {
|
||||||
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
|
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
|
||||||
const lines = try allocator.alloc(Line, terminal_buffer.width);
|
const lines = try allocator.alloc(Line, terminal_buffer.width);
|
||||||
|
|
||||||
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
|
initBuffers(dots, lines, terminal_buffer.width, terminal_buffer.height, terminal_buffer.random);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.start_time = try interop.getTimeOfDay(),
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.terminal_buffer = terminal_buffer,
|
.terminal_buffer = terminal_buffer,
|
||||||
.dots = dots,
|
.dots = dots,
|
||||||
@@ -51,14 +72,30 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min
|
|||||||
.frame = 3,
|
.frame = 3,
|
||||||
.count = 0,
|
.count = 0,
|
||||||
.fg = fg,
|
.fg = fg,
|
||||||
|
.head_col = head_col,
|
||||||
.min_codepoint = min_codepoint,
|
.min_codepoint = min_codepoint,
|
||||||
.max_codepoint = max_codepoint - min_codepoint,
|
.max_codepoint = max_codepoint - min_codepoint,
|
||||||
|
.animate = animate,
|
||||||
|
.timeout_sec = timeout_sec,
|
||||||
|
.frame_delay = frame_delay,
|
||||||
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
|
.default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn animation(self: *Matrix) Animation {
|
pub fn widget(self: *Matrix) *Widget {
|
||||||
return Animation.init(self, deinit, realloc, draw);
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Matrix",
|
||||||
|
null,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
realloc,
|
||||||
|
draw,
|
||||||
|
update,
|
||||||
|
null,
|
||||||
|
calculateTimeout,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn deinit(self: *Matrix) void {
|
fn deinit(self: *Matrix) void {
|
||||||
@@ -66,7 +103,7 @@ fn deinit(self: *Matrix) void {
|
|||||||
self.allocator.free(self.lines);
|
self.allocator.free(self.lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realloc(self: *Matrix) anyerror!void {
|
fn realloc(self: *Matrix) !void {
|
||||||
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1));
|
const dots = try self.allocator.realloc(self.dots, self.terminal_buffer.width * (self.terminal_buffer.height + 1));
|
||||||
const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);
|
const lines = try self.allocator.realloc(self.lines, self.terminal_buffer.width);
|
||||||
|
|
||||||
@@ -77,6 +114,8 @@ fn realloc(self: *Matrix) anyerror!void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn draw(self: *Matrix) void {
|
fn draw(self: *Matrix) void {
|
||||||
|
if (!self.animate.*) return;
|
||||||
|
|
||||||
const buf_height = self.terminal_buffer.height;
|
const buf_height = self.terminal_buffer.height;
|
||||||
const buf_width = self.terminal_buffer.width;
|
const buf_width = self.terminal_buffer.width;
|
||||||
self.count += 1;
|
self.count += 1;
|
||||||
@@ -86,17 +125,17 @@ fn draw(self: *Matrix) void {
|
|||||||
self.count = 0;
|
self.count = 0;
|
||||||
|
|
||||||
var x: usize = 0;
|
var x: usize = 0;
|
||||||
while (x < self.terminal_buffer.width) : (x += 2) {
|
while (x < buf_width) : (x += 2) {
|
||||||
var tail: usize = 0;
|
var tail: usize = 0;
|
||||||
var line = &self.lines[x];
|
var line = &self.lines[x];
|
||||||
if (self.frame <= line.update) continue;
|
if (self.frame <= line.update) continue;
|
||||||
|
|
||||||
if (self.dots[x].value == null and self.dots[self.terminal_buffer.width + x].value == ' ') {
|
if (self.dots[x].value == null and self.dots[buf_width + x].value == ' ') {
|
||||||
if (line.space > 0) {
|
if (line.space > 0) {
|
||||||
line.space -= 1;
|
line.space -= 1;
|
||||||
} else {
|
} else {
|
||||||
const randint = self.terminal_buffer.random.int(u16);
|
const randint = self.terminal_buffer.random.int(u16);
|
||||||
const h = self.terminal_buffer.height;
|
const h = buf_height;
|
||||||
line.length = @mod(randint, h - 3) + 3;
|
line.length = @mod(randint, h - 3) + 3;
|
||||||
self.dots[x].value = @mod(randint, self.max_codepoint) + self.min_codepoint;
|
self.dots[x].value = @mod(randint, self.max_codepoint) + self.min_codepoint;
|
||||||
line.space = @mod(randint, h + 1);
|
line.space = @mod(randint, h + 1);
|
||||||
@@ -153,19 +192,33 @@ fn draw(self: *Matrix) void {
|
|||||||
var x: usize = 0;
|
var x: usize = 0;
|
||||||
while (x < buf_width) : (x += 2) {
|
while (x < buf_width) : (x += 2) {
|
||||||
var y: usize = 1;
|
var y: usize = 1;
|
||||||
while (y <= self.terminal_buffer.height) : (y += 1) {
|
while (y <= buf_height) : (y += 1) {
|
||||||
const dot = self.dots[buf_width * y + x];
|
const dot = self.dots[buf_width * y + x];
|
||||||
const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{
|
const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{
|
||||||
.ch = @intCast(dot.value.?),
|
.ch = @intCast(dot.value.?),
|
||||||
.fg = if (dot.is_head) DOT_HEAD_COLOR else self.fg,
|
.fg = if (dot.is_head) self.head_col else self.fg,
|
||||||
.bg = self.terminal_buffer.bg,
|
.bg = self.terminal_buffer.bg,
|
||||||
};
|
};
|
||||||
|
|
||||||
cell.put(x, y - 1);
|
cell.put(x, y - 1);
|
||||||
|
// Fill background in between columns
|
||||||
|
self.default_cell.put(x + 1, y - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn update(self: *Matrix, _: *anyopaque) !void {
|
||||||
|
const time = try interop.getTimeOfDay();
|
||||||
|
|
||||||
|
if (self.timeout_sec > 0 and time.seconds - self.start_time.seconds > self.timeout_sec) {
|
||||||
|
self.animate.* = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculateTimeout(self: *Matrix, _: *anyopaque) !?usize {
|
||||||
|
return self.frame_delay;
|
||||||
|
}
|
||||||
|
|
||||||
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
|
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
|
||||||
var y: usize = 0;
|
var y: usize = 0;
|
||||||
while (y <= height) : (y += 1) {
|
while (y <= height) : (y += 1) {
|
||||||
|
|||||||
487
src/auth.zig
487
src/auth.zig
@@ -1,51 +1,61 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const build_options = @import("build_options");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const enums = @import("enums.zig");
|
|
||||||
const Environment = @import("Environment.zig");
|
|
||||||
const interop = @import("interop.zig");
|
|
||||||
const SharedError = @import("SharedError.zig");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const Md5 = std.crypto.hash.Md5;
|
const Md5 = std.crypto.hash.Md5;
|
||||||
|
const builtin = @import("builtin");
|
||||||
|
const build_options = @import("build_options");
|
||||||
|
|
||||||
|
const ly_core = @import("ly-ui").ly_core;
|
||||||
|
const interop = ly_core.interop;
|
||||||
|
const SharedError = ly_core.SharedError;
|
||||||
|
const LogFile = ly_core.LogFile;
|
||||||
const utmp = interop.utmp;
|
const utmp = interop.utmp;
|
||||||
const Utmp = utmp.utmpx;
|
const Utmp = utmp.utmpx;
|
||||||
|
|
||||||
|
const Environment = @import("Environment.zig");
|
||||||
|
|
||||||
pub const AuthOptions = struct {
|
pub const AuthOptions = struct {
|
||||||
tty: u8,
|
tty: u8,
|
||||||
service_name: [:0]const u8,
|
service_name: [:0]const u8,
|
||||||
path: ?[:0]const u8,
|
path: ?[]const u8,
|
||||||
session_log: []const u8,
|
session_log: ?[]const u8,
|
||||||
xauth_cmd: []const u8,
|
xauth_cmd: []const u8,
|
||||||
setup_cmd: []const u8,
|
setup_cmd: []const u8,
|
||||||
login_cmd: ?[]const u8,
|
login_cmd: ?[]const u8,
|
||||||
x_cmd: []const u8,
|
x_cmd: []const u8,
|
||||||
|
x_vt: ?u8,
|
||||||
session_pid: std.posix.pid_t,
|
session_pid: std.posix.pid_t,
|
||||||
|
use_kmscon_vt: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
var xorg_pid: std.posix.pid_t = 0;
|
var xorg_pid: std.posix.pid_t = 0;
|
||||||
pub fn xorgSignalHandler(i: c_int) callconv(.C) void {
|
pub fn xorgSignalHandler(sig: std.posix.SIG) callconv(.c) void {
|
||||||
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i);
|
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
var child_pid: std.posix.pid_t = 0;
|
var child_pid: std.posix.pid_t = 0;
|
||||||
pub fn sessionSignalHandler(i: c_int) callconv(.C) void {
|
pub fn sessionSignalHandler(sig: std.posix.SIG) callconv(.c) void {
|
||||||
if (child_pid > 0) _ = std.c.kill(child_pid, i);
|
if (child_pid > 0) _ = std.c.kill(child_pid, sig);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn authenticate(options: AuthOptions, current_environment: Environment, login: [:0]const u8, password: [:0]const u8) !void {
|
pub fn authenticate(allocator: std.mem.Allocator, io: std.Io, log_file: *LogFile, options: AuthOptions, current_environment: Environment, login: []const u8, password: []const u8) !void {
|
||||||
var tty_buffer: [3]u8 = undefined;
|
var tty_buffer: [3]u8 = undefined;
|
||||||
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{options.tty});
|
const tty_str = try std.fmt.bufPrint(&tty_buffer, "{d}", .{options.tty});
|
||||||
|
|
||||||
var pam_tty_buffer: [6]u8 = undefined;
|
var pam_tty_buffer: [6]u8 = undefined;
|
||||||
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty});
|
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{options.tty});
|
||||||
|
|
||||||
// Set the XDG environment variables
|
// Set the XDG environment variables
|
||||||
setXdgSessionEnv(current_environment.display_server);
|
try log_file.info(io, "auth/env", "setting xdg environment variables", .{});
|
||||||
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names);
|
try setXdgEnv(allocator, tty_str, current_environment);
|
||||||
|
|
||||||
// Open the PAM session
|
// Open the PAM session
|
||||||
var credentials = [_:null]?[*:0]const u8{ login, password };
|
try log_file.info(io, "auth/pam", "encoding credentials", .{});
|
||||||
|
const login_z = try allocator.dupeZ(u8, login);
|
||||||
|
defer allocator.free(login_z);
|
||||||
|
|
||||||
|
const password_z = try allocator.dupeZ(u8, password);
|
||||||
|
defer allocator.free(password_z);
|
||||||
|
|
||||||
|
var credentials = [_:null]?[*:0]const u8{ login_z, password_z };
|
||||||
|
|
||||||
const conv = interop.pam.pam_conv{
|
const conv = interop.pam.pam_conv{
|
||||||
.conv = loginConv,
|
.conv = loginConv,
|
||||||
@@ -53,53 +63,66 @@ pub fn authenticate(options: AuthOptions, current_environment: Environment, logi
|
|||||||
};
|
};
|
||||||
var handle: ?*interop.pam.pam_handle = undefined;
|
var handle: ?*interop.pam.pam_handle = undefined;
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/pam", "starting session", .{});
|
||||||
var status = interop.pam.pam_start(options.service_name, null, &conv, &handle);
|
var status = interop.pam.pam_start(options.service_name, null, &conv, &handle);
|
||||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||||
defer _ = interop.pam.pam_end(handle, status);
|
defer _ = interop.pam.pam_end(handle, status);
|
||||||
|
|
||||||
// Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module
|
// Set PAM_TTY as the current TTY. This is required in case it isn't being set by another PAM module
|
||||||
|
try log_file.info(io, "auth/pam", "setting tty", .{});
|
||||||
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
|
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
|
||||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||||
|
|
||||||
// Do the PAM routine
|
// Do the PAM routine
|
||||||
|
try log_file.info(io, "auth/pam", "authenticating", .{});
|
||||||
status = interop.pam.pam_authenticate(handle, 0);
|
status = interop.pam.pam_authenticate(handle, 0);
|
||||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/pam", "validating account", .{});
|
||||||
status = interop.pam.pam_acct_mgmt(handle, 0);
|
status = interop.pam.pam_acct_mgmt(handle, 0);
|
||||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/pam", "setting credentials", .{});
|
||||||
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
|
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
|
||||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||||
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
|
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/pam", "opening session", .{});
|
||||||
status = interop.pam.pam_open_session(handle, 0);
|
status = interop.pam.pam_open_session(handle, 0);
|
||||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||||
defer status = interop.pam.pam_close_session(handle, 0);
|
defer status = interop.pam.pam_close_session(handle, 0);
|
||||||
|
|
||||||
var pwd: *interop.pwd.passwd = undefined;
|
try log_file.info(io, "auth/passwd", "getting struct", .{});
|
||||||
|
var user_entry: interop.UsernameEntry = undefined;
|
||||||
{
|
{
|
||||||
defer interop.pwd.endpwent();
|
defer interop.closePasswordDatabase();
|
||||||
|
|
||||||
// Get password structure from username
|
// Get password structure from username
|
||||||
pwd = interop.pwd.getpwnam(login) orelse return error.GetPasswordNameFailed;
|
user_entry = interop.getUsernameEntry(login_z) orelse return error.GetPasswordNameFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set user shell if it hasn't already been set
|
// Set user shell if it hasn't already been set
|
||||||
if (pwd.pw_shell == null) {
|
try log_file.info(io, "auth/passwd", "setting user shell", .{});
|
||||||
interop.unistd.setusershell();
|
if (user_entry.shell == null) interop.setUserShell(&user_entry);
|
||||||
pwd.pw_shell = interop.unistd.getusershell();
|
|
||||||
interop.unistd.endusershell();
|
|
||||||
}
|
|
||||||
|
|
||||||
var shared_err = try SharedError.init();
|
var shared_err = try SharedError.init(null, null);
|
||||||
defer shared_err.deinit();
|
defer shared_err.deinit();
|
||||||
|
|
||||||
child_pid = try std.posix.fork();
|
log_file.deinit(io);
|
||||||
|
|
||||||
|
child_pid = std.posix.system.fork();
|
||||||
if (child_pid == 0) {
|
if (child_pid == 0) {
|
||||||
startSession(options, tty_str, pwd, handle, current_environment) catch |e| {
|
try log_file.reinit(io);
|
||||||
|
try log_file.info(io, "auth/sys", "starting session", .{});
|
||||||
|
|
||||||
|
startSession(log_file, allocator, io, options, tty_str, user_entry, handle, current_environment) catch |e| {
|
||||||
shared_err.writeError(e);
|
shared_err.writeError(e);
|
||||||
|
|
||||||
|
log_file.deinit(io);
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
log_file.deinit(io);
|
||||||
std.process.exit(0);
|
std.process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,123 +131,134 @@ pub fn authenticate(options: AuthOptions, current_environment: Environment, logi
|
|||||||
{
|
{
|
||||||
// If an error occurs here, we can send SIGTERM to the session
|
// If an error occurs here, we can send SIGTERM to the session
|
||||||
errdefer cleanup: {
|
errdefer cleanup: {
|
||||||
_ = std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup;
|
std.posix.kill(child_pid, std.posix.SIG.TERM) catch break :cleanup;
|
||||||
_ = std.posix.waitpid(child_pid, 0);
|
var child_status: c_int = undefined;
|
||||||
|
_ = std.posix.system.waitpid(child_pid, &child_status, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we receive SIGTERM, forward it to child_pid
|
// If we receive SIGTERM, forward it to child_pid
|
||||||
const act = std.posix.Sigaction{
|
const act = std.posix.Sigaction{
|
||||||
.handler = .{ .handler = &sessionSignalHandler },
|
.handler = .{ .handler = &sessionSignalHandler },
|
||||||
.mask = std.posix.empty_sigset,
|
.mask = std.posix.sigemptyset(),
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
};
|
};
|
||||||
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
||||||
|
|
||||||
try addUtmpEntry(&entry, pwd.pw_name.?, child_pid);
|
try addUtmpEntry(io, &entry, user_entry.username.?, child_pid);
|
||||||
}
|
}
|
||||||
// Wait for the session to stop
|
// Wait for the session to stop
|
||||||
_ = std.posix.waitpid(child_pid, 0);
|
var child_status: c_int = undefined;
|
||||||
|
_ = std.posix.system.waitpid(child_pid, &child_status, 0);
|
||||||
|
|
||||||
|
try log_file.reinit(io);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/utmp", "removing utmp entry", .{});
|
||||||
removeUtmpEntry(&entry);
|
removeUtmpEntry(&entry);
|
||||||
|
|
||||||
if (shared_err.readError()) |err| return err;
|
if (shared_err.readError()) |err| return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn startSession(
|
fn startSession(
|
||||||
|
log_file: *LogFile,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
io: std.Io,
|
||||||
options: AuthOptions,
|
options: AuthOptions,
|
||||||
tty_str: [:0]u8,
|
tty_str: []u8,
|
||||||
pwd: *interop.pwd.passwd,
|
user_entry: interop.UsernameEntry,
|
||||||
handle: ?*interop.pam.pam_handle,
|
handle: ?*interop.pam.pam_handle,
|
||||||
current_environment: Environment,
|
current_environment: Environment,
|
||||||
) !void {
|
) !void {
|
||||||
if (builtin.os.tag == .freebsd) {
|
// Set the user's GID & PID
|
||||||
// FreeBSD has initgroups() in unistd
|
try log_file.info(io, "auth/passwd", "setting user context", .{});
|
||||||
const status = interop.unistd.initgroups(pwd.pw_name, pwd.pw_gid);
|
try interop.setUserContext(allocator, user_entry);
|
||||||
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
|
// Set up the environment
|
||||||
try initEnv(pwd, options.path);
|
try log_file.info(io, "auth/env", "setting environment variables", .{});
|
||||||
|
try initEnv(allocator, user_entry, options.path);
|
||||||
|
|
||||||
// Reset the XDG environment variables
|
// Reset the XDG environment variables
|
||||||
setXdgSessionEnv(current_environment.display_server);
|
try log_file.info(io, "auth/env", "resetting xdg environment variables", .{});
|
||||||
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names);
|
try setXdgEnv(allocator, tty_str, current_environment);
|
||||||
|
try setXdgRuntimeDir(allocator);
|
||||||
|
|
||||||
// Set the PAM variables
|
// Set the PAM variables
|
||||||
const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle);
|
const pam_env_vars: ?[*:null]?[*:0]u8 = interop.pam.pam_getenvlist(handle);
|
||||||
if (pam_env_vars == null) return error.GetEnvListFailed;
|
if (pam_env_vars == null) return error.GetEnvListFailed;
|
||||||
|
|
||||||
const env_list = std.mem.span(pam_env_vars.?);
|
const env_list = std.mem.span(pam_env_vars.?);
|
||||||
for (env_list) |env_var| _ = interop.stdlib.putenv(env_var);
|
for (env_list) |env_var| {
|
||||||
|
if (env_var == null) continue;
|
||||||
|
try log_file.info(io, "auth/env", "setting pam environment variable: {s}", .{std.mem.span(env_var.?)});
|
||||||
|
try interop.putEnvironmentVariable(env_var);
|
||||||
|
}
|
||||||
|
|
||||||
|
const home_z = try allocator.dupeZ(u8, user_entry.home.?);
|
||||||
|
defer allocator.free(home_z);
|
||||||
|
|
||||||
// Change to the user's home directory
|
// Change to the user's home directory
|
||||||
std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed;
|
try log_file.info(io, "auth/sys", "changing cwd to user home", .{});
|
||||||
|
if (std.posix.system.chdir(home_z.ptr) < 0) return error.ChangeDirectoryFailed;
|
||||||
|
|
||||||
// Signal to the session process to give up control on the TTY
|
// Signal to the session process to give up control on the TTY
|
||||||
_ = std.posix.kill(options.session_pid, std.posix.SIG.CHLD) catch return error.TtyControlTransferFailed;
|
try log_file.info(io, "auth/sys", "releasing tty", .{});
|
||||||
|
std.posix.kill(options.session_pid, std.posix.SIG.INT) catch return error.TtyControlTransferFailed;
|
||||||
|
|
||||||
// Execute what the user requested
|
// Execute what the user requested
|
||||||
switch (current_environment.display_server) {
|
switch (current_environment.display_server) {
|
||||||
.wayland => try executeWaylandCmd(pwd.pw_shell.?, options, current_environment.cmd),
|
.wayland, .shell, .custom => try executeCmd(log_file, allocator, io, user_entry.shell.?, options, current_environment.is_terminal, current_environment.cmd),
|
||||||
.shell => try executeShellCmd(pwd.pw_shell.?, options),
|
|
||||||
.xinitrc, .x11 => if (build_options.enable_x11_support) {
|
.xinitrc, .x11 => if (build_options.enable_x11_support) {
|
||||||
var vt_buf: [5]u8 = undefined;
|
var vt_buf: [5]u8 = undefined;
|
||||||
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.tty});
|
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{options.x_vt orelse options.tty});
|
||||||
try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, options, current_environment.cmd, vt);
|
|
||||||
|
try log_file.info(io, "auth/x11", "setting vt to {s}", .{vt});
|
||||||
|
try executeX11Cmd(log_file, allocator, io, user_entry.shell.?, user_entry.home.?, options, current_environment.cmd orelse "", vt);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initEnv(pwd: *interop.pwd.passwd, path_env: ?[:0]const u8) !void {
|
fn initEnv(allocator: std.mem.Allocator, entry: interop.UsernameEntry, path_env: ?[]const u8) !void {
|
||||||
_ = interop.stdlib.setenv("HOME", pwd.pw_dir, 1);
|
if (entry.home) |home| {
|
||||||
_ = interop.stdlib.setenv("PWD", pwd.pw_dir, 1);
|
try interop.setEnvironmentVariable(allocator, "HOME", home, true);
|
||||||
_ = interop.stdlib.setenv("SHELL", pwd.pw_shell, 1);
|
try interop.setEnvironmentVariable(allocator, "PWD", home, true);
|
||||||
_ = interop.stdlib.setenv("USER", pwd.pw_name, 1);
|
} else return error.NoHomeDirectory;
|
||||||
_ = interop.stdlib.setenv("LOGNAME", pwd.pw_name, 1);
|
|
||||||
|
try interop.setEnvironmentVariable(allocator, "SHELL", entry.shell.?, true);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "USER", entry.username.?, true);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "LOGNAME", entry.username.?, true);
|
||||||
|
|
||||||
if (path_env) |path| {
|
if (path_env) |path| {
|
||||||
const status = interop.stdlib.setenv("PATH", path, 1);
|
interop.setEnvironmentVariable(allocator, "PATH", path, true) catch return error.SetPathFailed;
|
||||||
if (status != 0) return error.SetPathFailed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
|
fn setXdgEnv(allocator: std.mem.Allocator, tty_str: []u8, environment: Environment) !void {
|
||||||
_ = interop.stdlib.setenv("XDG_SESSION_TYPE", switch (display_server) {
|
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_TYPE", switch (environment.display_server) {
|
||||||
.wayland => "wayland",
|
.wayland => "wayland",
|
||||||
.shell => "tty",
|
.shell => "tty",
|
||||||
.xinitrc, .x11 => "x11",
|
.xinitrc, .x11 => "x11",
|
||||||
}, 0);
|
.custom => if (environment.is_terminal) "tty" else "unspecified",
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
if (environment.xdg_desktop_names) |xdg_desktop_names| try interop.setEnvironmentVariable(allocator, "XDG_CURRENT_DESKTOP", xdg_desktop_names, false);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_CLASS", "user", false);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "XDG_SESSION_ID", "1", false);
|
||||||
|
if (environment.xdg_session_desktop) |desktop_name| try interop.setEnvironmentVariable(allocator, "XDG_SESSION_DESKTOP", desktop_name, false);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "XDG_SEAT", "seat0", false);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "XDG_VTNR", tty_str, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setXdgEnv(tty_str: [:0]u8, maybe_desktop_name: ?[:0]const u8, maybe_xdg_desktop_names: ?[:0]const u8) !void {
|
fn setXdgRuntimeDir(allocator: std.mem.Allocator) !void {
|
||||||
// The "/run/user/%d" directory is not available on FreeBSD. It is much
|
// The "/run/user/%d" directory is not available on FreeBSD. It is much
|
||||||
// better to stick to the defaults and let applications using
|
// better to stick to the defaults and let applications using
|
||||||
// XDG_RUNTIME_DIR to fall back to directories inside user's home
|
// XDG_RUNTIME_DIR to fall back to directories inside user's home
|
||||||
// directory.
|
// directory.
|
||||||
if (builtin.os.tag != .freebsd) {
|
if (builtin.os.tag != .freebsd) {
|
||||||
const uid = interop.unistd.getuid();
|
const uid = std.posix.system.getuid();
|
||||||
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined;
|
var uid_buffer: [32]u8 = undefined; // No UID can be larger than this
|
||||||
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
|
const uid_str = try std.fmt.bufPrint(&uid_buffer, "/run/user/{d}", .{uid});
|
||||||
|
|
||||||
_ = interop.stdlib.setenv("XDG_RUNTIME_DIR", uid_str, 0);
|
try interop.setEnvironmentVariable(allocator, "XDG_RUNTIME_DIR", uid_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(
|
fn loginConv(
|
||||||
@@ -232,7 +266,7 @@ fn loginConv(
|
|||||||
msg: ?[*]?*const interop.pam.pam_message,
|
msg: ?[*]?*const interop.pam.pam_message,
|
||||||
resp: ?*?[*]interop.pam.pam_response,
|
resp: ?*?[*]interop.pam.pam_response,
|
||||||
appdata_ptr: ?*anyopaque,
|
appdata_ptr: ?*anyopaque,
|
||||||
) callconv(.C) c_int {
|
) callconv(.c) c_int {
|
||||||
const message_count: u32 = @intCast(num_msg);
|
const message_count: u32 = @intCast(num_msg);
|
||||||
const messages = msg.?;
|
const messages = msg.?;
|
||||||
|
|
||||||
@@ -276,8 +310,8 @@ fn loginConv(
|
|||||||
if (status != interop.pam.PAM_SUCCESS) {
|
if (status != interop.pam.PAM_SUCCESS) {
|
||||||
// Memory is freed by pam otherwise
|
// Memory is freed by pam otherwise
|
||||||
allocator.free(response);
|
allocator.free(response);
|
||||||
if (username != null) allocator.free(username.?);
|
if (username) |str| allocator.free(str);
|
||||||
if (password != null) allocator.free(password.?);
|
if (password) |str| allocator.free(str);
|
||||||
} else {
|
} else {
|
||||||
resp.?.* = response.ptr;
|
resp.?.* = response.ptr;
|
||||||
}
|
}
|
||||||
@@ -289,60 +323,64 @@ fn getFreeDisplay() !u8 {
|
|||||||
var buf: [15]u8 = undefined;
|
var buf: [15]u8 = undefined;
|
||||||
var i: u8 = 0;
|
var i: u8 = 0;
|
||||||
while (i < 200) : (i += 1) {
|
while (i < 200) : (i += 1) {
|
||||||
const xlock = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{i});
|
const xlock = try std.fmt.bufPrintZ(&buf, "/tmp/.X{d}-lock", .{i});
|
||||||
std.posix.access(xlock, std.posix.F_OK) catch break;
|
if (interop.isError(std.posix.system.access(xlock.ptr, std.posix.F_OK))) break;
|
||||||
}
|
}
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getXPid(display_num: u8) !i32 {
|
fn getXPid(io: std.Io, display_num: u8) !i32 {
|
||||||
var buf: [15]u8 = undefined;
|
var buf: [15]u8 = undefined;
|
||||||
const file_name = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{display_num});
|
const file_name = try std.fmt.bufPrint(&buf, "/tmp/.X{d}-lock", .{display_num});
|
||||||
const file = try std.fs.openFileAbsolute(file_name, .{});
|
const file = try std.Io.Dir.openFileAbsolute(io, file_name, .{});
|
||||||
defer file.close();
|
defer file.close(io);
|
||||||
|
|
||||||
var file_buf: [20]u8 = undefined;
|
var file_buffer: [32]u8 = undefined;
|
||||||
var fbs = std.io.fixedBufferStream(&file_buf);
|
var file_reader = file.reader(io, &file_buffer);
|
||||||
|
var reader = &file_reader.interface;
|
||||||
|
|
||||||
_ = try file.reader().streamUntilDelimiter(fbs.writer(), '\n', 20);
|
var buffer: [20]u8 = undefined;
|
||||||
const line = fbs.getWritten();
|
var writer = std.Io.Writer.fixed(&buffer);
|
||||||
|
|
||||||
return std.fmt.parseInt(i32, std.mem.trim(u8, line, " "), 10);
|
const written = try reader.streamDelimiter(&writer, '\n');
|
||||||
|
return std.fmt.parseInt(i32, std.mem.trim(u8, buffer[0..written], " "), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
|
fn createXauthFile(log_file: *LogFile, io: std.Io, pwd: []const u8, buffer: []u8) ![]const u8 {
|
||||||
var xauth_buf: [100]u8 = undefined;
|
var xauth_buf: [100]u8 = undefined;
|
||||||
var xauth_dir: [:0]const u8 = undefined;
|
var xauth_dir: []const u8 = undefined;
|
||||||
const xdg_rt_dir = std.posix.getenv("XDG_RUNTIME_DIR");
|
const xdg_rt_dir = std.posix.system.getenv("XDG_RUNTIME_DIR");
|
||||||
var xauth_file: []const u8 = "lyxauth";
|
var xauth_file: []const u8 = "lyxauth";
|
||||||
|
|
||||||
if (xdg_rt_dir == null) {
|
if (xdg_rt_dir == null) no_rt_dir: {
|
||||||
const xdg_cfg_home = std.posix.getenv("XDG_CONFIG_HOME");
|
const xdg_cfg_home = std.posix.system.getenv("XDG_CONFIG_HOME");
|
||||||
var sb: std.c.Stat = undefined;
|
if (xdg_cfg_home == null) no_cfg_home: {
|
||||||
if (xdg_cfg_home == null) {
|
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config", .{pwd});
|
||||||
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/.config", .{pwd});
|
|
||||||
_ = std.c.stat(xauth_dir, &sb);
|
var dir = std.Io.Dir.cwd().openDir(io, xauth_dir, .{}) catch {
|
||||||
const mode = sb.mode & std.posix.S.IFMT;
|
// xauth_dir isn't a directory
|
||||||
if (mode == std.posix.S.IFDIR) {
|
|
||||||
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xauth_dir});
|
|
||||||
} else {
|
|
||||||
xauth_dir = pwd;
|
xauth_dir = pwd;
|
||||||
xauth_file = ".lyxauth";
|
xauth_file = ".lyxauth";
|
||||||
}
|
break :no_cfg_home;
|
||||||
|
};
|
||||||
|
dir.close(io);
|
||||||
|
|
||||||
|
// xauth_dir is a directory, use it to store Xauthority
|
||||||
|
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/.config/ly", .{pwd});
|
||||||
} else {
|
} else {
|
||||||
xauth_dir = try std.fmt.bufPrintZ(&xauth_buf, "{s}/ly", .{xdg_cfg_home.?});
|
xauth_dir = try std.fmt.bufPrint(&xauth_buf, "{s}/ly", .{std.mem.span(xdg_cfg_home.?)});
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = std.c.stat(xauth_dir, &sb);
|
const file = std.Io.Dir.cwd().openFile(io, xauth_dir, .{}) catch break :no_rt_dir;
|
||||||
const mode = sb.mode & std.posix.S.IFMT;
|
file.close(io);
|
||||||
if (mode != std.posix.S.IFDIR) {
|
|
||||||
std.posix.mkdir(xauth_dir, 777) catch {
|
// xauth_dir is a file, create the parent directory
|
||||||
|
std.Io.Dir.createDirAbsolute(io, xauth_dir, .fromMode(777)) catch {
|
||||||
xauth_dir = pwd;
|
xauth_dir = pwd;
|
||||||
xauth_file = ".lyxauth";
|
xauth_file = ".lyxauth";
|
||||||
};
|
};
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
xauth_dir = xdg_rt_dir.?;
|
xauth_dir = std.mem.span(xdg_rt_dir.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim trailing slashes
|
// Trim trailing slashes
|
||||||
@@ -350,17 +388,21 @@ fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
|
|||||||
while (xauth_dir[i] == '/') i -= 1;
|
while (xauth_dir[i] == '/') i -= 1;
|
||||||
const trimmed_xauth_dir = xauth_dir[0 .. i + 1];
|
const trimmed_xauth_dir = xauth_dir[0 .. i + 1];
|
||||||
|
|
||||||
var buf: [256]u8 = undefined;
|
const xauthority: []u8 = try std.fmt.bufPrint(buffer, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file });
|
||||||
const xauthority: [:0]u8 = try std.fmt.bufPrintZ(&buf, "{s}/{s}", .{ trimmed_xauth_dir, xauth_file });
|
|
||||||
const file = try std.fs.createFileAbsoluteZ(xauthority, .{});
|
std.Io.Dir.cwd().createDirPath(io, trimmed_xauth_dir) catch {};
|
||||||
file.close();
|
|
||||||
|
try log_file.info(io, "auth/x11", "creating xauth file: {s}", .{xauthority});
|
||||||
|
|
||||||
|
const file = try std.Io.Dir.createFileAbsolute(io, xauthority, .{});
|
||||||
|
file.close(io);
|
||||||
|
|
||||||
return xauthority;
|
return xauthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mcookie() [Md5.digest_length * 2]u8 {
|
fn mcookie(io: std.Io) [Md5.digest_length * 2]u8 {
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [4096]u8 = undefined;
|
||||||
std.crypto.random.bytes(&buf);
|
io.random(&buf);
|
||||||
|
|
||||||
var out: [Md5.digest_length]u8 = undefined;
|
var out: [Md5.digest_length]u8 = undefined;
|
||||||
Md5.hash(&buf, &out, .{});
|
Md5.hash(&buf, &out, .{});
|
||||||
@@ -368,125 +410,189 @@ fn mcookie() [Md5.digest_length * 2]u8 {
|
|||||||
return std.fmt.bytesToHex(&out, .lower);
|
return std.fmt.bytesToHex(&out, .lower);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, options: AuthOptions) !void {
|
fn xauth(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, display_name: []u8, shell: [*:0]const u8, home: []const u8, xauth_buffer: []u8, options: AuthOptions) ![]const u8 {
|
||||||
var pwd_buf: [100]u8 = undefined;
|
const xauthority = try createXauthFile(log_file, io, home, xauth_buffer);
|
||||||
const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir});
|
try interop.setEnvironmentVariable(allocator, "XAUTHORITY", xauthority, true);
|
||||||
|
try interop.setEnvironmentVariable(allocator, "DISPLAY", display_name, true);
|
||||||
|
|
||||||
const xauthority = try createXauthFile(pwd);
|
const magic_cookie = mcookie(io);
|
||||||
_ = interop.stdlib.setenv("XAUTHORITY", xauthority, 1);
|
|
||||||
_ = interop.stdlib.setenv("DISPLAY", display_name, 1);
|
|
||||||
|
|
||||||
const magic_cookie = mcookie();
|
const pid = std.posix.system.fork();
|
||||||
|
|
||||||
const pid = try std.posix.fork();
|
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
var cmd_buffer: [1024]u8 = undefined;
|
var cmd_buffer: [1024]u8 = undefined;
|
||||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ options.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1);
|
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ options.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str });
|
||||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
||||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
_ = std.posix.system.execve(shell, &args, std.c.environ);
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = std.posix.waitpid(pid, 0);
|
var status: c_int = undefined;
|
||||||
if (status.status != 0) return error.XauthFailed;
|
const result = std.posix.system.waitpid(pid, &status, 0);
|
||||||
|
if (interop.isError(result) or status != 0) {
|
||||||
|
try log_file.err(
|
||||||
|
io,
|
||||||
|
"auth/x11",
|
||||||
|
"xauth command failed with status: {d}",
|
||||||
|
.{status},
|
||||||
|
);
|
||||||
|
return error.XauthFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xauthority;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn executeShellCmd(shell: [*:0]const u8, options: AuthOptions) !void {
|
fn executeX11Cmd(log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, shell: []const u8, home: []const u8, options: AuthOptions, desktop_cmd: []const u8, vt: []const u8) !void {
|
||||||
// We don't want to redirect stdout and stderr in a shell session
|
var xauth_buffer: [256]u8 = undefined;
|
||||||
|
|
||||||
var cmd_buffer: [1024]u8 = undefined;
|
try log_file.info(io, "auth/x11", "getting free display", .{});
|
||||||
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();
|
const display_num = try getFreeDisplay();
|
||||||
var buf: [5]u8 = undefined;
|
var buf: [4]u8 = undefined;
|
||||||
const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num});
|
const display_name = try std.fmt.bufPrint(&buf, ":{d}", .{display_num});
|
||||||
try xauth(display_name, shell, pw_dir, options);
|
try log_file.info(io, "auth/x11", "got free display: {d}", .{display_num});
|
||||||
|
|
||||||
const pid = try std.posix.fork();
|
const shell_z = try allocator.dupeZ(u8, shell);
|
||||||
|
defer allocator.free(shell_z);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/x11", "creating xauth file", .{});
|
||||||
|
const xauthority = try xauth(log_file, allocator, io, display_name, shell_z, home, &xauth_buffer, options);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/x11", "starting x server", .{});
|
||||||
|
const pid = std.posix.system.fork();
|
||||||
if (pid == 0) {
|
if (pid == 0) {
|
||||||
var cmd_buffer: [1024]u8 = undefined;
|
var cmd_buffer: [1024]u8 = undefined;
|
||||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.x_cmd, display_name, vt }) catch std.process.exit(1);
|
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} -auth {s}", .{ options.x_cmd, display_name, vt, xauthority }) catch std.process.exit(1);
|
||||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
try log_file.info(io, "auth/x11", "executing: {s} -c {s} -auth {s}", .{ shell, cmd_str, xauthority });
|
||||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
|
||||||
|
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
|
||||||
|
_ = std.posix.system.execve(shell_z, &args, std.c.environ);
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok: c_int = undefined;
|
try log_file.info(io, "auth/x11", "waiting for xcb connection", .{});
|
||||||
|
var ok: c_int = -1;
|
||||||
var xcb: ?*interop.xcb.xcb_connection_t = null;
|
var xcb: ?*interop.xcb.xcb_connection_t = null;
|
||||||
while (ok != 0) {
|
while (ok != 0) {
|
||||||
xcb = interop.xcb.xcb_connect(null, null);
|
xcb = interop.xcb.xcb_connect(null, null);
|
||||||
ok = interop.xcb.xcb_connection_has_error(xcb);
|
ok = interop.xcb.xcb_connection_has_error(xcb);
|
||||||
std.posix.kill(pid, 0) catch |e| {
|
std.posix.kill(pid, @enumFromInt(0)) catch |e| {
|
||||||
if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed;
|
if (e == error.ProcessNotFound and ok != 0) return error.XcbConnectionFailed;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// X Server detaches from the process.
|
// X Server detaches from the process.
|
||||||
// PID can be fetched from /tmp/X{d}.lock
|
// PID can be fetched from /tmp/X{d}.lock
|
||||||
const x_pid = try getXPid(display_num);
|
try log_file.info(io, "auth/x11", "getting x server pid", .{});
|
||||||
|
const x_pid = try getXPid(io, display_num);
|
||||||
|
try log_file.info(io, "auth/x11", "got x server pid: {d}", .{x_pid});
|
||||||
|
|
||||||
xorg_pid = try std.posix.fork();
|
try log_file.info(io, "auth/x11", "launching environment", .{});
|
||||||
|
xorg_pid = std.posix.system.fork();
|
||||||
if (xorg_pid == 0) {
|
if (xorg_pid == 0) {
|
||||||
var cmd_buffer: [1024]u8 = undefined;
|
var cmd_buffer: [1024]u8 = undefined;
|
||||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ options.setup_cmd, options.login_cmd orelse "", desktop_cmd }) catch std.process.exit(1);
|
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} {s}", .{ if (options.use_kmscon_vt) "kmscon-launch-gui" else "", options.setup_cmd, options.login_cmd orelse "", desktop_cmd }) catch std.process.exit(1);
|
||||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
try log_file.info(io, "auth/x11", "executing: {s} -c {s}", .{ shell, cmd_str });
|
||||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
|
||||||
|
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
|
||||||
|
_ = std.posix.system.execve(shell_z, &args, std.c.environ);
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we receive SIGTERM, clean up by killing the xorg_pid process
|
// If we receive SIGTERM, clean up by killing the xorg_pid process
|
||||||
const act = std.posix.Sigaction{
|
const act = std.posix.Sigaction{
|
||||||
.handler = .{ .handler = &xorgSignalHandler },
|
.handler = .{ .handler = &xorgSignalHandler },
|
||||||
.mask = std.posix.empty_sigset,
|
.mask = std.posix.sigemptyset(),
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
};
|
};
|
||||||
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
||||||
|
|
||||||
_ = std.posix.waitpid(xorg_pid, 0);
|
var xorg_status: c_int = undefined;
|
||||||
|
_ = std.posix.system.waitpid(xorg_pid, &xorg_status, 0);
|
||||||
|
|
||||||
|
try log_file.info(io, "auth/x11", "disconnecting xcb", .{});
|
||||||
interop.xcb.xcb_disconnect(xcb);
|
interop.xcb.xcb_disconnect(xcb);
|
||||||
|
|
||||||
std.posix.kill(x_pid, 0) catch return;
|
// TODO: Find a more robust way to ensure that X has been terminated (pidfds?)
|
||||||
std.posix.kill(x_pid, std.posix.SIG.KILL) catch {};
|
std.posix.kill(x_pid, std.posix.SIG.TERM) catch {};
|
||||||
|
io.sleep(.fromSeconds(1), .real) catch {}; // Wait 1 second before sending SIGKILL
|
||||||
|
std.posix.kill(x_pid, std.posix.SIG.KILL) catch return;
|
||||||
|
|
||||||
var status: c_int = 0;
|
var x_status: c_int = undefined;
|
||||||
_ = std.c.waitpid(x_pid, &status, 0);
|
_ = std.posix.system.waitpid(x_pid, &x_status, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn redirectStandardStreams(session_log: []const u8, create: bool) !std.fs.File {
|
fn executeCmd(global_log_file: *LogFile, allocator: std.mem.Allocator, io: std.Io, shell: []const u8, options: AuthOptions, is_terminal: bool, exec_cmd: ?[]const u8) !void {
|
||||||
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 global_log_file.info(io, "auth/sys", "launching wayland/shell/custom session", .{});
|
||||||
|
|
||||||
try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO);
|
var maybe_log_file: ?std.Io.File = null;
|
||||||
try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO);
|
if (!is_terminal) redirect_streams: {
|
||||||
|
if (options.use_kmscon_vt) {
|
||||||
|
try global_log_file.err(io, "auth/sys", "cannot redirect stdio & stderr with kmscon", .{});
|
||||||
|
break :redirect_streams;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For custom desktop entries, the "Terminal" value here determines if
|
||||||
|
// 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| {
|
||||||
|
try global_log_file.info(io, "auth/sys", "setting up stdio & stderr redirection", .{});
|
||||||
|
maybe_log_file = try redirectStandardStreams(global_log_file, io, log_path, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer if (maybe_log_file) |log_file| log_file.close(io);
|
||||||
|
|
||||||
|
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} {s}", .{ if (!is_terminal and options.use_kmscon_vt) "kmscon-launch-gui" else "", options.setup_cmd, options.login_cmd orelse "", exec_cmd orelse shell });
|
||||||
|
|
||||||
|
try global_log_file.info(io, "auth/sys", "executing: {s} -c {s}", .{ shell, cmd_str });
|
||||||
|
const args = [_:null]?[*:0]const u8{ shell_z, "-c", cmd_str };
|
||||||
|
_ = std.posix.system.execve(shell_z, &args, std.c.environ);
|
||||||
|
return error.CmdExecveFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn redirectStandardStreams(global_log_file: *LogFile, io: std.Io, session_log: []const u8, create: bool) !std.Io.File {
|
||||||
|
create_session_log_dir: {
|
||||||
|
const session_log_dir = std.Io.Dir.path.dirname(session_log) orelse break :create_session_log_dir;
|
||||||
|
std.Io.Dir.cwd().createDirPath(io, session_log_dir) catch |err| {
|
||||||
|
try global_log_file.err(io, "auth/sys", "failed to create session log file directory: {s}", .{@errorName(err)});
|
||||||
|
return err;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const log_file = if (create) (std.Io.Dir.cwd().createFile(io, session_log, .{ .permissions = .fromMode(0o666) }) catch |err| {
|
||||||
|
try global_log_file.err(io, "auth/sys", "failed to create new session log file: {s}", .{@errorName(err)});
|
||||||
|
return err;
|
||||||
|
}) else (std.Io.Dir.cwd().openFile(io, session_log, .{ .mode = .read_write }) catch |err| {
|
||||||
|
try global_log_file.err(io, "auth/sys", "failed to open existing session log file: {s}", .{@errorName(err)});
|
||||||
|
return err;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (interop.isError(std.posix.system.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO))) return error.StdoutDup2Failed;
|
||||||
|
if (interop.isError(std.posix.system.dup2(log_file.handle, std.posix.STDOUT_FILENO))) return error.LogFileDup2Failed;
|
||||||
|
|
||||||
return log_file;
|
return log_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
|
fn addUtmpEntry(io: std.Io, entry: *Utmp, username: []const u8, pid: c_int) !void {
|
||||||
entry.ut_type = utmp.USER_PROCESS;
|
entry.ut_type = utmp.USER_PROCESS;
|
||||||
entry.ut_pid = pid;
|
entry.ut_pid = pid;
|
||||||
|
|
||||||
var buf: [4096]u8 = undefined;
|
var buf: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
||||||
const ttyname = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf);
|
const length = try std.Io.File.stdin().realPath(io, &buf);
|
||||||
|
const tty_path = buf[0..length];
|
||||||
|
|
||||||
|
// Get the TTY name (i.e. without the /dev/ prefix)
|
||||||
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;
|
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;
|
||||||
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]});
|
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{tty_path["/dev/".len..]});
|
||||||
|
|
||||||
entry.ut_line = ttyname_buf;
|
entry.ut_line = ttyname_buf;
|
||||||
entry.ut_id = ttyname_buf["tty".len..7].*;
|
// Get the TTY ID (i.e. without the tty prefix) and truncate it to the size
|
||||||
|
// of ut_id if necessary
|
||||||
|
entry.ut_id = ttyname_buf["tty".len..(@sizeOf(@TypeOf(entry.ut_id)) + "tty".len)].*;
|
||||||
|
|
||||||
var username_buf: [@sizeOf(@TypeOf(entry.ut_user))]u8 = undefined;
|
var username_buf: [@sizeOf(@TypeOf(entry.ut_user))]u8 = undefined;
|
||||||
_ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username});
|
_ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username});
|
||||||
@@ -497,14 +603,17 @@ fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
|
|||||||
host[0] = 0;
|
host[0] = 0;
|
||||||
entry.ut_host = host;
|
entry.ut_host = host;
|
||||||
|
|
||||||
var tv: interop.system_time.timeval = undefined;
|
const time = try interop.getTimeOfDay();
|
||||||
_ = interop.system_time.gettimeofday(&tv, null);
|
|
||||||
|
|
||||||
entry.ut_tv = .{
|
entry.ut_tv = .{
|
||||||
.tv_sec = @intCast(tv.tv_sec),
|
.tv_sec = @intCast(time.seconds),
|
||||||
.tv_usec = @intCast(tv.tv_usec),
|
.tv_usec = @intCast(time.microseconds),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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.setutxent();
|
||||||
_ = utmp.pututxline(entry);
|
_ = utmp.pututxline(entry);
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const interop = @import("interop.zig");
|
|
||||||
const enums = @import("enums.zig");
|
|
||||||
const Lang = @import("bigclock/Lang.zig");
|
|
||||||
const en = @import("bigclock/en.zig");
|
|
||||||
const fa = @import("bigclock/fa.zig");
|
|
||||||
const Cell = @import("tui/Cell.zig");
|
|
||||||
|
|
||||||
const Bigclock = enums.Bigclock;
|
|
||||||
pub const WIDTH = Lang.WIDTH;
|
|
||||||
pub const HEIGHT = Lang.HEIGHT;
|
|
||||||
pub const SIZE = Lang.SIZE;
|
|
||||||
|
|
||||||
pub fn clockCell(animate: bool, char: u8, fg: u32, bg: u32, bigclock: Bigclock) [SIZE]Cell {
|
|
||||||
var cells: [SIZE]Cell = undefined;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [SIZE]Cell) void {
|
|
||||||
if (x + WIDTH >= tb_width or y + HEIGHT >= tb_height) return;
|
|
||||||
|
|
||||||
for (0..HEIGHT) |yy| {
|
|
||||||
for (0..WIDTH) |xx| {
|
|
||||||
const cell = cells[yy * WIDTH + xx];
|
|
||||||
cell.put(x + xx, y + yy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toBigNumber(char: u8, bigclock: Bigclock) [SIZE]u21 {
|
|
||||||
const locale_chars = switch (bigclock) {
|
|
||||||
.fa => fa.locale_chars,
|
|
||||||
.en => en.locale_chars,
|
|
||||||
.none => unreachable,
|
|
||||||
};
|
|
||||||
return switch (char) {
|
|
||||||
'0' => locale_chars.ZERO,
|
|
||||||
'1' => locale_chars.ONE,
|
|
||||||
'2' => locale_chars.TWO,
|
|
||||||
'3' => locale_chars.THREE,
|
|
||||||
'4' => locale_chars.FOUR,
|
|
||||||
'5' => locale_chars.FIVE,
|
|
||||||
'6' => locale_chars.SIX,
|
|
||||||
'7' => locale_chars.SEVEN,
|
|
||||||
'8' => locale_chars.EIGHT,
|
|
||||||
'9' => locale_chars.NINE,
|
|
||||||
':' => locale_chars.S,
|
|
||||||
else => locale_chars.E,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
pub const WIDTH = 5;
|
|
||||||
pub const HEIGHT = 5;
|
|
||||||
pub const SIZE = WIDTH * HEIGHT;
|
|
||||||
|
|
||||||
pub const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
|
|
||||||
pub const O: u32 = 0;
|
|
||||||
|
|
||||||
pub const LocaleChars = struct {
|
|
||||||
ZERO: [SIZE]u21,
|
|
||||||
ONE: [SIZE]u21,
|
|
||||||
TWO: [SIZE]u21,
|
|
||||||
THREE: [SIZE]u21,
|
|
||||||
FOUR: [SIZE]u21,
|
|
||||||
FIVE: [SIZE]u21,
|
|
||||||
SIX: [SIZE]u21,
|
|
||||||
SEVEN: [SIZE]u21,
|
|
||||||
EIGHT: [SIZE]u21,
|
|
||||||
NINE: [SIZE]u21,
|
|
||||||
S: [SIZE]u21,
|
|
||||||
E: [SIZE]u21,
|
|
||||||
};
|
|
||||||
118
src/components/InfoLine.zig
Normal file
118
src/components/InfoLine.zig
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const keyboard = ly_ui.keyboard;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
const CyclableLabel = ly_ui.CyclableLabel;
|
||||||
|
|
||||||
|
const MessageLabel = CyclableLabel(Message, Message);
|
||||||
|
|
||||||
|
const InfoLine = @This();
|
||||||
|
|
||||||
|
const Message = struct {
|
||||||
|
width: usize,
|
||||||
|
text: []const u8,
|
||||||
|
bg: u32,
|
||||||
|
fg: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
label: *MessageLabel,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
width: usize,
|
||||||
|
arrow_fg: u32,
|
||||||
|
arrow_bg: u32,
|
||||||
|
) !InfoLine {
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.label = try MessageLabel.init(
|
||||||
|
allocator,
|
||||||
|
io,
|
||||||
|
buffer,
|
||||||
|
drawItem,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
width,
|
||||||
|
true,
|
||||||
|
arrow_fg,
|
||||||
|
arrow_bg,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *InfoLine) void {
|
||||||
|
self.label.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *InfoLine) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"InfoLine",
|
||||||
|
self.label.keybinds,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
null,
|
||||||
|
handle,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
|
||||||
|
if (text.len == 0) return;
|
||||||
|
|
||||||
|
try self.label.addItem(.{
|
||||||
|
.width = TerminalBuffer.strWidth(text),
|
||||||
|
.text = text,
|
||||||
|
.bg = bg,
|
||||||
|
.fg = fg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clearRendered(self: InfoLine, allocator: Allocator) !void {
|
||||||
|
// Draw over the area
|
||||||
|
const spaces = try allocator.alloc(u8, self.label.width - 2);
|
||||||
|
defer allocator.free(spaces);
|
||||||
|
|
||||||
|
@memset(spaces, ' ');
|
||||||
|
|
||||||
|
TerminalBuffer.drawText(
|
||||||
|
spaces,
|
||||||
|
self.label.component_pos.x + 2,
|
||||||
|
self.label.component_pos.y,
|
||||||
|
TerminalBuffer.Color.DEFAULT,
|
||||||
|
TerminalBuffer.Color.DEFAULT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *InfoLine) void {
|
||||||
|
self.label.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(self: *InfoLine, maybe_key: ?keyboard.Key) !void {
|
||||||
|
self.label.handle(maybe_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawItem(label: *MessageLabel, message: Message, x: usize, y: usize, width: usize) void {
|
||||||
|
if (message.width == 0) return;
|
||||||
|
|
||||||
|
const x_offset = if (label.text_in_center and width >= message.width) (width - message.width) / 2 else 0;
|
||||||
|
|
||||||
|
label.cursor = message.width + x_offset;
|
||||||
|
TerminalBuffer.drawConfinedText(
|
||||||
|
message.text,
|
||||||
|
x + x_offset,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
message.fg,
|
||||||
|
message.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
123
src/components/Session.zig
Normal file
123
src/components/Session.zig
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const keyboard = ly_ui.keyboard;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
const CyclableLabel = ly_ui.CyclableLabel;
|
||||||
|
|
||||||
|
const UserList = @import("UserList.zig");
|
||||||
|
const Environment = @import("../Environment.zig");
|
||||||
|
|
||||||
|
const Env = struct {
|
||||||
|
environment: Environment,
|
||||||
|
index: usize,
|
||||||
|
};
|
||||||
|
const EnvironmentLabel = CyclableLabel(Env, *UserList);
|
||||||
|
|
||||||
|
const Session = @This();
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
label: *EnvironmentLabel,
|
||||||
|
user_list: *UserList,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
user_list: *UserList,
|
||||||
|
width: usize,
|
||||||
|
text_in_center: bool,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) !Session {
|
||||||
|
return .{
|
||||||
|
.instance = null,
|
||||||
|
.label = try EnvironmentLabel.init(
|
||||||
|
allocator,
|
||||||
|
io,
|
||||||
|
buffer,
|
||||||
|
drawItem,
|
||||||
|
sessionChanged,
|
||||||
|
user_list,
|
||||||
|
width,
|
||||||
|
text_in_center,
|
||||||
|
fg,
|
||||||
|
bg,
|
||||||
|
),
|
||||||
|
.user_list = user_list,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Session) void {
|
||||||
|
for (self.label.list.items) |*env| {
|
||||||
|
if (env.environment.entry_ini) |*entry_ini| entry_ini.deinit();
|
||||||
|
self.label.allocator.free(env.environment.file_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.label.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *Session) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"Session",
|
||||||
|
self.label.keybinds,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
null,
|
||||||
|
handle,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addEnvironment(self: *Session, environment: Environment) !void {
|
||||||
|
const env = Env{ .environment = environment, .index = self.label.list.items.len };
|
||||||
|
|
||||||
|
try self.label.addItem(env);
|
||||||
|
addedSession(env, self.user_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *Session) void {
|
||||||
|
self.label.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(self: *Session, maybe_key: ?keyboard.Key) !void {
|
||||||
|
self.label.handle(maybe_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addedSession(env: Env, user_list: *UserList) void {
|
||||||
|
const user = user_list.label.list.items[user_list.label.current];
|
||||||
|
if (!user.first_run) return;
|
||||||
|
|
||||||
|
user.session_index.* = env.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sessionChanged(env: Env, maybe_user_list: ?*UserList) void {
|
||||||
|
if (maybe_user_list) |user_list| {
|
||||||
|
user_list.label.list.items[user_list.label.current].session_index.* = env.index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawItem(label: *EnvironmentLabel, env: Env, x: usize, y: usize, width: usize) void {
|
||||||
|
if (width < 3) return;
|
||||||
|
|
||||||
|
const length = @min(TerminalBuffer.strWidth(env.environment.name), width - 3);
|
||||||
|
if (length == 0) return;
|
||||||
|
|
||||||
|
const x_offset = if (label.text_in_center and width >= length) (width - length) / 2 else 0;
|
||||||
|
|
||||||
|
label.cursor = length + x_offset;
|
||||||
|
TerminalBuffer.drawConfinedText(
|
||||||
|
env.environment.name,
|
||||||
|
x + x_offset,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
label.fg,
|
||||||
|
label.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
147
src/components/UserList.zig
Normal file
147
src/components/UserList.zig
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
const ly_ui = @import("ly-ui");
|
||||||
|
const keyboard = ly_ui.keyboard;
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Widget = ly_ui.Widget;
|
||||||
|
const CyclableLabel = ly_ui.CyclableLabel;
|
||||||
|
|
||||||
|
const Session = @import("Session.zig");
|
||||||
|
const SavedUsers = @import("../config/SavedUsers.zig");
|
||||||
|
|
||||||
|
const StringList = std.ArrayListUnmanaged([]const u8);
|
||||||
|
pub const User = struct {
|
||||||
|
name: []const u8,
|
||||||
|
session_index: *usize,
|
||||||
|
allocated_index: bool,
|
||||||
|
first_run: bool,
|
||||||
|
};
|
||||||
|
const UserLabel = CyclableLabel(User, *Session);
|
||||||
|
|
||||||
|
const UserList = @This();
|
||||||
|
|
||||||
|
instance: ?Widget = null,
|
||||||
|
label: *UserLabel,
|
||||||
|
|
||||||
|
pub fn init(
|
||||||
|
allocator: Allocator,
|
||||||
|
io: std.Io,
|
||||||
|
buffer: *TerminalBuffer,
|
||||||
|
usernames: StringList,
|
||||||
|
saved_users: *SavedUsers,
|
||||||
|
session: *Session,
|
||||||
|
width: usize,
|
||||||
|
text_in_center: bool,
|
||||||
|
fg: u32,
|
||||||
|
bg: u32,
|
||||||
|
) !UserList {
|
||||||
|
var user_list = UserList{
|
||||||
|
.instance = null,
|
||||||
|
.label = try UserLabel.init(
|
||||||
|
allocator,
|
||||||
|
io,
|
||||||
|
buffer,
|
||||||
|
drawItem,
|
||||||
|
usernameChanged,
|
||||||
|
session,
|
||||||
|
width,
|
||||||
|
text_in_center,
|
||||||
|
fg,
|
||||||
|
bg,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
for (usernames.items) |username| {
|
||||||
|
if (username.len == 0) continue;
|
||||||
|
|
||||||
|
var maybe_session_index: ?*usize = null;
|
||||||
|
var first_run = true;
|
||||||
|
for (saved_users.user_list.items) |*saved_user| {
|
||||||
|
if (std.mem.eql(u8, username, saved_user.username)) {
|
||||||
|
maybe_session_index = &saved_user.session_index;
|
||||||
|
first_run = saved_user.first_run;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allocated_index = false;
|
||||||
|
if (maybe_session_index == null) {
|
||||||
|
maybe_session_index = try allocator.create(usize);
|
||||||
|
maybe_session_index.?.* = 0;
|
||||||
|
allocated_index = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try user_list.label.addItem(.{
|
||||||
|
.name = username,
|
||||||
|
.session_index = maybe_session_index.?,
|
||||||
|
.allocated_index = allocated_index,
|
||||||
|
.first_run = first_run,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return user_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *UserList) void {
|
||||||
|
for (self.label.list.items) |user| {
|
||||||
|
if (user.allocated_index) {
|
||||||
|
self.label.allocator.destroy(user.session_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.label.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn widget(self: *UserList) *Widget {
|
||||||
|
if (self.instance) |*instance| return instance;
|
||||||
|
self.instance = Widget.init(
|
||||||
|
"UserList",
|
||||||
|
self.label.keybinds,
|
||||||
|
self,
|
||||||
|
deinit,
|
||||||
|
null,
|
||||||
|
draw,
|
||||||
|
null,
|
||||||
|
handle,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return &self.instance.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getCurrentUsername(self: UserList) []const u8 {
|
||||||
|
return self.label.list.items[self.label.current].name;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw(self: *UserList) void {
|
||||||
|
self.label.draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(self: *UserList, maybe_key: ?keyboard.Key) !void {
|
||||||
|
self.label.handle(maybe_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usernameChanged(user: User, maybe_session: ?*Session) void {
|
||||||
|
if (maybe_session) |session| {
|
||||||
|
session.label.current = @min(user.session_index.*, session.label.list.items.len - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drawItem(label: *UserLabel, user: User, x: usize, y: usize, width: usize) void {
|
||||||
|
if (width < 3) return;
|
||||||
|
|
||||||
|
const length = @min(TerminalBuffer.strWidth(user.name), width - 3);
|
||||||
|
if (length == 0) return;
|
||||||
|
|
||||||
|
const x_offset = if (label.text_in_center and width >= length) (width - length) / 2 else 0;
|
||||||
|
|
||||||
|
label.cursor = length + x_offset;
|
||||||
|
TerminalBuffer.drawConfinedText(
|
||||||
|
user.name,
|
||||||
|
x + x_offset,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
label.fg,
|
||||||
|
label.bg,
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,70 +1,102 @@
|
|||||||
const build_options = @import("build_options");
|
const build_options = @import("build_options");
|
||||||
const enums = @import("../enums.zig");
|
|
||||||
|
|
||||||
|
const enums = @import("../enums.zig");
|
||||||
const Animation = enums.Animation;
|
const Animation = enums.Animation;
|
||||||
const Input = enums.Input;
|
const Input = enums.Input;
|
||||||
const ViMode = enums.ViMode;
|
const ViMode = enums.ViMode;
|
||||||
const Bigclock = enums.Bigclock;
|
const Bigclock = enums.Bigclock;
|
||||||
|
const DurOffsetAlignment = enums.DurOffsetAlignment;
|
||||||
|
|
||||||
allow_empty_password: bool = true,
|
allow_empty_password: bool = true,
|
||||||
animation: Animation = .none,
|
animation: Animation = .none,
|
||||||
|
animation_frame_delay: u16 = 5,
|
||||||
animation_timeout_sec: u12 = 0,
|
animation_timeout_sec: u12 = 0,
|
||||||
asterisk: ?u32 = '*',
|
asterisk: ?u32 = '*',
|
||||||
auth_fails: u64 = 10,
|
auth_fails: u64 = 10,
|
||||||
|
battery_id: ?[]const u8 = null,
|
||||||
|
auto_login_service: [:0]const u8 = "ly-autologin",
|
||||||
|
auto_login_session: ?[]const u8 = null,
|
||||||
|
auto_login_user: ?[]const u8 = null,
|
||||||
bg: u32 = 0x00000000,
|
bg: u32 = 0x00000000,
|
||||||
bigclock: Bigclock = .none,
|
bigclock: Bigclock = .none,
|
||||||
|
bigclock_12hr: bool = false,
|
||||||
|
bigclock_seconds: bool = false,
|
||||||
blank_box: bool = true,
|
blank_box: bool = true,
|
||||||
border_fg: u32 = 0x00FFFFFF,
|
border_fg: u32 = 0x00FFFFFF,
|
||||||
box_title: ?[]const u8 = null,
|
box_title: ?[]const u8 = null,
|
||||||
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s 10%-",
|
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s 10%-",
|
||||||
brightness_down_key: ?[]const u8 = "F5",
|
brightness_down_key: ?[]const u8 = "F5",
|
||||||
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s +10%",
|
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q -n s +10%",
|
||||||
brightness_up_key: ?[]const u8 = "F6",
|
brightness_up_key: ?[]const u8 = "F6",
|
||||||
clear_password: bool = false,
|
clear_password: bool = false,
|
||||||
clock: ?[:0]const u8 = null,
|
clock: ?[:0]const u8 = null,
|
||||||
cmatrix_fg: u32 = 0x0000FF00,
|
cmatrix_fg: u32 = 0x0000FF00,
|
||||||
|
cmatrix_head_col: u32 = 0x01FFFFFF,
|
||||||
cmatrix_min_codepoint: u16 = 0x21,
|
cmatrix_min_codepoint: u16 = 0x21,
|
||||||
cmatrix_max_codepoint: u16 = 0x7B,
|
cmatrix_max_codepoint: u16 = 0x7B,
|
||||||
colormix_col1: u32 = 0x00FF0000,
|
colormix_col1: u32 = 0x00FF0000,
|
||||||
colormix_col2: u32 = 0x000000FF,
|
colormix_col2: u32 = 0x000000FF,
|
||||||
colormix_col3: u32 = 0x20000000,
|
colormix_col3: u32 = 0x20000000,
|
||||||
console_dev: []const u8 = "/dev/console",
|
custom_bind_width: ?u32 = null,
|
||||||
|
custom_sessions: []const u8 = build_options.config_directory ++ "/ly/custom-sessions",
|
||||||
default_input: Input = .login,
|
default_input: Input = .login,
|
||||||
|
doom_fire_height: u8 = 6,
|
||||||
|
doom_fire_spread: u8 = 2,
|
||||||
doom_top_color: u32 = 0x00FF0000,
|
doom_top_color: u32 = 0x00FF0000,
|
||||||
doom_middle_color: u32 = 0x00FFFF00,
|
doom_middle_color: u32 = 0x00FFFF00,
|
||||||
doom_bottom_color: u32 = 0x00FFFFFF,
|
doom_bottom_color: u32 = 0x00FFFFFF,
|
||||||
|
dur_file_path: []const u8 = build_options.config_directory ++ "/ly/example.dur",
|
||||||
|
dur_offset_alignment: DurOffsetAlignment = .center,
|
||||||
|
dur_x_offset: i32 = 0,
|
||||||
|
dur_y_offset: i32 = 0,
|
||||||
|
edge_margin: u8 = 0,
|
||||||
error_bg: u32 = 0x00000000,
|
error_bg: u32 = 0x00000000,
|
||||||
error_fg: u32 = 0x01FF0000,
|
error_fg: u32 = 0x01FF0000,
|
||||||
fg: u32 = 0x00FFFFFF,
|
fg: u32 = 0x00FFFFFF,
|
||||||
|
full_color: bool = true,
|
||||||
|
gameoflife_fg: u32 = 0x0000FF00,
|
||||||
|
gameoflife_entropy_interval: usize = 10,
|
||||||
|
gameoflife_frame_delay: usize = 6,
|
||||||
|
gameoflife_initial_density: f32 = 0.4,
|
||||||
|
hibernate_cmd: ?[]const u8 = null,
|
||||||
|
hibernate_key: []const u8 = "F4",
|
||||||
hide_borders: bool = false,
|
hide_borders: bool = false,
|
||||||
hide_key_hints: bool = false,
|
hide_key_hints: bool = false,
|
||||||
|
hide_keyboard_locks: bool = false,
|
||||||
|
hide_version_string: bool = false,
|
||||||
|
inactivity_cmd: ?[]const u8 = null,
|
||||||
|
inactivity_delay: u16 = 0,
|
||||||
initial_info_text: ?[]const u8 = null,
|
initial_info_text: ?[]const u8 = null,
|
||||||
input_len: u8 = 34,
|
input_len: u8 = 34,
|
||||||
lang: []const u8 = "en",
|
lang: []const u8 = "en",
|
||||||
load: bool = true,
|
|
||||||
login_cmd: ?[]const u8 = null,
|
login_cmd: ?[]const u8 = null,
|
||||||
|
login_defs_path: []const u8 = "/etc/login.defs",
|
||||||
logout_cmd: ?[]const u8 = null,
|
logout_cmd: ?[]const u8 = null,
|
||||||
|
ly_log: []const u8 = "/var/log/ly.log",
|
||||||
margin_box_h: u8 = 2,
|
margin_box_h: u8 = 2,
|
||||||
margin_box_v: u8 = 1,
|
margin_box_v: u8 = 1,
|
||||||
min_refresh_delta: u16 = 5,
|
|
||||||
numlock: bool = false,
|
numlock: bool = false,
|
||||||
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin",
|
path: ?[]const u8 = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
restart_cmd: []const u8 = "/sbin/shutdown -r now",
|
restart_cmd: []const u8 = "/sbin/shutdown -r now",
|
||||||
restart_key: []const u8 = "F2",
|
restart_key: []const u8 = "F2",
|
||||||
save: bool = true,
|
save: bool = true,
|
||||||
service_name: [:0]const u8 = "ly",
|
service_name: [:0]const u8 = "ly",
|
||||||
session_log: []const u8 = "ly-session.log",
|
session_log: ?[]const u8 = "ly-session.log",
|
||||||
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
|
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
|
||||||
|
shell: bool = true,
|
||||||
|
show_password_key: []const u8 = "F7",
|
||||||
|
show_tty: bool = false,
|
||||||
shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
|
shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
|
||||||
shutdown_key: []const u8 = "F1",
|
shutdown_key: []const u8 = "F1",
|
||||||
sleep_cmd: ?[]const u8 = null,
|
sleep_cmd: ?[]const u8 = null,
|
||||||
sleep_key: []const u8 = "F3",
|
sleep_key: []const u8 = "F3",
|
||||||
|
start_cmd: ?[]const u8 = null,
|
||||||
text_in_center: bool = false,
|
text_in_center: bool = false,
|
||||||
tty: u8 = build_options.tty,
|
|
||||||
vi_default_mode: ViMode = .normal,
|
vi_default_mode: ViMode = .normal,
|
||||||
vi_mode: bool = false,
|
vi_mode: bool = false,
|
||||||
waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",
|
waylandsessions: ?[]const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",
|
||||||
x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X",
|
x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X",
|
||||||
|
x_vt: ?u8 = null,
|
||||||
xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth",
|
xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth",
|
||||||
xinitrc: ?[]const u8 = "~/.xinitrc",
|
xinitrc: ?[]const u8 = "~/.xinitrc",
|
||||||
xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions",
|
xsessions: ?[]const u8 = build_options.prefix_directory ++ "/share/xsessions",
|
||||||
|
|||||||
@@ -7,17 +7,29 @@ authenticating: []const u8 = "authenticating...",
|
|||||||
brightness_down: []const u8 = "decrease brightness",
|
brightness_down: []const u8 = "decrease brightness",
|
||||||
brightness_up: []const u8 = "increase brightness",
|
brightness_up: []const u8 = "increase brightness",
|
||||||
capslock: []const u8 = "capslock",
|
capslock: []const u8 = "capslock",
|
||||||
|
custom: []const u8 = "custom",
|
||||||
|
custom_info_err_output_long: []const u8 = "output too long",
|
||||||
|
custom_info_err_no_output: []const u8 = "no output",
|
||||||
|
custom_info_err_no_output_error: []const u8 = ", possible error",
|
||||||
err_alloc: []const u8 = "failed memory allocation",
|
err_alloc: []const u8 = "failed memory allocation",
|
||||||
|
err_args: []const u8 = "unable to parse command line arguments",
|
||||||
|
err_autologin_session: []const u8 = "autologin session not found",
|
||||||
err_bounds: []const u8 = "out-of-bounds index",
|
err_bounds: []const u8 = "out-of-bounds index",
|
||||||
err_brightness_change: []const u8 = "failed to change brightness",
|
err_brightness_change: []const u8 = "failed to change brightness",
|
||||||
err_chdir: []const u8 = "failed to open home folder",
|
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_config: []const u8 = "unable to parse config file",
|
||||||
err_console_dev: []const u8 = "failed to access console",
|
err_crawl: []const u8 = "failed to crawl session directories",
|
||||||
err_dgn_oob: []const u8 = "log message",
|
err_dgn_oob: []const u8 = "log message",
|
||||||
err_domain: []const u8 = "invalid domain",
|
err_domain: []const u8 = "invalid domain",
|
||||||
err_empty_password: []const u8 = "empty password not allowed",
|
err_empty_password: []const u8 = "empty password not allowed",
|
||||||
err_envlist: []const u8 = "failed to get envlist",
|
err_envlist: []const u8 = "failed to get envlist",
|
||||||
|
err_get_active_tty: []const u8 = "failed to get active tty",
|
||||||
|
err_hibernate: []const u8 = "failed to execute hibernate command",
|
||||||
err_hostname: []const u8 = "failed to get hostname",
|
err_hostname: []const u8 = "failed to get hostname",
|
||||||
|
err_inactivity: []const u8 = "failed to execute inactivity command",
|
||||||
|
err_lock_state: []const u8 = "failed to get lock state",
|
||||||
|
err_log: []const u8 = "failed to open log file",
|
||||||
err_mlock: []const u8 = "failed to lock password memory",
|
err_mlock: []const u8 = "failed to lock password memory",
|
||||||
err_null: []const u8 = "null pointer",
|
err_null: []const u8 = "null pointer",
|
||||||
err_numlock: []const u8 = "failed to set numlock",
|
err_numlock: []const u8 = "failed to set numlock",
|
||||||
@@ -43,8 +55,12 @@ err_perm_group: []const u8 = "failed to downgrade group permissions",
|
|||||||
err_perm_user: []const u8 = "failed to downgrade user permissions",
|
err_perm_user: []const u8 = "failed to downgrade user permissions",
|
||||||
err_pwnam: []const u8 = "failed to get user info",
|
err_pwnam: []const u8 = "failed to get user info",
|
||||||
err_sleep: []const u8 = "failed to execute sleep command",
|
err_sleep: []const u8 = "failed to execute sleep command",
|
||||||
|
err_start: []const u8 = "failed to execute start command",
|
||||||
|
err_battery: []const u8 = "failed to load battery status",
|
||||||
|
err_switch_tty: []const u8 = "failed to switch tty",
|
||||||
err_tty_ctrl: []const u8 = "tty control transfer failed",
|
err_tty_ctrl: []const u8 = "tty control transfer failed",
|
||||||
err_unknown: []const u8 = "an unknown error occurred",
|
err_no_users: []const u8 = "no users found",
|
||||||
|
err_uid_range: []const u8 = "failed to dynamically get uid range",
|
||||||
err_user_gid: []const u8 = "failed to set user GID",
|
err_user_gid: []const u8 = "failed to set user GID",
|
||||||
err_user_init: []const u8 = "failed to initialize user",
|
err_user_init: []const u8 = "failed to initialize user",
|
||||||
err_user_uid: []const u8 = "failed to set user UID",
|
err_user_uid: []const u8 = "failed to set user UID",
|
||||||
@@ -52,18 +68,20 @@ err_xauth: []const u8 = "xauth command failed",
|
|||||||
err_xcb_conn: []const u8 = "xcb connection failed",
|
err_xcb_conn: []const u8 = "xcb connection failed",
|
||||||
err_xsessions_dir: []const u8 = "failed to find sessions folder",
|
err_xsessions_dir: []const u8 = "failed to find sessions folder",
|
||||||
err_xsessions_open: []const u8 = "failed to open sessions folder",
|
err_xsessions_open: []const u8 = "failed to open sessions folder",
|
||||||
|
hibernate: []const u8 = "hibernate",
|
||||||
insert: []const u8 = "insert",
|
insert: []const u8 = "insert",
|
||||||
login: []const u8 = "login:",
|
login: []const u8 = "login",
|
||||||
logout: []const u8 = "logged out",
|
logout: []const u8 = "logged out",
|
||||||
no_x11_support: []const u8 = "x11 support disabled at compile-time",
|
no_x11_support: []const u8 = "x11 support disabled at compile-time",
|
||||||
normal: []const u8 = "normal",
|
normal: []const u8 = "normal",
|
||||||
numlock: []const u8 = "numlock",
|
numlock: []const u8 = "numlock",
|
||||||
other: []const u8 = "other",
|
other: []const u8 = "other",
|
||||||
password: []const u8 = "password:",
|
password: []const u8 = "password",
|
||||||
restart: []const u8 = "reboot",
|
restart: []const u8 = "reboot",
|
||||||
shell: [:0]const u8 = "shell",
|
shell: [:0]const u8 = "shell",
|
||||||
shutdown: []const u8 = "shutdown",
|
shutdown: []const u8 = "shutdown",
|
||||||
sleep: []const u8 = "sleep",
|
sleep: []const u8 = "sleep",
|
||||||
|
toggle_password: []const u8 = "toggle password",
|
||||||
wayland: []const u8 = "wayland",
|
wayland: []const u8 = "wayland",
|
||||||
x11: []const u8 = "x11",
|
x11: []const u8 = "x11",
|
||||||
xinitrc: [:0]const u8 = "xinitrc",
|
xinitrc: [:0]const u8 = "xinitrc",
|
||||||
|
|||||||
28
src/config/SavedUsers.zig
Normal file
28
src/config/SavedUsers.zig
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const SavedUsers = @This();
|
||||||
|
|
||||||
|
const User = struct {
|
||||||
|
username: []const u8,
|
||||||
|
session_index: usize,
|
||||||
|
first_run: bool,
|
||||||
|
allocated_username: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
user_list: std.ArrayList(User),
|
||||||
|
last_username_index: ?usize,
|
||||||
|
|
||||||
|
pub fn init() SavedUsers {
|
||||||
|
return .{
|
||||||
|
.user_list = .empty,
|
||||||
|
.last_username_index = null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *SavedUsers, allocator: std.mem.Allocator) void {
|
||||||
|
for (self.user_list.items) |user| {
|
||||||
|
if (user.allocated_username) allocator.free(user.username);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.user_list.deinit(allocator);
|
||||||
|
}
|
||||||
25
src/config/custom.zig
Normal file
25
src/config/custom.zig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const custom = @This();
|
||||||
|
|
||||||
|
pub const CustomCommandBind = struct {
|
||||||
|
name: []const u8 = "",
|
||||||
|
cmd: []const u8 = "",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const UNDEFINED_CMD: []const u8 = "echo \"You forgot to define 'cmd'!\"";
|
||||||
|
|
||||||
|
pub const CustomCommandInfo = struct {
|
||||||
|
name: []const u8 = "",
|
||||||
|
cmd: ?[]const u8 = null,
|
||||||
|
/// To be set to the label's widget ID
|
||||||
|
id: u64 = 0,
|
||||||
|
|
||||||
|
/// In frames, the refresh rate for the `cmd` to run again
|
||||||
|
/// If 0, only run once.
|
||||||
|
refresh: u32 = 0,
|
||||||
|
counter: u32 = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub var binds: std.array_hash_map.String(CustomCommandBind) = undefined;
|
||||||
|
pub var labels: std.array_hash_map.String(CustomCommandInfo) = undefined;
|
||||||
@@ -1,9 +1,22 @@
|
|||||||
// The migrator ensures compatibility with <=0.6.0 configuration files
|
// The migrator ensures compatibility with older configuration files
|
||||||
|
// Properties removed or changed since 0.6.0
|
||||||
|
// Color codes interpreted differently since 1.1.0
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const ini = @import("zigini");
|
var temporary_allocator = std.heap.page_allocator;
|
||||||
const Save = @import("Save.zig");
|
|
||||||
const enums = @import("../enums.zig");
|
const ly_ui = @import("ly-ui");
|
||||||
|
const TerminalBuffer = ly_ui.TerminalBuffer;
|
||||||
|
const Color = TerminalBuffer.Color;
|
||||||
|
const Styling = TerminalBuffer.Styling;
|
||||||
|
const ly_core = ly_ui.ly_core;
|
||||||
|
const IniParser = ly_core.IniParser;
|
||||||
|
const ini = ly_core.ini;
|
||||||
|
|
||||||
|
const Config = @import("Config.zig");
|
||||||
|
const OldSave = @import("OldSave.zig");
|
||||||
|
const SavedUsers = @import("SavedUsers.zig");
|
||||||
|
const custom = @import("custom.zig");
|
||||||
|
|
||||||
const color_properties = [_][]const u8{
|
const color_properties = [_][]const u8{
|
||||||
"bg",
|
"bg",
|
||||||
@@ -16,6 +29,10 @@ const color_properties = [_][]const u8{
|
|||||||
"error_fg",
|
"error_fg",
|
||||||
"fg",
|
"fg",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var set_color_properties =
|
||||||
|
[_]bool{ false, false, false, false, false, false, false, false, false };
|
||||||
|
|
||||||
const removed_properties = [_][]const u8{
|
const removed_properties = [_][]const u8{
|
||||||
"wayland_specifier",
|
"wayland_specifier",
|
||||||
"max_desktop_len",
|
"max_desktop_len",
|
||||||
@@ -26,22 +43,20 @@ const removed_properties = [_][]const u8{
|
|||||||
"term_restore_cursor_cmd",
|
"term_restore_cursor_cmd",
|
||||||
"x_cmd_setup",
|
"x_cmd_setup",
|
||||||
"wayland_cmd",
|
"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_animate: ?bool = null;
|
||||||
pub var maybe_save_file: ?[]const u8 = 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 {
|
pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniField {
|
||||||
if (std.mem.eql(u8, field.key, "animate")) {
|
if (std.mem.eql(u8, field.key, "animate")) {
|
||||||
// The option doesn't exist anymore, but we save its value for "animation"
|
// The option doesn't exist anymore, but we save its value for "animation"
|
||||||
maybe_animate = std.mem.eql(u8, field.value, "true");
|
maybe_animate = std.mem.eql(u8, field.value, "true");
|
||||||
|
|
||||||
mapped_config_fields = true;
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,19 +72,29 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||||||
else => "none",
|
else => "none",
|
||||||
};
|
};
|
||||||
|
|
||||||
mapped_config_fields = true;
|
|
||||||
return mapped_field;
|
return mapped_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline for (color_properties) |property| {
|
inline for (color_properties, &set_color_properties) |property, *status| {
|
||||||
if (std.mem.eql(u8, field.key, property)) {
|
if (std.mem.eql(u8, field.key, property)) {
|
||||||
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
|
// Color has been set; it won't be overwritten if we default to eight-color output
|
||||||
const color = std.fmt.parseInt(u16, field.value, 0) catch return field;
|
status.* = true;
|
||||||
var mapped_field = field;
|
|
||||||
|
|
||||||
mapped_field.value = mapColor(color) catch return field;
|
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
|
||||||
mapped_config_fields = true;
|
// If they're all using eight-color codes, we start in eight-color mode
|
||||||
return mapped_field;
|
const color = std.fmt.parseInt(u16, field.value, 0) catch {
|
||||||
|
auto_eight_colors = false;
|
||||||
|
return field;
|
||||||
|
};
|
||||||
|
|
||||||
|
const color_no_styling = color & 0x00FF;
|
||||||
|
const styling_only = color & 0xFF00;
|
||||||
|
|
||||||
|
// If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM,
|
||||||
|
// we have an invalid color, so do not use eight-color mode
|
||||||
|
if (color_no_styling > 0x0008 or styling_only > 0x8000) auto_eight_colors = false;
|
||||||
|
|
||||||
|
return field;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +103,6 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||||||
var mapped_field = field;
|
var mapped_field = field;
|
||||||
mapped_field.key = "clear_password";
|
mapped_field.key = "clear_password";
|
||||||
|
|
||||||
mapped_config_fields = true;
|
|
||||||
return mapped_field;
|
return mapped_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +118,6 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||||||
else => "login",
|
else => "login",
|
||||||
};
|
};
|
||||||
|
|
||||||
mapped_config_fields = true;
|
|
||||||
return mapped_field;
|
return mapped_field;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,14 +125,12 @@ 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
|
// 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;
|
maybe_save_file = temporary_allocator.dupe(u8, field.value) catch return null;
|
||||||
|
|
||||||
mapped_config_fields = true;
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline for (removed_properties) |property| {
|
inline for (removed_properties) |property| {
|
||||||
if (std.mem.eql(u8, field.key, property)) {
|
if (std.mem.eql(u8, field.key, property)) {
|
||||||
// The options don't exist anymore
|
// The options don't exist anymore
|
||||||
mapped_config_fields = true;
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,83 +142,174 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
|
|||||||
|
|
||||||
if (std.mem.eql(u8, field.value, "true")) {
|
if (std.mem.eql(u8, field.value, "true")) {
|
||||||
mapped_field.value = "en";
|
mapped_field.value = "en";
|
||||||
mapped_config_fields = true;
|
|
||||||
} else if (std.mem.eql(u8, field.value, "false")) {
|
} else if (std.mem.eql(u8, field.value, "false")) {
|
||||||
mapped_field.value = "none";
|
mapped_field.value = "none";
|
||||||
mapped_config_fields = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mapped_field;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, field.key, "min_refresh_delta")) {
|
||||||
|
// The option has simply been renamed
|
||||||
|
var mapped_field = field;
|
||||||
|
mapped_field.key = "animation_frame_delay";
|
||||||
|
|
||||||
|
return mapped_field;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Dearest Melpert,
|
||||||
|
// I pray this message finds you well, as daylight dwindles and the witching hour
|
||||||
|
// approaches, I find it more and more imperative as time continues that I place
|
||||||
|
// this reminder here in such a format that you cannot ignore.
|
||||||
|
// Do you know how long I have been waiting for this petition to be authorized
|
||||||
|
// in regards to this particular segment of computerized instructions?
|
||||||
|
// It has been many a moon since this particular audit has been
|
||||||
|
// posted regarding the position of handling configurable literature
|
||||||
|
// apparatuses and plans for a new feature to the configuration
|
||||||
|
// interface and as time continues onwards I grow more restless
|
||||||
|
// on the progress of said interface, only to find out afterwards
|
||||||
|
// that you have PROCRASTINATED on the efforts meant to enhance
|
||||||
|
// configuration. Thus the requirement for this reminder larger
|
||||||
|
// compared to the two reminders regarding better methods of
|
||||||
|
// X termination detection and new usernames with existing
|
||||||
|
// save files.
|
||||||
|
//
|
||||||
|
// Thus is my que to leave this TODO at thy request,
|
||||||
|
//
|
||||||
|
// Forever Sullied,
|
||||||
|
//
|
||||||
|
// Ly Contributor.
|
||||||
|
//
|
||||||
|
if (std.mem.startsWith(u8, field.header, "cmd:")) {
|
||||||
|
const key = field.header["cmd:".len..];
|
||||||
|
const keyZ = temporary_allocator.dupe(u8, key) catch "";
|
||||||
|
if (!custom.binds.contains(key)) {
|
||||||
|
custom.binds.put(temporary_allocator, keyZ, .{}) catch {};
|
||||||
|
}
|
||||||
|
if (custom.binds.getPtr(keyZ)) |command| {
|
||||||
|
if (std.mem.eql(u8, field.key, "name")) {
|
||||||
|
command.name = temporary_allocator.dupe(u8, field.value) catch "";
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, field.key, "cmd")) {
|
||||||
|
command.cmd = temporary_allocator.dupe(u8, field.value) catch "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.startsWith(u8, field.header, "lbl:")) {
|
||||||
|
const key = field.header["lbl:".len..];
|
||||||
|
const keyZ = temporary_allocator.dupe(u8, key) catch "";
|
||||||
|
if (!custom.labels.contains(keyZ)) {
|
||||||
|
custom.labels.put(temporary_allocator, keyZ, .{ .name = keyZ }) catch {};
|
||||||
|
}
|
||||||
|
if (custom.labels.getPtr(keyZ)) |label| {
|
||||||
|
if (std.mem.eql(u8, field.key, "cmd")) {
|
||||||
|
label.cmd = temporary_allocator.dupe(u8, field.value) catch "";
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, field.key, "refresh")) {
|
||||||
|
label.refresh = std.fmt.parseInt(u32, field.value, 10) catch 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is the stuff we only handle after reading the config.
|
// This is the stuff we only handle after reading the config.
|
||||||
// For example, the "animate" field could come after "animation"
|
// For example, the "animate" field could come after "animation"
|
||||||
pub fn lateConfigFieldHandler(animation: *enums.Animation) void {
|
pub fn lateConfigFieldHandler(config: *Config) void {
|
||||||
if (maybe_animate) |animate| {
|
if (maybe_animate) |animate| {
|
||||||
if (!animate) animation.* = .none;
|
if (!animate) config.*.animation = .none;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto_eight_colors) {
|
||||||
|
// Valid config file predates true-color mode
|
||||||
|
// Will use eight-color output instead
|
||||||
|
config.full_color = false;
|
||||||
|
|
||||||
|
// We cannot rely on Config defaults when in eight-color mode,
|
||||||
|
// because they will appear as undesired colors.
|
||||||
|
// Instead set color properties to matching eight-color codes
|
||||||
|
config.doom_top_color = Color.ECOL_RED;
|
||||||
|
config.doom_middle_color = Color.ECOL_YELLOW;
|
||||||
|
config.doom_bottom_color = Color.ECOL_WHITE;
|
||||||
|
config.cmatrix_head_col = Styling.BOLD | Color.ECOL_WHITE;
|
||||||
|
|
||||||
|
// These may be in the config, so only change those which were not set
|
||||||
|
if (!set_color_properties[0]) config.bg = Color.DEFAULT;
|
||||||
|
if (!set_color_properties[1]) config.border_fg = Color.ECOL_WHITE;
|
||||||
|
if (!set_color_properties[2]) config.cmatrix_fg = Color.ECOL_GREEN;
|
||||||
|
if (!set_color_properties[3]) config.colormix_col1 = Color.ECOL_RED;
|
||||||
|
if (!set_color_properties[4]) config.colormix_col2 = Color.ECOL_BLUE;
|
||||||
|
if (!set_color_properties[5]) config.colormix_col3 = Color.ECOL_BLACK;
|
||||||
|
if (!set_color_properties[6]) config.error_bg = Color.DEFAULT;
|
||||||
|
if (!set_color_properties[7]) config.error_fg = Styling.BOLD | Color.ECOL_RED;
|
||||||
|
if (!set_color_properties[8]) config.fg = Color.ECOL_WHITE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
|
pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !?IniParser(OldSave) {
|
||||||
var save = Save{};
|
var save_parser = try IniParser(OldSave).init(allocator, io, path, null);
|
||||||
|
errdefer save_parser.deinit();
|
||||||
|
|
||||||
|
var user_buf: [32]u8 = undefined;
|
||||||
|
const maybe_save = if (save_parser.maybe_load_error == null) save_parser.structure else tryMigrateFirstSaveFile(io, &user_buf);
|
||||||
|
|
||||||
|
if (maybe_save) |save| {
|
||||||
|
// Add all other users to the list
|
||||||
|
for (usernames, 0..) |username, i| {
|
||||||
|
if (save.user) |user| {
|
||||||
|
if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
try saved_users.user_list.append(allocator, .{
|
||||||
|
.username = username,
|
||||||
|
.session_index = save.session_index orelse 0,
|
||||||
|
.first_run = false,
|
||||||
|
.allocated_username = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return save_parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tryMigrateFirstSaveFile(io: std.Io, user_buf: *[32]u8) ?OldSave {
|
||||||
if (maybe_save_file) |path| {
|
if (maybe_save_file) |path| {
|
||||||
defer temporary_allocator.free(path);
|
defer temporary_allocator.free(path);
|
||||||
|
|
||||||
var file = std.fs.openFileAbsolute(path, .{}) catch return save;
|
var save = OldSave{};
|
||||||
defer file.close();
|
var file = std.Io.Dir.openFileAbsolute(io, path, .{}) catch return null;
|
||||||
|
defer file.close(io);
|
||||||
|
|
||||||
const reader = file.reader();
|
var file_buffer: [64]u8 = undefined;
|
||||||
|
var file_reader = file.reader(io, &file_buffer);
|
||||||
|
var reader = &file_reader.interface;
|
||||||
|
|
||||||
var user_fbs = std.io.fixedBufferStream(user_buf);
|
var user_writer = std.Io.Writer.fixed(user_buf);
|
||||||
reader.streamUntilDelimiter(user_fbs.writer(), '\n', user_buf.len) catch return save;
|
var written = reader.streamDelimiter(&user_writer, '\n') catch return null;
|
||||||
const user = user_fbs.getWritten();
|
if (written > 0) save.user = user_buf[0..written];
|
||||||
if (user.len > 0) save.user = user;
|
|
||||||
|
|
||||||
var session_buf: [20]u8 = undefined;
|
var session_buf: [20]u8 = undefined;
|
||||||
var session_fbs = std.io.fixedBufferStream(&session_buf);
|
var session_writer = std.Io.Writer.fixed(&session_buf);
|
||||||
reader.streamUntilDelimiter(session_fbs.writer(), '\n', session_buf.len) catch return save;
|
written = reader.streamDelimiter(&session_writer, '\n') catch return null;
|
||||||
|
|
||||||
const session_index_str = session_fbs.getWritten();
|
|
||||||
var session_index: ?usize = null;
|
var session_index: ?usize = null;
|
||||||
if (session_index_str.len > 0) {
|
if (written > 0) {
|
||||||
session_index = std.fmt.parseUnsigned(usize, session_index_str, 10) catch return save;
|
session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return null;
|
||||||
}
|
}
|
||||||
save.session_index = session_index;
|
save.session_index = session_index;
|
||||||
}
|
|
||||||
|
|
||||||
return save;
|
return save;
|
||||||
}
|
|
||||||
|
|
||||||
fn mapColor(color: u16) ![]const u8 {
|
|
||||||
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 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,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 try std.fmt.bufPrint(&buffer, "0x{X}", .{new_color});
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Animation = enum {
|
pub const Animation = enum {
|
||||||
none,
|
none,
|
||||||
doom,
|
doom,
|
||||||
matrix,
|
matrix,
|
||||||
colormix,
|
colormix,
|
||||||
|
gameoflife,
|
||||||
|
dur_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const DisplayServer = enum {
|
pub const DisplayServer = enum {
|
||||||
@@ -10,6 +14,7 @@ pub const DisplayServer = enum {
|
|||||||
shell,
|
shell,
|
||||||
xinitrc,
|
xinitrc,
|
||||||
x11,
|
x11,
|
||||||
|
custom,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Input = enum {
|
pub const Input = enum {
|
||||||
@@ -17,6 +22,26 @@ pub const Input = enum {
|
|||||||
session,
|
session,
|
||||||
login,
|
login,
|
||||||
password,
|
password,
|
||||||
|
|
||||||
|
/// Moves the current Input forwards by one entry. If `reverse`, then the Input
|
||||||
|
/// moves backwards. If `wrap` is true, then the entry will wrap back around
|
||||||
|
pub fn move(self: *Input, reverse: bool, wrap: bool) void {
|
||||||
|
const maxNum = @typeInfo(Input).@"enum".fields.len - 1;
|
||||||
|
const selfNum = @intFromEnum(self.*);
|
||||||
|
if (reverse) {
|
||||||
|
if (wrap) {
|
||||||
|
self.* = @enumFromInt(selfNum -% 1);
|
||||||
|
} else if (selfNum != 0) {
|
||||||
|
self.* = @enumFromInt(selfNum - 1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (wrap) {
|
||||||
|
self.* = @enumFromInt(selfNum +% 1);
|
||||||
|
} else if (selfNum != maxNum) {
|
||||||
|
self.* = @enumFromInt(selfNum + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ViMode = enum {
|
pub const ViMode = enum {
|
||||||
@@ -29,3 +54,15 @@ pub const Bigclock = enum {
|
|||||||
en,
|
en,
|
||||||
fa,
|
fa,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const DurOffsetAlignment = enum {
|
||||||
|
topleft,
|
||||||
|
topcenter,
|
||||||
|
topright,
|
||||||
|
centerleft,
|
||||||
|
center,
|
||||||
|
centerright,
|
||||||
|
bottomleft,
|
||||||
|
bottomcenter,
|
||||||
|
bottomright,
|
||||||
|
};
|
||||||
|
|||||||
111
src/interop.zig
111
src/interop.zig
@@ -1,111 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
pub const termbox = @import("termbox2");
|
|
||||||
|
|
||||||
pub const pam = @cImport({
|
|
||||||
@cInclude("security/pam_appl.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const utmp = @cImport({
|
|
||||||
@cInclude("utmpx.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Exists for X11 support only
|
|
||||||
pub const xcb = @cImport({
|
|
||||||
@cInclude("xcb/xcb.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const unistd = @cImport({
|
|
||||||
@cInclude("unistd.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const time = @cImport({
|
|
||||||
@cInclude("time.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const system_time = @cImport({
|
|
||||||
@cInclude("sys/time.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const stdlib = @cImport({
|
|
||||||
@cInclude("stdlib.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const pwd = @cImport({
|
|
||||||
@cInclude("pwd.h");
|
|
||||||
// We include a FreeBSD-specific header here since login_cap.h references
|
|
||||||
// the passwd struct directly, so we can't import it separately'
|
|
||||||
if (builtin.os.tag == .freebsd) @cInclude("login_cap.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const grp = @cImport({
|
|
||||||
@cInclude("grp.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
// BSD-specific headers
|
|
||||||
pub const kbio = @cImport({
|
|
||||||
@cInclude("sys/kbio.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Linux-specific headers
|
|
||||||
pub const kd = @cImport({
|
|
||||||
@cInclude("sys/kd.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
pub const vt = @cImport({
|
|
||||||
@cInclude("sys/vt.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Used for getting & setting the lock state
|
|
||||||
const LedState = if (builtin.os.tag.isBSD()) c_int else c_char;
|
|
||||||
const get_led_state = if (builtin.os.tag.isBSD()) kbio.KDGETLED else kd.KDGKBLED;
|
|
||||||
const set_led_state = if (builtin.os.tag.isBSD()) kbio.KDSETLED else kd.KDSKBLED;
|
|
||||||
const numlock_led = if (builtin.os.tag.isBSD()) kbio.LED_NUM else kd.K_NUMLOCK;
|
|
||||||
const capslock_led = if (builtin.os.tag.isBSD()) kbio.LED_CAP else kd.K_CAPSLOCK;
|
|
||||||
|
|
||||||
pub fn timeAsString(buf: [:0]u8, format: [:0]const u8) ![]u8 {
|
|
||||||
const timer = std.time.timestamp();
|
|
||||||
const tm_info = time.localtime(&timer);
|
|
||||||
|
|
||||||
const len = time.strftime(buf, buf.len, format, tm_info);
|
|
||||||
if (len < 0) return error.CannotGetFormattedTime;
|
|
||||||
|
|
||||||
return buf[0..len];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn switchTty(console_dev: []const u8, tty: u8) !void {
|
|
||||||
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .WRONLY }, 0);
|
|
||||||
defer std.posix.close(fd);
|
|
||||||
|
|
||||||
_ = std.c.ioctl(fd, vt.VT_ACTIVATE, tty);
|
|
||||||
_ = std.c.ioctl(fd, vt.VT_WAITACTIVE, tty);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getLockState(console_dev: []const u8) !struct {
|
|
||||||
numlock: bool,
|
|
||||||
capslock: bool,
|
|
||||||
} {
|
|
||||||
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .RDONLY }, 0);
|
|
||||||
defer std.posix.close(fd);
|
|
||||||
|
|
||||||
var led: LedState = undefined;
|
|
||||||
_ = std.c.ioctl(fd, get_led_state, &led);
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.numlock = (led & numlock_led) != 0,
|
|
||||||
.capslock = (led & capslock_led) != 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setNumlock(val: bool) !void {
|
|
||||||
var led: LedState = undefined;
|
|
||||||
_ = std.c.ioctl(0, get_led_state, &led);
|
|
||||||
|
|
||||||
const numlock = (led & numlock_led) != 0;
|
|
||||||
if (numlock != val) {
|
|
||||||
const status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led);
|
|
||||||
if (status != 0) return error.FailedToSetNumlock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2686
src/main.zig
2686
src/main.zig
File diff suppressed because it is too large
Load Diff
@@ -1,61 +0,0 @@
|
|||||||
const Animation = @This();
|
|
||||||
|
|
||||||
const VTable = struct {
|
|
||||||
deinit_fn: *const fn (ptr: *anyopaque) void,
|
|
||||||
realloc_fn: *const fn (ptr: *anyopaque) anyerror!void,
|
|
||||||
draw_fn: *const fn (ptr: *anyopaque) void,
|
|
||||||
};
|
|
||||||
|
|
||||||
pointer: *anyopaque,
|
|
||||||
vtable: VTable,
|
|
||||||
|
|
||||||
pub fn init(
|
|
||||||
pointer: anytype,
|
|
||||||
comptime deinit_fn: fn (ptr: @TypeOf(pointer)) void,
|
|
||||||
comptime realloc_fn: fn (ptr: @TypeOf(pointer)) anyerror!void,
|
|
||||||
comptime draw_fn: fn (ptr: @TypeOf(pointer)) void,
|
|
||||||
) Animation {
|
|
||||||
const Pointer = @TypeOf(pointer);
|
|
||||||
const Impl = struct {
|
|
||||||
pub fn deinitImpl(ptr: *anyopaque) void {
|
|
||||||
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
|
||||||
return @call(.always_inline, deinit_fn, .{impl});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reallocImpl(ptr: *anyopaque) anyerror!void {
|
|
||||||
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
|
||||||
return @call(.always_inline, realloc_fn, .{impl});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawImpl(ptr: *anyopaque) void {
|
|
||||||
const impl: Pointer = @ptrCast(@alignCast(ptr));
|
|
||||||
return @call(.always_inline, draw_fn, .{impl});
|
|
||||||
}
|
|
||||||
|
|
||||||
const vtable = VTable{
|
|
||||||
.deinit_fn = deinitImpl,
|
|
||||||
.realloc_fn = reallocImpl,
|
|
||||||
.draw_fn = drawImpl,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.pointer = pointer,
|
|
||||||
.vtable = Impl.vtable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Animation) void {
|
|
||||||
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
|
||||||
return @call(.auto, self.vtable.deinit_fn, .{impl});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn realloc(self: *Animation) anyerror!void {
|
|
||||||
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
|
||||||
return @call(.auto, self.vtable.realloc_fn, .{impl});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: *Animation) void {
|
|
||||||
const impl: @TypeOf(self.pointer) = @ptrCast(@alignCast(self.pointer));
|
|
||||||
return @call(.auto, self.vtable.draw_fn, .{impl});
|
|
||||||
}
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const builtin = @import("builtin");
|
|
||||||
const interop = @import("../interop.zig");
|
|
||||||
const Cell = @import("Cell.zig");
|
|
||||||
|
|
||||||
const Random = std.Random;
|
|
||||||
|
|
||||||
const termbox = interop.termbox;
|
|
||||||
|
|
||||||
const TerminalBuffer = @This();
|
|
||||||
|
|
||||||
pub const InitOptions = struct {
|
|
||||||
fg: u32,
|
|
||||||
bg: u32,
|
|
||||||
border_fg: u32,
|
|
||||||
margin_box_h: u8,
|
|
||||||
margin_box_v: u8,
|
|
||||||
input_len: u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Styling = struct {
|
|
||||||
pub const BOLD = termbox.TB_BOLD;
|
|
||||||
pub const UNDERLINE = termbox.TB_UNDERLINE;
|
|
||||||
pub const REVERSE = termbox.TB_REVERSE;
|
|
||||||
pub const ITALIC = termbox.TB_ITALIC;
|
|
||||||
pub const BLINK = termbox.TB_BLINK;
|
|
||||||
pub const HI_BLACK = termbox.TB_HI_BLACK;
|
|
||||||
pub const BRIGHT = termbox.TB_BRIGHT;
|
|
||||||
pub const DIM = termbox.TB_DIM;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Color = struct {
|
|
||||||
pub const DEFAULT = 0x00000000;
|
|
||||||
pub const 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,
|
|
||||||
width: usize,
|
|
||||||
height: usize,
|
|
||||||
fg: u32,
|
|
||||||
bg: u32,
|
|
||||||
border_fg: u32,
|
|
||||||
box_chars: struct {
|
|
||||||
left_up: u32,
|
|
||||||
left_down: u32,
|
|
||||||
right_up: u32,
|
|
||||||
right_down: u32,
|
|
||||||
top: u32,
|
|
||||||
bottom: u32,
|
|
||||||
left: u32,
|
|
||||||
right: u32,
|
|
||||||
},
|
|
||||||
labels_max_length: usize,
|
|
||||||
box_x: usize,
|
|
||||||
box_y: usize,
|
|
||||||
box_width: usize,
|
|
||||||
box_height: usize,
|
|
||||||
margin_box_v: u8,
|
|
||||||
margin_box_h: u8,
|
|
||||||
blank_cell: Cell,
|
|
||||||
|
|
||||||
pub fn init(options: InitOptions, labels_max_length: usize, random: Random) TerminalBuffer {
|
|
||||||
return .{
|
|
||||||
.random = random,
|
|
||||||
.width = @intCast(termbox.tb_width()),
|
|
||||||
.height = @intCast(termbox.tb_height()),
|
|
||||||
.fg = options.fg,
|
|
||||||
.bg = options.bg,
|
|
||||||
.border_fg = options.border_fg,
|
|
||||||
.box_chars = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) .{
|
|
||||||
.left_up = 0x250C,
|
|
||||||
.left_down = 0x2514,
|
|
||||||
.right_up = 0x2510,
|
|
||||||
.right_down = 0x2518,
|
|
||||||
.top = 0x2500,
|
|
||||||
.bottom = 0x2500,
|
|
||||||
.left = 0x2502,
|
|
||||||
.right = 0x2502,
|
|
||||||
} else .{
|
|
||||||
.left_up = '+',
|
|
||||||
.left_down = '+',
|
|
||||||
.right_up = '+',
|
|
||||||
.right_down = '+',
|
|
||||||
.top = '-',
|
|
||||||
.bottom = '-',
|
|
||||||
.left = '|',
|
|
||||||
.right = '|',
|
|
||||||
},
|
|
||||||
.labels_max_length = labels_max_length,
|
|
||||||
.box_x = 0,
|
|
||||||
.box_y = 0,
|
|
||||||
.box_width = (2 * options.margin_box_h) + options.input_len + 1 + labels_max_length,
|
|
||||||
.box_height = 7 + (2 * options.margin_box_v),
|
|
||||||
.margin_box_v = options.margin_box_v,
|
|
||||||
.margin_box_h = options.margin_box_h,
|
|
||||||
.blank_cell = Cell.init(' ', options.fg, options.bg),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cascade(self: TerminalBuffer) bool {
|
|
||||||
var changed = false;
|
|
||||||
var y = self.height - 2;
|
|
||||||
|
|
||||||
while (y > 0) : (y -= 1) {
|
|
||||||
for (0..self.width) |x| {
|
|
||||||
var cell: termbox.tb_cell = undefined;
|
|
||||||
var cell_under: termbox.tb_cell = undefined;
|
|
||||||
|
|
||||||
_ = termbox.tb_get_cell(@intCast(x), @intCast(y - 1), 1, &cell);
|
|
||||||
_ = termbox.tb_get_cell(@intCast(x), @intCast(y), 1, &cell_under);
|
|
||||||
|
|
||||||
const char: u8 = @truncate(cell.ch);
|
|
||||||
if (std.ascii.isWhitespace(char)) continue;
|
|
||||||
|
|
||||||
const char_under: u8 = @truncate(cell_under.ch);
|
|
||||||
if (!std.ascii.isWhitespace(char_under)) continue;
|
|
||||||
|
|
||||||
changed = true;
|
|
||||||
|
|
||||||
if ((self.random.int(u16) % 10) > 7) continue;
|
|
||||||
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.fg, cell_under.bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
|
|
||||||
if (self.width < 2 or self.height < 2) return;
|
|
||||||
const x1 = (self.width - @min(self.width - 2, self.box_width)) / 2;
|
|
||||||
const y1 = (self.height - @min(self.height - 2, self.box_height)) / 2;
|
|
||||||
const x2 = (self.width + @min(self.width, self.box_width)) / 2;
|
|
||||||
const y2 = (self.height + @min(self.height, self.box_height)) / 2;
|
|
||||||
|
|
||||||
self.box_x = x1;
|
|
||||||
self.box_y = y1;
|
|
||||||
|
|
||||||
if (show_borders) {
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y1 - 1), self.box_chars.left_up, self.border_fg, self.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y1 - 1), self.box_chars.right_up, self.border_fg, self.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x1 - 1), @intCast(y2), self.box_chars.left_down, self.border_fg, self.bg);
|
|
||||||
_ = termbox.tb_set_cell(@intCast(x2), @intCast(y2), self.box_chars.right_down, self.border_fg, self.bg);
|
|
||||||
|
|
||||||
var c1 = Cell.init(self.box_chars.top, self.border_fg, self.bg);
|
|
||||||
var c2 = Cell.init(self.box_chars.bottom, self.border_fg, self.bg);
|
|
||||||
|
|
||||||
for (0..self.box_width) |i| {
|
|
||||||
c1.put(x1 + i, y1 - 1);
|
|
||||||
c2.put(x1 + i, y2);
|
|
||||||
}
|
|
||||||
|
|
||||||
c1.ch = self.box_chars.left;
|
|
||||||
c2.ch = self.box_chars.right;
|
|
||||||
|
|
||||||
for (0..self.box_height) |i| {
|
|
||||||
c1.put(x1 - 1, y1 + i);
|
|
||||||
c2.put(x2, y1 + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blank_box) {
|
|
||||||
for (0..self.box_height) |y| {
|
|
||||||
for (0..self.box_width) |x| {
|
|
||||||
self.blank_cell.put(x1 + x, y1 + y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
|
|
||||||
start_x: usize,
|
|
||||||
x: usize,
|
|
||||||
y: usize,
|
|
||||||
full_visible_length: usize,
|
|
||||||
visible_length: usize,
|
|
||||||
} {
|
|
||||||
const start_x = self.box_x + self.margin_box_h;
|
|
||||||
const x = start_x + self.labels_max_length + 1;
|
|
||||||
const y = self.box_y + self.margin_box_v;
|
|
||||||
const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x;
|
|
||||||
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.start_x = start_x,
|
|
||||||
.x = x,
|
|
||||||
.y = y,
|
|
||||||
.full_visible_length = full_visible_length,
|
|
||||||
.visible_length = visible_length,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
|
|
||||||
drawColorLabel(text, x, y, self.fg, self.bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u32, bg: u32) void {
|
|
||||||
const yc: c_int = @intCast(y);
|
|
||||||
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
|
||||||
var utf8 = utf8view.iterator();
|
|
||||||
|
|
||||||
var i = x;
|
|
||||||
while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
|
|
||||||
_ = termbox.tb_set_cell(@intCast(i), yc, codepoint, fg, bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize, max_length: usize) void {
|
|
||||||
const yc: c_int = @intCast(y);
|
|
||||||
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
|
||||||
var utf8 = utf8view.iterator();
|
|
||||||
|
|
||||||
var i: usize = 0;
|
|
||||||
while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
|
|
||||||
if (i >= max_length) break;
|
|
||||||
_ = termbox.tb_set_cell(@intCast(i + x), yc, codepoint, self.fg, self.bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawCharMultiple(self: TerminalBuffer, char: u32, x: usize, y: usize, length: usize) void {
|
|
||||||
const cell = Cell.init(char, self.fg, self.bg);
|
|
||||||
for (0..length) |xx| cell.put(x + xx, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Every codepoint is assumed to have a width of 1.
|
|
||||||
// Since Ly is normally running in a TTY, this should be fine.
|
|
||||||
pub fn strWidth(str: []const u8) !u8 {
|
|
||||||
const utf8view = try std.unicode.Utf8View.init(str);
|
|
||||||
var utf8 = utf8view.iterator();
|
|
||||||
var i: u8 = 0;
|
|
||||||
while (utf8.nextCodepoint()) |_| i += 1;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
|
||||||
const generic = @import("generic.zig");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const MessageLabel = generic.CyclableLabel(Message);
|
|
||||||
|
|
||||||
const InfoLine = @This();
|
|
||||||
|
|
||||||
const Message = struct {
|
|
||||||
width: u8,
|
|
||||||
text: []const u8,
|
|
||||||
bg: u32,
|
|
||||||
fg: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
label: MessageLabel,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
|
|
||||||
return .{
|
|
||||||
.label = MessageLabel.init(allocator, buffer, drawItem),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *InfoLine) void {
|
|
||||||
self.label.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
|
|
||||||
if (text.len == 0) return;
|
|
||||||
|
|
||||||
try self.label.addItem(.{
|
|
||||||
.width = try TerminalBuffer.strWidth(text),
|
|
||||||
.text = text,
|
|
||||||
.bg = bg,
|
|
||||||
.fg = fg,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void {
|
|
||||||
// Draw over the area
|
|
||||||
const y = buffer.box_y + buffer.margin_box_v;
|
|
||||||
const spaces = try allocator.alloc(u8, buffer.box_width);
|
|
||||||
defer allocator.free(spaces);
|
|
||||||
|
|
||||||
@memset(spaces, ' ');
|
|
||||||
|
|
||||||
buffer.drawLabel(spaces, buffer.box_x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool {
|
|
||||||
if (message.width == 0 or label.buffer.box_width <= message.width) return false;
|
|
||||||
|
|
||||||
const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2);
|
|
||||||
label.first_char_x = x + message.width;
|
|
||||||
|
|
||||||
TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
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 Allocator = std.mem.Allocator;
|
|
||||||
const DisplayServer = enums.DisplayServer;
|
|
||||||
const Ini = ini.Ini;
|
|
||||||
const EnvironmentLabel = generic.CyclableLabel(Environment);
|
|
||||||
|
|
||||||
const Session = @This();
|
|
||||||
|
|
||||||
label: EnvironmentLabel,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) Session {
|
|
||||||
return .{
|
|
||||||
.label = EnvironmentLabel.init(allocator, buffer, drawItem),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Session) void {
|
|
||||||
for (self.label.list.items) |*environment| {
|
|
||||||
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
|
|
||||||
if (environment.xdg_session_desktop) |session_desktop| self.label.allocator.free(session_desktop);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.label.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addEnvironment(self: *Session, environment: Environment) !void {
|
|
||||||
try self.label.addItem(environment);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn drawItem(label: *EnvironmentLabel, environment: Environment, x: usize, y: usize) bool {
|
|
||||||
const length = @min(environment.name.len, label.visible_length - 3);
|
|
||||||
if (length == 0) return false;
|
|
||||||
|
|
||||||
const nx = if (label.text_in_center) (label.x + (label.visible_length - environment.name.len) / 2) else (label.x + 2);
|
|
||||||
label.first_char_x = nx + environment.name.len;
|
|
||||||
|
|
||||||
label.buffer.drawLabel(environment.specifier, x, y);
|
|
||||||
label.buffer.drawLabel(environment.name, nx, label.y);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const interop = @import("../../interop.zig");
|
|
||||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
|
||||||
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const DynamicString = std.ArrayListUnmanaged(u8);
|
|
||||||
|
|
||||||
const termbox = interop.termbox;
|
|
||||||
|
|
||||||
const Text = @This();
|
|
||||||
|
|
||||||
allocator: Allocator,
|
|
||||||
buffer: *TerminalBuffer,
|
|
||||||
text: DynamicString,
|
|
||||||
end: usize,
|
|
||||||
cursor: usize,
|
|
||||||
visible_start: usize,
|
|
||||||
visible_length: usize,
|
|
||||||
x: usize,
|
|
||||||
y: usize,
|
|
||||||
masked: bool,
|
|
||||||
maybe_mask: ?u32,
|
|
||||||
|
|
||||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u32) Text {
|
|
||||||
const text: DynamicString = .empty;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.allocator = allocator,
|
|
||||||
.buffer = buffer,
|
|
||||||
.text = text,
|
|
||||||
.end = 0,
|
|
||||||
.cursor = 0,
|
|
||||||
.visible_start = 0,
|
|
||||||
.visible_length = 0,
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.masked = masked,
|
|
||||||
.maybe_mask = maybe_mask,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Text) void {
|
|
||||||
self.text.deinit(self.allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
|
|
||||||
self.x = x;
|
|
||||||
self.y = y;
|
|
||||||
self.visible_length = visible_length;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !void {
|
|
||||||
if (maybe_event) |event| blk: {
|
|
||||||
if (event.type != termbox.TB_EVENT_KEY) break :blk;
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
termbox.TB_KEY_ARROW_LEFT => self.goLeft(),
|
|
||||||
termbox.TB_KEY_ARROW_RIGHT => self.goRight(),
|
|
||||||
termbox.TB_KEY_DELETE => self.delete(),
|
|
||||||
termbox.TB_KEY_BACKSPACE, termbox.TB_KEY_BACKSPACE2 => {
|
|
||||||
if (insert_mode) {
|
|
||||||
self.backspace();
|
|
||||||
} else {
|
|
||||||
self.goLeft();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
termbox.TB_KEY_SPACE => try self.write(' '),
|
|
||||||
else => {
|
|
||||||
if (event.ch > 31 and event.ch < 127) {
|
|
||||||
if (insert_mode) {
|
|
||||||
try self.write(@intCast(event.ch));
|
|
||||||
} else {
|
|
||||||
switch (event.ch) {
|
|
||||||
'h' => self.goLeft(),
|
|
||||||
'l' => self.goRight(),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.masked and self.maybe_mask == null) {
|
|
||||||
_ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(self: Text) void {
|
|
||||||
if (self.masked) {
|
|
||||||
if (self.maybe_mask) |mask| {
|
|
||||||
const length = @min(self.text.items.len, self.visible_length - 1);
|
|
||||||
if (length == 0) return;
|
|
||||||
|
|
||||||
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const length = @min(self.text.items.len, self.visible_length);
|
|
||||||
if (length == 0) return;
|
|
||||||
|
|
||||||
const visible_slice = vs: {
|
|
||||||
if (self.text.items.len > self.visible_length and self.cursor < self.text.items.len) {
|
|
||||||
break :vs self.text.items[self.visible_start..(self.visible_length + self.visible_start)];
|
|
||||||
} else {
|
|
||||||
break :vs self.text.items[self.visible_start..];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.buffer.drawLabel(visible_slice, self.x, self.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(self: *Text) void {
|
|
||||||
self.text.clearRetainingCapacity();
|
|
||||||
self.end = 0;
|
|
||||||
self.cursor = 0;
|
|
||||||
self.visible_start = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn goLeft(self: *Text) void {
|
|
||||||
if (self.cursor == 0) return;
|
|
||||||
if (self.visible_start > 0) self.visible_start -= 1;
|
|
||||||
|
|
||||||
self.cursor -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn goRight(self: *Text) void {
|
|
||||||
if (self.cursor >= self.end) return;
|
|
||||||
if (self.cursor - self.visible_start == self.visible_length - 1) self.visible_start += 1;
|
|
||||||
|
|
||||||
self.cursor += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn delete(self: *Text) void {
|
|
||||||
if (self.cursor >= self.end) return;
|
|
||||||
|
|
||||||
_ = self.text.orderedRemove(self.cursor);
|
|
||||||
|
|
||||||
self.end -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backspace(self: *Text) void {
|
|
||||||
if (self.cursor == 0) return;
|
|
||||||
|
|
||||||
self.goLeft();
|
|
||||||
self.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(self: *Text, char: u8) !void {
|
|
||||||
if (char == 0) return;
|
|
||||||
|
|
||||||
try self.text.insert(self.allocator, self.cursor, char);
|
|
||||||
|
|
||||||
self.end += 1;
|
|
||||||
self.goRight();
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user