// 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"); var temporary_allocator = std.heap.page_allocator; const ly_ui = @import("ly-ui"); const TerminalBuffer = ly_ui.TerminalBuffer; const Color = TerminalBuffer.Color; const Styling = TerminalBuffer.Styling; const ly_core = ly_ui.ly_core; const IniParser = ly_core.IniParser; const ini = ly_core.ini; const Config = @import("Config.zig"); const OldSave = @import("OldSave.zig"); const SavedUsers = @import("SavedUsers.zig"); const custom = @import("custom.zig"); 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", }; 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; } if (std.mem.eql(u8, field.key, "min_refresh_delta")) { // The option has simply been renamed var mapped_field = field; mapped_field.key = "animation_frame_delay"; return mapped_field; } // TODO: Dearest Melpert, // I pray this message finds you well, as daylight dwindles and the witching hour // approaches, I find it more and more imperative as time continues that I place // this reminder here in such a format that you cannot ignore. // Do you know how long I have been waiting for this petition to be authorized // in regards to this particular segment of computerized instructions? // It has been many a moon since this particular audit has been // posted regarding the position of handling configurable literature // apparatuses and plans for a new feature to the configuration // interface and as time continues onwards I grow more restless // on the progress of said interface, only to find out afterwards // that you have PROCRASTINATED on the efforts meant to enhance // configuration. Thus the requirement for this reminder larger // compared to the two reminders regarding better methods of // X termination detection and new usernames with existing // save files. // // Thus is my que to leave this TODO at thy request, // // Forever Sullied, // // Ly Contributor. // if (std.mem.startsWith(u8, field.header, "cmd:")) { const key = field.header["cmd:".len..]; const keyZ = temporary_allocator.dupe(u8, key) catch ""; if (!custom.binds.contains(key)) { custom.binds.put(temporary_allocator, keyZ, .{}) catch {}; } if (custom.binds.getPtr(keyZ)) |command| { if (std.mem.eql(u8, field.key, "name")) { command.name = temporary_allocator.dupe(u8, field.value) catch ""; } if (std.mem.eql(u8, field.key, "cmd")) { command.cmd = temporary_allocator.dupe(u8, field.value) catch ""; } } } if (std.mem.startsWith(u8, field.header, "lbl:")) { const key = field.header["lbl:".len..]; const keyZ = temporary_allocator.dupe(u8, key) catch ""; if (!custom.labels.contains(keyZ)) { custom.labels.put(temporary_allocator, keyZ, .{ .name = keyZ }) catch {}; } if (custom.labels.getPtr(keyZ)) |label| { if (std.mem.eql(u8, field.key, "cmd")) { label.cmd = temporary_allocator.dupe(u8, field.value) catch ""; } if (std.mem.eql(u8, field.key, "refresh")) { label.refresh = std.fmt.parseInt(u32, field.value, 10) catch 0; } } } 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 tryMigrateIniSaveFile(allocator: std.mem.Allocator, io: std.Io, path: []const u8, saved_users: *SavedUsers, usernames: [][]const u8) !?IniParser(OldSave) { var save_parser = try IniParser(OldSave).init(allocator, io, path, null); errdefer save_parser.deinit(); var user_buf: [32]u8 = undefined; const maybe_save = if (save_parser.maybe_load_error == null) save_parser.structure else tryMigrateFirstSaveFile(io, &user_buf); if (maybe_save) |save| { // 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, .first_run = false, .allocated_username = false, }); } return save_parser; } return null; } fn tryMigrateFirstSaveFile(io: std.Io, user_buf: *[32]u8) ?OldSave { if (maybe_save_file) |path| { defer temporary_allocator.free(path); var save = OldSave{}; var file = std.Io.Dir.openFileAbsolute(io, path, .{}) catch return null; defer file.close(io); var file_buffer: [64]u8 = undefined; var file_reader = file.reader(io, &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 null; 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 null; var session_index: ?usize = null; if (written > 0) { session_index = std.fmt.parseUnsigned(usize, session_buf[0..written], 10) catch return null; } save.session_index = session_index; return save; } return null; }