219 lines
7.7 KiB
C
219 lines
7.7 KiB
C
// clock.c — Clock DXE application (main-loop with tsYield)
|
|
//
|
|
// This is a main-loop DXE app (hasMainLoop = true), in contrast to callback-
|
|
// only apps like notepad or progman. The difference is fundamental:
|
|
//
|
|
// Callback-only: appMain creates UI, returns. Shell calls callbacks.
|
|
// Main-loop: appMain runs forever in its own task, calling tsYield()
|
|
// to cooperatively yield to the shell and other tasks.
|
|
//
|
|
// A main-loop app is needed when the app has ongoing work that can't be
|
|
// expressed purely as event callbacks — here, polling the system clock
|
|
// every second and repainting. The shell allocates a dedicated task stack
|
|
// and schedules this task alongside the main event loop.
|
|
//
|
|
// The onPaint/onClose callbacks still execute in task 0 during dvxUpdate(),
|
|
// not in this task. The main loop only handles the clock-tick polling; the
|
|
// actual rendering and window management happen through the normal callback
|
|
// path. tsYield() is what makes this cooperative — without it, this task
|
|
// would starve the shell and all other apps.
|
|
|
|
#include "dvxApp.h"
|
|
#include "dvxWidget.h"
|
|
#include "dvxDraw.h"
|
|
#include "dvxVideo.h"
|
|
#include "shellApp.h"
|
|
#include "taskswitch.h"
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
// ============================================================
|
|
// Module state
|
|
// ============================================================
|
|
|
|
typedef struct {
|
|
bool quit;
|
|
char timeStr[32];
|
|
char dateStr[32];
|
|
time_t lastUpdate;
|
|
} ClockStateT;
|
|
|
|
static DxeAppContextT *sCtx = NULL;
|
|
static WindowT *sWin = NULL;
|
|
static ClockStateT sState;
|
|
|
|
// ============================================================
|
|
// Prototypes
|
|
// ============================================================
|
|
|
|
int32_t appMain(DxeAppContextT *ctx);
|
|
void appShutdown(void);
|
|
static void onClose(WindowT *win);
|
|
static void onPaint(WindowT *win, RectT *dirty);
|
|
static void updateTime(void);
|
|
|
|
// ============================================================
|
|
// App descriptor
|
|
// ============================================================
|
|
|
|
// hasMainLoop = true tells the shell to create a dedicated task for appMain.
|
|
// TS_PRIORITY_LOW because clock updates are cosmetic and should never
|
|
// preempt interactive apps or the shell's event processing.
|
|
AppDescriptorT appDescriptor = {
|
|
.name = "Clock",
|
|
.hasMainLoop = true,
|
|
.stackSize = 0,
|
|
.priority = TS_PRIORITY_LOW
|
|
};
|
|
|
|
// ============================================================
|
|
// Callbacks (fire in task 0 during dvxUpdate)
|
|
// ============================================================
|
|
|
|
// Setting quit = true tells the main loop (running in a separate task) to
|
|
// exit. The main loop then destroys the window and returns from appMain,
|
|
// which causes the shell to reap this task and unload the DXE.
|
|
static void onClose(WindowT *win) {
|
|
(void)win;
|
|
sState.quit = true;
|
|
}
|
|
|
|
|
|
// onPaint demonstrates the raw paint callback approach (no widget tree).
|
|
// Instead of using wgtInitWindow + widgets, this app renders directly into
|
|
// the window's content buffer. This is the lower-level alternative to the
|
|
// widget system — useful for custom rendering like this centered clock display.
|
|
//
|
|
// We create a temporary DisplayT struct pointing at the window's content buffer
|
|
// so the drawing primitives (rectFill, drawText) can operate on the window
|
|
// content directly rather than the backbuffer.
|
|
static void onPaint(WindowT *win, RectT *dirty) {
|
|
(void)dirty;
|
|
|
|
AppContextT *ac = sCtx->shellCtx;
|
|
const BlitOpsT *ops = dvxGetBlitOps(ac);
|
|
const BitmapFontT *font = dvxGetFont(ac);
|
|
const ColorSchemeT *colors = dvxGetColors(ac);
|
|
|
|
// Shallow copy of the display, redirected to the window's content buffer.
|
|
// This lets us reuse the standard drawing API without modifying the
|
|
// global display state.
|
|
DisplayT cd = *dvxGetDisplay(ac);
|
|
cd.backBuf = win->contentBuf;
|
|
cd.width = win->contentW;
|
|
cd.height = win->contentH;
|
|
cd.pitch = win->contentPitch;
|
|
cd.clipX = 0;
|
|
cd.clipY = 0;
|
|
cd.clipW = win->contentW;
|
|
cd.clipH = win->contentH;
|
|
|
|
// Background
|
|
rectFill(&cd, ops, 0, 0, win->contentW, win->contentH, colors->contentBg);
|
|
|
|
// Time string (large, centered)
|
|
int32_t timeW = textWidth(font, sState.timeStr);
|
|
int32_t timeX = (win->contentW - timeW) / 2;
|
|
int32_t timeY = win->contentH / 3;
|
|
drawText(&cd, ops, font, timeX, timeY, sState.timeStr, colors->contentFg, colors->contentBg, true);
|
|
|
|
// Date string (centered below)
|
|
int32_t dateW = textWidth(font, sState.dateStr);
|
|
int32_t dateX = (win->contentW - dateW) / 2;
|
|
int32_t dateY = timeY + font->charHeight + 8;
|
|
drawText(&cd, ops, font, dateX, dateY, sState.dateStr, colors->contentFg, colors->contentBg, true);
|
|
}
|
|
|
|
// ============================================================
|
|
// Time update
|
|
// ============================================================
|
|
|
|
static void updateTime(void) {
|
|
time_t now = time(NULL);
|
|
struct tm *tm = localtime(&now);
|
|
|
|
if (!tm) {
|
|
snprintf(sState.timeStr, sizeof(sState.timeStr), "--:--:--");
|
|
snprintf(sState.dateStr, sizeof(sState.dateStr), "----:--:--");
|
|
sState.lastUpdate = now;
|
|
return;
|
|
}
|
|
|
|
snprintf(sState.timeStr, sizeof(sState.timeStr), "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec);
|
|
snprintf(sState.dateStr, sizeof(sState.dateStr), "%04d-%02d-%02d", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
|
|
sState.lastUpdate = now;
|
|
}
|
|
|
|
// ============================================================
|
|
// Shutdown hook (optional DXE export)
|
|
// ============================================================
|
|
|
|
// The shell calls appShutdown (if exported) when force-killing an app via
|
|
// Task Manager or during shell shutdown. For main-loop apps, this is the
|
|
// signal to break out of the main loop. Without this, shellForceKillApp
|
|
// would have to terminate the task forcibly, potentially leaking resources.
|
|
void appShutdown(void) {
|
|
sState.quit = true;
|
|
}
|
|
|
|
// ============================================================
|
|
// Entry point (runs in its own task)
|
|
// ============================================================
|
|
|
|
// This runs in its own task. The shell creates the task before calling
|
|
// appMain, and reaps it when appMain returns.
|
|
int32_t appMain(DxeAppContextT *ctx) {
|
|
sCtx = ctx;
|
|
AppContextT *ac = ctx->shellCtx;
|
|
|
|
memset(&sState, 0, sizeof(sState));
|
|
updateTime();
|
|
|
|
// Position in the upper-right corner, out of the way of other windows
|
|
int32_t winW = 200;
|
|
int32_t winH = 100;
|
|
int32_t winX = ac->display.width - winW - 40;
|
|
int32_t winY = 40;
|
|
|
|
// resizable=false: clock has a fixed size, no resize handle
|
|
sWin = dvxCreateWindow(ac, "Clock", winX, winY, winW, winH, false);
|
|
|
|
if (!sWin) {
|
|
return -1;
|
|
}
|
|
|
|
sWin->onClose = onClose;
|
|
sWin->onPaint = onPaint;
|
|
|
|
// Initial paint — dvxInvalidateWindow calls onPaint automatically
|
|
dvxInvalidateWindow(ac, sWin);
|
|
|
|
// Main loop: check if the second has changed, repaint if so, then yield.
|
|
// tsYield() transfers control back to the shell's task scheduler.
|
|
// On a 486, time() resolution is 1 second, so we yield many times per
|
|
// second between actual updates — this keeps CPU usage near zero.
|
|
// dvxInvalidateWindow marks the window dirty and calls onPaint to
|
|
// update the content buffer before the compositor flushes it.
|
|
while (!sState.quit) {
|
|
time_t now = time(NULL);
|
|
|
|
if (now != sState.lastUpdate) {
|
|
updateTime();
|
|
dvxInvalidateWindow(ac, sWin);
|
|
}
|
|
|
|
tsYield();
|
|
}
|
|
|
|
// Cleanup
|
|
if (sWin) {
|
|
dvxDestroyWindow(ac, sWin);
|
|
sWin = NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|