mirror of
https://codeberg.org/fairyglade/ly.git
synced 2026-08-26 15:34:20 +02:00
Use Lua as a config file language (#1010)
## What are the changes about? Adds zlua for running Lua code as the configuration file. Missing as of this time: - ~~Parsing custom binds and labels~~ - ~~Fix memory leaks~~ - ~~Investigate issue with animation colours~~ - ~~Add examples and default config~~ ## What existing issue does this resolve? [#976](https://codeberg.org/fairyglade/ly/issues/976) ## Pre-requisites - [ ] I have tested & confirmed the changes work locally - [ ] I have run `zig fmt` throughout my changes Reviewed-on: https://codeberg.org/fairyglade/ly/pulls/1010 Reviewed-by: AnErrupTion <anerruption+codeberg@disroot.org>
This commit is contained in:
parent
882815343f
commit
b9742203ee
12 changed files with 833 additions and 39 deletions
|
|
@ -102,13 +102,6 @@ pub fn build(b: *std.Build) !void {
|
|||
}),
|
||||
});
|
||||
|
||||
const zlua = b.dependency("zlua", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.lang = .luajit,
|
||||
});
|
||||
exe.root_module.addImport("zlua", zlua.module("zlua"));
|
||||
|
||||
const ly_ui = b.dependency("ly_ui", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@
|
|||
.url = "git+https://github.com/Hejsil/zig-clap#fc1e5cc3f6d9d3001112385ee6256d694e959d2f",
|
||||
.hash = "clap-0.11.0-oBajB7foAQC3Iyn4IVCkUdYaOVVng5IZkSncySTjNig1",
|
||||
},
|
||||
.zlua = .{
|
||||
.url = "git+https://github.com/natecraddock/ziglua?ref=zig-0.16#8f271c82baa5fc43aa02a72f6da020c2025d9436",
|
||||
.hash = "zlua-0.1.0-hGRpC2aABQD4D9PBVH3wAW8k32-I4969MRQ0CpOwoley",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ fn installLy(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap, inst
|
|||
defer config_dir.close(io);
|
||||
|
||||
if (install_config) {
|
||||
const patched_config = try patchFile(allocator, io, "res/config.ini", patch_map);
|
||||
const patched_config = try patchFile(allocator, io, "res/config.lua", patch_map);
|
||||
defer allocator.free(patched_config);
|
||||
|
||||
try installText(io, patched_config, config_dir, ly_config_directory, "config.ini", .{});
|
||||
|
|
@ -135,10 +135,10 @@ fn installLy(allocator: std.mem.Allocator, io: std.Io, patch_map: PatchMap, inst
|
|||
try installFile(io, "res/startup.sh", config_dir, ly_config_directory, "startup.sh", .{ .permissions = .fromMode(0o755) });
|
||||
}
|
||||
|
||||
const patched_example_config = try patchFile(allocator, io, "res/config.ini", patch_map);
|
||||
const patched_example_config = try patchFile(allocator, io, "res/config.lua", patch_map);
|
||||
defer allocator.free(patched_example_config);
|
||||
|
||||
try installText(io, patched_example_config, config_dir, ly_config_directory, "config.ini.example", .{});
|
||||
try installText(io, patched_example_config, config_dir, ly_config_directory, "config.lua.example", .{});
|
||||
|
||||
const patched_setup = try patchFile(allocator, io, "res/setup.sh", patch_map);
|
||||
defer allocator.free(patched_setup);
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ pub fn build(b: *std.Build) void {
|
|||
const zigini = b.dependency("zigini", .{ .target = target, .optimize = optimize });
|
||||
mod.addImport("zigini", zigini.module("zigini"));
|
||||
|
||||
const zlua = b.dependency("zlua", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.lang = .luajit,
|
||||
});
|
||||
mod.addImport("zlua", zlua.module("zlua"));
|
||||
|
||||
const translate_c = b.dependency("translate_c", .{
|
||||
.target = target,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@
|
|||
.url = "git+https://codeberg.org/ziglang/translate-c?ref=zig-0.16.x#6fe0ffc4549f15c5f2d9432c2b4460ba90ff85ac",
|
||||
.hash = "translate_c-1.0.0-Q_BUWo_5BgD4flHdUhA31zOz0XvZk9k7lQv1ouzyNXj2",
|
||||
},
|
||||
.zlua = .{
|
||||
.url = "git+https://github.com/natecraddock/ziglua?ref=zig-0.16#8f271c82baa5fc43aa02a72f6da020c2025d9436",
|
||||
.hash = "zlua-0.1.0-hGRpC2aABQD4D9PBVH3wAW8k32-I4969MRQ0CpOwoley",
|
||||
},
|
||||
},
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
|
|
|
|||
|
|
@ -1,23 +1,57 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const ini = @import("zigini");
|
||||
pub const zlua = @import("zlua");
|
||||
pub const Lua = zlua.Lua;
|
||||
|
||||
pub const interop = @import("interop.zig");
|
||||
pub const UidRange = @import("UidRange.zig");
|
||||
pub const LogFile = @import("LogFile.zig");
|
||||
pub const SharedError = @import("SharedError.zig");
|
||||
pub const custom = @import("custom.zig");
|
||||
|
||||
pub fn Parser(comptime T: type) type {
|
||||
return union(enum) {
|
||||
ini: IniParser(T),
|
||||
lua: LuaParser(T),
|
||||
|
||||
pub fn errors(self: *const @This()) std.ArrayList(Error) {
|
||||
return switch (self.*) {
|
||||
inline else => |p| p.errors,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn maybe_load_error(self: *const @This()) ?anyerror {
|
||||
return switch (self.*) {
|
||||
inline else => |p| p.maybe_load_error,
|
||||
};
|
||||
}
|
||||
pub fn structure(self: *const @This()) T {
|
||||
return switch (self.*) {
|
||||
inline else => |p| p.structure,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
switch (self.*) {
|
||||
inline else => |*p| p.deinit(),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const Error = struct {
|
||||
type_name: []const u8,
|
||||
key: []const u8,
|
||||
value: []const u8,
|
||||
error_name: []const u8,
|
||||
};
|
||||
|
||||
pub fn IniParser(comptime Struct: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
const temporary_allocator = std.heap.page_allocator;
|
||||
|
||||
pub const Error = struct {
|
||||
type_name: []const u8,
|
||||
key: []const u8,
|
||||
value: []const u8,
|
||||
error_name: []const u8,
|
||||
};
|
||||
pub var global_errors: std.ArrayList(Error) = .empty;
|
||||
|
||||
ini_struct: ini.Ini(Struct),
|
||||
|
|
@ -76,3 +110,283 @@ pub fn IniParser(comptime Struct: type) type {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn LuaParser(comptime Struct: type) type {
|
||||
return struct {
|
||||
const Self = @This();
|
||||
const temporary_allocator = std.heap.page_allocator;
|
||||
|
||||
pub var global_errors: std.ArrayList(Error) = .empty;
|
||||
|
||||
structure: Struct,
|
||||
errors: std.ArrayList(Error),
|
||||
maybe_load_error: ?anyerror,
|
||||
allocator: std.mem.Allocator,
|
||||
arena: std.heap.ArenaAllocator,
|
||||
|
||||
pub fn init(
|
||||
allocator: std.mem.Allocator,
|
||||
path: []const u8,
|
||||
) !Self {
|
||||
var arena = std.heap.ArenaAllocator.init(allocator);
|
||||
const arena_alloc = arena.allocator();
|
||||
|
||||
var maybe_load_error: ?anyerror = null;
|
||||
errdefer |err| maybe_load_error = err;
|
||||
|
||||
const data = parseLua(arena_alloc, path) catch load_error: {
|
||||
break :load_error Struct{};
|
||||
};
|
||||
|
||||
if (global_errors.items.len != 0) {
|
||||
maybe_load_error = error.InvalidConfig;
|
||||
}
|
||||
|
||||
return .{
|
||||
.structure = data,
|
||||
.errors = global_errors,
|
||||
.maybe_load_error = maybe_load_error,
|
||||
.allocator = allocator,
|
||||
.arena = arena,
|
||||
};
|
||||
}
|
||||
|
||||
fn parseLua(
|
||||
allocator: std.mem.Allocator,
|
||||
path: []const u8,
|
||||
) !Struct {
|
||||
var lua: *Lua = try .init(allocator);
|
||||
defer lua.deinit();
|
||||
|
||||
lua.openBase();
|
||||
lua.openBit();
|
||||
lua.openMath();
|
||||
lua.openString();
|
||||
lua.openTable();
|
||||
|
||||
// convert to sentinel terminated slice
|
||||
const spath: [:0]const u8 = try allocator.dupeSentinel(u8, path, 0);
|
||||
defer allocator.free(spath);
|
||||
lua.doFile(spath) catch return error.LuaError;
|
||||
|
||||
var data: Struct = .{};
|
||||
switch (@typeInfo(Struct)) {
|
||||
.@"struct" => |struc| {
|
||||
const ly_type = lua.getGlobal("ly");
|
||||
defer lua.pop(1); // pop ly table
|
||||
if (ly_type == .nil) return error.MissingLyTable;
|
||||
|
||||
inline for (struc.fields) |field| {
|
||||
try setField(allocator, lua, field, &data);
|
||||
}
|
||||
},
|
||||
else => @compileError("Expected a struct."),
|
||||
}
|
||||
|
||||
// Parse custom binds and labels
|
||||
try parseCustom(lua);
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn setField(allocator: std.mem.Allocator, lua: *Lua, comptime field: std.builtin.Type.StructField, data: *Struct) !void {
|
||||
const type_info = @typeInfo(field.type);
|
||||
const actual_type, const is_optional = blk: {
|
||||
if (type_info == .optional) {
|
||||
break :blk .{ type_info.optional.child, true };
|
||||
}
|
||||
break :blk .{ field.type, false };
|
||||
};
|
||||
// push value to top of stack
|
||||
_ = lua.getField(-1, field.name);
|
||||
defer lua.pop(1);
|
||||
|
||||
// handle null, i.e. undefined fields
|
||||
if (is_optional and lua.isNil(-1)) {
|
||||
@field(data, field.name) = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// handle missing required fields
|
||||
if (lua.isNil(-1)) {
|
||||
return error.MissingRequiredField;
|
||||
}
|
||||
const actual_type_info = @typeInfo(actual_type);
|
||||
|
||||
errdefer |err| {
|
||||
const value = lua.toString(-1) catch "";
|
||||
const duped = allocator.dupe(u8, value) catch "";
|
||||
errorHandler(@typeName(field.type), field.name, duped, err);
|
||||
}
|
||||
|
||||
// dispatch depending on type
|
||||
if (actual_type_info == .int and is_optional) {
|
||||
if (lua.isNumber(-1)) {
|
||||
const value = try lua.toNumber(-1);
|
||||
@field(data, field.name) = @trunc(value);
|
||||
} else {
|
||||
const str = try lua.toString(-1);
|
||||
|
||||
var view = try std.unicode.Utf8View.init(str);
|
||||
var iter = view.iterator();
|
||||
|
||||
const codepoint = iter.nextCodepoint();
|
||||
|
||||
if (iter.nextCodepoint() != null) return error.ExpectedSingleCharacter;
|
||||
|
||||
@field(data, field.name) = if (codepoint) |cp| @intCast(cp) else null;
|
||||
}
|
||||
// non null integer
|
||||
} else if (actual_type_info == .int) {
|
||||
if (lua.isNumber(-1)) {
|
||||
const value = try lua.toNumber(-1);
|
||||
@field(data, field.name) = @trunc(value);
|
||||
} else {
|
||||
const str = try lua.toString(-1);
|
||||
|
||||
var view = try std.unicode.Utf8View.init(str);
|
||||
var iter = view.iterator();
|
||||
|
||||
const codepoint = iter.nextCodepoint() orelse return error.EmptyString;
|
||||
|
||||
if (iter.nextCodepoint() != null) return error.ExpectedSingleCharacter;
|
||||
|
||||
@field(data, field.name) = @intCast(codepoint);
|
||||
}
|
||||
} else if (actual_type_info == .float) { // all floats
|
||||
const value = try lua.toNumber(-1);
|
||||
@field(data, field.name) = @floatCast(value);
|
||||
} else if (actual_type_info == .bool) {
|
||||
if (!lua.isBoolean(-1)) return error.ExpectedBoolean;
|
||||
const value = lua.toBoolean(-1);
|
||||
@field(data, field.name) = value;
|
||||
} else if (actual_type == []const u8) {
|
||||
const value = try lua.toString(-1);
|
||||
const duped = try allocator.dupe(u8, value);
|
||||
@field(data, field.name) = duped;
|
||||
} else if (actual_type == [:0]const u8) {
|
||||
const value = try lua.toString(-1);
|
||||
const duped = try allocator.dupeSentinel(u8, value, 0);
|
||||
@field(data, field.name) = duped;
|
||||
} else if (actual_type_info == .@"enum") {
|
||||
const value = try lua.toString(-1);
|
||||
const variant = std.meta.stringToEnum(actual_type, value) orelse return error.InvalidVariant;
|
||||
@field(data, field.name) = variant;
|
||||
} else unreachable;
|
||||
}
|
||||
|
||||
pub fn parseCustom(lua: *Lua) !void {
|
||||
_ = lua.getGlobal("ly");
|
||||
defer lua.pop(1); // pop ly table
|
||||
if (!lua.isTable(-1)) return error.MissingLyTable;
|
||||
|
||||
_ = lua.getField(-1, "custom_commands");
|
||||
// custom_commands can be omitted or empty, so we just skip instead of erroring
|
||||
|
||||
if (lua.isTable(-1)) binds: {
|
||||
const len: usize = @intCast(lua.objectLen(-1));
|
||||
if (len == 0) break :binds;
|
||||
|
||||
for (1..len + 1) |i| {
|
||||
// push i-th table to stack
|
||||
lua.pushInteger(@intCast(i));
|
||||
const ith_table_type = lua.getTable(-2);
|
||||
defer lua.pop(1); // i-th table in custom_commands
|
||||
if (ith_table_type != .table) continue;
|
||||
|
||||
// skip command if binding isn't set or not a string
|
||||
const binding_type = lua.getField(-1, "binding");
|
||||
if (binding_type != .string) continue;
|
||||
const binding = lua.toString(-1) catch continue;
|
||||
const bindingZ = temporary_allocator.dupe(u8, binding) catch "";
|
||||
std.debug.print("{s}", .{bindingZ});
|
||||
lua.pop(1); // binding value
|
||||
|
||||
if (!custom.binds.contains(bindingZ)) {
|
||||
custom.binds.put(temporary_allocator, bindingZ, .{}) catch {};
|
||||
}
|
||||
if (custom.binds.getPtr(bindingZ)) |command| {
|
||||
// binding name
|
||||
const name_type = lua.getField(-1, "name");
|
||||
if (name_type != .string) continue;
|
||||
const binding_name = lua.toString(-1) catch "";
|
||||
command.name = temporary_allocator.dupe(u8, binding_name) catch "";
|
||||
lua.pop(1); // name value
|
||||
|
||||
// binding command
|
||||
const cmd_type = lua.getField(-1, "cmd");
|
||||
if (cmd_type != .string) continue;
|
||||
const binding_cmd = lua.toString(-1) catch "";
|
||||
command.cmd = temporary_allocator.dupe(u8, binding_cmd) catch "";
|
||||
lua.pop(1); // cmd value
|
||||
}
|
||||
}
|
||||
}
|
||||
lua.pop(1);
|
||||
|
||||
_ = lua.getField(-1, "custom_labels");
|
||||
// custom_labels can be omitted, so we just skip instead of erroring
|
||||
|
||||
if (lua.isTable(-1)) labels: {
|
||||
const len: usize = @intCast(lua.objectLen(-1));
|
||||
if (len == 0) break :labels;
|
||||
|
||||
for (1..len + 1) |i| {
|
||||
// push i-th table to stack
|
||||
lua.pushInteger(@intCast(i));
|
||||
const ith_table_type = lua.getTable(-2);
|
||||
defer lua.pop(1); // i-th table in custom_labels
|
||||
if (ith_table_type != .table) continue;
|
||||
|
||||
// skip command if binding isn't set or not a string
|
||||
const label_type = lua.getField(-1, "label");
|
||||
if (label_type != .string) continue;
|
||||
const label = lua.toString(-1) catch continue;
|
||||
const labelZ = temporary_allocator.dupe(u8, label) catch "";
|
||||
lua.pop(1); // label value
|
||||
|
||||
if (!custom.labels.contains(labelZ)) {
|
||||
custom.labels.put(temporary_allocator, labelZ, .{ .name = labelZ }) catch {};
|
||||
}
|
||||
if (custom.labels.getPtr(labelZ)) |label_ptr| {
|
||||
// label command
|
||||
const cmd_type = lua.getField(-1, "cmd");
|
||||
if (cmd_type != .string) continue;
|
||||
const label_cmd = lua.toString(-1) catch "";
|
||||
label_ptr.cmd = temporary_allocator.dupe(u8, label_cmd) catch "";
|
||||
lua.pop(1); // cmd value
|
||||
|
||||
// label refresh
|
||||
const name_type = lua.getField(-1, "refresh");
|
||||
if (name_type != .number) continue;
|
||||
const label_refresh: u32 = @intCast(lua.toInteger(-1) catch 0);
|
||||
label_ptr.refresh = label_refresh;
|
||||
lua.pop(1); // name value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lua.pop(1);
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.arena.deinit();
|
||||
for (0..global_errors.items.len) |i| {
|
||||
const err = global_errors.items[i];
|
||||
temporary_allocator.free(err.type_name);
|
||||
temporary_allocator.free(err.key);
|
||||
temporary_allocator.free(err.value);
|
||||
}
|
||||
|
||||
global_errors.deinit(temporary_allocator);
|
||||
}
|
||||
|
||||
fn errorHandler(type_name: []const u8, key: []const u8, value: []const u8, err: anyerror) void {
|
||||
global_errors.append(temporary_allocator, .{
|
||||
.type_name = temporary_allocator.dupe(u8, type_name) catch return,
|
||||
.key = temporary_allocator.dupe(u8, key) catch return,
|
||||
.value = temporary_allocator.dupe(u8, value) catch return,
|
||||
.error_name = @errorName(err),
|
||||
}) catch return;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -249,12 +249,14 @@ You can, of course, still select the init system of your choice when using this
|
|||
|
||||
## Configuration
|
||||
|
||||
You can find all the configuration in `/etc/ly/config.ini`. The file is fully commented, and includes the default values.
|
||||
You can find all the configuration in `/etc/ly/config.lua`. The file is fully commented, and includes the default values.
|
||||
|
||||
It uses the Lua language, which means you can make the configuration dynamic. You could, for example, choose a random animation each time Ly starts up.
|
||||
|
||||
You may also check the validity of your configuration file (i.e. if there are any errors in it) with the following command:
|
||||
|
||||
```
|
||||
$ ly --validate-config /etc/ly/config.ini
|
||||
$ ly --validate-config /etc/ly/config.lua
|
||||
```
|
||||
|
||||
## Controls
|
||||
|
|
|
|||
453
res/config.lua
Normal file
453
res/config.lua
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
ly = {
|
||||
-- Ly supports 24-bit true color with styling, which means each color is a 32-bit value.
|
||||
-- The format is 0xSSRRGGBB, where SS is the styling, RR is red, GG is green, and BB is blue.
|
||||
-- Here are the possible styling options:
|
||||
-- TB_BOLD 0x01000000
|
||||
-- TB_UNDERLINE 0x02000000
|
||||
-- TB_REVERSE 0x04000000
|
||||
-- TB_ITALIC 0x08000000
|
||||
-- TB_BLINK 0x10000000
|
||||
-- TB_HI_BLACK 0x20000000
|
||||
-- TB_BRIGHT 0x40000000
|
||||
-- TB_DIM 0x80000000
|
||||
-- Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's
|
||||
-- configuration doesn't support using it, you have to manually compute the color value.
|
||||
-- Note that, if you want to use the default color value of the terminal, you can use the
|
||||
-- special value 0x00000000. This means that, if you want to use black, you *must* use
|
||||
-- the styling option TB_HI_BLACK (the RGB values are ignored when using this option).
|
||||
|
||||
-- Allow empty password or not when authenticating
|
||||
allow_empty_password = true,
|
||||
|
||||
-- The active animation
|
||||
-- none -> Nothing
|
||||
-- doom -> PSX DOOM fire
|
||||
-- matrix -> CMatrix
|
||||
-- colormix -> Color mixing shader
|
||||
-- gameoflife -> John Conway's Game of Life
|
||||
-- dur_file -> .dur file format (https://github.com/cmang/durdraw/tree/master)
|
||||
-- lua -> user-made animation written in LuaJIT
|
||||
animation = none,
|
||||
|
||||
-- Delay between each animation frame in milliseconds
|
||||
animation_frame_delay = 5,
|
||||
|
||||
-- Stop the animation after some time
|
||||
-- 0 -> Run forever
|
||||
-- 1..2e12 -> Stop the animation after this many seconds
|
||||
animation_timeout_sec = 0,
|
||||
|
||||
-- The character used to mask the password
|
||||
-- You can either type it directly as a UTF-8 character (like *), or use a UTF-32
|
||||
-- codepoint (for example 0x2022 for a bullet point)
|
||||
-- If null, the password will be hidden
|
||||
-- Note: you can use a # by escaping it like so: \#
|
||||
asterisk = '*',
|
||||
|
||||
-- The number of failed authentications before a special animation is played... ;)
|
||||
-- If set to 0, the animation will never be played
|
||||
auth_fails = 10,
|
||||
|
||||
-- Automatic login configuration
|
||||
-- This feature allows Ly to automatically log in a user without password prompt.
|
||||
-- IMPORTANT: Both auto_login_user and auto_login_session must be set for this to work.
|
||||
-- Autologin only happens once at startup - it won't re-trigger after logout.
|
||||
|
||||
-- PAM service name to use for automatic login
|
||||
-- The default service (ly-autologin) uses pam_permit to allow login without password
|
||||
-- The appropriate platform-specific PAM configuration (ly-autologin) will be used automatically
|
||||
auto_login_service = "ly-autologin",
|
||||
|
||||
-- Session name to launch automatically
|
||||
-- To find available session names, check the .desktop files in:
|
||||
-- - /usr/share/xsessions/ (for X11 sessions)
|
||||
-- - /usr/share/wayland-sessions/ (for Wayland sessions)
|
||||
-- Use the filename without .desktop extension, the Name field inside the file or the value of the DesktopNames field
|
||||
-- Examples: "i3", "sway", "gnome", "plasma", "xfce"
|
||||
-- If null, automatic login is disabled
|
||||
auto_login_session = nil,
|
||||
|
||||
-- Username to automatically log in
|
||||
-- Must be a valid user on the system
|
||||
-- If null, automatic login is disabled
|
||||
auto_login_user = nil,
|
||||
|
||||
-- Identifier for battery whose charge to display at top left
|
||||
-- Primary battery is usually BAT0 or BAT1
|
||||
-- If set to null, battery status won't be shown
|
||||
-- Unused on FreeBSD (a sysctl is used there)
|
||||
battery_id = nil,
|
||||
|
||||
-- Background color id
|
||||
bg = 0x00000000,
|
||||
|
||||
-- Change the state and language of the big clock
|
||||
-- none -> Disabled (default)
|
||||
-- en -> English
|
||||
-- fa -> Farsi
|
||||
bigclock = "none",
|
||||
|
||||
-- Set bigclock to 12-hour notation.
|
||||
bigclock_12hr = false,
|
||||
|
||||
-- Set bigclock to show the seconds.
|
||||
bigclock_seconds = false,
|
||||
|
||||
-- Blank main box background
|
||||
-- Setting to false will make it transparent
|
||||
blank_box = true,
|
||||
|
||||
-- Border foreground color id
|
||||
border_fg = 0x00FFFFFF,
|
||||
|
||||
-- Relative horizontal position from the end of the screen
|
||||
-- default: 0.5
|
||||
box_position_h = 0.5,
|
||||
|
||||
-- Relative vertical position from the bottom of the screen
|
||||
-- default: 0.5
|
||||
box_position_v = 0.5,
|
||||
|
||||
-- Title to show at the top of the main box
|
||||
-- If set to null, none will be shown
|
||||
box_title = nil,
|
||||
|
||||
-- Brightness decrease command
|
||||
brightness_down_cmd = "$PREFIX_DIRECTORY/bin/brightnessctl -q -n s 10%-",
|
||||
|
||||
-- Brightness decrease key combination
|
||||
-- If null, the keybind is disabled and isn't shown
|
||||
brightness_down_key = "F5",
|
||||
|
||||
-- Brightness increase command
|
||||
brightness_up_cmd = "$PREFIX_DIRECTORY/bin/brightnessctl -q -n s +10%",
|
||||
|
||||
-- Brightness increase key combination
|
||||
-- If null, the keybind is disabled and isn't shown
|
||||
brightness_up_key = "F6",
|
||||
|
||||
-- Erase password input on failure
|
||||
clear_password = false,
|
||||
|
||||
-- Format string for clock in top right corner (see strftime specification). Example: %c
|
||||
-- If null, the clock won't be shown
|
||||
clock = nil,
|
||||
|
||||
-- CMatrix animation foreground color id
|
||||
cmatrix_fg = 0x0000FF00,
|
||||
|
||||
-- CMatrix animation character string head color id
|
||||
cmatrix_head_col = 0x01FFFFFF,
|
||||
|
||||
-- CMatrix animation minimum codepoint. It uses a 16-bit integer
|
||||
-- For Japanese characters for example, you can use 0x3000 here
|
||||
cmatrix_min_codepoint = 0x21,
|
||||
|
||||
-- CMatrix animation maximum codepoint. It uses a 16-bit integer
|
||||
-- For Japanese characters for example, you can use 0x30FF here
|
||||
cmatrix_max_codepoint = 0x7B,
|
||||
|
||||
-- Color mixing animation first color id
|
||||
colormix_col1 = 0x00FF0000,
|
||||
|
||||
-- Color mixing animation second color id
|
||||
colormix_col2 = 0x000000FF,
|
||||
|
||||
-- Color mixing animation third color id
|
||||
colormix_col3 = 0x20000000,
|
||||
|
||||
-- Screen corners customization
|
||||
-- Keywords:
|
||||
-- shutdown -> Shutdown key
|
||||
-- restart -> Restart key
|
||||
-- britup -> Brightness up key
|
||||
-- britdown -> Brightness down key
|
||||
-- password -> Toggle password key
|
||||
-- clock -> Clock (format defined by 'clock' option)
|
||||
-- tty -> Active TTY number
|
||||
-- battery -> Battery percentage
|
||||
-- version -> Ly version string
|
||||
-- numlock -> Numlock state
|
||||
-- capslock -> Capslock state
|
||||
-- labels -> All custom info labels (lbl:)
|
||||
-- binds -> All custom keybind hints (cmd:)
|
||||
-- lbl:name -> Specific custom info label
|
||||
-- cmd:key -> Specific custom keybind hint
|
||||
--
|
||||
-- If using a keyword that groups multiple labels into one (e.g. labels, binds),
|
||||
-- they'll be placed horizontally
|
||||
--
|
||||
-- Also, the order defines the vertical stack (first item is at the edge)
|
||||
-- If items are separted by commas, they'll be placed horizontally
|
||||
-- It is possible to have both horizontal and vertical items on the same corner
|
||||
|
||||
-- Bottom left
|
||||
corner_bottom_left = "version",
|
||||
|
||||
-- Bottom right
|
||||
corner_bottom_right = "labels",
|
||||
|
||||
-- Top left
|
||||
corner_top_left = "shutdown,restart,britup,britdown,password battery",
|
||||
|
||||
-- Top right
|
||||
corner_top_right = "clock numlock,capslock",
|
||||
|
||||
-- For custom binds: the horizontal limit in characters for each
|
||||
-- line of custom binds before moving on to the next.
|
||||
-- If null, defaults to the width of the terminal instead.
|
||||
custom_bind_width = nil,
|
||||
|
||||
-- Custom sessions directory
|
||||
-- You can specify multiple directories,
|
||||
-- e.g. $CONFIG_DIRECTORY/ly/custom-sessions:$PREFIX_DIRECTORY/share/custom-sessions
|
||||
custom_sessions = "$CONFIG_DIRECTORY/ly/custom-sessions",
|
||||
|
||||
-- Input box active by default on startup
|
||||
-- Available inputs: info_line, session, login, password
|
||||
default_input = "login",
|
||||
|
||||
-- DOOM animation fire height (1 thru 9)
|
||||
doom_fire_height = 6,
|
||||
|
||||
-- DOOM animation fire spread (0 thru 4)
|
||||
doom_fire_spread = 2,
|
||||
|
||||
-- DOOM animation custom top color (low intensity flames)
|
||||
doom_top_color = 0x009F2707,
|
||||
|
||||
-- DOOM animation custom middle color (medium intensity flames)
|
||||
doom_middle_color = 0x00C78F17,
|
||||
|
||||
-- DOOM animation custom bottom color (high intensity flames)
|
||||
doom_bottom_color = 0x00FFFFFF,
|
||||
|
||||
-- Dur file path
|
||||
dur_file_path = "$CONFIG_DIRECTORY/ly/example.dur",
|
||||
|
||||
-- Dur file alignment
|
||||
-- The dur file can be aligned with a direction and centered easily with the flags below
|
||||
-- Available inputs: topleft, topcenter, topright, centerleft, center, centerright, bottomleft, bottomcenter, bottomright
|
||||
dur_offset_alignment = "center",
|
||||
|
||||
-- Dur offset x direction (value is added to the current position determined by alignment, negatives are supported)
|
||||
dur_x_offset = 0,
|
||||
|
||||
-- Dur offset y direction (value is added to the current position determined by alignment, negatives are supported)
|
||||
dur_y_offset = 0,
|
||||
|
||||
-- Set margin to the edges of the DM (useful for curved monitors)
|
||||
edge_margin = 0,
|
||||
|
||||
-- Error background color id
|
||||
error_bg = 0x00000000,
|
||||
|
||||
-- Error foreground color id
|
||||
-- Default is red and bold
|
||||
error_fg = 0x01FF0000,
|
||||
|
||||
-- Foreground color id
|
||||
fg = 0x00FFFFFF,
|
||||
|
||||
-- Render true colors (if supported)
|
||||
-- If false, output will be in eight-color mode
|
||||
-- All eight-color mode color codes:
|
||||
-- TB_DEFAULT 0x0000
|
||||
-- TB_BLACK 0x0001
|
||||
-- TB_RED 0x0002
|
||||
-- TB_GREEN 0x0003
|
||||
-- TB_YELLOW 0x0004
|
||||
-- TB_BLUE 0x0005
|
||||
-- TB_MAGENTA 0x0006
|
||||
-- TB_CYAN 0x0007
|
||||
-- TB_WHITE 0x0008
|
||||
-- If full color is off, the styling options still work. The colors are
|
||||
-- always 32-bit values with the styling in the most significant byte.
|
||||
-- Note: If using the dur_file animation option and the dur file's color range
|
||||
-- is saved as 256 with this option disabled, the file will not be drawn.
|
||||
full_color = true,
|
||||
|
||||
-- Game of Life entropy interval (0 = disabled, >0 = add entropy every N generations),
|
||||
-- 0 -> Pure Conway's Game of Life (will eventually stabilize)
|
||||
-- 10 -> Add entropy every 10 generations (recommended for continuous activity)
|
||||
-- 50+ -> Less frequent entropy for more natural evolution
|
||||
gameoflife_entropy_interval = 10,
|
||||
|
||||
-- Game of Life animation foreground color id
|
||||
gameoflife_fg = 0x0000FF00,
|
||||
|
||||
-- Game of Life frame delay (lower = faster animation, higher = slower),
|
||||
-- 1-3 -> Very fast animation
|
||||
-- 6 -> Default smooth animation speed
|
||||
-- 10+ -> Slower, more contemplative speed
|
||||
gameoflife_frame_delay = 6,
|
||||
|
||||
-- Game of Life initial cell density (0.0 to 1.0)
|
||||
-- 0.1 -> Sparse, minimal activity
|
||||
-- 0.4 -> Balanced activity (recommended)
|
||||
-- 0.7+ -> Dense, chaotic patterns
|
||||
gameoflife_initial_density = 0.4,
|
||||
|
||||
-- Remove main box borders
|
||||
hide_borders = false,
|
||||
|
||||
-- Command executed when no input is detected for a certain time
|
||||
-- If null, no command will be executed
|
||||
inactivity_cmd = nil,
|
||||
|
||||
-- Executes a command after a certain amount of seconds
|
||||
inactivity_delay = 0,
|
||||
|
||||
-- Initial text to show on the info line
|
||||
-- If set to null, the info line defaults to the hostname
|
||||
initial_info_text = nil,
|
||||
|
||||
-- Input boxes length
|
||||
input_len = 34,
|
||||
|
||||
-- Active language
|
||||
-- Available languages are found in $CONFIG_DIRECTORY/ly/lang/
|
||||
lang = "en",
|
||||
|
||||
-- Command executed when logging in
|
||||
-- If null, no command will be executed
|
||||
-- Important: the code itself must end with `exec "$@"` in order to launch the session!
|
||||
-- You can also set environment variables in there, they'll persist until logout
|
||||
login_cmd = nil,
|
||||
|
||||
-- Path for login.defs file (used for listing all local users on the system on
|
||||
-- Linux)
|
||||
login_defs_path = "/etc/login.defs",
|
||||
|
||||
-- Command executed when logging out
|
||||
-- If null, no command will be executed
|
||||
-- Important: the session will already be terminated when this command is executed, so
|
||||
-- no need to add `exec "$@"` at the end
|
||||
logout_cmd = nil,
|
||||
|
||||
-- The file pointing to the Lua file to be used when using the Lua animation option
|
||||
lua_animation_file = "$CONFIG_DIRECTORY/ly/example.lua",
|
||||
|
||||
-- General log file path
|
||||
-- If null, syslog will be used instead
|
||||
ly_log = "/var/log/ly.log",
|
||||
|
||||
-- Main box horizontal margin
|
||||
margin_box_h = 2,
|
||||
|
||||
-- Main box vertical margin
|
||||
margin_box_v = 1,
|
||||
|
||||
-- Set numlock on/off at startup
|
||||
numlock = false,
|
||||
|
||||
-- Default path
|
||||
-- If null, ly doesn't set a path
|
||||
path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||
|
||||
-- Specifies the key combination used for restart
|
||||
-- If null, the keybind is disabled and isn't shown
|
||||
restart_key = "F2",
|
||||
|
||||
-- Absolute directory for the save file
|
||||
-- If null, current desktop & login won't be saved nor loaded
|
||||
save_file_dir = "$CONFIG_DIRECTORY/ly",
|
||||
|
||||
-- Service name (set to ly to use the provided pam config file)
|
||||
service_name = "ly",
|
||||
|
||||
-- Session log file path
|
||||
-- This will contain stdout and stderr of Wayland sessions
|
||||
-- By default it's saved in the user's home directory
|
||||
-- Important: due to technical limitations, X11, shell sessions as well as
|
||||
-- launching session via KMSCON aren't supported, which means you won't get any
|
||||
-- logs from those sessions.
|
||||
-- If null, no session log will be created
|
||||
session_log = ".local/state/ly-session.log",
|
||||
|
||||
-- Setup command
|
||||
setup_cmd = "$CONFIG_DIRECTORY/ly/setup.sh",
|
||||
|
||||
-- Show the shell session in the session list
|
||||
-- If false, the shell session will be hidden
|
||||
shell = true,
|
||||
|
||||
-- Specifies the key combination used for showing the password
|
||||
-- If null, the keybind is disabled and isn't shown
|
||||
show_password_key = "F7",
|
||||
|
||||
-- Specifies the key combination used for shutdown
|
||||
-- If null, the keybind is disabled and isn't shown
|
||||
shutdown_key = "F1",
|
||||
|
||||
-- Command executed when starting Ly (before the TTY is taken control of)
|
||||
-- See file at path below for an example of changing the default TTY colors
|
||||
start_cmd = "$CONFIG_DIRECTORY/ly/startup.sh",
|
||||
|
||||
-- Center the session name.
|
||||
text_in_center = false,
|
||||
|
||||
-- If true, user will need to manually type username instead of selecting from the list
|
||||
-- of discovered users
|
||||
type_username = false,
|
||||
|
||||
-- Default vi mode
|
||||
-- normal -> normal mode
|
||||
-- insert -> insert mode
|
||||
vi_default_mode = "normal",
|
||||
|
||||
-- Enable vi keybindings
|
||||
vi_mode = false,
|
||||
|
||||
-- Wayland desktop environments
|
||||
-- You can specify multiple directories,
|
||||
-- e.g. $PREFIX_DIRECTORY/share/wayland-sessions:$PREFIX_DIRECTORY/local/share/wayland-sessions
|
||||
-- If null, Wayland sessions will not be shown
|
||||
waylandsessions = "$PREFIX_DIRECTORY/share/wayland-sessions",
|
||||
|
||||
-- Xorg server command
|
||||
-- Add the -quiet argument to hide startup logs from the server
|
||||
x_cmd = "$PREFIX_DIRECTORY/bin/X",
|
||||
|
||||
-- Xorg virtual terminal number
|
||||
-- Mostly useful for FreeBSD where choosing the current TTY causes issues
|
||||
-- If null, the current TTY will be chosen
|
||||
x_vt = nil,
|
||||
|
||||
-- Xorg xauthority edition tool
|
||||
xauth_cmd = "$PREFIX_DIRECTORY/bin/xauth",
|
||||
|
||||
-- xinitrc
|
||||
-- If null, the xinitrc session will be hidden
|
||||
xinitrc = "~/.xinitrc",
|
||||
|
||||
-- Xorg desktop environments
|
||||
-- You can specify multiple directories,
|
||||
-- e.g. $PREFIX_DIRECTORY/share/xsessions:$PREFIX_DIRECTORY/local/share/xsessions
|
||||
-- If null, X11 sessions will not be shown
|
||||
xsessions = "$PREFIX_DIRECTORY/share/xsessions",
|
||||
|
||||
-- Custom Commands and Labels:
|
||||
-- The following examples below give an outline for setting up custom commands and labels.
|
||||
-- Unless specified as optional, an option is mandatory.
|
||||
|
||||
-- Comments preceding with '##' are for documentation.
|
||||
-- Comments preceding with '#' comment out the example INI.
|
||||
|
||||
--# Declare a command with the F8 binding.
|
||||
--[cmd:F8]
|
||||
--# The name of the command to show up in Ly.
|
||||
--# Note: "$" in "$brightness_up" fetches the appropriate string from the specified locale file
|
||||
--# and is replaced with the value representing "brightness_up".
|
||||
--# You can see the list of keys in any locale file in $CONFIG_DIRECTORY/ly/lang.
|
||||
--cmd = touch /tmp/ly.gaming,
|
||||
--name = custom command $brightness_up,
|
||||
|
||||
--# Declare a label with an ID. This ID should be unique across all labels.
|
||||
--[lbl:kernel]
|
||||
--cmd = uname -srn,
|
||||
--# Optional, defaulting to 0.
|
||||
--# In frames, the time to re-run the command and update the label.
|
||||
--# If 0, only run once and do not refresh afterwards
|
||||
--refresh = 0,
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ const Allocator = std.mem.Allocator;
|
|||
const InfoLine = @import("../components/InfoLine.zig");
|
||||
const Lang = @import("../config/Lang.zig");
|
||||
|
||||
const zlua = @import("zlua");
|
||||
const zlua = ly_ui.ly_core.zlua;
|
||||
|
||||
const ly_lua = @embedFile("ly.lua");
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const Config = @import("Config.zig");
|
|||
const Lang = @import("Lang.zig");
|
||||
const OldSave = @import("OldSave.zig");
|
||||
const SavedUsers = @import("SavedUsers.zig");
|
||||
const custom = @import("custom.zig");
|
||||
const custom = ly_core.custom;
|
||||
|
||||
const color_properties = [_][]const u8{
|
||||
"bg",
|
||||
|
|
|
|||
55
src/main.zig
55
src/main.zig
|
|
@ -19,9 +19,12 @@ const interop = ly_core.interop;
|
|||
const UidRange = ly_core.UidRange;
|
||||
const LogFile = ly_core.LogFile;
|
||||
const SharedError = ly_core.SharedError;
|
||||
const Parser = ly_core.Parser;
|
||||
const IniParser = ly_core.IniParser;
|
||||
const LuaParser = ly_core.LuaParser;
|
||||
const ini = ly_core.ini;
|
||||
const Ini = ini.Ini;
|
||||
const custom = ly_core.custom;
|
||||
|
||||
const Cascade = @import("animations/Cascade.zig");
|
||||
const ColorMix = @import("animations/ColorMix.zig");
|
||||
|
|
@ -39,7 +42,6 @@ const Lang = @import("config/Lang.zig");
|
|||
const migrator = @import("config/migrator.zig");
|
||||
const OldSave = @import("config/OldSave.zig");
|
||||
const SavedUsers = @import("config/SavedUsers.zig");
|
||||
const custom = @import("config/custom.zig");
|
||||
const DisplayServer = @import("enums.zig").DisplayServer;
|
||||
const Environment = @import("Environment.zig");
|
||||
const Entry = Environment.Entry;
|
||||
|
|
@ -203,22 +205,28 @@ pub fn main(init: std.process.Init) !void {
|
|||
if (res.args.config) |path| config_parent_path = path;
|
||||
if (res.args.@"use-kmscon-vt" != 0) state.use_kmscon_vt = true;
|
||||
if (res.args.@"validate-config") |path| {
|
||||
var parser = try IniParser(Config).init(
|
||||
state.allocator,
|
||||
state.io,
|
||||
path,
|
||||
migrator.configFieldHandler,
|
||||
);
|
||||
var parser: Parser(Config) = blk: {
|
||||
if (std.mem.endsWith(u8, path, ".ini")) {
|
||||
break :blk .{ .ini = try IniParser(Config).init(
|
||||
state.allocator,
|
||||
state.io,
|
||||
path,
|
||||
migrator.configFieldHandler,
|
||||
) };
|
||||
} else {
|
||||
break :blk .{ .lua = try LuaParser(Config).init(state.allocator, path) };
|
||||
}
|
||||
};
|
||||
defer parser.deinit();
|
||||
|
||||
for (parser.errors.items) |err| {
|
||||
for (parser.errors().items) |err| {
|
||||
std.log.err(
|
||||
"failed to convert value '{s}' of option '{s}' to type '{s}': {s}",
|
||||
.{ err.value, err.key, err.type_name, err.error_name },
|
||||
);
|
||||
}
|
||||
|
||||
if (parser.maybe_load_error) |err| {
|
||||
if (parser.maybe_load_error()) |err| {
|
||||
std.log.err("failed to load config file: {s}", .{@errorName(err)});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
|
@ -234,12 +242,29 @@ pub fn main(init: std.process.Init) !void {
|
|||
state.allocator.free(state.old_save_path);
|
||||
};
|
||||
|
||||
const config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
|
||||
// Test for presence of Lua config file first
|
||||
// If it fails, fall back to ini
|
||||
var config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.lua" });
|
||||
std.Io.Dir.accessAbsolute(state.io, config_path, .{}) catch {
|
||||
state.allocator.free(config_path);
|
||||
config_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "config.ini" });
|
||||
};
|
||||
defer state.allocator.free(config_path);
|
||||
|
||||
custom.binds = .empty;
|
||||
custom.labels = .empty;
|
||||
var config_parser = try IniParser(Config).init(state.allocator, state.io, config_path, migrator.configFieldHandler);
|
||||
var config_parser: Parser(Config) = blk: {
|
||||
if (std.mem.endsWith(u8, config_path, ".ini")) {
|
||||
break :blk .{ .ini = try IniParser(Config).init(
|
||||
state.allocator,
|
||||
state.io,
|
||||
config_path,
|
||||
migrator.configFieldHandler,
|
||||
) };
|
||||
} else {
|
||||
break :blk .{ .lua = try LuaParser(Config).init(state.allocator, config_path) };
|
||||
}
|
||||
};
|
||||
defer config_parser.deinit();
|
||||
defer if (!shutdown or !restart) {
|
||||
var iter = custom.binds.iterator();
|
||||
|
|
@ -258,7 +283,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
custom.labels.deinit(temporary_allocator);
|
||||
};
|
||||
|
||||
state.config = config_parser.structure;
|
||||
state.config = config_parser.structure();
|
||||
|
||||
var lang_buffer: [16]u8 = undefined;
|
||||
const lang_file = try std.fmt.bufPrint(&lang_buffer, "{s}.ini", .{state.config.lang});
|
||||
|
|
@ -276,7 +301,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
state.old_save_path = try std.Io.Dir.path.join(state.allocator, &[_][]const u8{ config_parent_path, "save.ini" });
|
||||
}
|
||||
|
||||
if (config_parser.maybe_load_error == null) {
|
||||
if (config_parser.maybe_load_error() == null and config_parser == .ini) {
|
||||
migrator.lateConfigFieldHandler(&state.config, state.lang);
|
||||
}
|
||||
|
||||
|
|
@ -636,7 +661,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
);
|
||||
}
|
||||
|
||||
if (config_parser.maybe_load_error) |load_error| {
|
||||
if (config_parser.maybe_load_error()) |load_error| {
|
||||
// We can't localize this since the config failed to load so we'd fallback to the default language anyway
|
||||
try state.info_line.addMessage(
|
||||
"unable to parse config file",
|
||||
|
|
@ -650,7 +675,7 @@ pub fn main(init: std.process.Init) !void {
|
|||
.{@errorName(load_error)},
|
||||
);
|
||||
|
||||
for (config_parser.errors.items) |err| {
|
||||
for (config_parser.errors().items) |err| {
|
||||
try state.log_file.err(
|
||||
state.io,
|
||||
"conf",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue