diff --git a/kernel/src/Api/Syscall.cpp b/kernel/src/Api/Syscall.cpp index 477a873..5948287 100644 --- a/kernel/src/Api/Syscall.cpp +++ b/kernel/src/Api/Syscall.cpp @@ -333,6 +333,10 @@ namespace Montauk { return Sys_BtInfo((BtAdapterInfo*)frame->arg1); case SYS_SUSPEND: return Sys_Suspend(); + case SYS_SETTZ: + return Sys_SetTZ((int32_t)frame->arg1); + case SYS_GETTZ: + return Sys_GetTZ(); default: return -1; } diff --git a/kernel/src/Api/Syscall.hpp b/kernel/src/Api/Syscall.hpp index f5cfad2..639a2b1 100644 --- a/kernel/src/Api/Syscall.hpp +++ b/kernel/src/Api/Syscall.hpp @@ -174,6 +174,10 @@ namespace Montauk { /* Power.hpp */ static constexpr uint64_t SYS_SUSPEND = 89; + /* Time.hpp */ + static constexpr uint64_t SYS_SETTZ = 90; + static constexpr uint64_t SYS_GETTZ = 91; + static constexpr int SOCK_TCP = 1; static constexpr int SOCK_UDP = 2; diff --git a/kernel/src/Api/Time.hpp b/kernel/src/Api/Time.hpp index efc85ca..b64e972 100644 --- a/kernel/src/Api/Time.hpp +++ b/kernel/src/Api/Time.hpp @@ -29,4 +29,13 @@ namespace Montauk { out->Minute = dt.Minute; out->Second = dt.Second; } + + static int64_t Sys_SetTZ(int32_t offsetMinutes) { + Timekeeping::SetTZOffset(offsetMinutes); + return 0; + } + + static int64_t Sys_GetTZ() { + return (int64_t)Timekeeping::GetTZOffset(); + } }; \ No newline at end of file diff --git a/kernel/src/Timekeeping/Time.cpp b/kernel/src/Timekeeping/Time.cpp index 2c527a6..3175f35 100644 --- a/kernel/src/Timekeeping/Time.cpp +++ b/kernel/src/Timekeeping/Time.cpp @@ -11,6 +11,7 @@ using namespace Kt; static int64_t g_bootEpoch = 0; +static int g_tzOffsetMinutes = 60; /* Default: UTC+1 (CET) until userspace overrides */ static bool IsLeapYear(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); @@ -68,34 +69,29 @@ static Timekeeping::DateTime EpochToDate(int64_t epoch) { void Timekeeping::Init(uint16_t Year, uint8_t Month, uint8_t Day, uint8_t Hour, uint8_t Minute, uint8_t Second) { g_bootEpoch = DateToEpoch(Year, Month, Day, Hour, Minute, Second); - /* Hardcode CET for now */ - TimeZone CET = { - "Central European Time", - "CET", - 1, /* UTC+1 */ - 0, - false - }; - Kt::KernelLogStream(INFO, "Timekeeping Service") << "Setting time zone to " << CET.TZLongName << " (" << CET.TZShortName << ")"; + /* Apply default timezone offset for boot log display */ + int adjMin = Minute + (g_tzOffsetMinutes % 60); + int adjHour = Hour + (g_tzOffsetMinutes / 60); + if (adjMin < 0) { adjMin += 60; adjHour -= 1; } + if (adjMin >= 60) { adjMin -= 60; adjHour += 1; } + if (adjHour < 0) adjHour += 24; + if (adjHour >= 24) adjHour -= 24; - Minute = Minute + CET.MinuteOffset; - Hour = Hour + CET.HourOffset; - if (Minute >= 60) { - Minute -= 60; - Hour += 1; - } - if (Hour >= 24) { - Hour -= 24; - Day += 1; - /* Note: No month/day overflow handling yet */ - } + int offH = g_tzOffsetMinutes / 60; + int offM = g_tzOffsetMinutes % 60; + if (offM < 0) offM = -offM; + + Kt::KernelLogStream(INFO, "Timekeeping Service") << "Time zone: UTC" + << (offH >= 0 ? "+" : "") << offH + << (offM ? ":" : "") << (offM >= 10 ? "" : (offM ? "0" : "")) + << (offM ? offM : 0); kcp::cstringstream minuteStream; - if (Minute < 10) { + if (adjMin < 10) { minuteStream << "0"; } - minuteStream << Minute; + minuteStream << adjMin; CString minuteStr = minuteStream.c_str(); kcp::cstringstream secondStream; @@ -111,12 +107,10 @@ void Timekeeping::Init(uint16_t Year, uint8_t Month, uint8_t Day, uint8_t Hour, << Day << " " << Months[Month] << " " << Year << ", " - << Hour << ":" - + << adjHour << ":" << minuteStr << ":" << secondStr - << " (" << CET.TZLongName << ")"; - + << " (UTC" << (offH >= 0 ? "+" : "") << offH << ")"; CString dateString = panelStr.c_str(); @@ -128,5 +122,13 @@ int64_t Timekeeping::GetUnixTimestamp() { } Timekeeping::DateTime Timekeeping::GetDateTime() { - return EpochToDate(GetUnixTimestamp()); + return EpochToDate(GetUnixTimestamp() + (int64_t)g_tzOffsetMinutes * 60); +} + +void Timekeeping::SetTZOffset(int totalMinutes) { + g_tzOffsetMinutes = totalMinutes; +} + +int Timekeeping::GetTZOffset() { + return g_tzOffsetMinutes; } \ No newline at end of file diff --git a/kernel/src/Timekeeping/Time.hpp b/kernel/src/Timekeeping/Time.hpp index 23dd516..599180d 100644 --- a/kernel/src/Timekeeping/Time.hpp +++ b/kernel/src/Timekeeping/Time.hpp @@ -53,4 +53,7 @@ namespace Timekeeping { void Init(uint16_t Year, uint8_t Month, uint8_t Day, uint8_t Hour, uint8_t Minute, uint8_t Second); int64_t GetUnixTimestamp(); DateTime GetDateTime(); + + void SetTZOffset(int totalMinutes); + int GetTZOffset(); }; diff --git a/programs/include/Api/Syscall.hpp b/programs/include/Api/Syscall.hpp index 3ea82c0..f04b8b2 100644 --- a/programs/include/Api/Syscall.hpp +++ b/programs/include/Api/Syscall.hpp @@ -116,6 +116,10 @@ namespace Montauk { // Power management static constexpr uint64_t SYS_SUSPEND = 89; + // Timezone + static constexpr uint64_t SYS_SETTZ = 90; + static constexpr uint64_t SYS_GETTZ = 91; + // Audio control commands (for SYS_AUDIOCTL) static constexpr int AUDIO_CTL_SET_VOLUME = 0; static constexpr int AUDIO_CTL_GET_VOLUME = 1; diff --git a/programs/include/gui/desktop.hpp b/programs/include/gui/desktop.hpp index 7e8c733..f8b3143 100644 --- a/programs/include/gui/desktop.hpp +++ b/programs/include/gui/desktop.hpp @@ -52,6 +52,7 @@ struct DesktopSettings { bool show_shadows; // window shadows on/off bool clock_24h; // 24-hour clock format int ui_scale; // 0=Small, 1=Default, 2=Large + int tz_offset_minutes; // timezone offset from UTC in minutes }; struct DesktopState { diff --git a/programs/include/libc/montauk.h b/programs/include/libc/montauk.h index f7314d3..3100347 100644 --- a/programs/include/libc/montauk.h +++ b/programs/include/libc/montauk.h @@ -94,6 +94,8 @@ extern "C" { #define MTK_SYS_AUDIOCLOSE 81 #define MTK_SYS_AUDIOWRITE 82 #define MTK_SYS_AUDIOCTL 83 +#define MTK_SYS_SETTZ 90 +#define MTK_SYS_GETTZ 91 #define MTK_SOCK_TCP 1 #define MTK_SOCK_UDP 2 @@ -411,6 +413,14 @@ static inline void mtk_gettime(mtk_datetime *out) { _mtk_syscall1(MTK_SYS_GETTIME, (long)out); } +static inline void mtk_settz(int offset_minutes) { + _mtk_syscall1(MTK_SYS_SETTZ, (long)offset_minutes); +} + +static inline int mtk_gettz(void) { + return (int)_mtk_syscall0(MTK_SYS_GETTZ); +} + /* ==================================================================== System info ==================================================================== */ diff --git a/programs/include/montauk/syscall.h b/programs/include/montauk/syscall.h index f9325ea..71d0431 100644 --- a/programs/include/montauk/syscall.h +++ b/programs/include/montauk/syscall.h @@ -249,6 +249,10 @@ namespace montauk { // Timekeeping (wall-clock) inline void gettime(Montauk::DateTime* out) { syscall1(Montauk::SYS_GETTIME, (uint64_t)out); } + // Timezone offset (total minutes from UTC) + inline void settz(int offset_minutes) { syscall1(Montauk::SYS_SETTZ, (uint64_t)(int64_t)offset_minutes); } + inline int gettz() { return (int)syscall0(Montauk::SYS_GETTZ); } + // Random number generation inline int64_t getrandom(void* buf, uint32_t len) { return syscall2(Montauk::SYS_GETRANDOM, (uint64_t)buf, (uint64_t)len); diff --git a/programs/src/desktop/apps/app_settings.cpp b/programs/src/desktop/apps/app_settings.cpp index eac991a..d5ed7b1 100644 --- a/programs/src/desktop/apps/app_settings.cpp +++ b/programs/src/desktop/apps/app_settings.cpp @@ -301,6 +301,35 @@ static void settings_draw_display(Canvas& c, SettingsState* st) { draw_toggle_btn(c, bx, y, sbw, btn_h, "Small", s.ui_scale == 0, accent); draw_toggle_btn(c, bx + sbw + 8, y, sbw, btn_h, "Default", s.ui_scale == 1, accent); draw_toggle_btn(c, bx + (sbw + 8) * 2, y, sbw, btn_h, "Large", s.ui_scale == 2, accent); + y += btn_h + 20; + + // Separator + c.hline(x, y, c.w - 2 * x, colors::BORDER); + y += 16; + + // UTC Offset + c.text(x, y + 6, "UTC Offset", colors::TEXT_COLOR); + bx = x + 180; + + // [-] button + draw_toggle_btn(c, bx, y, 36, btn_h, "-", false, accent); + + // Offset label + int off = s.tz_offset_minutes; + int offH = off / 60; + int offM = off % 60; + if (offM < 0) offM = -offM; + char tz_label[16]; + if (offM) + snprintf(tz_label, sizeof(tz_label), "UTC%s%d:%02d", offH >= 0 ? "+" : "", offH, offM); + else + snprintf(tz_label, sizeof(tz_label), "UTC%s%d", offH >= 0 ? "+" : "", offH); + int tw = text_width(tz_label); + int label_w = 90; + c.text(bx + 36 + (label_w - tw) / 2, y + 6, tz_label, colors::TEXT_COLOR); + + // [+] button + draw_toggle_btn(c, bx + 36 + label_w, y, 36, btn_h, "+", false, accent); } static void settings_draw_about(Canvas& c, SettingsState* st) { @@ -392,6 +421,14 @@ static void settings_persist(SettingsState* st) { doc.destroy(); } +static void settings_persist_tz(SettingsState* st) { + montauk::toml::Doc doc; + doc.init(); + montauk::config::set_int(&doc, "timezone.offset_minutes", st->desktop->settings.tz_offset_minutes); + montauk::config::save("timezone", &doc); + doc.destroy(); +} + // ============================================================================ // Users tab // ============================================================================ @@ -1295,6 +1332,27 @@ static void settings_on_mouse(Window* win, MouseEvent& ev) { settings_persist(st); return; } + y += btn_h + 20 + 16; + + // UTC Offset: [-] button + if (mx >= bx && mx < bx + 36 && cy >= y && cy < y + btn_h) { + if (s.tz_offset_minutes > -720) { + s.tz_offset_minutes -= 60; + montauk::settz(s.tz_offset_minutes); + settings_persist_tz(st); + } + return; + } + // UTC Offset: [+] button + int label_w = 90; + if (mx >= bx + 36 + label_w && mx < bx + 36 + label_w + 36 && cy >= y && cy < y + btn_h) { + if (s.tz_offset_minutes < 840) { + s.tz_offset_minutes += 60; + montauk::settz(s.tz_offset_minutes); + settings_persist_tz(st); + } + return; + } } else if (st->active_tab == 2) { // Users tab if (!st->desktop->is_admin) return; diff --git a/programs/src/desktop/main.cpp b/programs/src/desktop/main.cpp index 631ff23..f5765a2 100644 --- a/programs/src/desktop/main.cpp +++ b/programs/src/desktop/main.cpp @@ -256,6 +256,7 @@ void gui::desktop_init(DesktopState* ds) { ds->settings.show_shadows = true; ds->settings.clock_24h = true; ds->settings.ui_scale = 1; + ds->settings.tz_offset_minutes = 60; // default CET (UTC+1) // Load per-user desktop settings { @@ -311,6 +312,15 @@ void gui::desktop_init(DesktopState* ds) { doc.destroy(); } + + // Load timezone config + { + auto tz = montauk::config::load("timezone"); + ds->settings.tz_offset_minutes = (int)tz.get_int("timezone.offset_minutes", 60); + tz.destroy(); + } + montauk::settz(ds->settings.tz_offset_minutes); + montauk::win_setscale(ds->settings.ui_scale); ds->ctx_menu_open = false; diff --git a/template/sysroot/include/Api/Syscall.hpp b/template/sysroot/include/Api/Syscall.hpp index 7dc1435..9423f6e 100644 --- a/template/sysroot/include/Api/Syscall.hpp +++ b/template/sysroot/include/Api/Syscall.hpp @@ -113,6 +113,13 @@ namespace Montauk { static constexpr uint64_t SYS_BTLIST = 87; static constexpr uint64_t SYS_BTINFO = 88; + // Power management + static constexpr uint64_t SYS_SUSPEND = 89; + + // Timezone + static constexpr uint64_t SYS_SETTZ = 90; + static constexpr uint64_t SYS_GETTZ = 91; + // Audio control commands (for SYS_AUDIOCTL) static constexpr int AUDIO_CTL_SET_VOLUME = 0; static constexpr int AUDIO_CTL_GET_VOLUME = 1; diff --git a/template/sysroot/include/gui/desktop.hpp b/template/sysroot/include/gui/desktop.hpp index 098b678..22ed312 100644 --- a/template/sysroot/include/gui/desktop.hpp +++ b/template/sysroot/include/gui/desktop.hpp @@ -52,6 +52,7 @@ struct DesktopSettings { bool show_shadows; // window shadows on/off bool clock_24h; // 24-hour clock format int ui_scale; // 0=Small, 1=Default, 2=Large + int tz_offset_minutes; // timezone offset from UTC in minutes }; struct DesktopState { diff --git a/template/sysroot/include/montauk/syscall.h b/template/sysroot/include/montauk/syscall.h index 148a92b..8ba7938 100644 --- a/template/sysroot/include/montauk/syscall.h +++ b/template/sysroot/include/montauk/syscall.h @@ -249,6 +249,10 @@ namespace montauk { // Timekeeping (wall-clock) inline void gettime(Montauk::DateTime* out) { syscall1(Montauk::SYS_GETTIME, (uint64_t)out); } + // Timezone offset (total minutes from UTC) + inline void settz(int offset_minutes) { syscall1(Montauk::SYS_SETTZ, (uint64_t)(int64_t)offset_minutes); } + inline int gettz() { return (int)syscall0(Montauk::SYS_GETTZ); } + // Random number generation inline int64_t getrandom(void* buf, uint32_t len) { return syscall2(Montauk::SYS_GETRANDOM, (uint64_t)buf, (uint64_t)len);