feature: Add ELF metadata support

This commit is contained in:
boreddevnl 2026-04-21 00:29:39 +02:00
parent 2498045362
commit 9c600caf45
30 changed files with 714 additions and 16 deletions

View file

@ -22,7 +22,10 @@ USERLAND_COLLOID_ICONS = $(shell { \
find $(SRC_DIR)/userland -type f -name '*.c' ! -path '*/third_party/*' -exec grep -hoE '"[^"]+\.png"' {} + 2>/dev/null; \
find $(SRC_DIR)/userland -type f -name '*.h' ! -path '*/third_party/*' ! -name 'stb_image.h' -exec grep -hoE '"[^"]+\.png"' {} + 2>/dev/null; \
} | sed 's/"//g' | sed 's@.*/@@' | sort -u)
COLLOID_ICONS = $(sort $(DOCK_COLLOID_ICONS) $(USERLAND_COLLOID_ICONS))
USERLAND_METADATA_ICONS = $(shell { \
find $(SRC_DIR)/userland -type f -name '*.c' -exec sed -n 's@^[[:space:]]*//[[:space:]]*BOREDOS_APP_ICONS:[[:space:]]*@@p' {} + 2>/dev/null; \
} | tr ';' '\n' | sed 's@.*/@@' | sed '/^[[:space:]]*$$/d' | sort -u)
COLLOID_ICONS = $(sort $(DOCK_COLLOID_ICONS) $(USERLAND_COLLOID_ICONS) $(USERLAND_METADATA_ICONS) xterm.png)
C_SOURCES = $(wildcard $(SRC_DIR)/core/*.c) \
$(wildcard $(SRC_DIR)/sys/*.c) \

361
src/sys/app_metadata.c Normal file
View file

@ -0,0 +1,361 @@
// 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 "app_metadata.h"
#include "memory_manager.h"
#include "vfs.h"
#define APP_METADATA_CACHE_SIZE 64
typedef struct {
bool valid;
bool has_metadata;
char path[VFS_MAX_PATH];
boredos_app_metadata_t metadata;
} app_metadata_cache_entry_t;
static app_metadata_cache_entry_t g_app_metadata_cache[APP_METADATA_CACHE_SIZE];
static int g_app_metadata_cache_next = 0;
static size_t am_strlen(const char *str) {
size_t len = 0;
if (!str) return 0;
while (str[len]) len++;
return len;
}
static bool am_str_eq(const char *a, const char *b) {
if (!a || !b) return false;
while (*a && *b) {
if (*a != *b) return false;
a++;
b++;
}
return (*a == '\0' && *b == '\0');
}
static bool am_mem_eq(const uint8_t *a, const uint8_t *b, size_t len) {
if (!a || !b) return false;
for (size_t i = 0; i < len; i++) {
if (a[i] != b[i]) return false;
}
return true;
}
static void am_mem_copy(uint8_t *dest, const uint8_t *src, size_t len) {
if (!dest || !src) return;
for (size_t i = 0; i < len; i++) {
dest[i] = src[i];
}
}
static void am_str_copy(char *dest, const char *src, size_t dest_size) {
size_t i = 0;
if (!dest || dest_size == 0) return;
if (!src) {
dest[0] = '\0';
return;
}
while (src[i] && i + 1 < dest_size) {
dest[i] = src[i];
i++;
}
dest[i] = '\0';
}
static bool am_seek(vfs_file_t *file, uint64_t offset) {
if (!file) return false;
if (offset > 0x7FFFFFFFUL) return false;
return vfs_seek(file, (int)offset, 0) == 0;
}
static bool am_read_exact(vfs_file_t *file, void *buf, uint32_t size) {
uint8_t *dst = (uint8_t *)buf;
uint32_t total = 0;
while (total < size) {
int rc = vfs_read(file, dst + total, (int)(size - total));
if (rc <= 0) return false;
total += (uint32_t)rc;
}
return true;
}
static uint32_t am_align4(uint32_t value) {
return (value + 3U) & ~3U;
}
static bool am_validate_metadata(const boredos_app_metadata_t *metadata) {
if (!metadata) return false;
if (metadata->magic != BOREDOS_APP_METADATA_MAGIC) return false;
if (metadata->version != BOREDOS_APP_METADATA_VERSION) return false;
if (metadata->image_count > BOREDOS_APP_METADATA_MAX_IMAGES) return false;
return true;
}
static bool am_note_name_matches(const char *name, uint32_t name_size) {
size_t expected_len = am_strlen(BOREDOS_APP_NOTE_OWNER);
if (!name || name_size == 0) return false;
if ((size_t)name_size < expected_len) return false;
for (size_t i = 0; i < expected_len; i++) {
if (name[i] != BOREDOS_APP_NOTE_OWNER[i]) return false;
}
return true;
}
static void am_sanitize_metadata(boredos_app_metadata_t *metadata) {
if (!metadata) return;
metadata->app_name[BOREDOS_APP_METADATA_MAX_APP_NAME - 1] = '\0';
metadata->description[BOREDOS_APP_METADATA_MAX_DESCRIPTION - 1] = '\0';
for (uint32_t i = 0; i < BOREDOS_APP_METADATA_MAX_IMAGES; i++) {
metadata->images[i][BOREDOS_APP_METADATA_MAX_IMAGE_PATH - 1] = '\0';
}
if (metadata->image_count > BOREDOS_APP_METADATA_MAX_IMAGES) {
metadata->image_count = BOREDOS_APP_METADATA_MAX_IMAGES;
}
}
static bool am_parse_note_section(vfs_file_t *file,
const Elf64_Shdr *section,
boredos_app_metadata_t *out_metadata) {
uint32_t offset = 0;
if (!file || !section || !out_metadata) return false;
while ((uint64_t)offset + sizeof(Elf64_Nhdr) <= section->sh_size) {
Elf64_Nhdr nhdr;
if (!am_seek(file, section->sh_offset + offset)) return false;
if (!am_read_exact(file, &nhdr, sizeof(Elf64_Nhdr))) return false;
offset += (uint32_t)sizeof(Elf64_Nhdr);
if ((uint64_t)offset + nhdr.n_namesz > section->sh_size) return false;
if (nhdr.n_namesz > 256U) return false;
char *name_buf = (char *)kmalloc((size_t)nhdr.n_namesz + 1U);
if (!name_buf) return false;
if (!am_seek(file, section->sh_offset + offset) || !am_read_exact(file, name_buf, nhdr.n_namesz)) {
kfree(name_buf);
return false;
}
name_buf[nhdr.n_namesz] = '\0';
offset += nhdr.n_namesz;
offset = am_align4(offset);
if ((uint64_t)offset + nhdr.n_descsz > section->sh_size) {
kfree(name_buf);
return false;
}
bool is_target_note = (nhdr.n_type == BOREDOS_APP_NOTE_TYPE) && am_note_name_matches(name_buf, nhdr.n_namesz);
kfree(name_buf);
if (is_target_note) {
if (nhdr.n_descsz < sizeof(boredos_app_metadata_t)) return false;
boredos_app_metadata_t metadata;
if (!am_seek(file, section->sh_offset + offset) ||
!am_read_exact(file, &metadata, (uint32_t)sizeof(boredos_app_metadata_t))) {
return false;
}
if (!am_validate_metadata(&metadata)) return false;
am_sanitize_metadata(&metadata);
*out_metadata = metadata;
return true;
}
offset += nhdr.n_descsz;
offset = am_align4(offset);
}
return false;
}
static bool am_scan_raw_notes(vfs_file_t *file, uint32_t file_size, boredos_app_metadata_t *out_metadata) {
uint8_t *buf = NULL;
size_t owner_len = am_strlen(BOREDOS_APP_NOTE_OWNER);
if (!file || !out_metadata || file_size < sizeof(Elf64_Nhdr) + owner_len + sizeof(boredos_app_metadata_t)) {
return false;
}
if (file_size > 16U * 1024U * 1024U) {
return false;
}
buf = (uint8_t *)kmalloc(file_size);
if (!buf) return false;
if (!am_seek(file, 0) || !am_read_exact(file, buf, file_size)) {
kfree(buf);
return false;
}
for (uint32_t off = 0; off + sizeof(Elf64_Nhdr) <= file_size; off++) {
Elf64_Nhdr nhdr;
uint32_t name_off;
uint32_t desc_off;
boredos_app_metadata_t metadata;
am_mem_copy((uint8_t *)&nhdr, buf + off, sizeof(Elf64_Nhdr));
if (nhdr.n_type != BOREDOS_APP_NOTE_TYPE) continue;
if (nhdr.n_namesz < owner_len) continue;
if (nhdr.n_descsz < sizeof(boredos_app_metadata_t)) continue;
name_off = off + (uint32_t)sizeof(Elf64_Nhdr);
if ((uint64_t)name_off + nhdr.n_namesz > file_size) continue;
if (!am_mem_eq(buf + name_off, (const uint8_t *)BOREDOS_APP_NOTE_OWNER, owner_len)) continue;
desc_off = name_off + am_align4(nhdr.n_namesz);
if ((uint64_t)desc_off + sizeof(boredos_app_metadata_t) > file_size) continue;
am_mem_copy((uint8_t *)&metadata, buf + desc_off, sizeof(boredos_app_metadata_t));
if (!am_validate_metadata(&metadata)) continue;
am_sanitize_metadata(&metadata);
*out_metadata = metadata;
kfree(buf);
return true;
}
kfree(buf);
return false;
}
static bool app_metadata_read_uncached(const char *path, boredos_app_metadata_t *out_metadata) {
bool found = false;
vfs_file_t *file = NULL;
char *shstrtab = NULL;
uint32_t file_size = 0;
if (!path || !out_metadata) return false;
file = vfs_open(path, "r");
if (!file || !file->valid) goto cleanup;
file_size = vfs_file_size(file);
if (file_size > 0) {
found = am_scan_raw_notes(file, file_size, out_metadata);
if (found) goto cleanup;
}
Elf64_Ehdr ehdr;
if (!am_read_exact(file, &ehdr, sizeof(Elf64_Ehdr))) goto cleanup;
if (ehdr.e_ident[0] != ELFMAG0 || ehdr.e_ident[1] != ELFMAG1 ||
ehdr.e_ident[2] != ELFMAG2 || ehdr.e_ident[3] != ELFMAG3) {
goto cleanup;
}
if (ehdr.e_ident[4] != ELFCLASS64 || ehdr.e_ident[5] != ELFDATA2LSB) goto cleanup;
if (ehdr.e_shoff == 0 || ehdr.e_shnum == 0 || ehdr.e_shentsize < sizeof(Elf64_Shdr)) goto cleanup;
if (ehdr.e_shstrndx == 0 || ehdr.e_shstrndx >= ehdr.e_shnum) goto cleanup;
Elf64_Shdr shstr_hdr;
uint64_t shstr_off = ehdr.e_shoff + ((uint64_t)ehdr.e_shstrndx * ehdr.e_shentsize);
if (!am_seek(file, shstr_off) || !am_read_exact(file, &shstr_hdr, sizeof(Elf64_Shdr))) goto cleanup;
if (shstr_hdr.sh_size == 0 || shstr_hdr.sh_size > 65536U) goto cleanup;
shstrtab = (char *)kmalloc((size_t)shstr_hdr.sh_size + 1U);
if (!shstrtab) goto cleanup;
if (!am_seek(file, shstr_hdr.sh_offset) || !am_read_exact(file, shstrtab, (uint32_t)shstr_hdr.sh_size)) goto cleanup;
shstrtab[shstr_hdr.sh_size] = '\0';
for (uint16_t i = 0; i < ehdr.e_shnum; i++) {
Elf64_Shdr shdr;
uint64_t shdr_off = ehdr.e_shoff + ((uint64_t)i * ehdr.e_shentsize);
if (!am_seek(file, shdr_off) || !am_read_exact(file, &shdr, sizeof(Elf64_Shdr))) goto cleanup;
if (shdr.sh_type != SHT_NOTE) continue;
if (shdr.sh_name >= shstr_hdr.sh_size) continue;
const char *section_name = shstrtab + shdr.sh_name;
if (!am_str_eq(section_name, BOREDOS_APP_NOTE_SECTION)) continue;
if (am_parse_note_section(file, &shdr, out_metadata)) {
found = true;
break;
}
}
if (!found && file_size > 0) {
found = am_scan_raw_notes(file, file_size, out_metadata);
}
cleanup:
if (shstrtab) kfree(shstrtab);
if (file) vfs_close(file);
return found;
}
static bool app_metadata_cache_lookup(const char *path, boredos_app_metadata_t *out_metadata, bool *out_found) {
if (!path || !out_found) return false;
for (int i = 0; i < APP_METADATA_CACHE_SIZE; i++) {
app_metadata_cache_entry_t *entry = &g_app_metadata_cache[i];
if (!entry->valid) continue;
if (!am_str_eq(entry->path, path)) continue;
*out_found = entry->has_metadata;
if (entry->has_metadata && out_metadata) {
*out_metadata = entry->metadata;
}
return true;
}
return false;
}
static void app_metadata_cache_store(const char *path, const boredos_app_metadata_t *metadata, bool has_metadata) {
app_metadata_cache_entry_t *entry;
if (!path) return;
entry = &g_app_metadata_cache[g_app_metadata_cache_next];
g_app_metadata_cache_next = (g_app_metadata_cache_next + 1) % APP_METADATA_CACHE_SIZE;
entry->valid = true;
entry->has_metadata = has_metadata;
am_str_copy(entry->path, path, sizeof(entry->path));
if (has_metadata && metadata) {
entry->metadata = *metadata;
}
}
bool app_metadata_read(const char *path, boredos_app_metadata_t *out_metadata) {
bool found = false;
if (!path || !out_metadata) return false;
if (app_metadata_cache_lookup(path, out_metadata, &found)) {
return found;
}
found = app_metadata_read_uncached(path, out_metadata);
app_metadata_cache_store(path, out_metadata, found);
return found;
}
bool app_metadata_get_primary_image(const char *path, char *out_path, size_t out_path_size) {
boredos_app_metadata_t metadata;
if (!path || !out_path || out_path_size == 0) return false;
out_path[0] = '\0';
if (!app_metadata_read(path, &metadata)) return false;
if (metadata.image_count == 0) return false;
if (metadata.images[0][0] == '\0') return false;
am_str_copy(out_path, metadata.images[0], out_path_size);
return out_path[0] != '\0';
}

14
src/sys/app_metadata.h Normal file
View file

@ -0,0 +1,14 @@
// 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.
#ifndef APP_METADATA_H
#define APP_METADATA_H
#include <stdbool.h>
#include <stddef.h>
#include "elf.h"
bool app_metadata_read(const char *path, boredos_app_metadata_t *out_metadata);
bool app_metadata_get_primary_image(const char *path, char *out_path, size_t out_path_size);
#endif

View file

@ -44,6 +44,25 @@ typedef struct {
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;
typedef struct {
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
typedef struct {
Elf64_Word n_namesz; /* Name size in bytes */
Elf64_Word n_descsz; /* Descriptor size in bytes */
Elf64_Word n_type; /* Note type */
} Elf64_Nhdr;
/* e_ident constants */
#define ELFMAG0 0x7f
#define ELFMAG1 'E'
@ -63,11 +82,37 @@ typedef struct {
/* p_type constants */
#define PT_LOAD 1
/* sh_type constants */
#define SHT_NOTE 7
/* p_flags constants */
#define PF_X 1
#define PF_W 2
#define PF_R 4
/* BoredOS app metadata note constants */
#define BOREDOS_APP_NOTE_OWNER "BOREDOS"
#define BOREDOS_APP_NOTE_NAME BOREDOS_APP_NOTE_OWNER
#define BOREDOS_APP_NOTE_SECTION ".note.boredos.app"
#define BOREDOS_APP_NOTE_TYPE 0x41505031U
#define BOREDOS_APP_METADATA_MAGIC 0x414d4431U
#define BOREDOS_APP_METADATA_VERSION 1U
#define BOREDOS_APP_METADATA_MAX_APP_NAME 64
#define BOREDOS_APP_METADATA_MAX_DESCRIPTION 192
#define BOREDOS_APP_METADATA_MAX_IMAGES 4
#define BOREDOS_APP_METADATA_MAX_IMAGE_PATH 160
typedef struct __attribute__((packed)) {
uint32_t magic;
uint16_t version;
uint16_t image_count;
uint16_t reserved;
char app_name[BOREDOS_APP_METADATA_MAX_APP_NAME];
char description[BOREDOS_APP_METADATA_MAX_DESCRIPTION];
char images[BOREDOS_APP_METADATA_MAX_IMAGES][BOREDOS_APP_METADATA_MAX_IMAGE_PATH];
} boredos_app_metadata_t;
#include <stdbool.h>
#include <stddef.h>

View file

@ -5,10 +5,19 @@ CC = x86_64-elf-gcc
AS = nasm
LD = x86_64-elf-ld
CFLAGS = -Wall -Wextra -std=gnu11 -ffreestanding -O2 -fno-stack-protector -fno-stack-check -fno-lto -fno-pie -m64 -march=x86-64 -mno-red-zone -I. -Ilibc
CFLAGS = -Wall -Wextra -std=gnu11 -ffreestanding -O2 -fno-stack-protector -fno-stack-check -fno-lto -fno-pie -m64 -march=x86-64 -mno-red-zone -I. -Ilibc -I../sys
LDFLAGS = -m elf_x86_64 -nostdlib -static -no-pie -Ttext=0x40000000 --no-dynamic-linker -z text -z max-page-size=0x1000 -e _start
BIN_DIR = bin
APP_METADATA_TOOL = ../../tools/gen_userland_note.sh
APP_ICON_SOURCE_DIR = ../images/icons/colloid
APP_METADATA_SOURCE_DOOM = games/doom/doomgeneric_boredos.c
APP_METADATA_SOURCE_LUA = cli/third_party/lua/boredos_onelua.c
APP_SOURCE_DIRS = . cli gui sys games net cli/third_party
define app_source_for
$(if $(filter doom,$1),$(APP_METADATA_SOURCE_DOOM),$(if $(filter lua,$1),$(APP_METADATA_SOURCE_LUA),$(firstword $(foreach d,$(APP_SOURCE_DIRS),$(wildcard $(d)/$1.c)))))
endef
LIBC_SOURCES = $(wildcard libc/*.c)
LIBC_OBJS = $(patsubst libc/%.c, $(BIN_DIR)/%.o, $(LIBC_SOURCES)) $(BIN_DIR)/crt0.o $(BIN_DIR)/libwidget.o
@ -19,6 +28,9 @@ vpath %.c cli gui sys games libc net cli/third_party
APP_SOURCES_FULL = $(wildcard cli/*.c gui/*.c sys/*.c games/*.c *.c net/*.c cli/third_party/*.c)
APP_SOURCES = $(filter-out stb_image.c, $(APP_SOURCES_FULL))
APP_ELFS = $(patsubst %.c, $(BIN_DIR)/%.elf, $(notdir $(APP_SOURCES)))
APP_NAMES = $(sort $(basename $(notdir $(APP_SOURCES))) doom lua)
APP_NOTE_CSOURCES = $(patsubst %, $(BIN_DIR)/%.note.c, $(APP_NAMES))
APP_NOTE_OBJS = $(patsubst %, $(BIN_DIR)/%.note.o, $(APP_NAMES))
DOOM_SOURCES = $(wildcard games/doom/*.c)
DOOM_OBJS = $(patsubst games/doom/%.c, $(BIN_DIR)/%.o, $(DOOM_SOURCES))
@ -43,35 +55,46 @@ $(BIN_DIR)/libwidget.o: ../wm/libwidget.c | $(BIN_DIR)
$(BIN_DIR)/stb_image.o: stb_image.c | $(BIN_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/%.note.c: $(APP_METADATA_TOOL) | $(BIN_DIR)
src="$(call app_source_for,$*)"; \
if [ -z "$$src" ] || [ ! -f "$$src" ]; then \
echo "error: metadata source not found for app '$*'" >&2; \
exit 1; \
fi; \
sh $(APP_METADATA_TOOL) "$*" "$$src" "$(APP_ICON_SOURCE_DIR)" "$@"
$(BIN_DIR)/%.note.o: $(BIN_DIR)/%.note.c
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/%.o: %.c | $(BIN_DIR)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_DIR)/viewer.elf: $(LIBC_OBJS) $(BIN_DIR)/viewer.o $(BIN_DIR)/stb_image.o
$(BIN_DIR)/viewer.elf: $(LIBC_OBJS) $(BIN_DIR)/viewer.o $(BIN_DIR)/stb_image.o $(BIN_DIR)/viewer.note.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/settings.elf: $(LIBC_OBJS) $(BIN_DIR)/settings.o $(BIN_DIR)/stb_image.o
$(BIN_DIR)/settings.elf: $(LIBC_OBJS) $(BIN_DIR)/settings.o $(BIN_DIR)/stb_image.o $(BIN_DIR)/settings.note.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/browser.elf: $(LIBC_OBJS) $(BIN_DIR)/browser.o $(BIN_DIR)/stb_image.o
$(BIN_DIR)/browser.elf: $(LIBC_OBJS) $(BIN_DIR)/browser.o $(BIN_DIR)/stb_image.o $(BIN_DIR)/browser.note.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/screenshot.elf: $(LIBC_OBJS) $(BIN_DIR)/screenshot.o $(BIN_DIR)/stb_image.o
$(BIN_DIR)/screenshot.elf: $(LIBC_OBJS) $(BIN_DIR)/screenshot.o $(BIN_DIR)/stb_image.o $(BIN_DIR)/screenshot.note.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/%.o: games/doom/%.c | $(BIN_DIR)
$(CC) $(CFLAGS) -Wno-error -DBOREDOS -Igames/doom -c $< -o $@
$(BIN_DIR)/doom.elf: $(LIBC_OBJS) $(DOOM_OBJS) $(BIN_DIR)/stb_image.o
$(BIN_DIR)/doom.elf: $(LIBC_OBJS) $(DOOM_OBJS) $(BIN_DIR)/stb_image.o $(BIN_DIR)/doom.note.o
$(LD) $(LDFLAGS) $^ -o $@
$(BIN_DIR)/%.elf: $(LIBC_OBJS) $(BIN_DIR)/%.o
$(BIN_DIR)/%.elf: $(LIBC_OBJS) $(BIN_DIR)/%.o $(BIN_DIR)/%.note.o
$(LD) $(LDFLAGS) $^ -o $@
# Lua 5.5.0 - compiled as a single translation unit
$(BIN_DIR)/lua_onelua.o: $(LUA_DIR)/boredos_onelua.c | $(BIN_DIR)
$(CC) $(LUA_CFLAGS) -c $< -o $@
$(BIN_DIR)/lua.elf: $(LIBC_OBJS) $(BIN_DIR)/lua_onelua.o
$(BIN_DIR)/lua.elf: $(LIBC_OBJS) $(BIN_DIR)/lua_onelua.o $(BIN_DIR)/lua.note.o
$(LD) $(LDFLAGS) $^ -o $@
clean:

View file

@ -1,6 +1,7 @@
// 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.
// BOREDOS_APP_DESC: Show command and system help.
#include <stdlib.h>
#include <syscall.h>

View file

@ -1,6 +1,7 @@
// 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.
// BOREDOS_APP_DESC: Manual pages CLI utility
#include <stdlib.h>
#include <syscall.h>

View file

@ -3,6 +3,7 @@
** Based on the official onelua.c, adapted for BoredOS freestanding environment.
*/
// BOREDOS_APP_DESC: Lua REPL and scripting runtime.
#include <stdint.h>
#include <stddef.h>
#include <stdarg.h>

View file

@ -1,3 +1,5 @@
// BOREDOS_APP_DESC: 2048 number puzzle game.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/applications-games.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,3 +1,5 @@
// BOREDOS_APP_DESC: DOOM game runtime.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/doom-2016.png;/Library/images/icons/colloid/applications-games.png
#include "doomgeneric.h"
#include "doomkeys.h"
#include <libui.h>

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Minesweeper puzzle game.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/gnome-mines.png;/Library/images/icons/colloid/applications-games.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include <stdbool.h>

View file

@ -1,3 +1,5 @@
// BOREDOS_APP_DESC: Classic snake arcade game.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/cartridges.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Shows BoredOS information.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/indicator-cpufreq.png
#include "syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Document and PDF viewer/editor.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/libreoffice-writer.png;/Library/images/icons/colloid/text-editor.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,5 +1,7 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// BOREDOS_APP_DESC: Web browser for internet pages.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/web-browser.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "stb_image.h"
@ -2134,4 +2136,4 @@ int main(int argc, char **argv) {
}
}
return 0;
}
}

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Graphical calculator utility.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/calc.png
#include "syscall.h"
#include "libui.h"
#include "../../wm/libwidget.h"

View file

@ -2,6 +2,8 @@
// 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.
// BOREDOS_APP_DESC: 3/2D Graphing and plotting utility.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/se.sjoerd.Graphs.png;/Library/images/icons/colloid/app-icon-preview.png
#include "syscall.h"
#include "libui.h"
#include "../../wm/libwidget.h"
@ -1765,4 +1767,4 @@ int main(void) {
free(graph_fb);
sys_exit(0);
return 0;
}
}

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Markdown document viewer.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/text-editor.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Jotting down notes and thoughts.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/text-editor.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Simple drawing and paint app.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/gnome-paint.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"
@ -247,4 +249,4 @@ int main(int argc, char **argv) {
}
}
return 0;
}
}

View file

@ -1,3 +1,5 @@
// BOREDOS_APP_DESC: Screen capture utility.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/accessories-screenshot.png
#include <stdint.h>
#include <stdbool.h>
#include "stdlib.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: System configuration and preferences.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/preferences-system.png;/Library/images/icons/colloid/preferences-system-services.png
#include "libc/syscall.h"
#include "libc/libui.h"
#include "libc/stdlib.h"

View file

@ -1,5 +1,7 @@
// Copyright (c) 2023-2026 Chris (boreddevnl)
// This software is released under the GNU General Public License v3.0. See LICENSE file for details.
// BOREDOS_APP_DESC: Task and process manager.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/utilities-system-monitor.png
#include "syscall.h"
#include "libui.h"
#include "stdlib.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: BoredOS Terminal shell.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/xterm.png;/Library/images/icons/colloid/utilities-terminal_su.png
#include <stdlib.h>
#include <syscall.h>
#include "libc/libui.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Image viewer utility.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/preferences-desktop-wallpaper.png;/Library/images/icons/colloid/org.gnome.Loupe.png
#include "stb_image.h"
#include "libc/syscall.h"
#include "libc/libui.h"

View file

@ -1,6 +1,8 @@
// 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.
// BOREDOS_APP_DESC: Clock and time utility.
// BOREDOS_APP_ICONS: /Library/images/icons/colloid/preferences-system-time.png
#include "syscall.h"
#include "libui.h"
#include "stdlib.h"

View file

@ -10,6 +10,7 @@
#include "wm.h"
#include "memory_manager.h"
#include "process.h"
#include "app_metadata.h"
#define EXPLORER_ITEM_HEIGHT 80
#define EXPLORER_ITEM_WIDTH 120
#define EXPLORER_COLS 4
@ -901,8 +902,6 @@ static void explorer_draw_colloid_slot_icon(int x, int y, int slot_index) {
}
static void explorer_draw_file_icon(int x, int y, bool is_dir, const char *filename, const char *current_path) {
(void)current_path;
if (is_dir) {
explorer_draw_colloid_slot_icon(x + 5, y + 5, EXPLORER_DOCK_SLOT_FILES);
} else if (explorer_str_ends_with(filename, ".shortcut")) {
@ -948,7 +947,17 @@ static void explorer_draw_file_icon(int x, int y, bool is_dir, const char *filen
} else if (explorer_str_ends_with(filename, ".pdf")) {
explorer_draw_colloid_slot_icon(x + 5, y + 5, EXPLORER_DOCK_SLOT_WORD);
} else if (explorer_str_ends_with(filename, ".elf")) {
explorer_draw_colloid_slot_icon(x + 5, y + 5, EXPLORER_DOCK_SLOT_TERMINAL);
char app_path[FAT32_MAX_PATH];
char icon_path[BOREDOS_APP_METADATA_MAX_IMAGE_PATH];
explorer_strcpy(app_path, current_path);
if (app_path[explorer_strlen(app_path) - 1] != '/') explorer_strcat(app_path, "/");
explorer_strcat(app_path, filename);
if (!(app_metadata_get_primary_image(app_path, icon_path, sizeof(icon_path)) &&
draw_icon_path(x + 5, y + 5, icon_path))) {
explorer_draw_colloid_slot_icon(x + 5, y + 5, EXPLORER_DOCK_SLOT_TERMINAL);
}
} else {
explorer_draw_colloid_slot_icon(x + 5, y + 5, EXPLORER_DOCK_SLOT_NOTEPAD);
}

View file

@ -18,6 +18,7 @@
#include "userland/stb_image.h"
#include "memory_manager.h"
#include "disk.h"
#include "app_metadata.h"
#include "../sys/work_queue.h"
#include "../sys/smp.h"
#include "../core/kconsole.h"
@ -1103,6 +1104,39 @@ void draw_image_icon(int x, int y, const char *label) {
// Removing the explicit `draw_icon_label` call here to prevent double-text since `wm.c` or Explorer manually draws it as well inside their draw block
}
bool draw_icon_path(int x, int y, const char *path) {
uint32_t *icon = NULL;
if (!path || !path[0]) return false;
icon = thumb_cache_lookup(path);
if (!icon && !thumb_cache_is_failed(path)) {
icon = thumb_cache_decode(path);
}
if (!icon) return false;
int dx = x + 24;
int dy = y + 12;
for (int ty = 0; ty < 32; ty++) {
for (int tx = 0; tx < 32; tx++) {
int src_x = tx * 48 / 32;
int src_y = ty * 48 / 32;
uint32_t src = icon[src_y * 48 + src_x];
uint32_t a = (src >> 24) & 0xFF;
if (a == 0) continue;
if (a == 255) {
put_pixel(dx + tx, dy + ty, 0xFF000000 | (src & 0x00FFFFFF));
} else {
uint32_t dst = graphics_get_pixel(dx + tx, dy + ty);
put_pixel(dx + tx, dy + ty, blend_src_over_dst(dst, src));
}
}
}
return true;
}
void draw_notepad_icon(int x, int y, const char *label) {
draw_scaled_icon(x, y, draw_dock_notepad);
draw_icon_label(x, y, label);
@ -1839,7 +1873,29 @@ static void wm_paint_region(int y_start, int y_end, DirtyRect dirty, int pass) {
else if (str_starts_with(icon->name, "Grapher")) draw_grapher_icon(icon->x, icon->y, label);
else draw_icon(icon->x, icon->y, label);
} else {
if (str_ends_with(icon->name, ".elf")) draw_elf_icon(icon->x, icon->y, icon->name);
if (str_ends_with(icon->name, ".elf")) {
char full_path[128] = "/root/Desktop/";
char icon_path[BOREDOS_APP_METADATA_MAX_IMAGE_PATH];
bool drew_icon = false;
int p = 14;
int n = 0;
while (icon->name[n] && p < 127) full_path[p++] = icon->name[n++];
full_path[p] = 0;
if (app_metadata_get_primary_image(full_path, icon_path, sizeof(icon_path))) {
drew_icon = draw_icon_path(icon->x, icon->y, icon_path);
}
if (!drew_icon) {
drew_icon = draw_icon_path(icon->x, icon->y, "/Library/images/icons/colloid/xterm.png");
}
if (drew_icon) {
draw_icon_label(icon->x, icon->y, icon->name);
} else {
draw_elf_icon(icon->x, icon->y, icon->name);
}
}
else if (str_ends_with(icon->name, ".pnt")) draw_paint_icon(icon->x, icon->y, icon->name);
else if (is_image_file(icon->name)) {
char full_path[128] = "/root/Desktop/"; int p=14; int n=0; while(icon->name[n] && p < 127) full_path[p++] = icon->name[n++]; full_path[p]=0;

View file

@ -127,6 +127,7 @@ void draw_document_icon(int x, int y, const char *label);
void draw_pdf_icon(int x, int y, const char *label);
void draw_elf_icon(int x, int y, const char *label);
void draw_image_icon(int x, int y, const char *label);
bool draw_icon_path(int x, int y, const char *path);
void draw_notepad_icon(int x, int y, const char *label);
void draw_calculator_icon(int x, int y, const char *label);
void draw_terminal_icon(int x, int y, const char *label);

147
tools/gen_userland_note.sh Normal file
View file

@ -0,0 +1,147 @@
#!/usr/bin/env sh
set -eu
if [ "$#" -ne 4 ]; then
echo "usage: gen_userland_note.sh <app-name> <source-file> <icon-source-dir> <output-c>" >&2
exit 1
fi
APP_NAME="$1"
SOURCE_PATH="$2"
ICON_SOURCE_DIR="$3"
OUT_PATH="$4"
MAX_APP_NAME=63
MAX_DESC=191
MAX_IMAGE_PATH=159
MAX_IMAGES=4
DEFAULT_ICON_PATH="/Library/images/icons/colloid/xterm.png"
DEFAULT_DESC="BoredOS userspace application."
escape_c_string() {
printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g'
}
trim_spaces() {
printf '%s' "$1" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}
truncate_bytes() {
value="$1"
max_len="$2"
printf '%s' "$value" | cut -c1-"$max_len"
}
if [ ! -f "$SOURCE_PATH" ]; then
echo "error: source file '$SOURCE_PATH' not found for app '$APP_NAME'" >&2
exit 1
fi
app_desc="$DEFAULT_DESC"
image_spec="$DEFAULT_ICON_PATH"
source_desc=$(sed -n 's@^[[:space:]]*//[[:space:]]*BOREDOS_APP_DESC:[[:space:]]*@@p' "$SOURCE_PATH" | head -n 1)
source_icons=$(sed -n 's@^[[:space:]]*//[[:space:]]*BOREDOS_APP_ICONS:[[:space:]]*@@p' "$SOURCE_PATH" | head -n 1)
if [ -n "$source_desc" ]; then
app_desc="$source_desc"
fi
if [ -n "$source_icons" ]; then
image_spec="$source_icons"
fi
app_desc=$(trim_spaces "$app_desc")
if [ -z "$app_desc" ]; then
app_desc="$DEFAULT_DESC"
fi
image_spec=$(trim_spaces "$image_spec")
if [ -z "$image_spec" ]; then
image_spec="$DEFAULT_ICON_PATH"
fi
app_name_value=$(truncate_bytes "$APP_NAME" "$MAX_APP_NAME")
app_desc=$(truncate_bytes "$app_desc" "$MAX_DESC")
IMAGE_1=""
IMAGE_2=""
IMAGE_3=""
IMAGE_4=""
IMAGE_COUNT=0
saved_ifs="$IFS"
IFS=';'
for raw_image in $image_spec; do
image_path=$(trim_spaces "$raw_image")
if [ -z "$image_path" ]; then
continue
fi
image_path=$(truncate_bytes "$image_path" "$MAX_IMAGE_PATH")
image_file="${image_path##*/}"
if [ ! -f "$ICON_SOURCE_DIR/$image_file" ]; then
echo "error: icon '$image_file' (from '$image_path') not found in $ICON_SOURCE_DIR for app '$APP_NAME'" >&2
exit 1
fi
IMAGE_COUNT=$((IMAGE_COUNT + 1))
if [ "$IMAGE_COUNT" -gt "$MAX_IMAGES" ]; then
break
fi
case "$IMAGE_COUNT" in
1) IMAGE_1="$image_path" ;;
2) IMAGE_2="$image_path" ;;
3) IMAGE_3="$image_path" ;;
4) IMAGE_4="$image_path" ;;
esac
done
IFS="$saved_ifs"
if [ "$IMAGE_COUNT" -eq 0 ]; then
IMAGE_1="$DEFAULT_ICON_PATH"
IMAGE_COUNT=1
fi
app_name_escaped=$(escape_c_string "$app_name_value")
app_desc_escaped=$(escape_c_string "$app_desc")
image_1_escaped=$(escape_c_string "$IMAGE_1")
image_2_escaped=$(escape_c_string "$IMAGE_2")
image_3_escaped=$(escape_c_string "$IMAGE_3")
image_4_escaped=$(escape_c_string "$IMAGE_4")
cat > "$OUT_PATH" <<EOF
#include <stdint.h>
#include "elf.h"
struct __attribute__((packed, aligned(4))) boredos_app_note_blob {
Elf64_Word namesz;
Elf64_Word descsz;
Elf64_Word type;
char name[sizeof(BOREDOS_APP_NOTE_NAME)];
boredos_app_metadata_t metadata;
};
__attribute__((used, section(".note.boredos.app"), aligned(4)))
static const struct boredos_app_note_blob g_boredos_app_note = {
.namesz = sizeof(BOREDOS_APP_NOTE_NAME),
.descsz = sizeof(boredos_app_metadata_t),
.type = BOREDOS_APP_NOTE_TYPE,
.name = BOREDOS_APP_NOTE_NAME,
.metadata = {
.magic = BOREDOS_APP_METADATA_MAGIC,
.version = BOREDOS_APP_METADATA_VERSION,
.image_count = ${IMAGE_COUNT},
.reserved = 0,
.app_name = "${app_name_escaped}",
.description = "${app_desc_escaped}",
.images = {
"${image_1_escaped}",
"${image_2_escaped}",
"${image_3_escaped}",
"${image_4_escaped}",
},
},
};
EOF