{ Prog->DiskUtil - GUI disk utility application. Provides a graphical interface for inspecting storage devices, managing partitions, viewing volume information, and formatting volumes with registered filesystems. Layout: sidebar (device/volume list) + detail panel. @author(Aaron Hance ) } unit app.diskutil; interface procedure init(); procedure UnitTest; implementation uses driver.video.desktop, driver.storage.fs.mgr, memory.heap, driver.video.lvgl, driver.storage.vol.mbr, proc.mgr, proc.types, driver.storage.mgr, driver.storage.types, core.strings, io.syslog, debug.tracer, driver.video, driver.storage.vol.mgr, driver.video.windows, core.util, arch.platform.cpu; const WIN_W = 740; WIN_H = 480; SIDEBAR_W = 210; { Selection modes } SEL_NONE = 0; SEL_DEVICE = 1; SEL_VOLUME = 2; var win_id : uint32; proc_pid : uint32; sidebar : Plv_obj; detail : Plv_obj; { Current selection } sel_mode : uint32; sel_dev_idx : uint32; sel_vol_idx : uint32; { Partition slot for pending delete confirmation } pending_del_slot : sint32; { Active msgbox (only one at a time) } active_mbox : Plv_obj; { Widgets kept across dialogs } fmt_dropdown : Plv_obj; fmt_param_area : Plv_obj; fmt_cluster_input : Plv_obj; fmt_filecount_input: Plv_obj; fmt_label_input : Plv_obj; label_target_id, label_target_index : uint32; fmt_param_error_lbl: Plv_obj; add_textarea : Plv_obj; add_err_lbl : Plv_obj; { Sidebar item tracking for highlight } active_sidebar_btn : Plv_obj; fmt_poll_timer : Plv_timer; fmt_done_flag : uint32; fmt_done_error : TError; fmt_done_vol_idx : uint32; storage_dirty_flag : uint32; storage_watch_id : uint32; ui_session_id : uint32; operation_pending : boolean; done_kind, done_device : uint32; type PFmtUICallbackCtx = ^TFmtUICallbackCtx; TFmtUICallbackCtx = record SessionID : uint32; VolumeIdx : uint32; Kind, DeviceIdx : uint32; end; { ============================================================ Forward declarations ============================================================ } procedure launch; forward; procedure diskutil_entry(ctx : PProcessContext); forward; procedure onClose(wid: uint32); forward; procedure refreshSidebar; forward; procedure showDeviceDetail(devIdx: uint32); forward; procedure showVolumeDetail(volIdx: uint32); forward; procedure clearDetail; forward; procedure fmt_poll_cb(tmr: Plv_timer); cdecl; forward; procedure diskutil_storage_event(event : TStorageLifecycleEvent; device : PStorage_Device; volume : PStorage_Volume; error : TError; userdata : pointer); forward; procedure rebuild_fmt_param_ui(fs : PFilesystem); forward; { ============================================================ Helpers — size formatting ============================================================ } { All transient text belongs to gfxd and is released on its next UI tick. LVGL copies labels/options; no widget retains these pointers. A header in the same allocation handles nested concatenations without leaking intermediates. } type PUIText = ^TUIText; TUIText = record Next : PUIText; end; var TextTemps : PUIText = nil; procedure releaseTextTemps; var item : PUIText; begin while TextTemps <> nil do begin item := TextTemps; TextTemps := item^.Next; kfree(void(item)); end; end; function uiConcat(a, b : pchar) : pchar; var item : PUIText; n, m : uint32; dest : pchar; begin uiConcat := ''; n := stringSize(a); m := stringSize(b); item := PUIText(kalloc(sizeof(TUIText)+n+m+1)); if item = nil then exit; item^.Next := TextTemps; TextTemps := item; dest := pchar(PtrUInt(item)+sizeof(TUIText)); if n > 0 then memcpy(PtrUInt(a), PtrUInt(dest), n); if m > 0 then memcpy(PtrUInt(b), PtrUInt(dest)+n, m); dest[n+m] := #0; uiConcat := dest; end; function uiInt(value : uint64) : pchar; var digits : array[0..20] of char; pos : uint32; begin pos := 20; digits[pos] := #0; repeat Dec(pos); digits[pos] := char(ord('0') + value mod 10); value := value div 10; until value = 0; uiInt := uiConcat(@digits[pos], ''); end; procedure operation_done(error : TError; userdata : pointer); forward; function begin_partition_ui(kind : uint32) : PFmtUICallbackCtx; forward; function formatSizeStr(bytes: uint64): pchar; begin if bytes >= 1073741824 then formatSizeStr := uiConcat(uiInt(bytes div 1073741824), ' GB') else if bytes >= 1048576 then formatSizeStr := uiConcat(uiInt(bytes div 1048576), ' MB') else if bytes >= 1024 then formatSizeStr := uiConcat(uiInt(bytes div 1024), ' KB') else formatSizeStr := uiConcat(uiInt(bytes), ' B'); end; function formatSizePrecise(bytes: uint64): pchar; var whole, frac : uint64; begin if bytes >= 1073741824 then begin whole := bytes div 1073741824; frac := ((bytes mod 1073741824) * 10) div 1073741824; if frac > 0 then formatSizePrecise := uiConcat(uiConcat(uiInt(whole), uiConcat('.', uiInt(frac))), ' GB') else formatSizePrecise := uiConcat(uiInt(whole), ' GB'); end else if bytes >= 1048576 then begin whole := bytes div 1048576; frac := ((bytes mod 1048576) * 10) div 1048576; if frac > 0 then formatSizePrecise := uiConcat(uiConcat(uiInt(whole), uiConcat('.', uiInt(frac))), ' MB') else formatSizePrecise := uiConcat(uiInt(whole), ' MB'); end else if bytes >= 1024 then begin whole := bytes div 1024; formatSizePrecise := uiConcat(uiInt(whole), ' KB'); end else formatSizePrecise := uiConcat(uiInt(bytes), ' B'); end; function controllerStr(ct: TControllerType): pchar; begin controllerStr := driver.storage.mgr.controller_type_2_string(ct); end; function boolStr(b: boolean): pchar; begin if b then boolStr := 'Yes' else boolStr := 'No'; end; function formatErrorName(err : TError) : pchar; begin case err of eNone: formatErrorName := 'eNone'; eUnknown: formatErrorName := 'eUnknown'; eNotSupported: formatErrorName := 'eNotSupported'; eOutOfMemory: formatErrorName := 'eOutOfMemory'; eInvalidArgument: formatErrorName := 'eInvalidArgument'; eFileInUse: formatErrorName := 'eFileInUse'; eFileDoesNotExist: formatErrorName := 'eFileDoesNotExist'; eInvalidFileName: formatErrorName := 'eInvalidFileName'; eInvalidFileExtension: formatErrorName := 'eInvalidFileExtension'; eFilenameTooLong: formatErrorName := 'eFilenameTooLong'; eDirectoryDoesNotExist: formatErrorName := 'eDirectoryDoesNotExist'; eDirectoryAlreadyExists: formatErrorName := 'eDirectoryAlreadyExists'; eDirectoryNotEmpty: formatErrorName := 'eDirectoryNotEmpty'; eDirectoryFull: formatErrorName := 'eDirectoryFull'; eNotADirectory: formatErrorName := 'eNotADirectory'; eWriteOnly: formatErrorName := 'eWriteOnly'; eReadOnly: formatErrorName := 'eReadOnly'; ePermissionDenied: formatErrorName := 'ePermissionDenied'; eInvalidPath: formatErrorName := 'eInvalidPath'; eTooManyOpenFiles: formatErrorName := 'eTooManyOpenFiles'; eInvalidHandle: formatErrorName := 'eInvalidHandle'; eFileNotLoaded: formatErrorName := 'eFileNotLoaded'; eAlreadyExists: formatErrorName := 'eAlreadyExists'; eDiskFull: formatErrorName := 'eDiskFull'; eIOError: formatErrorName := 'eIOError'; eIOTimeout: formatErrorName := 'eIOTimeout'; eIOCancelled: formatErrorName := 'eIOCancelled'; eDeviceNotReady: formatErrorName := 'eDeviceNotReady'; eCorruptFilesystem: formatErrorName := 'eCorruptFilesystem'; eBadSector: formatErrorName := 'eBadSector'; eAlreadyMounted: formatErrorName := 'eAlreadyMounted'; eNotMounted: formatErrorName := 'eNotMounted'; eUnsupportedFilesystem: formatErrorName := 'eUnsupportedFilesystem'; eDeviceNotFound: formatErrorName := 'eDeviceNotFound'; eDeviceRemoved: formatErrorName := 'eDeviceRemoved'; eQueueFull: formatErrorName := 'eQueueFull'; eNoFreeSlot: formatErrorName := 'eNoFreeSlot'; eInvalidPartitionTable: formatErrorName := 'eInvalidPartitionTable'; eVolumeNotFound: formatErrorName := 'eVolumeNotFound'; else formatErrorName := 'eUnknown'; end; end; { Check if a pchar string contains only digits } function isNumericStr(s: pchar): boolean; var i, len : uint32; begin isNumericStr := false; if s = nil then exit; len := stringSize(s); if len = 0 then exit; for i := 0 to len - 1 do begin if (s[i] < '0') or (s[i] > '9') then exit; end; isNumericStr := true; end; { ============================================================ GUI helpers ============================================================ } procedure addInfoRow(parent: Plv_obj; row_w: sint32; key: pchar; value: pchar); var row, lbl_key, lbl_val : Plv_obj; begin row := lv_obj_create(parent); lv_obj_remove_style_all(row); lv_obj_set_size(row, row_w - 24, 24); lv_obj_set_style_pad_all(row, 0, 0); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_layout(row, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(row, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lbl_key := lv_label_create(row); lv_label_set_text(lbl_key, key); lv_obj_set_style_text_color(lbl_key, lv_color_make(140, 150, 170), 0); lv_obj_set_style_text_font(lbl_key, @lv_font_montserrat_14, 0); lbl_val := lv_label_create(row); lv_label_set_text(lbl_val, value); lv_obj_set_style_text_color(lbl_val, lv_color_make(220, 225, 240), 0); lv_obj_set_style_text_font(lbl_val, @lv_font_montserrat_14, 0); end; procedure addSectionHeader(parent: Plv_obj; title: pchar); var lbl : Plv_obj; begin lbl := lv_label_create(parent); lv_label_set_text(lbl, title); lv_obj_set_style_text_color(lbl, lv_color_make(100, 160, 255), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); end; procedure addSeparator(parent: Plv_obj; row_w: sint32); var sep : Plv_obj; begin sep := lv_obj_create(parent); lv_obj_remove_style_all(sep); lv_obj_set_size(sep, row_w - 24, 1); lv_obj_set_style_bg_color(sep, lv_color_make(55, 60, 78), 0); lv_obj_set_style_bg_opa(sep, LV_OPA_COVER, 0); lv_obj_remove_flag(sep, LV_OBJ_FLAG_SCROLLABLE); end; procedure addStatusLabel(parent: Plv_obj; text: pchar; r, g, b: uint8); var lbl : Plv_obj; begin lbl := lv_label_create(parent); lv_label_set_text(lbl, text); lv_obj_set_style_text_color(lbl, lv_color_make(r, g, b), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); end; function makeButton(parent: Plv_obj; text: pchar; w: sint32): Plv_obj; var btn, lbl : Plv_obj; begin btn := lv_button_create(parent); lv_obj_remove_style_all(btn); lv_obj_set_size(btn, w, 30); lv_obj_set_style_bg_color(btn, lv_color_make(55, 90, 190), 0); lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); lv_obj_set_style_radius(btn, 6, 0); lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_layout(btn, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lbl := lv_label_create(btn); lv_label_set_text(lbl, text); lv_obj_set_style_text_color(lbl, lv_color_make(225, 230, 245), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); makeButton := btn; end; function makeDangerButton(parent: Plv_obj; text: pchar; w: sint32): Plv_obj; var btn, lbl : Plv_obj; begin btn := lv_button_create(parent); lv_obj_remove_style_all(btn); lv_obj_set_size(btn, w, 26); lv_obj_set_style_bg_color(btn, lv_color_make(160, 50, 50), 0); lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_layout(btn, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lbl := lv_label_create(btn); lv_label_set_text(lbl, text); lv_obj_set_style_text_color(lbl, lv_color_make(240, 220, 220), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); makeDangerButton := btn; end; { Add a usage bar (colored bar showing used vs total) } procedure addUsageBar(parent: Plv_obj; row_w: sint32; used, total: uint64); var bar : Plv_obj; pct : sint32; begin if total = 0 then pct := 0 else pct := sint32((used * 100) div total); if pct > 100 then pct := 100; bar := lv_bar_create(parent); lv_obj_set_size(bar, row_w - 24, 14); lv_bar_set_range(bar, 0, 100); lv_bar_set_value(bar, pct, LV_ANIM_OFF); { Track background } lv_obj_set_style_bg_color(bar, lv_color_make(45, 48, 60), LV_PART_MAIN); lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_PART_MAIN); lv_obj_set_style_radius(bar, 4, LV_PART_MAIN); { Fill indicator } if pct < 75 then lv_obj_set_style_bg_color(bar, lv_color_make(70, 160, 100), LV_PART_INDICATOR) else if pct < 90 then lv_obj_set_style_bg_color(bar, lv_color_make(200, 170, 50), LV_PART_INDICATOR) else lv_obj_set_style_bg_color(bar, lv_color_make(200, 60, 60), LV_PART_INDICATOR); lv_obj_set_style_bg_opa(bar, LV_OPA_COVER, LV_PART_INDICATOR); lv_obj_set_style_radius(bar, 4, LV_PART_INDICATOR); end; { ============================================================ Size parser — accepts '500MB', '2GB', '1024KB', '4096B', or plain number (bytes). Returns sector count. Returns 0 on invalid input. ============================================================ } function parseSize(s: pchar; sectorSize: uint32): uint32; var i, digit : uint32; value, limit, multiplier, bytes : uint64; begin parseSize := 0; if (s = nil) or (sectorSize = 0) then exit; limit := uint64($FFFFFFFF) * sectorSize; i := 0; value := 0; while (s[i] >= '0') and (s[i] <= '9') do begin digit := ord(s[i])-ord('0'); if value > (limit-digit) div 10 then exit; value := value*10+digit; Inc(i); end; if (i = 0) or (value = 0) then exit; multiplier := 1; case s[i] of 'K','k': begin multiplier := 1024; Inc(i); end; 'M','m': begin multiplier := 1048576; Inc(i); end; 'G','g': begin multiplier := 1073741824; Inc(i); end; end; if (s[i] = 'B') or (s[i] = 'b') then Inc(i); if s[i] <> #0 then exit; if value > limit div multiplier then exit; bytes := value*multiplier; if (bytes mod sectorSize) <> 0 then exit; parseSize := uint32(bytes div sectorSize); end; procedure closeMsgBox; begin if active_mbox <> nil then begin lv_msgbox_close(active_mbox); active_mbox := nil; end; add_textarea := nil; add_err_lbl := nil; fmt_dropdown := nil; fmt_param_area := nil; fmt_cluster_input := nil; fmt_filecount_input := nil; fmt_label_input := nil; fmt_param_error_lbl := nil; end; procedure set_fmt_param_error(text : pchar); begin if fmt_param_area = nil then exit; if fmt_param_error_lbl = nil then begin fmt_param_error_lbl := lv_label_create(fmt_param_area); lv_obj_set_style_text_color(fmt_param_error_lbl, lv_color_make(230, 80, 80), 0); lv_obj_set_style_text_font(fmt_param_error_lbl, @lv_font_montserrat_14, 0); end; lv_label_set_text(fmt_param_error_lbl, text); end; procedure rebuild_fmt_param_ui(fs : PFilesystem); var lbl : Plv_obj; begin if fmt_param_area = nil then exit; fmt_cluster_input := nil; fmt_filecount_input := nil; fmt_label_input := nil; fmt_param_error_lbl := nil; lv_obj_clean(fmt_param_area); if fs = nil then exit; if fs^.formatParamFlags = 0 then begin addStatusLabel(fmt_param_area, 'No filesystem-specific format options.', 140, 150, 170); exit; end; if (fs^.formatParamFlags and FS_FORMAT_PARAM_CLUSTER_SIZE) <> 0 then begin lbl := lv_label_create(fmt_param_area); lv_label_set_text(lbl, 'Sectors per cluster (blank = auto, power of 2)'); lv_obj_set_style_text_color(lbl, lv_color_make(180, 190, 210), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); fmt_cluster_input := lv_textarea_create(fmt_param_area); lv_textarea_set_one_line(fmt_cluster_input, true); lv_textarea_set_placeholder_text(fmt_cluster_input, 'Auto'); lv_obj_set_width(fmt_cluster_input, 220); end; if (fs^.formatParamFlags and FS_FORMAT_PARAM_FILE_COUNT) <> 0 then begin lbl := lv_label_create(fmt_param_area); lv_label_set_text(lbl, 'Directory/file table entry count'); lv_obj_set_style_text_color(lbl, lv_color_make(180, 190, 210), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); fmt_filecount_input := lv_textarea_create(fmt_param_area); lv_textarea_set_one_line(fmt_filecount_input, true); lv_textarea_set_placeholder_text(fmt_filecount_input, '1000'); lv_obj_set_width(fmt_filecount_input, 220); end; if (fs^.formatParamFlags and FS_FORMAT_PARAM_LABEL) <> 0 then begin lbl := lv_label_create(fmt_param_area); lv_label_set_text(lbl, 'Volume label (up to 11 ASCII characters)'); fmt_label_input := lv_textarea_create(fmt_param_area); lv_textarea_set_one_line(fmt_label_input,true); lv_textarea_set_max_length(fmt_label_input,11); lv_textarea_set_placeholder_text(fmt_label_input,'Optional'); lv_obj_set_width(fmt_label_input,260); end; end; procedure fmt_fs_change_cb(e: Plv_event); cdecl; var code : uint32; selIdx : uint32; fs : PFilesystem; begin code := lv_event_get_code(e); if code <> LV_EVENT_VALUE_CHANGED then exit; selIdx := lv_dropdown_get_selected(fmt_dropdown); fs := driver.storage.fs.mgr.get_filesystem(selIdx); rebuild_fmt_param_ui(fs); end; { ============================================================ Dialog callbacks — Add Partition ============================================================ } procedure add_part_ok_cb(e: Plv_event); cdecl; var code : uint32; device : PStorage_Device; text : pchar; sectors : uint32; freeSec : uint32; slot : sint32; lba : uint32; part : TPartition_table; mbr_rec : PMaster_Boot_Record; needsInit : boolean; cbCtx : PFmtUICallbackCtx; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; device := driver.storage.mgr.get_device(sel_dev_idx); if device = nil then begin closeMsgBox; exit; end; { Check cached mbr to decide if disk needs initialisation } mbr_rec := driver.storage.mgr.get_cached_mbr(device); needsInit := (mbr_rec = nil) or (mbr_rec^.boot_sector <> $AA55); if needsInit then freeSec := device^.maxSectorCount - 1 else freeSec := driver.storage.vol.mgr.get_free_sector_count(device); text := lv_textarea_get_text(add_textarea); sectors := 0; { Parse size if provided } if (text <> nil) and (text^ <> #0) then begin sectors := parseSize(text, device^.sectorSize); if sectors = 0 then begin if add_err_lbl <> nil then lv_label_set_text(add_err_lbl, 'Invalid size. Use e.g. 100MB, 2GB, 512KB.') else begin add_err_lbl := lv_label_create(lv_msgbox_get_content(active_mbox)); lv_label_set_text(add_err_lbl, 'Invalid size. Use e.g. 100MB, 2GB, 512KB.'); lv_obj_set_style_text_color(add_err_lbl, lv_color_make(230, 80, 80), 0); lv_obj_set_style_text_font(add_err_lbl, @lv_font_montserrat_14, 0); end; exit; end; if sectors > freeSec then begin if add_err_lbl <> nil then lv_label_set_text(add_err_lbl, 'Requested size exceeds available free space.') else begin add_err_lbl := lv_label_create(lv_msgbox_get_content(active_mbox)); lv_label_set_text(add_err_lbl, 'Requested size exceeds available free space.'); lv_obj_set_style_text_color(add_err_lbl, lv_color_make(230, 80, 80), 0); lv_obj_set_style_text_font(add_err_lbl, @lv_font_montserrat_14, 0); end; exit; end; end; { If blank, use all free space } if sectors = 0 then sectors := freeSec; if sectors = 0 then begin closeMsgBox; exit; end; { If disk needs init, do it now (async write, but cache updated synchronously) } if needsInit then begin { Blank MBR and first partition are committed in one worker write. } slot := 0; lba := 1; end else begin slot := driver.storage.vol.mgr.find_free_slot(device); if slot < 0 then begin closeMsgBox; exit; end; lba := driver.storage.vol.mgr.find_free_space(device, sectors); if lba = 0 then begin if add_err_lbl <> nil then lv_label_set_text(add_err_lbl, 'Could not find contiguous free space.') else begin add_err_lbl := lv_label_create(lv_msgbox_get_content(active_mbox)); lv_label_set_text(add_err_lbl, 'Could not find contiguous free space.'); lv_obj_set_style_text_color(add_err_lbl, lv_color_make(230, 80, 80), 0); lv_obj_set_style_text_font(add_err_lbl, @lv_font_montserrat_14, 0); end; exit; end; end; memset(PtrUInt(@part), 0, sizeof(TPartition_table)); driver.storage.vol.mbr.setup_partition(@part, lba, sectors); cbCtx := begin_partition_ui(1); if cbCtx = nil then exit; driver.storage.vol.mgr.add_partition_async(device, uint32(slot), part, @operation_done, cbCtx); end; procedure add_part_cancel_cb(e: Plv_event); cdecl; var code : uint32; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; closeMsgBox; end; { ============================================================ Dialog callbacks — Delete Partition ============================================================ } procedure del_part_ok_cb(e: Plv_event); cdecl; var device : PStorage_Device; cbCtx : PFmtUICallbackCtx; slot : uint32; begin if lv_event_get_code(e) <> LV_EVENT_CLICKED then exit; if operation_pending then exit; device := driver.storage.mgr.get_device(sel_dev_idx); if (device = nil) or (pending_del_slot < 0) or (pending_del_slot > 3) then exit; slot := uint32(pending_del_slot); cbCtx := begin_partition_ui(2); if cbCtx = nil then exit; driver.storage.vol.mgr.remove_partition_async(device, slot, @operation_done, cbCtx); end; procedure del_part_cancel_cb(e: Plv_event); cdecl; var code : uint32; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; closeMsgBox; end; { Per-row delete button click — opens confirmation for that specific slot } procedure row_del_click_cb(e: Plv_event); cdecl; var code : uint32; slot : uint32; mbox : Plv_obj; btn_ok : Plv_obj; btn_no : Plv_obj; txt : pchar; device : PStorage_Device; mbr_rec : PMaster_Boot_Record; part : TPartition_table; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; slot := uint32(lv_event_get_user_data(e)); pending_del_slot := sint32(slot); txt := uiConcat('Delete partition in slot ', uiInt(slot)); device := driver.storage.mgr.get_device(sel_dev_idx); if device <> nil then begin mbr_rec := driver.storage.mgr.get_cached_mbr(device); if mbr_rec <> nil then begin part := mbr_rec^.partition[slot]; if part.sector_count > 0 then begin txt := uiConcat(txt, ' ('); txt := uiConcat(txt, formatSizeStr(uint64(part.sector_count) * device^.sectorSize)); txt := uiConcat(txt, ')'); end; end; end; txt := uiConcat(txt, '?'); closeMsgBox; mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Delete Partition'); lv_msgbox_add_text(mbox, txt); addStatusLabel(lv_msgbox_get_content(mbox), 'This action cannot be undone.', 230, 160, 80); btn_ok := lv_msgbox_add_footer_button(mbox, 'Delete'); lv_obj_add_event_cb(btn_ok, @del_part_ok_cb, LV_EVENT_CLICKED, nil); btn_no := lv_msgbox_add_footer_button(mbox, 'Cancel'); lv_obj_add_event_cb(btn_no, @del_part_cancel_cb, LV_EVENT_CLICKED, nil); end; { ============================================================ Dialog callbacks — Format Volume ============================================================ } { Async completion callback — fired from ISR context when format finishes. Only sets completion flags; LVGL work happens later on the UI poll timer. } procedure operation_done(error : TError; userdata : pointer); var ctx : PFmtUICallbackCtx; flags : PtrUInt; begin ctx := PFmtUICallbackCtx(userdata); if ctx <> nil then begin flags := arch.platform.cpu.lock; if ctx^.SessionID = ui_session_id then begin fmt_done_error := error; fmt_done_vol_idx := ctx^.VolumeIdx; done_kind := ctx^.Kind; done_device := ctx^.DeviceIdx; puint32(@fmt_done_flag)^ := 1; end; arch.platform.cpu.unlock(flags); kfree(void(ctx)); end; end; procedure diskutil_storage_event(event : TStorageLifecycleEvent; device : PStorage_Device; volume : PStorage_Volume; error : TError; userdata : pointer); begin puint32(@storage_dirty_flag)^ := 1; end; procedure fmt_poll_cb(tmr: Plv_timer); cdecl; var mbox : Plv_obj; begin releaseTextTemps; if puint32(@storage_dirty_flag)^ <> 0 then begin puint32(@storage_dirty_flag)^ := 0; refreshSidebar; if sel_mode = SEL_VOLUME then begin if driver.storage.vol.mgr.get_volume(sel_vol_idx) <> nil then showVolumeDetail(sel_vol_idx) else clearDetail; end else if sel_mode = SEL_DEVICE then begin if driver.storage.mgr.get_device(sel_dev_idx) <> nil then showDeviceDetail(sel_dev_idx) else clearDetail; end; end; if puint32(@fmt_done_flag)^ <> 1 then exit; puint32(@fmt_done_flag)^ := 0; closeMsgBox; refreshSidebar; operation_pending := false; if done_kind = 0 then showVolumeDetail(fmt_done_vol_idx) else showDeviceDetail(done_device); if fmt_done_error <> eNone then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Storage Operation Failed'); lv_msgbox_add_text(mbox, 'The operation did not complete. Check the error before retrying.'); lv_msgbox_add_text(mbox, formatErrorName(fmt_done_error)); lv_obj_add_event_cb(lv_msgbox_add_footer_button(mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end else begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Storage Operation Complete'); lv_msgbox_add_text(mbox, 'The requested changes have been written successfully.'); lv_obj_add_event_cb(lv_msgbox_add_footer_button(mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end; end; function begin_partition_ui(kind : uint32) : PFmtUICallbackCtx; var ctx : PFmtUICallbackCtx; begin begin_partition_ui := nil; if operation_pending then exit; ctx := PFmtUICallbackCtx(kalloc(sizeof(TFmtUICallbackCtx))); if ctx = nil then begin closeMsgBox; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Out of memory'); lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; ctx^.SessionID := ui_session_id; ctx^.VolumeIdx := sel_vol_idx; ctx^.Kind := kind; ctx^.DeviceIdx := sel_dev_idx; closeMsgBox; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Updating partition table'); lv_msgbox_add_text(active_mbox, 'The operation is running in the background.'); operation_pending := true; begin_partition_ui := ctx; end; procedure fmt_ok_cb(e: Plv_event); cdecl; var code : uint32; selIdx : uint32; fs : PFilesystem; vol : PStorage_Volume; cbCtx : PFmtUICallbackCtx; params : TFSFormatParams; paramPtr : PFSFormatParams; text : pchar; value : uint32; normalized : TVolumeLabel; begin if operation_pending then exit; code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; vol := driver.storage.vol.mgr.get_volume(sel_vol_idx); if vol = nil then begin closeMsgBox; exit; end; if driver.storage.vol.mgr.volume_is_busy(vol) then begin closeMsgBox; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Format Busy'); lv_msgbox_add_text(active_mbox, 'This volume is busy. Wait for the current operation to finish.'); lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; selIdx := lv_dropdown_get_selected(fmt_dropdown); fs := driver.storage.fs.mgr.get_filesystem(selIdx); if fs = nil then begin closeMsgBox; exit; end; memset(PtrUInt(@params), 0, sizeof(TFSFormatParams)); params.Version := FS_FORMAT_PARAMS_VERSION_1; paramPtr := nil; if (fs^.formatParamFlags and FS_FORMAT_PARAM_CLUSTER_SIZE) <> 0 then begin if fmt_cluster_input <> nil then begin text := lv_textarea_get_text(fmt_cluster_input); if (text <> nil) and (text^ <> #0) then begin if (stringSize(text) > 5) or (not isNumericStr(text)) then begin set_fmt_param_error('Cluster size must be a number.'); exit; end; value := stringToInt(text); if (value = 0) or (value > 128) or ((value and (value - 1)) <> 0) then begin set_fmt_param_error('Cluster size must be a power of two from 1 to 128.'); exit; end; params.Flags := params.Flags or FS_FORMAT_PARAM_CLUSTER_SIZE; params.ClusterSize := value; paramPtr := @params; end; end; end; if (fs^.formatParamFlags and FS_FORMAT_PARAM_FILE_COUNT) <> 0 then begin if fmt_filecount_input <> nil then begin text := lv_textarea_get_text(fmt_filecount_input); if (text <> nil) and (text^ <> #0) then begin if (stringSize(text) > 5) or (not isNumericStr(text)) then begin set_fmt_param_error('File count must be a number.'); exit; end; value := stringToInt(text); if (value = 0) or (value > 65535) then begin set_fmt_param_error('File count must be from 1 to 65535.'); exit; end; params.Flags := params.Flags or FS_FORMAT_PARAM_FILE_COUNT; params.FileCount := value; paramPtr := @params; end; end; end; if (fmt_label_input <> nil) and ((fs^.formatParamFlags and FS_FORMAT_PARAM_LABEL) <> 0) then begin text := lv_textarea_get_text(fmt_label_input); if (fs^.validateLabelCallback = nil) then exit; if fs^.validateLabelCallback(text,normalized) <> eNone then begin set_fmt_param_error('Use up to 11 ASCII characters without punctuation such as / or :'); exit; end; memcpy(PtrUInt(@normalized[0]),PtrUInt(@params.LabelText[0]),12); params.Flags := params.Flags or FS_FORMAT_PARAM_LABEL; paramPtr := @params; end; closeMsgBox; cbCtx := PFmtUICallbackCtx(kalloc(sizeof(TFmtUICallbackCtx))); if cbCtx = nil then begin active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Out of memory'); lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; cbCtx^.SessionID := ui_session_id; cbCtx^.VolumeIdx := sel_vol_idx; cbCtx^.Kind := 0; cbCtx^.DeviceIdx := sel_dev_idx; { Submit async format — returns immediately, fmt_done_cb only flips a flag. } if not driver.storage.vol.mgr.format_volume_async(vol^.device, sel_vol_idx, fs^.sName, paramPtr, @operation_done, cbCtx) then begin kfree(void(cbCtx)); closeMsgBox; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Format Failed'); lv_msgbox_add_text(active_mbox, 'Cannot start: device busy, unsupported geometry/options, or insufficient memory.'); lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox, 'OK'), @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end else begin operation_pending := true; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox, 'Formatting'); lv_msgbox_add_text(active_mbox, 'Formatting is running in the background.'); lv_msgbox_add_text(active_mbox, 'The volume will remount automatically when complete.'); end; end; procedure fmt_cancel_cb(e: Plv_event); cdecl; var code : uint32; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; closeMsgBox; end; { ============================================================ Button click handler — Add Partition ============================================================ } procedure btn_add_part_cb(e: Plv_event); cdecl; var code : uint32; device : PStorage_Device; mbox : Plv_obj; body : Plv_obj; btn_ok : Plv_obj; btn_no : Plv_obj; freeSec : uint32; freeSlot: sint32; infoTxt : pchar; freeSlotCnt : uint32; i : sint32; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; closeMsgBox; device := driver.storage.mgr.get_device(sel_dev_idx); if device = nil then exit; { Validate: writable } if not device^.writable then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Cannot Add Partition'); lv_msgbox_add_text(mbox, 'This device is read-only.'); btn_no := lv_msgbox_add_footer_button(mbox, 'OK'); lv_obj_add_event_cb(btn_no, @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; { Validate: free slot available } freeSlot := driver.storage.vol.mgr.find_free_slot(device); if freeSlot < 0 then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Cannot Add Partition'); lv_msgbox_add_text(mbox, 'All 4 partition slots are in use.'); btn_no := lv_msgbox_add_footer_button(mbox, 'OK'); lv_obj_add_event_cb(btn_no, @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; { Validate: free space exists } freeSec := driver.storage.vol.mgr.get_free_sector_count(device); if freeSec = 0 then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Cannot Add Partition'); lv_msgbox_add_text(mbox, 'No free space available on this device.'); btn_no := lv_msgbox_add_footer_button(mbox, 'OK'); lv_obj_add_event_cb(btn_no, @add_part_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; { Count free slots } freeSlotCnt := 0; for i := 0 to 3 do begin if driver.storage.vol.mgr.find_free_slot(device) >= 0 then freeSlotCnt := freeSlotCnt + 1; end; { Simpler: just use the volumes count } if device^.volumes <> nil then freeSlotCnt := 4 - device^.volumes^.Count else freeSlotCnt := 4; { Show dialog with context info } mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Add Partition'); infoTxt := uiConcat('Free space: ', formatSizePrecise(uint64(freeSec) * device^.sectorSize)); infoTxt := uiConcat(infoTxt, ' | Free slots: '); infoTxt := uiConcat(infoTxt, uiInt(freeSlotCnt)); lv_msgbox_add_text(mbox, infoTxt); lv_msgbox_add_text(mbox, 'Enter size (e.g. 100MB, 2GB) or leave blank for all free space:'); body := lv_msgbox_get_content(mbox); add_textarea := lv_textarea_create(body); lv_textarea_set_one_line(add_textarea, true); lv_textarea_set_placeholder_text(add_textarea, formatSizeStr(uint64(freeSec) * device^.sectorSize)); lv_obj_set_width(add_textarea, 280); add_err_lbl := nil; btn_ok := lv_msgbox_add_footer_button(mbox, 'Create'); lv_obj_add_event_cb(btn_ok, @add_part_ok_cb, LV_EVENT_CLICKED, nil); btn_no := lv_msgbox_add_footer_button(mbox, 'Cancel'); lv_obj_add_event_cb(btn_no, @add_part_cancel_cb, LV_EVENT_CLICKED, nil); end; { ============================================================ Button click handler — Format Volume ============================================================ } procedure btn_format_cb(e: Plv_event); cdecl; var code : uint32; vol : PStorage_Volume; mbox : Plv_obj; body : Plv_obj; btn_ok : Plv_obj; btn_no : Plv_obj; i : uint32; fs : PFilesystem; opts : pchar; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; closeMsgBox; vol := driver.storage.vol.mgr.get_volume(sel_vol_idx); if vol = nil then exit; if driver.storage.vol.mgr.volume_is_busy(vol) then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Cannot Format'); lv_msgbox_add_text(mbox, 'This volume is busy or already being formatted.'); btn_no := lv_msgbox_add_footer_button(mbox, 'OK'); lv_obj_add_event_cb(btn_no, @fmt_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; { Check writable } if (vol^.device <> nil) and (not vol^.device^.writable) then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Cannot Format'); lv_msgbox_add_text(mbox, 'The underlying device is read-only.'); btn_no := lv_msgbox_add_footer_button(mbox, 'OK'); lv_obj_add_event_cb(btn_no, @fmt_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; { Check filesystems available } if driver.storage.fs.mgr.get_filesystem_count() = 0 then begin mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Cannot Format'); lv_msgbox_add_text(mbox, 'No filesystems are registered.'); btn_no := lv_msgbox_add_footer_button(mbox, 'OK'); lv_obj_add_event_cb(btn_no, @fmt_cancel_cb, LV_EVENT_CLICKED, nil); exit; end; mbox := lv_msgbox_create(lv_layer_top); active_mbox := mbox; lv_msgbox_add_title(mbox, 'Format Volume'); lv_msgbox_add_text(mbox, 'Select filesystem. This will erase all data on the volume.'); body := lv_msgbox_get_content(mbox); addStatusLabel(body, uiConcat('Volume size: ', formatSizePrecise(uint64(vol^.sectorCount) * vol^.sectorSize)), 160, 170, 190); { Build newline-separated options for dropdown } opts := nil; for i := 0 to driver.storage.fs.mgr.get_filesystem_count() - 1 do begin fs := driver.storage.fs.mgr.get_filesystem(i); if fs <> nil then begin if opts = nil then opts := fs^.sName else begin opts := uiConcat(opts, #10); opts := uiConcat(opts, fs^.sName); end; end; end; fmt_dropdown := lv_dropdown_create(body); lv_obj_set_width(fmt_dropdown, 280); if opts <> nil then lv_dropdown_set_options(fmt_dropdown, opts); lv_obj_add_event_cb(fmt_dropdown, @fmt_fs_change_cb, LV_EVENT_VALUE_CHANGED, nil); fmt_param_area := lv_obj_create(body); lv_obj_remove_style_all(fmt_param_area); lv_obj_set_width(fmt_param_area, 300); lv_obj_set_style_layout(fmt_param_area, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(fmt_param_area, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_row(fmt_param_area, 4, 0); lv_obj_set_style_pad_all(fmt_param_area, 0, 0); lv_obj_remove_flag(fmt_param_area, LV_OBJ_FLAG_SCROLLABLE); fs := driver.storage.fs.mgr.get_filesystem(lv_dropdown_get_selected(fmt_dropdown)); rebuild_fmt_param_ui(fs); btn_ok := lv_msgbox_add_footer_button(mbox, 'Format'); lv_obj_add_event_cb(btn_ok, @fmt_ok_cb, LV_EVENT_CLICKED, nil); btn_no := lv_msgbox_add_footer_button(mbox, 'Cancel'); lv_obj_add_event_cb(btn_no, @fmt_cancel_cb, LV_EVENT_CLICKED, nil); end; { ============================================================ Clear the detail panel ============================================================ } procedure clearDetail; begin if detail <> nil then lv_obj_clean(detail); sel_mode := SEL_NONE; end; { Find the volume that corresponds to a partition on a device } function findVolumeForPartition(device: PStorage_Device; lbaStart: uint32): PStorage_Volume; var i : uint32; vol : PStorage_Volume; volCnt : uint32; begin findVolumeForPartition := nil; volCnt := driver.storage.vol.mgr.get_volume_count(); if volCnt > 0 then for i := 0 to volCnt - 1 do begin vol := driver.storage.vol.mgr.get_volume(i); if vol = nil then continue; if (vol^.device = device) and (vol^.sectorStart = lbaStart) then begin findVolumeForPartition := vol; exit; end; end; end; { ============================================================ Build a single partition row with inline delete button. Only called for non-empty partitions. ============================================================ } procedure addPartitionRow(parent: Plv_obj; row_w: sint32; slot: uint32; part: TPartition_table; sectorSize: uint32; device: PStorage_Device; canDelete: boolean); var row : Plv_obj; lbl : Plv_obj; del_btn : Plv_obj; sizeStr : pchar; vol : PStorage_Volume; freeStr : pchar; totalB : uint64; freeB : uint64; begin row := lv_obj_create(parent); lv_obj_remove_style_all(row); lv_obj_set_size(row, row_w - 24, 30); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_layout(row, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_column(row, 6, 0); lv_obj_set_style_pad_left(row, 6, 0); lv_obj_set_style_pad_right(row, 6, 0); lv_obj_set_style_bg_color(row, lv_color_make(42, 46, 58), 0); lv_obj_set_style_bg_opa(row, LV_OPA_COVER, 0); lv_obj_set_style_radius(row, 4, 0); { Slot number badge } lbl := lv_label_create(row); lv_label_set_text(lbl, uiInt(slot)); lv_obj_set_style_text_color(lbl, lv_color_make(100, 160, 255), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 20); { System ID } lbl := lv_label_create(row); lv_label_set_text(lbl, uiConcat('ID:', uiInt(part.system_id))); lv_obj_set_style_text_color(lbl, lv_color_make(160, 170, 190), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 55); { Size } totalB := uint64(part.sector_count) * sectorSize; sizeStr := formatSizeStr(totalB); lbl := lv_label_create(row); lv_label_set_text(lbl, sizeStr); lv_obj_set_style_text_color(lbl, lv_color_make(220, 225, 240), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 70); { Free space — look up the corresponding volume } vol := findVolumeForPartition(device, part.LBA_start); if vol <> nil then begin freeB := uint64(vol^.freeSectors) * vol^.sectorSize; if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then freeStr := 'Free: unknown' else freeStr := uiConcat(formatSizeStr(freeB), ' free'); lbl := lv_label_create(row); lv_label_set_text(lbl, freeStr); lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 80); end else begin lbl := lv_label_create(row); lv_label_set_text(lbl, 'No FS'); lv_obj_set_style_text_color(lbl, lv_color_make(120, 125, 145), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 80); end; { Boot indicator } if driver.storage.vol.mbr.get_bootable(@part) then begin lbl := lv_label_create(row); lv_label_set_text(lbl, 'BOOT'); lv_obj_set_style_text_color(lbl, lv_color_make(100, 200, 120), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); end; { Delete button } if canDelete then begin del_btn := makeDangerButton(row, 'Del', 42); lv_obj_add_event_cb(del_btn, @row_del_click_cb, LV_EVENT_CLICKED, pointer(slot)); end; end; { ============================================================ Show device detail — device info + partition list + actions ============================================================ } procedure showDeviceDetail(devIdx: uint32); var device : PStorage_Device; btn_row : Plv_obj; btn : Plv_obj; dw : sint32; i : uint32; totalBytes : uint64; freeBytes : uint64; volCnt : uint32; devVolCnt : uint32; vol : PStorage_Volume; fsName : pchar; row : Plv_obj; lbl : Plv_obj; sizeStr : pchar; freeStr : pchar; totalB : uint64; freeB : uint64; begin clearDetail; sel_mode := SEL_DEVICE; sel_dev_idx := devIdx; device := driver.storage.mgr.get_device(devIdx); if device = nil then begin addStatusLabel(detail, 'Device not found.', 230, 80, 80); exit; end; dw := WIN_W - SIDEBAR_W - 20; { ---- Device Info ---- } addSectionHeader(detail, 'Device Info'); addInfoRow(detail, dw, 'Device Index', uiInt(devIdx)); addInfoRow(detail, dw, 'Controller', controllerStr(device^.controller)); addInfoRow(detail, dw, 'Writable', boolStr(device^.writable)); addInfoRow(detail, dw, 'Sector Size', uiConcat(uiInt(device^.sectorSize), ' bytes')); totalBytes := uint64(device^.maxSectorCount) * device^.sectorSize; addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes)); { Use the same cached partition geometry as Add Partition. Volumes may still be undergoing filesystem probing after the device is published. } devVolCnt := 0; volCnt := driver.storage.vol.mgr.get_volume_count(); if volCnt > 0 then for i := 0 to volCnt - 1 do begin vol := driver.storage.vol.mgr.get_volume(i); if (vol <> nil) and (vol^.device = device) then begin devVolCnt := devVolCnt + 1; end; end; freeBytes := uint64(driver.storage.vol.mgr.get_free_sector_count(device)) * device^.sectorSize; addInfoRow(detail, dw, 'Unallocated Space', formatSizePrecise(freeBytes)); addInfoRow(detail, dw, 'Volumes', uiInt(devVolCnt)); { Usage bar } if totalBytes > 0 then addUsageBar(detail, dw, totalBytes - freeBytes, totalBytes); addSeparator(detail, dw); { ---- Volumes on this device (from cached data — no disk I/O) ---- } addSectionHeader(detail, 'Volumes'); if devVolCnt = 0 then addStatusLabel(detail, 'No volumes found.', 120, 125, 145) else begin if volCnt > 0 then for i := 0 to volCnt - 1 do begin vol := driver.storage.vol.mgr.get_volume(i); if (vol = nil) or (vol^.device <> device) then continue; row := lv_obj_create(detail); lv_obj_remove_style_all(row); lv_obj_set_size(row, dw - 24, 30); lv_obj_remove_flag(row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_layout(row, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(row, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lv_obj_set_style_pad_column(row, 6, 0); lv_obj_set_style_pad_left(row, 6, 0); lv_obj_set_style_pad_right(row, 6, 0); lv_obj_set_style_bg_color(row, lv_color_make(42, 46, 58), 0); lv_obj_set_style_bg_opa(row, LV_OPA_COVER, 0); lv_obj_set_style_radius(row, 4, 0); { Filesystem name } if vol^.filesystem <> nil then fsName := vol^.filesystem^.sName else fsName := 'Unknown'; lbl := lv_label_create(row); if vol^.labelText[0] <> #0 then lv_label_set_text(lbl,@vol^.labelText[0]) else lv_label_set_text(lbl, fsName); lv_obj_set_style_text_color(lbl, lv_color_make(100, 160, 255), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 70); { LBA start } lbl := lv_label_create(row); lv_label_set_text(lbl, uiConcat('LBA:', uiInt(vol^.sectorStart))); lv_obj_set_style_text_color(lbl, lv_color_make(160, 170, 190), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 80); { Size } totalB := uint64(vol^.sectorCount) * vol^.sectorSize; sizeStr := formatSizeStr(totalB); lbl := lv_label_create(row); lv_label_set_text(lbl, sizeStr); lv_obj_set_style_text_color(lbl, lv_color_make(220, 225, 240), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 70); { Free space } freeB := uint64(vol^.freeSectors) * vol^.sectorSize; if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then freeStr := 'Free: unknown' else freeStr := uiConcat(formatSizeStr(freeB), ' free'); lbl := lv_label_create(row); lv_label_set_text(lbl, freeStr); lv_obj_set_style_text_color(lbl, lv_color_make(100, 180, 130), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_set_width(lbl, 80); end; end; addSeparator(detail, dw); { ---- Action buttons ---- } if device^.writable then begin btn_row := lv_obj_create(detail); lv_obj_remove_style_all(btn_row); lv_obj_set_size(btn_row, dw - 24, 38); lv_obj_remove_flag(btn_row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_layout(btn_row, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(btn_row, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_column(btn_row, 8, 0); lv_obj_set_flex_align(btn_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); btn := makeButton(btn_row, 'Add Partition', 130); lv_obj_add_event_cb(btn, @btn_add_part_cb, LV_EVENT_CLICKED, nil); end else begin addStatusLabel(detail, 'Device is read-only.', 160, 150, 130); end; end; { ============================================================ Show volume detail — volume info + format action ============================================================ } procedure label_save_cb(e : Plv_event); cdecl; var vol : PStorage_Volume; ctx : PFmtUICallbackCtx; value : TVolumeLabel; err : TError; begin if (lv_event_get_code(e) <> LV_EVENT_CLICKED) or operation_pending then exit; vol := driver.storage.vol.mgr.get_volume(label_target_index); if (vol = nil) or (vol^.id <> label_target_id) or (vol^.filesystem = nil) then begin lv_label_set_text(add_err_lbl,'The selected volume is no longer available.'); exit; end; if vol^.filesystem^.validateLabelCallback = nil then exit; err := vol^.filesystem^.validateLabelCallback(lv_textarea_get_text(fmt_label_input),value); if err <> eNone then begin lv_label_set_text(add_err_lbl,'Invalid label. Use ASCII letters, digits, spaces, _ or -.'); exit; end; ctx := PFmtUICallbackCtx(kalloc(sizeof(TFmtUICallbackCtx))); if ctx = nil then begin lv_label_set_text(add_err_lbl,'Out of memory.'); exit; end; ctx^.SessionID := ui_session_id; ctx^.VolumeIdx := label_target_index; ctx^.Kind := 0; ctx^.DeviceIdx := sel_dev_idx; err := driver.storage.vol.mgr.set_volume_label_async(vol^.device,label_target_index,@value[0],@operation_done,ctx); if err <> eNone then begin kfree(void(ctx)); lv_label_set_text(add_err_lbl,formatErrorName(err)); exit; end; operation_pending := true; closeMsgBox; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox,'Saving volume label'); lv_msgbox_add_text(active_mbox,'Updating the label on disk.'); end; procedure label_edit_cb(e : Plv_event); cdecl; var vol : PStorage_Volume; content : Plv_obj; begin if (lv_event_get_code(e) <> LV_EVENT_CLICKED) or operation_pending then exit; vol := driver.storage.vol.mgr.get_volume(sel_vol_idx); if (vol = nil) or (vol^.filesystem = nil) then exit; if vol^.filesystem^.setLabelCallback = nil then exit; closeMsgBox; label_target_id := vol^.id; label_target_index := sel_vol_idx; active_mbox := lv_msgbox_create(lv_layer_top); lv_msgbox_add_title(active_mbox,'Rename Volume'); lv_msgbox_add_text(active_mbox,'Up to 11 ASCII characters, stored in uppercase. Leave blank to remove the label.'); content := lv_msgbox_get_content(active_mbox); fmt_label_input := lv_textarea_create(content); lv_textarea_set_one_line(fmt_label_input,true); lv_textarea_set_max_length(fmt_label_input,11); lv_textarea_set_text(fmt_label_input,@vol^.labelText[0]); lv_obj_set_width(fmt_label_input,300); add_err_lbl := lv_label_create(content); lv_label_set_text(add_err_lbl,''); lv_obj_set_style_text_color(add_err_lbl,lv_color_make(230,80,80),0); lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox,'Save'),@label_save_cb,LV_EVENT_CLICKED,nil); lv_obj_add_event_cb(lv_msgbox_add_footer_button(active_mbox,'Cancel'),@add_part_cancel_cb,LV_EVENT_CLICKED,nil); end; procedure showVolumeDetail(volIdx: uint32); var vol : PStorage_Volume; dw : sint32; totalBytes : uint64; freeBytes : uint64; usedBytes : uint64; fsName : pchar; btn_row : Plv_obj; btn : Plv_obj; pctUsed : uint32; devIdx : uint32; i : uint32; begin clearDetail; sel_mode := SEL_VOLUME; sel_vol_idx := volIdx; vol := driver.storage.vol.mgr.get_volume(volIdx); if vol = nil then begin addStatusLabel(detail, 'Volume not found.', 230, 80, 80); exit; end; dw := WIN_W - SIDEBAR_W - 20; addSectionHeader(detail, 'Volume Info'); addInfoRow(detail, dw, 'Volume Index', uiInt(volIdx)); if vol^.labelText[0] <> #0 then addInfoRow(detail,dw,'Label',@vol^.labelText[0]) else addInfoRow(detail,dw,'Label','Unlabelled'); { Find parent device index } devIdx := 0; if driver.storage.mgr.get_device_count() > 0 then for i := 0 to driver.storage.mgr.get_device_count() - 1 do begin if driver.storage.mgr.get_device(i) = vol^.device then begin devIdx := i; break; end; end; addInfoRow(detail, dw, 'Parent Device', uiConcat('Disk ', uiInt(devIdx))); if vol^.filesystem <> nil then fsName := vol^.filesystem^.sName else fsName := 'Unknown'; addInfoRow(detail, dw, 'Filesystem', fsName); addInfoRow(detail, dw, 'Sector Start', uiInt(vol^.sectorStart)); addInfoRow(detail, dw, 'Sector Count', uiInt(vol^.sectorCount)); addInfoRow(detail, dw, 'Sector Size', uiConcat(uiInt(vol^.sectorSize), ' bytes')); totalBytes := uint64(vol^.sectorCount) * vol^.sectorSize; freeBytes := uint64(vol^.freeSectors) * vol^.sectorSize; if freeBytes > totalBytes then freeBytes := totalBytes; usedBytes := totalBytes - freeBytes; addInfoRow(detail, dw, 'Total Size', formatSizePrecise(totalBytes)); if (vol^.lifecycleFlags and STORAGE_VOLUME_FLAG_FREE_UNKNOWN) <> 0 then begin addInfoRow(detail, dw, 'Free Space', 'Unknown'); addInfoRow(detail, dw, 'Used Space', 'Unknown'); end else begin addInfoRow(detail, dw, 'Free Space', formatSizePrecise(freeBytes)); addInfoRow(detail, dw, 'Used Space', formatSizePrecise(usedBytes)); if totalBytes > 0 then begin pctUsed := (usedBytes * 100) div totalBytes; addInfoRow(detail, dw, 'Usage', uiConcat(uiInt(pctUsed), '%')); addUsageBar(detail, dw, usedBytes, totalBytes); end; end; addInfoRow(detail, dw, 'Boot Drive', boolStr(vol^.isBootDrive)); addSeparator(detail, dw); { ---- Action buttons ---- } btn_row := lv_obj_create(detail); lv_obj_remove_style_all(btn_row); lv_obj_set_size(btn_row, dw - 24, 38); lv_obj_remove_flag(btn_row, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_style_layout(btn_row, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(btn_row, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_column(btn_row, 8, 0); lv_obj_set_flex_align(btn_row, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); if (vol^.device <> nil) and vol^.device^.writable then begin if driver.storage.vol.mgr.volume_is_formatting(vol) then addStatusLabel(detail, 'Format in progress. The volume will remount when complete.', 210, 180, 110) else begin btn := makeButton(btn_row, 'Format', 100); lv_obj_add_event_cb(btn, @btn_format_cb, LV_EVENT_CLICKED, nil); if vol^.filesystem <> nil then if vol^.filesystem^.setLabelCallback <> nil then begin btn := makeButton(btn_row,'Rename Volume',150); lv_obj_add_event_cb(btn,@label_edit_cb,LV_EVENT_CLICKED,nil); end; end; end else begin addStatusLabel(detail, 'Device is read-only. Format disabled.', 160, 150, 130); end; end; { ============================================================ Sidebar highlight ============================================================ } procedure highlightSidebarBtn(btn: Plv_obj); begin if active_sidebar_btn <> nil then lv_obj_set_style_border_width(active_sidebar_btn, 0, 0); active_sidebar_btn := btn; if btn <> nil then begin lv_obj_set_style_border_width(btn, 2, 0); lv_obj_set_style_border_color(btn, lv_color_make(100, 160, 255), 0); end; end; procedure sidebar_dev_click_hl(e: Plv_event); cdecl; var code : uint32; idx : uint32; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; idx := uint32(lv_event_get_user_data(e)); highlightSidebarBtn(lv_event_get_target(e)); showDeviceDetail(idx); end; procedure sidebar_vol_click_hl(e: Plv_event); cdecl; var code : uint32; idx : uint32; begin code := lv_event_get_code(e); if code <> LV_EVENT_CLICKED then exit; idx := uint32(lv_event_get_user_data(e)); highlightSidebarBtn(lv_event_get_target(e)); showVolumeDetail(idx); end; { ============================================================ Sidebar — build/rebuild the device and volume list ============================================================ } procedure refreshSidebar; var i, j : uint32; devCnt : uint32; volCnt : uint32; device : PStorage_Device; vol : PStorage_Volume; btn : Plv_obj; lbl : Plv_obj; hdr : Plv_obj; sizeStr : pchar; begin if sidebar = nil then exit; active_sidebar_btn := nil; lv_obj_clean(sidebar); { Sidebar title } hdr := lv_label_create(sidebar); lv_label_set_text(hdr, 'Storage'); lv_obj_set_style_text_color(hdr, lv_color_make(200, 205, 220), 0); lv_obj_set_style_text_font(hdr, @lv_font_montserrat_14, 0); devCnt := driver.storage.mgr.get_device_count(); if devCnt > 0 then for i := 0 to devCnt - 1 do begin device := driver.storage.mgr.get_device(i); if device = nil then continue; { Section label for this device } addSectionHeader(sidebar, uiConcat('Disk ', uiInt(i))); { Clickable device row } btn := lv_button_create(sidebar); lv_obj_remove_style_all(btn); lv_obj_set_size(btn, SIDEBAR_W - 24, 34); lv_obj_set_style_bg_color(btn, lv_color_make(45, 50, 68), 0); lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); lv_obj_set_style_radius(btn, 6, 0); lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_pad_left(btn, 10, 0); lv_obj_set_style_pad_right(btn, 8, 0); lv_obj_set_style_layout(btn, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lbl := lv_label_create(btn); lv_label_set_text(lbl, controllerStr(device^.controller)); lv_obj_set_style_text_color(lbl, lv_color_make(210, 215, 230), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); sizeStr := formatSizeStr(uint64(device^.maxSectorCount) * device^.sectorSize); lbl := lv_label_create(btn); lv_label_set_text(lbl, sizeStr); lv_obj_set_style_text_color(lbl, lv_color_make(140, 150, 170), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_add_event_cb(btn, @sidebar_dev_click_hl, LV_EVENT_CLICKED, pointer(i)); { Auto-highlight if currently selected } if (sel_mode = SEL_DEVICE) and (sel_dev_idx = i) then highlightSidebarBtn(btn); { List volumes that belong to this device } volCnt := driver.storage.vol.mgr.get_volume_count(); if volCnt > 0 then for j := 0 to volCnt - 1 do begin vol := driver.storage.vol.mgr.get_volume(j); if vol = nil then continue; if vol^.device <> device then continue; btn := lv_button_create(sidebar); lv_obj_remove_style_all(btn); lv_obj_set_size(btn, SIDEBAR_W - 36, 28); lv_obj_set_style_bg_color(btn, lv_color_make(38, 42, 56), 0); lv_obj_set_style_bg_opa(btn, LV_OPA_COVER, 0); lv_obj_set_style_radius(btn, 4, 0); lv_obj_add_flag(btn, LV_OBJ_FLAG_CLICKABLE); lv_obj_set_style_pad_left(btn, 14, 0); lv_obj_set_style_pad_right(btn, 8, 0); lv_obj_set_style_layout(btn, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(btn, LV_FLEX_FLOW_ROW); lv_obj_set_flex_align(btn, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); lbl := lv_label_create(btn); if vol^.labelText[0] <> #0 then lv_label_set_text(lbl,@vol^.labelText[0]) else if vol^.filesystem <> nil then lv_label_set_text(lbl, vol^.filesystem^.sName) else lv_label_set_text(lbl, 'Volume'); lv_obj_set_style_text_color(lbl, lv_color_make(185, 195, 215), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); sizeStr := formatSizeStr(uint64(vol^.sectorCount) * vol^.sectorSize); lbl := lv_label_create(btn); lv_label_set_text(lbl, sizeStr); lv_obj_set_style_text_color(lbl, lv_color_make(120, 130, 150), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lv_obj_add_event_cb(btn, @sidebar_vol_click_hl, LV_EVENT_CLICKED, pointer(j)); if (sel_mode = SEL_VOLUME) and (sel_vol_idx = j) then highlightSidebarBtn(btn); end; end; { Empty state placeholder } if devCnt = 0 then begin lbl := lv_label_create(sidebar); lv_label_set_text(lbl, 'No storage devices'); lv_obj_set_style_text_color(lbl, lv_color_make(120, 125, 145), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lbl := lv_label_create(sidebar); lv_label_set_text(lbl, 'detected.'); lv_obj_set_style_text_color(lbl, lv_color_make(120, 125, 145), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); end; end; { ============================================================ Window close handler ============================================================ } procedure diskutil_entry(ctx : PProcessContext); begin while (ctx^.State <> psFinished) and (ctx^.PendingMsg <> smKill) and (ctx^.PendingMsg <> smTerminate) do proc.mgr.proc_yield; end; procedure onClose(wid: uint32); begin debug.tracer.push_trace('diskutil.onClose'); closeMsgBox; releaseTextTemps; operation_pending := false; ui_session_id := ui_session_id + 1; puint32(@fmt_done_flag)^ := 0; puint32(@storage_dirty_flag)^ := 0; if fmt_poll_timer <> nil then begin lv_timer_delete(fmt_poll_timer); fmt_poll_timer := nil; end; if storage_watch_id <> 0 then begin driver.storage.vol.mgr.unwatch_lifecycle(storage_watch_id); storage_watch_id := 0; end; if proc_pid <> 0 then begin proc.mgr.kill(proc_pid); proc_pid := 0; end; driver.video.windows.destroyWindow(wid); win_id := 0; sidebar := nil; detail := nil; active_sidebar_btn := nil; debug.tracer.pop_trace; end; { ============================================================ Launch — create the window and populate it ============================================================ } procedure launch; var scr_w, scr_h : sint32; wx, wy : sint32; content : Plv_obj; lbl : Plv_obj; ctx : PProcessContext; begin debug.tracer.push_trace('diskutil.launch'); if driver.video.windows.isWindowOpen(win_id) then begin debug.tracer.pop_trace; exit; end; scr_w := sint32(driver.video.frontBufferWidth); scr_h := sint32(driver.video.frontBufferHeight); wx := (scr_w - WIN_W) div 2; wy := (scr_h - WIN_H) div 2 - 30; win_id := driver.video.windows.createWindow( 'Disk Utility', wx, wy, WIN_W, WIN_H, @onClose, nil ); if win_id = 0 then begin debug.tracer.pop_trace; exit; end; content := driver.video.windows.getWindowContent(win_id); if content = nil then begin debug.tracer.pop_trace; exit; end; { Content: horizontal flex row for sidebar + detail } lv_obj_set_style_layout(content, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(content, LV_FLEX_FLOW_ROW); lv_obj_set_style_pad_all(content, 0, 0); lv_obj_set_style_pad_column(content, 0, 0); lv_obj_remove_flag(content, LV_OBJ_FLAG_SCROLLABLE); { ---- Sidebar ---- } sidebar := lv_obj_create(content); lv_obj_remove_style_all(sidebar); lv_obj_set_size(sidebar, SIDEBAR_W, lv_pct(100)); lv_obj_set_style_bg_color(sidebar, lv_color_make(28, 31, 40), 0); lv_obj_set_style_bg_opa(sidebar, LV_OPA_COVER, 0); lv_obj_set_style_pad_all(sidebar, 10, 0); lv_obj_set_style_pad_row(sidebar, 4, 0); lv_obj_set_style_layout(sidebar, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(sidebar, LV_FLEX_FLOW_COLUMN); lv_obj_add_flag(sidebar, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_scrollbar_mode(sidebar, LV_SCROLLBAR_MODE_AUTO); lv_obj_set_style_border_width(sidebar, 1, 0); lv_obj_set_style_border_color(sidebar, lv_color_make(48, 52, 68), 0); { ---- Detail panel ---- } detail := lv_obj_create(content); lv_obj_remove_style_all(detail); lv_obj_set_size(detail, WIN_W - SIDEBAR_W - 20, lv_pct(100)); lv_obj_set_style_bg_color(detail, lv_color_make(35, 38, 48), 0); lv_obj_set_style_bg_opa(detail, LV_OPA_COVER, 0); lv_obj_set_style_pad_all(detail, 14, 0); lv_obj_set_style_pad_row(detail, 6, 0); lv_obj_set_style_layout(detail, LV_LAYOUT_FLEX, 0); lv_obj_set_flex_flow(detail, LV_FLEX_FLOW_COLUMN); lv_obj_add_flag(detail, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_scrollbar_mode(detail, LV_SCROLLBAR_MODE_AUTO); { Placeholder text } lbl := lv_label_create(detail); lv_label_set_text(lbl, 'Select a device or volume'); lv_obj_set_style_text_color(lbl, lv_color_make(120, 125, 145), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); lbl := lv_label_create(detail); lv_label_set_text(lbl, 'from the sidebar.'); lv_obj_set_style_text_color(lbl, lv_color_make(120, 125, 145), 0); lv_obj_set_style_text_font(lbl, @lv_font_montserrat_14, 0); { Reset state } operation_pending := false; sel_mode := SEL_NONE; sel_dev_idx := 0; sel_vol_idx := 0; pending_del_slot := -1; active_mbox := nil; fmt_dropdown := nil; fmt_param_area := nil; fmt_cluster_input := nil; fmt_filecount_input := nil; fmt_label_input := nil; fmt_param_error_lbl := nil; add_textarea := nil; add_err_lbl := nil; active_sidebar_btn := nil; fmt_done_flag := 0; fmt_done_error := eNone; fmt_done_vol_idx := 0; storage_dirty_flag := 0; fmt_poll_timer := lv_timer_create(@fmt_poll_cb, 50, nil); if fmt_poll_timer = nil then begin onClose(win_id); exit; end; storage_watch_id := driver.storage.vol.mgr.watch_lifecycle(@diskutil_storage_event, nil); refreshSidebar; { Create process so app.diskutil appears in PS and is manageable } ctx := proc.mgr.create('Disk Utility', @diskutil_entry, nil, 1); if ctx <> nil then begin proc_pid := ctx^.ProcessID; driver.video.windows.setWindowOwner(win_id, ctx^.ProcessID); end; debug.tracer.pop_trace; end; { ============================================================ Init — register with the driver.video.desktop program launcher ============================================================ } procedure UnitTest; var passed, failed, sectors, sectorSize : uint32; procedure Assert(ok : boolean; name : pchar); begin if ok then Inc(passed) else begin Inc(failed); io.syslog.logln('DISKUTIL FAIL', name); end; end; begin passed := 0; failed := 0; sectors := 8388608; sectorSize := 512; Assert(stringEquals(formatSizeStr(uint64(sectors)*sectorSize),'4 GB'),'4GB label'); sectors := 10485760; Assert(stringEquals(formatSizePrecise(uint64(sectors)*sectorSize),'5 GB'),'5GB label'); releaseTextTemps; Assert(TextTemps = nil,'temporary labels released'); Assert(parseSize('4GB',512) = 8388608, '4GB does not wrap to zero'); Assert(parseSize('5GB',512) = 10485760, '5GB does not wrap to 1GB'); Assert(parseSize('999999999999999999999GB',512) = 0, 'reject decimal overflow'); Assert(parseSize('2097152GB',512) = 0, 'reject LBA overflow'); Assert(parseSize('513B',512) = 0, 'reject fractional sectors'); Assert(parseSize('512B',0) = 0, 'reject zero sector size'); Assert(parseSize('',512) = 0, 'reject empty size'); io.syslog.log('DISKUTIL', 'Passed: '); io.syslog.writeint(passed); io.syslog.writestring(' Failed: '); io.syslog.writeintln(failed); end; procedure init(); begin debug.tracer.push_trace('diskutil.init'); win_id := 0; proc_pid := 0; sidebar := nil; detail := nil; sel_mode := SEL_NONE; sel_dev_idx := 0; sel_vol_idx := 0; pending_del_slot := -1; active_mbox := nil; fmt_dropdown := nil; fmt_param_area := nil; fmt_cluster_input := nil; fmt_filecount_input := nil; fmt_label_input := nil; fmt_param_error_lbl := nil; add_textarea := nil; add_err_lbl := nil; active_sidebar_btn := nil; fmt_poll_timer := nil; fmt_done_flag := 0; fmt_done_error := eNone; fmt_done_vol_idx := 0; storage_dirty_flag := 0; storage_watch_id := 0; ui_session_id := 1; driver.video.desktop.registerProgram('Disk Utility', @launch); debug.tracer.pop_trace; end; end.