mirror of
https://github.com/fairyglade/ly.git
synced 2025-12-21 03:34:54 +00:00
Compare commits
102 Commits
v1.0.0
...
JAicewizar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bf4de816e | ||
|
|
c42cf125d3 | ||
|
|
2db48c50c9 | ||
|
|
cc15ab35d0 | ||
|
|
6bf9b928f2 | ||
|
|
87ceba4de8 | ||
|
|
b80c276dad | ||
|
|
b84158e1c0 | ||
|
|
c87d5b4e7a | ||
|
|
028cb9496a | ||
|
|
00c94f8ffd | ||
|
|
2bd0d0d4f3 | ||
|
|
767bdaf166 | ||
|
|
c033f5bd03 | ||
|
|
096b1a7d44 | ||
|
|
f0869f0e13 | ||
|
|
1ca53f661e | ||
|
|
8562cf4e29 | ||
|
|
b5b3317dd8 | ||
|
|
2901b408dc | ||
|
|
4e40e32f59 | ||
|
|
5e85618730 | ||
|
|
2c428f5537 | ||
|
|
071b7a2182 | ||
|
|
1075c923ef | ||
|
|
391f86f602 | ||
|
|
6fbbb4eff0 | ||
|
|
c7f70ac78f | ||
|
|
ef86ea19ac | ||
|
|
37061269a4 | ||
|
|
7b9f03176d | ||
|
|
b73c78d2fb | ||
|
|
0bbe9c78dd | ||
|
|
b18f29a81a | ||
|
|
cab3a7d214 | ||
|
|
8995c590eb | ||
|
|
57d5d7497b | ||
|
|
ce3b310e58 | ||
|
|
5d3cd62434 | ||
|
|
d40ec873a7 | ||
|
|
61f3fadfbf | ||
|
|
1314c57796 | ||
|
|
872b15c0d4 | ||
|
|
9b4d381f1e | ||
|
|
bacbacd5fb | ||
|
|
ee0c00574a | ||
|
|
6df91cac12 | ||
|
|
598fa6a505 | ||
|
|
548a411ae2 | ||
|
|
48f28e40c4 | ||
|
|
46f9ddd5fc | ||
|
|
a393525212 | ||
|
|
961018e753 | ||
|
|
8b12ade372 | ||
|
|
a64d7efc69 | ||
|
|
b592a11fb0 | ||
|
|
3fedb59fdb | ||
|
|
b1bf89a4cf | ||
|
|
2dec2e0b7f | ||
|
|
5f2f21620a | ||
|
|
48185bdfe0 | ||
|
|
f646dddd02 | ||
|
|
c1f1c8f5c1 | ||
|
|
ee488ba36e | ||
|
|
56c210372d | ||
|
|
04a0ad3b33 | ||
|
|
2dd83b41e8 | ||
|
|
19d4b195f3 | ||
|
|
93554d9ba3 | ||
|
|
075bf67cef | ||
|
|
33791a2844 | ||
|
|
8333f7ea77 | ||
|
|
2bc12549a1 | ||
|
|
1df890b238 | ||
|
|
92c6a38835 | ||
|
|
a7e8b55c6e | ||
|
|
b7e1c81ad1 | ||
|
|
dc310ca80e | ||
|
|
da2ea08078 | ||
|
|
8c69472065 | ||
|
|
0ee28927cf | ||
|
|
b7934e42d1 | ||
|
|
e775827c8b | ||
|
|
9cd58123c4 | ||
|
|
0cead672da | ||
|
|
ce90f91bbf | ||
|
|
23c7175528 | ||
|
|
5ea780f806 | ||
|
|
2eaa473144 | ||
|
|
a939b82179 | ||
|
|
2b0301c1d0 | ||
|
|
b84e6c9eed | ||
|
|
49b8697546 | ||
|
|
1d7a001a0b | ||
|
|
3dc1482603 | ||
|
|
e4abf79ad5 | ||
|
|
5f8fbe381c | ||
|
|
c6d7d177b7 | ||
|
|
dc8d143fac | ||
|
|
cbe7b37564 | ||
|
|
4eb4e15e43 | ||
|
|
08f6ce5184 |
54
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
54
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
name: Bug report
|
||||
description: File a bug report.
|
||||
title: "[Bug] "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: prerequisites
|
||||
attributes:
|
||||
label: Pre-requisites
|
||||
description: By submitting this issue, you agree to have done the following.
|
||||
options:
|
||||
- label: I have looked for any other duplicate issues
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Ly version
|
||||
description: The output of `ly --version`
|
||||
placeholder: 1.1.0-dev.12+2b0301c
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: observed
|
||||
attributes:
|
||||
label: Observed behavior
|
||||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: What did you expect to happen instead?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: What **exactly** can someone else do in order to observe the problem you observed?
|
||||
placeholder: |
|
||||
1. Authenticate with ...
|
||||
2. Go to ...
|
||||
3. Create file ...
|
||||
4. Log out and log back in
|
||||
5. Observe error
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant logs
|
||||
description: Please copy and paste 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.
|
||||
render: shell
|
||||
22
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
22
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Feature request
|
||||
description: Request a new feature or enhancement.
|
||||
title: "[Feature] "
|
||||
labels: ["feature"]
|
||||
body:
|
||||
- type: checkboxes
|
||||
id: prerequisites
|
||||
attributes:
|
||||
label: Pre-requisites
|
||||
description: By submitting this issue, you agree to have done the following.
|
||||
options:
|
||||
- label: I have looked for any other duplicate issues
|
||||
required: true
|
||||
- label: I have confirmed the requested feature doesn't exist in the latest version in development
|
||||
required: true
|
||||
- type: textarea
|
||||
id: wanted
|
||||
attributes:
|
||||
label: Wanted behavior
|
||||
description: What do you want to be added? Describe the behavior clearly.
|
||||
validations:
|
||||
required: true
|
||||
BIN
.github/screenshot.png
vendored
Normal file
BIN
.github/screenshot.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.idea/
|
||||
zig-cache/
|
||||
zig-out/
|
||||
valgrind.log
|
||||
valgrind.log
|
||||
.zig-cache
|
||||
|
||||
358
build.zig
358
build.zig
@@ -1,29 +1,57 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const PatchMap = std.StringHashMap([]const u8);
|
||||
|
||||
const min_zig_string = "0.12.0";
|
||||
const current_zig = builtin.zig_version;
|
||||
|
||||
// Implementing zig version detection through compile time
|
||||
comptime {
|
||||
const min_zig = std.SemanticVersion.parse(min_zig_string) catch unreachable;
|
||||
if (current_zig.order(min_zig) == .lt) {
|
||||
@compileError(std.fmt.comptimePrint("Your Zig version v{} does not meet the minimum build requirement of v{}", .{ current_zig, min_zig }));
|
||||
}
|
||||
}
|
||||
|
||||
const ly_version = std.SemanticVersion{ .major = 1, .minor = 1, .patch = 0 };
|
||||
|
||||
const ly_version = std.SemanticVersion{ .major = 1, .minor = 0, .patch = 0 };
|
||||
var dest_directory: []const u8 = undefined;
|
||||
var data_directory: []const u8 = undefined;
|
||||
var exe_name: []const u8 = undefined;
|
||||
var config_directory: []const u8 = undefined;
|
||||
var prefix_directory: []const u8 = undefined;
|
||||
var executable_name: []const u8 = undefined;
|
||||
var default_tty_str: []const u8 = undefined;
|
||||
|
||||
const ProgressNode = if (current_zig.minor == 12) *std.Progress.Node else std.Progress.Node;
|
||||
|
||||
pub fn build(b: *std.Build) !void {
|
||||
dest_directory = b.option([]const u8, "dest_directory", "Specify a dest directory for installation") orelse "";
|
||||
data_directory = b.option([]const u8, "data_directory", "Specify a default data directory (default is /etc/ly)") orelse "/etc/ly";
|
||||
data_directory = try std.fs.path.join(b.allocator, &[_][]const u8{ dest_directory, data_directory });
|
||||
exe_name = b.option([]const u8, "name", "Specify installed executable file name (default is ly)") orelse "ly";
|
||||
dest_directory = b.option([]const u8, "dest_directory", "Specify a destination directory for installation") orelse "";
|
||||
config_directory = b.option([]const u8, "config_directory", "Specify a default config directory (default is /etc). This path gets embedded into the binary") orelse "/etc";
|
||||
prefix_directory = b.option([]const u8, "prefix_directory", "Specify a default prefix directory (default is /usr)") orelse "/usr";
|
||||
executable_name = b.option([]const u8, "name", "Specify installed executable file name (default is ly)") orelse "ly";
|
||||
|
||||
const bin_directory = try b.allocator.dupe(u8, config_directory);
|
||||
config_directory = try std.fs.path.join(b.allocator, &[_][]const u8{ dest_directory, config_directory });
|
||||
|
||||
const build_options = b.addOptions();
|
||||
build_options.addOption([]const u8, "data_directory", data_directory);
|
||||
|
||||
const version_str = try getVersionStr(b, "ly", ly_version);
|
||||
const enable_x11_support = b.option(bool, "enable_x11_support", "Enable X11 support (default is on)") orelse true;
|
||||
const default_tty = b.option(u8, "default_tty", "Set the TTY (default is 2)") orelse 2;
|
||||
|
||||
default_tty_str = try std.fmt.allocPrint(b.allocator, "{d}", .{default_tty});
|
||||
|
||||
build_options.addOption([]const u8, "config_directory", bin_directory);
|
||||
build_options.addOption([]const u8, "prefix_directory", prefix_directory);
|
||||
build_options.addOption([]const u8, "version", version_str);
|
||||
build_options.addOption(u8, "tty", default_tty);
|
||||
build_options.addOption(bool, "enable_x11_support", enable_x11_support);
|
||||
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "ly",
|
||||
.root_source_file = .{ .path = "src/main.zig" },
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
@@ -36,25 +64,20 @@ pub fn build(b: *std.Build) !void {
|
||||
const clap = b.dependency("clap", .{ .target = target, .optimize = optimize });
|
||||
exe.root_module.addImport("clap", clap.module("clap"));
|
||||
|
||||
exe.addIncludePath(.{ .path = "include" });
|
||||
exe.addIncludePath(b.path("include"));
|
||||
exe.linkSystemLibrary("pam");
|
||||
exe.linkSystemLibrary("xcb");
|
||||
if (enable_x11_support) exe.linkSystemLibrary("xcb");
|
||||
exe.linkLibC();
|
||||
|
||||
// HACK: Only fails with ReleaseSafe, so we'll override it.
|
||||
const translate_c = b.addTranslateC(.{
|
||||
.root_source_file = .{ .path = "include/termbox2.h" },
|
||||
.root_source_file = b.path("include/termbox2.h"),
|
||||
.target = target,
|
||||
.optimize = if (optimize == .ReleaseSafe) .ReleaseFast else optimize,
|
||||
.optimize = optimize,
|
||||
});
|
||||
translate_c.defineCMacroRaw("TB_IMPL");
|
||||
const termbox2 = translate_c.addModule("termbox2");
|
||||
exe.root_module.addImport("termbox2", termbox2);
|
||||
|
||||
if (optimize == .ReleaseSafe) {
|
||||
std.debug.print("warn: termbox2 module is being built in ReleaseFast due to a bug.\n", .{});
|
||||
}
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
@@ -86,14 +109,21 @@ pub fn build(b: *std.Build) !void {
|
||||
installrunit_step.makeFn = ServiceInstaller(.Runit).make;
|
||||
installrunit_step.dependOn(installexe_step);
|
||||
|
||||
const installs6_step = b.step("installs6", "Install the Ly s6 service");
|
||||
installs6_step.makeFn = ServiceInstaller(.S6).make;
|
||||
installs6_step.dependOn(installexe_step);
|
||||
|
||||
const installdinit_step = b.step("installdinit", "Install the Ly dinit service");
|
||||
installdinit_step.makeFn = ServiceInstaller(.Dinit).make;
|
||||
installdinit_step.dependOn(installexe_step);
|
||||
|
||||
const uninstallall_step = b.step("uninstallall", "Uninstall Ly and all services");
|
||||
uninstallall_step.makeFn = uninstallall;
|
||||
}
|
||||
|
||||
pub fn ExeInstaller(install_conf: bool) type {
|
||||
return struct {
|
||||
pub fn make(step: *std.Build.Step, progress: *std.Progress.Node) !void {
|
||||
_ = progress;
|
||||
pub fn make(step: *std.Build.Step, _: ProgressNode) !void {
|
||||
try install_ly(step.owner.allocator, install_conf);
|
||||
}
|
||||
};
|
||||
@@ -103,38 +133,88 @@ const InitSystem = enum {
|
||||
Systemd,
|
||||
Openrc,
|
||||
Runit,
|
||||
S6,
|
||||
Dinit,
|
||||
};
|
||||
|
||||
pub fn ServiceInstaller(comptime init_system: InitSystem) type {
|
||||
return struct {
|
||||
pub fn make(step: *std.Build.Step, progress: *std.Progress.Node) !void {
|
||||
_ = progress;
|
||||
pub fn make(step: *std.Build.Step, _: ProgressNode) !void {
|
||||
const allocator = step.owner.allocator;
|
||||
|
||||
var patch_map = PatchMap.init(allocator);
|
||||
defer patch_map.deinit();
|
||||
|
||||
try patch_map.put("$DEFAULT_TTY", default_tty_str);
|
||||
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
|
||||
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
|
||||
try patch_map.put("$EXECUTABLE_NAME", executable_name);
|
||||
|
||||
switch (init_system) {
|
||||
.Openrc => {
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/init.d" });
|
||||
.Systemd => {
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/lib/systemd/system" });
|
||||
std.fs.cwd().makePath(service_path) catch {};
|
||||
var service_dir = std.fs.openDirAbsolute(service_path, .{}) catch unreachable;
|
||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
||||
defer service_dir.close();
|
||||
|
||||
try std.fs.cwd().copyFile("res/ly-openrc", service_dir, exe_name, .{ .override_mode = 755 });
|
||||
const patched_service = try patchFile(allocator, "res/ly.service", patch_map);
|
||||
try installText(patched_service, service_dir, service_path, "ly.service", .{ .mode = 0o644 });
|
||||
},
|
||||
.Openrc => {
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/init.d" });
|
||||
std.fs.cwd().makePath(service_path) catch {};
|
||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
||||
defer service_dir.close();
|
||||
|
||||
const patched_service = try patchFile(allocator, "res/ly-openrc", patch_map);
|
||||
try installText(patched_service, service_dir, service_path, executable_name, .{ .mode = 0o755 });
|
||||
},
|
||||
.Runit => {
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/sv/ly" });
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/sv/ly" });
|
||||
std.fs.cwd().makePath(service_path) catch {};
|
||||
var service_dir = std.fs.openDirAbsolute(service_path, .{}) catch unreachable;
|
||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
||||
defer service_dir.close();
|
||||
|
||||
try std.fs.cwd().copyFile("res/ly-runit-service/conf", service_dir, "conf", .{});
|
||||
try std.fs.cwd().copyFile("res/ly-runit-service/finish", service_dir, "finish", .{});
|
||||
try std.fs.cwd().copyFile("res/ly-runit-service/run", service_dir, "run", .{});
|
||||
const supervise_path = try std.fs.path.join(allocator, &[_][]const u8{ service_path, "supervise" });
|
||||
|
||||
const patched_conf = try patchFile(allocator, "res/ly-runit-service/conf", patch_map);
|
||||
try installText(patched_conf, service_dir, service_path, "conf", .{});
|
||||
|
||||
try installFile("res/ly-runit-service/finish", service_dir, service_path, "finish", .{ .override_mode = 0o755 });
|
||||
|
||||
const patched_run = try patchFile(allocator, "res/ly-runit-service/run", patch_map);
|
||||
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
|
||||
|
||||
try std.fs.cwd().symLink("/run/runit/supervise.ly", supervise_path, .{});
|
||||
std.debug.print("info: installed symlink /run/runit/supervise.ly\n", .{});
|
||||
},
|
||||
.Systemd => {
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/lib/systemd/system" });
|
||||
.S6 => {
|
||||
const admin_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/adminsv/default/contents.d" });
|
||||
std.fs.cwd().makePath(admin_service_path) catch {};
|
||||
var admin_service_dir = std.fs.cwd().openDir(admin_service_path, .{}) catch unreachable;
|
||||
defer admin_service_dir.close();
|
||||
|
||||
const file = try admin_service_dir.createFile("ly-srv", .{});
|
||||
file.close();
|
||||
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/s6/sv/ly-srv" });
|
||||
std.fs.cwd().makePath(service_path) catch {};
|
||||
var service_dir = std.fs.openDirAbsolute(service_path, .{}) catch unreachable;
|
||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
||||
defer service_dir.close();
|
||||
|
||||
try std.fs.cwd().copyFile("res/ly.service", service_dir, "ly.service", .{ .override_mode = 644 });
|
||||
const patched_run = try patchFile(allocator, "res/ly-s6/run", patch_map);
|
||||
try installText(patched_run, service_dir, service_path, "run", .{ .mode = 0o755 });
|
||||
|
||||
try installFile("res/ly-s6/type", service_dir, service_path, "type", .{});
|
||||
},
|
||||
.Dinit => {
|
||||
const service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/dinit.d" });
|
||||
std.fs.cwd().makePath(service_path) catch {};
|
||||
var service_dir = std.fs.cwd().openDir(service_path, .{}) catch unreachable;
|
||||
defer service_dir.close();
|
||||
|
||||
const patched_service = try patchFile(allocator, "res/ly-dinit", patch_map);
|
||||
try installText(patched_service, service_dir, service_path, "ly", .{});
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -142,104 +222,115 @@ pub fn ServiceInstaller(comptime init_system: InitSystem) type {
|
||||
}
|
||||
|
||||
fn install_ly(allocator: std.mem.Allocator, install_config: bool) !void {
|
||||
std.fs.cwd().makePath(data_directory) catch {
|
||||
std.debug.print("warn: {s} already exists as a directory.\n", .{data_directory});
|
||||
const ly_config_directory = try std.fs.path.join(allocator, &[_][]const u8{ config_directory, "/ly" });
|
||||
|
||||
std.fs.cwd().makePath(ly_config_directory) catch {
|
||||
std.debug.print("warn: {s} already exists as a directory.\n", .{ly_config_directory});
|
||||
};
|
||||
|
||||
const lang_path = try std.fs.path.join(allocator, &[_][]const u8{ data_directory, "/lang" });
|
||||
std.fs.cwd().makePath(lang_path) catch {
|
||||
std.debug.print("warn: {s} already exists as a directory.\n", .{data_directory});
|
||||
const ly_lang_path = try std.fs.path.join(allocator, &[_][]const u8{ config_directory, "/ly/lang" });
|
||||
std.fs.cwd().makePath(ly_lang_path) catch {
|
||||
std.debug.print("warn: {s} already exists as a directory.\n", .{config_directory});
|
||||
};
|
||||
|
||||
var current_dir = std.fs.cwd();
|
||||
|
||||
{
|
||||
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/bin" });
|
||||
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin" });
|
||||
if (!std.mem.eql(u8, dest_directory, "")) {
|
||||
std.fs.cwd().makePath(exe_path) catch {
|
||||
std.debug.print("warn: {s} already exists as a directory.\n", .{exe_path});
|
||||
};
|
||||
}
|
||||
|
||||
var executable_dir = std.fs.openDirAbsolute(exe_path, .{}) catch unreachable;
|
||||
var executable_dir = std.fs.cwd().openDir(exe_path, .{}) catch unreachable;
|
||||
defer executable_dir.close();
|
||||
|
||||
try current_dir.copyFile("zig-out/bin/ly", executable_dir, exe_name, .{});
|
||||
try installFile("zig-out/bin/ly", executable_dir, exe_path, executable_name, .{});
|
||||
}
|
||||
|
||||
{
|
||||
var config_dir = std.fs.openDirAbsolute(data_directory, .{}) catch unreachable;
|
||||
var config_dir = std.fs.cwd().openDir(ly_config_directory, .{}) catch unreachable;
|
||||
defer config_dir.close();
|
||||
|
||||
if (install_config) {
|
||||
try current_dir.copyFile("res/config.ini", config_dir, "config.ini", .{});
|
||||
var patch_map = PatchMap.init(allocator);
|
||||
defer patch_map.deinit();
|
||||
|
||||
try patch_map.put("$DEFAULT_TTY", default_tty_str);
|
||||
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
|
||||
try patch_map.put("$PREFIX_DIRECTORY", prefix_directory);
|
||||
|
||||
const patched_config = try patchFile(allocator, "res/config.ini", patch_map);
|
||||
try installText(patched_config, config_dir, ly_config_directory, "config.ini", .{});
|
||||
}
|
||||
|
||||
{
|
||||
var patch_map = PatchMap.init(allocator);
|
||||
defer patch_map.deinit();
|
||||
|
||||
try patch_map.put("$CONFIG_DIRECTORY", config_directory);
|
||||
|
||||
const patched_setup = try patchFile(allocator, "res/setup.sh", patch_map);
|
||||
try installText(patched_setup, config_dir, ly_config_directory, "setup.sh", .{ .mode = 0o755 });
|
||||
}
|
||||
try current_dir.copyFile("res/xsetup.sh", config_dir, "xsetup.sh", .{});
|
||||
try current_dir.copyFile("res/wsetup.sh", config_dir, "wsetup.sh", .{});
|
||||
}
|
||||
|
||||
{
|
||||
var lang_dir = std.fs.openDirAbsolute(lang_path, .{}) catch unreachable;
|
||||
var lang_dir = std.fs.cwd().openDir(ly_lang_path, .{}) catch unreachable;
|
||||
defer lang_dir.close();
|
||||
|
||||
try current_dir.copyFile("res/lang/cat.ini", lang_dir, "cat.ini", .{});
|
||||
try current_dir.copyFile("res/lang/cs.ini", lang_dir, "cs.ini", .{});
|
||||
try current_dir.copyFile("res/lang/de.ini", lang_dir, "de.ini", .{});
|
||||
try current_dir.copyFile("res/lang/en.ini", lang_dir, "en.ini", .{});
|
||||
try current_dir.copyFile("res/lang/es.ini", lang_dir, "es.ini", .{});
|
||||
try current_dir.copyFile("res/lang/fr.ini", lang_dir, "fr.ini", .{});
|
||||
try current_dir.copyFile("res/lang/it.ini", lang_dir, "it.ini", .{});
|
||||
try current_dir.copyFile("res/lang/pl.ini", lang_dir, "pl.ini", .{});
|
||||
try current_dir.copyFile("res/lang/pt.ini", lang_dir, "pt.ini", .{});
|
||||
try current_dir.copyFile("res/lang/pt_BR.ini", lang_dir, "pt_BR.ini", .{});
|
||||
try current_dir.copyFile("res/lang/ro.ini", lang_dir, "ro.ini", .{});
|
||||
try current_dir.copyFile("res/lang/ru.ini", lang_dir, "ru.ini", .{});
|
||||
try current_dir.copyFile("res/lang/sr.ini", lang_dir, "sr.ini", .{});
|
||||
try current_dir.copyFile("res/lang/sv.ini", lang_dir, "sv.ini", .{});
|
||||
try current_dir.copyFile("res/lang/tr.ini", lang_dir, "tr.ini", .{});
|
||||
try current_dir.copyFile("res/lang/uk.ini", lang_dir, "uk.ini", .{});
|
||||
try installFile("res/lang/cat.ini", lang_dir, ly_lang_path, "cat.ini", .{});
|
||||
try installFile("res/lang/cs.ini", lang_dir, ly_lang_path, "cs.ini", .{});
|
||||
try installFile("res/lang/de.ini", lang_dir, ly_lang_path, "de.ini", .{});
|
||||
try installFile("res/lang/en.ini", lang_dir, ly_lang_path, "en.ini", .{});
|
||||
try installFile("res/lang/es.ini", lang_dir, ly_lang_path, "es.ini", .{});
|
||||
try installFile("res/lang/fr.ini", lang_dir, ly_lang_path, "fr.ini", .{});
|
||||
try installFile("res/lang/it.ini", lang_dir, ly_lang_path, "it.ini", .{});
|
||||
try installFile("res/lang/pl.ini", lang_dir, ly_lang_path, "pl.ini", .{});
|
||||
try installFile("res/lang/pt.ini", lang_dir, ly_lang_path, "pt.ini", .{});
|
||||
try installFile("res/lang/pt_BR.ini", lang_dir, ly_lang_path, "pt_BR.ini", .{});
|
||||
try installFile("res/lang/ro.ini", lang_dir, ly_lang_path, "ro.ini", .{});
|
||||
try installFile("res/lang/ru.ini", lang_dir, ly_lang_path, "ru.ini", .{});
|
||||
try installFile("res/lang/sr.ini", lang_dir, ly_lang_path, "sr.ini", .{});
|
||||
try installFile("res/lang/sv.ini", lang_dir, ly_lang_path, "sv.ini", .{});
|
||||
try installFile("res/lang/tr.ini", lang_dir, ly_lang_path, "tr.ini", .{});
|
||||
try installFile("res/lang/uk.ini", lang_dir, ly_lang_path, "uk.ini", .{});
|
||||
}
|
||||
|
||||
{
|
||||
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/pam.d" });
|
||||
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, config_directory, "/pam.d" });
|
||||
if (!std.mem.eql(u8, dest_directory, "")) {
|
||||
std.fs.cwd().makePath(pam_path) catch {
|
||||
std.debug.print("warn: {s} already exists as a directory.\n", .{pam_path});
|
||||
};
|
||||
}
|
||||
|
||||
var pam_dir = std.fs.openDirAbsolute(pam_path, .{}) catch unreachable;
|
||||
var pam_dir = std.fs.cwd().openDir(pam_path, .{}) catch unreachable;
|
||||
defer pam_dir.close();
|
||||
|
||||
try current_dir.copyFile("res/pam.d/ly", pam_dir, "ly", .{ .override_mode = 644 });
|
||||
try installFile("res/pam.d/ly", pam_dir, pam_path, "ly", .{ .override_mode = 0o644 });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uninstallall(step: *std.Build.Step, progress: *std.Progress.Node) !void {
|
||||
_ = progress;
|
||||
try std.fs.deleteTreeAbsolute(data_directory);
|
||||
pub fn uninstallall(step: *std.Build.Step, _: ProgressNode) !void {
|
||||
const allocator = step.owner.allocator;
|
||||
|
||||
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/bin/", exe_name });
|
||||
try std.fs.deleteFileAbsolute(exe_path);
|
||||
try deleteTree(allocator, config_directory, "/ly", "ly config directory not found");
|
||||
|
||||
const pam_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/pam.d/ly" });
|
||||
try std.fs.deleteFileAbsolute(pam_path);
|
||||
|
||||
const systemd_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/usr/lib/systemd/system/ly.service" });
|
||||
std.fs.deleteFileAbsolute(systemd_service_path) catch {
|
||||
std.debug.print("warn: systemd service not found.\n", .{});
|
||||
const exe_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix_directory, "/bin/", executable_name });
|
||||
var success = true;
|
||||
std.fs.cwd().deleteFile(exe_path) catch {
|
||||
std.debug.print("warn: ly executable not found\n", .{});
|
||||
success = false;
|
||||
};
|
||||
if (success) std.debug.print("info: deleted {s}\n", .{exe_path});
|
||||
|
||||
const openrc_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/init.d/ly" });
|
||||
std.fs.deleteFileAbsolute(openrc_service_path) catch {
|
||||
std.debug.print("warn: openrc service not found.\n", .{});
|
||||
};
|
||||
|
||||
const runit_service_path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, "/etc/sv/ly" });
|
||||
std.fs.deleteTreeAbsolute(runit_service_path) catch {
|
||||
std.debug.print("warn: runit service not found.\n", .{});
|
||||
};
|
||||
try deleteFile(allocator, config_directory, "/pam.d/ly", "ly pam file not found");
|
||||
try deleteFile(allocator, prefix_directory, "/lib/systemd/system/ly.service", "systemd service not found");
|
||||
try deleteFile(allocator, config_directory, "/init.d/ly", "openrc service not found");
|
||||
try deleteTree(allocator, config_directory, "/sv/ly", "runit service not found");
|
||||
try deleteTree(allocator, config_directory, "/s6/sv/ly-srv", "s6 service not found");
|
||||
try deleteFile(allocator, config_directory, "/s6/adminsv/default/contents.d/ly-srv", "s6 admin service not found");
|
||||
try deleteFile(allocator, config_directory, "/dinit.d/ly", "dinit service not found");
|
||||
}
|
||||
|
||||
fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion) ![]const u8 {
|
||||
@@ -296,3 +387,90 @@ fn getVersionStr(b: *std.Build, name: []const u8, version: std.SemanticVersion)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn installFile(
|
||||
source_file: []const u8,
|
||||
destination_directory: std.fs.Dir,
|
||||
destination_directory_path: []const u8,
|
||||
destination_file: []const u8,
|
||||
options: std.fs.Dir.CopyFileOptions,
|
||||
) !void {
|
||||
try std.fs.cwd().copyFile(source_file, destination_directory, destination_file, options);
|
||||
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
|
||||
}
|
||||
|
||||
fn patchFile(allocator: std.mem.Allocator, source_file: []const u8, patch_map: PatchMap) ![]const u8 {
|
||||
var file = try std.fs.cwd().openFile(source_file, .{});
|
||||
defer file.close();
|
||||
|
||||
const reader = file.reader();
|
||||
var text = try reader.readAllAlloc(allocator, std.math.maxInt(u16));
|
||||
|
||||
var iterator = patch_map.iterator();
|
||||
while (iterator.next()) |kv| {
|
||||
const new_text = try std.mem.replaceOwned(u8, allocator, text, kv.key_ptr.*, kv.value_ptr.*);
|
||||
allocator.free(text);
|
||||
text = new_text;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
fn installText(
|
||||
text: []const u8,
|
||||
destination_directory: std.fs.Dir,
|
||||
destination_directory_path: []const u8,
|
||||
destination_file: []const u8,
|
||||
options: std.fs.File.CreateFlags,
|
||||
) !void {
|
||||
var file = try destination_directory.createFile(destination_file, options);
|
||||
defer file.close();
|
||||
|
||||
const writer = file.writer();
|
||||
try writer.writeAll(text);
|
||||
|
||||
std.debug.print("info: installed {s}/{s}\n", .{ destination_directory_path, destination_file });
|
||||
}
|
||||
|
||||
fn deleteFile(
|
||||
allocator: std.mem.Allocator,
|
||||
prefix: []const u8,
|
||||
file: []const u8,
|
||||
warning: []const u8,
|
||||
) !void {
|
||||
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, file });
|
||||
|
||||
std.fs.cwd().deleteFile(path) catch |err| {
|
||||
if (err == error.FileNotFound) {
|
||||
std.debug.print("warn: {s}\n", .{warning});
|
||||
return;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
|
||||
std.debug.print("info: deleted {s}\n", .{path});
|
||||
}
|
||||
|
||||
fn deleteTree(
|
||||
allocator: std.mem.Allocator,
|
||||
prefix: []const u8,
|
||||
directory: []const u8,
|
||||
warning: []const u8,
|
||||
) !void {
|
||||
const path = try std.fs.path.join(allocator, &[_][]const u8{ dest_directory, prefix, directory });
|
||||
|
||||
var dir = std.fs.cwd().openDir(path, .{}) catch |err| {
|
||||
if (err == error.FileNotFound) {
|
||||
std.debug.print("warn: {s}\n", .{warning});
|
||||
return;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
dir.close();
|
||||
|
||||
try std.fs.cwd().deleteTree(path);
|
||||
|
||||
std.debug.print("info: deleted {s}\n", .{path});
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
.{
|
||||
.name = "ly",
|
||||
.version = "1.0.0",
|
||||
.minimum_zig_version = "0.12.0",
|
||||
.dependencies = .{
|
||||
.clap = .{
|
||||
.url = "https://github.com/Hejsil/zig-clap/archive/8c98e6404b22aafc0184e999d8f068b81cc22fa1.tar.gz",
|
||||
.hash = "122014e73fd712190e109950837b97f6143f02d7e2b6986e1db70b6f4aadb5ba6a0d",
|
||||
.url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
|
||||
.hash = "122062d301a203d003547b414237229b09a7980095061697349f8bef41be9c30266b",
|
||||
},
|
||||
.zigini = .{
|
||||
.url = "https://github.com/Kawaii-Ash/zigini/archive/ce1f322482099db058f5d9fdd05fbfa255d79723.tar.gz",
|
||||
.hash = "1220e7a99793a0430e0a7c0b938cb3c98321035bc297e21cd0e2413cf740b4923b9f",
|
||||
.url = "https://github.com/Kawaii-Ash/zigini/archive/0bba97a12582928e097f4074cc746c43351ba4c8.tar.gz",
|
||||
.hash = "12209b971367b4066d40ecad4728e6fdffc4cc4f19356d424c2de57f5b69ac7a619a",
|
||||
},
|
||||
},
|
||||
.paths = .{""},
|
||||
|
||||
52
changelog.md
52
changelog.md
@@ -1,52 +0,0 @@
|
||||
# Zig Rewrite (Version 1.0.0)
|
||||
|
||||
## Config Options
|
||||
|
||||
res/config.ini contains all of the available config options and their default values.
|
||||
|
||||
### Additions
|
||||
|
||||
+ `border_fg` has been introduced to change the color of the borders.
|
||||
+ `term_restore_cursor_cmd` should restore the cursor to it's usual state.
|
||||
+ `vi_mode` to enable vi keybindings.
|
||||
+ `sleep_key` and `sleep_cmd`.
|
||||
+ `numlock` to set numlock on startup.
|
||||
+ `initial_info_text` allows changing the initial text on the info line.
|
||||
+ `box_title` to display a title at the top of the main box
|
||||
|
||||
Note: `sleep_cmd` is unset by default, meaning it's hidden and has no effect.
|
||||
|
||||
### Changes
|
||||
|
||||
+ xinitrc can be set to null to hide it.
|
||||
+ `blank_password` has been renamed to `clear_password`.
|
||||
+ `save_file` has been deprecated and will be removed in a future version.
|
||||
|
||||
### Removals
|
||||
|
||||
+ `wayland_specifier` has been removed.
|
||||
|
||||
## Save File
|
||||
|
||||
The save file is now in .ini format and stored in the same directory as the config.
|
||||
Older save files will be migrated to the new format.
|
||||
|
||||
Example:
|
||||
|
||||
```ini
|
||||
user = ash
|
||||
session_index = 0
|
||||
```
|
||||
|
||||
## Misc
|
||||
|
||||
+ Display server name added next to selected session.
|
||||
+ getty@tty2 has been added as a conflict in res/ly.service, so if it is running, ly should still be able to start.
|
||||
+ `XDG_CURRENT_DESKTOP` is now set by ly.
|
||||
+ LANG is no longer set by ly.
|
||||
+ X Server PID is fetched from /tmp/X{d}.lock to be able to kill the process since it detaches.
|
||||
+ Non .desktop files are now ignored in sessions directory.
|
||||
+ PAM auth is now done in a child process. (Fixes some issues with logging out and back in).
|
||||
+ When ly receives SIGTERM, the terminal is now cleared and existing child processes are cleaned up.
|
||||
+ Shift+Tab now focuses previous input.
|
||||
+ Display text in the info line when authenticating.
|
||||
@@ -23,8 +23,8 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __TERMBOX_H
|
||||
#define __TERMBOX_H
|
||||
#ifndef TERMBOX_H_INCL
|
||||
#define TERMBOX_H_INCL
|
||||
|
||||
#ifndef _XOPEN_SOURCE
|
||||
#define _XOPEN_SOURCE
|
||||
@@ -105,7 +105,7 @@ extern "C" {
|
||||
#elif defined TB_OPT_ATTR_W && TB_OPT_ATTR_W == 64
|
||||
#else
|
||||
#undef TB_OPT_ATTR_W
|
||||
#if defined TB_OPT_TRUECOLOR // Back-compat for old flag
|
||||
#if defined TB_OPT_TRUECOLOR // Deprecated. Back-compat for old flag.
|
||||
#define TB_OPT_ATTR_W 32
|
||||
#else
|
||||
#define TB_OPT_ATTR_W 16
|
||||
@@ -347,7 +347,7 @@ extern "C" {
|
||||
#define TB_ERR_SELECT TB_ERR_POLL
|
||||
#define TB_ERR_RESIZE_SELECT TB_ERR_RESIZE_POLL
|
||||
|
||||
/* Function types to be used with tb_set_func() */
|
||||
/* Deprecated. Function types to be used with tb_set_func(). */
|
||||
#define TB_FUNC_EXTRACT_PRE 0
|
||||
#define TB_FUNC_EXTRACT_POST 1
|
||||
|
||||
@@ -646,8 +646,8 @@ int tb_printf_ex(int x, int y, uintattr_t fg, uintattr_t bg, size_t *out_w,
|
||||
int tb_send(const char *buf, size_t nbuf);
|
||||
int tb_sendf(const char *fmt, ...);
|
||||
|
||||
/* Set custom functions. fn_type is one of TB_FUNC_* constants, fn is a
|
||||
* compatible function pointer, or NULL to clear.
|
||||
/* Deprecated. Set custom functions. fn_type is one of TB_FUNC_* constants, fn
|
||||
* is a compatible function pointer, or NULL to clear.
|
||||
*
|
||||
* TB_FUNC_EXTRACT_PRE:
|
||||
* If specified, invoke this function BEFORE termbox tries to extract any
|
||||
@@ -683,17 +683,35 @@ int tb_utf8_unicode_to_char(char *out, uint32_t c);
|
||||
/* Library utility functions */
|
||||
int tb_last_errno(void);
|
||||
const char *tb_strerror(int err);
|
||||
struct tb_cell *tb_cell_buffer(void);
|
||||
struct tb_cell *tb_cell_buffer(void); // Deprecated
|
||||
int tb_has_truecolor(void);
|
||||
int tb_has_egc(void);
|
||||
int tb_attr_width(void);
|
||||
const char *tb_version(void);
|
||||
|
||||
/* Deprecation notice!
|
||||
*
|
||||
* The following will be removed in version 3.x (ABI version 3):
|
||||
*
|
||||
* TB_256_BLACK (use TB_HI_BLACK)
|
||||
* TB_OPT_TRUECOLOR (use TB_OPT_ATTR_W)
|
||||
* TB_TRUECOLOR_BOLD (use TB_BOLD)
|
||||
* TB_TRUECOLOR_UNDERLINE (use TB_UNDERLINE)
|
||||
* TB_TRUECOLOR_REVERSE (use TB_REVERSE)
|
||||
* TB_TRUECOLOR_ITALIC (use TB_ITALICe)
|
||||
* TB_TRUECOLOR_BLINK (use TB_BLINK)
|
||||
* TB_TRUECOLOR_BLACK (use TB_HI_BLACK)
|
||||
* tb_cell_buffer
|
||||
* tb_set_func
|
||||
* TB_FUNC_EXTRACT_PRE
|
||||
* TB_FUNC_EXTRACT_POST
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __TERMBOX_H */
|
||||
#endif /* TERMBOX_H_INCL */
|
||||
|
||||
#ifdef TB_IMPL
|
||||
|
||||
@@ -1648,6 +1666,7 @@ int tb_present(void) {
|
||||
|
||||
send_attr(back->fg, back->bg);
|
||||
if (w > 1 && x >= global.front.width - (w - 1)) {
|
||||
// Not enough room for wide char, send spaces
|
||||
for (i = x; i < global.front.width; i++) {
|
||||
send_char(i, y, ' ');
|
||||
}
|
||||
@@ -1660,12 +1679,20 @@ int tb_present(void) {
|
||||
#endif
|
||||
send_char(x, y, back->ch);
|
||||
}
|
||||
|
||||
// When wcwidth>1, we need to advance the cursor by more
|
||||
// than 1, thereby skipping some cells. Set these skipped
|
||||
// cells to an invalid codepoint in the front buffer, so
|
||||
// that if this cell is later replaced by a wcwidth==1 char,
|
||||
// we'll get a cell_cmp diff for the skipped cells and
|
||||
// properly re-render.
|
||||
for (i = 1; i < w; i++) {
|
||||
struct tb_cell *front_wide;
|
||||
uint32_t invalid = -1;
|
||||
if_err_return(rv,
|
||||
cellbuf_get(&global.front, x + i, y, &front_wide));
|
||||
if_err_return(rv,
|
||||
cell_set(front_wide, 0, 1, back->fg, back->bg));
|
||||
cell_set(front_wide, &invalid, 1, -1, -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
69
readme.md
69
readme.md
@@ -1,20 +1,18 @@
|
||||
|
||||
# Ly - a TUI display manager
|
||||

|
||||

|
||||
|
||||
Ly is a lightweight TUI (ncurses-like) display manager for Linux and BSD.
|
||||
|
||||
## Dependencies
|
||||
- Compile-time:
|
||||
- zig 0.12.0
|
||||
- a C standard library
|
||||
- zig >=0.12.0
|
||||
- libc
|
||||
- pam
|
||||
- xcb
|
||||
- xcb (optional, required by default; needed for X11 support)
|
||||
- Runtime (with default config):
|
||||
- xorg
|
||||
- xorg-xauth
|
||||
- mcookie
|
||||
- tput
|
||||
- shutdown
|
||||
|
||||
### Debian
|
||||
@@ -36,6 +34,7 @@ The following desktop environments were tested with success:
|
||||
- bspwm
|
||||
- budgie
|
||||
- cinnamon
|
||||
- cosmic
|
||||
- deepin
|
||||
- dwl
|
||||
- dwm
|
||||
@@ -104,7 +103,7 @@ disable getty on Ly's tty to prevent "login" from spawning on top of it
|
||||
```
|
||||
|
||||
### OpenRC
|
||||
**NOTE**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
|
||||
**NOTE 1**: On Gentoo, Ly will disable the `display-manager-init` service in order to run.
|
||||
|
||||
Clone, compile and test.
|
||||
|
||||
@@ -126,13 +125,15 @@ then you have to disable getty, so it doesn't respawn on top of ly
|
||||
# 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).
|
||||
|
||||
### runit
|
||||
```
|
||||
# zig build installrunit
|
||||
# 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`
|
||||
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.:
|
||||
|
||||
@@ -148,6 +149,31 @@ you should disable the agetty-tty2 service like this:
|
||||
# rm /var/service/agetty-tty2
|
||||
```
|
||||
|
||||
### s6
|
||||
```
|
||||
# zig build installs6
|
||||
```
|
||||
|
||||
Then, edit `/etc/s6/config/ttyX.conf` and set `SPAWN="no"`, where X is the TTY ID (e.g. `2`).
|
||||
|
||||
Finally, enable the service:
|
||||
|
||||
```
|
||||
# s6-service add default ly-srv
|
||||
# s6-db-reload
|
||||
# s6-rc -u change ly-srv
|
||||
```
|
||||
|
||||
### dinit
|
||||
```
|
||||
# zig build installdinit
|
||||
# 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 do that, change `ACTIVE_CONSOLES` so that the tty that ly should use in `/etc/ly/config.ini` is free.
|
||||
|
||||
### Updating
|
||||
You can also install Ly without copying the system service and the configuration file. That's
|
||||
called *updating*. To update, simply run:
|
||||
@@ -168,6 +194,29 @@ You can install ly from the [`[extra]` repos](https://archlinux.org/packages/ext
|
||||
$ sudo pacman -S ly
|
||||
```
|
||||
|
||||
## Gentoo Installation
|
||||
You can install ly from the GURU repository:
|
||||
|
||||
Note: If the package is masked, you may need to unmask it using ~amd64 keyword:
|
||||
```bash
|
||||
# echo 'x11-misc/ly ~amd64' >> /etc/portage/package.accept_keywords
|
||||
```
|
||||
|
||||
1. Enable the GURU repository:
|
||||
```bash
|
||||
# eselect repository enable guru
|
||||
```
|
||||
|
||||
2. Sync the GURU repository:
|
||||
```bash
|
||||
# emaint sync -r guru
|
||||
```
|
||||
|
||||
3. Install ly from source:
|
||||
```bash
|
||||
# emerge --ask x11-misc/ly
|
||||
```
|
||||
|
||||
## Configuration
|
||||
You can find all the configuration in `/etc/ly/config.ini`.
|
||||
The file is commented, and includes the default values.
|
||||
@@ -196,12 +245,10 @@ Take a look at your .xsession if X doesn't start, as it can interfere
|
||||
|
||||
## PSX DOOM fire animation
|
||||
To enable the famous PSX DOOM fire described by [Fabien Sanglard](http://fabiensanglard.net/doom_fire_psx/index.html),
|
||||
just uncomment `animate = true` in `/etc/ly/config.ini`. You may also
|
||||
just set `animation = doom` in `/etc/ly/config.ini`. You may also
|
||||
disable the main box borders with `hide_borders = true`.
|
||||
|
||||
## Additional Information
|
||||
The name "Ly" is a tribute to the fairy from the game Rayman.
|
||||
Ly was tested by oxodao, who is some seriously awesome dude.
|
||||
|
||||
## Gentoo (OpenRC) installation tip
|
||||
To avoid a console spawning on top on Ly, comment out the appropriate line from /etc/inittab (default is 2).
|
||||
|
||||
279
res/config.ini
279
res/config.ini
@@ -1,25 +1,4 @@
|
||||
# The active animation
|
||||
# none -> Nothing (default)
|
||||
# doom -> PSX DOOM fire
|
||||
# matrix -> CMatrix
|
||||
animation = none
|
||||
|
||||
# Format string for clock in top right corner (see strftime specification). Example: %c
|
||||
clock = null
|
||||
|
||||
# Enable/disable big clock
|
||||
bigclock = false
|
||||
|
||||
# The character used to mask the password
|
||||
asterisk = *
|
||||
|
||||
# Erase password input on failure
|
||||
clear_password = false
|
||||
|
||||
# Enable vi keybindings
|
||||
vi_mode = false
|
||||
|
||||
# The `fg` and `bg` color settings take a digit 0-8 corresponding to:
|
||||
# The color settings in Ly take a digit 0-8 corresponding to:
|
||||
#define TB_DEFAULT 0x00
|
||||
#define TB_BLACK 0x01
|
||||
#define TB_RED 0x02
|
||||
@@ -29,98 +8,142 @@ vi_mode = false
|
||||
#define TB_MAGENTA 0x06
|
||||
#define TB_CYAN 0x07
|
||||
#define TB_WHITE 0x08
|
||||
# The default color varies, but usually it makes the background black and the foreground white.
|
||||
# You can also combine these colors with the following style attributes using bitwise OR:
|
||||
#define TB_BOLD 0x0100
|
||||
#define TB_UNDERLINE 0x0200
|
||||
#define TB_REVERSE 0x0400
|
||||
#define TB_ITALIC 0x0800
|
||||
#define TB_BLINK 0x1000
|
||||
#define TB_HI_BLACK 0x2000
|
||||
#define TB_BRIGHT 0x4000
|
||||
#define TB_DIM 0x8000
|
||||
# For example, to set the foreground color to red and bold, you would do 0x02 | 0x0100 = 0x0102.
|
||||
# Note that you must pre-calculate the value because Ly doesn't parse bitwise OR operations in its config.
|
||||
#
|
||||
# Setting both to zero makes `bg` black and `fg` white. To set the actual color palette you are encouraged to use another tool
|
||||
# such as [mkinitcpio-colors](https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with
|
||||
# `mkinitcpio-colors` takes 16 colors (0-15), only values 0-8 are valid for `ly` config and these values do not correspond
|
||||
# exactly. For instance, in defining palettes with `mkinitcpio-colors` the order is black, dark red, dark green, brown, dark
|
||||
# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright
|
||||
# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio
|
||||
# config) will be used by `ly` for `fg = 8`.
|
||||
# Moreover, to set the VT color palette, you are encouraged to use another tool such as
|
||||
# mkinitcpio-colors (https://github.com/evanpurkhiser/mkinitcpio-colors). Note that the color palette defined with
|
||||
# mkinitcpio-colors takes 16 colors (0-15), only values 0-8 are valid with Ly and these values do not correspond
|
||||
# exactly. For instance, in defining palettes with mkinitcpio-colors, the order is black, dark red, dark green, brown, dark
|
||||
# blue, dark purple, dark cyan, light gray, dark gray, bright red, bright green, yellow, bright blue, bright purple, bright
|
||||
# cyan, and white, indexed in that order 0 through 15. For example, the color defined for white (indexed at 15 in the mkinitcpio
|
||||
# config) will be used by Ly for fg = 0x0008.
|
||||
|
||||
# The active animation
|
||||
# none -> Nothing
|
||||
# doom -> PSX DOOM fire
|
||||
# matrix -> CMatrix
|
||||
animation = none
|
||||
|
||||
# Stop the animation after some time
|
||||
# 0 -> Run forever
|
||||
# 1..2e12 -> Stop the animation after this many seconds
|
||||
animation_timeout_sec = 0
|
||||
|
||||
# The character used to mask the password
|
||||
# If null, the password will be hidden
|
||||
# Note: you can use a # by escaping it like so: \#
|
||||
asterisk = *
|
||||
|
||||
# The number of failed authentications before a special animation is played... ;)
|
||||
auth_fails = 10
|
||||
|
||||
# Background color id
|
||||
bg = 0
|
||||
bg = 0x0000
|
||||
|
||||
# Foreground color id
|
||||
fg = 8
|
||||
|
||||
# Border color
|
||||
border_fg = 8
|
||||
|
||||
# Title to show at the top of the main box
|
||||
box_title = null
|
||||
|
||||
# Initial text to show on the info line (Defaults to hostname)
|
||||
initial_info_text = null
|
||||
# Change the state and language of the big clock
|
||||
# none -> Disabled (default)
|
||||
# en -> English
|
||||
# fa -> Farsi
|
||||
bigclock = none
|
||||
|
||||
# Blank main box background
|
||||
# Setting to false will make it transparent
|
||||
blank_box = true
|
||||
|
||||
# Remove main box borders
|
||||
hide_borders = false
|
||||
# Border foreground color id
|
||||
border_fg = 0x0008
|
||||
|
||||
# Main box margins
|
||||
margin_box_h = 2
|
||||
margin_box_v = 1
|
||||
# Title to show at the top of the main box
|
||||
# If set to null, none will be shown
|
||||
box_title = null
|
||||
|
||||
# Input boxes length
|
||||
input_len = 34
|
||||
# Brightness increase command
|
||||
brightness_down_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s 10%-
|
||||
|
||||
# Max input sizes
|
||||
max_desktop_len = 100
|
||||
max_login_len = 255
|
||||
max_password_len = 255
|
||||
# Brightness decrease key
|
||||
brightness_down_key = F5
|
||||
|
||||
# Input box active by default on startup
|
||||
# Available inputs: session, login, password
|
||||
default_input = login
|
||||
# Brightness increase command
|
||||
brightness_up_cmd = $PREFIX_DIRECTORY/bin/brightnessctl -q s +10%
|
||||
|
||||
# Load the saved desktop and username
|
||||
load = true
|
||||
# Brightness increase key
|
||||
brightness_up_key = F6
|
||||
|
||||
# Save the current desktop and login as defaults
|
||||
save = true
|
||||
# Erase password input on failure
|
||||
clear_password = false
|
||||
|
||||
# Deprecated - Will be removed in a future version
|
||||
# New save files are now loaded from the same directory as the config
|
||||
# Currently used to migrate old save files to the new version
|
||||
# File in which to save and load the default desktop and login
|
||||
save_file = /etc/ly/save
|
||||
# Format string for clock in top right corner (see strftime specification). Example: %c
|
||||
# If null, the clock won't be shown
|
||||
clock = null
|
||||
|
||||
# Remove power management command hints
|
||||
hide_key_hints = false
|
||||
|
||||
# Specifies the key used for shutdown (F1-F12)
|
||||
shutdown_key = F1
|
||||
|
||||
# Specifies the key used for restart (F1-F12)
|
||||
restart_key = F2
|
||||
|
||||
# Specifies the key used for sleep (F1-F12)
|
||||
sleep_key = F3
|
||||
|
||||
# Command executed when pressing shutdown_key
|
||||
shutdown_cmd = /sbin/shutdown -a now
|
||||
|
||||
# Command executed when pressing restart_key
|
||||
restart_cmd = /sbin/shutdown -r now
|
||||
|
||||
# Command executed when pressing sleep key (can be null)
|
||||
sleep_cmd = null
|
||||
|
||||
# Active language
|
||||
# Available languages are found in /etc/ly/lang/
|
||||
lang = en
|
||||
|
||||
# TTY in use
|
||||
tty = 2
|
||||
# CMatrix animation foreground color id
|
||||
cmatrix_fg = 0x0003
|
||||
|
||||
# Console path
|
||||
console_dev = /dev/console
|
||||
|
||||
# Default path. If null, ly doesn't set a path.
|
||||
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin
|
||||
# Input box active by default on startup
|
||||
# Available inputs: info_line, session, login, password
|
||||
default_input = login
|
||||
|
||||
# Error background color id
|
||||
error_bg = 0x0000
|
||||
|
||||
# Error foreground color id
|
||||
# Default is red and bold: TB_RED | TB_BOLD
|
||||
error_fg = 0x0102
|
||||
|
||||
# Foreground color id
|
||||
fg = 0x0008
|
||||
|
||||
# Remove main box borders
|
||||
hide_borders = false
|
||||
|
||||
# Remove power management command hints
|
||||
hide_key_hints = false
|
||||
|
||||
# Initial text to show on the info line
|
||||
# If set to null, the info line defaults to the hostname
|
||||
initial_info_text = null
|
||||
|
||||
# Input boxes length
|
||||
input_len = 34
|
||||
|
||||
# Active language
|
||||
# Available languages are found in $CONFIG_DIRECTORY/ly/lang/
|
||||
lang = en
|
||||
|
||||
# Load the saved desktop and username
|
||||
load = true
|
||||
|
||||
# Command executed when logging in
|
||||
# If null, no command will be executed
|
||||
# Important: the code itself must end with `exec "$@"` in order to launch the session!
|
||||
# You can also set environment variables in there, they'll persist until logout
|
||||
login_cmd = null
|
||||
|
||||
# Command executed when logging out
|
||||
# If null, no command will be executed
|
||||
# Important: the session will already be terminated when this command is executed, so
|
||||
# no need to add `exec "$@"` at the end
|
||||
logout_cmd = null
|
||||
|
||||
# Main box horizontal margin
|
||||
margin_box_h = 2
|
||||
|
||||
# Main box vertical margin
|
||||
margin_box_v = 1
|
||||
|
||||
# Event timeout in milliseconds
|
||||
min_refresh_delta = 5
|
||||
@@ -128,35 +151,69 @@ min_refresh_delta = 5
|
||||
# Set numlock on/off at startup
|
||||
numlock = false
|
||||
|
||||
# Default path
|
||||
# If null, ly doesn't set a path
|
||||
path = /sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
|
||||
|
||||
# Command executed when pressing restart_key
|
||||
restart_cmd = /sbin/shutdown -r now
|
||||
|
||||
# Specifies the key used for restart (F1-F12)
|
||||
restart_key = F2
|
||||
|
||||
# Save the current desktop and login as defaults
|
||||
save = true
|
||||
|
||||
# Service name (set to ly to use the provided pam config file)
|
||||
service_name = ly
|
||||
|
||||
# Terminal reset command (tput is faster)
|
||||
term_reset_cmd = /usr/bin/tput reset
|
||||
# Session log file path
|
||||
# This will contain stdout and stderr of X11 and Wayland sessions
|
||||
# By default it's saved in the user's home directory
|
||||
# Note: this file won't be used in a shell session (due to the need of stdout and stderr)
|
||||
session_log = ly-session.log
|
||||
|
||||
# Terminal restore cursor command
|
||||
term_restore_cursor_cmd = /usr/bin/tput cnorm
|
||||
# Setup command
|
||||
setup_cmd = $CONFIG_DIRECTORY/ly/setup.sh
|
||||
|
||||
# Cookie generator
|
||||
mcookie_cmd = /usr/bin/mcookie
|
||||
# Command executed when pressing shutdown_key
|
||||
shutdown_cmd = /sbin/shutdown -a now
|
||||
|
||||
# Wayland setup command
|
||||
wayland_cmd = /etc/ly/wsetup.sh
|
||||
# Specifies the key used for shutdown (F1-F12)
|
||||
shutdown_key = F1
|
||||
|
||||
# Command executed when pressing sleep key (can be null)
|
||||
sleep_cmd = null
|
||||
|
||||
# Specifies the key used for sleep (F1-F12)
|
||||
sleep_key = F3
|
||||
|
||||
# Center the session name.
|
||||
text_in_center = false
|
||||
|
||||
# TTY in use
|
||||
tty = $DEFAULT_TTY
|
||||
|
||||
# Default vi mode
|
||||
# normal -> normal mode
|
||||
# insert -> insert mode
|
||||
vi_default_mode = normal
|
||||
|
||||
# Enable vi keybindings
|
||||
vi_mode = false
|
||||
|
||||
# Wayland desktop environments
|
||||
waylandsessions = /usr/share/wayland-sessions
|
||||
|
||||
# xinitrc (hidden if null)
|
||||
xinitrc = ~/.xinitrc
|
||||
waylandsessions = $PREFIX_DIRECTORY/share/wayland-sessions
|
||||
|
||||
# Xorg server command
|
||||
x_cmd = /usr/bin/X
|
||||
|
||||
# Xorg setup command
|
||||
x_cmd_setup = /etc/ly/xsetup.sh
|
||||
x_cmd = $PREFIX_DIRECTORY/bin/X
|
||||
|
||||
# Xorg xauthority edition tool
|
||||
xauth_cmd = /usr/bin/xauth
|
||||
xauth_cmd = $PREFIX_DIRECTORY/bin/xauth
|
||||
|
||||
# xinitrc
|
||||
# If null, the xinitrc session will be hidden
|
||||
xinitrc = ~/.xinitrc
|
||||
|
||||
# Xorg desktop environments
|
||||
xsessions = /usr/share/xsessions
|
||||
xsessions = $PREFIX_DIRECTORY/share/xsessions
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
authenticating = authenticating...
|
||||
brightness_down = decrease brightness
|
||||
brightness_up = increase brightness
|
||||
capslock = capslock
|
||||
err_alloc = failed memory allocation
|
||||
err_bounds = out-of-bounds index
|
||||
err_brightness_change = failed to change brightness
|
||||
err_chdir = failed to open home folder
|
||||
err_console_dev = failed to access console
|
||||
err_dgn_oob = log message
|
||||
err_domain = invalid domain
|
||||
err_envlist = failed to get envlist
|
||||
err_hostname = failed to get hostname
|
||||
err_mcookie = mcookie command failed
|
||||
err_mlock = failed to lock password memory
|
||||
err_null = null pointer
|
||||
err_numlock = failed to set numlock
|
||||
err_pam = pam transaction failed
|
||||
err_pam_abort = pam transaction aborted
|
||||
err_pam_acct_expired = account expired
|
||||
@@ -44,6 +47,7 @@ insert = insert
|
||||
login = login
|
||||
logout = logged out
|
||||
normal = normal
|
||||
no_x11_support = x11 support disabled at compile-time
|
||||
numlock = numlock
|
||||
password = password
|
||||
restart = reboot
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
authenticating = autenticando...
|
||||
brightness_down = bajar brillo
|
||||
brightness_up = subir brillo
|
||||
capslock = Bloq Mayús
|
||||
err_alloc = asignación de memoria fallida
|
||||
err_bounds = índice fuera de límites
|
||||
@@ -34,12 +37,17 @@ err_user_init = error al inicializar usuario
|
||||
err_user_uid = error al establecer el UID del usuario
|
||||
err_xsessions_dir = error al buscar la carpeta de sesiones
|
||||
err_xsessions_open = error al abrir la carpeta de sesiones
|
||||
login = iniciar sesión
|
||||
insert = insertar
|
||||
login = usuario
|
||||
logout = cerrar sesión
|
||||
no_x11_support = soporte para x11 deshabilitado en tiempo de compilación
|
||||
normal = normal
|
||||
numlock = Bloq Num
|
||||
other = otro
|
||||
password = contraseña
|
||||
restart = reiniciar
|
||||
shell = shell
|
||||
shutdown = apagar
|
||||
sleep = suspender
|
||||
wayland = wayland
|
||||
xinitrc = xinitrc
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
capslock = verr.maj
|
||||
authenticating = authentification...
|
||||
brightness_down = diminuer la luminosité
|
||||
brightness_up = augmenter la luminosité
|
||||
capslock = verr.maj
|
||||
err_alloc = échec d'allocation mémoire
|
||||
err_bounds = indice hors-limite
|
||||
err_brightness_change = échec du changement de luminosité
|
||||
err_chdir = échec de l'ouverture du répertoire home
|
||||
err_console_dev = échec d'accès à la console
|
||||
err_dgn_oob = message
|
||||
err_domain = domaine invalide
|
||||
err_envlist = échec de lecture de la liste d'environnement
|
||||
err_hostname = échec de lecture du nom d'hôte
|
||||
err_mlock = échec du verrouillage mémoire
|
||||
err_null = pointeur null
|
||||
err_numlock = échec de modification du verr.num
|
||||
err_pam = échec de la transaction pam
|
||||
err_pam_abort = transaction pam avortée
|
||||
err_pam_acct_expired = compte expiré
|
||||
err_pam_auth = erreur d'authentification
|
||||
err_pam_authok_reqd = tiquet expiré
|
||||
err_pam_authinfo_unavail = échec de l'obtention des infos utilisateur
|
||||
err_pam_authok_reqd = tiquet expiré
|
||||
err_pam_buf = erreur de mémoire tampon
|
||||
err_pam_cred_err = échec de la modification des identifiants
|
||||
err_pam_cred_expired = identifiants expirés
|
||||
@@ -29,17 +35,25 @@ err_perm_dir = échec de changement de répertoire
|
||||
err_perm_group = échec du déclassement des permissions de groupe
|
||||
err_perm_user = échec du déclassement des permissions utilisateur
|
||||
err_pwnam = échec de lecture des infos utilisateur
|
||||
err_unknown = une erreur inconnue est survenue
|
||||
err_user_gid = échec de modification du GID
|
||||
err_user_init = échec d'initialisation de l'utilisateur
|
||||
err_user_uid = échec de modification du UID
|
||||
err_xauth = échec de la commande xauth
|
||||
err_xcb_conn = échec de la connexion xcb
|
||||
err_xsessions_dir = échec de la recherche du dossier de sessions
|
||||
err_xsessions_open = échec de l'ouverture du dossier de sessions
|
||||
insert = insertion
|
||||
login = identifiant
|
||||
logout = déconnection
|
||||
logout = déconnecté
|
||||
normal = normal
|
||||
no_x11_support = support pour x11 désactivé lors de la compilation
|
||||
numlock = verr.num
|
||||
password = mot de passe
|
||||
restart = redémarrer
|
||||
shell = shell
|
||||
shutdown = éteindre
|
||||
sleep = veille
|
||||
wayland = wayland
|
||||
xinitrc = xinitrc
|
||||
x11 = x11
|
||||
|
||||
8
res/ly-dinit
Normal file
8
res/ly-dinit
Normal file
@@ -0,0 +1,8 @@
|
||||
type = process
|
||||
restart = true
|
||||
smooth-recovery = true
|
||||
command = $PREFIX_DIRECTORY/bin/$EXE_NAME
|
||||
depends-on = loginready
|
||||
termsignal = HUP
|
||||
# ly needs access to the console while loginready already occupies it
|
||||
options = shares-console
|
||||
@@ -20,16 +20,16 @@ then
|
||||
fi
|
||||
|
||||
## Get the tty from the conf file
|
||||
CONFTTY=$(cat /etc/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
|
||||
CONFTTY=$(cat $CONFIG_DIRECTORY/ly/config.ini | sed -n 's/^tty.*=[^1-9]*// p')
|
||||
|
||||
## The execution vars
|
||||
# If CONFTTY is empty then default to 2
|
||||
TTY="tty${CONFTTY:-2}"
|
||||
# If CONFTTY is empty then default to $DEFAULT_TTY
|
||||
TTY="tty${CONFTTY:-$DEFAULT_TTY}"
|
||||
TERM=linux
|
||||
BAUD=38400
|
||||
# If we don't have getty then we should have agetty
|
||||
command=${commandB:-$commandUL}
|
||||
command_args_foreground="-nl /usr/bin/ly $TTY $BAUD $TERM"
|
||||
command_args_foreground="-nl $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME $TTY $BAUD $TERM"
|
||||
|
||||
depend() {
|
||||
after agetty
|
||||
|
||||
@@ -8,5 +8,5 @@ fi
|
||||
BAUD_RATE=38400
|
||||
TERM_NAME=linux
|
||||
|
||||
auxtty=$(/bin/cat /etc/ly/config.ini 2>/dev/null 1| /bin/sed -n 's/\(^[[:space:]]*tty[[:space:]]*=[[:space:]]*\)\([[:digit:]][[:digit:]]*\)\(.*\)/\2/p')
|
||||
TTY=tty${auxtty:-2}
|
||||
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}
|
||||
|
||||
@@ -10,4 +10,4 @@ elif [ -x /sbin/agetty -o -x /bin/agetty ]; then
|
||||
GETTY=agetty
|
||||
fi
|
||||
|
||||
exec setsid ${GETTY} ${GETTY_ARGS} -nl /usr/bin/ly "${TTY}" "${BAUD_RATE}" "${TERM_NAME}"
|
||||
exec setsid ${GETTY} ${GETTY_ARGS} -nl $PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME "${TTY}" "${BAUD_RATE}" "${TERM_NAME}"
|
||||
|
||||
2
res/ly-s6/run
Normal file
2
res/ly-s6/run
Normal file
@@ -0,0 +1,2 @@
|
||||
#!/bin/execlineb -P
|
||||
exec agetty -L -8 -n -l $PREFIX_DIRECTORY/bin/$EXE_NAME tty$DEFAULT_TTY 115200
|
||||
1
res/ly-s6/type
Normal file
1
res/ly-s6/type
Normal file
@@ -0,0 +1 @@
|
||||
longrun
|
||||
@@ -1,15 +1,14 @@
|
||||
[Unit]
|
||||
Description=TUI display manager
|
||||
After=systemd-user-sessions.service plymouth-quit-wait.service
|
||||
After=getty@tty2.service
|
||||
Conflicts=getty@tty2.service
|
||||
After=getty@tty$DEFAULT_TTY.service
|
||||
Conflicts=getty@tty$DEFAULT_TTY.service
|
||||
|
||||
[Service]
|
||||
Type=idle
|
||||
ExecStart=/usr/bin/ly
|
||||
StandardError=journal
|
||||
ExecStart=$PREFIX_DIRECTORY/bin/$EXECUTABLE_NAME
|
||||
StandardInput=tty
|
||||
TTYPath=/dev/tty2
|
||||
TTYPath=/dev/tty$DEFAULT_TTY
|
||||
TTYReset=yes
|
||||
TTYVHangup=yes
|
||||
|
||||
|
||||
12
res/pam.d/ly
12
res/pam.d/ly
@@ -1,6 +1,18 @@
|
||||
#%PAM-1.0
|
||||
|
||||
auth sufficient pam_unix.so try_first_pass likeauth nullok # empty-password will not pass this, but will not fail causing the next line to get executed
|
||||
-auth sufficient pam_fprintd.so # We do not want to get errors when pam_fprintd.so is not present
|
||||
auth include login
|
||||
-auth optional pam_gnome_keyring.so
|
||||
-auth optional pam_kwallet5.so
|
||||
|
||||
account include login
|
||||
|
||||
password include login
|
||||
-password optional pam_gnome_keyring.so use_authtok
|
||||
|
||||
-session optional pam_systemd.so class=greeter
|
||||
-session optional pam_elogind.so
|
||||
session include login
|
||||
-session optional pam_gnome_keyring.so auto_start
|
||||
-session optional pam_kwallet5.so auto_start
|
||||
|
||||
107
res/setup.sh
Executable file
107
res/setup.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/bin/sh
|
||||
# Shell environment setup after login
|
||||
# Copyright (C) 2015-2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
|
||||
|
||||
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
|
||||
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
|
||||
|
||||
# Copyright (C) 2024 The Fairy Glade
|
||||
# This work is free. You can redistribute it and/or modify it under the
|
||||
# terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
# as published by Sam Hocevar. See the LICENSE file for more details.
|
||||
|
||||
# Note that the respective logout scripts are not sourced.
|
||||
case $SHELL in
|
||||
*/bash)
|
||||
[ -z "$BASH" ] && exec $SHELL "$0" "$@"
|
||||
set +o posix
|
||||
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
|
||||
if [ -f "$HOME"/.bash_profile ]; then
|
||||
. "$HOME"/.bash_profile
|
||||
elif [ -f "$HOME"/.bash_login ]; then
|
||||
. "$HOME"/.bash_login
|
||||
elif [ -f "$HOME"/.profile ]; then
|
||||
. "$HOME"/.profile
|
||||
fi
|
||||
;;
|
||||
*/zsh)
|
||||
[ -z "$ZSH_NAME" ] && exec $SHELL "$0" "$@"
|
||||
[ -d "$CONFIG_DIRECTORY"/zsh ] && zdir="$CONFIG_DIRECTORY"/zsh || zdir="$CONFIG_DIRECTORY"
|
||||
zhome=${ZDOTDIR:-"$HOME"}
|
||||
# zshenv is always sourced automatically.
|
||||
[ -f "$zdir"/zprofile ] && . "$zdir"/zprofile
|
||||
[ -f "$zhome"/.zprofile ] && . "$zhome"/.zprofile
|
||||
[ -f "$zdir"/zlogin ] && . "$zdir"/zlogin
|
||||
[ -f "$zhome"/.zlogin ] && . "$zhome"/.zlogin
|
||||
emulate -R sh
|
||||
;;
|
||||
*/csh|*/tcsh)
|
||||
# [t]cshrc is always sourced automatically.
|
||||
# Note that sourcing csh.login after .cshrc is non-standard.
|
||||
sess_tmp=$(mktemp /tmp/sess-env-XXXXXX)
|
||||
$SHELL -c "if (-f $CONFIG_DIRECTORY/csh.login) source $CONFIG_DIRECTORY/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $sess_tmp"
|
||||
. "$sess_tmp"
|
||||
rm -f "$sess_tmp"
|
||||
;;
|
||||
*/fish)
|
||||
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
|
||||
[ -f "$HOME"/.profile ] && . "$HOME"/.profile
|
||||
sess_tmp=$(mktemp /tmp/sess-env-XXXXXX)
|
||||
$SHELL --login -c "/bin/sh -c 'export -p' > $sess_tmp"
|
||||
. "$sess_tmp"
|
||||
rm -f "$sess_tmp"
|
||||
;;
|
||||
*) # Plain sh, ksh, and anything we do not know.
|
||||
[ -f "$CONFIG_DIRECTORY"/profile ] && . "$CONFIG_DIRECTORY"/profile
|
||||
[ -f "$HOME"/.profile ] && . "$HOME"/.profile
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$XDG_SESSION_TYPE" = "x11" ]; then
|
||||
[ -f "$CONFIG_DIRECTORY"/xprofile ] && . "$CONFIG_DIRECTORY"/xprofile
|
||||
[ -f "$HOME"/.xprofile ] && . "$HOME"/.xprofile
|
||||
|
||||
# run all system xinitrc shell scripts.
|
||||
if [ -d "$CONFIG_DIRECTORY"/X11/xinit/xinitrc.d ]; then
|
||||
for i in "$CONFIG_DIRECTORY"/X11/xinit/xinitrc.d/* ; do
|
||||
if [ -x "$i" ]; then
|
||||
. "$i"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Load Xsession scripts
|
||||
# OPTIONFILE, USERXSESSION, USERXSESSIONRC and ALTUSERXSESSION are required
|
||||
# by the scripts to work
|
||||
xsessionddir="$CONFIG_DIRECTORY"/X11/Xsession.d
|
||||
export OPTIONFILE="$CONFIG_DIRECTORY"/X11/Xsession.options
|
||||
export USERXSESSION="$HOME"/.xsession
|
||||
export USERXSESSIONRC="$HOME"/.xsessionrc
|
||||
export ALTUSERXSESSION="$HOME"/.Xsession
|
||||
|
||||
if [ -d "$xsessionddir" ]; then
|
||||
for i in $(ls "$xsessionddir"); do
|
||||
script="$xsessionddir/$i"
|
||||
echo "Loading X session script $script"
|
||||
if [ -r "$script" ] && [ -f "$script" ] && expr "$i" : '^[[:alnum:]_-]\+$' > /dev/null; then
|
||||
. "$script"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -d "$CONFIG_DIRECTORY"/X11/Xresources ]; then
|
||||
for i in "$CONFIG_DIRECTORY"/X11/Xresources/*; do
|
||||
[ -f "$i" ] && xrdb -merge "$i"
|
||||
done
|
||||
elif [ -f "$CONFIG_DIRECTORY"/X11/Xresources ]; then
|
||||
xrdb -merge "$CONFIG_DIRECTORY"/X11/Xresources
|
||||
fi
|
||||
[ -f "$HOME"/.Xresources ] && xrdb -merge "$HOME"/.Xresources
|
||||
[ -f "$XDG_CONFIG_HOME"/X11/Xresources ] && xrdb -merge "$XDG_CONFIG_HOME"/X11/Xresources
|
||||
|
||||
if [ -f "$USERXSESSION" ]; then
|
||||
. "$USERXSESSION"
|
||||
fi
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
@@ -1,54 +0,0 @@
|
||||
#!/bin/sh
|
||||
# wayland-session - run as user
|
||||
# Copyright (C) 2015-2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
|
||||
|
||||
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
|
||||
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
|
||||
|
||||
# Note that the respective logout scripts are not sourced.
|
||||
case $SHELL in
|
||||
*/bash)
|
||||
[ -z "$BASH" ] && exec $SHELL $0 "$@"
|
||||
set +o posix
|
||||
[ -f /etc/profile ] && . /etc/profile
|
||||
if [ -f $HOME/.bash_profile ]; then
|
||||
. $HOME/.bash_profile
|
||||
elif [ -f $HOME/.bash_login ]; then
|
||||
. $HOME/.bash_login
|
||||
elif [ -f $HOME/.profile ]; then
|
||||
. $HOME/.profile
|
||||
fi
|
||||
;;
|
||||
*/zsh)
|
||||
[ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@"
|
||||
[ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc
|
||||
zhome=${ZDOTDIR:-$HOME}
|
||||
# zshenv is always sourced automatically.
|
||||
[ -f $zdir/zprofile ] && . $zdir/zprofile
|
||||
[ -f $zhome/.zprofile ] && . $zhome/.zprofile
|
||||
[ -f $zdir/zlogin ] && . $zdir/zlogin
|
||||
[ -f $zhome/.zlogin ] && . $zhome/.zlogin
|
||||
emulate -R sh
|
||||
;;
|
||||
*/csh|*/tcsh)
|
||||
# [t]cshrc is always sourced automatically.
|
||||
# Note that sourcing csh.login after .cshrc is non-standard.
|
||||
wlsess_tmp=`mktemp /tmp/wlsess-env-XXXXXX`
|
||||
$SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $wlsess_tmp"
|
||||
. $wlsess_tmp
|
||||
rm -f $wlsess_tmp
|
||||
;;
|
||||
*/fish)
|
||||
[ -f /etc/profile ] && . /etc/profile
|
||||
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
|
||||
$SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp"
|
||||
. $xsess_tmp
|
||||
rm -f $xsess_tmp
|
||||
;;
|
||||
*) # Plain sh, ksh, and anything we do not know.
|
||||
[ -f /etc/profile ] && . /etc/profile
|
||||
[ -f $HOME/.profile ] && . $HOME/.profile
|
||||
;;
|
||||
esac
|
||||
|
||||
exec "$@"
|
||||
103
res/xsetup.sh
103
res/xsetup.sh
@@ -1,103 +0,0 @@
|
||||
#! /bin/sh
|
||||
# Xsession - run as user
|
||||
# Copyright (C) 2016 Pier Luigi Fiorini <pierluigi.fiorini@gmail.com>
|
||||
|
||||
# This file is extracted from kde-workspace (kdm/kfrontend/genkdmconf.c)
|
||||
# Copyright (C) 2001-2005 Oswald Buddenhagen <ossi@kde.org>
|
||||
|
||||
# Note that the respective logout scripts are not sourced.
|
||||
case $SHELL in
|
||||
*/bash)
|
||||
[ -z "$BASH" ] && exec $SHELL $0 "$@"
|
||||
set +o posix
|
||||
[ -f /etc/profile ] && . /etc/profile
|
||||
if [ -f $HOME/.bash_profile ]; then
|
||||
. $HOME/.bash_profile
|
||||
elif [ -f $HOME/.bash_login ]; then
|
||||
. $HOME/.bash_login
|
||||
elif [ -f $HOME/.profile ]; then
|
||||
. $HOME/.profile
|
||||
fi
|
||||
;;
|
||||
*/zsh)
|
||||
[ -z "$ZSH_NAME" ] && exec $SHELL $0 "$@"
|
||||
[ -d /etc/zsh ] && zdir=/etc/zsh || zdir=/etc
|
||||
zhome=${ZDOTDIR:-$HOME}
|
||||
# zshenv is always sourced automatically.
|
||||
[ -f $zdir/zprofile ] && . $zdir/zprofile
|
||||
[ -f $zhome/.zprofile ] && . $zhome/.zprofile
|
||||
[ -f $zdir/zlogin ] && . $zdir/zlogin
|
||||
[ -f $zhome/.zlogin ] && . $zhome/.zlogin
|
||||
emulate -R sh
|
||||
;;
|
||||
*/csh|*/tcsh)
|
||||
# [t]cshrc is always sourced automatically.
|
||||
# Note that sourcing csh.login after .cshrc is non-standard.
|
||||
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
|
||||
$SHELL -c "if (-f /etc/csh.login) source /etc/csh.login; if (-f ~/.login) source ~/.login; /bin/sh -c 'export -p' >! $xsess_tmp"
|
||||
. $xsess_tmp
|
||||
rm -f $xsess_tmp
|
||||
;;
|
||||
*/fish)
|
||||
[ -f /etc/profile ] && . /etc/profile
|
||||
xsess_tmp=`mktemp /tmp/xsess-env-XXXXXX`
|
||||
$SHELL --login -c "/bin/sh -c 'export -p' > $xsess_tmp"
|
||||
. $xsess_tmp
|
||||
rm -f $xsess_tmp
|
||||
;;
|
||||
*) # Plain sh, ksh, and anything we do not know.
|
||||
[ -f /etc/profile ] && . /etc/profile
|
||||
[ -f $HOME/.profile ] && . $HOME/.profile
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -f /etc/xprofile ] && . /etc/xprofile
|
||||
[ -f $HOME/.xprofile ] && . $HOME/.xprofile
|
||||
|
||||
# run all system xinitrc shell scripts.
|
||||
if [ -d /etc/X11/xinit/xinitrc.d ]; then
|
||||
for i in /etc/X11/xinit/xinitrc.d/* ; do
|
||||
if [ -x "$i" ]; then
|
||||
. "$i"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Load Xsession scripts
|
||||
# OPTIONFILE, USERXSESSION, USERXSESSIONRC and ALTUSERXSESSION are required
|
||||
# by the scripts to work
|
||||
xsessionddir="/etc/X11/Xsession.d"
|
||||
OPTIONFILE=/etc/X11/Xsession.options
|
||||
USERXSESSION=$HOME/.xsession
|
||||
USERXSESSIONRC=$HOME/.xsessionrc
|
||||
ALTUSERXSESSION=$HOME/.Xsession
|
||||
|
||||
if [ -d "$xsessionddir" ]; then
|
||||
for i in `ls $xsessionddir`; do
|
||||
script="$xsessionddir/$i"
|
||||
echo "Loading X session script $script"
|
||||
if [ -r "$script" -a -f "$script" ] && expr "$i" : '^[[:alnum:]_-]\+$' > /dev/null; then
|
||||
. "$script"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
if [ -d /etc/X11/Xresources ]; then
|
||||
for i in /etc/X11/Xresources/*; do
|
||||
[ -f $i ] && xrdb -merge $i
|
||||
done
|
||||
elif [ -f /etc/X11/Xresources ]; then
|
||||
xrdb -merge /etc/X11/Xresources
|
||||
fi
|
||||
[ -f $HOME/.Xresources ] && xrdb -merge $HOME/.Xresources
|
||||
[ -f $XDG_CONFIG_HOME/X11/Xresources ] && xrdb -merge $XDG_CONFIG_HOME/X11/Xresources
|
||||
|
||||
if [ -f "$USERXSESSION" ]; then
|
||||
. "$USERXSESSION"
|
||||
fi
|
||||
|
||||
if [ -z "$*" ]; then
|
||||
exec xmessage -center -buttons OK:0 -default OK "Sorry, $DESKTOP_SESSION is no valid session."
|
||||
else
|
||||
exec $@
|
||||
fi
|
||||
@@ -9,7 +9,7 @@ const termbox = interop.termbox;
|
||||
const Doom = @This();
|
||||
|
||||
pub const STEPS = 13;
|
||||
pub const FIRE = [_]termbox.tb_cell{
|
||||
pub const FIRE = [_]utils.Cell{
|
||||
utils.initCell(' ', 9, 0),
|
||||
utils.initCell(0x2591, 2, 0), // Red
|
||||
utils.initCell(0x2592, 2, 0), // Red
|
||||
@@ -68,13 +68,13 @@ pub fn draw(self: Doom) void {
|
||||
if (buffer_dest > 12) buffer_dest = 0;
|
||||
self.buffer[dest] = @intCast(buffer_dest);
|
||||
|
||||
self.terminal_buffer.buffer[dest] = FIRE[buffer_dest];
|
||||
self.terminal_buffer.buffer[source] = FIRE[buffer_source];
|
||||
self.terminal_buffer.buffer[dest] = toTermboxCell(FIRE[buffer_dest]);
|
||||
self.terminal_buffer.buffer[source] = toTermboxCell(FIRE[buffer_source]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initBuffer(buffer: []u8, width: u64) void {
|
||||
fn initBuffer(buffer: []u8, width: usize) void {
|
||||
const length = buffer.len - width;
|
||||
const slice_start = buffer[0..length];
|
||||
const slice_end = buffer[length..];
|
||||
@@ -82,3 +82,11 @@ fn initBuffer(buffer: []u8, width: u64) void {
|
||||
@memset(slice_start, 0);
|
||||
@memset(slice_end, STEPS - 1);
|
||||
}
|
||||
|
||||
fn toTermboxCell(cell: utils.Cell) termbox.tb_cell {
|
||||
return .{
|
||||
.ch = cell.ch,
|
||||
.fg = cell.fg,
|
||||
.bg = cell.bg,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,8 +34,9 @@ dots: []Dot,
|
||||
lines: []Line,
|
||||
frame: u64,
|
||||
count: u64,
|
||||
fg_ini: u16,
|
||||
|
||||
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix {
|
||||
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_ini: u16) !Matrix {
|
||||
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
|
||||
const lines = try allocator.alloc(Line, terminal_buffer.width);
|
||||
|
||||
@@ -48,6 +49,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer) !Matrix {
|
||||
.lines = lines,
|
||||
.frame = 3,
|
||||
.count = 0,
|
||||
.fg_ini = fg_ini,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,9 +77,9 @@ pub fn draw(self: *Matrix) void {
|
||||
if (self.frame > 4) self.frame = 1;
|
||||
self.count = 0;
|
||||
|
||||
var x: u64 = 0;
|
||||
var x: usize = 0;
|
||||
while (x < self.terminal_buffer.width) : (x += 2) {
|
||||
var tail: u64 = 0;
|
||||
var tail: usize = 0;
|
||||
var line = &self.lines[x];
|
||||
if (self.frame <= line.update) continue;
|
||||
|
||||
@@ -93,7 +95,7 @@ pub fn draw(self: *Matrix) void {
|
||||
}
|
||||
}
|
||||
|
||||
var y: u64 = 0;
|
||||
var y: usize = 0;
|
||||
var first_col = true;
|
||||
var seg_len: u64 = 0;
|
||||
height_it: while (y <= buf_height) : (y += 1) {
|
||||
@@ -140,12 +142,13 @@ pub fn draw(self: *Matrix) void {
|
||||
}
|
||||
}
|
||||
|
||||
var x: u64 = 0;
|
||||
var x: usize = 0;
|
||||
while (x < buf_width) : (x += 2) {
|
||||
var y: u64 = 1;
|
||||
var y: usize = 1;
|
||||
while (y <= self.terminal_buffer.height) : (y += 1) {
|
||||
const dot = self.dots[buf_width * y + x];
|
||||
var fg: u16 = @intCast(termbox.TB_GREEN);
|
||||
|
||||
var fg = self.fg_ini;
|
||||
|
||||
if (dot.value == -1 or dot.value == ' ') {
|
||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', fg, termbox.TB_DEFAULT);
|
||||
@@ -158,16 +161,16 @@ pub fn draw(self: *Matrix) void {
|
||||
}
|
||||
}
|
||||
|
||||
fn initBuffers(dots: []Dot, lines: []Line, width: u64, height: u64, random: Random) void {
|
||||
var y: u64 = 0;
|
||||
fn initBuffers(dots: []Dot, lines: []Line, width: usize, height: usize, random: Random) void {
|
||||
var y: usize = 0;
|
||||
while (y <= height) : (y += 1) {
|
||||
var x: u64 = 0;
|
||||
var x: usize = 0;
|
||||
while (x < width) : (x += 2) {
|
||||
dots[y * width + x].value = -1;
|
||||
}
|
||||
}
|
||||
|
||||
var x: u64 = 0;
|
||||
var x: usize = 0;
|
||||
while (x < width) : (x += 2) {
|
||||
var line = lines[x];
|
||||
const h: isize = @intCast(height);
|
||||
|
||||
318
src/auth.zig
318
src/auth.zig
@@ -1,15 +1,78 @@
|
||||
const std = @import("std");
|
||||
const build_options = @import("build_options");
|
||||
const builtin = @import("builtin");
|
||||
const enums = @import("enums.zig");
|
||||
const interop = @import("interop.zig");
|
||||
const TerminalBuffer = @import("tui/TerminalBuffer.zig");
|
||||
const Desktop = @import("tui/components/Desktop.zig");
|
||||
const Session = @import("tui/components/Session.zig");
|
||||
const Text = @import("tui/components/Text.zig");
|
||||
const Config = @import("config/Config.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Md5 = std.crypto.hash.Md5;
|
||||
const utmp = interop.utmp;
|
||||
const Utmp = utmp.utmp;
|
||||
const Utmp = utmp.utmpx;
|
||||
const SharedError = @import("SharedError.zig");
|
||||
|
||||
// When setting the currentLogin you must deallocate the previous currentLogin first
|
||||
pub var currentLogin: ?[:0]const u8 = null;
|
||||
pub var asyncPamHandle: ?*interop.pam.pam_handle = null;
|
||||
pub var current_environment: ?Session.Environment = null;
|
||||
|
||||
fn environment_equals(e0: Session.Environment, e1: Session.Environment) bool {
|
||||
if (!std.mem.eql(u8, e0.cmd, e1.cmd)) {
|
||||
return false;
|
||||
}
|
||||
if (!std.mem.eql(u8, e0.name, e1.name)) {
|
||||
return false;
|
||||
}
|
||||
if (!std.mem.eql(u8, e0.specifier, e1.specifier)) {
|
||||
return false;
|
||||
}
|
||||
if (!(e0.xdg_desktop_names == null and e1.xdg_desktop_names == null) or
|
||||
(e0.xdg_desktop_names != null and e1.xdg_desktop_names != null and !std.mem.eql(u8, e0.xdg_desktop_names.?, e1.xdg_desktop_names.?)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!(e0.xdg_session_desktop == null and e1.xdg_session_desktop == null) or
|
||||
(e0.xdg_session_desktop != null and e1.xdg_session_desktop != null and !std.mem.eql(u8, e0.xdg_session_desktop.?, e1.xdg_session_desktop.?)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (e0.display_server != e1.display_server) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn automaticLogin(config: Config, login: [:0]const u8, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void {
|
||||
while (asyncPamHandle == null and currentLogin != null and std.mem.eql(u8, currentLogin.?, login)) {
|
||||
if (authenticate(config, login, "", environment)) |handle| {
|
||||
if (currentLogin != null and !std.mem.eql(u8, currentLogin.?, login) and environment_equals(current_environment.?, environment)) {
|
||||
return;
|
||||
}
|
||||
asyncPamHandle = handle;
|
||||
wakesem.post();
|
||||
return;
|
||||
} else |_| {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn startAutomaticLogin(allocator: std.mem.Allocator, config: Config, login: Text, environment: Session.Environment, wakesem: *std.Thread.Semaphore) !void {
|
||||
if (currentLogin) |clogin| {
|
||||
allocator.free(clogin);
|
||||
currentLogin = null;
|
||||
}
|
||||
const login_text = try allocator.dupeZ(u8, login.text.items);
|
||||
currentLogin = login_text;
|
||||
var handle = try std.Thread.spawn(.{}, automaticLogin, .{
|
||||
config,
|
||||
login_text,
|
||||
environment,
|
||||
wakesem,
|
||||
});
|
||||
handle.detach();
|
||||
}
|
||||
|
||||
var xorg_pid: std.posix.pid_t = 0;
|
||||
pub fn xorgSignalHandler(i: c_int) callconv(.C) void {
|
||||
if (xorg_pid > 0) _ = std.c.kill(xorg_pid, i);
|
||||
@@ -20,13 +83,20 @@ pub fn sessionSignalHandler(i: c_int) callconv(.C) void {
|
||||
if (child_pid > 0) _ = std.c.kill(child_pid, i);
|
||||
}
|
||||
|
||||
pub fn authenticate(config: Config, current_environment: Desktop.Environment, login: [:0]const u8, password: [:0]const u8) !void {
|
||||
pub fn authenticate(
|
||||
config: Config,
|
||||
login: [:0]const u8,
|
||||
password: [:0]const u8,
|
||||
environment: Session.Environment,
|
||||
) !*interop.pam.pam_handle {
|
||||
var pam_tty_buffer: [6]u8 = undefined;
|
||||
const pam_tty_str = try std.fmt.bufPrintZ(&pam_tty_buffer, "tty{d}", .{config.tty});
|
||||
var tty_buffer: [2]u8 = undefined;
|
||||
const tty_str = try std.fmt.bufPrintZ(&tty_buffer, "{d}", .{config.tty});
|
||||
|
||||
// Set the XDG environment variables
|
||||
setXdgSessionEnv(current_environment.display_server);
|
||||
try setXdgEnv(tty_str, current_environment.xdg_session_desktop, current_environment.xdg_desktop_names orelse "");
|
||||
setXdgSessionEnv(environment.display_server);
|
||||
try setXdgEnv(tty_str, environment.xdg_session_desktop orelse "", environment.xdg_desktop_names orelse "");
|
||||
|
||||
// Open the PAM session
|
||||
var credentials = [_:null]?[*:0]const u8{ login, password };
|
||||
@@ -37,11 +107,15 @@ pub fn authenticate(config: Config, current_environment: Desktop.Environment, lo
|
||||
};
|
||||
var handle: ?*interop.pam.pam_handle = undefined;
|
||||
|
||||
var status = interop.pam.pam_start(config.service_name.ptr, null, &conv, &handle);
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
defer _ = interop.pam.pam_end(handle, status);
|
||||
|
||||
// Do the PAM routine
|
||||
var status = interop.pam.pam_start(config.service_name, null, &conv, &handle);
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
errdefer _ = 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
|
||||
status = interop.pam.pam_set_item(handle, interop.pam.PAM_TTY, pam_tty_str.ptr);
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
|
||||
status = interop.pam.pam_authenticate(handle, 0);
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
|
||||
@@ -49,26 +123,35 @@ pub fn authenticate(config: Config, current_environment: Desktop.Environment, lo
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
|
||||
status = interop.pam.pam_setcred(handle, interop.pam.PAM_ESTABLISH_CRED);
|
||||
errdefer _ = interop.pam.pam_end(handle, status);
|
||||
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
return handle.?;
|
||||
}
|
||||
|
||||
pub fn finaliseAuth(config: Config, environment: Session.Environment, handle: ?*interop.pam.pam_handle, login: [:0]const u8) !void {
|
||||
var status: c_int = undefined;
|
||||
defer status = interop.pam.pam_end(handle, status);
|
||||
defer status = interop.pam.pam_setcred(handle, interop.pam.PAM_DELETE_CRED);
|
||||
|
||||
// Open the PAM session
|
||||
status = interop.pam.pam_open_session(handle, 0);
|
||||
if (status != interop.pam.PAM_SUCCESS) return pamDiagnose(status);
|
||||
defer status = interop.pam.pam_close_session(handle, 0);
|
||||
defer status = interop.pam.pam_close_session(handle, status);
|
||||
|
||||
var pwd: *interop.passwd = undefined;
|
||||
var pwd: *interop.pwd.passwd = undefined;
|
||||
{
|
||||
defer interop.endpwent();
|
||||
defer interop.pwd.endpwent();
|
||||
|
||||
// Get password structure from username
|
||||
pwd = interop.getpwnam(login.ptr) orelse return error.GetPasswordNameFailed;
|
||||
pwd = interop.pwd.getpwnam(login) orelse return error.GetPasswordNameFailed;
|
||||
}
|
||||
|
||||
// Set user shell if it hasn't already been set
|
||||
if (pwd.pw_shell[0] == 0) {
|
||||
interop.setusershell();
|
||||
pwd.pw_shell = interop.getusershell();
|
||||
interop.endusershell();
|
||||
if (pwd.pw_shell == null) {
|
||||
interop.unistd.setusershell();
|
||||
pwd.pw_shell = interop.unistd.getusershell();
|
||||
interop.unistd.endusershell();
|
||||
}
|
||||
|
||||
var shared_err = try SharedError.init();
|
||||
@@ -76,7 +159,7 @@ pub fn authenticate(config: Config, current_environment: Desktop.Environment, lo
|
||||
|
||||
child_pid = try std.posix.fork();
|
||||
if (child_pid == 0) {
|
||||
startSession(config, pwd, handle, current_environment) catch |e| {
|
||||
startSession(config, pwd, handle, environment) catch |e| {
|
||||
shared_err.writeError(e);
|
||||
std.process.exit(1);
|
||||
};
|
||||
@@ -100,29 +183,37 @@ pub fn authenticate(config: Config, current_environment: Desktop.Environment, lo
|
||||
};
|
||||
try std.posix.sigaction(std.posix.SIG.TERM, &act, null);
|
||||
|
||||
try addUtmpEntry(&entry, pwd.pw_name, child_pid);
|
||||
try addUtmpEntry(&entry, pwd.pw_name.?, child_pid);
|
||||
}
|
||||
// Wait for the session to stop
|
||||
_ = std.posix.waitpid(child_pid, 0);
|
||||
|
||||
removeUtmpEntry(&entry);
|
||||
|
||||
try resetTerminal(pwd.pw_shell, config.term_reset_cmd);
|
||||
|
||||
if (shared_err.readError()) |err| return err;
|
||||
}
|
||||
|
||||
fn startSession(
|
||||
config: Config,
|
||||
pwd: *interop.passwd,
|
||||
pwd: *interop.pwd.passwd,
|
||||
handle: ?*interop.pam.pam_handle,
|
||||
current_environment: Desktop.Environment,
|
||||
environment: Session.Environment,
|
||||
) !void {
|
||||
const status = interop.initgroups(pwd.pw_name, pwd.pw_gid);
|
||||
if (status != 0) return error.GroupInitializationFailed;
|
||||
if (builtin.os.tag == .freebsd) {
|
||||
// FreeBSD has initgroups() in unistd
|
||||
const status = interop.unistd.initgroups(pwd.pw_name, pwd.pw_gid);
|
||||
if (status != 0) return error.GroupInitializationFailed;
|
||||
|
||||
std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed;
|
||||
std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed;
|
||||
// FreeBSD sets the GID and UID with setusercontext()
|
||||
const result = interop.pwd.setusercontext(null, pwd, pwd.pw_uid, interop.pwd.LOGIN_SETALL);
|
||||
if (result != 0) return error.SetUserUidFailed;
|
||||
} else {
|
||||
const status = interop.grp.initgroups(pwd.pw_name, pwd.pw_gid);
|
||||
if (status != 0) return error.GroupInitializationFailed;
|
||||
|
||||
std.posix.setgid(pwd.pw_gid) catch return error.SetUserGidFailed;
|
||||
std.posix.setuid(pwd.pw_uid) catch return error.SetUserUidFailed;
|
||||
}
|
||||
|
||||
// Set up the environment
|
||||
try initEnv(pwd, config.path);
|
||||
@@ -132,39 +223,38 @@ fn startSession(
|
||||
if (pam_env_vars == null) return error.GetEnvListFailed;
|
||||
|
||||
const env_list = std.mem.span(pam_env_vars.?);
|
||||
for (env_list) |env_var| _ = interop.putenv(env_var.?);
|
||||
for (env_list) |env_var| _ = interop.stdlib.putenv(env_var);
|
||||
|
||||
// Change to the user's home directory
|
||||
std.posix.chdirZ(pwd.pw_dir.?) catch return error.ChangeDirectoryFailed;
|
||||
|
||||
// Execute what the user requested
|
||||
std.posix.chdirZ(pwd.pw_dir) catch return error.ChangeDirectoryFailed;
|
||||
|
||||
try resetTerminal(pwd.pw_shell, config.term_reset_cmd);
|
||||
|
||||
switch (current_environment.display_server) {
|
||||
.wayland => try executeWaylandCmd(pwd.pw_shell, config.wayland_cmd, current_environment.cmd),
|
||||
.shell => try executeShellCmd(pwd.pw_shell),
|
||||
.xinitrc, .x11 => {
|
||||
switch (environment.display_server) {
|
||||
.wayland => try executeWaylandCmd(pwd.pw_shell.?, config, environment.cmd),
|
||||
.shell => try executeShellCmd(pwd.pw_shell.?, config),
|
||||
.xinitrc, .x11 => if (build_options.enable_x11_support) {
|
||||
var vt_buf: [5]u8 = undefined;
|
||||
const vt = try std.fmt.bufPrint(&vt_buf, "vt{d}", .{config.tty});
|
||||
try executeX11Cmd(pwd.pw_shell, pwd.pw_dir, config, current_environment.cmd, vt);
|
||||
try executeX11Cmd(pwd.pw_shell.?, pwd.pw_dir.?, config, environment.cmd, vt);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn initEnv(pwd: *interop.passwd, path_env: ?[:0]const u8) !void {
|
||||
_ = interop.setenv("HOME", pwd.pw_dir, 1);
|
||||
_ = interop.setenv("PWD", pwd.pw_dir, 1);
|
||||
_ = interop.setenv("SHELL", pwd.pw_shell, 1);
|
||||
_ = interop.setenv("USER", pwd.pw_name, 1);
|
||||
_ = interop.setenv("LOGNAME", pwd.pw_name, 1);
|
||||
fn initEnv(pwd: *interop.pwd.passwd, path_env: ?[:0]const u8) !void {
|
||||
_ = interop.stdlib.setenv("HOME", pwd.pw_dir, 1);
|
||||
_ = interop.stdlib.setenv("PWD", pwd.pw_dir, 1);
|
||||
_ = interop.stdlib.setenv("SHELL", pwd.pw_shell, 1);
|
||||
_ = interop.stdlib.setenv("USER", pwd.pw_name, 1);
|
||||
_ = interop.stdlib.setenv("LOGNAME", pwd.pw_name, 1);
|
||||
|
||||
if (path_env) |path| {
|
||||
const status = interop.setenv("PATH", path, 1);
|
||||
const status = interop.stdlib.setenv("PATH", path, 1);
|
||||
if (status != 0) return error.SetPathFailed;
|
||||
}
|
||||
}
|
||||
|
||||
fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
|
||||
_ = interop.setenv("XDG_SESSION_TYPE", switch (display_server) {
|
||||
_ = interop.stdlib.setenv("XDG_SESSION_TYPE", switch (display_server) {
|
||||
.wayland => "wayland",
|
||||
.shell => "tty",
|
||||
.xinitrc, .x11 => "x11",
|
||||
@@ -172,17 +262,24 @@ fn setXdgSessionEnv(display_server: enums.DisplayServer) void {
|
||||
}
|
||||
|
||||
fn setXdgEnv(tty_str: [:0]u8, desktop_name: [:0]const u8, xdg_desktop_names: [:0]const u8) !void {
|
||||
const uid = interop.getuid();
|
||||
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined;
|
||||
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
|
||||
// The "/run/user/%d" directory is not available on FreeBSD. It is much
|
||||
// better to stick to the defaults and let applications using
|
||||
// XDG_RUNTIME_DIR to fall back to directories inside user's home
|
||||
// directory.
|
||||
if (builtin.os.tag != .freebsd) {
|
||||
const uid = interop.unistd.getuid();
|
||||
var uid_buffer: [10 + @sizeOf(u32) + 1]u8 = undefined;
|
||||
const uid_str = try std.fmt.bufPrintZ(&uid_buffer, "/run/user/{d}", .{uid});
|
||||
|
||||
_ = interop.setenv("XDG_CURRENT_DESKTOP", xdg_desktop_names.ptr, 0);
|
||||
_ = interop.setenv("XDG_RUNTIME_DIR", uid_str.ptr, 0);
|
||||
_ = interop.setenv("XDG_SESSION_CLASS", "user", 0);
|
||||
_ = interop.setenv("XDG_SESSION_ID", "1", 0);
|
||||
_ = interop.setenv("XDG_SESSION_DESKTOP", desktop_name.ptr, 0);
|
||||
_ = interop.setenv("XDG_SEAT", "seat0", 0);
|
||||
_ = interop.setenv("XDG_VTNR", tty_str.ptr, 0);
|
||||
_ = interop.stdlib.setenv("XDG_RUNTIME_DIR", uid_str, 0);
|
||||
}
|
||||
|
||||
_ = 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);
|
||||
_ = 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(
|
||||
@@ -213,7 +310,7 @@ fn loginConv(
|
||||
status = interop.pam.PAM_BUF_ERR;
|
||||
break :set_credentials;
|
||||
};
|
||||
response[i].resp = username.?.ptr;
|
||||
response[i].resp = username.?;
|
||||
},
|
||||
interop.pam.PAM_PROMPT_ECHO_OFF => {
|
||||
const data: [*][*:0]u8 = @ptrCast(@alignCast(appdata_ptr));
|
||||
@@ -221,7 +318,7 @@ fn loginConv(
|
||||
status = interop.pam.PAM_BUF_ERR;
|
||||
break :set_credentials;
|
||||
};
|
||||
response[i].resp = password.?.ptr;
|
||||
response[i].resp = password.?;
|
||||
},
|
||||
interop.pam.PAM_ERROR_MSG => {
|
||||
status = interop.pam.PAM_CONV_ERR;
|
||||
@@ -243,17 +340,6 @@ fn loginConv(
|
||||
return status;
|
||||
}
|
||||
|
||||
fn resetTerminal(shell: [*:0]const u8, term_reset_cmd: [:0]const u8) !void {
|
||||
const pid = try std.posix.fork();
|
||||
if (pid == 0) {
|
||||
const args = [_:null]?[*:0]const u8{ shell, "-c", term_reset_cmd };
|
||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
_ = std.posix.waitpid(pid, 0);
|
||||
}
|
||||
|
||||
fn getFreeDisplay() !u8 {
|
||||
var buf: [15]u8 = undefined;
|
||||
var i: u8 = 0;
|
||||
@@ -327,49 +413,33 @@ fn createXauthFile(pwd: [:0]const u8) ![:0]const u8 {
|
||||
return xauthority;
|
||||
}
|
||||
|
||||
pub fn mcookie(cmd: [:0]const u8) ![32]u8 {
|
||||
const pipe = try std.posix.pipe();
|
||||
defer std.posix.close(pipe[1]);
|
||||
fn mcookie() [Md5.digest_length * 2]u8 {
|
||||
var buf: [4096]u8 = undefined;
|
||||
std.crypto.random.bytes(&buf);
|
||||
|
||||
const output = std.fs.File{ .handle = pipe[0] };
|
||||
defer output.close();
|
||||
var out: [Md5.digest_length]u8 = undefined;
|
||||
Md5.hash(&buf, &out, .{});
|
||||
|
||||
const pid = try std.posix.fork();
|
||||
if (pid == 0) {
|
||||
std.posix.close(pipe[0]);
|
||||
|
||||
std.posix.dup2(pipe[1], std.posix.STDOUT_FILENO) catch std.process.exit(1);
|
||||
std.posix.close(pipe[1]);
|
||||
|
||||
const args = [_:null]?[*:0]u8{};
|
||||
std.posix.execveZ(cmd.ptr, &args, std.c.environ) catch {};
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
const result = std.posix.waitpid(pid, 0);
|
||||
|
||||
if (result.status != 0) return error.McookieFailed;
|
||||
|
||||
var buf: [32]u8 = undefined;
|
||||
const len = try output.read(&buf);
|
||||
if (len != 32) return error.McookieFailed;
|
||||
return buf;
|
||||
return std.fmt.bytesToHex(&out, .lower);
|
||||
}
|
||||
|
||||
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xauth_cmd: []const u8, mcookie_cmd: [:0]const u8) !void {
|
||||
fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config) !void {
|
||||
var pwd_buf: [100]u8 = undefined;
|
||||
const pwd = try std.fmt.bufPrintZ(&pwd_buf, "{s}", .{pw_dir});
|
||||
|
||||
const xauthority = try createXauthFile(pwd);
|
||||
_ = interop.setenv("XAUTHORITY", xauthority, 1);
|
||||
_ = interop.setenv("DISPLAY", display_name, 1);
|
||||
_ = interop.stdlib.setenv("XAUTHORITY", xauthority, 1);
|
||||
_ = interop.stdlib.setenv("DISPLAY", display_name, 1);
|
||||
|
||||
const mcookie_output = try mcookie(mcookie_cmd);
|
||||
const magic_cookie = mcookie();
|
||||
|
||||
const pid = try std.posix.fork();
|
||||
if (pid == 0) {
|
||||
const log_file = try redirectStandardStreams(config.session_log, true);
|
||||
defer log_file.close();
|
||||
|
||||
var cmd_buffer: [1024]u8 = undefined;
|
||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ xauth_cmd, display_name, mcookie_output }) catch std.process.exit(1);
|
||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} add {s} . {s}", .{ config.xauth_cmd, display_name, magic_cookie }) catch std.process.exit(1);
|
||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
||||
std.process.exit(1);
|
||||
@@ -379,14 +449,21 @@ fn xauth(display_name: [:0]u8, shell: [*:0]const u8, pw_dir: [*:0]const u8, xaut
|
||||
if (status.status != 0) return error.XauthFailed;
|
||||
}
|
||||
|
||||
fn executeShellCmd(shell: [*:0]const u8) !void {
|
||||
const args = [_:null]?[*:0]const u8{shell};
|
||||
fn executeShellCmd(shell: [*:0]const u8, config: Config) !void {
|
||||
// We don't want to redirect stdout and stderr in a shell session
|
||||
|
||||
var cmd_buffer: [1024]u8 = undefined;
|
||||
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.setup_cmd, config.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, wayland_cmd: []const u8, desktop_cmd: []const u8) !void {
|
||||
fn executeWaylandCmd(shell: [*:0]const u8, config: Config, desktop_cmd: []const u8) !void {
|
||||
const log_file = try redirectStandardStreams(config.session_log, true);
|
||||
defer log_file.close();
|
||||
|
||||
var cmd_buffer: [1024]u8 = undefined;
|
||||
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ wayland_cmd, desktop_cmd });
|
||||
const cmd_str = try std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.setup_cmd, config.login_cmd orelse "", desktop_cmd });
|
||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
||||
return std.posix.execveZ(shell, &args, std.c.environ);
|
||||
}
|
||||
@@ -395,12 +472,12 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
|
||||
const display_num = try getFreeDisplay();
|
||||
var buf: [5]u8 = undefined;
|
||||
const display_name = try std.fmt.bufPrintZ(&buf, ":{d}", .{display_num});
|
||||
try xauth(display_name, shell, pw_dir, config.xauth_cmd, config.mcookie_cmd);
|
||||
try xauth(display_name, shell, pw_dir, config);
|
||||
|
||||
const pid = try std.posix.fork();
|
||||
if (pid == 0) {
|
||||
var cmd_buffer: [1024]u8 = undefined;
|
||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s}", .{ config.x_cmd, display_name, vt }) catch std.process.exit(1);
|
||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} >{s} 2>&1", .{ config.x_cmd, display_name, vt, config.session_log }) catch std.process.exit(1);
|
||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
||||
std.process.exit(1);
|
||||
@@ -423,7 +500,7 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
|
||||
xorg_pid = try std.posix.fork();
|
||||
if (xorg_pid == 0) {
|
||||
var cmd_buffer: [1024]u8 = undefined;
|
||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s}", .{ config.x_cmd_setup, desktop_cmd }) catch std.process.exit(1);
|
||||
const cmd_str = std.fmt.bufPrintZ(&cmd_buffer, "{s} {s} {s} >{s} 2>&1", .{ config.setup_cmd, config.login_cmd orelse "", desktop_cmd, config.session_log }) catch std.process.exit(1);
|
||||
const args = [_:null]?[*:0]const u8{ shell, "-c", cmd_str };
|
||||
std.posix.execveZ(shell, &args, std.c.environ) catch {};
|
||||
std.process.exit(1);
|
||||
@@ -447,6 +524,15 @@ fn executeX11Cmd(shell: [*:0]const u8, pw_dir: [*:0]const u8, config: Config, de
|
||||
_ = std.c.waitpid(x_pid, &status, 0);
|
||||
}
|
||||
|
||||
fn redirectStandardStreams(session_log: []const u8, create: bool) !std.fs.File {
|
||||
const log_file = if (create) (try std.fs.cwd().createFile(session_log, .{ .mode = 0o666 })) else (try std.fs.cwd().openFile(session_log, .{ .mode = .read_write }));
|
||||
|
||||
try std.posix.dup2(std.posix.STDOUT_FILENO, std.posix.STDERR_FILENO);
|
||||
try std.posix.dup2(log_file.handle, std.posix.STDOUT_FILENO);
|
||||
|
||||
return log_file;
|
||||
}
|
||||
|
||||
fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
|
||||
entry.ut_type = utmp.USER_PROCESS;
|
||||
entry.ut_pid = pid;
|
||||
@@ -454,23 +540,23 @@ fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
|
||||
var buf: [4096]u8 = undefined;
|
||||
const ttyname = try std.os.getFdPath(std.posix.STDIN_FILENO, &buf);
|
||||
|
||||
var ttyname_buf: [32]u8 = undefined;
|
||||
var ttyname_buf: [@sizeOf(@TypeOf(entry.ut_line))]u8 = undefined;
|
||||
_ = try std.fmt.bufPrintZ(&ttyname_buf, "{s}", .{ttyname["/dev/".len..]});
|
||||
|
||||
entry.ut_line = ttyname_buf;
|
||||
entry.ut_id = ttyname_buf["tty".len..7].*;
|
||||
|
||||
var username_buf: [32]u8 = undefined;
|
||||
var username_buf: [@sizeOf(@TypeOf(entry.ut_user))]u8 = undefined;
|
||||
_ = try std.fmt.bufPrintZ(&username_buf, "{s}", .{username});
|
||||
|
||||
entry.ut_user = username_buf;
|
||||
|
||||
var host: [256]u8 = undefined;
|
||||
var host: [@sizeOf(@TypeOf(entry.ut_host))]u8 = undefined;
|
||||
host[0] = 0;
|
||||
entry.ut_host = host;
|
||||
|
||||
var tv: std.c.timeval = undefined;
|
||||
_ = std.c.gettimeofday(&tv, null);
|
||||
var tv: interop.system_time.timeval = undefined;
|
||||
_ = interop.system_time.gettimeofday(&tv, null);
|
||||
|
||||
entry.ut_tv = .{
|
||||
.tv_sec = @intCast(tv.tv_sec),
|
||||
@@ -478,18 +564,18 @@ fn addUtmpEntry(entry: *Utmp, username: [*:0]const u8, pid: c_int) !void {
|
||||
};
|
||||
entry.ut_addr_v6[0] = 0;
|
||||
|
||||
utmp.setutent();
|
||||
_ = utmp.pututline(entry);
|
||||
utmp.endutent();
|
||||
utmp.setutxent();
|
||||
_ = utmp.pututxline(entry);
|
||||
utmp.endutxent();
|
||||
}
|
||||
|
||||
fn removeUtmpEntry(entry: *Utmp) void {
|
||||
entry.ut_type = utmp.DEAD_PROCESS;
|
||||
entry.ut_line[0] = 0;
|
||||
entry.ut_user[0] = 0;
|
||||
utmp.setutent();
|
||||
_ = utmp.pututline(entry);
|
||||
utmp.endutent();
|
||||
utmp.setutxent();
|
||||
_ = utmp.pututxline(entry);
|
||||
utmp.endutxent();
|
||||
}
|
||||
|
||||
fn pamDiagnose(status: c_int) anyerror {
|
||||
|
||||
150
src/bigclock.zig
150
src/bigclock.zig
@@ -1,140 +1,58 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const interop = @import("interop.zig");
|
||||
const utils = @import("tui/utils.zig");
|
||||
const enums = @import("enums.zig");
|
||||
const Lang = @import("bigclock/Lang.zig");
|
||||
const en = @import("bigclock/en.zig");
|
||||
const fa = @import("bigclock/fa.zig");
|
||||
|
||||
const termbox = interop.termbox;
|
||||
const termbox = interop.termbox;
|
||||
const Bigclock = enums.Bigclock;
|
||||
pub const WIDTH = Lang.WIDTH;
|
||||
pub const HEIGHT = Lang.HEIGHT;
|
||||
pub const SIZE = Lang.SIZE;
|
||||
|
||||
const X: u32 = if (builtin.os.tag == .linux or builtin.os.tag.isBSD()) 0x2593 else '#';
|
||||
const O: u32 = 0;
|
||||
pub fn clockCell(animate: bool, char: u8, fg: u16, bg: u16, bigclock: Bigclock) [SIZE]utils.Cell {
|
||||
var cells: [SIZE]utils.Cell = undefined;
|
||||
|
||||
pub const WIDTH = 5;
|
||||
pub const HEIGHT = 5;
|
||||
pub const SIZE = WIDTH * HEIGHT;
|
||||
var tv: interop.system_time.timeval = undefined;
|
||||
_ = interop.system_time.gettimeofday(&tv, null);
|
||||
|
||||
// zig fmt: off
|
||||
const ZERO = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const ONE = [_]u21{
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
};
|
||||
const TWO = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const THREE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const FOUR = [_]u21{
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
};
|
||||
const FIVE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const SIX = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const SEVEN = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
};
|
||||
const EIGHT = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const NINE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
};
|
||||
const S = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
};
|
||||
const E = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
};
|
||||
// zig fmt: on
|
||||
|
||||
pub fn clockCell(animate: bool, char: u8, fg: u8, bg: u8) [SIZE]termbox.tb_cell {
|
||||
var cells: [SIZE]termbox.tb_cell = undefined;
|
||||
|
||||
var tv: std.c.timeval = undefined;
|
||||
_ = std.c.gettimeofday(&tv, null);
|
||||
|
||||
const clock_chars = toBigNumber(if (animate and char == ':' and @divTrunc(tv.tv_usec, 500000) != 0) ' ' else char);
|
||||
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] = utils.initCell(clock_chars[i], fg, bg);
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
pub fn alphaBlit(buffer: [*]termbox.tb_cell, x: u64, y: u64, tb_width: u64, tb_height: u64, cells: [SIZE]termbox.tb_cell) void {
|
||||
pub fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [SIZE]utils.Cell) void {
|
||||
if (x + WIDTH >= tb_width or y + HEIGHT >= tb_height) return;
|
||||
|
||||
for (0..HEIGHT) |yy| {
|
||||
for (0..WIDTH) |xx| {
|
||||
const cell = cells[yy * WIDTH + xx];
|
||||
if (cell.ch != 0) buffer[(y + yy) * tb_width + (x + xx)] = cell;
|
||||
if (cell.ch != 0) utils.putCell(x + xx, y + yy, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn toBigNumber(char: u8) []const u21 {
|
||||
fn toBigNumber(char: u8, bigclock: Bigclock) []const u21 {
|
||||
const locale_chars = switch (bigclock) {
|
||||
.fa => fa.locale_chars,
|
||||
.en => en.locale_chars,
|
||||
.none => unreachable,
|
||||
};
|
||||
return switch (char) {
|
||||
'0' => &ZERO,
|
||||
'1' => &ONE,
|
||||
'2' => &TWO,
|
||||
'3' => &THREE,
|
||||
'4' => &FOUR,
|
||||
'5' => &FIVE,
|
||||
'6' => &SIX,
|
||||
'7' => &SEVEN,
|
||||
'8' => &EIGHT,
|
||||
'9' => &NINE,
|
||||
':' => &S,
|
||||
else => &E,
|
||||
'0' => &locale_chars.ZERO,
|
||||
'1' => &locale_chars.ONE,
|
||||
'2' => &locale_chars.TWO,
|
||||
'3' => &locale_chars.THREE,
|
||||
'4' => &locale_chars.FOUR,
|
||||
'5' => &locale_chars.FIVE,
|
||||
'6' => &locale_chars.SIX,
|
||||
'7' => &locale_chars.SEVEN,
|
||||
'8' => &locale_chars.EIGHT,
|
||||
'9' => &locale_chars.NINE,
|
||||
':' => &locale_chars.S,
|
||||
else => &locale_chars.E,
|
||||
};
|
||||
}
|
||||
|
||||
23
src/bigclock/Lang.zig
Normal file
23
src/bigclock/Lang.zig
Normal file
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
};
|
||||
94
src/bigclock/en.zig
Normal file
94
src/bigclock/en.zig
Normal file
@@ -0,0 +1,94 @@
|
||||
const Lang = @import("Lang.zig");
|
||||
|
||||
const LocaleChars = Lang.LocaleChars;
|
||||
const X = Lang.X;
|
||||
const O = Lang.O;
|
||||
|
||||
// zig fmt: off
|
||||
pub const locale_chars = LocaleChars{
|
||||
.ZERO = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.ONE = [_]u21{
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
},
|
||||
.TWO = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.THREE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.FOUR = [_]u21{
|
||||
X,X,O,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
},
|
||||
.FIVE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.SIX = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,O,O,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.SEVEN = [_]u21{
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
O,O,O,X,X,
|
||||
},
|
||||
.EIGHT = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.NINE = [_]u21{
|
||||
X,X,X,X,X,
|
||||
X,X,O,X,X,
|
||||
X,X,X,X,X,
|
||||
O,O,O,X,X,
|
||||
X,X,X,X,X,
|
||||
},
|
||||
.S = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.E = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
};
|
||||
// zig fmt: on
|
||||
94
src/bigclock/fa.zig
Normal file
94
src/bigclock/fa.zig
Normal file
@@ -0,0 +1,94 @@
|
||||
const Lang = @import("Lang.zig");
|
||||
|
||||
const LocaleChars = Lang.LocaleChars;
|
||||
const X = Lang.X;
|
||||
const O = Lang.O;
|
||||
|
||||
// zig fmt: off
|
||||
pub const locale_chars = LocaleChars{
|
||||
.ZERO = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,X,O,X,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.ONE = [_]u21{
|
||||
O,O,X,O,O,
|
||||
O,X,X,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,X,O,O,
|
||||
},
|
||||
.TWO = [_]u21{
|
||||
O,X,O,X,O,
|
||||
O,X,X,X,O,
|
||||
O,X,O,O,O,
|
||||
O,X,O,O,O,
|
||||
O,X,O,O,O,
|
||||
},
|
||||
.THREE = [_]u21{
|
||||
X,O,X,O,X,
|
||||
X,X,X,X,X,
|
||||
X,O,O,O,O,
|
||||
X,O,O,O,O,
|
||||
X,O,O,O,O,
|
||||
},
|
||||
.FOUR = [_]u21{
|
||||
O,X,O,X,X,
|
||||
O,X,X,O,O,
|
||||
O,X,X,X,X,
|
||||
O,X,O,O,O,
|
||||
O,X,O,O,O,
|
||||
},
|
||||
.FIVE = [_]u21{
|
||||
O,O,X,X,O,
|
||||
O,X,O,O,X,
|
||||
X,O,O,O,X,
|
||||
X,O,X,O,X,
|
||||
O,X,O,X,O,
|
||||
},
|
||||
.SIX = [_]u21{
|
||||
O,X,X,O,O,
|
||||
O,X,O,O,X,
|
||||
O,O,X,O,O,
|
||||
O,X,O,O,O,
|
||||
X,O,O,O,O,
|
||||
},
|
||||
.SEVEN = [_]u21{
|
||||
X,O,O,O,X,
|
||||
X,O,O,O,X,
|
||||
O,X,O,X,O,
|
||||
O,X,O,X,O,
|
||||
O,O,X,O,O,
|
||||
},
|
||||
.EIGHT = [_]u21{
|
||||
O,O,O,X,O,
|
||||
O,O,X,O,X,
|
||||
O,O,X,O,X,
|
||||
O,X,O,O,X,
|
||||
O,X,O,O,X,
|
||||
},
|
||||
.NINE = [_]u21{
|
||||
O,X,X,X,O,
|
||||
O,X,O,X,O,
|
||||
O,X,X,X,O,
|
||||
O,O,O,X,O,
|
||||
O,O,O,X,O,
|
||||
},
|
||||
.S = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,X,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
.E = [_]u21{
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
O,O,O,O,O,
|
||||
},
|
||||
};
|
||||
// zig fmt: on
|
||||
@@ -3,51 +3,59 @@ const enums = @import("../enums.zig");
|
||||
|
||||
const Animation = enums.Animation;
|
||||
const Input = enums.Input;
|
||||
const ViMode = enums.ViMode;
|
||||
const Bigclock = enums.Bigclock;
|
||||
|
||||
animation: Animation = .none,
|
||||
asterisk: u8 = '*',
|
||||
bg: u8 = 0,
|
||||
bigclock: bool = false,
|
||||
animation_timeout_sec: u12 = 0,
|
||||
asterisk: ?u8 = '*',
|
||||
auth_fails: u64 = 10,
|
||||
bg: u16 = 0,
|
||||
bigclock: Bigclock = .none,
|
||||
blank_box: bool = true,
|
||||
border_fg: u8 = 8,
|
||||
border_fg: u16 = 8,
|
||||
box_title: ?[]const u8 = null,
|
||||
brightness_down_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s 10%-",
|
||||
brightness_down_key: []const u8 = "F5",
|
||||
brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/brightnessctl -q s +10%",
|
||||
brightness_up_key: []const u8 = "F6",
|
||||
clear_password: bool = false,
|
||||
clock: ?[:0]const u8 = null,
|
||||
console_dev: [:0]const u8 = "/dev/console",
|
||||
cmatrix_fg: u16 = 3,
|
||||
console_dev: []const u8 = "/dev/console",
|
||||
default_input: Input = .login,
|
||||
fg: u8 = 8,
|
||||
error_bg: u16 = 0,
|
||||
error_fg: u16 = 258,
|
||||
fg: u16 = 8,
|
||||
hide_borders: bool = false,
|
||||
hide_key_hints: bool = false,
|
||||
initial_info_text: ?[]const u8 = null,
|
||||
input_len: u8 = 34,
|
||||
lang: []const u8 = "en",
|
||||
load: bool = true,
|
||||
login_cmd: ?[]const u8 = null,
|
||||
logout_cmd: ?[]const u8 = null,
|
||||
margin_box_h: u8 = 2,
|
||||
margin_box_v: u8 = 1,
|
||||
max_desktop_len: u8 = 100,
|
||||
max_login_len: u8 = 255,
|
||||
max_password_len: u8 = 255,
|
||||
mcookie_cmd: [:0]const u8 = "/usr/bin/mcookie",
|
||||
min_refresh_delta: u16 = 5,
|
||||
numlock: bool = false,
|
||||
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin",
|
||||
path: ?[:0]const u8 = "/sbin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin",
|
||||
restart_cmd: []const u8 = "/sbin/shutdown -r now",
|
||||
restart_key: []const u8 = "F2",
|
||||
save: bool = true,
|
||||
save_file: []const u8 = "/etc/ly/save",
|
||||
service_name: [:0]const u8 = "ly",
|
||||
session_log: []const u8 = "ly-session.log",
|
||||
setup_cmd: []const u8 = build_options.config_directory ++ "/ly/setup.sh",
|
||||
shutdown_cmd: []const u8 = "/sbin/shutdown -a now",
|
||||
shutdown_key: []const u8 = "F1",
|
||||
sleep_cmd: ?[]const u8 = null,
|
||||
sleep_key: []const u8 = "F3",
|
||||
term_reset_cmd: [:0]const u8 = "/usr/bin/tput reset",
|
||||
term_restore_cursor_cmd: []const u8 = "/usr/bin/tput cnorm",
|
||||
tty: u8 = 2,
|
||||
text_in_center: bool = false,
|
||||
tty: u8 = build_options.tty,
|
||||
vi_default_mode: ViMode = .normal,
|
||||
vi_mode: bool = false,
|
||||
wayland_cmd: []const u8 = build_options.data_directory ++ "/wsetup.sh",
|
||||
waylandsessions: []const u8 = "/usr/share/wayland-sessions",
|
||||
x_cmd: []const u8 = "/usr/bin/X",
|
||||
waylandsessions: []const u8 = build_options.prefix_directory ++ "/share/wayland-sessions",
|
||||
x_cmd: []const u8 = build_options.prefix_directory ++ "/bin/X",
|
||||
xauth_cmd: []const u8 = build_options.prefix_directory ++ "/bin/xauth",
|
||||
xinitrc: ?[]const u8 = "~/.xinitrc",
|
||||
x_cmd_setup: []const u8 = build_options.data_directory ++ "/xsetup.sh",
|
||||
xauth_cmd: []const u8 = "/usr/bin/xauth",
|
||||
xsessions: []const u8 = "/usr/share/xsessions",
|
||||
xsessions: []const u8 = build_options.prefix_directory ++ "/share/xsessions",
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
authenticating: []const u8 = "authenticating...",
|
||||
brightness_down: []const u8 = "decrease brightness",
|
||||
brightness_up: []const u8 = "increase brightness",
|
||||
capslock: []const u8 = "capslock",
|
||||
err_alloc: []const u8 = "failed memory allocation",
|
||||
err_bounds: []const u8 = "out-of-bounds index",
|
||||
err_brightness_change: []const u8 = "failed to change brightness",
|
||||
err_chdir: []const u8 = "failed to open home folder",
|
||||
err_config: []const u8 = "unable to parse config file",
|
||||
err_console_dev: []const u8 = "failed to access console",
|
||||
err_dgn_oob: []const u8 = "log message",
|
||||
err_domain: []const u8 = "invalid domain",
|
||||
err_envlist: []const u8 = "failed to get envlist",
|
||||
err_hostname: []const u8 = "failed to get hostname",
|
||||
err_mcookie: []const u8 = "mcookie command failed",
|
||||
err_mlock: []const u8 = "failed to lock password memory",
|
||||
err_null: []const u8 = "null pointer",
|
||||
err_numlock: []const u8 = "failed to set numlock",
|
||||
err_pam: []const u8 = "pam transaction failed",
|
||||
err_pam_abort: []const u8 = "pam transaction aborted",
|
||||
err_pam_acct_expired: []const u8 = "account expired",
|
||||
@@ -44,6 +48,7 @@ insert: []const u8 = "insert",
|
||||
login: []const u8 = "login:",
|
||||
logout: []const u8 = "logged out",
|
||||
normal: []const u8 = "normal",
|
||||
no_x11_support: []const u8 = "x11 support disabled at compile-time",
|
||||
numlock: []const u8 = "numlock",
|
||||
other: []const u8 = "other",
|
||||
password: []const u8 = "password:",
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
user: ?[]const u8 = null,
|
||||
session_index: ?u64 = null,
|
||||
session_index: ?usize = null,
|
||||
|
||||
@@ -1,30 +1,144 @@
|
||||
// The migrator ensures compatibility with <=0.6.0 configuration files
|
||||
|
||||
const std = @import("std");
|
||||
const ini = @import("zigini");
|
||||
const Save = @import("Save.zig");
|
||||
const enums = @import("../enums.zig");
|
||||
|
||||
pub fn tryMigrateSaveFile(user_buf: *[32]u8, path: []const u8) Save {
|
||||
var temporary_allocator = std.heap.page_allocator;
|
||||
|
||||
pub var maybe_animate: ?bool = null;
|
||||
pub var maybe_save_file: ?[]const u8 = null;
|
||||
|
||||
pub var mapped_config_fields = false;
|
||||
|
||||
pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniField {
|
||||
if (std.mem.eql(u8, field.key, "animate")) {
|
||||
// The option doesn't exist anymore, but we save its value for "animation"
|
||||
maybe_animate = std.mem.eql(u8, field.value, "true");
|
||||
|
||||
mapped_config_fields = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "animation")) {
|
||||
// The option now uses a string (which then gets converted into an enum) instead of an integer
|
||||
// It also combines the previous "animate" and "animation" options
|
||||
const animation = std.fmt.parseInt(u8, field.value, 10) catch return field;
|
||||
var mapped_field = field;
|
||||
|
||||
mapped_field.value = switch (animation) {
|
||||
0 => "doom",
|
||||
1 => "matrix",
|
||||
else => "none",
|
||||
};
|
||||
|
||||
mapped_config_fields = true;
|
||||
return mapped_field;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "blank_password")) {
|
||||
// The option has simply been renamed
|
||||
var mapped_field = field;
|
||||
mapped_field.key = "clear_password";
|
||||
|
||||
mapped_config_fields = true;
|
||||
return mapped_field;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "default_input")) {
|
||||
// The option now uses a string (which then gets converted into an enum) instead of an integer
|
||||
const default_input = std.fmt.parseInt(u8, field.value, 10) catch return field;
|
||||
var mapped_field = field;
|
||||
|
||||
mapped_field.value = switch (default_input) {
|
||||
0 => "session",
|
||||
1 => "login",
|
||||
2 => "password",
|
||||
else => "login",
|
||||
};
|
||||
|
||||
mapped_config_fields = true;
|
||||
return mapped_field;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "save_file")) {
|
||||
// The option doesn't exist anymore, but we save its value for migration later on
|
||||
maybe_save_file = temporary_allocator.dupe(u8, field.value) catch return null;
|
||||
|
||||
mapped_config_fields = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "wayland_specifier") or
|
||||
std.mem.eql(u8, field.key, "max_desktop_len") or
|
||||
std.mem.eql(u8, field.key, "max_login_len") or
|
||||
std.mem.eql(u8, field.key, "max_password_len") or
|
||||
std.mem.eql(u8, field.key, "mcookie_cmd") or
|
||||
std.mem.eql(u8, field.key, "term_reset_cmd") or
|
||||
std.mem.eql(u8, field.key, "term_restore_cursor_cmd") or
|
||||
std.mem.eql(u8, field.key, "x_cmd_setup") or
|
||||
std.mem.eql(u8, field.key, "wayland_cmd"))
|
||||
{
|
||||
// The options don't exist anymore
|
||||
mapped_config_fields = true;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, field.key, "bigclock")) {
|
||||
// The option now uses a string (which then gets converted into an enum) instead of an boolean
|
||||
// It also includes the ability to change active bigclock's language
|
||||
var mapped_field = field;
|
||||
|
||||
if (std.mem.eql(u8, field.value, "true")){
|
||||
mapped_field.value = "en";
|
||||
mapped_config_fields = true;
|
||||
}else if (std.mem.eql(u8, field.value, "false")){
|
||||
mapped_field.value = "none";
|
||||
mapped_config_fields = true;
|
||||
}
|
||||
|
||||
return mapped_field;
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
|
||||
// This is the stuff we only handle after reading the config.
|
||||
// For example, the "animate" field could come after "animation"
|
||||
pub fn lateConfigFieldHandler(animation: *enums.Animation) void {
|
||||
if (maybe_animate) |animate| {
|
||||
if (!animate) animation.* = .none;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
|
||||
var save = Save{};
|
||||
|
||||
var file = std.fs.openFileAbsolute(path, .{}) catch return save;
|
||||
defer file.close();
|
||||
if (maybe_save_file) |path| {
|
||||
defer temporary_allocator.free(path);
|
||||
|
||||
const reader = file.reader();
|
||||
var file = std.fs.openFileAbsolute(path, .{}) catch return save;
|
||||
defer file.close();
|
||||
|
||||
var user_fbs = std.io.fixedBufferStream(user_buf);
|
||||
reader.streamUntilDelimiter(user_fbs.writer(), '\n', 32) catch return save;
|
||||
const user = user_fbs.getWritten();
|
||||
if (user.len > 0) save.user = user;
|
||||
const reader = file.reader();
|
||||
|
||||
var session_buf: [20]u8 = undefined;
|
||||
var session_fbs = std.io.fixedBufferStream(&session_buf);
|
||||
reader.streamUntilDelimiter(session_fbs.writer(), '\n', 20) catch {};
|
||||
var user_fbs = std.io.fixedBufferStream(user_buf);
|
||||
reader.streamUntilDelimiter(user_fbs.writer(), '\n', user_buf.len) catch return save;
|
||||
const user = user_fbs.getWritten();
|
||||
if (user.len > 0) save.user = user;
|
||||
|
||||
const session_index_str = session_fbs.getWritten();
|
||||
var session_index: ?u64 = null;
|
||||
if (session_index_str.len > 0) {
|
||||
session_index = std.fmt.parseUnsigned(u64, session_index_str, 10) catch return save;
|
||||
var session_buf: [20]u8 = undefined;
|
||||
var session_fbs = std.io.fixedBufferStream(&session_buf);
|
||||
reader.streamUntilDelimiter(session_fbs.writer(), '\n', session_buf.len) catch return save;
|
||||
|
||||
const session_index_str = session_fbs.getWritten();
|
||||
var session_index: ?usize = null;
|
||||
if (session_index_str.len > 0) {
|
||||
session_index = std.fmt.parseUnsigned(usize, session_index_str, 10) catch return save;
|
||||
}
|
||||
save.session_index = session_index;
|
||||
}
|
||||
save.session_index = session_index;
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,19 @@ pub const DisplayServer = enum {
|
||||
};
|
||||
|
||||
pub const Input = enum {
|
||||
info_line,
|
||||
session,
|
||||
login,
|
||||
password,
|
||||
};
|
||||
|
||||
pub const ViMode = enum {
|
||||
normal,
|
||||
insert,
|
||||
};
|
||||
|
||||
pub const Bigclock = enum {
|
||||
none,
|
||||
en,
|
||||
fa,
|
||||
};
|
||||
136
src/interop.zig
136
src/interop.zig
@@ -9,111 +9,103 @@ pub const pam = @cImport({
|
||||
});
|
||||
|
||||
pub const utmp = @cImport({
|
||||
@cInclude("utmp.h");
|
||||
@cInclude("utmpx.h");
|
||||
});
|
||||
|
||||
// Exists for X11 support only
|
||||
pub const xcb = @cImport({
|
||||
@cInclude("xcb/xcb.h");
|
||||
});
|
||||
|
||||
pub const c_size = u64;
|
||||
pub const c_uid = u32;
|
||||
pub const c_gid = u32;
|
||||
pub const c_time = c_long;
|
||||
pub const tm = extern struct {
|
||||
tm_sec: c_int,
|
||||
tm_min: c_int,
|
||||
tm_hour: c_int,
|
||||
tm_mday: c_int,
|
||||
tm_mon: c_int,
|
||||
tm_year: c_int,
|
||||
tm_wday: c_int,
|
||||
tm_yday: c_int,
|
||||
tm_isdst: c_int,
|
||||
};
|
||||
pub const passwd = extern struct {
|
||||
pw_name: [*:0]u8,
|
||||
pw_passwd: [*:0]u8,
|
||||
pub const unistd = @cImport({
|
||||
@cInclude("unistd.h");
|
||||
});
|
||||
|
||||
pw_uid: c_uid,
|
||||
pw_gid: c_gid,
|
||||
pw_gecos: [*:0]u8,
|
||||
pw_dir: [*:0]u8,
|
||||
pw_shell: [*:0]u8,
|
||||
};
|
||||
pub const time = @cImport({
|
||||
@cInclude("time.h");
|
||||
});
|
||||
|
||||
pub const VT_ACTIVATE: c_int = 0x5606;
|
||||
pub const VT_WAITACTIVE: c_int = 0x5607;
|
||||
pub const system_time = @cImport({
|
||||
@cInclude("sys/time.h");
|
||||
});
|
||||
|
||||
pub const KDGETLED: c_int = 0x4B31;
|
||||
pub const KDSETLED: c_int = 0x4B32;
|
||||
pub const KDGKBLED: c_int = 0x4B64;
|
||||
pub const KDSKBLED: c_int = 0x4B65;
|
||||
pub const stdlib = @cImport({
|
||||
@cInclude("stdlib.h");
|
||||
});
|
||||
|
||||
pub const LED_NUM: c_int = 0x02;
|
||||
pub const LED_CAP: c_int = 0x04;
|
||||
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 K_NUMLOCK: c_int = 0x02;
|
||||
pub const K_CAPSLOCK: c_int = 0x04;
|
||||
pub const grp = @cImport({
|
||||
@cInclude("grp.h");
|
||||
});
|
||||
|
||||
pub extern "c" fn localtime(timer: *const c_time) *tm;
|
||||
pub extern "c" fn strftime(str: [*:0]u8, maxsize: c_size, format: [*:0]const u8, timeptr: *const tm) c_size;
|
||||
pub extern "c" fn setenv(name: [*:0]const u8, value: [*:0]const u8, overwrite: c_int) c_int;
|
||||
pub extern "c" fn putenv(name: [*:0]u8) c_int;
|
||||
pub extern "c" fn getuid() c_uid;
|
||||
pub extern "c" fn getpwnam(name: [*:0]const u8) ?*passwd;
|
||||
pub extern "c" fn endpwent() void;
|
||||
pub extern "c" fn setusershell() void;
|
||||
pub extern "c" fn getusershell() [*:0]u8;
|
||||
pub extern "c" fn endusershell() void;
|
||||
pub extern "c" fn initgroups(user: [*:0]const u8, group: c_gid) c_int;
|
||||
// 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 = localtime(&timer);
|
||||
const tm_info = time.localtime(&timer);
|
||||
|
||||
const len = strftime(buf, buf.len, format, tm_info);
|
||||
const len = time.strftime(buf, buf.len, format, tm_info);
|
||||
if (len < 0) return error.CannotGetFormattedTime;
|
||||
|
||||
return buf[0..len];
|
||||
}
|
||||
|
||||
pub fn getLockState(console_dev: [:0]const u8) !struct {
|
||||
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 = std.c.open(console_dev, .{ .ACCMODE = .RDONLY });
|
||||
if (fd < 0) return error.CannotOpenConsoleDev;
|
||||
defer _ = std.c.close(fd);
|
||||
const fd = try std.posix.open(console_dev, .{ .ACCMODE = .RDONLY }, 0);
|
||||
defer std.posix.close(fd);
|
||||
|
||||
var numlock = false;
|
||||
var capslock = false;
|
||||
|
||||
if (builtin.os.tag.isBSD()) {
|
||||
var led: c_int = undefined;
|
||||
_ = std.c.ioctl(fd, KDGETLED, &led);
|
||||
numlock = (led & LED_NUM) != 0;
|
||||
capslock = (led & LED_CAP) != 0;
|
||||
} else {
|
||||
var led: c_char = undefined;
|
||||
_ = std.c.ioctl(fd, KDGKBLED, &led);
|
||||
numlock = (led & K_NUMLOCK) != 0;
|
||||
capslock = (led & K_CAPSLOCK) != 0;
|
||||
}
|
||||
var led: LedState = undefined;
|
||||
_ = std.c.ioctl(fd, get_led_state, &led);
|
||||
|
||||
return .{
|
||||
.numlock = numlock,
|
||||
.capslock = capslock,
|
||||
.numlock = (led & numlock_led) != 0,
|
||||
.capslock = (led & capslock_led) != 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setNumlock(val: bool) !void {
|
||||
var led: c_char = undefined;
|
||||
_ = std.c.ioctl(0, KDGKBLED, &led);
|
||||
var led: LedState = undefined;
|
||||
_ = std.c.ioctl(0, get_led_state, &led);
|
||||
|
||||
const numlock = (led & K_NUMLOCK) != 0;
|
||||
const numlock = (led & numlock_led) != 0;
|
||||
if (numlock != val) {
|
||||
const status = std.c.ioctl(std.posix.STDIN_FILENO, KDSKBLED, led ^ K_NUMLOCK);
|
||||
const status = std.c.ioctl(std.posix.STDIN_FILENO, set_led_state, led ^ numlock_led);
|
||||
if (status != 0) return error.FailedToSetNumlock;
|
||||
}
|
||||
}
|
||||
|
||||
739
src/main.zig
739
src/main.zig
@@ -9,7 +9,7 @@ const interop = @import("interop.zig");
|
||||
const Doom = @import("animations/Doom.zig");
|
||||
const Matrix = @import("animations/Matrix.zig");
|
||||
const TerminalBuffer = @import("tui/TerminalBuffer.zig");
|
||||
const Desktop = @import("tui/components/Desktop.zig");
|
||||
const Session = @import("tui/components/Session.zig");
|
||||
const Text = @import("tui/components/Text.zig");
|
||||
const InfoLine = @import("tui/components/InfoLine.zig");
|
||||
const Config = @import("config/Config.zig");
|
||||
@@ -21,6 +21,8 @@ const utils = @import("tui/utils.zig");
|
||||
|
||||
const Ini = ini.Ini;
|
||||
const termbox = interop.termbox;
|
||||
const unistd = interop.unistd;
|
||||
const temporary_allocator = std.heap.page_allocator;
|
||||
|
||||
var session_pid: std.posix.pid_t = -1;
|
||||
pub fn signalHandler(i: c_int) callconv(.C) void {
|
||||
@@ -37,12 +39,49 @@ pub fn signalHandler(i: c_int) callconv(.C) void {
|
||||
std.c.exit(i);
|
||||
}
|
||||
|
||||
fn eventThreadMain(event: *?termbox.tb_event, event_error: *c_int, timeout: *c_int, wake: *std.Thread.Semaphore, cont: *std.Thread.Semaphore) void {
|
||||
while (true) {
|
||||
cont.wait();
|
||||
var e: termbox.tb_event = undefined;
|
||||
event_error.* = termbox.tb_peek_event(&e, timeout.*);
|
||||
event.* = e;
|
||||
wake.post();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var shutdown = false;
|
||||
var restart = false;
|
||||
var shutdown_cmd: []const u8 = undefined;
|
||||
var restart_cmd: []const u8 = undefined;
|
||||
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
defer {
|
||||
// If we can't shutdown or restart due to an error, we print it to standard error. If that fails, just bail out
|
||||
if (shutdown) {
|
||||
const shutdown_error = std.process.execv(temporary_allocator, &[_][]const u8{ "/bin/sh", "-c", shutdown_cmd });
|
||||
stderr.print("error: couldn't shutdown: {any}\n", .{shutdown_error}) catch std.process.exit(1);
|
||||
} else if (restart) {
|
||||
const restart_error = std.process.execv(temporary_allocator, &[_][]const u8{ "/bin/sh", "-c", restart_cmd });
|
||||
stderr.print("error: couldn't restart: {any}\n", .{restart_error}) catch std.process.exit(1);
|
||||
} else {
|
||||
// The user has quit Ly using Ctrl+C
|
||||
temporary_allocator.free(shutdown_cmd);
|
||||
temporary_allocator.free(restart_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
// to be able to stop the animation after some time
|
||||
|
||||
var tv_zero: interop.system_time.timeval = undefined;
|
||||
_ = interop.system_time.gettimeofday(&tv_zero, null);
|
||||
var animation_timed_out: bool = false;
|
||||
|
||||
const allocator = gpa.allocator();
|
||||
const stderr = std.io.getStdErr().writer();
|
||||
|
||||
// Load arguments
|
||||
const params = comptime clap.parseParamsComptime(
|
||||
@@ -61,12 +100,12 @@ pub fn main() !void {
|
||||
var config: Config = undefined;
|
||||
var lang: Lang = undefined;
|
||||
var save: Save = undefined;
|
||||
var info_line = InfoLine{};
|
||||
var config_load_failed = false;
|
||||
|
||||
if (res.args.help != 0) {
|
||||
try clap.help(stderr, clap.Help, ¶ms, .{});
|
||||
|
||||
_ = try stderr.write("Note: if you want to configure Ly, please check the config file, which is usually located at /etc/ly/config.ini.\n");
|
||||
_ = try stderr.write("Note: if you want to configure Ly, please check the config file, which is located at " ++ build_options.config_directory ++ "/ly/config.ini.\n");
|
||||
std.process.exit(0);
|
||||
}
|
||||
if (res.args.version != 0) {
|
||||
@@ -84,14 +123,13 @@ pub fn main() !void {
|
||||
var save_ini = Ini(Save).init(allocator);
|
||||
defer save_ini.deinit();
|
||||
|
||||
var save_path: []const u8 = build_options.data_directory ++ "/save.ini";
|
||||
var save_path: []const u8 = build_options.config_directory ++ "/ly/save.ini";
|
||||
var save_path_alloc = false;
|
||||
defer {
|
||||
if (save_path_alloc) allocator.free(save_path);
|
||||
}
|
||||
|
||||
// Compatibility with v0.6.0
|
||||
const mapped_config_fields = .{.{ "blank_password", "clear_password" }};
|
||||
const comment_characters = "#";
|
||||
|
||||
if (res.args.config) |s| {
|
||||
const trailing_slash = if (s[s.len - 1] != '/') "/" else "";
|
||||
@@ -99,47 +137,60 @@ pub fn main() !void {
|
||||
const config_path = try std.fmt.allocPrint(allocator, "{s}{s}config.ini", .{ s, trailing_slash });
|
||||
defer allocator.free(config_path);
|
||||
|
||||
config = config_ini.readFileToStructWithMap(config_path, mapped_config_fields) catch Config{};
|
||||
config = config_ini.readFileToStruct(config_path, comment_characters, migrator.configFieldHandler) catch _config: {
|
||||
config_load_failed = true;
|
||||
break :_config Config{};
|
||||
};
|
||||
|
||||
const lang_path = try std.fmt.allocPrint(allocator, "{s}{s}lang/{s}.ini", .{ s, trailing_slash, config.lang });
|
||||
defer allocator.free(lang_path);
|
||||
|
||||
lang = lang_ini.readFileToStruct(lang_path) catch Lang{};
|
||||
lang = lang_ini.readFileToStruct(lang_path, comment_characters, null) catch Lang{};
|
||||
|
||||
if (config.load) {
|
||||
save_path = try std.fmt.allocPrint(allocator, "{s}{s}save.ini", .{ s, trailing_slash });
|
||||
save_path_alloc = true;
|
||||
|
||||
var user_buf: [32]u8 = undefined;
|
||||
save = save_ini.readFileToStruct(save_path) catch migrator.tryMigrateSaveFile(&user_buf, config.save_file);
|
||||
save = save_ini.readFileToStruct(save_path, comment_characters, null) catch migrator.tryMigrateSaveFile(&user_buf);
|
||||
}
|
||||
} else {
|
||||
config = config_ini.readFileToStructWithMap(build_options.data_directory ++ "/config.ini", mapped_config_fields) catch Config{};
|
||||
|
||||
const lang_path = try std.fmt.allocPrint(allocator, "{s}/lang/{s}.ini", .{ build_options.data_directory, config.lang });
|
||||
migrator.lateConfigFieldHandler(&config.animation);
|
||||
} else {
|
||||
const config_path = build_options.config_directory ++ "/ly/config.ini";
|
||||
|
||||
config = config_ini.readFileToStruct(config_path, comment_characters, migrator.configFieldHandler) catch _config: {
|
||||
config_load_failed = true;
|
||||
break :_config Config{};
|
||||
};
|
||||
|
||||
const lang_path = try std.fmt.allocPrint(allocator, "{s}/ly/lang/{s}.ini", .{ build_options.config_directory, config.lang });
|
||||
defer allocator.free(lang_path);
|
||||
|
||||
lang = lang_ini.readFileToStruct(lang_path) catch Lang{};
|
||||
lang = lang_ini.readFileToStruct(lang_path, comment_characters, null) catch Lang{};
|
||||
|
||||
if (config.load) {
|
||||
var user_buf: [32]u8 = undefined;
|
||||
save = save_ini.readFileToStruct(save_path) catch migrator.tryMigrateSaveFile(&user_buf, config.save_file);
|
||||
save = save_ini.readFileToStruct(save_path, comment_characters, null) catch migrator.tryMigrateSaveFile(&user_buf);
|
||||
}
|
||||
|
||||
migrator.lateConfigFieldHandler(&config.animation);
|
||||
}
|
||||
|
||||
interop.setNumlock(config.numlock) catch {};
|
||||
// if (migrator.mapped_config_fields) save_migrated_config: {
|
||||
// var file = try std.fs.cwd().createFile(config_path, .{});
|
||||
// defer file.close();
|
||||
|
||||
if (config.initial_info_text) |text| {
|
||||
try info_line.setText(text);
|
||||
} else get_host_name: {
|
||||
// Initialize information line with host name
|
||||
var name_buf: [std.posix.HOST_NAME_MAX]u8 = undefined;
|
||||
const hostname = std.posix.gethostname(&name_buf) catch {
|
||||
try info_line.setText(lang.err_hostname);
|
||||
break :get_host_name;
|
||||
};
|
||||
try info_line.setText(hostname);
|
||||
}
|
||||
// const writer = file.writer();
|
||||
// ini.writeFromStruct(config, writer, null, true, .{}) catch {
|
||||
// break :save_migrated_config;
|
||||
// };
|
||||
// }
|
||||
|
||||
// These strings only end up getting freed if the user quits Ly using Ctrl+C, which is fine since in the other cases
|
||||
// we end up shutting down or restarting the system
|
||||
shutdown_cmd = try temporary_allocator.dupe(u8, config.shutdown_cmd);
|
||||
restart_cmd = try temporary_allocator.dupe(u8, config.restart_cmd);
|
||||
|
||||
// Initialize termbox
|
||||
_ = termbox.tb_init();
|
||||
@@ -161,32 +212,67 @@ pub fn main() !void {
|
||||
// Initialize terminal buffer
|
||||
const labels_max_length = @max(lang.login.len, lang.password.len);
|
||||
|
||||
var buffer = TerminalBuffer.init(config, labels_max_length);
|
||||
var seed: u64 = undefined;
|
||||
std.crypto.random.bytes(std.mem.asBytes(&seed)); // Get a random seed for the PRNG (used by animations)
|
||||
|
||||
var prng = std.Random.DefaultPrng.init(seed);
|
||||
const random = prng.random();
|
||||
|
||||
var buffer = TerminalBuffer.init(config, labels_max_length, random);
|
||||
|
||||
// Initialize components
|
||||
var desktop = try Desktop.init(allocator, &buffer, config.max_desktop_len, lang);
|
||||
defer desktop.deinit();
|
||||
var info_line = InfoLine.init(allocator, &buffer);
|
||||
defer info_line.deinit();
|
||||
|
||||
desktop.addEnvironment(.{ .Name = lang.shell }, "", .shell) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
};
|
||||
if (config.xinitrc) |xinitrc| {
|
||||
desktop.addEnvironment(.{ .Name = lang.xinitrc, .Exec = xinitrc }, "", .xinitrc) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
};
|
||||
if (config_load_failed) {
|
||||
// We can't localize this since the config failed to load so we'd fallback to the default language anyway
|
||||
try info_line.addMessage("unable to parse config file", config.error_bg, config.error_fg);
|
||||
}
|
||||
|
||||
try desktop.crawl(config.waylandsessions, .wayland);
|
||||
try desktop.crawl(config.xsessions, .x11);
|
||||
interop.setNumlock(config.numlock) catch {
|
||||
try info_line.addMessage(lang.err_numlock, config.error_bg, config.error_fg);
|
||||
};
|
||||
|
||||
var login = try Text.init(allocator, &buffer, config.max_login_len);
|
||||
var session = Session.init(allocator, &buffer, lang);
|
||||
defer session.deinit();
|
||||
|
||||
session.addEnvironment(.{ .Name = lang.shell }, null, .shell) catch {
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
};
|
||||
|
||||
if (build_options.enable_x11_support) {
|
||||
if (config.xinitrc) |xinitrc| {
|
||||
session.addEnvironment(.{ .Name = lang.xinitrc, .Exec = xinitrc }, null, .xinitrc) catch {
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
};
|
||||
}
|
||||
} else {
|
||||
try info_line.addMessage(lang.no_x11_support, config.bg, config.fg);
|
||||
}
|
||||
|
||||
if (config.initial_info_text) |text| {
|
||||
try info_line.addMessage(text, config.bg, config.fg);
|
||||
} else get_host_name: {
|
||||
// Initialize information line with host name
|
||||
var name_buf: [std.posix.HOST_NAME_MAX]u8 = undefined;
|
||||
const hostname = std.posix.gethostname(&name_buf) catch {
|
||||
try info_line.addMessage(lang.err_hostname, config.error_bg, config.error_fg);
|
||||
break :get_host_name;
|
||||
};
|
||||
try info_line.addMessage(hostname, config.bg, config.fg);
|
||||
}
|
||||
|
||||
try session.crawl(config.waylandsessions, .wayland);
|
||||
if (build_options.enable_x11_support) try session.crawl(config.xsessions, .x11);
|
||||
|
||||
var login = Text.init(allocator, &buffer, false, null);
|
||||
defer login.deinit();
|
||||
|
||||
var password = try Text.init(allocator, &buffer, config.max_password_len);
|
||||
var password = Text.init(allocator, &buffer, true, config.asterisk);
|
||||
defer password.deinit();
|
||||
|
||||
var active_input = config.default_input;
|
||||
var insert_mode = !config.vi_mode;
|
||||
var insert_mode = !config.vi_mode or config.vi_default_mode == .insert;
|
||||
|
||||
// Load last saved username and desktop selection, if any
|
||||
if (config.load) {
|
||||
@@ -198,7 +284,7 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
if (save.session_index) |session_index| {
|
||||
if (session_index < desktop.environments.items.len) desktop.current = session_index;
|
||||
if (session_index < session.label.list.items.len) session.label.current = session_index;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,17 +293,19 @@ pub fn main() !void {
|
||||
buffer.drawBoxCenter(!config.hide_borders, config.blank_box);
|
||||
|
||||
const coordinates = buffer.calculateComponentCoordinates();
|
||||
desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length);
|
||||
info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null);
|
||||
session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center);
|
||||
login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length);
|
||||
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
|
||||
|
||||
switch (active_input) {
|
||||
.session => desktop.handle(null, insert_mode),
|
||||
.info_line => info_line.label.handle(null, insert_mode),
|
||||
.session => session.label.handle(null, insert_mode),
|
||||
.login => login.handle(null, insert_mode) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
.password => password.handle(null, insert_mode) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -229,7 +317,7 @@ pub fn main() !void {
|
||||
switch (config.animation) {
|
||||
.none => {},
|
||||
.doom => doom = try Doom.init(allocator, &buffer),
|
||||
.matrix => matrix = try Matrix.init(allocator, &buffer),
|
||||
.matrix => matrix = try Matrix.init(allocator, &buffer, config.cmatrix_fg),
|
||||
}
|
||||
defer {
|
||||
switch (config.animation) {
|
||||
@@ -245,27 +333,37 @@ pub fn main() !void {
|
||||
const restart_key = try std.fmt.parseInt(u8, config.restart_key[1..], 10);
|
||||
const restart_len = try utils.strWidth(lang.restart);
|
||||
const sleep_key = try std.fmt.parseInt(u8, config.sleep_key[1..], 10);
|
||||
const sleep_len = try utils.strWidth(lang.sleep);
|
||||
const brightness_down_key = try std.fmt.parseInt(u8, config.brightness_down_key[1..], 10);
|
||||
const brightness_down_len = try utils.strWidth(lang.brightness_down);
|
||||
const brightness_up_key = try std.fmt.parseInt(u8, config.brightness_up_key[1..], 10);
|
||||
const brightness_up_len = try utils.strWidth(lang.brightness_up);
|
||||
|
||||
var event: termbox.tb_event = undefined;
|
||||
var event_timeout: c_int = std.math.maxInt(c_int);
|
||||
var event_error: c_int = undefined;
|
||||
var event: ?termbox.tb_event = null;
|
||||
var wake: std.Thread.Semaphore = .{};
|
||||
var doneEvent: std.Thread.Semaphore = .{};
|
||||
var run = true;
|
||||
var update = true;
|
||||
var resolution_changed = false;
|
||||
var shutdown = false;
|
||||
var restart = false;
|
||||
var auth_fails: u64 = 0;
|
||||
|
||||
// Switch to selected TTY if possible
|
||||
open_console_dev: {
|
||||
const fd = std.posix.open(config.console_dev, .{ .ACCMODE = .WRONLY }, 0) catch {
|
||||
try info_line.setText(lang.err_console_dev);
|
||||
break :open_console_dev;
|
||||
};
|
||||
defer std.posix.close(fd);
|
||||
interop.switchTty(config.console_dev, config.tty) catch {
|
||||
try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg);
|
||||
};
|
||||
|
||||
_ = std.c.ioctl(fd, interop.VT_ACTIVATE, config.tty);
|
||||
_ = std.c.ioctl(fd, interop.VT_WAITACTIVE, config.tty);
|
||||
doneEvent.post();
|
||||
const thandle = try std.Thread.spawn(.{}, eventThreadMain, .{ &event, &event_error, &event_timeout, &wake, &doneEvent });
|
||||
|
||||
thandle.detach();
|
||||
|
||||
{
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
|
||||
}
|
||||
|
||||
defer allocator.free(auth.currentLogin.?);
|
||||
while (run) {
|
||||
// If there's no input or there's an animation, a resolution change needs to be checked
|
||||
if (!update or config.animation != .none) {
|
||||
@@ -273,49 +371,45 @@ pub fn main() !void {
|
||||
|
||||
_ = termbox.tb_present(); // Required to update tb_width(), tb_height() and tb_cell_buffer()
|
||||
|
||||
const width: u64 = @intCast(termbox.tb_width());
|
||||
const height: u64 = @intCast(termbox.tb_height());
|
||||
const width: usize = @intCast(termbox.tb_width());
|
||||
const height: usize = @intCast(termbox.tb_height());
|
||||
|
||||
if (width != buffer.width or height != buffer.height) {
|
||||
// If it did change, then update the cell buffer, reallocate the current animation's buffers, and force a draw update
|
||||
|
||||
if (width != buffer.width) {
|
||||
buffer.width = width;
|
||||
resolution_changed = true;
|
||||
}
|
||||
|
||||
if (height != buffer.height) {
|
||||
buffer.height = height;
|
||||
resolution_changed = true;
|
||||
}
|
||||
|
||||
// If it did change, then update the cell buffer, reallocate the current animation's buffers, and force a draw update
|
||||
if (resolution_changed) {
|
||||
buffer.buffer = termbox.tb_cell_buffer();
|
||||
|
||||
switch (config.animation) {
|
||||
.none => {},
|
||||
.doom => doom.realloc() catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
.matrix => matrix.realloc() catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
}
|
||||
|
||||
update = true;
|
||||
resolution_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (update) {
|
||||
// If the user entered a wrong password 10 times in a row, play a cascade animation, else update normally
|
||||
if (auth_fails < 10) {
|
||||
if (auth_fails < config.auth_fails) {
|
||||
_ = termbox.tb_clear();
|
||||
|
||||
switch (config.animation) {
|
||||
.none => {},
|
||||
.doom => doom.draw(),
|
||||
.matrix => matrix.draw(),
|
||||
if (!animation_timed_out) {
|
||||
switch (config.animation) {
|
||||
.none => {},
|
||||
.doom => doom.draw(),
|
||||
.matrix => matrix.draw(),
|
||||
}
|
||||
}
|
||||
|
||||
if (config.bigclock and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: {
|
||||
if (config.bigclock != .none and buffer.box_height + (bigclock.HEIGHT + 2) * 2 < buffer.height) draw_big_clock: {
|
||||
const format = "%H:%M";
|
||||
const xo = buffer.width / 2 - @min(buffer.width, (format.len * (bigclock.WIDTH + 1))) / 2;
|
||||
const yo = (buffer.height - buffer.box_height) / 2 - bigclock.HEIGHT - 2;
|
||||
@@ -326,8 +420,8 @@ pub fn main() !void {
|
||||
};
|
||||
|
||||
for (clock_str, 0..) |c, i| {
|
||||
const clock_cell = bigclock.clockCell(animate, c, buffer.fg, buffer.bg);
|
||||
bigclock.alphaBlit(buffer.buffer, xo + i * (bigclock.WIDTH + 1), yo, buffer.width, buffer.height, clock_cell);
|
||||
const clock_cell = bigclock.clockCell(animate, c, buffer.fg, buffer.bg, config.bigclock);
|
||||
bigclock.alphaBlit(xo + i * (bigclock.WIDTH + 1), yo, buffer.width, buffer.height, clock_cell);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +429,8 @@ pub fn main() !void {
|
||||
|
||||
if (resolution_changed) {
|
||||
const coordinates = buffer.calculateComponentCoordinates();
|
||||
desktop.position(coordinates.x, coordinates.y + 2, coordinates.visible_length);
|
||||
info_line.label.position(coordinates.start_x, coordinates.y, coordinates.full_visible_length, null);
|
||||
session.label.position(coordinates.x, coordinates.y + 2, coordinates.visible_length, config.text_in_center);
|
||||
login.position(coordinates.x, coordinates.y + 4, coordinates.visible_length);
|
||||
password.position(coordinates.x, coordinates.y + 6, coordinates.visible_length);
|
||||
|
||||
@@ -343,12 +438,13 @@ pub fn main() !void {
|
||||
}
|
||||
|
||||
switch (active_input) {
|
||||
.session => desktop.handle(null, insert_mode),
|
||||
.info_line => info_line.label.handle(null, insert_mode),
|
||||
.session => session.label.handle(null, insert_mode),
|
||||
.login => login.handle(null, insert_mode) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
.password => password.handle(null, insert_mode) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
}
|
||||
|
||||
@@ -369,10 +465,10 @@ pub fn main() !void {
|
||||
buffer.drawLabel(lang.login, label_x, label_y + 4);
|
||||
buffer.drawLabel(lang.password, label_x, label_y + 6);
|
||||
|
||||
info_line.draw(buffer);
|
||||
info_line.label.draw();
|
||||
|
||||
if (!config.hide_key_hints) {
|
||||
var length: u64 = 0;
|
||||
var length: usize = 0;
|
||||
|
||||
buffer.drawLabel(config.shutdown_key, length, 0);
|
||||
length += config.shutdown_key.len + 1;
|
||||
@@ -394,7 +490,22 @@ pub fn main() !void {
|
||||
buffer.drawLabel(" ", length - 1, 0);
|
||||
|
||||
buffer.drawLabel(lang.sleep, length, 0);
|
||||
length += sleep_len + 1;
|
||||
}
|
||||
|
||||
buffer.drawLabel(config.brightness_down_key, length, 0);
|
||||
length += config.brightness_down_key.len + 1;
|
||||
buffer.drawLabel(" ", length - 1, 0);
|
||||
|
||||
buffer.drawLabel(lang.brightness_down, length, 0);
|
||||
length += brightness_down_len + 1;
|
||||
|
||||
buffer.drawLabel(config.brightness_up_key, length, 0);
|
||||
length += config.brightness_up_key.len + 1;
|
||||
buffer.drawLabel(" ", length - 1, 0);
|
||||
|
||||
buffer.drawLabel(lang.brightness_up, length, 0);
|
||||
length += brightness_up_len + 1;
|
||||
}
|
||||
|
||||
if (config.box_title) |title| {
|
||||
@@ -408,12 +519,12 @@ pub fn main() !void {
|
||||
|
||||
draw_lock_state: {
|
||||
const lock_state = interop.getLockState(config.console_dev) catch {
|
||||
try info_line.setText(lang.err_console_dev);
|
||||
try info_line.addMessage(lang.err_console_dev, config.error_bg, config.error_fg);
|
||||
break :draw_lock_state;
|
||||
};
|
||||
|
||||
var lock_state_x = buffer.width - @min(buffer.width, lang.numlock.len);
|
||||
const lock_state_y: u64 = if (config.clock != null) 1 else 0;
|
||||
const lock_state_y: usize = if (config.clock != null) 1 else 0;
|
||||
|
||||
if (lock_state.numlock) buffer.drawLabel(lang.numlock, lock_state_x, lock_state_y);
|
||||
|
||||
@@ -423,11 +534,9 @@ pub fn main() !void {
|
||||
}
|
||||
}
|
||||
|
||||
desktop.draw();
|
||||
session.label.draw();
|
||||
login.draw();
|
||||
password.drawMasked(config.asterisk);
|
||||
|
||||
update = animate;
|
||||
password.draw();
|
||||
} else {
|
||||
std.time.sleep(std.time.ns_per_ms * 10);
|
||||
update = buffer.cascade();
|
||||
@@ -441,199 +550,312 @@ pub fn main() !void {
|
||||
_ = termbox.tb_present();
|
||||
}
|
||||
|
||||
var timeout: i32 = -1;
|
||||
var timeout: u64 = std.math.maxInt(u64);
|
||||
|
||||
// Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for the event indefinitely instead
|
||||
if (animate) {
|
||||
// Calculate the maximum timeout based on current animations, or the (big) clock. If there's none, we wait for 100ms instead
|
||||
if (animate and !animation_timed_out) {
|
||||
timeout = config.min_refresh_delta;
|
||||
} else if (config.bigclock and config.clock == null) {
|
||||
var tv: std.c.timeval = undefined;
|
||||
_ = std.c.gettimeofday(&tv, null);
|
||||
|
||||
// check how long we have been running so we can turn off the animation
|
||||
var tv: interop.system_time.timeval = undefined;
|
||||
_ = interop.system_time.gettimeofday(&tv, null);
|
||||
|
||||
if (config.animation_timeout_sec > 0 and tv.tv_sec - tv_zero.tv_sec > config.animation_timeout_sec) {
|
||||
animation_timed_out = true;
|
||||
switch (config.animation) {
|
||||
.none => {},
|
||||
.doom => doom.deinit(),
|
||||
.matrix => matrix.deinit(),
|
||||
}
|
||||
}
|
||||
} else if (config.bigclock != .none and config.clock == null) {
|
||||
var tv: interop.system_time.timeval = undefined;
|
||||
_ = interop.system_time.gettimeofday(&tv, null);
|
||||
|
||||
timeout = @intCast((60 - @rem(tv.tv_sec, 60)) * 1000 - @divTrunc(tv.tv_usec, 1000) + 1);
|
||||
} else if (config.clock != null or auth_fails >= 10) {
|
||||
var tv: std.c.timeval = undefined;
|
||||
_ = std.c.gettimeofday(&tv, null);
|
||||
} else if (config.clock != null or auth_fails >= config.auth_fails) {
|
||||
var tv: interop.system_time.timeval = undefined;
|
||||
_ = interop.system_time.gettimeofday(&tv, null);
|
||||
|
||||
timeout = @intCast(1000 - @divTrunc(tv.tv_usec, 1000) + 1);
|
||||
}
|
||||
|
||||
const event_error = if (timeout == -1) termbox.tb_poll_event(&event) else termbox.tb_peek_event(&event, timeout);
|
||||
var skipEvent: bool = false;
|
||||
_ = wake.timedWait(timeout) catch .{};
|
||||
if (auth.asyncPamHandle) |handle| {
|
||||
var shared_err = try SharedError.init();
|
||||
defer shared_err.deinit();
|
||||
|
||||
if (event_error < 0 or event.type != termbox.TB_EVENT_KEY) continue;
|
||||
{
|
||||
const login_text = try allocator.dupeZ(u8, login.text.items);
|
||||
defer allocator.free(login_text);
|
||||
|
||||
switch (event.key) {
|
||||
termbox.TB_KEY_ESC => {
|
||||
if (config.vi_mode and insert_mode) {
|
||||
insert_mode = false;
|
||||
update = true;
|
||||
}
|
||||
},
|
||||
termbox.TB_KEY_F12...termbox.TB_KEY_F1 => {
|
||||
const pressed_key = 0xFFFF - event.key + 1;
|
||||
if (pressed_key == shutdown_key) {
|
||||
shutdown = true;
|
||||
run = false;
|
||||
} else if (pressed_key == restart_key) {
|
||||
restart = true;
|
||||
run = false;
|
||||
} else if (pressed_key == sleep_key) {
|
||||
if (config.sleep_cmd) |sleep_cmd| {
|
||||
var sleep = std.ChildProcess.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, allocator);
|
||||
_ = sleep.spawnAndWait() catch .{};
|
||||
}
|
||||
}
|
||||
},
|
||||
termbox.TB_KEY_CTRL_C => run = false,
|
||||
termbox.TB_KEY_CTRL_U => {
|
||||
if (active_input == .login) {
|
||||
login.clear();
|
||||
update = true;
|
||||
} else if (active_input == .password) {
|
||||
password.clear();
|
||||
update = true;
|
||||
}
|
||||
},
|
||||
termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => {
|
||||
active_input = switch (active_input) {
|
||||
.session, .login => .session,
|
||||
.password => .login,
|
||||
};
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => {
|
||||
active_input = switch (active_input) {
|
||||
.session => .login,
|
||||
.login, .password => .password,
|
||||
};
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_TAB => {
|
||||
active_input = switch (active_input) {
|
||||
.session => .login,
|
||||
.login => .password,
|
||||
.password => .session,
|
||||
};
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_BACK_TAB => {
|
||||
active_input = switch (active_input) {
|
||||
.session => .password,
|
||||
.login => .session,
|
||||
.password => .login,
|
||||
try info_line.addMessage(lang.authenticating, config.error_bg, config.error_fg);
|
||||
InfoLine.clearRendered(allocator, buffer) catch {
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
};
|
||||
info_line.label.draw();
|
||||
_ = termbox.tb_present();
|
||||
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_ENTER => {
|
||||
if (config.save) save_last_settings: {
|
||||
var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings;
|
||||
defer file.close();
|
||||
|
||||
const save_data = Save{
|
||||
.user = login.text.items,
|
||||
.session_index = desktop.current,
|
||||
session_pid = try std.posix.fork();
|
||||
if (session_pid == 0) {
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| {
|
||||
shared_err.writeError(err);
|
||||
std.process.exit(1);
|
||||
};
|
||||
ini.writeFromStruct(save_data, file.writer(), null) catch break :save_last_settings;
|
||||
|
||||
std.process.exit(0);
|
||||
}
|
||||
_ = std.posix.waitpid(session_pid, 0);
|
||||
|
||||
var shared_err = try SharedError.init();
|
||||
defer shared_err.deinit();
|
||||
session_pid = -1;
|
||||
}
|
||||
|
||||
{
|
||||
const login_text = try allocator.dupeZ(u8, login.text.items);
|
||||
defer allocator.free(login_text);
|
||||
const password_text = try allocator.dupeZ(u8, password.text.items);
|
||||
defer allocator.free(password_text);
|
||||
auth.asyncPamHandle = null;
|
||||
|
||||
try info_line.setText(lang.authenticating);
|
||||
InfoLine.clearRendered(allocator, buffer) catch {};
|
||||
info_line.draw(buffer);
|
||||
const auth_err = shared_err.readError();
|
||||
if (auth_err) |err| {
|
||||
try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg);
|
||||
// We don't want to start login back in instantly. The user must first edit
|
||||
// the login/desktop in order to login. Only in case of a failed login (so not a logout)
|
||||
// should we automatically restart it.
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
|
||||
} else {
|
||||
try info_line.addMessage(lang.logout, config.error_bg, config.error_fg);
|
||||
}
|
||||
|
||||
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios);
|
||||
_ = termbox.tb_clear();
|
||||
_ = termbox.tb_present();
|
||||
|
||||
update = true;
|
||||
|
||||
_ = termbox.tb_set_cursor(0, 0);
|
||||
_ = termbox.tb_present();
|
||||
} else if (event) |e| {
|
||||
defer doneEvent.post();
|
||||
update = timeout != -1;
|
||||
skipEvent = event_error < 0;
|
||||
if (skipEvent or e.type != termbox.TB_EVENT_KEY) continue;
|
||||
|
||||
switch (e.key) {
|
||||
termbox.TB_KEY_ESC => {
|
||||
if (config.vi_mode and insert_mode) {
|
||||
insert_mode = false;
|
||||
update = true;
|
||||
}
|
||||
},
|
||||
termbox.TB_KEY_F12...termbox.TB_KEY_F1 => {
|
||||
const pressed_key = 0xFFFF - e.key + 1;
|
||||
if (pressed_key == shutdown_key) {
|
||||
shutdown = true;
|
||||
run = false;
|
||||
} else if (pressed_key == restart_key) {
|
||||
restart = true;
|
||||
run = false;
|
||||
} else if (pressed_key == sleep_key) {
|
||||
if (config.sleep_cmd) |sleep_cmd| {
|
||||
var sleep = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", sleep_cmd }, allocator);
|
||||
_ = sleep.spawnAndWait() catch .{};
|
||||
}
|
||||
} else if (pressed_key == brightness_down_key) {
|
||||
var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_down_cmd }, allocator);
|
||||
_ = brightness.spawnAndWait() catch .{};
|
||||
} else if (pressed_key == brightness_up_key) {
|
||||
var brightness = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", config.brightness_up_cmd }, allocator);
|
||||
_ = brightness.spawnAndWait() catch .{};
|
||||
}
|
||||
},
|
||||
termbox.TB_KEY_CTRL_C => run = false,
|
||||
termbox.TB_KEY_CTRL_U => {
|
||||
if (active_input == .login) {
|
||||
login.clear();
|
||||
update = true;
|
||||
} else if (active_input == .password) {
|
||||
password.clear();
|
||||
update = true;
|
||||
}
|
||||
},
|
||||
termbox.TB_KEY_CTRL_K, termbox.TB_KEY_ARROW_UP => {
|
||||
active_input = switch (active_input) {
|
||||
.session, .info_line => .info_line,
|
||||
.login => .session,
|
||||
.password => .login,
|
||||
};
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_CTRL_J, termbox.TB_KEY_ARROW_DOWN => {
|
||||
if (active_input == .login) {
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
|
||||
}
|
||||
active_input = switch (active_input) {
|
||||
.info_line => .session,
|
||||
.session => .login,
|
||||
.login, .password => .password,
|
||||
};
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_TAB => {
|
||||
if (active_input == .login) {
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
|
||||
}
|
||||
active_input = switch (active_input) {
|
||||
.info_line => .session,
|
||||
.session => .login,
|
||||
.login => .password,
|
||||
.password => .info_line,
|
||||
};
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_BACK_TAB => {
|
||||
if (active_input == .info_line) {
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
|
||||
}
|
||||
active_input = switch (active_input) {
|
||||
.info_line => .password,
|
||||
.session => .info_line,
|
||||
.login => .session,
|
||||
.password => .login,
|
||||
};
|
||||
|
||||
update = true;
|
||||
},
|
||||
termbox.TB_KEY_ENTER => {
|
||||
try info_line.addMessage(lang.authenticating, config.bg, config.fg);
|
||||
InfoLine.clearRendered(allocator, buffer) catch {
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
};
|
||||
info_line.label.draw();
|
||||
_ = termbox.tb_present();
|
||||
|
||||
session_pid = try std.posix.fork();
|
||||
if (session_pid == 0) {
|
||||
const current_environment = desktop.environments.items[desktop.current];
|
||||
auth.authenticate(config, current_environment, login_text, password_text) catch |err| {
|
||||
shared_err.writeError(err);
|
||||
std.process.exit(1);
|
||||
if (config.save) save_last_settings: {
|
||||
var file = std.fs.cwd().createFile(save_path, .{}) catch break :save_last_settings;
|
||||
defer file.close();
|
||||
|
||||
const save_data = Save{
|
||||
.user = login.text.items,
|
||||
.session_index = session.label.current,
|
||||
};
|
||||
std.process.exit(0);
|
||||
ini.writeFromStruct(save_data, file.writer(), null, true, .{}) catch break :save_last_settings;
|
||||
|
||||
// Delete previous save file if it exists
|
||||
if (migrator.maybe_save_file) |path| std.fs.cwd().deleteFile(path) catch {};
|
||||
}
|
||||
|
||||
_ = std.posix.waitpid(session_pid, 0);
|
||||
session_pid = -1;
|
||||
}
|
||||
var shared_err = try SharedError.init();
|
||||
defer shared_err.deinit();
|
||||
|
||||
const auth_err = shared_err.readError();
|
||||
if (auth_err) |err| {
|
||||
auth_fails += 1;
|
||||
active_input = .password;
|
||||
try info_line.setText(getAuthErrorMsg(err, lang));
|
||||
if (config.clear_password or err != error.PamAuthError) password.clear();
|
||||
} else {
|
||||
password.clear();
|
||||
try info_line.setText(lang.logout);
|
||||
}
|
||||
{
|
||||
const login_text = try allocator.dupeZ(u8, login.text.items);
|
||||
defer allocator.free(login_text);
|
||||
const password_text = try allocator.dupeZ(u8, password.text.items);
|
||||
defer allocator.free(password_text);
|
||||
|
||||
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios);
|
||||
if (auth_fails < 10) {
|
||||
_ = termbox.tb_clear();
|
||||
// Give up control on the TTY
|
||||
_ = termbox.tb_shutdown();
|
||||
|
||||
session_pid = try std.posix.fork();
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
if (auth.authenticate(config, login_text, password_text, current_environment)) |handle| {
|
||||
if (session_pid == 0) {
|
||||
auth.finaliseAuth(config, current_environment, handle, login_text) catch |err| {
|
||||
shared_err.writeError(err);
|
||||
std.process.exit(1);
|
||||
};
|
||||
|
||||
std.process.exit(0);
|
||||
}
|
||||
_ = std.posix.waitpid(session_pid, 0);
|
||||
} else |err| {
|
||||
shared_err.writeError(err);
|
||||
}
|
||||
|
||||
session_pid = -1;
|
||||
}
|
||||
|
||||
// Take back control of the TTY
|
||||
_ = termbox.tb_init();
|
||||
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL);
|
||||
|
||||
const auth_err = shared_err.readError();
|
||||
if (auth_err) |err| {
|
||||
auth_fails += 1;
|
||||
active_input = .password;
|
||||
try info_line.addMessage(getAuthErrorMsg(err, lang), config.error_bg, config.error_fg);
|
||||
if (config.clear_password or err != error.PamAuthError) password.clear();
|
||||
} else {
|
||||
if (config.logout_cmd) |logout_cmd| {
|
||||
var logout_process = std.process.Child.init(&[_][]const u8{ "/bin/sh", "-c", logout_cmd }, allocator);
|
||||
_ = logout_process.spawnAndWait() catch .{};
|
||||
}
|
||||
|
||||
password.clear();
|
||||
try info_line.addMessage(lang.logout, config.bg, config.fg);
|
||||
}
|
||||
|
||||
try std.posix.tcsetattr(std.posix.STDIN_FILENO, .FLUSH, tb_termios);
|
||||
if (auth_fails < config.auth_fails) _ = termbox.tb_clear();
|
||||
|
||||
update = true;
|
||||
|
||||
// Restore the cursor
|
||||
_ = termbox.tb_set_cursor(0, 0);
|
||||
_ = termbox.tb_present();
|
||||
}
|
||||
|
||||
update = true;
|
||||
|
||||
var restore_cursor = std.ChildProcess.init(&[_][]const u8{ "/bin/sh", "-c", config.term_restore_cursor_cmd }, allocator);
|
||||
_ = restore_cursor.spawnAndWait() catch .{};
|
||||
},
|
||||
else => {
|
||||
if (!insert_mode) {
|
||||
switch (event.ch) {
|
||||
'k' => {
|
||||
active_input = switch (active_input) {
|
||||
.session, .login => .session,
|
||||
.password => .login,
|
||||
};
|
||||
update = true;
|
||||
continue;
|
||||
},
|
||||
'j' => {
|
||||
active_input = switch (active_input) {
|
||||
.session => .login,
|
||||
.login, .password => .password,
|
||||
};
|
||||
update = true;
|
||||
continue;
|
||||
},
|
||||
'i' => {
|
||||
insert_mode = true;
|
||||
update = true;
|
||||
continue;
|
||||
},
|
||||
else => {},
|
||||
},
|
||||
else => {
|
||||
if (!insert_mode) {
|
||||
switch (e.ch) {
|
||||
'k' => {
|
||||
active_input = switch (active_input) {
|
||||
.session, .info_line => .info_line,
|
||||
.login => .session,
|
||||
.password => .login,
|
||||
};
|
||||
update = true;
|
||||
continue;
|
||||
},
|
||||
'j' => {
|
||||
if (active_input == .login) {
|
||||
const current_environment = session.label.list.items[session.label.current];
|
||||
try auth.startAutomaticLogin(allocator, config, login, current_environment, &wake);
|
||||
}
|
||||
active_input = switch (active_input) {
|
||||
.info_line => .session,
|
||||
.session => .login,
|
||||
.login, .password => .password,
|
||||
};
|
||||
update = true;
|
||||
continue;
|
||||
},
|
||||
'i' => {
|
||||
insert_mode = true;
|
||||
update = true;
|
||||
continue;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (active_input) {
|
||||
.session => desktop.handle(&event, insert_mode),
|
||||
.login => login.handle(&event, insert_mode) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
},
|
||||
.password => password.handle(&event, insert_mode) catch {
|
||||
try info_line.setText(lang.err_alloc);
|
||||
},
|
||||
}
|
||||
update = true;
|
||||
},
|
||||
switch (active_input) {
|
||||
.info_line => info_line.label.handle(&event.?, insert_mode),
|
||||
.session => session.label.handle(&event.?, insert_mode),
|
||||
.login => login.handle(&event.?, insert_mode) catch {
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
.password => password.handle(&event.?, insert_mode) catch {
|
||||
try info_line.addMessage(lang.err_alloc, config.error_bg, config.error_fg);
|
||||
},
|
||||
}
|
||||
update = true;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shutdown) {
|
||||
return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.shutdown_cmd });
|
||||
} else if (restart) {
|
||||
return std.process.execv(allocator, &[_][]const u8{ "/bin/sh", "-c", config.restart_cmd });
|
||||
}
|
||||
}
|
||||
|
||||
fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {
|
||||
@@ -641,7 +863,6 @@ fn getAuthErrorMsg(err: anyerror, lang: Lang) []const u8 {
|
||||
error.GetPasswordNameFailed => lang.err_pwnam,
|
||||
error.GetEnvListFailed => lang.err_envlist,
|
||||
error.XauthFailed => lang.err_xauth,
|
||||
error.McookieFailed => lang.err_mcookie,
|
||||
error.XcbConnectionFailed => lang.err_xcb_conn,
|
||||
error.GroupInitializationFailed => lang.err_user_init,
|
||||
error.SetUserGidFailed => lang.err_user_gid,
|
||||
|
||||
@@ -4,19 +4,19 @@ const interop = @import("../interop.zig");
|
||||
const utils = @import("utils.zig");
|
||||
const Config = @import("../config/Config.zig");
|
||||
|
||||
const Random = std.rand.Random;
|
||||
const Random = std.Random;
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
const TerminalBuffer = @This();
|
||||
|
||||
random: Random,
|
||||
width: u64,
|
||||
height: u64,
|
||||
width: usize,
|
||||
height: usize,
|
||||
buffer: [*]termbox.tb_cell,
|
||||
fg: u8,
|
||||
bg: u8,
|
||||
border_fg: u8,
|
||||
fg: u16,
|
||||
bg: u16,
|
||||
border_fg: u16,
|
||||
box_chars: struct {
|
||||
left_up: u32,
|
||||
left_down: u32,
|
||||
@@ -27,19 +27,17 @@ box_chars: struct {
|
||||
left: u32,
|
||||
right: u32,
|
||||
},
|
||||
labels_max_length: u64,
|
||||
box_x: u64,
|
||||
box_y: u64,
|
||||
box_width: u64,
|
||||
box_height: u64,
|
||||
labels_max_length: usize,
|
||||
box_x: usize,
|
||||
box_y: usize,
|
||||
box_width: usize,
|
||||
box_height: usize,
|
||||
margin_box_v: u8,
|
||||
margin_box_h: u8,
|
||||
|
||||
pub fn init(config: Config, labels_max_length: u64) TerminalBuffer {
|
||||
var prng = std.rand.Isaac64.init(@intCast(std.time.timestamp()));
|
||||
|
||||
pub fn init(config: Config, labels_max_length: usize, random: Random) TerminalBuffer {
|
||||
return .{
|
||||
.random = prng.random(),
|
||||
.random = random,
|
||||
.width = @intCast(termbox.tb_width()),
|
||||
.height = @intCast(termbox.tb_height()),
|
||||
.buffer = termbox.tb_cell_buffer(),
|
||||
@@ -76,27 +74,30 @@ pub fn init(config: Config, labels_max_length: u64) TerminalBuffer {
|
||||
}
|
||||
|
||||
pub fn cascade(self: TerminalBuffer) bool {
|
||||
var changes = false;
|
||||
|
||||
var changed = false;
|
||||
var y = self.height - 2;
|
||||
|
||||
while (y > 0) : (y -= 1) {
|
||||
for (0..self.width) |x| {
|
||||
const c: u8 = @truncate(self.buffer[(y - 1) * self.width + x].ch);
|
||||
if (std.ascii.isWhitespace(c)) continue;
|
||||
const cell = self.buffer[(y - 1) * self.width + x];
|
||||
const cell_under = self.buffer[y * self.width + x];
|
||||
|
||||
const c_under: u8 = @truncate(self.buffer[y * self.width + x].ch);
|
||||
if (!std.ascii.isWhitespace(c_under)) continue;
|
||||
const char: u8 = @truncate(cell.ch);
|
||||
if (std.ascii.isWhitespace(char)) continue;
|
||||
|
||||
changes = true;
|
||||
const char_under: u8 = @truncate(cell_under.ch);
|
||||
if (!std.ascii.isWhitespace(char_under)) continue;
|
||||
|
||||
changed = true;
|
||||
|
||||
if ((self.random.int(u16) % 10) > 7) continue;
|
||||
|
||||
self.buffer[y * self.width + x] = self.buffer[(y - 1) * self.width + x];
|
||||
self.buffer[(y - 1) * self.width + x].ch = ' ';
|
||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
|
||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y - 1), ' ', cell_under.fg, cell_under.bg);
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
return changed;
|
||||
}
|
||||
|
||||
pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool) void {
|
||||
@@ -119,16 +120,16 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool)
|
||||
var c2 = utils.initCell(self.box_chars.bottom, self.border_fg, self.bg);
|
||||
|
||||
for (0..self.box_width) |i| {
|
||||
_ = utils.putCell(@intCast(x1 + i), @intCast(y1 - 1), &c1);
|
||||
_ = utils.putCell(@intCast(x1 + i), @intCast(y2), &c2);
|
||||
utils.putCell(x1 + i, y1 - 1, c1);
|
||||
utils.putCell(x1 + i, y2, c2);
|
||||
}
|
||||
|
||||
c1.ch = self.box_chars.left;
|
||||
c2.ch = self.box_chars.right;
|
||||
|
||||
for (0..self.box_height) |i| {
|
||||
_ = utils.putCell(@intCast(x1 - 1), @intCast(y1 + i), &c1);
|
||||
_ = utils.putCell(@intCast(x2), @intCast(y1 + i), &c2);
|
||||
utils.putCell(x1 - 1, y1 + i, c1);
|
||||
utils.putCell(x2, y1 + i, c2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,40 +138,50 @@ pub fn drawBoxCenter(self: *TerminalBuffer, show_borders: bool, blank_box: bool)
|
||||
|
||||
for (0..self.box_height) |y| {
|
||||
for (0..self.box_width) |x| {
|
||||
_ = utils.putCell(@intCast(x1 + x), @intCast(y1 + y), &blank);
|
||||
utils.putCell(x1 + x, y1 + y, blank);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculateComponentCoordinates(self: TerminalBuffer) struct {
|
||||
x: u64,
|
||||
y: u64,
|
||||
visible_length: u64,
|
||||
start_x: usize,
|
||||
x: usize,
|
||||
y: usize,
|
||||
full_visible_length: usize,
|
||||
visible_length: usize,
|
||||
} {
|
||||
const x = self.box_x + self.margin_box_h + self.labels_max_length + 1;
|
||||
const start_x = self.box_x + self.margin_box_h;
|
||||
const x = start_x + self.labels_max_length + 1;
|
||||
const y = self.box_y + self.margin_box_v;
|
||||
const full_visible_length = self.box_x + self.box_width - self.margin_box_h - start_x;
|
||||
const visible_length = self.box_x + self.box_width - self.margin_box_h - x;
|
||||
|
||||
return .{
|
||||
.start_x = start_x,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.full_visible_length = full_visible_length,
|
||||
.visible_length = visible_length,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64) void {
|
||||
pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) void {
|
||||
drawColorLabel(text, x, y, self.fg, self.bg);
|
||||
}
|
||||
|
||||
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u16, bg: u16) void {
|
||||
const yc: c_int = @intCast(y);
|
||||
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
||||
var utf8 = utf8view.iterator();
|
||||
|
||||
var i = x;
|
||||
while (utf8.nextCodepoint()) |codepoint| : (i += 1) {
|
||||
_ = termbox.tb_set_cell(@intCast(i), yc, codepoint, self.fg, self.bg);
|
||||
_ = termbox.tb_set_cell(@intCast(i), yc, codepoint, fg, bg);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64, max_length: u64) void {
|
||||
pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize, max_length: usize) void {
|
||||
const yc: c_int = @intCast(y);
|
||||
const utf8view = std.unicode.Utf8View.init(text) catch return;
|
||||
var utf8 = utf8view.iterator();
|
||||
@@ -182,9 +193,7 @@ pub fn drawConfinedLabel(self: TerminalBuffer, text: []const u8, x: u64, y: u64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: u64, y: u64, length: u64) void {
|
||||
const yc: c_int = @intCast(y);
|
||||
pub fn drawCharMultiple(self: TerminalBuffer, char: u8, x: usize, y: usize, length: usize) void {
|
||||
const cell = utils.initCell(char, self.fg, self.bg);
|
||||
|
||||
for (0..length) |xx| _ = utils.putCell(@intCast(x + xx), yc, &cell);
|
||||
for (0..length) |xx| utils.putCell(x + xx, y, cell);
|
||||
}
|
||||
|
||||
@@ -1,223 +0,0 @@
|
||||
const std = @import("std");
|
||||
const enums = @import("../../enums.zig");
|
||||
const interop = @import("../../interop.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const Ini = @import("zigini").Ini;
|
||||
const Lang = @import("../../config/Lang.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const EnvironmentList = std.ArrayList(Environment);
|
||||
|
||||
const DisplayServer = enums.DisplayServer;
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
const Desktop = @This();
|
||||
|
||||
pub const Environment = struct {
|
||||
entry_ini: ?Ini(Entry) = null,
|
||||
name: [:0]const u8 = "",
|
||||
xdg_session_desktop: [:0]const u8 = "",
|
||||
xdg_desktop_names: ?[:0]const u8 = "",
|
||||
cmd: []const u8 = "",
|
||||
specifier: []const u8 = "",
|
||||
display_server: DisplayServer = .wayland,
|
||||
};
|
||||
|
||||
const DesktopEntry = struct {
|
||||
Exec: []const u8 = "",
|
||||
Name: [:0]const u8 = "",
|
||||
DesktopNames: ?[]const u8 = null,
|
||||
};
|
||||
|
||||
pub const Entry = struct { @"Desktop Entry": DesktopEntry = DesktopEntry{} };
|
||||
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
environments: EnvironmentList,
|
||||
current: u64,
|
||||
visible_length: u64,
|
||||
x: u64,
|
||||
y: u64,
|
||||
lang: Lang,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64, lang: Lang) !Desktop {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.buffer = buffer,
|
||||
.environments = try EnvironmentList.initCapacity(allocator, max_length),
|
||||
.current = 0,
|
||||
.visible_length = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.lang = lang,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Desktop) void {
|
||||
for (self.environments.items) |*environment| {
|
||||
if (environment.entry_ini) |*entry_ini| entry_ini.deinit();
|
||||
if (environment.xdg_desktop_names) |desktop_name| self.allocator.free(desktop_name);
|
||||
self.allocator.free(environment.xdg_session_desktop);
|
||||
}
|
||||
|
||||
self.environments.deinit();
|
||||
}
|
||||
|
||||
pub fn position(self: *Desktop, x: u64, y: u64, visible_length: u64) void {
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
self.visible_length = visible_length;
|
||||
}
|
||||
|
||||
pub fn addEnvironment(self: *Desktop, entry: DesktopEntry, xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
|
||||
var xdg_desktop_names: ?[:0]const u8 = null;
|
||||
if (entry.DesktopNames) |desktop_names| {
|
||||
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
|
||||
for (desktop_names_z) |*c| {
|
||||
if (c.* == ';') c.* = ':';
|
||||
}
|
||||
xdg_desktop_names = desktop_names_z;
|
||||
}
|
||||
|
||||
errdefer {
|
||||
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
|
||||
}
|
||||
|
||||
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
|
||||
errdefer self.allocator.free(session_desktop);
|
||||
|
||||
try self.environments.append(.{
|
||||
.entry_ini = null,
|
||||
.name = entry.Name,
|
||||
.xdg_session_desktop = session_desktop,
|
||||
.xdg_desktop_names = xdg_desktop_names,
|
||||
.cmd = entry.Exec,
|
||||
.specifier = switch (display_server) {
|
||||
.wayland => self.lang.wayland,
|
||||
.x11 => self.lang.x11,
|
||||
else => self.lang.other,
|
||||
},
|
||||
.display_server = display_server,
|
||||
});
|
||||
|
||||
self.current = self.environments.items.len - 1;
|
||||
}
|
||||
|
||||
pub fn addEnvironmentWithIni(self: *Desktop, entry_ini: Ini(Entry), xdg_session_desktop: []const u8, display_server: DisplayServer) !void {
|
||||
const entry = entry_ini.data.@"Desktop Entry";
|
||||
var xdg_desktop_names: ?[:0]const u8 = null;
|
||||
if (entry.DesktopNames) |desktop_names| {
|
||||
const desktop_names_z = try self.allocator.dupeZ(u8, desktop_names);
|
||||
for (desktop_names_z) |*c| {
|
||||
if (c.* == ';') c.* = ':';
|
||||
}
|
||||
xdg_desktop_names = desktop_names_z;
|
||||
}
|
||||
|
||||
errdefer {
|
||||
if (xdg_desktop_names) |desktop_names| self.allocator.free(desktop_names);
|
||||
}
|
||||
|
||||
const session_desktop = try self.allocator.dupeZ(u8, xdg_session_desktop);
|
||||
errdefer self.allocator.free(session_desktop);
|
||||
|
||||
try self.environments.append(.{
|
||||
.entry_ini = entry_ini,
|
||||
.name = entry.Name,
|
||||
.xdg_session_desktop = session_desktop,
|
||||
.xdg_desktop_names = xdg_desktop_names,
|
||||
.cmd = entry.Exec,
|
||||
.specifier = switch (display_server) {
|
||||
.wayland => self.lang.wayland,
|
||||
.x11 => self.lang.x11,
|
||||
else => self.lang.other,
|
||||
},
|
||||
.display_server = display_server,
|
||||
});
|
||||
|
||||
self.current = self.environments.items.len - 1;
|
||||
}
|
||||
|
||||
pub fn crawl(self: *Desktop, path: []const u8, display_server: DisplayServer) !void {
|
||||
var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return;
|
||||
defer iterable_directory.close();
|
||||
|
||||
var iterator = iterable_directory.iterate();
|
||||
while (try iterator.next()) |item| {
|
||||
if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue;
|
||||
|
||||
const entry_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ path, item.name });
|
||||
defer self.allocator.free(entry_path);
|
||||
var entry_ini = Ini(Entry).init(self.allocator);
|
||||
_ = try entry_ini.readFileToStruct(entry_path);
|
||||
errdefer entry_ini.deinit();
|
||||
|
||||
var xdg_session_desktop: []const u8 = undefined;
|
||||
const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
|
||||
if (maybe_desktop_names) |desktop_names| {
|
||||
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
|
||||
} else {
|
||||
// if DesktopNames is empty, we'll take the name of the session file
|
||||
xdg_session_desktop = std.fs.path.stem(item.name);
|
||||
}
|
||||
|
||||
try self.addEnvironmentWithIni(entry_ini, xdg_session_desktop, display_server);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle(self: *Desktop, maybe_event: ?*termbox.tb_event, insert_mode: bool) void {
|
||||
if (maybe_event) |event| blk: {
|
||||
if (event.type != termbox.TB_EVENT_KEY) break :blk;
|
||||
|
||||
switch (event.key) {
|
||||
termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(),
|
||||
termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(),
|
||||
else => {
|
||||
if (!insert_mode) {
|
||||
switch (event.ch) {
|
||||
'h' => self.goLeft(),
|
||||
'l' => self.goRight(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_ = termbox.tb_set_cursor(@intCast(self.x + 2), @intCast(self.y));
|
||||
}
|
||||
|
||||
pub fn draw(self: Desktop) void {
|
||||
const environment = self.environments.items[self.current];
|
||||
|
||||
const length = @min(environment.name.len, self.visible_length - 3);
|
||||
if (length == 0) return;
|
||||
|
||||
const x = self.buffer.box_x + self.buffer.margin_box_h;
|
||||
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
|
||||
self.buffer.drawLabel(environment.specifier, x, y);
|
||||
|
||||
_ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
|
||||
_ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
|
||||
|
||||
self.buffer.drawLabel(environment.name, self.x + 2, self.y);
|
||||
}
|
||||
|
||||
fn goLeft(self: *Desktop) void {
|
||||
if (self.current == 0) {
|
||||
self.current = self.environments.items.len - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current -= 1;
|
||||
}
|
||||
|
||||
fn goRight(self: *Desktop) void {
|
||||
if (self.current == self.environments.items.len - 1) {
|
||||
self.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current += 1;
|
||||
}
|
||||
@@ -1,28 +1,46 @@
|
||||
const std = @import("std");
|
||||
const utils = @import("../utils.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const generic = @import("generic.zig");
|
||||
const utils = @import("../utils.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const MessageLabel = generic.CyclableLabel(Message);
|
||||
|
||||
const InfoLine = @This();
|
||||
|
||||
text: []const u8 = "",
|
||||
width: u8 = 0,
|
||||
const Message = struct {
|
||||
width: u8,
|
||||
text: []const u8,
|
||||
bg: u16,
|
||||
fg: u16,
|
||||
};
|
||||
|
||||
pub fn setText(self: *InfoLine, text: []const u8) !void {
|
||||
self.width = if (text.len > 0) try utils.strWidth(text) else 0;
|
||||
self.text = text;
|
||||
label: MessageLabel,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer) InfoLine {
|
||||
return .{
|
||||
.label = MessageLabel.init(allocator, buffer, drawItem),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw(self: InfoLine, buffer: TerminalBuffer) void {
|
||||
if (self.width > 0 and buffer.box_width > self.width) {
|
||||
const label_y = buffer.box_y + buffer.margin_box_v;
|
||||
const x = buffer.box_x + ((buffer.box_width - self.width) / 2);
|
||||
|
||||
buffer.drawLabel(self.text, x, label_y);
|
||||
}
|
||||
pub fn deinit(self: InfoLine) void {
|
||||
self.label.deinit();
|
||||
}
|
||||
|
||||
pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void {
|
||||
// draw over the area
|
||||
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u16, fg: u16) !void {
|
||||
if (text.len == 0) return;
|
||||
|
||||
try self.label.addItem(.{
|
||||
.width = try utils.strWidth(text),
|
||||
.text = text,
|
||||
.bg = bg,
|
||||
.fg = fg,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn clearRendered(allocator: Allocator, buffer: TerminalBuffer) !void {
|
||||
// Draw over the area
|
||||
const y = buffer.box_y + buffer.margin_box_v;
|
||||
const spaces = try allocator.alloc(u8, buffer.box_width);
|
||||
defer allocator.free(spaces);
|
||||
@@ -31,3 +49,13 @@ pub fn clearRendered(allocator: std.mem.Allocator, buffer: TerminalBuffer) !void
|
||||
|
||||
buffer.drawLabel(spaces, buffer.box_x, y);
|
||||
}
|
||||
|
||||
fn drawItem(label: *MessageLabel, message: Message, _: usize, _: usize) bool {
|
||||
if (message.width == 0 or label.buffer.box_width <= message.width) return false;
|
||||
|
||||
const x = label.buffer.box_x + ((label.buffer.box_width - message.width) / 2);
|
||||
label.first_char_x = x + message.width;
|
||||
|
||||
TerminalBuffer.drawColorLabel(message.text, x, label.y, message.fg, message.bg);
|
||||
return true;
|
||||
}
|
||||
|
||||
142
src/tui/components/Session.zig
Normal file
142
src/tui/components/Session.zig
Normal file
@@ -0,0 +1,142 @@
|
||||
const std = @import("std");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
const enums = @import("../../enums.zig");
|
||||
const generic = @import("generic.zig");
|
||||
const Ini = @import("zigini").Ini;
|
||||
const Lang = @import("../../config/Lang.zig");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const DisplayServer = enums.DisplayServer;
|
||||
|
||||
const EnvironmentLabel = generic.CyclableLabel(Environment);
|
||||
|
||||
const Session = @This();
|
||||
|
||||
pub const Environment = struct {
|
||||
entry_ini: ?Ini(Entry) = null,
|
||||
name: [:0]const u8 = "",
|
||||
xdg_session_desktop: ?[:0]const u8 = null,
|
||||
xdg_desktop_names: ?[:0]const u8 = null,
|
||||
cmd: []const u8 = "",
|
||||
specifier: []const u8 = "",
|
||||
display_server: DisplayServer = .wayland,
|
||||
};
|
||||
|
||||
const DesktopEntry = struct {
|
||||
Exec: []const u8 = "",
|
||||
Name: [:0]const u8 = "",
|
||||
DesktopNames: ?[:0]u8 = null,
|
||||
};
|
||||
|
||||
pub const Entry = struct { @"Desktop Entry": DesktopEntry = .{} };
|
||||
|
||||
label: EnvironmentLabel,
|
||||
lang: Lang,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, lang: Lang) Session {
|
||||
return .{
|
||||
.label = EnvironmentLabel.init(allocator, buffer, drawItem),
|
||||
.lang = lang,
|
||||
};
|
||||
}
|
||||
|
||||
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, entry: DesktopEntry, xdg_session_desktop: ?[:0]const u8, display_server: DisplayServer) !void {
|
||||
var xdg_desktop_names: ?[:0]const u8 = null;
|
||||
if (entry.DesktopNames) |desktop_names| {
|
||||
for (desktop_names) |*c| {
|
||||
if (c.* == ';') c.* = ':';
|
||||
}
|
||||
xdg_desktop_names = desktop_names;
|
||||
}
|
||||
|
||||
try self.label.addItem(.{
|
||||
.entry_ini = null,
|
||||
.name = entry.Name,
|
||||
.xdg_session_desktop = xdg_session_desktop,
|
||||
.xdg_desktop_names = xdg_desktop_names,
|
||||
.cmd = entry.Exec,
|
||||
.specifier = switch (display_server) {
|
||||
.wayland => self.lang.wayland,
|
||||
.x11 => self.lang.x11,
|
||||
else => self.lang.other,
|
||||
},
|
||||
.display_server = display_server,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addEnvironmentWithIni(self: *Session, entry_ini: Ini(Entry), xdg_session_desktop: ?[:0]const u8, display_server: DisplayServer) !void {
|
||||
const entry = entry_ini.data.@"Desktop Entry";
|
||||
var xdg_desktop_names: ?[:0]const u8 = null;
|
||||
if (entry.DesktopNames) |desktop_names| {
|
||||
for (desktop_names) |*c| {
|
||||
if (c.* == ';') c.* = ':';
|
||||
}
|
||||
xdg_desktop_names = desktop_names;
|
||||
}
|
||||
|
||||
try self.label.addItem(.{
|
||||
.entry_ini = entry_ini,
|
||||
.name = entry.Name,
|
||||
.xdg_session_desktop = xdg_session_desktop,
|
||||
.xdg_desktop_names = xdg_desktop_names,
|
||||
.cmd = entry.Exec,
|
||||
.specifier = switch (display_server) {
|
||||
.wayland => self.lang.wayland,
|
||||
.x11 => self.lang.x11,
|
||||
else => self.lang.other,
|
||||
},
|
||||
.display_server = display_server,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn crawl(self: *Session, path: []const u8, display_server: DisplayServer) !void {
|
||||
var iterable_directory = std.fs.openDirAbsolute(path, .{ .iterate = true }) catch return;
|
||||
defer iterable_directory.close();
|
||||
|
||||
var iterator = iterable_directory.iterate();
|
||||
while (try iterator.next()) |item| {
|
||||
if (!std.mem.eql(u8, std.fs.path.extension(item.name), ".desktop")) continue;
|
||||
|
||||
const entry_path = try std.fmt.allocPrint(self.label.allocator, "{s}/{s}", .{ path, item.name });
|
||||
defer self.label.allocator.free(entry_path);
|
||||
var entry_ini = Ini(Entry).init(self.label.allocator);
|
||||
_ = try entry_ini.readFileToStruct(entry_path, "#", null);
|
||||
errdefer entry_ini.deinit();
|
||||
|
||||
var xdg_session_desktop: []const u8 = undefined;
|
||||
const maybe_desktop_names = entry_ini.data.@"Desktop Entry".DesktopNames;
|
||||
if (maybe_desktop_names) |desktop_names| {
|
||||
xdg_session_desktop = std.mem.sliceTo(desktop_names, ';');
|
||||
} else {
|
||||
// if DesktopNames is empty, we'll take the name of the session file
|
||||
xdg_session_desktop = std.fs.path.stem(item.name);
|
||||
}
|
||||
|
||||
const session_desktop = try self.label.allocator.dupeZ(u8, xdg_session_desktop);
|
||||
errdefer self.label.allocator.free(session_desktop);
|
||||
|
||||
try self.addEnvironmentWithIni(entry_ini, session_desktop, display_server);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -13,15 +13,17 @@ const Text = @This();
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
text: DynamicString,
|
||||
end: u64,
|
||||
cursor: u64,
|
||||
visible_start: u64,
|
||||
visible_length: u64,
|
||||
x: u64,
|
||||
y: u64,
|
||||
end: usize,
|
||||
cursor: usize,
|
||||
visible_start: usize,
|
||||
visible_length: usize,
|
||||
x: usize,
|
||||
y: usize,
|
||||
masked: bool,
|
||||
maybe_mask: ?u8,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Text {
|
||||
const text = try DynamicString.initCapacity(allocator, max_length);
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, masked: bool, maybe_mask: ?u8) Text {
|
||||
const text = DynamicString.init(allocator);
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
@@ -33,6 +35,8 @@ pub fn init(allocator: Allocator, buffer: *TerminalBuffer, max_length: u64) !Tex
|
||||
.visible_length = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.masked = masked,
|
||||
.maybe_mask = maybe_mask,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,7 +44,7 @@ pub fn deinit(self: Text) void {
|
||||
self.text.deinit();
|
||||
}
|
||||
|
||||
pub fn position(self: *Text, x: u64, y: u64, visible_length: u64) void {
|
||||
pub fn position(self: *Text, x: usize, y: usize, visible_length: usize) void {
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
self.visible_length = visible_length;
|
||||
@@ -78,10 +82,25 @@ pub fn handle(self: *Text, maybe_event: ?*termbox.tb_event, insert_mode: bool) !
|
||||
}
|
||||
}
|
||||
|
||||
if (self.masked and self.maybe_mask == null) {
|
||||
_ = termbox.tb_set_cursor(@intCast(self.x), @intCast(self.y));
|
||||
return;
|
||||
}
|
||||
|
||||
_ = termbox.tb_set_cursor(@intCast(self.x + (self.cursor - self.visible_start)), @intCast(self.y));
|
||||
}
|
||||
|
||||
pub fn draw(self: Text) void {
|
||||
if (self.masked) {
|
||||
if (self.maybe_mask) |mask| {
|
||||
const length = @min(self.text.items.len, self.visible_length - 1);
|
||||
if (length == 0) return;
|
||||
|
||||
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const length = @min(self.text.items.len, self.visible_length);
|
||||
if (length == 0) return;
|
||||
|
||||
@@ -96,13 +115,6 @@ pub fn draw(self: Text) void {
|
||||
self.buffer.drawLabel(visible_slice, self.x, self.y);
|
||||
}
|
||||
|
||||
pub fn drawMasked(self: Text, mask: u8) void {
|
||||
const length = @min(self.text.items.len, self.visible_length - 1);
|
||||
if (length == 0) return;
|
||||
|
||||
self.buffer.drawCharMultiple(mask, self.x, self.y, length);
|
||||
}
|
||||
|
||||
pub fn clear(self: *Text) void {
|
||||
self.text.clearRetainingCapacity();
|
||||
self.end = 0;
|
||||
|
||||
115
src/tui/components/generic.zig
Normal file
115
src/tui/components/generic.zig
Normal file
@@ -0,0 +1,115 @@
|
||||
const std = @import("std");
|
||||
const enums = @import("../../enums.zig");
|
||||
const interop = @import("../../interop.zig");
|
||||
const TerminalBuffer = @import("../TerminalBuffer.zig");
|
||||
|
||||
pub fn CyclableLabel(comptime ItemType: type) type {
|
||||
return struct {
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ItemList = std.ArrayList(ItemType);
|
||||
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
const Self = @This();
|
||||
|
||||
allocator: Allocator,
|
||||
buffer: *TerminalBuffer,
|
||||
list: ItemList,
|
||||
current: usize,
|
||||
visible_length: usize,
|
||||
x: usize,
|
||||
y: usize,
|
||||
first_char_x: usize,
|
||||
text_in_center: bool,
|
||||
draw_item_fn: DrawItemFn,
|
||||
|
||||
pub fn init(allocator: Allocator, buffer: *TerminalBuffer, draw_item_fn: DrawItemFn) Self {
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.buffer = buffer,
|
||||
.list = ItemList.init(allocator),
|
||||
.current = 0,
|
||||
.visible_length = 0,
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.first_char_x = 0,
|
||||
.text_in_center = false,
|
||||
.draw_item_fn = draw_item_fn,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
self.list.deinit();
|
||||
}
|
||||
|
||||
pub fn position(self: *Self, x: usize, y: usize, visible_length: usize, text_in_center: ?bool) void {
|
||||
self.x = x;
|
||||
self.y = y;
|
||||
self.visible_length = visible_length;
|
||||
self.first_char_x = x + 2;
|
||||
if (text_in_center) |value| {
|
||||
self.text_in_center = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn addItem(self: *Self, item: ItemType) !void {
|
||||
try self.list.append(item);
|
||||
self.current = self.list.items.len - 1;
|
||||
}
|
||||
|
||||
pub fn handle(self: *Self, maybe_event: ?*termbox.tb_event, insert_mode: bool) void {
|
||||
if (maybe_event) |event| blk: {
|
||||
if (event.type != termbox.TB_EVENT_KEY) break :blk;
|
||||
|
||||
switch (event.key) {
|
||||
termbox.TB_KEY_ARROW_LEFT, termbox.TB_KEY_CTRL_H => self.goLeft(),
|
||||
termbox.TB_KEY_ARROW_RIGHT, termbox.TB_KEY_CTRL_L => self.goRight(),
|
||||
else => {
|
||||
if (!insert_mode) {
|
||||
switch (event.ch) {
|
||||
'h' => self.goLeft(),
|
||||
'l' => self.goRight(),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
_ = termbox.tb_set_cursor(@intCast(self.first_char_x), @intCast(self.y));
|
||||
}
|
||||
|
||||
pub fn draw(self: *Self) void {
|
||||
if (self.list.items.len == 0) return;
|
||||
|
||||
const current_item = self.list.items[self.current];
|
||||
const x = self.buffer.box_x + self.buffer.margin_box_h;
|
||||
const y = self.buffer.box_y + self.buffer.margin_box_v + 2;
|
||||
|
||||
const continue_drawing = @call(.auto, self.draw_item_fn, .{ self, current_item, x, y });
|
||||
if (!continue_drawing) return;
|
||||
|
||||
_ = termbox.tb_set_cell(@intCast(self.x), @intCast(self.y), '<', self.buffer.fg, self.buffer.bg);
|
||||
_ = termbox.tb_set_cell(@intCast(self.x + self.visible_length - 1), @intCast(self.y), '>', self.buffer.fg, self.buffer.bg);
|
||||
}
|
||||
|
||||
fn goLeft(self: *Self) void {
|
||||
if (self.current == 0) {
|
||||
self.current = self.list.items.len - 1;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current -= 1;
|
||||
}
|
||||
|
||||
fn goRight(self: *Self) void {
|
||||
if (self.current == self.list.items.len - 1) {
|
||||
self.current = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
self.current += 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -3,7 +3,13 @@ const interop = @import("../interop.zig");
|
||||
|
||||
const termbox = interop.termbox;
|
||||
|
||||
pub fn initCell(ch: u32, fg: u16, bg: u16) termbox.tb_cell {
|
||||
pub const Cell = struct {
|
||||
ch: u32,
|
||||
fg: u16,
|
||||
bg: u16,
|
||||
};
|
||||
|
||||
pub fn initCell(ch: u32, fg: u16, bg: u16) Cell {
|
||||
return .{
|
||||
.ch = ch,
|
||||
.fg = fg,
|
||||
@@ -11,8 +17,8 @@ pub fn initCell(ch: u32, fg: u16, bg: u16) termbox.tb_cell {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn putCell(x: i32, y: i32, cell: *const termbox.tb_cell) c_int {
|
||||
return termbox.tb_set_cell(x, y, cell.ch, cell.fg, cell.bg);
|
||||
pub fn putCell(x: usize, y: usize, cell: Cell) void {
|
||||
_ = termbox.tb_set_cell(@intCast(x), @intCast(y), cell.ch, cell.fg, cell.bg);
|
||||
}
|
||||
|
||||
// Every codepoint is assumed to have a width of 1.
|
||||
|
||||
Reference in New Issue
Block a user