diff --git a/README.md b/README.md
index c3b29a3..9b28355 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,16 @@
# BoredOS
BoredOS has now exited Beta stage and is "stable enough" to be put out as a "stable" product.
-
+
+
+====================== __ ____ ____
+===================== / /_ / __ \/ ___\
+==================== / __ \/ / / /\___ \
+=================== / /_/ / /_/ /____/ /
+================== /_.___/\____//_____/
+=================
+
+
BoredOS is a simple x86_64 hobbyist operating system.
It features a DE (and WM), a FAT32 filesystem, customizable UI and much much more!
diff --git a/boredos.iso b/boredos.iso
index 84b6fce..0ac7c54 100644
Binary files a/boredos.iso and b/boredos.iso differ
diff --git a/build/graphics.o b/build/graphics.o
index 3484a2e..3662779 100644
Binary files a/build/graphics.o and b/build/graphics.o differ
diff --git a/build/rtc.o b/build/rtc.o
index 7a2119a..68ed7e4 100644
Binary files a/build/rtc.o and b/build/rtc.o differ
diff --git a/src/kernel/rtc.c b/src/kernel/rtc.c
index d2c85e9..7f48aab 100644
--- a/src/kernel/rtc.c
+++ b/src/kernel/rtc.c
@@ -74,3 +74,40 @@ void rtc_get_datetime(int *year, int *month, int *day, int *hour, int *minute, i
// Calculate full year
*year += 2000;
}
+
+static void set_rtc_register(int reg, uint8_t value) {
+ outb(CMOS_ADDRESS, reg);
+ outb(CMOS_DATA, value);
+}
+
+void rtc_set_datetime(int year, int month, int day, int hour, int minute, int second) {
+ uint8_t registerB = get_rtc_register(0x0B);
+
+ // Disable NMI and start update
+ outb(CMOS_ADDRESS, 0x8B);
+ uint8_t prev_b = inb(CMOS_DATA);
+ outb(CMOS_DATA, prev_b | 0x80); // Set SET bit to prevent updates while writing
+
+ if (year >= 2000) year -= 2000;
+
+ if (!(registerB & 0x04)) {
+ // Convert binary to BCD
+ second = ((second / 10) << 4) | (second % 10);
+ minute = ((minute / 10) << 4) | (minute % 10);
+ hour = ((hour / 10) << 4) | (hour % 10);
+ day = ((day / 10) << 4) | (day % 10);
+ month = ((month / 10) << 4) | (month % 10);
+ year = ((year / 10) << 4) | (year % 10);
+ }
+
+ set_rtc_register(0x00, (uint8_t)second);
+ set_rtc_register(0x02, (uint8_t)minute);
+ set_rtc_register(0x04, (uint8_t)hour);
+ set_rtc_register(0x07, (uint8_t)day);
+ set_rtc_register(0x08, (uint8_t)month);
+ set_rtc_register(0x09, (uint8_t)year);
+
+ // Re-enable updates
+ outb(CMOS_ADDRESS, 0x8B);
+ outb(CMOS_DATA, prev_b & ~0x80);
+}
diff --git a/src/kernel/rtc.h b/src/kernel/rtc.h
index 32b5a4c..ee12955 100644
--- a/src/kernel/rtc.h
+++ b/src/kernel/rtc.h
@@ -7,5 +7,6 @@
#include
void rtc_get_datetime(int *year, int *month, int *day, int *hour, int *minute, int *second);
+void rtc_set_datetime(int year, int month, int day, int hour, int minute, int second);
#endif
diff --git a/src/kernel/syscall.c b/src/kernel/syscall.c
index 2223ef1..bf54d4b 100644
--- a/src/kernel/syscall.c
+++ b/src/kernel/syscall.c
@@ -725,6 +725,12 @@ static uint64_t syscall_handler_inner(uint64_t syscall_num, uint64_t arg1, uint6
extern void wallpaper_request_set_from_file(const char *path);
wallpaper_request_set_from_file(kernel_path);
return 0;
+ } else if (cmd == 32) { // SYSTEM_CMD_RTC_SET
+ int *dt = (int *)arg2;
+ if (!dt) return -1;
+ extern void rtc_set_datetime(int y, int m, int d, int h, int min, int s);
+ rtc_set_datetime(dt[0], dt[1], dt[2], dt[3], dt[4], dt[5]);
+ return 0;
}
return -1;
}
diff --git a/src/kernel/userland/clock.c b/src/kernel/userland/clock.c
new file mode 100644
index 0000000..f87b96c
--- /dev/null
+++ b/src/kernel/userland/clock.c
@@ -0,0 +1,263 @@
+// Copyright (c) 2023-2026 Chris (boreddevnl)
+// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
+// This header needs to maintain in any file it is present in, as per the GPL license terms.
+#include "syscall.h"
+#include "libui.h"
+#include "stdlib.h"
+#include
+
+#define WIN_W 250
+#define WIN_H 250
+
+#define COLOR_DARK_BG 0xFF1E1E1E
+#define COLOR_DARK_PANEL 0xFF2D2D2D
+#define COLOR_DARK_TEXT 0xFFF0F0F0
+#define COLOR_DARK_BORDER 0xFF3A3A3A
+#define COLOR_TAB_ACTIVE 0xFF4A90E2
+#define COLOR_ACCENT 0xFF4A90E2
+
+typedef enum {
+ TAB_CLOCK,
+ TAB_TIMER,
+ TAB_STOPWATCH
+} tab_t;
+
+static ui_window_t win_clock;
+static tab_t current_tab = TAB_CLOCK;
+
+// Clock State
+static int adj_h = -1, adj_m = 0, adj_s = 0;
+static int cur_h, cur_m, cur_s;
+static int cur_y, cur_mon, cur_d;
+
+// Timer State
+static int timer_h = 0, timer_m = 0, timer_s = 0;
+static long long timer_end_ticks = 0;
+static bool timer_running = false;
+
+// Stopwatch State
+static long long sw_start_ticks = 0;
+static long long sw_elapsed_ticks = 0;
+static bool sw_running = false;
+
+static void format_time(char *buf, int h, int m, int s) {
+ buf[0] = '0' + (h / 10);
+ buf[1] = '0' + (h % 10);
+ buf[2] = ':';
+ buf[3] = '0' + (m / 10);
+ buf[4] = '0' + (m % 10);
+ buf[5] = ':';
+ buf[6] = '0' + (s / 10);
+ buf[7] = '0' + (s % 10);
+ buf[8] = 0;
+}
+
+static void update_rtc_state(void) {
+ int dt[6];
+ int old_s = cur_s;
+ sys_system(11, (uint64_t)dt, 0, 0, 0);
+ cur_y = dt[0]; cur_mon = dt[1]; cur_d = dt[2];
+ cur_h = dt[3]; cur_m = dt[4]; cur_s = dt[5];
+
+ if (adj_h == -1) {
+ adj_h = cur_h; adj_m = cur_m; adj_s = cur_s;
+ } else if (cur_s != old_s) {
+ adj_s++;
+ if (adj_s >= 60) { adj_s = 0; adj_m++; }
+ if (adj_m >= 60) { adj_m = 0; adj_h++; }
+ if (adj_h >= 24) { adj_h = 0; }
+ }
+}
+
+static void draw_btn(int x, int y, int w, int h, const char *label, bool highlight) {
+ ui_draw_rounded_rect_filled(win_clock, x, y, w, h, 8, highlight ? COLOR_ACCENT : COLOR_DARK_BORDER);
+ int tw = strlen(label) * 8;
+ ui_draw_string(win_clock, x + (w - tw) / 2, y + (h - 8) / 2, label, COLOR_DARK_TEXT);
+}
+
+static void clock_paint(void) {
+ ui_draw_rect(win_clock, 0, 0, WIN_W -5, WIN_H -20, COLOR_DARK_BG);
+
+ const char *tabs[] = {"Clock", "Timer", "Stopwatch"};
+ int tab_w = 70;
+ int total_tabs_w = tab_w * 3 + 16;
+ int start_x = (WIN_W - total_tabs_w) / 2;
+ int tab_y = 8;
+ int tab_h = 24;
+
+ for (int i = 0; i < 3; i++) {
+ uint32_t bg = (current_tab == i) ? COLOR_TAB_ACTIVE : COLOR_DARK_PANEL;
+ ui_draw_rounded_rect_filled(win_clock, start_x + i * (tab_w + 8), tab_y, tab_w, tab_h, 12, bg);
+ int tw = strlen(tabs[i]) * 8;
+ ui_draw_string(win_clock, start_x + i * (tab_w + 8) + (tab_w - tw) / 2, tab_y + (tab_h - 8) / 2, tabs[i], COLOR_DARK_TEXT);
+ }
+
+ char buf[64];
+ if (current_tab == TAB_CLOCK) {
+ update_rtc_state();
+ ui_draw_string(win_clock, (WIN_W - 12 * 8) / 2, 60, "Current Time", COLOR_DARK_TEXT);
+ format_time(buf, cur_h, cur_m, cur_s);
+ ui_draw_string(win_clock, (WIN_W - 8 * 8) / 2, 80, buf, COLOR_DARK_TEXT);
+
+ ui_draw_string(win_clock, (WIN_W - 12 * 8) / 2, 115, "Adjust Time:", COLOR_DARK_TEXT);
+
+ char adj_buf[32];
+ format_time(buf, adj_h, adj_m, adj_s);
+ adj_buf[0] = buf[0]; adj_buf[1] = buf[1]; adj_buf[2] = ' ';
+ adj_buf[3] = ':'; adj_buf[4] = ' ';
+ adj_buf[5] = buf[3]; adj_buf[6] = buf[4]; adj_buf[7] = ' ';
+ adj_buf[8] = ':'; adj_buf[9] = ' ';
+ adj_buf[10] = buf[6]; adj_buf[11] = buf[7]; adj_buf[12] = 0;
+ int ax = (WIN_W - 12 * 8) / 2;
+ ui_draw_string(win_clock, ax, 150, adj_buf, COLOR_DARK_TEXT);
+
+ // Arrows
+ ui_draw_string(win_clock, ax + 4, 135, "^", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 44, 135, "^", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 84, 135, "^", COLOR_ACCENT);
+
+ ui_draw_string(win_clock, ax + 4, 165, "v", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 44, 165, "v", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 84, 165, "v", COLOR_ACCENT);
+
+ draw_btn((WIN_W - 120) / 2, 195, 120, 28, "Apply changes", false);
+
+ } else if (current_tab == TAB_TIMER) {
+ long long now = sys_system(16, 0, 0, 0, 0);
+ int display_h = timer_h, display_m = timer_m, display_s = timer_s;
+
+ if (timer_running) {
+ long long rem = timer_end_ticks - now;
+ if (rem <= 0) {
+ timer_running = false;
+ display_h = display_m = display_s = 0;
+ for(int i=0; i<3; i++) sys_system(14, 440, 200, 0, 0);
+ } else {
+ int s = rem / 60;
+ display_h = s / 3600;
+ display_m = (s % 3600) / 60;
+ display_s = s % 60;
+ }
+ }
+
+ format_time(buf, display_h, display_m, display_s);
+ ui_draw_string(win_clock, (WIN_W - 8 * 8) / 2, 85, buf, COLOR_DARK_TEXT);
+
+ int ax = (WIN_W - 8 * 8) / 2;
+ if (!timer_running) {
+ ui_draw_string(win_clock, ax + 4, 65, "^", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 28, 65, "^", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 52, 65, "^", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 4, 105, "v", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 28, 105, "v", COLOR_ACCENT);
+ ui_draw_string(win_clock, ax + 52, 105, "v", COLOR_ACCENT);
+ }
+
+ draw_btn((WIN_W - 120) / 2, 150, 120, 30, timer_running ? "Stop Timer" : "Start Timer", false);
+
+ } else if (current_tab == TAB_STOPWATCH) {
+ long long elapsed = sw_elapsed_ticks;
+ if (sw_running) elapsed += (sys_system(16, 0, 0, 0, 0) - sw_start_ticks);
+
+ int ms_val = (elapsed % 60) * 1000 / 60;
+ int s_val = (elapsed / 60) % 60;
+ int m_val = (elapsed / 3600) % 60;
+
+ buf[0] = '0' + (m_val / 10); buf[1] = '0' + (m_val % 10); buf[2] = ':';
+ buf[3] = '0' + (s_val / 10); buf[4] = '0' + (s_val % 10); buf[5] = '.';
+ buf[6] = '0' + (ms_val / 100); buf[7] = '0' + ((ms_val / 10) % 10); buf[8] = '0' + (ms_val % 10); buf[9] = 0;
+ ui_draw_string(win_clock, (WIN_W - 9 * 8) / 2, 85, buf, COLOR_DARK_TEXT);
+
+ draw_btn(20, 150, 90, 30, sw_running ? "Stop" : "Start", false);
+ draw_btn(140, 150, 90, 30, "Reset", false);
+ }
+}
+
+static void clock_click(int x, int y) {
+ int tab_w = 70;
+ int tab_spacing = 8;
+ int total_tabs_w = tab_w * 3 + tab_spacing * 2;
+ int start_x = (WIN_W - total_tabs_w) / 2;
+ int tab_y = 8;
+ int tab_h = 24;
+
+ if (y >= tab_y && y <= tab_y + tab_h) {
+ for (int i = 0; i < 3; i++) {
+ int tx = start_x + i * (tab_w + tab_spacing);
+ if (x >= tx && x <= tx + tab_w) {
+ current_tab = (tab_t)i;
+ clock_paint();
+ ui_mark_dirty(win_clock, 0, 0, WIN_W, WIN_H);
+ return;
+ }
+ }
+ }
+
+ int ax_clock = (WIN_W - 12 * 8) / 2;
+ int ax_timer = (WIN_W - 8 * 8) / 2;
+
+ if (current_tab == TAB_CLOCK) {
+ if (y >= 130 && y <= 150) {
+ if (x >= ax_clock && x <= ax_clock + 32) adj_h = (adj_h + 1) % 24;
+ else if (x >= ax_clock + 33 && x <= ax_clock + 72) adj_m = (adj_m + 1) % 60;
+ else if (x >= ax_clock + 73 && x <= ax_clock + 110) adj_s = (adj_s + 1) % 60;
+ } else if (y >= 160 && y <= 180) {
+ if (x >= ax_clock && x <= ax_clock + 32) adj_h = (adj_h + 23) % 24;
+ else if (x >= ax_clock + 33 && x <= ax_clock + 72) adj_m = (adj_m + 59) % 60;
+ else if (x >= ax_clock + 73 && x <= ax_clock + 110) adj_s = (adj_s + 59) % 60;
+ } else if (x >= (WIN_W - 120) / 2 && x <= (WIN_W + 120) / 2 && y >= 195 && y <= 223) {
+ int dt[6] = {cur_y, cur_mon, cur_d, adj_h, adj_m, adj_s};
+ sys_system(32, (uint64_t)dt, 0, 0, 0);
+ }
+ } else if (current_tab == TAB_TIMER) {
+ if (!timer_running) {
+ if (y >= 60 && y <= 85) {
+ if (x >= ax_timer && x <= ax_timer + 20) timer_h = (timer_h + 1) % 24;
+ else if (x >= ax_timer + 24 && x <= ax_timer + 44) timer_m = (timer_m + 1) % 60;
+ else if (x >= ax_timer + 48 && x <= ax_timer + 68) timer_s = (timer_s + 1) % 60;
+ } else if (y >= 100 && y <= 125) {
+ if (x >= ax_timer && x <= ax_timer + 20) timer_h = (timer_h + 23) % 24;
+ else if (x >= ax_timer + 24 && x <= ax_timer + 44) timer_m = (timer_m + 59) % 60;
+ else if (x >= ax_timer + 48 && x <= ax_timer + 68) timer_s = (timer_s + 59) % 60;
+ }
+ }
+ if (x >= (WIN_W - 120) / 2 && x <= (WIN_W + 120) / 2 && y >= 150 && y <= 180) {
+ if (!timer_running) {
+ if (timer_h > 0 || timer_m > 0 || timer_s > 0) {
+ timer_running = true;
+ timer_end_ticks = sys_system(16, 0, 0, 0, 0) + (long long)(timer_h * 3600 + timer_m * 60 + timer_s) * 60;
+ }
+ } else timer_running = false;
+ }
+ } else if (current_tab == TAB_STOPWATCH) {
+ if (x >= 20 && x <= 110 && y >= 150 && y <= 180) {
+ if (sw_running) { sw_elapsed_ticks += (sys_system(16, 0, 0, 0, 0) - sw_start_ticks); sw_running = false; }
+ else { sw_start_ticks = sys_system(16, 0, 0, 0, 0); sw_running = true; }
+ } else if (x >= 140 && x <= 230 && y >= 150 && y <= 180) {
+ sw_elapsed_ticks = 0; sw_start_ticks = sys_system(16, 0, 0, 0, 0);
+ }
+ }
+ clock_paint();
+ ui_mark_dirty(win_clock, 0, 0, WIN_W, WIN_H);
+}
+
+int main(void) {
+ win_clock = ui_window_create("Clock", 200, 150, WIN_W, WIN_H);
+ update_rtc_state();
+ clock_paint();
+ ui_mark_dirty(win_clock, 0, 0, WIN_W, WIN_H);
+ gui_event_t ev;
+ long long last_rep = 0;
+ while (1) {
+ if (ui_get_event(win_clock, &ev)) {
+ if (ev.type == GUI_EVENT_PAINT) { clock_paint(); ui_mark_dirty(win_clock, 0, 0, WIN_W, WIN_H); }
+ else if (ev.type == GUI_EVENT_CLICK) clock_click(ev.arg1, ev.arg2);
+ else if (ev.type == GUI_EVENT_CLOSE) sys_exit(0);
+ } else {
+ long long now = sys_system(16, 0, 0, 0, 0);
+ if (now - last_rep >= 6) { clock_paint(); ui_mark_dirty(win_clock, 0, 0, WIN_W, WIN_H); last_rep = now; }
+ for(volatile int i=0; i<5000; i++);
+ }
+ }
+ return 0;
+}
diff --git a/src/kernel/wm.c b/src/kernel/wm.c
index bebf0b6..e29ca9c 100644
--- a/src/kernel/wm.c
+++ b/src/kernel/wm.c
@@ -385,6 +385,7 @@ static void draw_dock_calculator(int x, int y);
static void draw_dock_terminal(int x, int y);
static void draw_dock_minesweeper(int x, int y);
static void draw_dock_paint(int x, int y);
+static void draw_dock_clock(int x, int y);
static void draw_dock_editor(int x, int y);
static void draw_filled_circle(int cx, int cy, int r, uint32_t color);
@@ -713,6 +714,11 @@ void draw_control_panel_icon(int x, int y, const char *label) {
draw_icon_label(x, y, label);
}
+void draw_clock_icon(int x, int y, const char *label) {
+ draw_scaled_icon(x, y, draw_dock_clock);
+ draw_icon_label(x, y, label);
+}
+
void draw_about_icon(int x, int y, const char *label) {
uint32_t icon_buf[48 * 48];
for (int i = 0; i < 48 * 48; i++) icon_buf[i] = 0xFFFF00FF;
@@ -953,6 +959,21 @@ static void draw_dock_paint(int x, int y) {
draw_rounded_rect_filled(x + 30, y + 22, 3, 7, 1, 0xFF1A1A1A);
}
+static void draw_dock_clock(int x, int y) {
+ draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF4A4A4A);
+ draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF6E6E6E);
+ draw_rounded_rect_filled(x + 1, y + 24, 46, 23, 9, 0xFF5A5A5A);
+
+ int cx = x + 24, cy = y + 24;
+ draw_filled_circle(cx, cy, 18, 0xFFF0F0F0);
+ draw_filled_circle(cx, cy, 1, 0xFF333333);
+
+ // Hour hand
+ draw_rect(cx - 1, cy - 8, 2, 8, 0xFF333333);
+ // Minute hand
+ draw_rect(cx, cy - 1, 10, 2, 0xFF333333);
+}
+
static void draw_dock_editor(int x, int y) {
draw_rounded_rect_filled(x, y, 48, 48, 10, 0xFF0A1628);
draw_rounded_rect_filled(x + 1, y + 1, 46, 28, 9, 0xFF1565C0);
@@ -1130,6 +1151,7 @@ void wm_paint(void) {
else if (str_starts_with(icon->name, "Terminal")) draw_terminal_icon(icon->x, icon->y, label);
else if (str_starts_with(icon->name, "Minesweeper")) draw_minesweeper_icon(icon->x, icon->y, label);
else if (str_starts_with(icon->name, "Settings")) draw_control_panel_icon(icon->x, icon->y, label);
+ else if (str_starts_with(icon->name, "Clock")) draw_clock_icon(icon->x, icon->y, label);
else if (str_starts_with(icon->name, "About")) draw_about_icon(icon->x, icon->y, label);
else if (str_starts_with(icon->name, "Recycle Bin")) draw_recycle_bin_icon(icon->x, icon->y, label);
else if (str_starts_with(icon->name, "Files")) draw_folder_icon(icon->x, icon->y, label);
@@ -1192,7 +1214,7 @@ void wm_paint(void) {
int dock_y = sh - dock_h - 6;
int dock_item_size = 48;
int dock_spacing = 10;
- int total_dock_width = 7 * (dock_item_size + dock_spacing);
+ int total_dock_width = 8 * (dock_item_size + dock_spacing);
int dock_bg_x = (sw - total_dock_width) / 2 - 12;
int dock_bg_w = total_dock_width + 24;
draw_rounded_rect_filled(dock_bg_x, dock_y, dock_bg_w, dock_h, 18, COLOR_DOCK_BG);
@@ -1213,6 +1235,8 @@ void wm_paint(void) {
draw_dock_minesweeper(dock_x, dock_item_y);
dock_x += dock_item_size + dock_spacing;
draw_dock_paint(dock_x, dock_item_y);
+ dock_x += dock_item_size + dock_spacing;
+ draw_dock_clock(dock_x, dock_item_y);
// Editor removed from dock
// Desktop Context Menu (with rounded corners)
@@ -1725,7 +1749,7 @@ void wm_handle_right_click(int x, int y) {
int dock_y = sh - dock_h - 6;
int dock_item_size = 48;
int dock_spacing = 10;
- int total_dock_width = 7 * (dock_item_size + dock_spacing);
+ int total_dock_width = 8 * (dock_item_size + dock_spacing);
int dock_bg_x = (sw - total_dock_width) / 2 - 12;
int dock_bg_w = total_dock_width + 24;
@@ -1743,6 +1767,7 @@ void wm_handle_right_click(int x, int y) {
else if (item == 4) start_menu_pending_app = "Terminal";
else if (item == 5) start_menu_pending_app = "Minesweeper";
else if (item == 6) start_menu_pending_app = "Paint";
+ else if (item == 7) start_menu_pending_app = "Clock";
}
} else {
wm_handle_click(mx, my);
@@ -1858,6 +1883,10 @@ void wm_handle_right_click(int x, int y) {
Window *existing = wm_find_window_by_title("Paint");
if (existing) wm_bring_to_front(existing);
else process_create_elf("/bin/paint.elf", NULL);
+ } else if (str_starts_with(start_menu_pending_app, "Clock")) {
+ Window *existing = wm_find_window_by_title("Clock");
+ if (existing) wm_bring_to_front(existing);
+ else process_create_elf("/bin/clock.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "About")) {
process_create_elf("/bin/about.elf", NULL);
} else if (str_starts_with(start_menu_pending_app, "Shutdown")) {