diff --git a/res/config.ini b/res/config.ini index 17db32c..b987e94 100644 --- a/res/config.ini +++ b/res/config.ini @@ -1,14 +1,14 @@ # 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 +# 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 @@ -89,6 +89,9 @@ clock = null # 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 @@ -140,6 +143,22 @@ 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. +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) diff --git a/src/animations/Doom.zig b/src/animations/Doom.zig index 76850fa..9917bcd 100644 --- a/src/animations/Doom.zig +++ b/src/animations/Doom.zig @@ -23,11 +23,11 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, top_color: u const levels = [_]Cell{ - Cell.init(' ', TerminalBuffer.Color.DEFAULT, TerminalBuffer.Color.DEFAULT), - Cell.init(0x2591, top_color, TerminalBuffer.Color.DEFAULT), - Cell.init(0x2592, top_color, TerminalBuffer.Color.DEFAULT), - Cell.init(0x2593, top_color, TerminalBuffer.Color.DEFAULT), - Cell.init(0x2588, top_color, TerminalBuffer.Color.DEFAULT), + Cell.init(' ', terminal_buffer.bg, terminal_buffer.bg), + Cell.init(0x2591, top_color, terminal_buffer.bg), + Cell.init(0x2592, top_color, terminal_buffer.bg), + Cell.init(0x2593, top_color, terminal_buffer.bg), + Cell.init(0x2588, top_color, terminal_buffer.bg), Cell.init(0x2591, middle_color, top_color), Cell.init(0x2592, middle_color, top_color), Cell.init(0x2593, middle_color, top_color), diff --git a/src/animations/Matrix.zig b/src/animations/Matrix.zig index 4069b34..5def466 100644 --- a/src/animations/Matrix.zig +++ b/src/animations/Matrix.zig @@ -11,8 +11,6 @@ pub const FRAME_DELAY: usize = 8; // Characters change mid-scroll pub const MID_SCROLL_CHANGE = true; -const DOT_HEAD_COLOR: u32 = @intCast(TerminalBuffer.Color.WHITE | TerminalBuffer.Styling.BOLD); - const Matrix = @This(); pub const Dot = struct { @@ -33,11 +31,12 @@ lines: []Line, frame: usize, count: usize, fg: u32, +head_col: u32, min_codepoint: u16, max_codepoint: u16, default_cell: Cell, -pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min_codepoint: u16, max_codepoint: u16) !Matrix { +pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, head_col: u32, min_codepoint: u16, max_codepoint: u16) !Matrix { const dots = try allocator.alloc(Dot, terminal_buffer.width * (terminal_buffer.height + 1)); const lines = try allocator.alloc(Line, terminal_buffer.width); @@ -51,6 +50,7 @@ pub fn init(allocator: Allocator, terminal_buffer: *TerminalBuffer, fg: u32, min .frame = 3, .count = 0, .fg = fg, + .head_col = head_col, .min_codepoint = min_codepoint, .max_codepoint = max_codepoint - min_codepoint, .default_cell = .{ .ch = ' ', .fg = fg, .bg = terminal_buffer.bg }, @@ -157,11 +157,13 @@ fn draw(self: *Matrix) void { const dot = self.dots[buf_width * y + x]; const cell = if (dot.value == null or dot.value == ' ') self.default_cell else Cell{ .ch = @intCast(dot.value.?), - .fg = if (dot.is_head) DOT_HEAD_COLOR else self.fg, + .fg = if (dot.is_head) self.head_col else self.fg, .bg = self.terminal_buffer.bg, }; cell.put(x, y - 1); + // Fill background in between columns + self.default_cell.put(x + 1, y - 1); } } } diff --git a/src/config/Config.zig b/src/config/Config.zig index 1aea18a..4fc0af7 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -25,6 +25,7 @@ brightness_up_key: ?[]const u8 = "F6", clear_password: bool = false, clock: ?[:0]const u8 = null, cmatrix_fg: u32 = 0x0000FF00, +cmatrix_head_col: u32 = 0x01FFFFFF, cmatrix_min_codepoint: u16 = 0x21, cmatrix_max_codepoint: u16 = 0x7B, colormix_col1: u32 = 0x00FF0000, @@ -40,6 +41,7 @@ doom_bottom_color: u32 = 0x00FFFFFF, error_bg: u32 = 0x00000000, error_fg: u32 = 0x01FF0000, fg: u32 = 0x00FFFFFF, +full_color: bool = true, gameoflife_fg: u32 = 0x0000FF00, gameoflife_entropy_interval: usize = 10, gameoflife_frame_delay: usize = 6, diff --git a/src/config/migrator.zig b/src/config/migrator.zig index 5f1269b..6d3f73b 100644 --- a/src/config/migrator.zig +++ b/src/config/migrator.zig @@ -1,9 +1,15 @@ -// The migrator ensures compatibility with <=0.6.0 configuration files +// The migrator ensures compatibility with older configuration files +// Properties removed or changed since 0.6.0 +// Color codes interpreted differently since 1.1.0 const std = @import("std"); const ini = @import("zigini"); +const Config = @import("Config.zig"); const Save = @import("Save.zig"); -const enums = @import("../enums.zig"); +const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); + +const Color = TerminalBuffer.Color; +const Styling = TerminalBuffer.Styling; const color_properties = [_][]const u8{ "bg", @@ -16,6 +22,10 @@ const color_properties = [_][]const u8{ "error_fg", "fg", }; + +var set_color_properties = + [_]bool{ false, false, false, false, false, false, false, false, false }; + const removed_properties = [_][]const u8{ "wayland_specifier", "max_desktop_len", @@ -32,6 +42,8 @@ const removed_properties = [_][]const u8{ var temporary_allocator = std.heap.page_allocator; var buffer = std.mem.zeroes([10 * color_properties.len]u8); +pub var auto_eight_colors: bool = true; + pub var maybe_animate: ?bool = null; pub var maybe_save_file: ?[]const u8 = null; @@ -62,15 +74,26 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie return mapped_field; } - inline for (color_properties) |property| { + inline for (color_properties, &set_color_properties) |property, *status| { if (std.mem.eql(u8, field.key, property)) { - // 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; + // Color has been set; it won't be overwritten if we default to eight-color output + status.* = true; - mapped_field.value = mapColor(color) catch return field; - mapped_config_fields = true; - return mapped_field; + // These options now uses a 32-bit RGB value instead of an arbitrary 16-bit integer + // If they're all using eight-color codes, we start in eight-color mode + const color = std.fmt.parseInt(u16, field.value, 0) catch { + auto_eight_colors = false; + return field; + }; + + const color_no_styling = color & 0x00FF; + const styling_only = color & 0xFF00; + + // If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM, + // we have an invalid color, so do not use eight-color mode + if (color_no_styling > 0x0008 or styling_only > 0x8000) auto_eight_colors = false; + + return field; } } @@ -131,14 +154,45 @@ pub fn configFieldHandler(_: std.mem.Allocator, field: ini.IniField) ?ini.IniFie return mapped_field; } + if (std.mem.eql(u8, field.key, "full_color")) { + // If color mode is defined, definitely don't set it automatically + auto_eight_colors = false; + return field; + } + 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 { +pub fn lateConfigFieldHandler(config: *Config) void { if (maybe_animate) |animate| { - if (!animate) animation.* = .none; + if (!animate) config.*.animation = .none; + } + + if (auto_eight_colors) { + // Valid config file predates true-color mode + // Will use eight-color output instead + config.full_color = false; + + // We cannot rely on Config defaults when in eight-color mode, + // because they will appear as undesired colors. + // Instead set color properties to matching eight-color codes + config.doom_top_color = Color.ECOL_RED; + config.doom_middle_color = Color.ECOL_YELLOW; + config.doom_bottom_color = Color.ECOL_WHITE; + config.cmatrix_head_col = Styling.BOLD | Color.ECOL_WHITE; + + // These may be in the config, so only change those which were not set + if (!set_color_properties[0]) config.bg = Color.DEFAULT; + if (!set_color_properties[1]) config.border_fg = Color.ECOL_WHITE; + if (!set_color_properties[2]) config.cmatrix_fg = Color.ECOL_GREEN; + if (!set_color_properties[3]) config.colormix_col1 = Color.ECOL_RED; + if (!set_color_properties[4]) config.colormix_col2 = Color.ECOL_BLUE; + if (!set_color_properties[5]) config.colormix_col3 = Color.ECOL_BLACK; + if (!set_color_properties[6]) config.error_bg = Color.DEFAULT; + if (!set_color_properties[7]) config.error_fg = Styling.BOLD | Color.ECOL_RED; + if (!set_color_properties[8]) config.fg = Color.ECOL_WHITE; } } @@ -172,33 +226,3 @@ pub fn tryMigrateSaveFile(user_buf: *[32]u8) Save { return save; } - -fn mapColor(color: u16) ![]const u8 { - const color_no_styling = color & 0x00FF; - const styling_only = color & 0xFF00; - - // If color is "greater" than TB_WHITE, or the styling is "greater" than TB_DIM, - // we have an invalid color, so return an error - if (color_no_styling > 0x0008 or styling_only > 0x8000) return error.InvalidColor; - - var new_color: u32 = switch (color_no_styling) { - 0x0000 => 0x00000000, // Default - 0x0001 => 0x20000000, // "Hi-black" styling - 0x0002 => 0x00FF0000, // Red - 0x0003 => 0x0000FF00, // Green - 0x0004 => 0x00FFFF00, // Yellow - 0x0005 => 0x000000FF, // Blue - 0x0006 => 0x00FF00FF, // Magenta - 0x0007 => 0x0000FFFF, // Cyan - 0x0008 => 0x00FFFFFF, // White - else => unreachable, - }; - - // Only applying styling if color isn't black and styling isn't also black - if (!(new_color == 0x20000000 and styling_only == 0x20000000)) { - // Shift styling by 16 to the left to apply it to the new 32-bit color - new_color |= @as(u32, @intCast(styling_only)) << 16; - } - - return try std.fmt.bufPrint(&buffer, "0x{X}", .{new_color}); -} diff --git a/src/main.zig b/src/main.zig index aeab079..710d67b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -170,7 +170,9 @@ pub fn main() !void { }) catch migrator.tryMigrateSaveFile(&user_buf); } - migrator.lateConfigFieldHandler(&config.animation); + if (!config_load_failed) { + migrator.lateConfigFieldHandler(&config); + } } else { const config_path = build_options.config_directory ++ "/ly/config.ini"; @@ -198,7 +200,9 @@ pub fn main() !void { }) catch migrator.tryMigrateSaveFile(&user_buf); } - migrator.lateConfigFieldHandler(&config.animation); + if (!config_load_failed) { + migrator.lateConfigFieldHandler(&config); + } } var log_file: std.fs.File = undefined; @@ -250,7 +254,13 @@ pub fn main() !void { }; std.posix.sigaction(std.posix.SIG.TERM, &act, null); - _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR); + if (config.full_color) { + _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR); + try log_writer.writeAll("termbox2 set to 24-bit color output mode\n"); + } else { + try log_writer.writeAll("termbox2 set to eight-color output mode\n"); + } + _ = termbox.tb_clear(); // Let's take some precautions here and clear the back buffer as well @@ -431,7 +441,7 @@ pub fn main() !void { animation = doom.animation(); }, .matrix => { - var matrix = try Matrix.init(allocator, &buffer, config.cmatrix_fg, config.cmatrix_min_codepoint, config.cmatrix_max_codepoint); + var matrix = try Matrix.init(allocator, &buffer, config.cmatrix_fg, config.cmatrix_head_col, config.cmatrix_min_codepoint, config.cmatrix_max_codepoint); animation = matrix.animation(); }, .colormix => { @@ -866,7 +876,10 @@ pub fn main() !void { // Take back control of the TTY _ = termbox.tb_init(); - _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR); + + if (config.full_color) { + _ = termbox.tb_set_output_mode(termbox.TB_OUTPUT_TRUECOLOR); + } const auth_err = shared_err.readError(); if (auth_err) |err| { diff --git a/src/tui/TerminalBuffer.zig b/src/tui/TerminalBuffer.zig index 555f26b..4987208 100644 --- a/src/tui/TerminalBuffer.zig +++ b/src/tui/TerminalBuffer.zig @@ -31,14 +31,22 @@ pub const Styling = struct { pub const Color = struct { pub const DEFAULT = 0x00000000; - pub const BLACK = Styling.HI_BLACK; - pub const RED = 0x00FF0000; - pub const GREEN = 0x0000FF00; - pub const YELLOW = 0x00FFFF00; - pub const BLUE = 0x000000FF; - pub const MAGENTA = 0x00FF00FF; - pub const CYAN = 0x0000FFFF; - pub const WHITE = 0x00FFFFFF; + pub const TRUE_BLACK = Styling.HI_BLACK; + pub const TRUE_RED = 0x00FF0000; + pub const TRUE_GREEN = 0x0000FF00; + pub const TRUE_YELLOW = 0x00FFFF00; + pub const TRUE_BLUE = 0x000000FF; + pub const TRUE_MAGENTA = 0x00FF00FF; + pub const TRUE_CYAN = 0x0000FFFF; + pub const TRUE_WHITE = 0x00FFFFFF; + pub const ECOL_BLACK = 1; + pub const ECOL_RED = 2; + pub const ECOL_GREEN = 3; + pub const ECOL_YELLOW = 4; + pub const ECOL_BLUE = 5; + pub const ECOL_MAGENTA = 6; + pub const ECOL_CYAN = 7; + pub const ECOL_WHITE = 8; }; random: Random,