Resource support! Apps now show as icons in the Program Manager.

This commit is contained in:
Scott Duensing 2026-03-26 20:13:26 -05:00
parent 9b136995b7
commit fdc72f98a1
23 changed files with 1564 additions and 31 deletions

View file

@ -3,9 +3,9 @@
# Builds the full DVX stack: core library, task switcher, # Builds the full DVX stack: core library, task switcher,
# bootstrap loader, text help library, widgets, shell, and apps. # bootstrap loader, text help library, widgets, shell, and apps.
.PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr apps .PHONY: all clean core tasks loader texthelp listhelp widgets shell taskmgr apps tools
all: core tasks loader texthelp listhelp widgets shell taskmgr apps all: core tasks loader texthelp listhelp widgets shell taskmgr apps tools
core: core:
$(MAKE) -C core $(MAKE) -C core
@ -31,7 +31,10 @@ shell: core tasks
taskmgr: shell taskmgr: shell
$(MAKE) -C taskmgr $(MAKE) -C taskmgr
apps: core tasks shell tools:
$(MAKE) -C tools
apps: core tasks shell tools
$(MAKE) -C apps $(MAKE) -C apps
clean: clean:
@ -44,6 +47,7 @@ clean:
$(MAKE) -C shell clean $(MAKE) -C shell clean
$(MAKE) -C taskmgr clean $(MAKE) -C taskmgr clean
$(MAKE) -C apps clean $(MAKE) -C apps clean
$(MAKE) -C tools clean
-rmdir obj 2>/dev/null -rmdir obj 2>/dev/null
-rm -rf bin/config bin/widgets bin/libs -rm -rf bin/config bin/widgets bin/libs
-rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin 2>/dev/null -rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin 2>/dev/null

View file

@ -8,6 +8,7 @@ CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/pl
OBJDIR = ../obj/apps OBJDIR = ../obj/apps
BINDIR = ../bin/apps BINDIR = ../bin/apps
DVXRES = ../bin/dvxres
# App definitions: each is a subdir with a single .c file # App definitions: each is a subdir with a single .c file
APPS = progman notepad clock dvxdemo cpanel imgview APPS = progman notepad clock dvxdemo cpanel imgview
@ -23,25 +24,30 @@ notepad: $(BINDIR)/notepad/notepad.app
clock: $(BINDIR)/clock/clock.app clock: $(BINDIR)/clock/clock.app
dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app dvxdemo: $(BINDIR)/dvxdemo/dvxdemo.app
$(BINDIR)/cpanel/cpanel.app: $(OBJDIR)/cpanel.o | $(BINDIR)/cpanel $(BINDIR)/cpanel/cpanel.app: $(OBJDIR)/cpanel.o cpanel/cpanel.res cpanel/icon32.bmp | $(BINDIR)/cpanel
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(DVXRES) build $@ cpanel/cpanel.res
$(BINDIR)/imgview/imgview.app: $(OBJDIR)/imgview.o | $(BINDIR)/imgview $(BINDIR)/imgview/imgview.app: $(OBJDIR)/imgview.o imgview/imgview.res imgview/icon32.bmp | $(BINDIR)/imgview
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(DVXRES) build $@ imgview/imgview.res
$(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman $(BINDIR)/progman/progman.app: $(OBJDIR)/progman.o | $(BINDIR)/progman
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(BINDIR)/notepad/notepad.app: $(OBJDIR)/notepad.o | $(BINDIR)/notepad $(BINDIR)/notepad/notepad.app: $(OBJDIR)/notepad.o notepad/notepad.res notepad/icon32.bmp | $(BINDIR)/notepad
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(DVXRES) build $@ notepad/notepad.res
$(BINDIR)/clock/clock.app: $(OBJDIR)/clock.o | $(BINDIR)/clock $(BINDIR)/clock/clock.app: $(OBJDIR)/clock.o clock/clock.res clock/icon32.bmp | $(BINDIR)/clock
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -E _appShutdown -U $<
$(DVXRES) build $@ clock/clock.res
DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp DVXDEMO_BMPS = logo.bmp new.bmp open.bmp sample.bmp save.bmp
$(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) | $(BINDIR)/dvxdemo $(BINDIR)/dvxdemo/dvxdemo.app: $(OBJDIR)/dvxdemo.o $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) dvxdemo/dvxdemo.res dvxdemo/icon32.bmp | $(BINDIR)/dvxdemo
$(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $< $(DXE3GEN) -o $@ -E _appDescriptor -E _appMain -U $<
$(DVXRES) build $@ dvxdemo/dvxdemo.res
cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/ cp $(addprefix dvxdemo/,$(DVXDEMO_BMPS)) $(BINDIR)/dvxdemo/
$(OBJDIR)/cpanel.o: cpanel/cpanel.c | $(OBJDIR) $(OBJDIR)/cpanel.o: cpanel/cpanel.c | $(OBJDIR)

5
apps/clock/clock.res Normal file
View file

@ -0,0 +1,5 @@
# clock.res -- Resource manifest for Clock
icon32 icon clock/icon32.bmp
name text "Clock"
author text "DVX Project"
description text "Digital clock with date display"

BIN
apps/clock/icon32.bmp (Stored with Git LFS) Normal file

Binary file not shown.

5
apps/cpanel/cpanel.res Normal file
View file

@ -0,0 +1,5 @@
# cpanel.res -- Resource manifest for Control Panel
icon32 icon cpanel/icon32.bmp
name text "Control Panel"
author text "DVX Project"
description text "System settings and preferences"

BIN
apps/cpanel/icon32.bmp (Stored with Git LFS) Normal file

Binary file not shown.

5
apps/dvxdemo/dvxdemo.res Normal file
View file

@ -0,0 +1,5 @@
# dvxdemo.res -- Resource manifest for DVX Demo
icon32 icon dvxdemo/icon32.bmp
name text "DVX Demo"
author text "DVX Project"
description text "Widget toolkit demonstration"

BIN
apps/dvxdemo/icon32.bmp (Stored with Git LFS) Normal file

Binary file not shown.

BIN
apps/imgview/icon32.bmp (Stored with Git LFS) Normal file

Binary file not shown.

5
apps/imgview/imgview.res Normal file
View file

@ -0,0 +1,5 @@
# imgview.res -- Resource manifest for Image Viewer
icon32 icon imgview/icon32.bmp
name text "Image Viewer"
author text "DVX Project"
description text "BMP, PNG, JPEG, and GIF viewer"

BIN
apps/notepad/icon32.bmp (Stored with Git LFS) Normal file

Binary file not shown.

5
apps/notepad/notepad.res Normal file
View file

@ -0,0 +1,5 @@
# notepad.res -- Resource manifest for Notepad
icon32 icon notepad/icon32.bmp
name text "Notepad"
author text "DVX Project"
description text "Simple text editor"

View file

@ -31,6 +31,8 @@
#include "dvxPlatform.h" #include "dvxPlatform.h"
#include "shellApp.h" #include "shellApp.h"
#include "shellInfo.h" #include "shellInfo.h"
#include "dvxResource.h"
#include "widgetImageButton.h"
#include <stdint.h> #include <stdint.h>
#include <stdbool.h> #include <stdbool.h>
@ -50,9 +52,12 @@
// DOS 8.3 paths are short, but long names under DJGPP can reach ~260 // DOS 8.3 paths are short, but long names under DJGPP can reach ~260
#define MAX_PATH_LEN 260 #define MAX_PATH_LEN 260
// Grid layout for app buttons: 4 columns, rows created dynamically // Grid layout for app buttons: 4 columns, rows created dynamically
#define PM_TOOLTIP_LEN 128
#define PM_GRID_COLS 4 #define PM_GRID_COLS 4
#define PM_BTN_W 100 #define PM_BTN_W 100
#define PM_BTN_H 24 #define PM_BTN_H 24
#define PM_CELL_W 80
#define PM_CELL_H 64
#define PM_WIN_W 440 #define PM_WIN_W 440
#define PM_WIN_H 340 #define PM_WIN_H 340
#define PM_GRID_SPACING 8 #define PM_GRID_SPACING 8
@ -77,8 +82,13 @@
// Each discovered .app file in the apps/ directory tree // Each discovered .app file in the apps/ directory tree
typedef struct { typedef struct {
char name[SHELL_APP_NAME_MAX]; // display name (filename without .app) char name[SHELL_APP_NAME_MAX]; // short display name (from "name" resource or filename)
char tooltip[PM_TOOLTIP_LEN]; // description for tooltip (from "description" resource)
char path[MAX_PATH_LEN]; // full path char path[MAX_PATH_LEN]; // full path
uint8_t *iconData; // 32x32 icon in display format (NULL if none)
int32_t iconW;
int32_t iconH;
int32_t iconPitch;
} AppEntryT; } AppEntryT;
// Module-level statics (s prefix). DXE apps use file-scoped statics because // Module-level statics (s prefix). DXE apps use file-scoped statics because
@ -182,23 +192,53 @@ static void buildPmWindow(void) {
if (sAppCount == 0) { if (sAppCount == 0) {
wgtLabel(appFrame, "(No applications found in apps/ directory)"); wgtLabel(appFrame, "(No applications found in apps/ directory)");
} else { } else {
// Build rows of buttons. Each row is an HBox holding PM_GRID_COLS // Build grid of app icons. Each cell is a VBox with an image
// buttons. userData points back to the AppEntryT so the click // button (or text button if no icon) and a label underneath.
// callback knows which app to launch.
WidgetT *hbox = NULL; WidgetT *hbox = NULL;
for (int32_t i = 0; i < sAppCount; i++) { for (int32_t i = 0; i < sAppCount; i++) {
if (i % PM_GRID_COLS == 0) { if (i % PM_GRID_COLS == 0) {
hbox = wgtHBox(appFrame); hbox = wgtHBox(appFrame);
hbox->spacing = wgtPixels(PM_GRID_SPACING); hbox->spacing = wgtPixels(PM_GRID_SPACING);
hbox->align = AlignCenterE;
} }
WidgetT *btn = wgtButton(hbox, sAppFiles[i].name); WidgetT *cell = wgtVBox(hbox);
cell->align = AlignCenterE;
cell->minW = wgtPixels(PM_CELL_W);
cell->minH = wgtPixels(PM_CELL_H);
WidgetT *btn = NULL;
if (sAppFiles[i].iconData) {
int32_t dataSize = sAppFiles[i].iconPitch * sAppFiles[i].iconH;
uint8_t *iconCopy = (uint8_t *)malloc(dataSize);
if (iconCopy) {
memcpy(iconCopy, sAppFiles[i].iconData, dataSize);
btn = wgtImageButton(cell, iconCopy, sAppFiles[i].iconW, sAppFiles[i].iconH, sAppFiles[i].iconPitch);
btn->maxW = wgtPixels(sAppFiles[i].iconW + 4);
btn->maxH = wgtPixels(sAppFiles[i].iconH + 4);
}
}
if (!btn) {
btn = wgtButton(cell, sAppFiles[i].name);
btn->prefW = wgtPixels(PM_BTN_W); btn->prefW = wgtPixels(PM_BTN_W);
btn->prefH = wgtPixels(PM_BTN_H); btn->prefH = wgtPixels(PM_BTN_H);
}
btn->userData = &sAppFiles[i]; btn->userData = &sAppFiles[i];
btn->onDblClick = onAppButtonClick; btn->onDblClick = onAppButtonClick;
btn->onClick = onAppButtonClick; btn->onClick = onAppButtonClick;
if (sAppFiles[i].tooltip[0]) {
wgtSetTooltip(btn, sAppFiles[i].tooltip);
}
WidgetT *label = wgtLabel(cell, sAppFiles[i].name);
label->maxW = wgtPixels(PM_CELL_W);
wgtLabelSetAlign(label, AlignCenterE);
} }
} }
@ -386,7 +426,11 @@ static void scanAppsDirRecurse(const char *dirPath) {
AppEntryT *entry = &sAppFiles[sAppCount]; AppEntryT *entry = &sAppFiles[sAppCount];
// Name = filename without extension snprintf(entry->path, sizeof(entry->path), "%s", fullPath);
entry->iconData = NULL;
entry->tooltip[0] = '\0';
// Default name from filename (without .app extension)
int32_t nameLen = len - 4; int32_t nameLen = len - 4;
if (nameLen >= SHELL_APP_NAME_MAX) { if (nameLen >= SHELL_APP_NAME_MAX) {
@ -396,12 +440,43 @@ static void scanAppsDirRecurse(const char *dirPath) {
memcpy(entry->name, ent->d_name, nameLen); memcpy(entry->name, ent->d_name, nameLen);
entry->name[nameLen] = '\0'; entry->name[nameLen] = '\0';
// Capitalize first letter for display
if (entry->name[0] >= 'a' && entry->name[0] <= 'z') { if (entry->name[0] >= 'a' && entry->name[0] <= 'z') {
entry->name[0] -= 32; entry->name[0] -= 32;
} }
snprintf(entry->path, sizeof(entry->path), "%s", fullPath); // Override from embedded resources if available
DvxResHandleT *res = dvxResOpen(fullPath);
if (res) {
uint32_t iconSize = 0;
void *iconBuf = dvxResRead(res, "icon32", &iconSize);
if (iconBuf) {
entry->iconData = dvxLoadImageFromMemory(sAc, (const uint8_t *)iconBuf, (int32_t)iconSize, &entry->iconW, &entry->iconH, &entry->iconPitch);
free(iconBuf);
}
uint32_t nameSize = 0;
void *nameBuf = dvxResRead(res, "name", &nameSize);
if (nameBuf) {
strncpy(entry->name, (const char *)nameBuf, SHELL_APP_NAME_MAX - 1);
entry->name[SHELL_APP_NAME_MAX - 1] = '\0';
free(nameBuf);
}
uint32_t descSize = 0;
void *descBuf = dvxResRead(res, "description", &descSize);
if (descBuf) {
strncpy(entry->tooltip, (const char *)descBuf, sizeof(entry->tooltip) - 1);
entry->tooltip[sizeof(entry->tooltip) - 1] = '\0';
free(descBuf);
}
dvxResClose(res);
}
sAppCount++; sAppCount++;
} }

View file

@ -14,7 +14,7 @@ LIBSDIR = ../bin/libs
# Core sources # Core sources
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \ SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \
dvxApp.c dvxDialog.c dvxPrefs.c \ dvxApp.c dvxDialog.c dvxPrefs.c dvxResource.c \
widgetClass.c widgetCore.c widgetScrollbar.c \ widgetClass.c widgetCore.c widgetScrollbar.c \
widgetLayout.c widgetEvent.c widgetOps.c widgetLayout.c widgetEvent.c widgetOps.c

View file

@ -2529,6 +2529,72 @@ uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, i
} }
// ============================================================
// dvxLoadImageFromMemory
// ============================================================
//
// Same as dvxLoadImage but loads from a memory buffer (e.g. a
// resource read via dvxResRead). Supports BMP, PNG, JPEG, GIF.
uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch) {
if (!ctx || !data || dataLen <= 0) {
return NULL;
}
const DisplayT *d = &ctx->display;
int imgW;
int imgH;
int channels;
uint8_t *rgb = stbi_load_from_memory(data, dataLen, &imgW, &imgH, &channels, 3);
if (!rgb) {
return NULL;
}
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = imgW * bpp;
uint8_t *buf = (uint8_t *)malloc(pitch * imgH);
if (!buf) {
stbi_image_free(rgb);
return NULL;
}
for (int32_t y = 0; y < imgH; y++) {
for (int32_t x = 0; x < imgW; x++) {
const uint8_t *src = rgb + (y * imgW + x) * 3;
uint32_t color = packColor(d, src[0], src[1], src[2]);
uint8_t *dst = buf + y * pitch + x * bpp;
if (bpp == 1) {
*dst = (uint8_t)color;
} else if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else {
*(uint32_t *)dst = color;
}
}
}
stbi_image_free(rgb);
if (outW) {
*outW = imgW;
}
if (outH) {
*outH = imgH;
}
if (outPitch) {
*outPitch = pitch;
}
return buf;
}
// ============================================================ // ============================================================
// dvxInvalidateRect // dvxInvalidateRect
// ============================================================ // ============================================================

View file

@ -289,6 +289,10 @@ void dvxTileWindowsV(AppContextT *ctx);
// image dimensions and row pitch in bytes. // image dimensions and row pitch in bytes.
uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch); uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch);
// Load an image from a memory buffer (e.g. resource data). Same output
// format as dvxLoadImage. Caller must free the result with dvxFreeImage.
uint8_t *dvxLoadImageFromMemory(const AppContextT *ctx, const uint8_t *data, int32_t dataLen, int32_t *outW, int32_t *outH, int32_t *outPitch);
// Free a pixel buffer returned by dvxLoadImage. // Free a pixel buffer returned by dvxLoadImage.
void dvxFreeImage(uint8_t *data); void dvxFreeImage(uint8_t *data);

140
core/dvxResource.c Normal file
View file

@ -0,0 +1,140 @@
// dvxResource.c -- DVX resource runtime API
//
// Reads the resource block appended to DXE3 files. The resource
// block is located by reading the footer at the end of the file.
#include "dvxResource.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
DvxResHandleT *dvxResOpen(const char *path) {
if (!path) {
return NULL;
}
FILE *f = fopen(path, "rb");
if (!f) {
return NULL;
}
// Read footer from end of file
if (fseek(f, -(int32_t)sizeof(DvxResFooterT), SEEK_END) != 0) {
fclose(f);
return NULL;
}
DvxResFooterT footer;
if (fread(&footer, sizeof(footer), 1, f) != 1) {
fclose(f);
return NULL;
}
if (footer.magic != DVX_RES_MAGIC || footer.entryCount == 0) {
fclose(f);
return NULL;
}
// Read directory
if (fseek(f, footer.dirOffset, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT));
if (!entries) {
fclose(f);
return NULL;
}
if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) {
free(entries);
fclose(f);
return NULL;
}
fclose(f);
DvxResHandleT *h = (DvxResHandleT *)malloc(sizeof(DvxResHandleT));
if (!h) {
free(entries);
return NULL;
}
strncpy(h->path, path, sizeof(h->path) - 1);
h->path[sizeof(h->path) - 1] = '\0';
h->entries = entries;
h->entryCount = footer.entryCount;
return h;
}
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name) {
if (!h || !name) {
return NULL;
}
for (uint32_t i = 0; i < h->entryCount; i++) {
if (strcmp(h->entries[i].name, name) == 0) {
return &h->entries[i];
}
}
return NULL;
}
void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize) {
const DvxResDirEntryT *entry = dvxResFind(h, name);
if (!entry) {
return NULL;
}
FILE *f = fopen(h->path, "rb");
if (!f) {
return NULL;
}
if (fseek(f, entry->offset, SEEK_SET) != 0) {
fclose(f);
return NULL;
}
void *buf = malloc(entry->size);
if (!buf) {
fclose(f);
return NULL;
}
if (fread(buf, 1, entry->size, f) != entry->size) {
free(buf);
fclose(f);
return NULL;
}
fclose(f);
if (outSize) {
*outSize = entry->size;
}
return buf;
}
void dvxResClose(DvxResHandleT *h) {
if (h) {
free(h->entries);
free(h);
}
}

73
core/dvxResource.h Normal file
View file

@ -0,0 +1,73 @@
// dvxResource.h -- DVX resource format and runtime API
//
// Resources are appended to DXE3 files (.app, .wgt, .lib) after the
// normal DXE content. dlopen never reads past the DXE header-specified
// sections, so the appended data is invisible to the loader.
//
// File layout:
// [DXE3 content] -- untouched, loaded by dlopen
// [resource data entries] -- sequential, variable length
// [resource directory] -- fixed-size entries, one per resource
// [footer] -- magic + directory offset + count
//
// Reading starts from the end: seek to EOF - sizeof(footer), verify
// the magic, then seek to the directory and read entries.
#ifndef DVX_RESOURCE_H
#define DVX_RESOURCE_H
#include <stdint.h>
// Resource type IDs
#define DVX_RES_ICON 1 // image data (BMP icon: 16x16, 32x32, etc.)
#define DVX_RES_TEXT 2 // null-terminated string (author, copyright, etc.)
#define DVX_RES_BINARY 3 // arbitrary binary data (app-specific)
// Footer magic: "DVXR" as a 32-bit little-endian value
#define DVX_RES_MAGIC 0x52585644UL
// Maximum resource name length (including null terminator)
#define DVX_RES_NAME_MAX 32
// Directory entry (48 bytes each)
typedef struct {
char name[DVX_RES_NAME_MAX]; // resource name (e.g. "icon16", "author")
uint32_t type; // DVX_RES_ICON, DVX_RES_TEXT, DVX_RES_BINARY
uint32_t offset; // absolute file offset of data
uint32_t size; // data size in bytes
uint32_t reserved; // padding for future use
} DvxResDirEntryT;
// Footer (16 bytes, at the very end of the file)
typedef struct {
uint32_t magic; // DVX_RES_MAGIC
uint32_t dirOffset; // absolute file offset of directory
uint32_t entryCount; // number of resources
uint32_t reserved;
} DvxResFooterT;
// Runtime resource handle (opaque to callers)
typedef struct {
char path[260];
DvxResDirEntryT *entries;
uint32_t entryCount;
} DvxResHandleT;
// Open a resource handle by reading the footer and directory.
// Returns NULL if the file has no resource block.
DvxResHandleT *dvxResOpen(const char *path);
// Find a resource by name and read its data into a malloc'd buffer.
// Sets *outSize to the data size. Returns NULL if not found.
// Caller must free the returned buffer.
void *dvxResRead(DvxResHandleT *h, const char *name, uint32_t *outSize);
// Find a resource by name and return its directory entry.
// Returns NULL if not found. The returned pointer is valid until
// dvxResClose is called.
const DvxResDirEntryT *dvxResFind(DvxResHandleT *h, const char *name);
// Close the handle and free all associated memory.
void dvxResClose(DvxResHandleT *h);
#endif // DVX_RESOURCE_H

View file

@ -290,15 +290,35 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
} }
// Assign geometry // Assign geometry
int32_t crossSize = availCross;
// Apply max size on cross axis
if (horiz && c->maxH) {
int32_t maxPx = wgtResolveSize(c->maxH, innerH, font->charWidth);
if (crossSize > maxPx) {
crossSize = maxPx;
}
} else if (!horiz && c->maxW) {
int32_t maxPx = wgtResolveSize(c->maxW, innerW, font->charWidth);
if (crossSize > maxPx) {
crossSize = maxPx;
}
}
// Center on cross axis when child is constrained smaller
int32_t crossOff = (crossSize < availCross) ? (availCross - crossSize) / 2 : 0;
if (horiz) { if (horiz) {
c->x = pos; c->x = pos;
c->y = innerY; c->y = innerY + crossOff;
c->w = mainSize; c->w = mainSize;
c->h = availCross; c->h = crossSize;
} else { } else {
c->x = innerX; c->x = innerX + crossOff;
c->y = pos; c->y = pos;
c->w = availCross; c->w = crossSize;
c->h = mainSize; c->h = mainSize;
} }

View file

@ -276,6 +276,10 @@ int shellMain(int argc, char *argv[]) {
sCtx.onTitleChange = titleChangeHandler; sCtx.onTitleChange = titleChangeHandler;
sCtx.titleChangeCtx = &sCtx; sCtx.titleChangeCtx = &sCtx;
// Install crash handler before loading apps so faults during app
// initialization are caught and recovered from gracefully.
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, dvxLog);
// Load the desktop app (configurable via [shell] desktop= in dvx.ini) // Load the desktop app (configurable via [shell] desktop= in dvx.ini)
const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP); const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP);
int32_t desktopId = shellLoadApp(&sCtx, desktopApp); int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
@ -287,12 +291,6 @@ int shellMain(int argc, char *argv[]) {
return 1; return 1;
} }
// Install crash handler after everything is initialized -- if
// initialization itself crashes, we want the default behavior
// (abort with register dump) rather than our recovery path, because
// the system isn't in a recoverable state yet.
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, dvxLog);
dvxLog("DVX Shell ready."); dvxLog("DVX Shell ready.");
// Set recovery point for crash handler. setjmp returns 0 on initial // Set recovery point for crash handler. setjmp returns 0 on initial

22
tools/Makefile Normal file
View file

@ -0,0 +1,22 @@
# DVX Tools Makefile
#
# Builds native (host) utilities. These run on the development machine
# (Linux or DOS), not on the target. No cross-compilation needed.
CC = gcc
CFLAGS = -O2 -Wall -Wextra -I../core
BINDIR = ../bin
.PHONY: all clean
all: $(BINDIR)/dvxres
$(BINDIR)/dvxres: dvxres.c ../core/dvxResource.c ../core/dvxResource.h | $(BINDIR)
$(CC) $(CFLAGS) -o $@ dvxres.c ../core/dvxResource.c
$(BINDIR):
mkdir -p $(BINDIR)
clean:
rm -f $(BINDIR)/dvxres

814
tools/dvxres.c Normal file
View file

@ -0,0 +1,814 @@
// dvxres.c -- DVX resource tool
//
// Manages resource blocks appended to DXE3 files (.app, .wgt, .lib).
// Builds on both DOS (DJGPP) and Linux (GCC) with no dependencies
// beyond standard C.
//
// Commands:
// dvxres add <file> <name> <type> <data|@file>
// dvxres build <file> <manifest.res>
// dvxres list <file>
// dvxres get <file> <name> [outfile]
// dvxres strip <file>
//
// The resource block is appended after the DXE3 content:
// [resource data entries]
// [resource directory]
// [footer with magic + directory offset + count]
#include "../core/dvxResource.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// Prototypes
// ============================================================
static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, const char *dataArg);
static int cmdBuild(const char *dxePath, const char *manifestPath);
static int cmdGet(const char *dxePath, const char *name, const char *outPath);
static int cmdList(const char *dxePath);
static int cmdStrip(const char *dxePath);
static long dxeContentSize(const char *path);
static int parseType(const char *typeStr);
static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData);
static void usage(void);
// ============================================================
// dxeContentSize
// ============================================================
//
// Returns the size of the DXE3 content (before any appended resources).
// If the file has a resource footer, returns the start of the resource
// data. Otherwise returns the full file size.
static long dxeContentSize(const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
return -1;
}
fseek(f, 0, SEEK_END);
long fileSize = ftell(f);
if (fileSize < (long)sizeof(DvxResFooterT)) {
fclose(f);
return fileSize;
}
// Check for resource footer
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
DvxResFooterT footer;
if (fread(&footer, sizeof(footer), 1, f) == 1 && footer.magic == DVX_RES_MAGIC) {
// Find the earliest resource data offset
fseek(f, footer.dirOffset, SEEK_SET);
DvxResDirEntryT entry;
long earliest = footer.dirOffset;
for (uint32_t i = 0; i < footer.entryCount; i++) {
if (fread(&entry, sizeof(entry), 1, f) == 1) {
if ((long)entry.offset < earliest) {
earliest = (long)entry.offset;
}
}
}
fclose(f);
return earliest;
}
fclose(f);
return fileSize;
}
// ============================================================
// parseType
// ============================================================
static int parseType(const char *typeStr) {
if (strcmp(typeStr, "icon") == 0 || strcmp(typeStr, "image") == 0) {
return DVX_RES_ICON;
}
if (strcmp(typeStr, "text") == 0) {
return DVX_RES_TEXT;
}
if (strcmp(typeStr, "binary") == 0) {
return DVX_RES_BINARY;
}
return -1;
}
// ============================================================
// readExistingResources
// ============================================================
//
// Reads any existing resource block from the file. Returns arrays
// of directory entries and data blobs. Caller frees everything.
static int readExistingResources(const char *path, long dxeSize, DvxResDirEntryT **outEntries, uint32_t *outCount, uint8_t ***outData) {
*outEntries = NULL;
*outCount = 0;
*outData = NULL;
FILE *f = fopen(path, "rb");
if (!f) {
return -1;
}
fseek(f, 0, SEEK_END);
long fileSize = ftell(f);
if (fileSize <= dxeSize) {
fclose(f);
return 0;
}
// Check for footer
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
DvxResFooterT footer;
if (fread(&footer, sizeof(footer), 1, f) != 1 || footer.magic != DVX_RES_MAGIC) {
fclose(f);
return 0;
}
// Read directory
fseek(f, footer.dirOffset, SEEK_SET);
DvxResDirEntryT *entries = (DvxResDirEntryT *)malloc(footer.entryCount * sizeof(DvxResDirEntryT));
if (!entries) {
fclose(f);
return -1;
}
if (fread(entries, sizeof(DvxResDirEntryT), footer.entryCount, f) != footer.entryCount) {
free(entries);
fclose(f);
return -1;
}
// Read each resource's data
uint8_t **data = (uint8_t **)malloc(footer.entryCount * sizeof(uint8_t *));
if (!data) {
free(entries);
fclose(f);
return -1;
}
for (uint32_t i = 0; i < footer.entryCount; i++) {
data[i] = (uint8_t *)malloc(entries[i].size);
if (!data[i]) {
for (uint32_t j = 0; j < i; j++) {
free(data[j]);
}
free(data);
free(entries);
fclose(f);
return -1;
}
fseek(f, entries[i].offset, SEEK_SET);
fread(data[i], 1, entries[i].size, f);
}
fclose(f);
*outEntries = entries;
*outCount = footer.entryCount;
*outData = data;
return 0;
}
// ============================================================
// writeResources
// ============================================================
//
// Truncates the file to dxeSize and writes the resource block.
static int writeResources(const char *path, long dxeSize, DvxResDirEntryT *entries, uint32_t count, uint8_t **data) {
// Read the DXE content first
FILE *f = fopen(path, "rb");
if (!f) {
return -1;
}
uint8_t *dxeBuf = (uint8_t *)malloc(dxeSize);
if (!dxeBuf) {
fclose(f);
return -1;
}
if (fread(dxeBuf, 1, dxeSize, f) != (size_t)dxeSize) {
free(dxeBuf);
fclose(f);
return -1;
}
fclose(f);
// Rewrite file: DXE content + resources
f = fopen(path, "wb");
if (!f) {
free(dxeBuf);
return -1;
}
fwrite(dxeBuf, 1, dxeSize, f);
free(dxeBuf);
// Write resource data entries and update offsets
for (uint32_t i = 0; i < count; i++) {
entries[i].offset = (uint32_t)ftell(f);
fwrite(data[i], 1, entries[i].size, f);
}
// Write directory
uint32_t dirOffset = (uint32_t)ftell(f);
fwrite(entries, sizeof(DvxResDirEntryT), count, f);
// Write footer
DvxResFooterT footer;
footer.magic = DVX_RES_MAGIC;
footer.dirOffset = dirOffset;
footer.entryCount = count;
footer.reserved = 0;
fwrite(&footer, sizeof(footer), 1, f);
fclose(f);
return 0;
}
// ============================================================
// cmdAdd
// ============================================================
static int cmdAdd(const char *dxePath, const char *name, const char *typeStr, const char *dataArg) {
int type = parseType(typeStr);
if (type < 0) {
fprintf(stderr, "Unknown resource type: %s (use icon, text, or binary)\n", typeStr);
return 1;
}
if (strlen(name) >= DVX_RES_NAME_MAX) {
fprintf(stderr, "Resource name too long (max %d chars)\n", DVX_RES_NAME_MAX - 1);
return 1;
}
long dxeSize = dxeContentSize(dxePath);
if (dxeSize < 0) {
fprintf(stderr, "Cannot open: %s\n", dxePath);
return 1;
}
// Read existing resources
DvxResDirEntryT *entries = NULL;
uint32_t count = 0;
uint8_t **data = NULL;
readExistingResources(dxePath, dxeSize, &entries, &count, &data);
// Remove existing entry with same name (replace)
for (uint32_t i = 0; i < count; i++) {
if (strcmp(entries[i].name, name) == 0) {
free(data[i]);
for (uint32_t j = i; j < count - 1; j++) {
entries[j] = entries[j + 1];
data[j] = data[j + 1];
}
count--;
break;
}
}
// Prepare new resource data
uint8_t *newData = NULL;
uint32_t newSize = 0;
if (type == DVX_RES_TEXT) {
// Text data from command line argument
newSize = (uint32_t)strlen(dataArg) + 1;
newData = (uint8_t *)malloc(newSize);
if (!newData) {
fprintf(stderr, "Out of memory\n");
return 1;
}
memcpy(newData, dataArg, newSize);
} else {
// Binary/icon data from file
FILE *df = fopen(dataArg, "rb");
if (!df) {
fprintf(stderr, "Cannot open data file: %s\n", dataArg);
return 1;
}
fseek(df, 0, SEEK_END);
newSize = (uint32_t)ftell(df);
fseek(df, 0, SEEK_SET);
newData = (uint8_t *)malloc(newSize);
if (!newData) {
fclose(df);
fprintf(stderr, "Out of memory\n");
return 1;
}
if (fread(newData, 1, newSize, df) != newSize) {
fclose(df);
free(newData);
fprintf(stderr, "Failed to read: %s\n", dataArg);
return 1;
}
fclose(df);
}
// Append to arrays
entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
memset(&entries[count], 0, sizeof(DvxResDirEntryT));
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1);
entries[count].type = (uint32_t)type;
entries[count].size = newSize;
data[count] = newData;
count++;
// Write
int result = writeResources(dxePath, dxeSize, entries, count, data);
// Cleanup
for (uint32_t i = 0; i < count; i++) {
free(data[i]);
}
free(data);
free(entries);
if (result != 0) {
fprintf(stderr, "Failed to write resources to: %s\n", dxePath);
return 1;
}
printf("Added '%s' (%s, %u bytes) to %s\n", name, typeStr, newSize, dxePath);
return 0;
}
// ============================================================
// cmdBuild
// ============================================================
static int cmdBuild(const char *dxePath, const char *manifestPath) {
FILE *mf = fopen(manifestPath, "r");
if (!mf) {
fprintf(stderr, "Cannot open manifest: %s\n", manifestPath);
return 1;
}
long dxeSize = dxeContentSize(dxePath);
if (dxeSize < 0) {
fclose(mf);
fprintf(stderr, "Cannot open: %s\n", dxePath);
return 1;
}
// Strip any existing resources first -- start fresh
DvxResDirEntryT *entries = NULL;
uint32_t count = 0;
uint8_t **data = NULL;
char line[1024];
int lineNum = 0;
while (fgets(line, sizeof(line), mf)) {
lineNum++;
// Strip trailing newline/CR
size_t len = strlen(line);
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) {
line[--len] = '\0';
}
// Skip empty lines and comments
char *p = line;
while (*p && isspace((unsigned char)*p)) {
p++;
}
if (*p == '\0' || *p == '#') {
continue;
}
// Parse: name type data
// name and type are whitespace-delimited
// data is either a quoted string or a filename (rest of line)
char name[DVX_RES_NAME_MAX];
char typeStr[16];
char dataArg[512];
if (sscanf(p, "%31s %15s", name, typeStr) != 2) {
fprintf(stderr, "%s:%d: expected: name type data\n", manifestPath, lineNum);
goto fail;
}
// Advance past name and type to get data
char *dp = p;
// Skip name
while (*dp && !isspace((unsigned char)*dp)) {
dp++;
}
while (*dp && isspace((unsigned char)*dp)) {
dp++;
}
// Skip type
while (*dp && !isspace((unsigned char)*dp)) {
dp++;
}
while (*dp && isspace((unsigned char)*dp)) {
dp++;
}
if (*dp == '\0') {
fprintf(stderr, "%s:%d: missing data argument\n", manifestPath, lineNum);
goto fail;
}
// Handle quoted strings: strip quotes
if (*dp == '"') {
dp++;
char *end = strrchr(dp, '"');
if (end) {
*end = '\0';
}
}
strncpy(dataArg, dp, sizeof(dataArg) - 1);
dataArg[sizeof(dataArg) - 1] = '\0';
int type = parseType(typeStr);
if (type < 0) {
fprintf(stderr, "%s:%d: unknown type: %s\n", manifestPath, lineNum, typeStr);
goto fail;
}
// Prepare data
uint8_t *newData = NULL;
uint32_t newSize = 0;
if (type == DVX_RES_TEXT) {
newSize = (uint32_t)strlen(dataArg) + 1;
newData = (uint8_t *)malloc(newSize);
if (!newData) {
fprintf(stderr, "Out of memory\n");
goto fail;
}
memcpy(newData, dataArg, newSize);
} else {
FILE *df = fopen(dataArg, "rb");
if (!df) {
fprintf(stderr, "%s:%d: cannot open: %s\n", manifestPath, lineNum, dataArg);
goto fail;
}
fseek(df, 0, SEEK_END);
newSize = (uint32_t)ftell(df);
fseek(df, 0, SEEK_SET);
newData = (uint8_t *)malloc(newSize);
if (!newData) {
fclose(df);
fprintf(stderr, "Out of memory\n");
goto fail;
}
if (fread(newData, 1, newSize, df) != newSize) {
fclose(df);
free(newData);
fprintf(stderr, "%s:%d: failed to read: %s\n", manifestPath, lineNum, dataArg);
goto fail;
}
fclose(df);
}
// Append
entries = (DvxResDirEntryT *)realloc(entries, (count + 1) * sizeof(DvxResDirEntryT));
data = (uint8_t **)realloc(data, (count + 1) * sizeof(uint8_t *));
memset(&entries[count], 0, sizeof(DvxResDirEntryT));
strncpy(entries[count].name, name, DVX_RES_NAME_MAX - 1);
entries[count].type = (uint32_t)type;
entries[count].size = newSize;
data[count] = newData;
count++;
printf(" %s (%s, %u bytes)\n", name, typeStr, newSize);
}
fclose(mf);
if (count == 0) {
printf("No resources in manifest.\n");
free(entries);
free(data);
return 0;
}
int result = writeResources(dxePath, dxeSize, entries, count, data);
for (uint32_t i = 0; i < count; i++) {
free(data[i]);
}
free(data);
free(entries);
if (result != 0) {
fprintf(stderr, "Failed to write resources to: %s\n", dxePath);
return 1;
}
printf("Built %u resource(s) into %s\n", count, dxePath);
return 0;
fail:
fclose(mf);
for (uint32_t i = 0; i < count; i++) {
free(data[i]);
}
free(data);
free(entries);
return 1;
}
// ============================================================
// cmdList
// ============================================================
static int cmdList(const char *dxePath) {
FILE *f = fopen(dxePath, "rb");
if (!f) {
fprintf(stderr, "Cannot open: %s\n", dxePath);
return 1;
}
fseek(f, -(long)sizeof(DvxResFooterT), SEEK_END);
DvxResFooterT footer;
if (fread(&footer, sizeof(footer), 1, f) != 1 || footer.magic != DVX_RES_MAGIC) {
fclose(f);
printf("No resources in %s\n", dxePath);
return 0;
}
fseek(f, footer.dirOffset, SEEK_SET);
printf("Resources in %s (%u entries):\n", dxePath, footer.entryCount);
printf(" %-31s %-8s %s\n", "Name", "Type", "Size");
printf(" %-31s %-8s %s\n", "-------------------------------", "--------", "--------");
for (uint32_t i = 0; i < footer.entryCount; i++) {
DvxResDirEntryT entry;
if (fread(&entry, sizeof(entry), 1, f) != 1) {
break;
}
const char *typeStr = "unknown";
if (entry.type == DVX_RES_ICON) {
typeStr = "icon";
} else if (entry.type == DVX_RES_TEXT) {
typeStr = "text";
} else if (entry.type == DVX_RES_BINARY) {
typeStr = "binary";
}
printf(" %-31s %-8s %u\n", entry.name, typeStr, entry.size);
}
fclose(f);
return 0;
}
// ============================================================
// cmdGet
// ============================================================
static int cmdGet(const char *dxePath, const char *name, const char *outPath) {
DvxResHandleT *h = dvxResOpen(dxePath);
if (!h) {
fprintf(stderr, "No resources in %s\n", dxePath);
return 1;
}
uint32_t size = 0;
void *buf = dvxResRead(h, name, &size);
dvxResClose(h);
if (!buf) {
fprintf(stderr, "Resource not found: %s\n", name);
return 1;
}
if (outPath) {
FILE *f = fopen(outPath, "wb");
if (!f) {
free(buf);
fprintf(stderr, "Cannot write: %s\n", outPath);
return 1;
}
fwrite(buf, 1, size, f);
fclose(f);
printf("Extracted '%s' (%u bytes) to %s\n", name, size, outPath);
} else {
// Write to stdout
fwrite(buf, 1, size, stdout);
}
free(buf);
return 0;
}
// ============================================================
// cmdStrip
// ============================================================
static int cmdStrip(const char *dxePath) {
long dxeSize = dxeContentSize(dxePath);
if (dxeSize < 0) {
fprintf(stderr, "Cannot open: %s\n", dxePath);
return 1;
}
// Read file size
FILE *f = fopen(dxePath, "rb");
if (!f) {
return 1;
}
fseek(f, 0, SEEK_END);
long fileSize = ftell(f);
fclose(f);
if (fileSize == dxeSize) {
printf("No resources to strip.\n");
return 0;
}
// Read DXE content
f = fopen(dxePath, "rb");
uint8_t *buf = (uint8_t *)malloc(dxeSize);
if (!buf) {
fclose(f);
fprintf(stderr, "Out of memory\n");
return 1;
}
fread(buf, 1, dxeSize, f);
fclose(f);
// Rewrite truncated
f = fopen(dxePath, "wb");
if (!f) {
free(buf);
return 1;
}
fwrite(buf, 1, dxeSize, f);
fclose(f);
free(buf);
printf("Stripped %ld bytes of resources from %s\n", fileSize - dxeSize, dxePath);
return 0;
}
// ============================================================
// usage
// ============================================================
static void usage(void) {
fprintf(stderr, "DVX Resource Tool\n\n");
fprintf(stderr, "Usage:\n");
fprintf(stderr, " dvxres add <file> <name> <type> <data|@file>\n");
fprintf(stderr, " dvxres build <file> <manifest.res>\n");
fprintf(stderr, " dvxres list <file>\n");
fprintf(stderr, " dvxres get <file> <name> [outfile]\n");
fprintf(stderr, " dvxres strip <file>\n\n");
fprintf(stderr, "Types: icon (or image), text, binary\n\n");
fprintf(stderr, "For 'add' with type 'text', <data> is the string value.\n");
fprintf(stderr, "For 'add' with type 'icon' or 'binary', <data> is a file path.\n\n");
fprintf(stderr, "Manifest format (one per line):\n");
fprintf(stderr, " name type data\n");
fprintf(stderr, " # lines starting with # are comments\n");
}
// ============================================================
// main
// ============================================================
int main(int argc, char **argv) {
if (argc < 3) {
usage();
return 1;
}
const char *cmd = argv[1];
const char *file = argv[2];
if (strcmp(cmd, "add") == 0) {
if (argc < 6) {
fprintf(stderr, "Usage: dvxres add <file> <name> <type> <data>\n");
return 1;
}
return cmdAdd(file, argv[3], argv[4], argv[5]);
}
if (strcmp(cmd, "build") == 0) {
if (argc < 4) {
fprintf(stderr, "Usage: dvxres build <file> <manifest.res>\n");
return 1;
}
return cmdBuild(file, argv[3]);
}
if (strcmp(cmd, "list") == 0) {
return cmdList(file);
}
if (strcmp(cmd, "get") == 0) {
if (argc < 4) {
fprintf(stderr, "Usage: dvxres get <file> <name> [outfile]\n");
return 1;
}
return cmdGet(file, argv[3], argc >= 5 ? argv[4] : NULL);
}
if (strcmp(cmd, "strip") == 0) {
return cmdStrip(file);
}
fprintf(stderr, "Unknown command: %s\n", cmd);
usage();
return 1;
}

271
tools/mkicon.c Normal file
View file

@ -0,0 +1,271 @@
// mkicon.c -- Generate simple 32x32 BMP icons for DVX apps
//
// Usage: mkicon <output.bmp> <type>
// Types: clock, notepad, cpanel, dvxdemo, imgview
//
// Generates recognizable pixel-art icons at 32x32 24-bit BMP.
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define W 32
#define H 32
static uint8_t pixels[H][W][3]; // BGR
static void clear(uint8_t r, uint8_t g, uint8_t b) {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
pixels[y][x][0] = b;
pixels[y][x][1] = g;
pixels[y][x][2] = r;
}
}
}
static void pixel(int x, int y, uint8_t r, uint8_t g, uint8_t b) {
if (x >= 0 && x < W && y >= 0 && y < H) {
pixels[y][x][0] = b;
pixels[y][x][1] = g;
pixels[y][x][2] = r;
}
}
static void rect(int x0, int y0, int w, int h, uint8_t r, uint8_t g, uint8_t b) {
for (int y = y0; y < y0 + h && y < H; y++) {
for (int x = x0; x < x0 + w && x < W; x++) {
pixel(x, y, r, g, b);
}
}
}
static void circle(int cx, int cy, int radius, uint8_t r, uint8_t g, uint8_t b) {
for (int y = -radius; y <= radius; y++) {
for (int x = -radius; x <= radius; x++) {
if (x * x + y * y <= radius * radius) {
pixel(cx + x, cy + y, r, g, b);
}
}
}
}
static void hline(int x0, int y, int w, uint8_t r, uint8_t g, uint8_t b) {
for (int x = x0; x < x0 + w; x++) {
pixel(x, y, r, g, b);
}
}
static void vline(int x, int y0, int h, uint8_t r, uint8_t g, uint8_t b) {
for (int y = y0; y < y0 + h; y++) {
pixel(x, y, r, g, b);
}
}
// Clock icon: circle with hands
static void iconClock(void) {
clear(192, 192, 192);
circle(15, 15, 13, 255, 255, 255);
circle(15, 15, 12, 240, 240, 255);
// Rim
for (int a = 0; a < 360; a++) {
double rad = a * 3.14159265 / 180.0;
int x = 15 + (int)(13.0 * __builtin_cos(rad));
int y = 15 + (int)(13.0 * __builtin_sin(rad));
pixel(x, y, 0, 0, 128);
}
// Hour marks
for (int i = 0; i < 12; i++) {
double rad = i * 30.0 * 3.14159265 / 180.0;
int x = 15 + (int)(11.0 * __builtin_cos(rad));
int y = 15 + (int)(11.0 * __builtin_sin(rad));
pixel(x, y, 0, 0, 128);
}
// Hour hand (pointing ~10 o'clock)
vline(15, 7, 8, 0, 0, 128);
// Minute hand (pointing ~2 o'clock)
for (int i = 0; i < 10; i++) {
pixel(15 + i * 2 / 3, 15 - i * 2 / 3, 0, 0, 200);
}
// Center dot
pixel(15, 15, 200, 0, 0);
}
// Notepad icon: page with lines
static void iconNotepad(void) {
clear(192, 192, 192);
rect(6, 3, 20, 26, 255, 255, 240);
// Border
hline(6, 3, 20, 0, 0, 0);
hline(6, 28, 20, 0, 0, 0);
vline(6, 3, 26, 0, 0, 0);
vline(25, 3, 26, 0, 0, 0);
// Folded corner
for (int i = 0; i < 5; i++) {
hline(21 + i, 3 + i, 5 - i, 200, 200, 180);
}
// Text lines
for (int i = 0; i < 7; i++) {
int lineW = (i == 3) ? 12 : (i == 6) ? 8 : 16;
hline(9, 8 + i * 3, lineW, 0, 0, 128);
}
}
// Control Panel icon: gear/wrench
static void iconCpanel(void) {
clear(192, 192, 192);
// Gear body
circle(15, 15, 8, 128, 128, 128);
circle(15, 15, 5, 192, 192, 192);
// Gear teeth (N/S/E/W and diagonals)
rect(13, 2, 5, 4, 128, 128, 128);
rect(13, 26, 5, 4, 128, 128, 128);
rect(2, 13, 4, 5, 128, 128, 128);
rect(26, 13, 4, 5, 128, 128, 128);
rect(4, 4, 4, 4, 128, 128, 128);
rect(24, 4, 4, 4, 128, 128, 128);
rect(4, 24, 4, 4, 128, 128, 128);
rect(24, 24, 4, 4, 128, 128, 128);
// Inner circle (hole)
circle(15, 15, 3, 64, 64, 64);
}
// DVX Demo icon: colorful diamond
static void iconDvxdemo(void) {
clear(192, 192, 192);
// Diamond shape with color gradient
for (int y = 0; y < 16; y++) {
int hw = y + 1;
for (int x = -hw; x <= hw; x++) {
int px = 15 + x;
int py = 2 + y;
uint8_t r = (uint8_t)(128 + y * 8);
uint8_t g = (uint8_t)(64 + (hw - (x < 0 ? -x : x)) * 8);
uint8_t b = (uint8_t)(200 - y * 4);
pixel(px, py, r, g, b);
}
}
for (int y = 0; y < 14; y++) {
int hw = 14 - y;
for (int x = -hw; x <= hw; x++) {
int px = 15 + x;
int py = 18 + y;
uint8_t r = (uint8_t)(255 - y * 8);
uint8_t g = (uint8_t)(128 - y * 4);
uint8_t b = (uint8_t)(100 + y * 8);
pixel(px, py, r, g, b);
}
}
}
// Image Viewer icon: landscape in a frame
static void iconImgview(void) {
clear(192, 192, 192);
// Frame
rect(3, 5, 26, 22, 100, 80, 60);
rect(5, 7, 22, 18, 135, 200, 235);
// Sun
circle(21, 11, 3, 255, 220, 50);
// Mountain
for (int y = 0; y < 10; y++) {
int w = y * 2 + 1;
hline(15 - y, 15 + y, w, 80, 140, 80);
}
// Ground
rect(5, 22, 22, 3, 60, 120, 60);
}
static void writeBmp(const char *path) {
int32_t rowPad = (4 - (W * 3) % 4) % 4;
int32_t rowSize = W * 3 + rowPad;
int32_t dataSize = rowSize * H;
int32_t fileSize = 54 + dataSize;
uint8_t header[54];
memset(header, 0, sizeof(header));
// BMP header
header[0] = 'B';
header[1] = 'M';
*(int32_t *)&header[2] = fileSize;
*(int32_t *)&header[10] = 54;
// DIB header
*(int32_t *)&header[14] = 40;
*(int32_t *)&header[18] = W;
*(int32_t *)&header[22] = H;
*(int16_t *)&header[26] = 1;
*(int16_t *)&header[28] = 24;
*(int32_t *)&header[34] = dataSize;
FILE *f = fopen(path, "wb");
if (!f) {
fprintf(stderr, "Cannot write: %s\n", path);
return;
}
fwrite(header, 1, 54, f);
// BMP rows are bottom-to-top
uint8_t pad[3] = {0};
for (int y = H - 1; y >= 0; y--) {
fwrite(pixels[y], 1, W * 3, f);
if (rowPad > 0) {
fwrite(pad, 1, rowPad, f);
}
}
fclose(f);
}
int main(int argc, char **argv) {
if (argc < 3) {
fprintf(stderr, "Usage: mkicon <output.bmp> <type>\n");
fprintf(stderr, "Types: clock, notepad, cpanel, dvxdemo, imgview\n");
return 1;
}
const char *path = argv[1];
const char *type = argv[2];
if (strcmp(type, "clock") == 0) {
iconClock();
} else if (strcmp(type, "notepad") == 0) {
iconNotepad();
} else if (strcmp(type, "cpanel") == 0) {
iconCpanel();
} else if (strcmp(type, "dvxdemo") == 0) {
iconDvxdemo();
} else if (strcmp(type, "imgview") == 0) {
iconImgview();
} else {
fprintf(stderr, "Unknown icon type: %s\n", type);
return 1;
}
writeBmp(path);
printf("Generated %s (%s)\n", path, type);
return 0;
}