From e9d8c66ff5b59412875f90ebec63fa9e698d6c9b Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Tue, 21 Jul 2026 14:44:44 +0200 Subject: [PATCH] feat: files app - render modified dates in Properties dialog --- kernel/src/Api/BuildNo.hpp | 2 +- programs/src/desktop/apps/apps_common.hpp | 42 +++++++++++++++++++ .../apps/filemanager/filemanager_internal.hpp | 1 + .../desktop/apps/filemanager/filesystem.cpp | 38 ++++++++++------- .../desktop/apps/filemanager/properties.cpp | 14 ++++++- 5 files changed, 81 insertions(+), 16 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 34ee96d..43b7931 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 4 +#define MONTAUK_BUILD_NUMBER 6 diff --git a/programs/src/desktop/apps/apps_common.hpp b/programs/src/desktop/apps/apps_common.hpp index 447e803..be0ba18 100644 --- a/programs/src/desktop/apps/apps_common.hpp +++ b/programs/src/desktop/apps/apps_common.hpp @@ -158,6 +158,48 @@ inline void format_size(char* buf, uint64_t size) { } } +// ============================================================================ +// Date/time formatting +// ============================================================================ + +// Format a UTC unix timestamp as a local "YYYY-MM-DD HH:MM" string. +// tz_offset_minutes shifts UTC to local time (pass montauk::gettz()). A zero +// or negative timestamp -- meaning the filesystem did not record a time -- is +// rendered as "--". buf must hold at least 17 bytes. +inline void format_datetime(char* buf, int buf_size, int64_t unix_utc, int tz_offset_minutes) { + if (buf_size <= 0) return; + if (unix_utc <= 0) { snprintf(buf, buf_size, "--"); return; } + + int64_t t = unix_utc + (int64_t)tz_offset_minutes * 60; + if (t < 0) t = 0; + + int64_t days = t / 86400; + int64_t rem = t % 86400; + int hour = (int)(rem / 3600); + int minute = (int)((rem % 3600) / 60); + + int year = 1970; + for (;;) { + bool leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + int diy = leap ? 366 : 365; + if (days < diy) break; + days -= diy; + year++; + } + + static const int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int month = 0; + for (; month < 12; month++) { + int dim = mdays[month]; + if (month == 1 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) dim = 29; + if (days < dim) break; + days -= dim; + } + + int day = (int)days + 1; + snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d", year, month + 1, day, hour, minute); +} + // ============================================================================ // Desktop builtins // ============================================================================ diff --git a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp index 3631879..b74fb4a 100644 --- a/programs/src/desktop/apps/filemanager/filemanager_internal.hpp +++ b/programs/src/desktop/apps/filemanager/filemanager_internal.hpp @@ -70,6 +70,7 @@ struct FileManagerState { char (*entry_names)[128]; int* entry_types; uint64_t* entry_sizes; // 64-bit: avoids overflow on files >= 2 GiB + int64_t* entry_mtimes; // last-modified time, UTC unix seconds (0 = unknown) int entry_count; int entry_cap; // allocated capacity of the per-entry arrays int selected; diff --git a/programs/src/desktop/apps/filemanager/filesystem.cpp b/programs/src/desktop/apps/filemanager/filesystem.cpp index 3e087e9..2ef23c0 100644 --- a/programs/src/desktop/apps/filemanager/filesystem.cpp +++ b/programs/src/desktop/apps/filemanager/filesystem.cpp @@ -30,6 +30,9 @@ bool filemanager_ensure_entry_capacity(FileManagerState* fm, int needed) { uint64_t* sizes = (uint64_t*)montauk::realloc(fm->entry_sizes, (uint64_t)new_cap * sizeof(uint64_t)); if (!sizes) return false; fm->entry_sizes = sizes; + int64_t* mtimes = (int64_t*)montauk::realloc(fm->entry_mtimes, (uint64_t)new_cap * sizeof(int64_t)); + if (!mtimes) return false; + fm->entry_mtimes = mtimes; bool* isdir = (bool*)montauk::realloc(fm->is_dir, (uint64_t)new_cap * sizeof(bool)); if (!isdir) return false; fm->is_dir = isdir; @@ -81,6 +84,7 @@ void filemanager_free_arrays(FileManagerState* fm) { montauk::mfree(fm->entry_names); fm->entry_names = nullptr; montauk::mfree(fm->entry_types); fm->entry_types = nullptr; montauk::mfree(fm->entry_sizes); fm->entry_sizes = nullptr; + montauk::mfree(fm->entry_mtimes); fm->entry_mtimes = nullptr; montauk::mfree(fm->is_dir); fm->is_dir = nullptr; montauk::mfree(fm->drive_indices); fm->drive_indices = nullptr; montauk::mfree(fm->drive_kinds); fm->drive_kinds = nullptr; @@ -125,6 +129,7 @@ static void filemanager_add_root_entry(FileManagerState* fm, montauk::strncpy(fm->entry_names[idx], name, 63); fm->entry_types[idx] = type; fm->entry_sizes[idx] = 0; + fm->entry_mtimes[idx] = 0; fm->is_dir[idx] = true; fm->drive_indices[idx] = drive_index; fm->drive_kinds[idx] = 0; @@ -143,6 +148,7 @@ static int filemanager_add_virtual_entry(FileManagerState* fm, montauk::strncpy(fm->entry_names[idx], name, 63); fm->entry_types[idx] = FM_ENTRY_VIRTUAL_APP; fm->entry_sizes[idx] = 0; + fm->entry_mtimes[idx] = 0; fm->is_dir[idx] = false; fm->drive_indices[idx] = -1; filemanager_reset_virtual_entry(&fm->virtual_entries[idx]); @@ -322,21 +328,22 @@ void filemanager_read_dir(FileManagerState* fm) { fm->entry_types[i] = detect_file_type(fm->entry_names[i], fm->is_dir[i]); - // Get file size (64-bit: no truncation for files >= 2 GiB) + // Fetch size + modified time in one stat call (works for files and + // directories). Filesystems without stat support leave both 0. fm->entry_sizes[i] = 0; - if (!fm->is_dir[i]) { - char fullpath[512]; - montauk::strcpy(fullpath, fm->current_path); - int plen = montauk::slen(fullpath); - if (plen > 0 && fullpath[plen - 1] != '/') { - str_append(fullpath, "/", 512); - } - str_append(fullpath, fm->entry_names[i], 512); - int fd = montauk::open(fullpath); - if (fd >= 0) { - fm->entry_sizes[i] = montauk::getsize(fd); - montauk::close(fd); - } + fm->entry_mtimes[i] = 0; + char fullpath[512]; + montauk::strcpy(fullpath, fm->current_path); + int plen = montauk::slen(fullpath); + if (plen > 0 && fullpath[plen - 1] != '/') { + str_append(fullpath, "/", 512); + } + str_append(fullpath, fm->entry_names[i], 512); + + montauk::abi::FileStat stt; + if (montauk::stat(fullpath, &stt) == 0) { + if (!fm->is_dir[i]) fm->entry_sizes[i] = stt.size; + fm->entry_mtimes[i] = stt.mtime; } } @@ -349,6 +356,7 @@ void filemanager_read_dir(FileManagerState* fm) { char tmp_name[128]; int tmp_type = fm->entry_types[i]; uint64_t tmp_size = fm->entry_sizes[i]; + int64_t tmp_mtime = fm->entry_mtimes[i]; bool tmp_isdir = fm->is_dir[i]; montauk::strcpy(tmp_name, fm->entry_names[i]); @@ -367,12 +375,14 @@ void filemanager_read_dir(FileManagerState* fm) { montauk::strcpy(fm->entry_names[j + 1], fm->entry_names[j]); fm->entry_types[j + 1] = fm->entry_types[j]; fm->entry_sizes[j + 1] = fm->entry_sizes[j]; + fm->entry_mtimes[j + 1] = fm->entry_mtimes[j]; fm->is_dir[j + 1] = fm->is_dir[j]; j--; } montauk::strcpy(fm->entry_names[j + 1], tmp_name); fm->entry_types[j + 1] = tmp_type; fm->entry_sizes[j + 1] = tmp_size; + fm->entry_mtimes[j + 1] = tmp_mtime; fm->is_dir[j + 1] = tmp_isdir; } diff --git a/programs/src/desktop/apps/filemanager/properties.cpp b/programs/src/desktop/apps/filemanager/properties.cpp index 617bb4c..3bdda21 100644 --- a/programs/src/desktop/apps/filemanager/properties.cpp +++ b/programs/src/desktop/apps/filemanager/properties.cpp @@ -21,10 +21,12 @@ struct FilePropertiesDialogState { char path[512]; char size[32]; char contains[32]; + char modified[24]; bool has_path; bool has_size; bool has_contains; + bool has_modified; bool owns_icon; bool fallback_is_dir; SvgIcon icon; @@ -293,6 +295,14 @@ static void props_populate(FilePropertiesDialogState* st, st->has_size = true; } + // Modified time (files and directories), when the filesystem recorded one. + if (!filemanager_is_computer_view(fm) && !filemanager_is_virtual_view(fm) && + fm->entry_mtimes[idx] > 0) { + format_datetime(st->modified, sizeof(st->modified), + fm->entry_mtimes[idx], montauk::gettz()); + st->has_modified = true; + } + if (props_entry_is_directory_like(fm, idx) && st->has_path) { const char* names[256]; int count = montauk::readdir(st->path, names, 256); @@ -368,6 +378,8 @@ static void props_on_draw(Window* win, Framebuffer& fb) { y = props_draw_row(c, theme, pad, y, 82, "Size", st->size); if (st->has_contains) y = props_draw_row(c, theme, pad, y, 82, "Contains", st->contains); + if (st->has_modified) + y = props_draw_row(c, theme, pad, y, 82, "Modified", st->modified); Rect close = props_close_button_rect(c.w, c.h); mtk::draw_button(c, close, "Close", mtk::BUTTON_PRIMARY, @@ -932,7 +944,7 @@ void filemanager_show_properties(FileManagerState* fm, int target_idx) { props_populate(st, fm, target_idx); int w = 420; - int h = 300; + int h = 330; int wx = 0; int wy = 0; props_center_dialog(ds, fm, w, h, &wx, &wy);