// 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 OldSave = @import("OldSave.zig"); const SavedUsers = @import("SavedUsers.zig"); const TerminalBuffer = @import("../tui/TerminalBuffer.zig"); const Color = TerminalBuffer.Color; const Styling = TerminalBuffer.Styling; const color_properties = [_][]const u8{ "bg", "border_fg", "cmatrix_fg", "colormix_col1", "colormix_col2", "colormix_col3", "error_bg", "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", "max_login_len", "max_password_len", "mcookie_cmd", "term_reset_cmd", "term_restore_cursor_cmd", "x_cmd_setup", "wayland_cmd", "console_dev", "load", }; var temporary_allocator = std.heap.page_allocator; pub var auto_eight_colors: bool = true; pub var maybe_animate: ?bool = null; pub var maybe_save_file: ?[]const u8 = null; 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"); 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", }; return mapped_field; } inline for (color_properties, &set_color_properties) |property, *status| { if (std.mem.eql(u8, field.key, property)) { // Color has been set; it won't be overwritten if we default to eight-color output status.* = true; // 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; } } if (std.mem.eql(u8, field.key, "blank_password")) { // The option has simply been renamed var mapped_field = field; mapped_field.key = "clear_password"; 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", }; 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; return null; } inline for (removed_properties) |property| { if (std.mem.eql(u8, field.key, property)) { // The options don't exist anymore 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"; } else if (std.mem.eql(u8, field.value, "false")) { mapped_field.value = "none"; } 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(config: *Config) void { if (maybe_animate) |animate| { 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; } } pub fn tryMigrateFirstSaveFile(user_buf: *[32]u8) OldSave { var save = OldSave{}; if (maybe_save_file) |path| { defer temporary_allocator.free(path); var file = std.fs.openFileAbsolute(path, .{}) catch return save; defer file.close(); var file_buffer: [64]u8 = undefined; var file_reader = file.reader(&file_buffer); var reader = &file_reader.interface; var user_writer = std.Io.Writer.fixed(user_buf); var written = reader.streamDelimiter(&user_writer, '\n') catch return save; if (written > 0) save.user = user_buf[0..written]; var session_buf: [20]u8 = undefined; var session_writer = std.Io.Writer.fixed(&session_buf); written = reader.streamDelimiter(&session_writer, '\n') catch return save; var session_index: ?usize = null; if (written > 0) { session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return save; } save.session_index = session_index; } return save; } pub fn tryMigrateIniSaveFile(allocator: std.mem.Allocator, save_ini: *ini.Ini(OldSave), path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !bool { var old_save_file_exists = true; var user_buf: [32]u8 = undefined; const save = save_ini.readFileToStruct(path, .{ .fieldHandler = null, .comment_characters = "#", }) catch no_save_file: { old_save_file_exists = false; break :no_save_file tryMigrateFirstSaveFile(&user_buf); }; if (!old_save_file_exists) return false; // Add all other users to the list for (usernames, 0..) |username, i| { if (save.user) |user| { if (std.mem.eql(u8, user, username)) saved_users.last_username_index = i; } try saved_users.user_list.append(allocator, .{ .username = username, .session_index = save.session_index orelse 0 }); } return true; }