Enable true color output (closes #705)

Signed-off-by: AnErrupTion <anerruption@disroot.org>
This commit is contained in:
AnErrupTion 2025-03-06 12:47:28 +01:00
parent a766dc2b9c
commit d0ccaa4d69
No known key found for this signature in database
11 changed files with 134 additions and 92 deletions

View File

@ -72,6 +72,7 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
});
translate_c.defineCMacroRaw("TB_IMPL");
translate_c.defineCMacro("TB_OPT_ATTR_W", "32"); // Enable 24-bit color support + styling (32-bit)
const termbox2 = translate_c.addModule("termbox2");
exe.root_module.addImport("termbox2", termbox2);

View File

@ -1,33 +1,19 @@
# The color settings in Ly take a digit 0-8 corresponding to:
#define TB_DEFAULT 0x00
#define TB_BLACK 0x01
#define TB_RED 0x02
#define TB_GREEN 0x03
#define TB_YELLOW 0x04
#define TB_BLUE 0x05
#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.
#
# 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.
# 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:
#define TB_BOLD 0x01000000
#define TB_UNDERLINE 0x02000000
#define TB_REVERSE 0x04000000
#define TB_ITALIC 0x08000000
#define TB_BLINK 0x10000000
#define TB_HI_BLACK 0x20000000
#define TB_BRIGHT 0x40000000
#define TB_DIM 0x80000000
# Programmatically, you'd apply them using the bitwise OR operator (|), but because Ly's
# configuration doesn't support using it, you have to manually compute the color value.
# Note that, if you want to use the default color value of the terminal, you can use the
# 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).
# The active animation
# none -> Nothing
@ -50,7 +36,7 @@ asterisk = *
auth_fails = 10
# Background color id
bg = 0x0000
bg = 0x00000000
# Change the state and language of the big clock
# none -> Disabled (default)
@ -63,7 +49,7 @@ bigclock = none
blank_box = true
# Border foreground color id
border_fg = 0x0008
border_fg = 0x00FFFFFF
# Title to show at the top of the main box
# If set to null, none will be shown
@ -89,16 +75,16 @@ clear_password = false
clock = null
# CMatrix animation foreground color id
cmatrix_fg = 0x0003
cmatrix_fg = 0x0000FF00
# Color mixing animation first color id
colormix_col1 = 0x0002
colormix_col1 = 0x00FF0000
# Color mixing animation second color id
colormix_col2 = 0x0005
colormix_col2 = 0x000000FF
# Color mixing animation third color id
colormix_col3 = 0x0001
colormix_col3 = 0x20000000
# Console path
console_dev = /dev/console
@ -108,14 +94,14 @@ console_dev = /dev/console
default_input = login
# Error background color id
error_bg = 0x0000
error_bg = 0x00000000
# Error foreground color id
# Default is red and bold: TB_RED | TB_BOLD
error_fg = 0x0102
# Default is red and bold
error_fg = 0x11FF0000
# Foreground color id
fg = 0x0008
fg = 0x00FFFFFF
# Remove main box borders
hide_borders = false

View File

@ -20,7 +20,7 @@ pattern_cos_mod: f32,
pattern_sin_mod: f32,
palette: [palette_len]utils.Cell,
pub fn init(terminal_buffer: *TerminalBuffer, col1: u16, col2: u16, col3: u16) ColorMix {
pub fn init(terminal_buffer: *TerminalBuffer, col1: u32, col2: u32, col3: u32) ColorMix {
return .{
.terminal_buffer = terminal_buffer,
.frames = 0,

View File

@ -34,9 +34,9 @@ dots: []Dot,
lines: []Line,
frame: u64,
count: u64,
fg_ini: u16,
fg_ini: u32,
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_ini: u16) !Matrix {
pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg_ini: u32) !Matrix {
const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1));
const lines = try allocator.alloc(Line, terminal_buffer.width);

View File

@ -2,17 +2,17 @@ const std = @import("std");
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 Lang = @import("bigclock/Lang.zig");
const en = @import("bigclock/en.zig");
const fa = @import("bigclock/fa.zig");
const termbox = interop.termbox;
const Bigclock = enums.Bigclock;
pub const WIDTH = Lang.WIDTH;
const termbox = interop.termbox;
const Bigclock = enums.Bigclock;
pub const WIDTH = Lang.WIDTH;
pub const HEIGHT = Lang.HEIGHT;
pub const SIZE = Lang.SIZE;
pub const SIZE = Lang.SIZE;
pub fn clockCell(animate: bool, char: u8, fg: u16, bg: u16, bigclock: Bigclock) [SIZE]utils.Cell {
pub fn clockCell(animate: bool, char: u8, fg: u32, bg: u32, bigclock: Bigclock) [SIZE]utils.Cell {
var cells: [SIZE]utils.Cell = undefined;
var tv: interop.system_time.timeval = undefined;
@ -37,9 +37,9 @@ pub fn alphaBlit(x: usize, y: usize, tb_width: usize, tb_height: usize, cells: [
fn toBigNumber(char: u8, bigclock: Bigclock) []const u21 {
const locale_chars = switch (bigclock) {
.fa => fa.locale_chars,
.en => en.locale_chars,
.none => unreachable,
.fa => fa.locale_chars,
.en => en.locale_chars,
.none => unreachable,
};
return switch (char) {
'0' => &locale_chars.ZERO,

View File

@ -10,10 +10,10 @@ animation: Animation = .none,
animation_timeout_sec: u12 = 0,
asterisk: ?u8 = '*',
auth_fails: u64 = 10,
bg: u16 = 0,
bg: u32 = 0x00000000,
bigclock: Bigclock = .none,
blank_box: bool = true,
border_fg: u16 = 8,
border_fg: u32 = 0x00FFFFFF,
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",
@ -21,15 +21,15 @@ brightness_up_cmd: [:0]const u8 = build_options.prefix_directory ++ "/bin/bright
brightness_up_key: []const u8 = "F6",
clear_password: bool = false,
clock: ?[:0]const u8 = null,
cmatrix_fg: u16 = 3,
colormix_col1: u16 = 2,
colormix_col2: u16 = 5,
colormix_col3: u16 = 1,
cmatrix_fg: u32 = 0x0000FF00,
colormix_col1: u32 = 0x00FF0000,
colormix_col2: u32 = 0x000000FF,
colormix_col3: u32 = 0x20000000,
console_dev: []const u8 = "/dev/console",
default_input: Input = .login,
error_bg: u16 = 0,
error_fg: u16 = 258,
fg: u16 = 8,
error_bg: u32 = 0x00000000,
error_fg: u32 = 0x11FF0000,
fg: u32 = 0x00FFFFFF,
hide_borders: bool = false,
hide_key_hints: bool = false,
initial_info_text: ?[]const u8 = null,

View File

@ -2,9 +2,34 @@
const std = @import("std");
const ini = @import("zigini");
const interop = @import("../interop.zig");
const Save = @import("Save.zig");
const enums = @import("../enums.zig");
const termbox = interop.termbox;
const color_properties = [_][]const u8{
"bg",
"border_fg",
"cmatrix_fg",
"colormix_col1",
"colormix_col2",
"colormix_col3",
"error_bg",
"error_fg",
"fg",
};
const removed_properties = [_][]const u8{
"wayland_specifier",
"max_desktop_len",
"max_login_len",
"max_password_len",
"mcookie_cmd",
"term_reset_cmd",
"term_restore_cursor_cmd",
"x_cmd_setup",
"wayland_cmd",
};
var temporary_allocator = std.heap.page_allocator;
pub var maybe_animate: ?bool = null;
@ -12,7 +37,7 @@ pub var maybe_save_file: ?[]const u8 = null;
pub var mapped_config_fields = false;
pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniField {
pub fn configFieldHandler(allocator: 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");
@ -37,6 +62,18 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
return mapped_field;
}
inline for (color_properties) |property| {
if (std.mem.eql(u8, field.key, property)) {
// These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer
const color = std.fmt.parseInt(u16, field.value, 0) catch return field;
var mapped_field = field;
mapped_field.value = mapColor(allocator, color) catch return field;
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;
@ -70,19 +107,12 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
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;
inline for (removed_properties) |property| {
if (std.mem.eql(u8, field.key, property)) {
// The options don't exist anymore
mapped_config_fields = true;
return null;
}
}
if (std.mem.eql(u8, field.key, "bigclock")) {
@ -90,14 +120,14 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie
// It also includes the ability to change active bigclock's language
var mapped_field = field;
if (std.mem.eql(u8, field.value, "true")){
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")){
} else if (std.mem.eql(u8, field.value, "false")) {
mapped_field.value = "none";
mapped_config_fields = true;
}
return mapped_field;
}
@ -142,3 +172,28 @@ pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save {
return save;
}
fn mapColor(allocator: std.mem.Allocator, color: u16) ![]const u8 {
const color_no_styling = color & 0x00FF;
const styling_only = color & 0xFF00;
var new_color: u32 = switch (color_no_styling) {
termbox.TB_BLACK => termbox.TB_HI_BLACK,
termbox.TB_RED => 0x00FF0000,
termbox.TB_GREEN => 0x0000FF00,
termbox.TB_YELLOW => 0x00FFFF00,
termbox.TB_BLUE => 0x000000FF,
termbox.TB_MAGENTA => 0x00FF00FF,
termbox.TB_CYAN => 0x0000FFFF,
termbox.TB_WHITE => 0x00FFFFFF,
else => termbox.TB_DEFAULT,
};
// Only applying styling if color isn't black and styling isn't also black
if (!(new_color == termbox.TB_HI_BLACK and styling_only == termbox.TB_HI_BLACK)) {
// Shift styling by 16 to the left to apply it to the new 32-bit color
new_color |= @as(u32, @intCast(styling_only)) << 16;
}
return try std.fmt.allocPrint(allocator, "0x{X}", .{new_color});
}

View File

@ -212,7 +212,7 @@ pub fn main() !void {
};
std.posix.sigaction(std.posix.SIG.TERM, &act, null);
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL);
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
_ = termbox.tb_clear();
// Needed to reset termbox after auth
@ -729,7 +729,7 @@ pub fn main() !void {
// Take back control of the TTY
_ = termbox.tb_init();
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_NORMAL);
_ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR);
const auth_err = shared_err.readError();
if (auth_err) |err| {

View File

@ -14,9 +14,9 @@ random: Random,
width: usize,
height: usize,
buffer: [*]termbox.tb_cell,
fg: u16,
bg: u16,
border_fg: u16,
fg: u32,
bg: u32,
border_fg: u32,
box_chars: struct {
left_up: u32,
left_down: u32,
@ -170,7 +170,7 @@ pub fn drawLabel(self: TerminalBuffer, text: []const u8, x: usize, y: usize) voi
drawColorLabel(text, x, y, self.fg, self.bg);
}
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u16, bg: u16) void {
pub fn drawColorLabel(text: []const u8, x: usize, y: usize, fg: u32, bg: u32) void {
const yc: c_int = @intCast(y);
const utf8view = std.unicode.Utf8View.init(text) catch return;
var utf8 = utf8view.iterator();

View File

@ -12,8 +12,8 @@ const InfoLine = @This();
const Message = struct {
width: u8,
text: []const u8,
bg: u16,
fg: u16,
bg: u32,
fg: u32,
};
label: MessageLabel,
@ -28,7 +28,7 @@ pub fn deinit(self: InfoLine) void {
self.label.deinit();
}
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u16, fg: u16) !void {
pub fn addMessage(self: *InfoLine, text: []const u8, bg: u32, fg: u32) !void {
if (text.len == 0) return;
try self.label.addItem(.{

View File

@ -5,11 +5,11 @@ const termbox = interop.termbox;
pub const Cell = struct {
ch: u32,
fg: u16,
bg: u16,
fg: u32,
bg: u32,
};
pub fn initCell(ch: u32, fg: u16, bg: u16) Cell {
pub fn initCell(ch: u32, fg: u32, bg: u32) Cell {
return .{
.ch = ch,
.fg = fg,