601 lines
22 KiB
C
Executable File
601 lines
22 KiB
C
Executable File
/*
|
|
* retro.c -- OreBolt OS Module: Retro Game Launcher
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*
|
|
* Scans /data/roms/ for ROM files, auto-detects the target system by
|
|
* file extension, displays an LVGL picker menu, and launches the
|
|
* matching emulator binary via fork()+execv(). Standalone .mod
|
|
* binary with its own main() and LVGL init.
|
|
*
|
|
* Supported systems (by file extension):
|
|
* .nes - Nintendo Entertainment System
|
|
* .fds - Famicom Disk System
|
|
* .smc,.sfc,.fig - Super Nintendo (SNES)
|
|
* .gb - Game Boy
|
|
* .gbc - Game Boy Color
|
|
* .gba - Game Boy Advance
|
|
* .smd,.md,.bin,.gen - Sega Genesis / Mega Drive
|
|
* .sms - Sega Master System
|
|
* .gg - Sega Game Gear
|
|
* .pce - PC Engine / TurboGrafx-16
|
|
* .a26 - Atari 2600
|
|
* .ngp,.ngc - Neo Geo Pocket / Color
|
|
*
|
|
* Emulator binaries must be installed at the paths listed in
|
|
* rom_types[] below.
|
|
*
|
|
* INPUT MAPPING:
|
|
* The companion retro_input_mapper daemon (started by S98retro-input
|
|
* at boot) creates a virtual gamepad via uinput. Emulators should
|
|
* read from this virtual gamepad rather than /dev/input/event0.
|
|
*
|
|
* D-pad: Rotary CW/CCW with axis toggle (rotary press switches
|
|
* between vertical U/D and horizontal L/R modes, auto-
|
|
* reverts to V-mode after 3s idle).
|
|
* A: PLAY short press (confirm/jump/shoot)
|
|
* B: BACK short press (cancel/run)
|
|
* START: PLAY long press (1.5s)
|
|
* SELECT: BACK long press (1.5s)
|
|
* L/R: Side buttons PREV(165) / NEXT(163)
|
|
* X/Y: Combo buttons (PLAY+rotate / BACK+rotate)
|
|
*
|
|
* N64 is NOT supported -- the X1000E MIPS32r2 @ ~1 GHz with 64 MB RAM
|
|
* cannot run N64 emulation at playable framerates.
|
|
*
|
|
* Toolchain: mipsel-linux-musl-gcc (MIPS32r2, musl libc)
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <linux/input.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <ctype.h>
|
|
#include "h2_ui.h"
|
|
#include "log_manager.h"
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* ROM type database */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
#define MAX_ROM_TYPES 32
|
|
#define MAX_ROMS 256
|
|
#define ROM_DIR "/data/roms"
|
|
#define PATH_BUF 512
|
|
|
|
typedef struct {
|
|
const char *ext; /* file extension, lowercase, with dot */
|
|
const char *system; /* human-readable system name */
|
|
const char *emulator; /* absolute path to emulator binary */
|
|
const char *core; /* emulator-specific core/flag, or NULL */
|
|
} rom_type_t;
|
|
|
|
static const rom_type_t rom_db[MAX_ROM_TYPES] = {
|
|
/* --- Nintendo --- */
|
|
{ ".nes", "NES", "/usr/bin/fceux", NULL },
|
|
{ ".fds", "FDS", "/usr/bin/fceux", NULL },
|
|
{ ".smc", "SNES", "/usr/bin/snes9x", NULL },
|
|
{ ".sfc", "SNES", "/usr/bin/snes9x", NULL },
|
|
{ ".fig", "SNES", "/usr/bin/snes9x", NULL },
|
|
{ ".gb", "GameBoy", "/usr/bin/gambatte", NULL },
|
|
{ ".gbc", "GBC", "/usr/bin/gambatte", NULL },
|
|
{ ".gba", "GBA", "/usr/bin/gpsp", NULL },
|
|
/* --- Sega --- */
|
|
{ ".smd", "Genesis", "/usr/bin/dgen", NULL },
|
|
{ ".md", "Genesis", "/usr/bin/dgen", NULL },
|
|
{ ".gen", "Genesis", "/usr/bin/dgen", NULL },
|
|
{ ".bin", "Genesis", "/usr/bin/dgen", "-g" },
|
|
{ ".sms", "SMS", "/usr/bin/mednafen", "-ss" },
|
|
{ ".gg", "GameGear", "/usr/bin/mednafen", "-gg" },
|
|
/* --- NEC / Hudson --- */
|
|
{ ".pce", "PCE", "/usr/bin/mednafen", "-pce" },
|
|
/* --- Atari --- */
|
|
{ ".a26", "Atari2600", "/usr/bin/stella", NULL },
|
|
/* --- SNK --- */
|
|
{ ".ngp", "NeoGeoP", "/usr/bin/mednafen", "-ngp" },
|
|
{ ".ngc", "NeoGeoP", "/usr/bin/mednafen", "-ngp" },
|
|
/* sentinel */
|
|
{ NULL, NULL, NULL, NULL }
|
|
};
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* ROM entry (populated at scan time) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
typedef struct {
|
|
char path[PATH_BUF]; /* full path to ROM file */
|
|
char name[64]; /* display name (filename only) */
|
|
const rom_type_t *type; /* pointer into rom_db */
|
|
} rom_entry_t;
|
|
|
|
static rom_entry_t roms[MAX_ROMS];
|
|
static int rom_count = 0;
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Utility: lowercase the last N chars of a string in-place */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void str_lower_tail(char *s, int n) {
|
|
int len = (int)strlen(s);
|
|
int start = len - n;
|
|
if (start < 0) start = 0;
|
|
for (int i = start; s[i]; i++)
|
|
s[i] = (char)tolower((unsigned char)s[i]);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Utility: extract filename from full path */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void extract_filename(const char *path, char *out, int outlen) {
|
|
const char *slash = strrchr(path, '/');
|
|
if (slash) slash++;
|
|
else slash = path;
|
|
|
|
/* strip the extension for display */
|
|
const char *dot = strrchr(slash, '.');
|
|
int namelen = dot ? (int)(dot - slash) : (int)strlen(slash);
|
|
if (namelen > outlen - 1) namelen = outlen - 1;
|
|
|
|
memcpy(out, slash, (size_t)namelen);
|
|
out[namelen] = '\0';
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Find the rom_type_t that matches a filename extension */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static const rom_type_t *match_rom_type(const char *filename) {
|
|
char lower[PATH_BUF];
|
|
snprintf(lower, sizeof(lower), "%s", filename);
|
|
str_lower_tail(lower, 6); /* extensions are max 4 chars */
|
|
|
|
const char *dot = strrchr(lower, '.');
|
|
if (!dot) return NULL;
|
|
|
|
for (int i = 0; i < MAX_ROM_TYPES && rom_db[i].ext; i++) {
|
|
if (strcmp(dot, rom_db[i].ext) == 0)
|
|
return &rom_db[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Scan ROM_DIR recursively and populate roms[] */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void scan_roms_recursive(const char *basepath) {
|
|
DIR *dir = opendir(basepath);
|
|
if (!dir) return;
|
|
|
|
struct dirent *de;
|
|
while ((de = readdir(dir)) != NULL) {
|
|
if (de->d_name[0] == '.') continue; /* skip hidden / . / .. */
|
|
|
|
char fullpath[PATH_BUF];
|
|
snprintf(fullpath, sizeof(fullpath), "%s/%s", basepath, de->d_name);
|
|
|
|
struct stat st;
|
|
if (lstat(fullpath, &st) != 0) continue;
|
|
|
|
if (S_ISDIR(st.st_mode)) {
|
|
/* recurse into subdirectories (system-named folders) */
|
|
scan_roms_recursive(fullpath);
|
|
continue;
|
|
}
|
|
if (!S_ISREG(st.st_mode)) continue;
|
|
|
|
const rom_type_t *t = match_rom_type(de->d_name);
|
|
if (!t) continue; /* unknown extension, skip */
|
|
|
|
if (rom_count >= MAX_ROMS) break;
|
|
|
|
rom_entry_t *r = &roms[rom_count];
|
|
snprintf(r->path, PATH_BUF, "%s", fullpath);
|
|
extract_filename(de->d_name, r->name, (int)sizeof(r->name));
|
|
r->type = t;
|
|
rom_count++;
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* System filter state */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static const char *system_filters[] = {
|
|
"ALL",
|
|
"NES", "FDS", "SNES",
|
|
"GameBoy", "GBC", "GBA",
|
|
"Genesis", "SMS", "GameGear",
|
|
"PCE", "Atari2600", "NeoGeoP",
|
|
NULL /* sentinel */
|
|
};
|
|
#define NUM_FILTERS (sizeof(system_filters) / sizeof(system_filters[0]) - 1)
|
|
|
|
static int current_filter = 0; /* index into system_filters, 0 = ALL */
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* LVGL UI objects (module-global for event loop access) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static lv_obj_t *title_label = NULL;
|
|
static lv_obj_t *filter_label = NULL;
|
|
static lv_obj_t *count_label = NULL;
|
|
static lv_obj_t *rom_list = NULL;
|
|
static lv_obj_t *list_btns[MAX_ROMS];
|
|
static int visible_count = 0;
|
|
static int scroll_index = 0;
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Build the ROM list UI (called on filter change) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void build_rom_list(void) {
|
|
/* clear existing list buttons */
|
|
if (rom_list) {
|
|
lv_obj_clean(rom_list);
|
|
}
|
|
|
|
visible_count = 0;
|
|
scroll_index = 0;
|
|
|
|
const char *filter = system_filters[current_filter];
|
|
int is_all = (current_filter == 0);
|
|
|
|
for (int i = 0; i < rom_count && visible_count < MAX_ROMS; i++) {
|
|
if (!is_all && strcmp(roms[i].type->system, filter) != 0)
|
|
continue;
|
|
|
|
/* build display text: "[SYSTEM] name" */
|
|
char display[80];
|
|
snprintf(display, sizeof(display), "%s %s",
|
|
roms[i].type->system, roms[i].name);
|
|
|
|
list_btns[visible_count] = lv_list_add_btn(rom_list,
|
|
LV_SYMBOL_PLAY,
|
|
display);
|
|
lv_obj_set_style_text_color(list_btns[visible_count],
|
|
COLOR_TEXT, LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(list_btns[visible_count],
|
|
&lv_font_montserrat_12, LV_PART_MAIN);
|
|
visible_count++;
|
|
}
|
|
|
|
/* update count label */
|
|
char count_buf[48];
|
|
snprintf(count_buf, sizeof(count_buf),
|
|
"%d / %d ROMs", visible_count, rom_count);
|
|
lv_label_set_text(count_label, count_buf);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Scroll the list */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void scroll_rom_list(int delta) {
|
|
scroll_index += delta;
|
|
if (scroll_index < 0) scroll_index = 0;
|
|
if (scroll_index >= visible_count) scroll_index = visible_count - 1;
|
|
|
|
if (visible_count > 0 && scroll_index < visible_count) {
|
|
lv_obj_scroll_to_view(list_btns[scroll_index], LV_ANIM_ON);
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Cycle system filter (rapid-rotate = cycle filter) */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void cycle_filter(void) {
|
|
current_filter++;
|
|
if (system_filters[current_filter] == NULL)
|
|
current_filter = 0;
|
|
|
|
char fbuf[48];
|
|
snprintf(fbuf, sizeof(fbuf), "Filter: %s", system_filters[current_filter]);
|
|
lv_label_set_text(filter_label, fbuf);
|
|
|
|
build_rom_list();
|
|
LOG_INF("filter changed to: %s", system_filters[current_filter]);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* Launch the selected ROM */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
static void launch_rom(int index) {
|
|
if (index < 0 || index >= visible_count) return;
|
|
|
|
/* find the actual rom_entry_t (skip filtered-out entries) */
|
|
const char *filter = system_filters[current_filter];
|
|
int is_all = (current_filter == 0);
|
|
int real_index = -1;
|
|
int target = 0;
|
|
|
|
for (int i = 0; i < rom_count; i++) {
|
|
if (!is_all && strcmp(roms[i].type->system, filter) != 0)
|
|
continue;
|
|
if (target == index) { real_index = i; break; }
|
|
target++;
|
|
}
|
|
if (real_index < 0) return;
|
|
|
|
const rom_entry_t *rom = &roms[real_index];
|
|
const rom_type_t *type = rom->type;
|
|
|
|
LOG_INF("launching ROM: %s [%s]", rom->name, type->system);
|
|
|
|
/* clear screen before launch */
|
|
lv_obj_clean(lv_scr_act());
|
|
|
|
lv_obj_t *launch_lbl = lv_label_create(lv_scr_act());
|
|
char launch_buf[80];
|
|
snprintf(launch_buf, sizeof(launch_buf),
|
|
"Launching %s...\n%s\n[BACK] to exit",
|
|
type->system, rom->name);
|
|
lv_label_set_text(launch_lbl, launch_buf);
|
|
lv_obj_set_style_text_color(launch_lbl, COLOR_ACCENT, LV_PART_MAIN);
|
|
lv_obj_align(launch_lbl, LV_ALIGN_CENTER, 0, 0);
|
|
lv_obj_set_style_text_align(launch_lbl, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
|
|
|
/* flush the "Launching..." screen */
|
|
for (int i = 0; i < 10; i++) lv_timer_handler();
|
|
|
|
pid_t pid = fork();
|
|
if (pid == 0) {
|
|
/*
|
|
* Child: exec the emulator with the ROM path.
|
|
* If the emulator has a core flag (e.g. mednafen -ss), pass it.
|
|
*/
|
|
if (type->core) {
|
|
char *args[] = { (char *)type->emulator,
|
|
(char *)type->core,
|
|
(char *)rom->path,
|
|
NULL };
|
|
execv(type->emulator, args);
|
|
} else {
|
|
char *args[] = { (char *)type->emulator,
|
|
(char *)rom->path,
|
|
NULL };
|
|
execv(type->emulator, args);
|
|
}
|
|
/* exec failed -- emulator binary not found */
|
|
_exit(127);
|
|
}
|
|
|
|
if (pid < 0) {
|
|
LOG_ERR("fork failed for emulator launch");
|
|
return; /* fork failed */
|
|
}
|
|
|
|
LOG_INF("emulator child pid %d started", (int)pid);
|
|
|
|
/*
|
|
* Parent: non-blocking wait in event loop.
|
|
* BACK key sends SIGTERM to the emulator child.
|
|
* Rebuilds the ROM list when child exits.
|
|
*/
|
|
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
|
struct input_event ev;
|
|
int child_running = 1;
|
|
|
|
while (child_running) {
|
|
lv_timer_handler();
|
|
|
|
/* non-blocking child check */
|
|
int status;
|
|
pid_t w = waitpid(pid, &status, WNOHANG);
|
|
if (w == pid) {
|
|
child_running = 0;
|
|
LOG_INF("emulator child %d exited", (int)pid);
|
|
break;
|
|
}
|
|
|
|
/* read input */
|
|
if (input_fd >= 0 &&
|
|
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
|
|
|
if (ev.type == EV_KEY && ev.value == 1) {
|
|
if (ev.code == H2_KEY_BACK) {
|
|
kill(pid, SIGTERM);
|
|
/* give emulator 300ms to clean up */
|
|
usleep(300000);
|
|
waitpid(pid, &status, WNOHANG);
|
|
child_running = 0;
|
|
LOG_INF("emulator child %d terminated by user", (int)pid);
|
|
}
|
|
}
|
|
}
|
|
usleep(15000);
|
|
}
|
|
|
|
if (input_fd >= 0) close(input_fd);
|
|
|
|
/* rebuild the UI for the ROM picker */
|
|
lv_obj_clean(lv_scr_act());
|
|
}
|
|
|
|
/* ------------------------------------------------------------------ */
|
|
/* main -- module entry point */
|
|
/* ------------------------------------------------------------------ */
|
|
|
|
int main(void) {
|
|
init_h2_graphics_runtime("RETRO GAME LAUNCHER");
|
|
LOG_INF("retro module started");
|
|
|
|
lv_obj_t *scr = lv_scr_act();
|
|
|
|
/* ---- header ---- */
|
|
title_label = lv_label_create(scr);
|
|
lv_label_set_text(title_label, "RETRO GAME LAUNCHER");
|
|
lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 8);
|
|
lv_obj_set_style_text_color(title_label, COLOR_PRIMARY, LV_PART_MAIN);
|
|
|
|
/* ---- filter indicator (PLAY = launch, rapid-rotate = filter) ---- */
|
|
filter_label = lv_label_create(scr);
|
|
lv_label_set_text(filter_label, "Filter: ALL");
|
|
lv_obj_align(filter_label, LV_ALIGN_TOP_LEFT, 8, 30);
|
|
lv_obj_set_style_text_color(filter_label, COLOR_ACCENT, LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(filter_label, &lv_font_montserrat_10,
|
|
LV_PART_MAIN);
|
|
|
|
/* ---- ROM count ---- */
|
|
count_label = lv_label_create(scr);
|
|
lv_obj_align(count_label, LV_ALIGN_TOP_RIGHT, -8, 30);
|
|
lv_obj_set_style_text_color(count_label, lv_color_make(120, 120, 140),
|
|
LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(count_label, &lv_font_montserrat_10,
|
|
LV_PART_MAIN);
|
|
|
|
/* ---- ROM list ---- */
|
|
rom_list = lv_list_create(scr);
|
|
lv_obj_set_size(rom_list, 296, 168);
|
|
lv_obj_align(rom_list, LV_ALIGN_TOP_MID, 0, 46);
|
|
lv_obj_set_style_bg_color(rom_list,
|
|
lv_color_make(18, 22, 32), LV_PART_MAIN);
|
|
lv_obj_set_style_border_width(rom_list, 1, LV_PART_MAIN);
|
|
lv_obj_set_style_border_color(rom_list,
|
|
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
|
|
|
/* ---- status bar ---- */
|
|
lv_obj_t *status = lv_label_create(scr);
|
|
lv_label_set_text(status, "ROTATE=scroll PLAY=launch BACK=exit");
|
|
lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4);
|
|
lv_obj_set_style_text_color(status, lv_color_make(80, 85, 100),
|
|
LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(status, &lv_font_montserrat_10, LV_PART_MAIN);
|
|
|
|
/* ---- scan ROMs ---- */
|
|
scan_roms_recursive(ROM_DIR);
|
|
LOG_INF("ROM scan complete: %d ROM(s) found", rom_count);
|
|
build_rom_list();
|
|
|
|
if (rom_count == 0) {
|
|
lv_obj_t *empty = lv_label_create(rom_list);
|
|
lv_label_set_text(empty,
|
|
LV_SYMBOL_WARNING " No ROMs found.\n\n"
|
|
"Place ROM files in:\n"
|
|
" /data/roms/\n\n"
|
|
"Organize by folder:\n"
|
|
" /data/roms/nes/\n"
|
|
" /data/roms/snes/\n"
|
|
" /data/roms/gb/\n"
|
|
" ...etc");
|
|
lv_obj_set_style_text_color(empty, lv_color_make(160, 100, 60),
|
|
LV_PART_MAIN);
|
|
lv_obj_set_style_text_align(empty, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN);
|
|
}
|
|
|
|
/* ---- input loop ---- */
|
|
int input_fd = open("/dev/input/event0", O_RDONLY | O_NONBLOCK);
|
|
struct input_event ev;
|
|
|
|
/* rotary accumulator for filter cycling:
|
|
* 8 fast clicks in < 200ms each = cycle filter
|
|
* (avoids needing a separate button for filter) */
|
|
int rotary_clicks = 0;
|
|
long long last_click_ms = 0;
|
|
|
|
while (1) {
|
|
lv_timer_handler();
|
|
|
|
if (input_fd >= 0 &&
|
|
read(input_fd, &ev, sizeof(struct input_event)) > 0) {
|
|
|
|
if (ev.type == EV_REL && ev.code == 0) {
|
|
if (ev.value != 0) {
|
|
/* track rapid scrolling for filter toggle */
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
long long now = (long long)ts.tv_sec * 1000 +
|
|
ts.tv_nsec / 1000000;
|
|
|
|
if (now - last_click_ms < 200) {
|
|
rotary_clicks++;
|
|
if (rotary_clicks >= 8) {
|
|
cycle_filter();
|
|
rotary_clicks = 0;
|
|
}
|
|
} else {
|
|
rotary_clicks = 1;
|
|
}
|
|
last_click_ms = now;
|
|
|
|
scroll_rom_list(ev.value > 0 ? 1 : -1);
|
|
}
|
|
}
|
|
|
|
if (ev.type == EV_KEY && ev.value == 1) {
|
|
if (ev.code == H2_KEY_PLAY && visible_count > 0) {
|
|
/* PLAY: launch selected ROM */
|
|
launch_rom(scroll_index);
|
|
/* after launch returns, rebuild UI */
|
|
lv_obj_clean(lv_scr_act());
|
|
|
|
/* re-create widgets */
|
|
title_label = lv_label_create(scr);
|
|
lv_label_set_text(title_label, "RETRO GAME LAUNCHER");
|
|
lv_obj_align(title_label, LV_ALIGN_TOP_MID, 0, 8);
|
|
lv_obj_set_style_text_color(title_label, COLOR_PRIMARY,
|
|
LV_PART_MAIN);
|
|
|
|
filter_label = lv_label_create(scr);
|
|
char fbuf[48];
|
|
snprintf(fbuf, sizeof(fbuf), "Filter: %s",
|
|
system_filters[current_filter]);
|
|
lv_label_set_text(filter_label, fbuf);
|
|
lv_obj_align(filter_label, LV_ALIGN_TOP_LEFT, 8, 30);
|
|
lv_obj_set_style_text_color(filter_label, COLOR_ACCENT,
|
|
LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(filter_label,
|
|
&lv_font_montserrat_10, LV_PART_MAIN);
|
|
|
|
count_label = lv_label_create(scr);
|
|
lv_obj_align(count_label, LV_ALIGN_TOP_RIGHT, -8, 30);
|
|
lv_obj_set_style_text_color(count_label,
|
|
lv_color_make(120, 120, 140), LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(count_label,
|
|
&lv_font_montserrat_10, LV_PART_MAIN);
|
|
|
|
rom_list = lv_list_create(scr);
|
|
lv_obj_set_size(rom_list, 296, 168);
|
|
lv_obj_align(rom_list, LV_ALIGN_TOP_MID, 0, 46);
|
|
lv_obj_set_style_bg_color(rom_list,
|
|
lv_color_make(18, 22, 32), LV_PART_MAIN);
|
|
lv_obj_set_style_border_width(rom_list, 1, LV_PART_MAIN);
|
|
lv_obj_set_style_border_color(rom_list,
|
|
lv_color_make(50, 55, 70), LV_PART_MAIN);
|
|
|
|
status = lv_label_create(scr);
|
|
lv_label_set_text(status,
|
|
"ROTATE=scroll PLAY=launch BACK=exit");
|
|
lv_obj_align(status, LV_ALIGN_BOTTOM_MID, 0, -4);
|
|
lv_obj_set_style_text_color(status,
|
|
lv_color_make(80, 85, 100), LV_PART_MAIN);
|
|
lv_obj_set_style_text_font(status,
|
|
&lv_font_montserrat_10, LV_PART_MAIN);
|
|
|
|
build_rom_list();
|
|
}
|
|
|
|
if (ev.code == H2_KEY_BACK) {
|
|
/* BACK: exit module */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
usleep(15000);
|
|
}
|
|
|
|
if (input_fd >= 0) close(input_fd);
|
|
LOG_INF("retro module exited");
|
|
return 0;
|
|
} |