Split core code into ly-core library

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion
2026-01-18 21:07:53 +01:00
parent 456916f059
commit d7f64676ee
18 changed files with 64 additions and 21 deletions

View File

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

View File

@@ -1,39 +0,0 @@
const std = @import("std");
const ErrInt = std.meta.Int(.unsigned, @bitSizeOf(anyerror));
const ErrorHandler = packed struct {
has_error: bool = false,
err_int: ErrInt = 0,
};
const SharedError = @This();
data: []align(std.heap.page_size_min) u8,
pub fn init() !SharedError {
const data = try std.posix.mmap(null, @sizeOf(ErrorHandler), std.posix.PROT.READ | std.posix.PROT.WRITE, .{ .TYPE = .SHARED, .ANONYMOUS = true }, -1, 0);
return .{ .data = data };
}
pub fn deinit(self: *SharedError) void {
std.posix.munmap(self.data);
}
pub fn writeError(self: SharedError, err: anyerror) void {
var buf_stream = std.io.fixedBufferStream(self.data);
const writer = buf_stream.writer();
writer.writeStruct(ErrorHandler{ .has_error = true, .err_int = @intFromError(err) }) catch {};
}
pub fn readError(self: SharedError) ?anyerror {
var buf_stream = std.io.fixedBufferStream(self.data);
const reader = buf_stream.reader();
const err_handler = try reader.readStruct(ErrorHandler);
if (err_handler.has_error)
return @errorFromInt(err_handler.err_int);
return null;
}

View File

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

View File

@@ -1,12 +1,13 @@
const std = @import("std");
const build_options = @import("build_options");
const builtin = @import("builtin");
const ly_core = @import("ly-core");
const Environment = @import("Environment.zig");
const interop = @import("interop.zig");
const SharedError = @import("SharedError.zig");
const LogFile = @import("LogFile.zig");
const Md5 = std.crypto.hash.Md5;
const interop = ly_core.interop;
const SharedError = ly_core.SharedError;
const LogFile = ly_core.LogFile;
const utmp = interop.utmp;
const Utmp = utmp.utmpx;

View File

@@ -1,11 +1,12 @@
const std = @import("std");
const interop = @import("interop.zig");
const ly_core = @import("ly-core");
const enums = @import("enums.zig");
const Lang = @import("bigclock/Lang.zig");
const en = @import("bigclock/en.zig");
const fa = @import("bigclock/fa.zig");
const Cell = @import("tui/Cell.zig");
const interop = ly_core.interop;
const Bigclock = enums.Bigclock;
pub const WIDTH = Lang.WIDTH;
pub const HEIGHT = Lang.HEIGHT;

View File

@@ -1,10 +1,10 @@
const interop = @import("../interop.zig");
const ly_core = @import("ly-core");
pub const WIDTH = 5;
pub const HEIGHT = 5;
pub const SIZE = WIDTH * HEIGHT;
pub const X: u32 = if (interop.supportsUnicode()) 0x2593 else '#';
pub const X: u32 = if (ly_core.interop.supportsUnicode()) 0x2593 else '#';
pub const O: u32 = 0;
// zig fmt: off

View File

@@ -1,4 +1,5 @@
const std = @import("std");
pub const Animation = enum {
none,
doom,

View File

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

View File

@@ -1,13 +1,13 @@
const std = @import("std");
const build_options = @import("build_options");
const builtin = @import("builtin");
const ly_core = @import("ly-core");
const clap = @import("clap");
const ini = @import("zigini");
const auth = @import("auth.zig");
const bigclock = @import("bigclock.zig");
const enums = @import("enums.zig");
const Environment = @import("Environment.zig");
const interop = @import("interop.zig");
const ColorMix = @import("animations/ColorMix.zig");
const Doom = @import("animations/Doom.zig");
const Dummy = @import("animations/Dummy.zig");
@@ -25,15 +25,16 @@ const Lang = @import("config/Lang.zig");
const OldSave = @import("config/OldSave.zig");
const SavedUsers = @import("config/SavedUsers.zig");
const migrator = @import("config/migrator.zig");
const SharedError = @import("SharedError.zig");
const LogFile = @import("LogFile.zig");
const UidRange = @import("UidRange.zig");
const StringList = std.ArrayListUnmanaged([]const u8);
const Ini = ini.Ini;
const DisplayServer = enums.DisplayServer;
const Entry = Environment.Entry;
const termbox = interop.termbox;
const interop = ly_core.interop;
const UidRange = ly_core.UidRange;
const LogFile = ly_core.LogFile;
const SharedError = ly_core.SharedError;
const termbox = TerminalBuffer.termbox;
const temporary_allocator = std.heap.page_allocator;
const ly_version_str = "Ly version " ++ build_options.version;

View File

@@ -1,6 +1,6 @@
const interop = @import("../interop.zig");
const TerminalBuffer = @import("TerminalBuffer.zig");
const termbox = interop.termbox;
const termbox = TerminalBuffer.termbox;
const Cell = @This();

View File

@@ -1,10 +1,12 @@
const std = @import("std");
const interop = @import("../interop.zig");
const ly_core = @import("ly-core");
const Cell = @import("Cell.zig");
pub const termbox = @import("termbox2");
const Random = std.Random;
const termbox = interop.termbox;
const interop = ly_core.interop;
const TerminalBuffer = @This();

View File

@@ -1,11 +1,10 @@
const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
const Allocator = std.mem.Allocator;
const DynamicString = std.ArrayListUnmanaged(u8);
const termbox = interop.termbox;
const termbox = TerminalBuffer.termbox;
const Text = @This();

View File

@@ -1,5 +1,4 @@
const std = @import("std");
const interop = @import("../../interop.zig");
const TerminalBuffer = @import("../TerminalBuffer.zig");
pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) type {
@@ -9,7 +8,7 @@ pub fn CyclableLabel(comptime ItemType: type, comptime ChangeItemType: type) typ
const DrawItemFn = *const fn (*Self, ItemType, usize, usize) bool;
const ChangeItemFn = *const fn (ItemType, ?ChangeItemType) void;
const termbox = interop.termbox;
const termbox = TerminalBuffer.termbox;
const Self = @This();