diff --git a/.gitignore b/.gitignore index 3136a5393..1d41efc6f 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,7 @@ workspace/.container_runtime AGENTS.md CLAUDE.md .worktrees/ + +# local tooling data (headroom memory, graphify output) +.headroom/ +graphify-out/ diff --git a/workspace/all/batmon/batmon.c b/workspace/all/batmon/batmon.cpp similarity index 97% rename from workspace/all/batmon/batmon.c rename to workspace/all/batmon/batmon.cpp index 25761af08..629efac48 100644 --- a/workspace/all/batmon/batmon.c +++ b/workspace/all/batmon/batmon.cpp @@ -1,17 +1,21 @@ // heavily modified from the Onion original: https://github.com/OnionUI/Onion/tree/main/src/batmon -#include -#include -#include -#include +#include +#include +#include #include #include #include +#include + +// Project + libbatmondb C headers declare C-linkage symbols defined in the +// still-C common/ and libbatmondb translation units; include them as extern "C" +// so this C++ unit links against the unmangled names. +extern "C" { #include #include - -#include #include +} #define CHECK_BATTERY_TIMEOUT_S 15 // s - check battery percentage every 15s @@ -24,7 +28,7 @@ static bool is_suspended = false; int battery_current_state_duration = 0; int best_session_time = 0; -char *device_model = NULL; +char *device_model = nullptr; void register_handler(); void sigintHandler(int signum) { diff --git a/workspace/all/batmon/makefile b/workspace/all/batmon/makefile index 869d837d5..2d8a8f534 100644 --- a/workspace/all/batmon/makefile +++ b/workspace/all/batmon/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = batmon INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings -lbatmondb -lsqlite3 PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h $(PREFIX_LOCAL)/include/batmondb.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/battery/battery.c b/workspace/all/battery/battery.cpp similarity index 86% rename from workspace/all/battery/battery.c rename to workspace/all/battery/battery.cpp index 990d63ac7..2426ac76a 100644 --- a/workspace/all/battery/battery.c +++ b/workspace/all/battery/battery.cpp @@ -1,16 +1,20 @@ // heavily modified from the Onion original: https://github.com/OnionUI/Onion/tree/main/src/batteryMonitorUI -#include +#include #include -#include -#include -#include +#include +#include + +#include +// Project + libbatmondb C headers declare C-linkage symbols defined in the +// still-C common/ and libbatmondb translation units; include as extern "C". +extern "C" { +#include #include "defines.h" #include "api.h" #include "utils.h" - -#include #include +} #define GRAPH_LINE_WIDTH 1 @@ -126,8 +130,8 @@ static void sigHandler(int sig) static SDL_Surface *screen; -char *device_model = NULL; -sqlite3 *bat_log_db = NULL; +char *device_model = nullptr; +sqlite3 *bat_log_db = nullptr; void secondsToHoursMinutes(int seconds, char *output) { @@ -196,8 +200,10 @@ int _renderText(const char *text, TTF_Font *font, SDL_Color color, SDL_Rect *rec if (textSurface != NULL) { text_width = textSurface->w; - if (right_align) - SDL_BlitSurface(textSurface, NULL, screen, &(SDL_Rect){rect->x - textSurface->w, rect->y, rect->w, rect->h}); + if (right_align) { + SDL_Rect r = {rect->x - textSurface->w, rect->y, rect->w, rect->h}; + SDL_BlitSurface(textSurface, NULL, screen, &r); + } else SDL_BlitSurface(textSurface, NULL, screen, rect); SDL_FreeSurface(textSurface); @@ -395,18 +401,20 @@ void drawBatteryIcon(int percent, SDL_Rect dst) { int x = dst.x; int y = dst.y; //SDL_Rect rect = asset_rects[ASSET_BATTERY]; - SDL_Rect rect = (SDL_Rect){SCALE4(47,51,17,10)}; - GFX_blitAsset(ASSET_BATTERY, NULL, screen, &(SDL_Rect){x,y}); + SDL_Rect rect = {SCALE4(47,51,17,10)}; + SDL_Rect battery_dst = {x,y}; + GFX_blitAsset(ASSET_BATTERY, NULL, screen, &battery_dst); //rect = asset_rects[ASSET_BATTERY_FILL]; - rect = (SDL_Rect){SCALE4(81,33,12,6)}; + rect = SDL_Rect{SCALE4(81,33,12,6)}; SDL_Rect clip = rect; clip.w *= percent; clip.w /= 100; if (clip.w<=0) return; clip.x = rect.w - clip.w; clip.y = 0; - - GFX_blitAsset(ASSET_BATTERY_FILL, &clip, screen, &(SDL_Rect){x+SCALE1(3)+clip.x,y+SCALE1(2)}); + + SDL_Rect fill_dst = {x+SCALE1(3)+clip.x,y+SCALE1(2)}; + GFX_blitAsset(ASSET_BATTERY_FILL, &clip, screen, &fill_dst); } void renderPage() @@ -453,29 +461,37 @@ void renderPage() switch_zoom_profile(segment_duration); // x axis labels - renderText(label[0], font.small, COLOR_WHITE, &(SDL_Rect){graph.layout.label1_x, graph.layout.label_y, 32, 32}); - renderText(label[1], font.small, COLOR_WHITE, &(SDL_Rect){graph.layout.label2_x, graph.layout.label_y, 32, 32}); - renderText(label[2], font.small, COLOR_WHITE, &(SDL_Rect){graph.layout.label3_x, graph.layout.label_y, 32, 32}); - renderText(label[3], font.small, COLOR_WHITE, &(SDL_Rect){graph.layout.label4_x, graph.layout.label_y, 32, 32}); + SDL_Rect lbl0 = {graph.layout.label1_x, graph.layout.label_y, 32, 32}; + renderText(label[0], GFX_getFonts()->small, COLOR_WHITE, &lbl0); + SDL_Rect lbl1 = {graph.layout.label2_x, graph.layout.label_y, 32, 32}; + renderText(label[1], GFX_getFonts()->small, COLOR_WHITE, &lbl1); + SDL_Rect lbl2 = {graph.layout.label3_x, graph.layout.label_y, 32, 32}; + renderText(label[2], GFX_getFonts()->small, COLOR_WHITE, &lbl2); + SDL_Rect lbl3 = {graph.layout.label4_x, graph.layout.label_y, 32, 32}; + renderText(label[3], GFX_getFonts()->small, COLOR_WHITE, &lbl3); // y axis "labels" - drawBatteryIcon(100, (SDL_Rect){graph.layout.icon_x, graph.layout.icon1_y}); - drawBatteryIcon(66, (SDL_Rect){graph.layout.icon_x, graph.layout.icon2_y}); - drawBatteryIcon(33, (SDL_Rect){graph.layout.icon_x, graph.layout.icon3_y}); - drawBatteryIcon(0, (SDL_Rect){graph.layout.icon_x, graph.layout.icon4_y}); + drawBatteryIcon(100, SDL_Rect{graph.layout.icon_x, graph.layout.icon1_y}); + drawBatteryIcon(66, SDL_Rect{graph.layout.icon_x, graph.layout.icon2_y}); + drawBatteryIcon(33, SDL_Rect{graph.layout.icon_x, graph.layout.icon3_y}); + drawBatteryIcon(0, SDL_Rect{graph.layout.icon_x, graph.layout.icon4_y}); char text_line[255]; sprintf(text_line, "Since Charge: %s", session_duration); - renderText(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_session_x, graph.layout.label_session_y, graph.layout.label_size_x, graph.layout.label_size_y}); + SDL_Rect session_rect = {graph.layout.label_session_x, graph.layout.label_session_y, graph.layout.label_size_x, graph.layout.label_size_y}; + renderText(text_line, GFX_getFonts()->medium, COLOR_WHITE, &session_rect); sprintf(text_line, "Current: %s", current_percentage); - renderText(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_current_x, graph.layout.label_current_y, graph.layout.label_size_x, graph.layout.label_size_y}); + SDL_Rect current_rect = {graph.layout.label_current_x, graph.layout.label_current_y, graph.layout.label_size_x, graph.layout.label_size_y}; + renderText(text_line, GFX_getFonts()->medium, COLOR_WHITE, ¤t_rect); sprintf(text_line, "Remaining: %s", session_left); - renderTextAlignRight(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_left_x, graph.layout.label_left_y, graph.layout.label_size_x, graph.layout.label_size_y}); + SDL_Rect left_rect = {graph.layout.label_left_x, graph.layout.label_left_y, graph.layout.label_size_x, graph.layout.label_size_y}; + renderTextAlignRight(text_line, GFX_getFonts()->medium, COLOR_WHITE, &left_rect); sprintf(text_line, "Longest: %s", session_best); - renderTextAlignRight(text_line, font.medium, COLOR_WHITE, &(SDL_Rect){graph.layout.label_best_x, graph.layout.label_best_y, graph.layout.label_size_x, graph.layout.label_size_y}); + SDL_Rect best_rect = {graph.layout.label_best_x, graph.layout.label_best_y, graph.layout.label_size_x, graph.layout.label_size_y}; + renderTextAlignRight(text_line, GFX_getFonts()->medium, COLOR_WHITE, &best_rect); int half_line_width = (int)(GRAPH_LINE_WIDTH) / 2; @@ -556,8 +572,10 @@ void renderPage() } } SDL_UnlockSurface(screen); - if (x_end != 0) - GFX_blitAsset(ASSET_BATTERY_LOW, NULL, screen, &(SDL_Rect){x_end, y_end}); + if (x_end != 0) { + SDL_Rect low_dst = {x_end, y_end}; + GFX_blitAsset(ASSET_BATTERY_LOW, NULL, screen, &low_dst); + } } } @@ -746,14 +764,17 @@ int main(int argc, char *argv[]) } char title[256]; - int text_width = GFX_truncateText(font.large, display_name, title, max_width, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_truncateText(GFX_getFonts()->large, display_name, title, max_width, SCALE1(BUTTON_PADDING * 2)); max_width = MIN(max_width, text_width); SDL_Surface *text; - text = TTF_RenderUTF8_Blended(font.large, title, COLOR_WHITE); + text = TTF_RenderUTF8_Blended(GFX_getFonts()->large, title, COLOR_WHITE); - GFX_blitPill(ASSET_BLACK_PILL, screen, &(SDL_Rect){SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, screen, &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); + SDL_Rect title_pill = {SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}; + GFX_blitPill(ASSET_BLACK_PILL, screen, &title_pill); + SDL_Rect title_src = {0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}; + SDL_Rect title_dst = {SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}; + SDL_BlitSurface(text, &title_src, screen, &title_dst); SDL_FreeSurface(text); } @@ -761,10 +782,15 @@ int main(int argc, char *argv[]) if (show_setting) GFX_blitHardwareHints(screen, show_setting); - else - GFX_blitButtonGroup((char *[]){"L/R", "SCROLL", "L1/R1", "ZOOM", NULL}, 0, screen, 0); + else { + // Named local array: g++ 8.3 miscompiles a (char*[]){...} compound + // literal passed straight to a function. + const char* hints_scroll[] = {"L/R", "SCROLL", "L1/R1", "ZOOM", NULL}; + GFX_blitButtonGroup((char**)hints_scroll, 0, screen, 0); + } - GFX_blitButtonGroup((char *[]){"B", "BACK", NULL}, 1, screen, 1); + const char* hints_back[] = {"B", "BACK", NULL}; + GFX_blitButtonGroup((char**)hints_back, 1, screen, 1); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/battery/makefile b/workspace/all/battery/makefile index 98856bfba..bce505120 100644 --- a/workspace/all/battery/makefile +++ b/workspace/all/battery/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = battery INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings -lbatmondb -lsqlite3 PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/batmondb.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/bootlogo/bootlogo.c b/workspace/all/bootlogo/bootlogo.cpp similarity index 84% rename from workspace/all/bootlogo/bootlogo.c rename to workspace/all/bootlogo/bootlogo.cpp index c9c2913a3..52bccd62a 100644 --- a/workspace/all/bootlogo/bootlogo.c +++ b/workspace/all/bootlogo/bootlogo.cpp @@ -1,14 +1,17 @@ -#include +#include #include -#include -#include +#include #include #include -#include +// Project + libmsettings C headers declare C-linkage symbols defined in the +// still-C common/ and libmsettings translation units; include as extern "C". +extern "C" { +#include #include "defines.h" #include "api.h" #include "utils.h" +} static bool quit = false; @@ -57,9 +60,9 @@ int loadImages() SDL_Surface *bmp = IMG_Load(path); if (bmp) { count++; - images = realloc(images, sizeof(SDL_Surface*) * count); + images = (SDL_Surface**)realloc(images, sizeof(SDL_Surface*) * count); images[count-1] = bmp; - image_paths = realloc(image_paths, sizeof(char*) * count); + image_paths = (char**)realloc(image_paths, sizeof(char*) * count); image_paths[count-1] = strdup(path); } } @@ -140,7 +143,7 @@ int main(int argc, char *argv[]) // sync // umount $BOOT_PATH // reboot - char* boot_path = "/mnt/boot/"; + const char* boot_path = "/mnt/boot/"; char* logo_path = image_paths[selected]; char cmd[256]; snprintf(cmd, sizeof(cmd), "mkdir -p %s && mount -t vfat /dev/mmcblk0p1 %s && cp \"%s\" %s/bootlogo.bmp && sync && umount %s && reboot", boot_path, boot_path, logo_path, boot_path, boot_path); @@ -179,8 +182,12 @@ int main(int argc, char *argv[]) SDL_BlitSurface(image, NULL, screen, &image_rect); } - GFX_blitButtonGroup((char *[]){"L/R", "SCROLL", NULL}, 0, screen, 0); - GFX_blitButtonGroup((char *[]){"A", "SET", "B", "BACK", NULL}, 1, screen, 1); + // Route button hints through named local arrays: g++ 8.3 miscompiles + // a (char*[]){...} compound literal passed straight to a function. + const char* hints_scroll[] = {"L/R", "SCROLL", NULL}; + GFX_blitButtonGroup((char**)hints_scroll, 0, screen, 0); + const char* hints_set[] = {"A", "SET", "B", "BACK", NULL}; + GFX_blitButtonGroup((char**)hints_set, 1, screen, 1); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/bootlogo/makefile b/workspace/all/bootlogo/makefile index b9a16f2bb..e62b41fc8 100644 --- a/workspace/all/bootlogo/makefile +++ b/workspace/all/bootlogo/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = bootlogo INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/clock/clock.c b/workspace/all/clock/clock.cpp similarity index 80% rename from workspace/all/clock/clock.c rename to workspace/all/clock/clock.cpp index 3734f6329..6547e933a 100644 --- a/workspace/all/clock/clock.c +++ b/workspace/all/clock/clock.cpp @@ -1,13 +1,17 @@ // loosely based on https://github.com/gameblabla/clock_sdl_app -#include -#include +#include +#include #include -#include +// Project + libmsettings C headers declare C-linkage symbols defined in the +// still-C common/ and libmsettings translation units; include as extern "C". +extern "C" { +#include #include "defines.h" #include "api.h" #include "utils.h" +} enum { CURSOR_YEAR, @@ -32,20 +36,21 @@ int main(int argc , char* argv[]) { SDL_FillRect(digits, NULL, RGB_BLACK); SDL_Surface* digit; - char* chars[] = { "0","1","2","3","4","5","6","7","8","9","/",":", NULL }; - char* c; + const char* chars[] = { "0","1","2","3","4","5","6","7","8","9","/",":", NULL }; + const char* c; int i = 0; #define DIGIT_WIDTH 10 #define DIGIT_HEIGHT 16 #define CHAR_SLASH 10 #define CHAR_COLON 11 - while (c = chars[i]) { - digit = TTF_RenderUTF8_Blended(font.large, c, COLOR_WHITE); + while ((c = chars[i])) { + digit = TTF_RenderUTF8_Blended(GFX_getFonts()->large, c, COLOR_WHITE); int y = i==CHAR_COLON ? SCALE1(-1.5) : 0; // : sits too low naturally // TODO: y offset is wrong here // printf("%s x:%i y:%i SCALE1(DIGIT_HEIGHT):%i SCALE1(DIGIT_HEIGHT) - digit->h:%i\n", c, (i * SCALE1(DIGIT_WIDTH)), y, SCALE1(DIGIT_HEIGHT), SCALE1(DIGIT_HEIGHT) - digit->h); fflush(stdout); - SDL_BlitSurface(digit, NULL, digits, &(SDL_Rect){ (i * SCALE1(DIGIT_WIDTH)) + (SCALE1(DIGIT_WIDTH) - digit->w)/2, y + (SCALE1(DIGIT_HEIGHT) - digit->h)/2 }); + SDL_Rect dst = { (i * SCALE1(DIGIT_WIDTH)) + (SCALE1(DIGIT_WIDTH) - digit->w)/2, y + (SCALE1(DIGIT_HEIGHT) - digit->h)/2 }; + SDL_BlitSurface(digit, NULL, digits, &dst); SDL_FreeSurface(digit); i += 1; } @@ -67,15 +72,20 @@ int main(int argc , char* argv[]) { int32_t seconds_selected = tm.tm_sec; int32_t am_selected = tm.tm_hour < 12; - // x,y,w are pre-scaled - int blit(int i, int x, int y) { - SDL_BlitSurface(digits, &(SDL_Rect){i*SCALE1(10),0,SCALE2(10,16)}, screen, &(SDL_Rect){x,y}); + // x,y,w are pre-scaled. These were GCC nested functions (a C-only extension); + // as C++ they become lambdas capturing the enclosing state (digits/screen and + // the date fields) by reference. + auto blit = [&](int i, int x, int y) -> int { + SDL_Rect src = { i*SCALE1(10), 0, SCALE2(10,16) }; + SDL_Rect dst = { x, y }; + SDL_BlitSurface(digits, &src, screen, &dst); return x + SCALE1(10); - } - void blitBar(int x, int y, int w) { - GFX_blitPill(ASSET_UNDERLINE, screen, &(SDL_Rect){x,y,w}); - } - int blitNumber(int num, int x, int y) { + }; + auto blitBar = [&](int x, int y, int w) { + SDL_Rect dst = { x, y, w }; + GFX_blitPill(ASSET_UNDERLINE, screen, &dst); + }; + auto blitNumber = [&](int num, int x, int y) -> int { int n; if (num > 999) { n = num / 1000; @@ -94,8 +104,8 @@ int main(int argc , char* argv[]) { x = blit(n, x,y); return x; - } - void validate(void) { + }; + auto validate = [&]() { // leap year uint32_t february_days = 28; if ( ((year_selected % 4 == 0) && (year_selected % 100 != 0)) || (year_selected % 400 == 0)) february_days = 29; @@ -135,7 +145,7 @@ int main(int argc , char* argv[]) { else if (minute_selected < 0) minute_selected += 60; if (seconds_selected > 59) seconds_selected -= 60; else if (seconds_selected < 0) seconds_selected += 60; - } + }; int option_count = 7; @@ -254,9 +264,15 @@ int main(int argc , char* argv[]) { GFX_blitHardwareGroup(screen, show_setting); if (show_setting) GFX_blitHardwareHints(screen, show_setting); - else GFX_blitButtonGroup((char*[]){ "SELECT",show_24hour?"12 HOUR":"24 HOUR", NULL }, 0, screen, 0); + else { + // Named local array: g++ 8.3 miscompiles a (char*[]){...} compound + // literal passed straight to a function. + const char* hints_hour[] = { "SELECT", show_24hour?"12 HOUR":"24 HOUR", NULL }; + GFX_blitButtonGroup((char**)hints_hour, 0, screen, 0); + } - GFX_blitButtonGroup((char*[]){ "B","CANCEL", "A","SET", NULL }, 1, screen, 1); + const char* hints_setcancel[] = { "B","CANCEL", "A","SET", NULL }; + GFX_blitButtonGroup((char**)hints_setcancel, 1, screen, 1); // 376 or 446 (@2x) // 188 or 223 (@1x) @@ -291,12 +307,13 @@ int main(int argc , char* argv[]) { x = blit(CHAR_COLON, x,y); x = blitNumber(seconds_selected, x,y); - int ampm_w; + int ampm_w = 0; if (!show_24hour) { x += SCALE1(10); // space - SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, am_selected ? "AM" : "PM", COLOR_WHITE); + SDL_Surface* text = TTF_RenderUTF8_Blended(GFX_getFonts()->large, am_selected ? "AM" : "PM", COLOR_WHITE); ampm_w = text->w + SCALE1(2); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x,y-SCALE1(3)}); + SDL_Rect dst = { x, y-SCALE1(3) }; + SDL_BlitSurface(text, NULL, screen, &dst); SDL_FreeSurface(text); } diff --git a/workspace/all/clock/makefile b/workspace/all/clock/makefile index cd5b08bde..5dd8e3b19 100644 --- a/workspace/all/clock/makefile +++ b/workspace/all/clock/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = clock INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/common/api.c b/workspace/all/common/api.cpp similarity index 95% rename from workspace/all/common/api.c rename to workspace/all/common/api.cpp index 19f3f5439..a35570c1f 100644 --- a/workspace/all/common/api.c +++ b/workspace/all/common/api.cpp @@ -1,13 +1,8 @@ -#include "defines.h" -#include "api.h" - #include #include #include -#include #include #include -#include #include #include #include @@ -16,10 +11,16 @@ #include #include +// Project + libmsettings C headers declare C-linkage symbols; include as +// extern "C" so this C++ unit's own exports keep C linkage for the still-C +// platform.c. api.h / utils.h / config.h carry their own guards. +extern "C" { +#include +#include "defines.h" +#include "api.h" #include "utils.h" #include "config.h" - -#include +} extern pthread_mutex_t audio_mutex; @@ -94,7 +95,8 @@ static struct GFX_Context static SDL_Rect asset_rects[ASSET_COUNT]; static uint32_t asset_rgbs[ASSET_COLORS]; static SDL_Rect input_rects[INPUT_COUNT]; -GFX_Fonts font; +static GFX_Fonts font; +GFX_Fonts* GFX_getFonts(void) { return &font; } /////////////////////////////// @@ -218,15 +220,7 @@ static double current_fps = SCREEN_FPS; static int fps_counter = 0; PerfProfile perf = {0}; -int currentshaderpass = 0; -int currentshadersrcw = 0; -int currentshadersrch = 0; -int currentshaderdstw = 0; -int currentshaderdsth = 0; -int currentshadertexw = 0; -int currentshadertexh = 0; - -int should_rotate = 0; +GFX_RenderState gfx_render = {0}; static pthread_mutex_t perf_cpu_monitor_mutex = PTHREAD_MUTEX_INITIALIZER; static int perf_cpu_monitor_enabled = 0; @@ -1089,9 +1083,8 @@ int GFX_blitWrappedText(TTF_Font *font, const char *text, int max_width, int max // Render line and continue to next SDL_Surface *line_surface = TTF_RenderUTF8_Blended(font, line, color); if (line_surface) { - SDL_BlitSurface(line_surface, NULL, surface, &(SDL_Rect){ - center_x - line_surface->w / 2, y - }); + { SDL_Rect _r = { center_x - line_surface->w / 2, y }; + SDL_BlitSurface(line_surface, NULL, surface, &_r); } y += line_surface->h; SDL_FreeSurface(line_surface); } @@ -1103,9 +1096,8 @@ int GFX_blitWrappedText(TTF_Font *font, const char *text, int max_width, int max snprintf(truncated, sizeof(truncated), "%s...", line); SDL_Surface *line_surface = TTF_RenderUTF8_Blended(font, truncated, color); if (line_surface) { - SDL_BlitSurface(line_surface, NULL, surface, &(SDL_Rect){ - center_x - line_surface->w / 2, y - }); + { SDL_Rect _r = { center_x - line_surface->w / 2, y }; + SDL_BlitSurface(line_surface, NULL, surface, &_r); } y += line_surface->h; SDL_FreeSurface(line_surface); } @@ -1121,9 +1113,8 @@ int GFX_blitWrappedText(TTF_Font *font, const char *text, int max_width, int max if (line[0] != '\0') { SDL_Surface *line_surface = TTF_RenderUTF8_Blended(font, line, color); if (line_surface) { - SDL_BlitSurface(line_surface, NULL, surface, &(SDL_Rect){ - center_x - line_surface->w / 2, y - }); + { SDL_Rect _r = { center_x - line_surface->w / 2, y }; + SDL_BlitSurface(line_surface, NULL, surface, &_r); } y += line_surface->h; SDL_FreeSurface(line_surface); } @@ -1863,7 +1854,7 @@ void GFX_blitPillColor(int asset, SDL_Surface *dst, SDL_Rect *dst_rect, uint32_t w = h; w -= h; - GFX_blitAssetColor(asset, &(SDL_Rect){0, 0, r, h}, dst, &(SDL_Rect){x, y}, asset_color); + { SDL_Rect _s = {0, 0, r, h}, _d = {x, y}; GFX_blitAssetColor(asset, &_s, dst, &_d, asset_color); } x += r; if (w > 0) { @@ -1878,11 +1869,11 @@ void GFX_blitPillColor(int asset, SDL_Surface *dst, SDL_Rect *dst_rect, uint32_t else { // Fallback for non-pill assets that still use this helper. - GFX_fillRectBlend(dst, &(SDL_Rect){x, y, w, h}, asset_color); + { SDL_Rect _r = {x, y, w, h}; GFX_fillRectBlend(dst, &_r, asset_color); } } x += w; } - GFX_blitAssetColor(asset, &(SDL_Rect){r, 0, r, h}, dst, &(SDL_Rect){x, y}, asset_color); + { SDL_Rect _s = {r, 0, r, h}, _d = {x, y}; GFX_blitAssetColor(asset, &_s, dst, &_d, asset_color); } } void GFX_blitPill(int asset, SDL_Surface *dst, SDL_Rect *dst_rect) { @@ -1936,13 +1927,13 @@ void GFX_blitRectColor(int asset, SDL_Surface *dst, SDL_Rect *dst_rect, uint32_t int d = rect->w; int r = d / 2; - GFX_blitAssetColor(asset, &(SDL_Rect){0, 0, r, r}, dst, &(SDL_Rect){x, y}, asset_color); - SDL_FillRect(dst, &(SDL_Rect){x + r, y, w - d, r}, asset_color); - GFX_blitAssetColor(asset, &(SDL_Rect){r, 0, r, r}, dst, &(SDL_Rect){x + w - r, y}, asset_color); - SDL_FillRect(dst, &(SDL_Rect){x, y + r, w, h - d}, asset_color); - GFX_blitAssetColor(asset, &(SDL_Rect){0, r, r, r}, dst, &(SDL_Rect){x, y + h - r}, asset_color); - SDL_FillRect(dst, &(SDL_Rect){x + r, y + h - r, w - d, r}, asset_color); - GFX_blitAssetColor(asset, &(SDL_Rect){r, r, r, r}, dst, &(SDL_Rect){x + w - r, y + h - r}, asset_color); + { SDL_Rect _s = {0, 0, r, r}, _d = {x, y}; GFX_blitAssetColor(asset, &_s, dst, &_d, asset_color); } + { SDL_Rect _r = {x + r, y, w - d, r}; SDL_FillRect(dst, &_r, asset_color); } + { SDL_Rect _s = {r, 0, r, r}, _d = {x + w - r, y}; GFX_blitAssetColor(asset, &_s, dst, &_d, asset_color); } + { SDL_Rect _r = {x, y + r, w, h - d}; SDL_FillRect(dst, &_r, asset_color); } + { SDL_Rect _s = {0, r, r, r}, _d = {x, y + h - r}; GFX_blitAssetColor(asset, &_s, dst, &_d, asset_color); } + { SDL_Rect _r = {x + r, y + h - r, w - d, r}; SDL_FillRect(dst, &_r, asset_color); } + { SDL_Rect _s = {r, r, r, r}, _d = {x + w - r, y + h - r}; GFX_blitAssetColor(asset, &_s, dst, &_d, asset_color); } } void GFX_assetRect(int asset, SDL_Rect *dst_rect) @@ -2058,7 +2049,7 @@ void GFX_blitButton(char *hint, char *button, SDL_Surface *dst, SDL_Rect *dst_re else { // label text = TTF_RenderUTF8_Blended(font.medium, button, ALT_BUTTON_TEXT_COLOR); - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){dst_rect->x + (SCALE1(BUTTON_SIZE) - text->w) / 2, dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2}); + { SDL_Rect _r = {dst_rect->x + (SCALE1(BUTTON_SIZE) - text->w) / 2, dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2}; SDL_BlitSurface(text, NULL, dst, &_r); } ox += SCALE1(BUTTON_SIZE); SDL_FreeSurface(text); } @@ -2074,11 +2065,11 @@ void GFX_blitButton(char *hint, char *button, SDL_Surface *dst, SDL_Rect *dst_re } else { text = TTF_RenderUTF8_Blended(special_case ? font.large : font.tiny, button, ALT_BUTTON_TEXT_COLOR); - GFX_blitPillDark(ASSET_BUTTON, dst, &(SDL_Rect){dst_rect->x, dst_rect->y, SCALE1(BUTTON_SIZE) / 2 + text->w, SCALE1(BUTTON_SIZE)}); + { SDL_Rect _r = {dst_rect->x, dst_rect->y, SCALE1(BUTTON_SIZE) / 2 + text->w, SCALE1(BUTTON_SIZE)}; GFX_blitPillDark(ASSET_BUTTON, dst, &_r); } ox += SCALE1(BUTTON_SIZE) / 4; int oy = special_case ? SCALE1(-2) : 0; - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){ox + dst_rect->x, oy + dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2, text->w, text->h}); + { SDL_Rect _r = {ox + dst_rect->x, oy + dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2, text->w, text->h}; SDL_BlitSurface(text, NULL, dst, &_r); } ox += text->w; ox += SCALE1(BUTTON_SIZE) / 4; SDL_FreeSurface(text); @@ -2090,13 +2081,14 @@ void GFX_blitButton(char *hint, char *button, SDL_Surface *dst, SDL_Rect *dst_re // hint text SDL_Color text_color = uintToColour(THEME_COLOR6_255); text = TTF_RenderUTF8_Blended(font.small, hint, text_color); - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){ox + dst_rect->x, dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2, text->w, text->h}); + { SDL_Rect _r = {ox + dst_rect->x, dst_rect->y + (SCALE1(BUTTON_SIZE) - text->h) / 2, text->w, text->h}; SDL_BlitSurface(text, NULL, dst, &_r); } SDL_FreeSurface(text); } void GFX_blitMessage(TTF_Font *font, char *msg, SDL_Surface *dst, SDL_Rect *dst_rect) { + SDL_Rect _full_rect = {0, 0, dst->w, dst->h}; if (!dst_rect) - dst_rect = &(SDL_Rect){0, 0, dst->w, dst->h}; + dst_rect = &_full_rect; // LOG_info("GFX_blitMessage: %p (%ix%i)", dst, dst_rect->w,dst_rect->h); @@ -2141,7 +2133,7 @@ void GFX_blitMessage(TTF_Font *font, char *msg, SDL_Surface *dst, SDL_Rect *dst_ text = TTF_RenderUTF8_Blended_Wrapped(font, line, uintToColour(CFG_getColor(COLOR_LIST_TEXT)), dst_rect->w); int x = dst_rect->x; x += (dst_rect->w - text->w) / 2; - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){x, y}); + { SDL_Rect _r = {x, y}; SDL_BlitSurface(text, NULL, dst, &_r); } SDL_FreeSurface(text); } y += line_height; @@ -2154,13 +2146,13 @@ void GFX_blitBatteryAtPosition(SDL_Surface *dst, int x, int y) if (SDL_AtomicGet(&pwr.is_charging)) { - GFX_blitAssetColor(ASSET_BATTERY, NULL, dst, &(SDL_Rect){x, y}, THEME_COLOR6); - GFX_blitAssetColor(ASSET_BATTERY_BOLT, NULL, dst, &(SDL_Rect){x + SCALE1(3), y + SCALE1(2)}, THEME_COLOR6); + { SDL_Rect _d = {x, y}; GFX_blitAssetColor(ASSET_BATTERY, NULL, dst, &_d, THEME_COLOR6); } + { SDL_Rect _d = {x + SCALE1(3), y + SCALE1(2)}; GFX_blitAssetColor(ASSET_BATTERY_BOLT, NULL, dst, &_d, THEME_COLOR6); } } else { int percent = SDL_AtomicGet(&pwr.charge); - GFX_blitAssetColor(percent <= 10 ? ASSET_BATTERY_LOW : ASSET_BATTERY, NULL, dst, &(SDL_Rect){x, y}, THEME_COLOR6); + { SDL_Rect _d = {x, y}; GFX_blitAssetColor(percent <= 10 ? ASSET_BATTERY_LOW : ASSET_BATTERY, NULL, dst, &_d, THEME_COLOR6); } if (CFG_getShowBatteryPercent()) { @@ -2183,8 +2175,8 @@ void GFX_blitBatteryAtPosition(SDL_Surface *dst, int x, int y) { clip.x = fill_rect.w - clip.w; clip.y = 0; - GFX_blitAssetColor(percent <= 20 ? ASSET_BATTERY_FILL_LOW : ASSET_BATTERY_FILL, &clip, dst, - &(SDL_Rect){x + SCALE1(3) + clip.x, y + SCALE1(2)}, THEME_COLOR6); + { SDL_Rect _d = {x + SCALE1(3) + clip.x, y + SCALE1(2)}; + GFX_blitAssetColor(percent <= 20 ? ASSET_BATTERY_FILL_LOW : ASSET_BATTERY_FILL, &clip, dst, &_d, THEME_COLOR6); } } } } @@ -2205,8 +2197,8 @@ int GFX_blitHardwareIndicator(SDL_Surface *dst, int x, int y, IndicatorType indi int oy = y; // Draw the pill background - GFX_blitPillLight(ASSET_WHITE_PILL, dst, &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}); - + { SDL_Rect _d = {ox, oy, ow, SCALE1(PILL_SIZE)}; GFX_blitPillLight(ASSET_WHITE_PILL, dst, &_d); } + // Determine which setting to display if (indicator_type == INDICATOR_BRIGHTNESS) { @@ -2241,13 +2233,13 @@ int GFX_blitHardwareIndicator(SDL_Surface *dst, int x, int y, IndicatorType indi GFX_assetRect(asset, &asset_rect); int ax = ox + (SCALE1(PILL_SIZE) - asset_rect.w) / 2; int ay = oy + (SCALE1(PILL_SIZE) - asset_rect.h) / 2; - GFX_blitAssetColor(asset, NULL, dst, &(SDL_Rect){ax, ay}, THEME_COLOR6_255); + { SDL_Rect _d = {ax, ay}; GFX_blitAssetColor(asset, NULL, dst, &_d, THEME_COLOR6_255); } // Draw the progress bar background ox += SCALE1(PILL_SIZE); int bar_y = y + SCALE1((PILL_SIZE - SETTINGS_SIZE) / 2); - GFX_blitPillColor(gfx.mode == MODE_MAIN ? ASSET_BAR_BG : ASSET_BAR_BG_MENU, dst, - &(SDL_Rect){ox, bar_y, SCALE1(SETTINGS_WIDTH), SCALE1(SETTINGS_SIZE)}, THEME_COLOR3, RGB_WHITE); + { SDL_Rect _d = {ox, bar_y, SCALE1(SETTINGS_WIDTH), SCALE1(SETTINGS_SIZE)}; + GFX_blitPillColor(gfx.mode == MODE_MAIN ? ASSET_BAR_BG : ASSET_BAR_BG_MENU, dst, &_d, THEME_COLOR3, RGB_WHITE); } // Draw the lock icon centered over the bar if the setting is read-only if (readonly) @@ -2256,7 +2248,7 @@ int GFX_blitHardwareIndicator(SDL_Surface *dst, int x, int y, IndicatorType indi GFX_assetRect(ASSET_LOCK, &lock_rect); int lx = ox + (SCALE1(SETTINGS_WIDTH) - lock_rect.w) / 2; int ly = bar_y + (SCALE1(SETTINGS_SIZE) - lock_rect.h) / 2; - GFX_blitAssetColor(ASSET_LOCK, NULL, dst, &(SDL_Rect){lx, ly}, THEME_COLOR6_255); + { SDL_Rect _d = {lx, ly}; GFX_blitAssetColor(ASSET_LOCK, NULL, dst, &_d, THEME_COLOR6_255); } } else { // Draw the progress bar fill @@ -2264,7 +2256,7 @@ int GFX_blitHardwareIndicator(SDL_Surface *dst, int x, int y, IndicatorType indi if (indicator_type == 1 || indicator_type == 3 || setting_value > 0) { if(!readonly) - GFX_blitPillDark(ASSET_BAR, dst, &(SDL_Rect){ox, bar_y, SCALE1(SETTINGS_WIDTH) * percent, SCALE1(SETTINGS_SIZE)}); + { SDL_Rect _d = {ox, bar_y, (int)(SCALE1(SETTINGS_WIDTH) * percent), SCALE1(SETTINGS_SIZE)}; GFX_blitPillDark(ASSET_BAR, dst, &_d); } } } @@ -2312,7 +2304,7 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) ox = dst->w - SCALE1(PADDING) - ow; oy = SCALE1(PADDING); - GFX_blitPillColor(ASSET_WHITE_PILL, dst, &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}, THEME_COLOR2, RGB_WHITE); + { SDL_Rect _d = {ox, oy, ow, SCALE1(PILL_SIZE)}; GFX_blitPillColor(ASSET_WHITE_PILL, dst, &_d, THEME_COLOR2, RGB_WHITE); } int battery_x = ox + (SCALE1(PILL_SIZE) - (battery_rect.w + FIXED_SCALE)) / 2; int battery_y = oy + (SCALE1(PILL_SIZE) - battery_rect.h) / 2; @@ -2362,7 +2354,7 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) ox = dst->w - SCALE1(PADDING) - ow; oy = SCALE1(PADDING); - GFX_blitPillColor(ASSET_WHITE_PILL, dst, &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}, THEME_COLOR2, RGB_WHITE); + { SDL_Rect _d = {ox, oy, ow, SCALE1(PILL_SIZE)}; GFX_blitPillColor(ASSET_WHITE_PILL, dst, &_d, THEME_COLOR2, RGB_WHITE); } ox += SCALE1(BUTTON_MARGIN); @@ -2373,7 +2365,7 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) int x = ox; int y = oy + (SCALE1(PILL_SIZE) - bt_rect.h) / 2; - GFX_blitAssetColor(asset, NULL, dst, &(SDL_Rect){x, y}, THEME_COLOR6); + { SDL_Rect _d = {x, y}; GFX_blitAssetColor(asset, NULL, dst, &_d, THEME_COLOR6); } ox += bt_rect.w + SCALE1(BUTTON_MARGIN); } @@ -2387,7 +2379,7 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) int x = ox; int y = oy + (SCALE1(PILL_SIZE) - wifi_rect.h) / 2; - GFX_blitAssetColor(asset, NULL, dst, &(SDL_Rect){x, y}, THEME_COLOR6); + { SDL_Rect _d = {x, y}; GFX_blitAssetColor(asset, NULL, dst, &_d, THEME_COLOR6); } ox += wifi_rect.w + SCALE1(BUTTON_MARGIN); } @@ -2398,7 +2390,7 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) int x = ox; int y = oy + (SCALE1(PILL_SIZE) - external_audio_rect.h) / 2; - GFX_blitAssetColor(asset, NULL, dst, &(SDL_Rect){x, y}, THEME_COLOR6); + { SDL_Rect _d = {x, y}; GFX_blitAssetColor(asset, NULL, dst, &_d, THEME_COLOR6); } ox += external_audio_rect.w + SCALE1(BUTTON_MARGIN); } @@ -2412,7 +2404,7 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) { int x = ox; int y = oy + (SCALE1(PILL_SIZE) - clock->h) / 2; - SDL_BlitSurface(clock, NULL, dst, &(SDL_Rect){x, y}); + { SDL_Rect _r = {x, y}; SDL_BlitSurface(clock, NULL, dst, &_r); } SDL_FreeSurface(clock); } } @@ -2423,12 +2415,20 @@ int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting) void GFX_blitHardwareHints(SDL_Surface *dst, int show_setting) { - if (show_setting == 1) - GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, "BRIGHTNESS", NULL}, 0, dst, 0); - else if (show_setting == 3) - GFX_blitButtonGroup((char *[]){BRIGHTNESS_BUTTON_LABEL, "COLOR TEMP", NULL}, 0, dst, 0); - else - GFX_blitButtonGroup((char *[]){"MNU", "BRGHT", "SEL", "CLTMP", NULL}, 0, dst, 0); + // Named local arrays: g++ 8.3 miscompiles a (char*[]){...} compound literal + // passed straight to a function. + if (show_setting == 1) { + const char* _h[] = {BRIGHTNESS_BUTTON_LABEL, "BRIGHTNESS", NULL}; + GFX_blitButtonGroup((char**)_h, 0, dst, 0); + } + else if (show_setting == 3) { + const char* _h[] = {BRIGHTNESS_BUTTON_LABEL, "COLOR TEMP", NULL}; + GFX_blitButtonGroup((char**)_h, 0, dst, 0); + } + else { + const char* _h[] = {"MNU", "BRGHT", "SEL", "CLTMP", NULL}; + GFX_blitButtonGroup((char**)_h, 0, dst, 0); + } } int GFX_blitButtonGroup(char **pairs, int primary, SDL_Surface *dst, int align_right) @@ -2471,13 +2471,13 @@ int GFX_blitButtonGroup(char **pairs, int primary, SDL_Surface *dst, int align_r ow += SCALE1(BUTTON_MARGIN); if (align_right) ox -= ow; - GFX_blitPillColor(ASSET_WHITE_PILL, dst, &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)}, THEME_COLOR2, RGB_WHITE); + { SDL_Rect _d = {ox, oy, ow, SCALE1(PILL_SIZE)}; GFX_blitPillColor(ASSET_WHITE_PILL, dst, &_d, THEME_COLOR2, RGB_WHITE); } ox += SCALE1(BUTTON_MARGIN); oy += SCALE1(BUTTON_MARGIN); for (int i = 0; i < h; i++) { - GFX_blitButton(hints[i].hint, hints[i].button, dst, &(SDL_Rect){ox, oy}); + { SDL_Rect _r = {ox, oy}; GFX_blitButton(hints[i].hint, hints[i].button, dst, &_r); } ox += hints[i].ow + SCALE1(BUTTON_MARGIN); } return ow; @@ -2549,8 +2549,9 @@ void GFX_sizeText(TTF_Font *font, const char *str, int leading, int *w, int *h) } void GFX_blitText(TTF_Font *font, const char *str, int leading, SDL_Color color, SDL_Surface *dst, SDL_Rect *dst_rect) { + SDL_Rect _full_rect = {0, 0, dst->w, dst->h}; if (dst_rect == NULL) - dst_rect = &(SDL_Rect){0, 0, dst->w, dst->h}; + dst_rect = &_full_rect; const char *lines[MAX_TEXT_LINES]; int count = 0; @@ -2587,7 +2588,7 @@ void GFX_blitText(TTF_Font *font, const char *str, int leading, SDL_Color color, if (len) { text = TTF_RenderUTF8_Blended(font, line, color); - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){x + ((dst_rect->w - text->w) / 2), y + (i * leading)}); + { SDL_Rect _r = {x + ((dst_rect->w - text->w) / 2), y + (i * leading)}; SDL_BlitSurface(text, NULL, dst, &_r); } SDL_FreeSurface(text); } } @@ -2736,8 +2737,8 @@ ResampledFrames resample_audio(const SND_Frame *input_frames, .data_out = output_buffer, .input_frames = input_frame_count, .output_frames = max_output_frames, - .src_ratio = final_ratio, - .end_of_input = 0}; + .end_of_input = 0, + .src_ratio = final_ratio}; if (src_process(src_state, &src_data) != 0) { @@ -3222,7 +3223,7 @@ FALLBACK_IMPLEMENTATION void PLAT_overrideMute(int mute) {} /////////////////////////////// -LID_Context lid = { +static LID_Context lid = { .has_lid = 0, .is_open = 1, }; @@ -3232,7 +3233,8 @@ FALLBACK_IMPLEMENTATION int PLAT_lidChanged(int *state) { return 0; } /////////////////////////////// -PAD_Context pad; +static PAD_Context pad; +PAD_Context* PAD_getContext(void) { return &pad; } #define AXIS_DEADZONE 0x4000 void PAD_setAnalog(int neg_id, int pos_id, int value, int repeat_at) @@ -4230,7 +4232,7 @@ void PWR_powerOff(int reboot) PLAT_clearLayers(0); PLAT_clearAll(); - GFX_blitMessage(font.large, msg, gfx.screen, &(SDL_Rect){0, 0, gfx.screen->w, gfx.screen->h}); //, NULL); + { SDL_Rect _r = {0, 0, gfx.screen->w, gfx.screen->h}; GFX_blitMessage(font.large, msg, gfx.screen, &_r); } //, NULL); GFX_flip(gfx.screen); system("killall -TERM keymon.elf"); @@ -4627,7 +4629,7 @@ bool LEDS_pushProfileOverride(int profile) LOG_debug("LED_profile stack is full, ignoring.\n"); return false; } - profile_override[++profile_override_top] = profile; + profile_override[++profile_override_top] = (LightProfile)profile; LEDS_applyRules(); return true; diff --git a/workspace/all/common/api.h b/workspace/all/common/api.h index 828af8271..6372c6324 100644 --- a/workspace/all/common/api.h +++ b/workspace/all/common/api.h @@ -6,6 +6,10 @@ #include "config.h" #include +#ifdef __cplusplus +extern "C" { +#endif + /////////////////////////////// enum { @@ -103,16 +107,21 @@ typedef struct { extern PerfProfile perf; -// TODO: do we need that many free externs? This should move -// to a structure or something. -extern int currentshaderpass; -extern int currentshadersrcw; -extern int currentshadersrch; -extern int currentshaderdstw; -extern int currentshaderdsth; -extern int currentshadertexw; -extern int currentshadertexh; -extern int should_rotate; +// Current render-pass state, shared between the core renderer (api.c), +// the platform GLES pipeline (generic_video.c) and minarch's video path +// (ma_video.c). Was eight loose `extern int`s; grouped into one struct so +// there's a single well-named extern instead of the soup this used to be. +typedef struct GFX_RenderState { + int pass; // was currentshaderpass + int src_w; // currentshadersrcw + int src_h; // currentshadersrch + int dst_w; // currentshaderdstw + int dst_h; // currentshaderdsth + int tex_w; // currentshadertexw + int tex_h; // currentshadertexh + int should_rotate; // was should_rotate +} GFX_RenderState; +extern GFX_RenderState gfx_render; enum { ASSET_WHITE_PILL, ASSET_BLACK_PILL, @@ -222,7 +231,9 @@ typedef struct GFX_Fonts { TTF_Font* tiny; // multi char button label TTF_Font* micro; // icon overlay text } GFX_Fonts; -extern GFX_Fonts font; +// The loaded fonts live inside api.c; reach them through this accessor rather +// than a bare extern global. +GFX_Fonts* GFX_getFonts(void); enum { SHARPNESS_SHARP, @@ -489,7 +500,6 @@ typedef struct LID_Context { int has_lid; int is_open; } LID_Context; -extern LID_Context lid; void PLAT_initLid(void); int PLAT_lidChanged(int* state); @@ -513,7 +523,9 @@ typedef struct PAD_Context { PAD_Axis laxis; PAD_Axis raxis; } PAD_Context; -extern PAD_Context pad; +// The pad state lives inside api.c; reach it through this accessor rather than +// a bare extern global. Most consumers should use the PAD_* facade instead. +PAD_Context* PAD_getContext(void); #define PAD_REPEAT_DELAY 300 #define PAD_REPEAT_INTERVAL 100 @@ -966,4 +978,8 @@ void PLAT_bluetoothSetVolume(int vol); #define BT_getVolume PLAT_bluetoothVolume #define BT_setVolume PLAT_bluetoothSetVolume +#ifdef __cplusplus +} +#endif + #endif diff --git a/workspace/all/common/config.c b/workspace/all/common/config.cpp similarity index 98% rename from workspace/all/common/config.c rename to workspace/all/common/config.cpp index e1ec3ba7b..b82cb27a8 100644 --- a/workspace/all/common/config.c +++ b/workspace/all/common/config.cpp @@ -1,12 +1,17 @@ -#include "config.h" #include #include #include -#include #include #include + +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit's own exports keep C linkage for the still-C platform.c and any C +// consumers. config.h / utils.h carry their own guards. +extern "C" { +#include "config.h" #include "defines.h" #include "utils.h" +} NextUISettings settings = {0}; @@ -31,7 +36,6 @@ void CFG_defaults(NextUISettings *cfg) return; NextUISettings defaults = { - .fontFile = CFG_DEFAULT_FONT_FILE, .fontStyle = CFG_DEFAULT_FONT_STYLE, .color1_255 = CFG_DEFAULT_COLOR1, .color2_255 = CFG_DEFAULT_COLOR2, @@ -41,10 +45,10 @@ void CFG_defaults(NextUISettings *cfg) .color6_255 = CFG_DEFAULT_COLOR6, .color7_255 = CFG_DEFAULT_COLOR7, .thumbRadius = CFG_DEFAULT_THUMBRADIUS, + .gameSwitcherScaling = CFG_DEFAULT_GAMESWITCHERSCALING, + .gameSwitcherCurtain = CFG_DEFAULT_GAMESWITCHER_CURTAIN, .gameArtWidth = CFG_DEFAULT_GAMEARTWIDTH, - .showFolderNamesAtRoot = CFG_DEFAULT_SHOWFOLDERNAMESATROOT, .inputPromptStyle = CFG_DEFAULT_INPUT_PROMPT_STYLE, - .paletteName = CFG_DEFAULT_PALETTE_NAME, .customColors = {CFG_DEFAULT_COLOR1, CFG_DEFAULT_COLOR2, CFG_DEFAULT_COLOR3, CFG_DEFAULT_COLOR4, CFG_DEFAULT_COLOR5, CFG_DEFAULT_COLOR6, CFG_DEFAULT_COLOR7}, @@ -58,10 +62,10 @@ void CFG_defaults(NextUISettings *cfg) .showTools = CFG_DEFAULT_SHOWTOOLS, .showCollections = CFG_DEFAULT_SHOWCOLLECTIONS, .showGameArt = CFG_DEFAULT_SHOWGAMEART, - .gameSwitcherScaling = CFG_DEFAULT_GAMESWITCHERSCALING, - .defaultView = CFG_DEFAULT_VIEW, + .showFolderNamesAtRoot = CFG_DEFAULT_SHOWFOLDERNAMESATROOT, + .romsUseFolderBackground = CFG_DEFAULT_ROMSUSEFOLDERBACKGROUND, .showQuickSwitcherUi = CFG_DEFAULT_SHOWQUICKWITCHERUI, - .gameSwitcherCurtain = CFG_DEFAULT_GAMESWITCHER_CURTAIN, + .defaultView = CFG_DEFAULT_VIEW, .muteLeds = CFG_DEFAULT_MUTELEDS, @@ -72,12 +76,12 @@ void CFG_defaults(NextUISettings *cfg) .powerOffProtection = CFG_DEFAULT_POWEROFFPROTECTION, .keepAwakeWhenUSB = CFG_DEFAULT_KEEPAWAKEWHENUSB, - .haptics = CFG_DEFAULT_HAPTICS, - .romsUseFolderBackground = CFG_DEFAULT_ROMSUSEFOLDERBACKGROUND, .saveFormat = CFG_DEFAULT_SAVEFORMAT, .stateFormat = CFG_DEFAULT_STATEFORMAT, .useExtractedFileName = CFG_DEFAULT_EXTRACTEDFILENAME, + .haptics = CFG_DEFAULT_HAPTICS, + .ntp = CFG_DEFAULT_NTP, .currentTimezone = CFG_DEFAULT_TIMEZONE, .wifi = CFG_DEFAULT_WIFI, @@ -93,11 +97,7 @@ void CFG_defaults(NextUISettings *cfg) .notifyDuration = CFG_DEFAULT_NOTIFY_DURATION, .raEnable = CFG_DEFAULT_RA_ENABLE, - .raUsername = CFG_DEFAULT_RA_USERNAME, - .raPassword = CFG_DEFAULT_RA_PASSWORD, .raHardcoreMode = CFG_DEFAULT_RA_HARDCOREMODE, - .raToken = CFG_DEFAULT_RA_TOKEN, - .raServerUsername = CFG_DEFAULT_RA_SERVER_USERNAME, .raAuthenticated = CFG_DEFAULT_RA_AUTHENTICATED, .raShowNotifications = CFG_DEFAULT_RA_SHOW_NOTIFICATIONS, .raNotificationDuration = CFG_DEFAULT_RA_NOTIFICATION_DURATION, @@ -105,6 +105,15 @@ void CFG_defaults(NextUISettings *cfg) .raAchievementSortOrder = CFG_DEFAULT_RA_ACHIEVEMENT_SORT_ORDER, }; + // g++ 8.3 (gnu++2a) rejects designated initializers for char-array members, + // so set the string fields here instead of in the aggregate above. + strcpy(defaults.fontFile, CFG_DEFAULT_FONT_FILE); + strcpy(defaults.paletteName, CFG_DEFAULT_PALETTE_NAME); + strcpy(defaults.raUsername, CFG_DEFAULT_RA_USERNAME); + strcpy(defaults.raPassword, CFG_DEFAULT_RA_PASSWORD); + strcpy(defaults.raToken, CFG_DEFAULT_RA_TOKEN); + strcpy(defaults.raServerUsername, CFG_DEFAULT_RA_SERVER_USERNAME); + *cfg = defaults; } diff --git a/workspace/all/common/config.h b/workspace/all/common/config.h index cdd577866..40443732c 100644 --- a/workspace/all/common/config.h +++ b/workspace/all/common/config.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + // portability, deprecated extern uint32_t THEME_COLOR1_255; extern uint32_t THEME_COLOR2_255; @@ -479,4 +483,8 @@ void CFG_setRAAchievementSortOrder(int sortOrder); void CFG_sync(void); void CFG_quit(void); +#ifdef __cplusplus +} +#endif + #endif diff --git a/workspace/all/common/core/str.h b/workspace/all/common/core/str.h new file mode 100644 index 000000000..dc43a4648 --- /dev/null +++ b/workspace/all/common/core/str.h @@ -0,0 +1,58 @@ +// core/str.h — type-safe bounded string helpers (C++, header-only). +// +// The point of these over raw snprintf/strcpy: the destination is taken by +// reference to a fixed-size char array, so its size N is deduced at compile +// time. You physically cannot call these on a decayed char* pointer — that's a +// compile error, not a silent 8-byte "size" like snprintf(ptr, sizeof(ptr), …) +// would give you. So they can't overflow and can't be mis-sized. +// +// For the cases where the destination genuinely is a pointer with a runtime +// length (a function param, or a cursor into a larger buffer), keep using +// snprintf directly with the real remaining size — these helpers are for the +// common "write into a local char buf[N]" case. +// +// Usable from any C++ translation unit (nextui, settings, and minarch once its +// modules are C++). Include as: #include "core/str.h" + +#ifndef NEXTUI_CORE_STR_H +#define NEXTUI_CORE_STR_H + +#include +#include +#include + +namespace core { + +// Bounded copy of src into dst[N]. Truncates rather than overflowing. A null +// src yields an empty string. Returns true if the whole source fit. +template +inline bool copy(char (&dst)[N], const char* src) { + int n = snprintf(dst, N, "%s", src ? src : ""); + return n >= 0 && static_cast(n) < N; +} + +// Bounded printf-style format into dst[N]. Truncates rather than overflowing. +// Returns true if the whole formatted string fit. +template +inline bool format(char (&dst)[N], const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(dst, N, fmt, ap); + va_end(ap); + return n >= 0 && static_cast(n) < N; +} + +// Bounded append of src onto the string already in dst[N] (like strlcat). +// Truncates rather than overflowing. Returns true if the whole source fit. +template +inline bool append(char (&dst)[N], const char* src) { + size_t len = 0; + while (len < N && dst[len] != '\0') len++; + if (len >= N) return false; // not null-terminated within the buffer + int n = snprintf(dst + len, N - len, "%s", src ? src : ""); + return n >= 0 && static_cast(len) + static_cast(n) < N; +} + +} // namespace core + +#endif // NEXTUI_CORE_STR_H diff --git a/workspace/all/common/core/surface.h b/workspace/all/common/core/surface.h new file mode 100644 index 000000000..7d4d7a0d3 --- /dev/null +++ b/workspace/all/common/core/surface.h @@ -0,0 +1,52 @@ +// core/surface.h — RAII owner for SDL_Surface (C++, header-only). +// +// SDL_Surface is a C resource freed with SDL_FreeSurface(). Scattering manual +// SDL_FreeSurface() calls through the launcher is exactly the ownership +// bookkeeping that leaks on an early return and double-frees when two variables +// alias the same surface — the class of bug Phase A's audit kept turning up. +// +// core::SurfacePtr is a std::unique_ptr specialization that frees the surface +// when it goes out of scope, moves ownership explicitly, and (via .get()) +// hands a raw SDL_Surface* to the many C APIs — SDL_BlitSurface, the GFX_* +// helpers, IMG/TTF calls — that still take one. +// +// Usage: +// core::SurfacePtr img{IMG_Load(path)}; // adopt a freshly-made surface +// if (!img) return; // NULL-safe, like the raw ptr +// SDL_BlitSurface(img.get(), NULL, dst, &r); // hand the raw ptr to C APIs +// thumb = std::move(img); // transfer ownership, no double free +// // freed automatically at scope exit +// +// Ownership mirrors unique_ptr: exactly one owner, copy deleted, move transfers. +// Use .release() to hand ownership to a C API that will free it itself, and +// .reset(p) to replace the managed surface (frees the old one first). +// +// This header forward-declares SDL_Surface / SDL_FreeSurface so core/ stays +// clear of the SDL1-vs-SDL2 include tangle (see common/sdl.h); every real use +// site already includes SDL through api.h, which supplies the complete type the +// deleter needs at the point of destruction. +// +// Usable from any C++ translation unit. Include as: #include "core/surface.h" + +#ifndef NEXTUI_CORE_SURFACE_H +#define NEXTUI_CORE_SURFACE_H + +#include + +struct SDL_Surface; +extern "C" void SDL_FreeSurface(SDL_Surface* surface); + +namespace core { + +// Deleter that returns an owned SDL_Surface to SDL. unique_ptr only invokes it +// on a non-null pointer, so no null guard is needed here. +struct SurfaceDeleter { + void operator()(SDL_Surface* s) const { SDL_FreeSurface(s); } +}; + +// Move-only owning handle for an SDL_Surface. Frees on destruction. +using SurfacePtr = std::unique_ptr; + +} // namespace core + +#endif // NEXTUI_CORE_SURFACE_H diff --git a/workspace/all/common/core/tests/str_test.cpp b/workspace/all/common/core/tests/str_test.cpp new file mode 100644 index 000000000..55587d594 --- /dev/null +++ b/workspace/all/common/core/tests/str_test.cpp @@ -0,0 +1,64 @@ +// Desktop self-test for core/str.h. Pure, no dependencies — compile and run: +// g++ -std=c++17 -I../../ str_test.cpp -o str_test && ./str_test +// Exits non-zero on the first failed check. + +#include "core/str.h" +#include +#include + +static int failures = 0; +#define CHECK(cond) do { \ + if (!(cond)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); failures++; } \ +} while (0) + +int main() { + // copy: normal + { + char b[16]; + CHECK(core::copy(b, "hello")); + CHECK(strcmp(b, "hello") == 0); + } + // copy: exact fit (15 chars + NUL in 16) + { + char b[16]; + CHECK(core::copy(b, "123456789012345")); + CHECK(strlen(b) == 15); + } + // copy: truncation returns false, stays NUL-terminated, no overflow + { + char b[8] = {}; + CHECK(!core::copy(b, "this is far too long")); + CHECK(strcmp(b, "this is") == 0); + CHECK(b[7] == '\0'); + } + // copy: null source -> empty + { + char b[8]; b[0] = 'x'; + CHECK(core::copy(b, nullptr)); + CHECK(b[0] == '\0'); + } + // format: normal + truncation + { + char b[16]; + CHECK(core::format(b, "%s-%d", "id", 42)); + CHECK(strcmp(b, "id-42") == 0); + char s[6]; + CHECK(!core::format(s, "%s", "toolong")); + CHECK(strcmp(s, "toolo") == 0); + } + // append: onto existing + truncation + { + char b[16]; + core::copy(b, "foo"); + CHECK(core::append(b, "bar")); + CHECK(strcmp(b, "foobar") == 0); + char s[8]; + core::copy(s, "abcd"); + CHECK(!core::append(s, "efghij")); + CHECK(strcmp(s, "abcdefg") == 0); + CHECK(s[7] == '\0'); + } + + if (failures == 0) printf("core/str.h: all checks passed\n"); + return failures == 0 ? 0 : 1; +} diff --git a/workspace/all/common/core/tests/surface_test.cpp b/workspace/all/common/core/tests/surface_test.cpp new file mode 100644 index 000000000..5d0065772 --- /dev/null +++ b/workspace/all/common/core/tests/surface_test.cpp @@ -0,0 +1,95 @@ +// Desktop self-test for core/surface.h. Standalone — no SDL link needed: +// g++ -std=c++17 -I../../ surface_test.cpp -o surface_test && ./surface_test +// Exits non-zero on the first failed check. +// +// We supply our own complete SDL_Surface plus a counting SDL_FreeSurface stub +// so the RAII semantics (single free, move transfer, reset, release) can be +// checked without pulling in real SDL. + +struct SDL_Surface { int id; }; + +static int free_calls = 0; +extern "C" void SDL_FreeSurface(SDL_Surface* s) { + if (s) { free_calls++; delete s; } +} + +#include "core/surface.h" + +#include +#include + +static int failures = 0; +#define CHECK(cond) do { \ + if (!(cond)) { printf("FAIL %s:%d %s\n", __FILE__, __LINE__, #cond); failures++; } \ +} while (0) + +static SDL_Surface* make() { return new SDL_Surface{0}; } + +int main() { + // scope exit frees exactly once + { + free_calls = 0; + { core::SurfacePtr s{make()}; CHECK(free_calls == 0); } + CHECK(free_calls == 1); + } + // get() exposes the raw pointer without giving up ownership + { + free_calls = 0; + SDL_Surface* raw = make(); + core::SurfacePtr s{raw}; + CHECK(s.get() == raw); + CHECK(static_cast(s)); + CHECK(free_calls == 0); + } // freed here + CHECK(free_calls == 1); + // move transfers ownership — source empties, no double free + { + free_calls = 0; + core::SurfacePtr a{make()}; + core::SurfacePtr b{std::move(a)}; + CHECK(a.get() == nullptr); + CHECK(!a); + CHECK(b.get() != nullptr); + CHECK(free_calls == 0); + } // only b frees + CHECK(free_calls == 1); + // move-assignment frees the old target before taking the new surface + { + free_calls = 0; + core::SurfacePtr a{make()}; + core::SurfacePtr b{make()}; + b = std::move(a); // b's original surface freed here + CHECK(free_calls == 1); + } // b (now holding a's surface) frees + CHECK(free_calls == 2); + // reset(p) frees the old surface and adopts the new one + { + free_calls = 0; + core::SurfacePtr s{make()}; + s.reset(make()); + CHECK(free_calls == 1); + s.reset(); // explicit empty + CHECK(free_calls == 2); + CHECK(!s); + } + CHECK(free_calls == 2); // already empty, nothing more freed at scope exit + // release() yields the pointer without freeing; caller owns it now + { + free_calls = 0; + core::SurfacePtr s{make()}; + SDL_Surface* raw = s.release(); + CHECK(!s); + CHECK(free_calls == 0); + SDL_FreeSurface(raw); // manual free + CHECK(free_calls == 1); + } + // a default (null) handle frees nothing + { + free_calls = 0; + { core::SurfacePtr s; CHECK(!s); } + CHECK(free_calls == 0); + } + + if (failures == 0) printf("core/surface.h: all checks passed\n"); + return failures == 0 ? 0 : 1; +} diff --git a/workspace/all/common/core/thread_safe_queue.h b/workspace/all/common/core/thread_safe_queue.h new file mode 100644 index 000000000..286ebe1e2 --- /dev/null +++ b/workspace/all/common/core/thread_safe_queue.h @@ -0,0 +1,66 @@ +// core/thread_safe_queue.h -- a small bounded producer/consumer queue (C++, +// header-only). +// +// Built for the image loaders: the main (producer) thread pushes load requests, +// a worker (consumer) thread pops them, blocking when there's nothing to do. +// When a capacity is set and the queue is full, push() drops the OLDEST item so +// the newest wins -- exactly right for backgrounds/thumbnails, where a request +// for a folder the user has already navigated away from is worthless. +// +// This replaces the hand-rolled SDL_mutex/SDL_cond linked-list queues whose +// late size-counter decrement and cross-thread task ownership caused the +// memory corruption that the POC had to disable. Correctness lives in one small +// place now instead of being re-implemented per queue. + +#ifndef NEXTUI_CORE_THREAD_SAFE_QUEUE_H +#define NEXTUI_CORE_THREAD_SAFE_QUEUE_H + +#include +#include +#include +#include +#include + +namespace core { + +template +class ThreadSafeQueue { +public: + // capacity == 0 means unbounded. + explicit ThreadSafeQueue(size_t capacity = 0) : capacity_(capacity) {} + + void push(T item) { + std::lock_guard lock(mutex_); + if (capacity_ && queue_.size() >= capacity_) queue_.pop(); // drop oldest + queue_.push(std::move(item)); + cond_.notify_one(); + } + + // Blocks until an item is available. Returns false once shutdown() has been + // called and the queue has drained -- the signal for a consumer to exit. + bool pop(T& out) { + std::unique_lock lock(mutex_); + cond_.wait(lock, [this] { return !queue_.empty() || shutdown_; }); + if (queue_.empty()) return false; + out = std::move(queue_.front()); + queue_.pop(); + return true; + } + + void shutdown() { + std::lock_guard lock(mutex_); + shutdown_ = true; + cond_.notify_all(); + } + +private: + std::queue queue_; + std::mutex mutex_; + std::condition_variable cond_; + size_t capacity_; + bool shutdown_ = false; +}; + +} // namespace core + +#endif // NEXTUI_CORE_THREAD_SAFE_QUEUE_H diff --git a/workspace/all/common/device.h b/workspace/all/common/device.h new file mode 100644 index 000000000..20ade265a --- /dev/null +++ b/workspace/all/common/device.h @@ -0,0 +1,38 @@ +#ifndef DEVICE_H +#define DEVICE_H + +// A device model is described by data, not code: the fields below are the +// only things that differ between hardware variants. Each platform defines +// one or more descriptors and points deviceModel at the active one (see +// resolveDeviceModel in the platform layer). Adding a device becomes a +// matter of adding a descriptor rather than another runtime branch. +typedef struct DeviceDescriptor { + int scale; // FIXED_SCALE + int width; // FIXED_WIDTH + int height; // FIXED_HEIGHT + int main_row_count; // MAIN_ROW_COUNT + int quick_switcher_count; // QUICK_SWITCHER_COUNT + int padding; // PADDING + int joy_l3; // JOY_L3 + int joy_r3; // JOY_R3 + int joy_l4; // JOY_L4 (Brick Pro rear triggers) + int joy_r4; // JOY_R4 (Brick Pro rear triggers) + int joy_menu_alt; // JOY_MENU_ALT (Brick Pro) + int joy_plus; // JOY_PLUS + int joy_minus; // JOY_MINUS + // sysfs hardware nodes that differ across platforms -- the seams a future + // device redefines. Identical for Brick and Smart Pro; tg5050, for example, + // uses cpu4 not cpu0, thermal_zone5 not zone2, gpio236 not gpio227. + const char* cpu_speed_path; + const char* gpu_temp_path; + const char* rumble_gpio_path; + // GPU frequency/utilization: some devices read a sysfs node, others report + // a fixed clock. gpu_freq_path NULL => PLAT_getGPUSpeed uses gpu_speed_fixed; + // gpu_usage_path NULL => GPU utilization is unavailable on this device. + int gpu_speed_fixed; + const char* gpu_freq_path; + const char* gpu_usage_path; +} DeviceDescriptor; +extern const DeviceDescriptor* deviceModel; + +#endif // DEVICE_H diff --git a/workspace/all/common/displaycal.c b/workspace/all/common/displaycal.cpp similarity index 100% rename from workspace/all/common/displaycal.c rename to workspace/all/common/displaycal.cpp diff --git a/workspace/all/common/generic_platform.c b/workspace/all/common/generic_platform.c new file mode 100644 index 000000000..4daa8ce68 --- /dev/null +++ b/workspace/all/common/generic_platform.c @@ -0,0 +1,399 @@ +// generic_platform.c -- PLAT_ functions identical across tg5040 and tg5050. +// Text-included near the end of each platform's platform.c (after the file-local +// static helpers and globals it references). Phase C device-HAL dedup: divergent +// functions (LEDs, timezones, NTP, rumble, model, input init) stay per-platform; +// a new device only reimplements those. + +void PLAT_quitInput(void) { + if (joysticks) { + for (int i = 0; i < num_joysticks; i++) { + if (SDL_JoystickGetAttached(joysticks[i])) { + LOG_info("Closing joystick %d: %s\n", i, SDL_JoystickName(joysticks[i])); + SDL_JoystickClose(joysticks[i]); + } + } + free(joysticks); + joysticks = NULL; + num_joysticks = 0; + } + SDL_QuitSubSystem(SDL_INIT_JOYSTICK); +} + +void PLAT_updateInput(const SDL_Event *event) { + switch (event->type) { + case SDL_JOYDEVICEADDED: { + int device_index = event->jdevice.which; + SDL_Joystick *new_joy = SDL_JoystickOpen(device_index); + if (new_joy) { + joysticks = (SDL_Joystick **)realloc(joysticks, sizeof(SDL_Joystick *) * (num_joysticks + 1)); + joysticks[num_joysticks++] = new_joy; + LOG_info("Joystick added at index %d: %s\n", device_index, SDL_JoystickName(new_joy)); + } else { + LOG_error("Failed to open added joystick at index %d: %s\n", device_index, SDL_GetError()); + } + break; + } + + case SDL_JOYDEVICEREMOVED: { + SDL_JoystickID removed_id = event->jdevice.which; + for (int i = 0; i < num_joysticks; ++i) { + if (SDL_JoystickInstanceID(joysticks[i]) == removed_id) { + LOG_info("Joystick removed: %s\n", SDL_JoystickName(joysticks[i])); + SDL_JoystickClose(joysticks[i]); + + // Shift down the remaining entries + for (int j = i; j < num_joysticks - 1; ++j) + joysticks[j] = joysticks[j + 1]; + num_joysticks--; + + if (num_joysticks == 0) { + free(joysticks); + joysticks = NULL; + } else { + joysticks = (SDL_Joystick **)realloc(joysticks, sizeof(SDL_Joystick *) * num_joysticks); + } + break; + } + } + break; + } + + default: + break; + } +} + +void PLAT_getBatteryStatus(int* is_charging, int* charge) { + PLAT_getBatteryStatusFine(is_charging, charge); + + // worry less about battery and more about the game you're playing + if (*charge>80) *charge = 100; + else if (*charge>60) *charge = 80; + else if (*charge>40) *charge = 60; + else if (*charge>20) *charge = 40; + else if (*charge>10) *charge = 20; + else *charge = 10; +} + +void PLAT_getCPUTemp() { + perf.cpu_temp = getInt("/sys/devices/virtual/thermal/thermal_zone0/temp")/1000; +} + +void PLAT_getCPUSpeed() +{ + perf.cpu_speed = getInt(deviceModel->cpu_speed_path)/1000; +} + +void PLAT_getGPUTemp() { + perf.gpu_temp = getInt(deviceModel->gpu_temp_path)/1000; +} + +void PLAT_getGPUSpeed() { + perf.gpu_speed = deviceModel->gpu_freq_path ? getInt(deviceModel->gpu_freq_path)/1000000 : deviceModel->gpu_speed_fixed; // MHz +} + +void PLAT_getNetworkStatus(int* is_online) +{ + if(WIFI_enabled()) + WIFI_connectionInfo(&connection); + else + connection_reset(&connection); + + if(is_online) + *is_online = (connection.valid && connection.ssid[0] != '\0'); + + if(BT_enabled()) { + bluetoothConnected = PLAT_bluetoothConnected(); + } + else + bluetoothConnected = false; +} + +void PLAT_getBatteryStatusFine(int *is_charging, int *charge) +{ + if(is_charging) { + int time_to_full = getInt("/sys/class/power_supply/axp2202-battery/time_to_full_now"); + int charger_present = getInt("/sys/class/power_supply/axp2202-usb/online"); + *is_charging = (charger_present == 1) && (time_to_full > 0); + } + if(charge) { + *charge = getInt("/sys/class/power_supply/axp2202-battery/capacity"); + } +} + +void PLAT_powerOff(int reboot) { + if (CFG_getHaptics()) { + VIB_singlePulse(VIB_bootStrength, VIB_bootDuration_ms); + } + system("rm -f /tmp/nextui_exec && sync"); + sleep(2); + + SetRawVolume(MUTE_VOLUME_RAW); + PLAT_enableBacklight(0); + SND_quit(); + VIB_quit(); + PWR_quit(); + GFX_quit(); + + system("cat /dev/zero > /dev/fb0 2>/dev/null"); + if(reboot > 0) + touch("/tmp/reboot"); + else + touch("/tmp/poweroff"); + sync(); + exit(0); +} + +int PLAT_supportsDeepSleep(void) { return 1; } + +void *PLAT_cpu_monitor(void *arg) { + if (!Perf_tryBeginCPUMonitor()) return NULL; + + double prev_real_time = get_time_sec(); + double prev_cpu_time = get_process_cpu_time_sec(); + + double cpu_usage_history[ROLLING_WINDOW] = {0}; + int history_index = 0; + int history_count = 0; + + while (Perf_isCPUMonitorEnabled()) { + double curr_real_time = get_time_sec(); + double curr_cpu_time = get_process_cpu_time_sec(); + + double elapsed_real_time = curr_real_time - prev_real_time; + double elapsed_cpu_time = curr_cpu_time - prev_cpu_time; + + if (elapsed_real_time > 0) { + double cpu_usage = (elapsed_cpu_time / elapsed_real_time) * 100.0; + + pthread_mutex_lock(¤tcpuinfo); + + cpu_usage_history[history_index] = cpu_usage; + history_index = (history_index + 1) % ROLLING_WINDOW; + if (history_count < ROLLING_WINDOW) history_count++; + + double sum_cpu_usage = 0; + for (int i = 0; i < history_count; i++) sum_cpu_usage += cpu_usage_history[i]; + perf.cpu_usage = sum_cpu_usage / history_count; + + pthread_mutex_unlock(¤tcpuinfo); + } + + prev_real_time = curr_real_time; + prev_cpu_time = curr_cpu_time; + usleep(100000); + } + + Perf_endCPUMonitor(); + return NULL; +} + +void PLAT_setCPUSpeed(int speed) { + const char* mode; + switch (speed) { + case CPU_SPEED_AUTO: mode = "auto"; break; + case CPU_SPEED_PERFORMANCE: mode = "performance"; break; + case CPU_SPEED_POWERSAVE: mode = "powersave"; break; + default: return; + } + + const char* system_path = getenv("SYSTEM_PATH"); + if (!system_path) { + LOG_info("WARNING: SYSTEM_PATH not set, cannot run governor script\n"); + return; + } + char cmd[512]; + int n = snprintf(cmd, sizeof(cmd), "sh \"%s/bin/governor.sh\" \"%s\"", system_path, mode); + if (n < 0 || n >= (int)sizeof(cmd)) { + LOG_info("WARNING: SYSTEM_PATH too long for governor script path\n"); + return; + } + int ret = system(cmd); + if (ret != 0) LOG_info("WARNING: governor script exited with status %d for mode '%s'\n", ret, mode); +} + +int PLAT_pickSampleRate(int requested, int max) { + // bluetooth: allow limiting the maximum to improve compatibility + if(PLAT_bluetoothConnected()) + return MIN(requested, CFG_getBluetoothSamplingrateLimit()); + + return MIN(requested, max); +} + +void PLAT_overrideMute(int mute) { + putInt("/sys/class/speaker/mute", mute); +} + +void PLAT_getOsVersionInfo(char* output_str, size_t max_len) +{ + return getFile("/etc/version", output_str,max_len); +} + +bool PLAT_btIsConnected(void) +{ + return bluetoothConnected; +} + +ConnectionStrength PLAT_connectionStrength(void) { + if(!WIFI_enabled() || !connection.valid || connection.rssi == -1) + return SIGNAL_STRENGTH_OFF; + else if (connection.rssi == 0) + return SIGNAL_STRENGTH_DISCONNECTED; + else if (connection.rssi >= -60) + return SIGNAL_STRENGTH_HIGH; + else if (connection.rssi >= -70) + return SIGNAL_STRENGTH_MED; + else + return SIGNAL_STRENGTH_LOW; +} + +void PLAT_setLedEffect(LightSettings *led) +{ + char filepath[256]; + FILE *file; + // first set brightness + snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_%s", led->filename); + file = fopen(filepath, "w"); + if (file != NULL) + { + fprintf(file, "%i\n", led->effect); + fclose(file); + } +} + +void PLAT_setLedEffectCycles(LightSettings *led) +{ + char filepath[256]; + FILE *file; + // first set brightness + snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_cycles_%s", led->filename); + file = fopen(filepath, "w"); + if (file != NULL) + { + fprintf(file, "%i\n", led->cycles); + fclose(file); + } +} + +void PLAT_setLedEffectSpeed(LightSettings *led) +{ + char filepath[256]; + FILE *file; + // first set brightness + snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_duration_%s", led->filename); + file = fopen(filepath, "w"); + if (file != NULL) + { + fprintf(file, "%i\n", led->speed); + fclose(file); + } +} + +void PLAT_setLedColor(LightSettings *led) +{ + char filepath[256]; + FILE *file; + // first set brightness + snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_rgb_hex_%s", led->filename); + file = fopen(filepath, "w"); + if (file != NULL) + { + fprintf(file, "%06X\n", led->color1); + fclose(file); + } +} + +bool PLAT_canTurbo(void) { return true; } + +int PLAT_toggleTurbo(int btn_id) +{ + // avoid extra file IO on each call + static int initialized = 0; + if (!initialized) { + mkdir(INPUTD_PATH, 0755); + initialized = 1; + } + + for (int i = 0; turbo_mapping[i].path; i++) { + if (turbo_mapping[i].brn_id == btn_id) { + return toggle_file(turbo_mapping[i].path); + } + } + return 0; +} + +void PLAT_clearTurbo() { + for (int i = 0; turbo_mapping[i].path; i++) { + unlink(turbo_mapping[i].path); + } +} + +int PLAT_setDateTime(int y, int m, int d, int h, int i, int s) { + char cmd[512]; + sprintf(cmd, "date -s '%d-%d-%d %d:%d:%d'; hwclock -u -w", y,m,d,h,i,s); + system(cmd); + return 0; // why does this return an int? +} + +void PLAT_initTimezones() { + if (cached_tz_count != -1) { // Already initialized + return; + } + + FILE *file = fopen(ZONE_TAB_PATH, "r"); + if (!file) { + LOG_info("Error opening file %s\n", ZONE_TAB_PATH); + return; + } + + char line[MAX_LINE_LENGTH]; + cached_tz_count = 0; + + while (fgets(line, sizeof(line), file)) { + // Skip comment lines + if (line[0] == '#' || strlen(line) < 3) { + continue; + } + + char *token = strtok(line, "\t"); // Skip country code + if (!token) continue; + + token = strtok(NULL, "\t"); // Skip latitude/longitude + if (!token) continue; + + token = strtok(NULL, "\t\n"); // Extract timezone + if (!token) continue; + + // Check for duplicates before adding + int duplicate = 0; + for (int i = 0; i < cached_tz_count; i++) { + if (strcmp(cached_timezones[i], token) == 0) { + duplicate = 1; + break; + } + } + + if (!duplicate && cached_tz_count < MAX_TIMEZONES) { + strncpy(cached_timezones[cached_tz_count], token, MAX_TZ_LENGTH - 1); + cached_timezones[cached_tz_count][MAX_TZ_LENGTH - 1] = '\0'; // Ensure null-termination + cached_tz_count++; + } + } + + fclose(file); + + // Sort the list alphabetically + qsort(cached_timezones, cached_tz_count, MAX_TZ_LENGTH, compare_timezones); +} + +void PLAT_getTimezones(char timezones[MAX_TIMEZONES][MAX_TZ_LENGTH], int *tz_count) { + if (cached_tz_count == -1) { + LOG_warn("Error: Timezones not initialized. Call PLAT_initTimezones first.\n"); + *tz_count = 0; + return; + } + + memcpy(timezones, cached_timezones, sizeof(cached_timezones)); + *tz_count = cached_tz_count; +} + diff --git a/workspace/all/common/generic_video.c b/workspace/all/common/generic_video.c index 00cb0cbf8..9a5f33b4f 100644 --- a/workspace/all/common/generic_video.c +++ b/workspace/all/common/generic_video.c @@ -320,7 +320,7 @@ GLuint load_shader_from_file(GLenum type, const char* filepath) { LOG_info("load shader from file %s\n", filepath); // Filter out lines starting with "#pragma parameter" - char* cleaned = malloc(strlen(source) + 1); + char* cleaned = (char*)malloc(strlen(source) + 1); if (!cleaned) { fprintf(stderr, "Out of memory\n"); free(source); @@ -475,7 +475,7 @@ GLuint load_shader_from_file(GLenum type, const char* filepath) { #define MAX_SHADER_PRAGMAS 32 void loadShaderPragmas(ShaderProgram *shader, const char *shaderSource) { - shader->pragmas = calloc(MAX_SHADER_PRAGMAS, sizeof(ShaderParam)); + shader->pragmas = (ShaderParam*)calloc(MAX_SHADER_PRAGMAS, sizeof(ShaderParam)); if (!shader->pragmas) { fprintf(stderr, "Out of memory allocating pragmas for %s\n", shader->filename); return; @@ -906,12 +906,12 @@ static struct FX_Context { int live_type; } effect = { .scale = 1, - .next_scale = 1, .type = EFFECT_NONE, - .next_type = EFFECT_NONE, - .live_type = EFFECT_NONE, .color = 0, + .next_scale = 1, + .next_type = EFFECT_NONE, .next_color = 0, + .live_type = EFFECT_NONE, }; static void rgb565_to_rgb888(uint32_t rgb565, uint8_t *r, uint8_t *g, uint8_t *b) { // Extract the red component (5 bits) @@ -1048,7 +1048,7 @@ void PLAT_setOverlay(const char* filename, const char* tag) { } size_t path_len = strlen(OVERLAYS_FOLDER) + strlen(tag) + strlen(filename) + 4; // +3 for slashes and null-terminator - overlay_path = malloc(path_len); + overlay_path = (char*)malloc(path_len); if (!overlay_path) { perror("malloc failed"); @@ -1586,7 +1586,7 @@ void setRectToAspectRatio(SDL_Rect* dst_rect) { dst_rect->w = w; dst_rect->h = h; } else if (vid.blit->aspect > 0) { - if (should_rotate) { + if (gfx_render.should_rotate) { h = device_width; w = h * vid.blit->aspect; if (w > device_height) { @@ -1608,8 +1608,8 @@ void setRectToAspectRatio(SDL_Rect* dst_rect) { } else { dst_rect->x = screenx; dst_rect->y = screeny; - dst_rect->w = should_rotate ? device_height : device_width; - dst_rect->h = should_rotate ? device_width : device_height; + dst_rect->w = gfx_render.should_rotate ? device_height : device_width; + dst_rect->h = gfx_render.should_rotate ? device_width : device_height; } } @@ -1690,12 +1690,12 @@ void PLAT_flip(SDL_Surface* IGNORED, int ignored) { target = vid.target; } - SDL_Rect* src_rect = &(SDL_Rect){x, y, w, h}; - SDL_Rect* dst_rect = &(SDL_Rect){0, 0, device_width, device_height}; + SDL_Rect src_rect = {x, y, w, h}; + SDL_Rect dst_rect = {0, 0, device_width, device_height}; - setRectToAspectRatio(dst_rect); + setRectToAspectRatio(&dst_rect); - SDL_RenderCopy(vid.renderer, target, src_rect, dst_rect); + SDL_RenderCopy(vid.renderer, target, &src_rect, &dst_rect); SDL_RenderPresent(vid.renderer); vid.blit = NULL; @@ -2174,13 +2174,13 @@ void PLAT_GL_Swap() { static int shaderinfocount = 0; static int shaderinfoscreen = 0; if (shaderinfocount > 600 && shaderinfoscreen == i) { - currentshaderpass = i + 1; - currentshadertexw = shaders[i].texw; - currentshadertexh = shaders[i].texh; - currentshadersrcw = shaders[i].srcw; - currentshadersrch = shaders[i].srch; - currentshaderdstw = dst_w; - currentshaderdsth = dst_h; + gfx_render.pass = i + 1; + gfx_render.tex_w = shaders[i].texw; + gfx_render.tex_h = shaders[i].texh; + gfx_render.src_w = shaders[i].srcw; + gfx_render.src_h = shaders[i].srch; + gfx_render.dst_w = dst_w; + gfx_render.dst_h = dst_h; shaderinfocount = 0; shaderinfoscreen++; if (shaderinfoscreen >= nrofshaders) @@ -2310,7 +2310,7 @@ unsigned char* PLAT_GL_screenCapture(int* outWidth, int* outHeight) { if (outWidth) *outWidth = width; if (outHeight) *outHeight = height; - unsigned char* pixels = malloc(width * height * 4); // RGBA + unsigned char* pixels = (unsigned char*)malloc(width * height * 4); // RGBA if (!pixels) return NULL; glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); diff --git a/workspace/all/common/http.c b/workspace/all/common/http.cpp similarity index 91% rename from workspace/all/common/http.c rename to workspace/all/common/http.cpp index 16397f0b9..515f39650 100644 --- a/workspace/all/common/http.c +++ b/workspace/all/common/http.cpp @@ -1,5 +1,10 @@ +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit links against the unmangled names and its own exports keep C linkage +// for the still-C platform.c. http.h carries its own guard. +extern "C" { #include "http.h" #include "defines.h" +} #include #include @@ -10,7 +15,9 @@ // SDL is used here only for threading (SDL_CreateThread/SDL_DetachThread) // to run HTTP requests asynchronously without blocking the main loop. // HTTP communication itself uses curl via popen(). +extern "C" { #include "sdl.h" +} // Build version info (defined in makefile) #ifndef BUILD_HASH @@ -33,7 +40,7 @@ typedef struct { static int HTTPBuffer_init(HTTPBuffer* buf) { buf->capacity = 4096; - buf->data = malloc(buf->capacity); + buf->data = (char*)malloc(buf->capacity); if (!buf->data) return -1; buf->data[0] = '\0'; buf->size = 0; @@ -49,7 +56,7 @@ static int HTTPBuffer_append(HTTPBuffer* buf, const char* data, size_t len) { if (new_cap > HTTP_MAX_RESPONSE_SIZE) { return -1; // Too large } - char* new_data = realloc(buf->data, new_cap); + char* new_data = (char*)realloc(buf->data, new_cap); if (!new_data) return -1; buf->data = new_data; buf->capacity = new_cap; @@ -81,7 +88,7 @@ static char* shell_escape(const char* str) { } // Allocate: original + 3 chars per quote ('"'"') + 2 for surrounding quotes + 1 for null - char* escaped = malloc(len + quotes * 3 + 3); + char* escaped = (char*)malloc(len + quotes * 3 + 3); if (!escaped) return NULL; char* p = escaped; @@ -106,7 +113,7 @@ static char* shell_escape(const char* str) { // Execute curl and capture output static HTTP_Response* execute_curl(const char* url, const char* post_data, const char* content_type) { - HTTP_Response* response = calloc(1, sizeof(HTTP_Response)); + HTTP_Response* response = (HTTP_Response*)calloc(1, sizeof(HTTP_Response)); if (!response) return NULL; response->http_status = -1; @@ -287,10 +294,10 @@ static int async_request_thread(void* data) { static void start_async_request(const char* url, const char* post_data, const char* content_type, HTTP_Callback callback, void* userdata) { - AsyncRequestData* req = calloc(1, sizeof(AsyncRequestData)); + AsyncRequestData* req = (AsyncRequestData*)calloc(1, sizeof(AsyncRequestData)); if (!req) { // Callback with error - HTTP_Response* response = calloc(1, sizeof(HTTP_Response)); + HTTP_Response* response = (HTTP_Response*)calloc(1, sizeof(HTTP_Response)); if (response) { response->http_status = -1; response->error = strdup("Memory allocation failed"); @@ -309,7 +316,7 @@ static void start_async_request(const char* url, const char* post_data, free(req->post_data); free(req->content_type); free(req); - HTTP_Response* response = calloc(1, sizeof(HTTP_Response)); + HTTP_Response* response = (HTTP_Response*)calloc(1, sizeof(HTTP_Response)); if (response) { response->http_status = -1; response->error = strdup("Memory allocation failed"); @@ -325,7 +332,7 @@ static void start_async_request(const char* url, const char* post_data, free(req->post_data); free(req->content_type); free(req); - HTTP_Response* response = calloc(1, sizeof(HTTP_Response)); + HTTP_Response* response = (HTTP_Response*)calloc(1, sizeof(HTTP_Response)); if (response) { response->http_status = -1; response->error = strdup("Failed to create thread"); @@ -371,7 +378,7 @@ char* HTTP_urlEncode(const char* str) { // Count required size (worst case: every char becomes %XX) size_t len = strlen(str); - char* encoded = malloc(len * 3 + 1); + char* encoded = (char*)malloc(len * 3 + 1); if (!encoded) return NULL; char* p = encoded; diff --git a/workspace/all/common/http.h b/workspace/all/common/http.h index 81aef77c2..f0b6ab0a4 100644 --- a/workspace/all/common/http.h +++ b/workspace/all/common/http.h @@ -3,6 +3,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + /** * HTTP client wrapper for NextUI * @@ -89,4 +93,8 @@ char* HTTP_urlEncode(const char* str); */ void HTTP_getUserAgent(char* buffer, size_t buffer_size); +#ifdef __cplusplus +} +#endif + #endif // __HTTP_H__ diff --git a/workspace/all/common/notification.c b/workspace/all/common/notification.cpp similarity index 96% rename from workspace/all/common/notification.c rename to workspace/all/common/notification.cpp index f8d454f4a..2c8d5a085 100644 --- a/workspace/all/common/notification.c +++ b/workspace/all/common/notification.cpp @@ -1,7 +1,12 @@ +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit links against the unmangled names and its own exports keep C linkage +// for the still-C platform.c. notification.h carries its own guard. +extern "C" { #include "notification.h" #include "defines.h" #include "api.h" #include "config.h" +} #include #include @@ -304,7 +309,7 @@ static void render_progress_indicator(const ProgressIndicatorState* snap) { } int text_w = 0, text_h = 0; - TTF_SizeUTF8(font.tiny, progress_text, &text_w, &text_h); + TTF_SizeUTF8(GFX_getFonts()->tiny, progress_text, &text_w, &text_h); // Calculate icon dimensions if present int icon_w = 0, icon_h = 0, icon_total_w = 0; @@ -339,7 +344,7 @@ static void render_progress_indicator(const ProgressIndicatorState* snap) { content_x += icon_total_w; } - SDL_Surface* text_surf = TTF_RenderUTF8_Blended(font.tiny, progress_text, text_color); + SDL_Surface* text_surf = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, progress_text, text_color); if (text_surf) { SDL_SetSurfaceBlendMode(text_surf, SDL_BLENDMODE_BLEND); SDL_Rect text_dst = {content_x, notif_padding_y, text_surf->w, text_surf->h}; @@ -356,7 +361,7 @@ static void render_progress_indicator(const ProgressIndicatorState* snap) { // Render a single notification pill static void render_notification_pill(Notification* n, int x, int y, SDL_Color text_color, SDL_Color bg_color_sdl) { int text_w = 0, text_h = 0; - TTF_SizeUTF8(font.tiny, n->message, &text_w, &text_h); + TTF_SizeUTF8(GFX_getFonts()->tiny, n->message, &text_w, &text_h); int icon_w = 0, icon_h = 0, icon_total_w = 0; if (n->icon) { @@ -397,12 +402,13 @@ static void render_notification_pill(Notification* n, int x, int y, SDL_Color te if (n->type == NOTIFICATION_OFFLINE_ACHIEVEMENT) { int wifi_size = SCALE1(12); int wifi_y = notif_padding_y + (text_h - wifi_size) / 2; + SDL_Rect wifi_dst = {content_x, wifi_y}; GFX_blitAssetColor(ASSET_WIFI_OFF, NULL, notif_surface, - &(SDL_Rect){content_x, wifi_y}, THEME_COLOR1_255); + &wifi_dst, THEME_COLOR1_255); content_x += wifi_size + notif_icon_gap; } - SDL_Surface* text_surf = TTF_RenderUTF8_Blended(font.tiny, n->message, text_color); + SDL_Surface* text_surf = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, n->message, text_color); if (text_surf) { SDL_SetSurfaceBlendMode(text_surf, SDL_BLENDMODE_BLEND); SDL_Rect text_dst = {content_x, notif_padding_y, text_surf->w, text_surf->h}; @@ -428,14 +434,14 @@ static void render_notification_stack(void) { Notification* n = ¬ifications[i]; int text_h = 0; - TTF_SizeUTF8(font.tiny, n->message, NULL, &text_h); + TTF_SizeUTF8(GFX_getFonts()->tiny, n->message, NULL, &text_h); int pill_h = text_h + (notif_padding_y * 2); // Calculate stack offset (how far up from base) int stack_offset = 0; for (int j = i + 1; j < notification_count; j++) { int other_text_h = 0; - TTF_SizeUTF8(font.tiny, notifications[j].message, NULL, &other_text_h); + TTF_SizeUTF8(GFX_getFonts()->tiny, notifications[j].message, NULL, &other_text_h); int other_pill_h = other_text_h + (notif_padding_y * 2); stack_offset += other_pill_h + notif_stack_gap; } diff --git a/workspace/all/common/notification.h b/workspace/all/common/notification.h index eeb3608fc..40c877281 100644 --- a/workspace/all/common/notification.h +++ b/workspace/all/common/notification.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /////////////////////////////// // Notification System // Toast-style notifications for save states, achievements, etc. @@ -157,4 +161,8 @@ void Notification_setProgressIndicatorPersistent(bool persistent); */ bool Notification_hasProgressIndicator(void); +#ifdef __cplusplus +} +#endif + #endif // __NOTIFICATION_H__ diff --git a/workspace/all/common/palette.c b/workspace/all/common/palette.cpp similarity index 86% rename from workspace/all/common/palette.c rename to workspace/all/common/palette.cpp index 4a3c17f15..00b351e57 100644 --- a/workspace/all/common/palette.c +++ b/workspace/all/common/palette.cpp @@ -1,13 +1,14 @@ #include "palette.h" -#include -#include -#include +#include +#include #include #include #include "defines.h" +extern "C" { #include "config.h" +} static const uint32_t palette_default_colors[PALETTE_COLOR_COUNT] = { CFG_DEFAULT_COLOR1, CFG_DEFAULT_COLOR2, CFG_DEFAULT_COLOR3, @@ -15,7 +16,6 @@ static const uint32_t palette_default_colors[PALETTE_COLOR_COUNT] = { CFG_DEFAULT_COLOR7, }; -// Derive a display name from a filename: strip .txt, replace underscores with spaces. static void palette_nameFromFilename(const char *filename, char *out, size_t out_size) { size_t len = strlen(filename); @@ -28,7 +28,6 @@ static void palette_nameFromFilename(const char *filename, char *out, size_t out out[len] = '\0'; } -// Parse a single palette file. Returns true on success and a supported version. static bool palette_loadFile(const char *path, const char *filename, bool builtin, ColorPalette *out) { FILE *file = fopen(path, "r"); @@ -76,7 +75,6 @@ static bool palette_loadFile(const char *path, const char *filename, bool builti } fclose(file); - // Skip palettes authored for a newer format than we understand. if (out->version > PALETTE_VERSION_MAX) return false; @@ -116,7 +114,6 @@ int PALETTE_enumerate(ColorPalette *out, int max) return 0; int count = 0; - // Built-ins first (read-only), then user/community drop-ins. count = palette_scanDir(RES_PATH "/palettes", true, out, max, count); count = palette_scanDir(SDCARD_PATH "/Palettes", false, out, max, count); return count; @@ -126,8 +123,5 @@ void PALETTE_apply(const ColorPalette *palette) { if (!palette) return; - // CFG_applyPalette sets the 7 colors and the palette name as one atomic step, - // so the persisted "current palette" can never point at a color set that - // doesn't match it. CFG_applyPalette(palette->name, palette->colors); } diff --git a/workspace/all/common/palette.h b/workspace/all/common/palette.h index bb704dd46..88f71e6b8 100644 --- a/workspace/all/common/palette.h +++ b/workspace/all/common/palette.h @@ -1,40 +1,20 @@ -#ifndef __PALETTE_H__ -#define __PALETTE_H__ +#pragma once -#include -#include +#include -// Predefined color palettes: file-based color presets users can pick from in -// Settings, in addition to the fully custom color1-7 controls. "Palette" is -// deliberately scoped to colors only - a future "Theme" feature may bundle -// palettes with other assets (fonts, backgrounds, etc.). See config.h for the -// persisted "currently selected palette" state (CFG_getPaletteName/ -// CFG_applyPalette/CFG_clearPalette). - -// Highest palette-file format version this build understands. Palette files -// with a higher version= are skipped during enumeration. #define PALETTE_VERSION_MAX 1 #define PALETTE_NAME_MAX 64 // keep in sync with NextUISettings.paletteName in config.h #define PALETTE_PATH_MAX 512 #define PALETTE_COLOR_COUNT 7 -typedef struct +struct ColorPalette { int version; // palette-file format version char name[PALETTE_NAME_MAX]; // display label char path[PALETTE_PATH_MAX]; // absolute path to the palette file bool builtin; // true if shipped in RES_PATH/palettes (read-only) uint32_t colors[PALETTE_COLOR_COUNT]; // packed RGBA, index 0 == color1 -} ColorPalette; +}; -// Enumerate predefined color palettes found on disk. -// Scans RES_PATH/palettes (built-ins, listed first) and SDCARD_PATH/Palettes (user). -// Files whose version= exceeds PALETTE_VERSION_MAX are skipped. Missing colors fall -// back to the CFG defaults. Writes up to max entries into out; returns the count. int PALETTE_enumerate(ColorPalette *out, int max); - -// Apply a palette's colors to the current settings and record it as the selected -// palette. Fires the color-set callback per color; does not modify the palette file. void PALETTE_apply(const ColorPalette *palette); - -#endif diff --git a/workspace/all/common/ra_auth.c b/workspace/all/common/ra_auth.cpp similarity index 96% rename from workspace/all/common/ra_auth.c rename to workspace/all/common/ra_auth.cpp index 1f3587aa5..80fb792a3 100644 --- a/workspace/all/common/ra_auth.c +++ b/workspace/all/common/ra_auth.cpp @@ -1,8 +1,13 @@ +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit links against the unmangled names and its own exports keep C linkage +// for the still-C platform.c. ra_auth.h / http.h carry their own guards. +extern "C" { #include "ra_auth.h" #include "ra_util.h" #include "http.h" #include "config.h" #include "defines.h" +} #include #include @@ -138,7 +143,7 @@ void RA_authenticate(const char* username, const char* password, } // Create async context - RA_AsyncAuthContext* ctx = calloc(1, sizeof(RA_AsyncAuthContext)); + RA_AsyncAuthContext* ctx = (RA_AsyncAuthContext*)calloc(1, sizeof(RA_AsyncAuthContext)); if (!ctx) { RA_AuthResponse response = {0}; response.result = RA_AUTH_ERROR_UNKNOWN; diff --git a/workspace/all/common/ra_auth.h b/workspace/all/common/ra_auth.h index 90ae88267..e88545791 100644 --- a/workspace/all/common/ra_auth.h +++ b/workspace/all/common/ra_auth.h @@ -1,6 +1,10 @@ #ifndef __RA_AUTH_H__ #define __RA_AUTH_H__ +#ifdef __cplusplus +extern "C" { +#endif + // Handles RA login outside of rc_client for use before rc_client initialization. // See: https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration#login @@ -56,4 +60,8 @@ void RA_authenticate(const char* username, const char* password, RA_AuthResult RA_authenticateSync(const char* username, const char* password, RA_AuthResponse* response); +#ifdef __cplusplus +} +#endif + #endif // __RA_AUTH_H__ diff --git a/workspace/all/common/ra_badges.c b/workspace/all/common/ra_badges.cpp similarity index 98% rename from workspace/all/common/ra_badges.c rename to workspace/all/common/ra_badges.cpp index 24039645a..c96b5eee7 100644 --- a/workspace/all/common/ra_badges.c +++ b/workspace/all/common/ra_badges.cpp @@ -1,6 +1,10 @@ #define RA_LOG_PREFIX "RA_BADGES" -#include "ra_log.h" +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit links against the unmangled names and its own exports keep C linkage +// for the still-C platform.c. (Same pattern as ra_integration.cpp.) +extern "C" { +#include "ra_log.h" #include "ra_badges.h" #include "ra_util.h" #include "http.h" @@ -8,6 +12,7 @@ #include "api.h" #include "sdl.h" #include "notification.h" +} #include #include diff --git a/workspace/all/common/ra_badges.h b/workspace/all/common/ra_badges.h index ba93d55a8..f4b9d0ef1 100644 --- a/workspace/all/common/ra_badges.h +++ b/workspace/all/common/ra_badges.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + // Implements the badge download/caching that the integration guide leaves to the emulator. // See: https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration#showing-the-game-placard @@ -110,4 +114,8 @@ void RA_Badges_getCachePath(const char* badge_name, bool locked, char* buffer, s */ void RA_Badges_getUrl(const char* badge_name, bool locked, char* buffer, size_t buffer_size); +#ifdef __cplusplus +} +#endif + #endif // __RA_BADGES_H__ diff --git a/workspace/all/common/ra_event_queue.c b/workspace/all/common/ra_event_queue.cpp similarity index 97% rename from workspace/all/common/ra_event_queue.c rename to workspace/all/common/ra_event_queue.cpp index 7a612ef13..b31f84dfe 100644 --- a/workspace/all/common/ra_event_queue.c +++ b/workspace/all/common/ra_event_queue.cpp @@ -18,7 +18,7 @@ * deps) so declare LOG_note and define the macros we need locally. * Values must stay in sync with the LOG_* enum in api.h. */ enum { LOG_DEBUG = 0, LOG_INFO = 1, LOG_WARN = 2 }; -void LOG_note(int level, const char* fmt, ...); +extern "C" void LOG_note(int level, const char* fmt, ...); // C symbol defined in api.c #define LOG_debug(fmt, ...) LOG_note(LOG_DEBUG, fmt, ##__VA_ARGS__) #define LOG_info(fmt, ...) LOG_note(LOG_INFO, fmt, ##__VA_ARGS__) #define LOG_warn(fmt, ...) LOG_note(LOG_WARN, fmt, ##__VA_ARGS__) diff --git a/workspace/all/common/ra_offline.c b/workspace/all/common/ra_offline.cpp similarity index 98% rename from workspace/all/common/ra_offline.c rename to workspace/all/common/ra_offline.cpp index 2d9374ea7..29565c3dd 100644 --- a/workspace/all/common/ra_offline.c +++ b/workspace/all/common/ra_offline.cpp @@ -1,10 +1,15 @@ #define RA_LOG_PREFIX "RA_OFFLINE" -#include "ra_log.h" +// Project C headers declare C-linkage symbols; include them as extern "C" so +// this C++ unit links against the unmangled names and its own exports keep C +// linkage for the still-C platform.c. (Same pattern as ra_integration.cpp.) +extern "C" { +#include "ra_log.h" #include "ra_offline.h" #include "ra_util.h" #include "defines.h" #include "api.h" +} #include #include @@ -436,6 +441,12 @@ static bool patch_startsession_with_ledger(char* body, size_t body_len, /* Get pending unlocks from ledger, pre-filtered by game hash */ RA_PendingUnlock* pending = NULL; uint32_t pending_count = 0; + /* Hoisted above the `goto passthrough` jumps below: C++ forbids a goto that + * crosses a variable's initialization (the label doesn't use these). */ + char* current_body = body; + size_t current_len = body_len; + bool body_replaced = false; + uint32_t total_injected = 0; if (!RA_Offline_ledgerGetPendingByGameHash(game_hash, &pending, &pending_count)) goto passthrough; @@ -448,10 +459,6 @@ static bool patch_startsession_with_ledger(char* body, size_t body_len, * in both modes, preventing it from re-awarding them via its own * server call (which would lack &o= and record the wrong timestamp). */ - char* current_body = body; - size_t current_len = body_len; - bool body_replaced = false; - uint32_t total_injected = 0; /* Pass 1: Inject into "Unlocks" (softcore) */ { @@ -1444,26 +1451,38 @@ void RA_Offline_patchStartsessionCacheWithUnlock(const char* game_hash, /* Read existing cache file */ char* body = NULL; size_t body_len = 0; + /* Hoisted above the `goto done` jumps below: C++ forbids a goto that crosses + * a variable's initialization (the label only unlocks the mutex). */ + const char* unlocks_key = "\"Unlocks\""; + char* unlocks_pos; + char* arr_start; + char* arr_end; + bool already_in_softcore = false; + bool already_in_hardcore = false; + const char* hc_key = "\"HardcoreUnlocks\""; + char* hc_pos; + char* current_body; + size_t current_len; + bool body_replaced = false; if (!cache_read_file(path, &body, &body_len)) { /* No cached startsession for this game, or corrupt — nothing to patch */ goto done; } /* Find the "Unlocks" array */ - const char* unlocks_key = "\"Unlocks\""; - char* unlocks_pos = strstr(body, unlocks_key); + unlocks_pos = strstr(body, unlocks_key); if (!unlocks_pos) { free(body); goto done; } - char* arr_start = strchr(unlocks_pos + strlen(unlocks_key), '['); + arr_start = strchr(unlocks_pos + strlen(unlocks_key), '['); if (!arr_start) { free(body); goto done; } - char* arr_end = strchr(arr_start, ']'); + arr_end = strchr(arr_start, ']'); if (!arr_end) { free(body); goto done; @@ -1472,7 +1491,6 @@ void RA_Offline_patchStartsessionCacheWithUnlock(const char* game_hash, /* Check if this achievement ID is already in the Unlocks array */ char id_pattern[32]; snprintf(id_pattern, sizeof(id_pattern), "\"ID\":%u", achievement_id); - bool already_in_softcore = false; for (char* p = arr_start; p < arr_end; p++) { if (strncmp(p, id_pattern, strlen(id_pattern)) == 0) { already_in_softcore = true; @@ -1481,9 +1499,7 @@ void RA_Offline_patchStartsessionCacheWithUnlock(const char* game_hash, } /* Also check HardcoreUnlocks array */ - bool already_in_hardcore = false; - const char* hc_key = "\"HardcoreUnlocks\""; - char* hc_pos = strstr(body, hc_key); + hc_pos = strstr(body, hc_key); if (hc_pos) { char* hc_arr_start = strchr(hc_pos + strlen(hc_key), '['); if (hc_arr_start) { @@ -1519,9 +1535,8 @@ void RA_Offline_patchStartsessionCacheWithUnlock(const char* game_hash, * but if we pass NULL as game_hash it accepts all entries). */ single.game_hash[0] = '\0'; - char* current_body = body; - size_t current_len = body_len; - bool body_replaced = false; + current_body = body; + current_len = body_len; /* Inject into "Unlocks" if not already present */ if (!already_in_softcore) { diff --git a/workspace/all/common/ra_offline.h b/workspace/all/common/ra_offline.h index ba0c333be..1ac0991c6 100644 --- a/workspace/all/common/ra_offline.h +++ b/workspace/all/common/ra_offline.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Offline RetroAchievements support for NextUI. * @@ -312,4 +316,8 @@ void RA_Offline_patchStartsessionCacheWithUnlock(const char* game_hash, uint32_t achievement_id, uint32_t timestamp); +#ifdef __cplusplus +} +#endif + #endif /* RA_OFFLINE_H */ diff --git a/workspace/all/common/ra_sync.c b/workspace/all/common/ra_sync.cpp similarity index 97% rename from workspace/all/common/ra_sync.c rename to workspace/all/common/ra_sync.cpp index 1665f5af2..715aba3dd 100644 --- a/workspace/all/common/ra_sync.c +++ b/workspace/all/common/ra_sync.cpp @@ -10,16 +10,22 @@ */ #define RA_LOG_PREFIX "RA_SYNC" -#include "ra_log.h" +// Project C headers declare C-linkage symbols (LOG_*, config, http, api, the RA +// helpers, …). Include them as extern "C" so this C++ unit links against the +// unmangled names — and so this unit's own exports keep C linkage for the +// still-C platform.c and any C consumers. (Same pattern as ra_integration.cpp.) +#define RA_UTIL_NEED_SDL +extern "C" { +#include "ra_log.h" #include "ra_sync.h" #include "ra_offline.h" -#define RA_UTIL_NEED_SDL #include "ra_util.h" #include "http.h" #include "config.h" #include "defines.h" #include "api.h" +} #include #include diff --git a/workspace/all/common/scaler.c b/workspace/all/common/scaler.cpp similarity index 99% rename from workspace/all/common/scaler.c rename to workspace/all/common/scaler.cpp index 1ca94ef19..7727c2242 100644 --- a/workspace/all/common/scaler.c +++ b/workspace/all/common/scaler.cpp @@ -2,7 +2,13 @@ #include #include +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit's exports (declared in scaler.h) keep C linkage for the still-C +// platform.c and any C consumers. +extern "C" { #include "platform.h" // for HAS_NEON +#include "scaler.h" +} // // arm NEON / C integer scalers for ARMv7 devices @@ -2914,8 +2920,8 @@ void scale1x_line(void* __restrict src, void* __restrict dst, uint32_t sw, uint3 int cpy_pitch = MIN(ip, dp); uint16_t k = 0x0000; - uint16_t* restrict src_row = (uint16_t*)src; - uint16_t* restrict dst_row = (uint16_t*)dst; + uint16_t* __restrict src_row = (uint16_t*)src; + uint16_t* __restrict dst_row = (uint16_t*)dst; for (int y=0; y +#ifdef __cplusplus +extern "C" { +#endif + // // arm NEON / C integer scalers for rg35xx // args/ src : src offset address of top left corner @@ -187,4 +191,8 @@ void scale4x_line(void* __restrict src, void* __restrict dst, uint32_t sw, uint3 void scale2x_grid(void* __restrict src, void* __restrict dst, uint32_t sw, uint32_t sh, uint32_t sp, uint32_t dw, uint32_t dh, uint32_t dp); void scale3x_grid(void* __restrict src, void* __restrict dst, uint32_t sw, uint32_t sh, uint32_t sp, uint32_t dw, uint32_t dh, uint32_t dp); +#ifdef __cplusplus +} +#endif + #endif diff --git a/workspace/all/common/utils.c b/workspace/all/common/utils.cpp similarity index 96% rename from workspace/all/common/utils.c rename to workspace/all/common/utils.cpp index d3e69813b..fc10ae0cd 100644 --- a/workspace/all/common/utils.c +++ b/workspace/all/common/utils.cpp @@ -11,8 +11,14 @@ #include #include #include + +// Project C headers declare C-linkage symbols; include as extern "C" so this +// C++ unit's own exports keep C linkage for the still-C platform.c and any C +// consumers. utils.h carries its own guard. +extern "C" { #include "defines.h" #include "utils.h" +} /////////////////////////////////////// @@ -93,7 +99,7 @@ char *replaceString2(const char *orig, char *rep, char *with) // count the number of replacements needed ins = orig; - for (count = 0; (tmp = strstr(ins, rep)); ++count) + for (count = 0; (tmp = (char*)strstr(ins, rep)); ++count) ins = tmp + len_rep; char *result = @@ -241,8 +247,8 @@ char *removeExtension(const char *myStr) } const char *baseName(const char *filename) { - char *p = strrchr(filename, '/'); - return p ? p + 1 : (char *)filename; + const char *p = strrchr(filename, '/'); + return p ? p + 1 : filename; } void folderPath(const char *path, char *result) { char pathCopy[256]; @@ -431,7 +437,7 @@ int toggle(char *path) { return 1; } } -void putFile(char* path, char* contents) { +void putFile(const char* path, char* contents) { FILE* file = fopen(path, "w"); if (file) { fputs(contents, file); @@ -456,7 +462,7 @@ char* allocFile(char* path) { // caller must free! if (file) { fseek(file, 0L, SEEK_END); size_t size = ftell(file); - contents = calloc(size+1, sizeof(char)); + contents = (char*)calloc(size+1, sizeof(char)); fseek(file, 0L, SEEK_SET); fread(contents, sizeof(char), size, file); fclose(file); @@ -464,7 +470,7 @@ char* allocFile(char* path) { // caller must free! } return contents; } -int getInt(char* path) { +int getInt(const char* path) { int i = 0; if(path == NULL) return i; @@ -478,7 +484,7 @@ int getInt(char* path) { } return i; } -void putInt(char* path, int value) { +void putInt(const char* path, int value) { char buffer[8]; sprintf(buffer, "%d", value); putFile(path, buffer); diff --git a/workspace/all/common/utils.h b/workspace/all/common/utils.h index 9444164ea..5a1b0f39b 100644 --- a/workspace/all/common/utils.h +++ b/workspace/all/common/utils.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + int prefixMatch(char* pre, const char* str); int suffixMatch(char* suf,const char* str); int exactMatch(const char* str1, const char* str2); @@ -36,11 +40,11 @@ void trimSortingMeta(char** str); int exists(char* path); void touch(char* path); int toggle(char *path); // creates or removes file -void putFile(char *path, char *contents); +void putFile(const char *path, char *contents); char* allocFile(char* path); // caller must free void getFile(char* path, char* buffer, size_t buffer_size); -void putInt(char* path, int value); -int getInt(char* path); +void putInt(const char* path, int value); +int getInt(const char* path); uint64_t getMicroseconds(void); @@ -49,4 +53,8 @@ double clampd(double x, double lower, double upper); char* findFileInDir(const char *directory, const char *filename); +#ifdef __cplusplus +} +#endif + #endif diff --git a/workspace/all/gametime/gametime.c b/workspace/all/gametime/gametime.cpp similarity index 82% rename from workspace/all/gametime/gametime.c rename to workspace/all/gametime/gametime.cpp index a30f67c5c..7ce3b92cb 100644 --- a/workspace/all/gametime/gametime.c +++ b/workspace/all/gametime/gametime.cpp @@ -1,16 +1,20 @@ // heavily modified from the Onion original: https://github.com/OnionUI/Onion/blob/main/src/playActivity/playActivityUI.c -#include +#include #include -#include -#include -#include +#include +#include + +#include +// Project + libgametimedb C headers declare C-linkage symbols defined in the +// still-C common/ and libgametimedb translation units; include as extern "C". +extern "C" { +#include #include "defines.h" #include "api.h" #include "utils.h" - -#include #include +} struct ListLayout { @@ -59,8 +63,10 @@ int _renderText(const char *text, TTF_Font *font, SDL_Color color, SDL_Rect *rec SDL_Surface *textSurface = TTF_RenderUTF8_Blended(font, text, color); if (textSurface != NULL) { text_width = textSurface->w; - if (right_align) - SDL_BlitSurface(textSurface, NULL, screen, &(SDL_Rect){rect->w - textSurface->w, rect->y, rect->w, rect->h}); + if (right_align) { + SDL_Rect r = {rect->w - textSurface->w, rect->y, rect->w, rect->h}; + SDL_BlitSurface(textSurface, NULL, screen, &r); + } else SDL_BlitSurface(textSurface, NULL, screen, rect); SDL_FreeSurface(textSurface); @@ -172,7 +178,7 @@ SDL_Surface *loadRomImage(char *image_path) void preloadRomImages() { // load all rom images into SDL_Surfaces - romImages = malloc(sizeof(SDL_Surface *) * play_activities->count); + romImages = (SDL_Surface **)malloc(sizeof(SDL_Surface *) * play_activities->count); for (int i = 0; i < play_activities->count; i++) { PlayActivity *entry = play_activities->play_activity[i]; ROM *rom = entry->rom; @@ -209,12 +215,13 @@ void renderList(int count, int start, int end, int selected) PlayActivity *entry = play_activities->play_activity[index]; ROM *rom = entry->rom; - renderRoundedRectangle((SDL_Rect){ - layout.list_display_start_x, - layout.list_display_start_y + row * elemHeight, - layout.list_display_size_x, + SDL_Rect rowRect = { + layout.list_display_start_x, + layout.list_display_start_y + row * elemHeight, + layout.list_display_size_x, elemHeight - }, isSelected ? RGB_WHITE : RGB_BLACK, SCALE1(24)); + }; + renderRoundedRectangle(rowRect, isSelected ? RGB_WHITE : RGB_BLACK, SCALE1(24)); SDL_Surface *romImage = romImages[index]; if (romImage) { @@ -238,13 +245,14 @@ void renderList(int count, int start, int end, int selected) // TODO: no getter exposed for this right now //SDL_Rect rect = asset_rects[ASSET_GAMEPAD]; - SDL_Rect rect = (SDL_Rect){SCALE4(92,51,18,10)}; + SDL_Rect rect = {SCALE4(92,51,18,10)}; int x = rectRomImage.x; int y = rectRomImage.y; x += (SCALE1(IMG_MAX_WIDTH) - rect.w) / 2; y += (SCALE1(IMG_MAX_HEIGHT) - rect.h) / 2; - GFX_blitAssetColor(ASSET_GAMEPAD, NULL, screen, &(SDL_Rect){x,y}, THEME_COLOR1_255); + SDL_Rect gamepad_dst = {x,y}; + GFX_blitAssetColor(ASSET_GAMEPAD, NULL, screen, &gamepad_dst, THEME_COLOR1_255); } cleanName(rom_name, rom->name); @@ -253,11 +261,12 @@ void renderList(int count, int start, int end, int selected) //textColor = uintToColour(THEME_COLOR1); textColor = COLOR_BLACK; } - renderText(rom_name, font.medium, textColor, &(SDL_Rect){ - layout.list_display_start_x + num_width + thumbMargin + SCALE1(IMG_MAX_WIDTH), - layout.list_display_start_y + thumbMargin / 2 + elemHeight * row, - layout.list_display_size_x, - textHeight}); + SDL_Rect nameRect = { + layout.list_display_start_x + num_width + thumbMargin + SCALE1(IMG_MAX_WIDTH), + layout.list_display_start_y + thumbMargin / 2 + elemHeight * row, + layout.list_display_size_x, + textHeight}; + renderText(rom_name, GFX_getFonts()->medium, textColor, &nameRect); serializeTime(total, entry->play_time_total); serializeTime(average, entry->play_time_average); @@ -274,7 +283,7 @@ void renderList(int count, int start, int end, int selected) SDL_Color detailCol = i % 2 == 0 ? COLOR_DARK_TEXT : uintToColour(THEME_COLOR2_255); //SDL_Color detailCol = uintToColour(i % 2 == 0 ? THEME_COLOR3_255 : THEME_COLOR2_255); //SDL_Color detailCol = i % 2 == 0 ? COLOR_DARK_TEXT : COLOR_LIGHT_TEXT; - detailsRect.x += renderText(details[i], font.small, detailCol, &detailsRect); + detailsRect.x += renderText(details[i], GFX_getFonts()->small, detailCol, &detailsRect); } } @@ -283,10 +292,14 @@ void renderList(int count, int start, int end, int selected) #define SCROLL_HEIGHT 4 int ox = (screen->w - SCALE1(SCROLL_WIDTH)) / 2; int oy = SCALE1((PILL_SIZE - SCROLL_HEIGHT) / 2); - if (start>0) - GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, &(SDL_Rect){ox, SCALE1(PADDING + PILL_SIZE)}); - if (endh - SCALE1(PADDING + PILL_SIZE + BUTTON_SIZE) + oy}); + if (start>0) { + SDL_Rect up_dst = {ox, SCALE1(PADDING + PILL_SIZE)}; + GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, &up_dst); + } + if (endh - SCALE1(PADDING + PILL_SIZE + BUTTON_SIZE) + oy}; + GFX_blitAsset(ASSET_SCROLL_DOWN, NULL, screen, &down_dst); + } } } @@ -410,13 +423,16 @@ int main(int argc, char *argv[]) sprintf(display_name, "Time spent having fun: %s", play_time_total_formatted); char title[256]; - int text_width = GFX_truncateText(font.large, display_name, title, max_width, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_truncateText(GFX_getFonts()->large, display_name, title, max_width, SCALE1(BUTTON_PADDING * 2)); max_width = MIN(max_width, text_width); SDL_Surface *text; - text = TTF_RenderUTF8_Blended(font.large, title, COLOR_WHITE); - GFX_blitPill(ASSET_BLACK_PILL, screen, &(SDL_Rect){SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, screen, &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); + text = TTF_RenderUTF8_Blended(GFX_getFonts()->large, title, COLOR_WHITE); + SDL_Rect pill = {SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}; + GFX_blitPill(ASSET_BLACK_PILL, screen, &pill); + SDL_Rect title_src = {0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}; + SDL_Rect title_dst = {SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}; + SDL_BlitSurface(text, &title_src, screen, &title_dst); SDL_FreeSurface(text); } @@ -424,10 +440,15 @@ int main(int argc, char *argv[]) if (show_setting) GFX_blitHardwareHints(screen, show_setting); - else - GFX_blitButtonGroup((char *[]){"U/D", "SCROLL", NULL}, 0, screen, 0); + else { + // Named local array: g++ 8.3 miscompiles a (char*[]){...} compound + // literal passed straight to a function. + const char* hints_scroll[] = {"U/D", "SCROLL", NULL}; + GFX_blitButtonGroup((char**)hints_scroll, 0, screen, 0); + } - GFX_blitButtonGroup((char *[]){"B", "BACK", NULL}, 1, screen, 1); + const char* hints_back[] = {"B", "BACK", NULL}; + GFX_blitButtonGroup((char**)hints_back, 1, screen, 1); GFX_flip(screen); dirty = 0; diff --git a/workspace/all/gametime/makefile b/workspace/all/gametime/makefile index 6448fcee6..b5edb96a3 100644 --- a/workspace/all/gametime/makefile +++ b/workspace/all/gametime/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = gametime INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings -lgametimedb -lsqlite3 PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/gametimedb.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/gametimectl/gametimectl.c b/workspace/all/gametimectl/gametimectl.cpp similarity index 91% rename from workspace/all/gametimectl/gametimectl.c rename to workspace/all/gametimectl/gametimectl.cpp index 9b030b76c..c2b05e3a3 100644 --- a/workspace/all/gametimectl/gametimectl.c +++ b/workspace/all/gametimectl/gametimectl.cpp @@ -1,12 +1,17 @@ // heavily modified from the Onion original: https://github.com/OnionUI/Onion/blob/main/src/playActivity/playActivity.c -#include +#include +#include +#include + +// Project + libgametimedb C headers declare C-linkage symbols defined in the +// still-C common/ and libgametimedb translation units; include as extern "C". +extern "C" { #include "defines.h" #include "api.h" #include "utils.h" - -#include #include +} void printUsage() { diff --git a/workspace/all/gametimectl/makefile b/workspace/all/gametimectl/makefile index 77d515141..c5c098398 100644 --- a/workspace/all/gametimectl/makefile +++ b/workspace/all/gametimectl/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = gametimectl INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/scaler.c ../common/config.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/scaler.cpp ../common/config.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings -lgametimedb -lsqlite3 PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/gametimedb.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/ledcontrol/ledcontrol.c b/workspace/all/ledcontrol/ledcontrol.cpp similarity index 84% rename from workspace/all/ledcontrol/ledcontrol.c rename to workspace/all/ledcontrol/ledcontrol.cpp index c38167a3c..aa886d415 100644 --- a/workspace/all/ledcontrol/ledcontrol.c +++ b/workspace/all/ledcontrol/ledcontrol.cpp @@ -1,10 +1,12 @@ -#include +// Project + libmsettings C headers declare C-linkage symbols defined in the +// still-C common/ and libmsettings translation units; include as extern "C". +extern "C" { #include - #include "sdl.h" #include "defines.h" #include "api.h" #include "utils.h" +} #define NUM_OPTIONS 4 @@ -307,8 +309,10 @@ int main(int argc, char *argv[]) if (show_setting) GFX_blitHardwareHints(screen, show_setting); - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 1, screen, 1); - GFX_blitButtonGroup((char*[]){ "L/R","Select light", NULL }, 0, screen, 0); + const char* hints_back[] = { "B","BACK", NULL }; + GFX_blitButtonGroup((char**)hints_back, 1, screen, 1); + const char* hints_light[] = { "L/R","Select light", NULL }; + GFX_blitButtonGroup((char**)hints_light, 0, screen, 0); int max_width = screen->w - SCALE1(PADDING * 2) - ow; @@ -317,13 +321,16 @@ int main(int argc, char *argv[]) snprintf(light_name_text, sizeof(light_name_text), "%s", lightnames[selected_light]); char title[256]; - int text_width = GFX_truncateText(font.medium, light_name_text, title, max_width, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_truncateText(GFX_getFonts()->medium, light_name_text, title, max_width, SCALE1(BUTTON_PADDING * 2)); max_width = MIN(max_width, text_width); SDL_Surface *text; - text = TTF_RenderUTF8_Blended(font.medium, title, COLOR_WHITE); - GFX_blitPill(ASSET_BLACK_PILL, screen, &(SDL_Rect){SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, &(SDL_Rect){0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, screen, &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); + text = TTF_RenderUTF8_Blended(GFX_getFonts()->medium, title, COLOR_WHITE); + SDL_Rect title_pill = {SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}; + GFX_blitPill(ASSET_BLACK_PILL, screen, &title_pill); + SDL_Rect title_src = {0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}; + SDL_Rect title_dst = {SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}; + SDL_BlitSurface(text, &title_src, screen, &title_dst); SDL_FreeSurface(text); // Display settings @@ -339,7 +346,7 @@ int main(int argc, char *argv[]) } int settings_values[5] = { lightsDefault[selected_light].effect, - lightsDefault[selected_light].color1, + (int)lightsDefault[selected_light].color1, // 0xRRGGBB fits in int; explicit narrowing lightsDefault[selected_light].speed, lightsDefault[selected_light].brightness, lightsDefault[selected_light].inbrightness @@ -355,39 +362,40 @@ int main(int argc, char *argv[]) if (j == 0) { // Display effect name instead of number snprintf(setting_text, sizeof(setting_text), "%s: %s", settings_labels[j], selected_light == 3 ? lr_effect_names[settings_values[j] - 1] : selected_light == 2 ? topbar_effect_names[settings_values[j] - 1] : effect_names[settings_values[j] - 1]); - SDL_Surface *text = TTF_RenderUTF8_Blended(font.medium, setting_text, current_color); + SDL_Surface *text = TTF_RenderUTF8_Blended(GFX_getFonts()->medium, setting_text, current_color); int text_width = text->w + SCALE1(BUTTON_PADDING * 2); - GFX_blitPill(selected ? ASSET_WHITE_PILL : ASSET_BLACK_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), y, text_width, SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, - &(SDL_Rect){0, 0, text->w, text->h}, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), y + SCALE1(4)}); + SDL_Rect pill = {SCALE1(PADDING), y, text_width, SCALE1(PILL_SIZE)}; + GFX_blitPill(selected ? ASSET_WHITE_PILL : ASSET_BLACK_PILL, screen, &pill); + SDL_Rect blit_src = {0, 0, text->w, text->h}; + SDL_Rect blit_dst = {SCALE1(PADDING + BUTTON_PADDING), y + SCALE1(4)}; + SDL_BlitSurface(text, &blit_src, screen, &blit_dst); SDL_FreeSurface(text); } else if (j == 1) { // Display color as hex code snprintf(setting_text, sizeof(setting_text), "%s", settings_labels[j]); - SDL_Surface *text = TTF_RenderUTF8_Blended(font.medium, setting_text, current_color); + SDL_Surface *text = TTF_RenderUTF8_Blended(GFX_getFonts()->medium, setting_text, current_color); int text_width = text->w + SCALE1(BUTTON_PADDING * 2); - GFX_blitPill(selected ? ASSET_WHITE_PILL : ASSET_BLACK_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), y, text_width + SCALE1(BUTTON_MARGIN + BUTTON_SIZE), SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, - &(SDL_Rect){0, 0, text->w, text->h}, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), y + SCALE1(4)}); + SDL_Rect pill = {SCALE1(PADDING), y, text_width + SCALE1(BUTTON_MARGIN + BUTTON_SIZE), SCALE1(PILL_SIZE)}; + GFX_blitPill(selected ? ASSET_WHITE_PILL : ASSET_BLACK_PILL, screen, &pill); + SDL_Rect blit_src = {0, 0, text->w, text->h}; + SDL_Rect blit_dst = {SCALE1(PADDING + BUTTON_PADDING), y + SCALE1(4)}; + SDL_BlitSurface(text, &blit_src, screen, &blit_dst); SDL_FreeSurface(text); // color1 is stored as 0xRRGGBB; GFX_blitAssetColor expects 0xRRGGBBAA - GFX_blitAssetColor(ASSET_BUTTON, NULL, screen, &(SDL_Rect){ + SDL_Rect color_dst = { SCALE1(PADDING) + text_width, y + SCALE1(BUTTON_MARGIN) - }, ((uint32_t)settings_values[j] << 8) | 0xFF); + }; + GFX_blitAssetColor(ASSET_BUTTON, NULL, screen, &color_dst, ((uint32_t)settings_values[j] << 8) | 0xFF); } else { snprintf(setting_text, sizeof(setting_text), "%s: %d", settings_labels[j], settings_values[j]); - SDL_Surface *text = TTF_RenderUTF8_Blended(font.medium, setting_text, current_color); + SDL_Surface *text = TTF_RenderUTF8_Blended(GFX_getFonts()->medium, setting_text, current_color); int text_width = text->w + SCALE1(BUTTON_PADDING * 2); - GFX_blitPill(selected ? ASSET_WHITE_PILL : ASSET_BLACK_PILL, screen, - &(SDL_Rect){SCALE1(PADDING), y, text_width, SCALE1(PILL_SIZE)}); - SDL_BlitSurface(text, - &(SDL_Rect){0, 0, text->w, text->h}, screen, - &(SDL_Rect){SCALE1(PADDING + BUTTON_PADDING), y + SCALE1(4)}); + SDL_Rect pill = {SCALE1(PADDING), y, text_width, SCALE1(PILL_SIZE)}; + GFX_blitPill(selected ? ASSET_WHITE_PILL : ASSET_BLACK_PILL, screen, &pill); + SDL_Rect blit_src = {0, 0, text->w, text->h}; + SDL_Rect blit_dst = {SCALE1(PADDING + BUTTON_PADDING), y + SCALE1(4)}; + SDL_BlitSurface(text, &blit_src, screen, &blit_dst); SDL_FreeSurface(text); } } diff --git a/workspace/all/ledcontrol/makefile b/workspace/all/ledcontrol/makefile index c893d5787..c5e72d9b6 100644 --- a/workspace/all/ledcontrol/makefile +++ b/workspace/all/ledcontrol/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = ledcontrol INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/libbatmondb/batmondb.c b/workspace/all/libbatmondb/batmondb.cpp similarity index 88% rename from workspace/all/libbatmondb/batmondb.c rename to workspace/all/libbatmondb/batmondb.cpp index 68c5a5192..d375acbea 100644 --- a/workspace/all/libbatmondb/batmondb.c +++ b/workspace/all/libbatmondb/batmondb.cpp @@ -1,14 +1,18 @@ // heavily modified from the Onion original: https://github.com/OnionUI/Onion/tree/main/src/batmon -#include -#include +#include #include -#include -#include +#include +#include #include +#include + +// Project C headers declare C-linkage symbols defined in the still-C common/ +// translation units; include as extern "C". batmondb.h carries its own guard. +extern "C" { #include #include -#include +} #include "batmondb.h" @@ -22,12 +26,12 @@ sqlite3* open_battery_log_db(void) if (!db_exists) touch(BATTERY_LOG_FILE); - sqlite3 *bat_log_db = NULL; + sqlite3 *bat_log_db = nullptr; if (sqlite3_open(BATTERY_LOG_FILE, &bat_log_db) != SQLITE_OK) { printf("%s\n", sqlite3_errmsg(bat_log_db)); close_battery_log_db(bat_log_db); - return NULL; + return nullptr; } if (!db_exists) { @@ -49,7 +53,7 @@ sqlite3* open_battery_log_db(void) void close_battery_log_db(sqlite3* bat_log_db) { sqlite3_close(bat_log_db); - bat_log_db = NULL; + bat_log_db = nullptr; } int get_best_session_time(sqlite3* bat_log_db, const char* device) diff --git a/workspace/all/libbatmondb/batmondb.h b/workspace/all/libbatmondb/batmondb.h index 7483d7fdb..92eb63582 100644 --- a/workspace/all/libbatmondb/batmondb.h +++ b/workspace/all/libbatmondb/batmondb.h @@ -2,8 +2,16 @@ #ifndef __batmon_db_h__ #define __batmon_db_h__ +#ifdef __cplusplus +extern "C" { +#endif + sqlite3* open_battery_log_db(void); void close_battery_log_db(sqlite3* ctx); int get_best_session_time(sqlite3* ctx, const char* device); +#ifdef __cplusplus +} +#endif + #endif // __batmon_db_h__ \ No newline at end of file diff --git a/workspace/all/libbatmondb/makefile b/workspace/all/libbatmondb/makefile index 1fbf98c30..1e5e34e00 100644 --- a/workspace/all/libbatmondb/makefile +++ b/workspace/all/libbatmondb/makefile @@ -29,22 +29,32 @@ TARGET=batmondb .PHONY: clean CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) SYSROOT := $(shell $(CC) --print-sysroot) INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../../$(PLATFORM)/platform/platform.c +# The library itself is C++20 and pulls in only common/utils. (It used to also +# compile the platform TU, but that object was never linked into the .so.) +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp CFLAGS += $(OPT) CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +# -Wno-write-strings: the exported functions call common C helpers that take +# char* (exists/touch); passing string literals is benign but would error under +# C++ -Werror. +CXXFLAGS = $(OPT) $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=$(CXXSTD) -Wno-write-strings LDFLAGS += -s -lsqlite3 PRODUCT= build/$(PLATFORM)/lib$(TARGET).so -build: +build: mkdir -p build/$(PLATFORM) - $(CC) -c -Werror -fpic $(SOURCE) -Wl,--no-as-needed $(LDFLAGS) $(CFLAGS) - $(CC) -shared -o $(PRODUCT) "$(TARGET).o" "utils.o" $(LDFLAGS) + $(CXX) -c -Werror -fpic $(CXXSOURCE) $(CXXFLAGS) + $(CXX) -shared -o $(PRODUCT) "$(TARGET).o" "utils.o" $(LDFLAGS) -lstdc++ mkdir -p "$(PREFIX_LOCAL)/include" mkdir -p "$(PREFIX_LOCAL)/lib" cp "$(TARGET).h" "$(PREFIX_LOCAL)/include" diff --git a/workspace/all/libgametimedb/gametimedb.c b/workspace/all/libgametimedb/gametimedb.cpp similarity index 98% rename from workspace/all/libgametimedb/gametimedb.c rename to workspace/all/libgametimedb/gametimedb.cpp index afbb0469d..3648d1014 100644 --- a/workspace/all/libgametimedb/gametimedb.c +++ b/workspace/all/libgametimedb/gametimedb.cpp @@ -1,14 +1,18 @@ // heavily modified from the Onion original: https://github.com/OnionUI/Onion/blob/main/src/playActivity/playActivityDB.h -#include -#include +#include #include -#include -#include +#include +#include #include +#include + +// Project C headers declare C-linkage symbols defined in the still-C common/ +// translation units; include as extern "C". gametimedb.h carries its own guard. +extern "C" { #include #include -#include +} #include "gametimedb.h" @@ -184,7 +188,7 @@ PlayActivities *play_activity_find_all(void) rom->name = strdup((const char *)sqlite3_column_text(stmt, 2)); if (sqlite3_column_text(stmt, 3) != NULL) { rom->file_path = strdup((const char *)sqlite3_column_text(stmt, 3)); - rom->image_path = malloc(STR_MAX * sizeof(char)); + rom->image_path = (char *)malloc(STR_MAX * sizeof(char)); memset(rom->image_path, 0, STR_MAX); get_rom_image_path(rom->file_path, rom->image_path); } diff --git a/workspace/all/libgametimedb/gametimedb.h b/workspace/all/libgametimedb/gametimedb.h index 4c0e4eb1d..c5b00ae07 100644 --- a/workspace/all/libgametimedb/gametimedb.h +++ b/workspace/all/libgametimedb/gametimedb.h @@ -2,6 +2,10 @@ #ifndef __gametime_db_h__ #define __gametime_db_h__ +#ifdef __cplusplus +extern "C" { +#endif + typedef struct ROM ROM; typedef struct PlayActivity PlayActivity; typedef struct PlayActivities PlayActivities; @@ -42,4 +46,8 @@ void play_activity_stop(char *rom_file_path); void play_activity_stop_all(void); void play_activity_list_all(void); +#ifdef __cplusplus +} +#endif + #endif // __gametime_db_h__ \ No newline at end of file diff --git a/workspace/all/libgametimedb/makefile b/workspace/all/libgametimedb/makefile index 1b1c941ac..980361ebc 100644 --- a/workspace/all/libgametimedb/makefile +++ b/workspace/all/libgametimedb/makefile @@ -29,22 +29,31 @@ TARGET=gametimedb .PHONY: clean CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) SYSROOT := $(shell $(CC) --print-sysroot) INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../../$(PLATFORM)/platform/platform.c +# The library itself is C++20 and pulls in only common/utils. (It used to also +# compile the platform TU, but that object was never linked into the .so.) +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp CFLAGS += $(OPT) CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +# -Wno-write-strings: the exported functions call common C helpers that take +# char*; passing string literals is benign but would error under C++ -Werror. +CXXFLAGS = $(OPT) $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=$(CXXSTD) -Wno-write-strings LDFLAGS += -s -lsqlite3 PRODUCT= build/$(PLATFORM)/lib$(TARGET).so -build: +build: mkdir -p build/$(PLATFORM) - $(CC) -c -Werror -fpic $(SOURCE) -Wl,--no-as-needed $(LDFLAGS) $(CFLAGS) - $(CC) -shared -o $(PRODUCT) "$(TARGET).o" "utils.o" $(LDFLAGS) + $(CXX) -c -Werror -fpic $(CXXSOURCE) $(CXXFLAGS) + $(CXX) -shared -o $(PRODUCT) "$(TARGET).o" "utils.o" $(LDFLAGS) -lstdc++ cp "$(TARGET).h" "$(PREFIX_LOCAL)/include" cp $(PRODUCT) "$(PREFIX_LOCAL)/lib" diff --git a/workspace/all/minarch/chd_reader.c b/workspace/all/minarch/chd_reader.cpp similarity index 97% rename from workspace/all/minarch/chd_reader.c rename to workspace/all/minarch/chd_reader.cpp index 0c48f723f..2f67b41ce 100644 --- a/workspace/all/minarch/chd_reader.c +++ b/workspace/all/minarch/chd_reader.cpp @@ -1,17 +1,23 @@ /** - * chd_reader.c - CHD file reader for rcheevos hashing - * + * chd_reader.cpp - CHD file reader for rcheevos hashing + * * Provides CD reader callbacks for rcheevos to hash CHD disc images. */ #include "chd_reader.h" -#include -#include -#include -#include +#include +#include +#include +#include +// libchdr is a C library. chd.h carries its own extern "C" guard; cdrom.h does +// not, so wrap both to keep any function declarations at C linkage. Allocation +// on this path stays malloc/free (non-throwing): these readers run inside the +// rcheevos C hashing callbacks, where a C++ exception would be undefined. +extern "C" { #include #include +} /***************************************************************************** * Track info structure diff --git a/workspace/all/minarch/chd_reader.h b/workspace/all/minarch/chd_reader.h index 1aca00d63..400504741 100644 --- a/workspace/all/minarch/chd_reader.h +++ b/workspace/all/minarch/chd_reader.h @@ -12,6 +12,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** * Open a track from a CHD file. * @@ -65,4 +69,8 @@ uint32_t chd_first_track_sector(void* track_handle); */ int chd_reader_is_chd(const char* path); +#ifdef __cplusplus +} +#endif + #endif /* CHD_READER_H */ diff --git a/workspace/all/minarch/ma_audio.c b/workspace/all/minarch/ma_audio.cpp similarity index 65% rename from workspace/all/minarch/ma_audio.c rename to workspace/all/minarch/ma_audio.cpp index a8f66e765..98ce3c2c0 100644 --- a/workspace/all/minarch/ma_audio.c +++ b/workspace/all/minarch/ma_audio.cpp @@ -1,8 +1,15 @@ #include #include + +// minarch's C headers declare C-linkage symbols (core, SND_*, LOG_*, …) that +// are defined in the still-C translation units. Include them as extern "C" so +// this C++ unit links against the unmangled names. (Same pattern nextui.cpp and +// settings use around api.h.) +extern "C" { #include #include "ma_internal.h" #include "ma_audio.h" +} static bool resetAudio = false; @@ -32,21 +39,24 @@ void Audio_checkAndResetIfNeeded(void) { } void audio_sample_callback(int16_t left, int16_t right) { - if (rewinding && !rewind_ctx.audio) return; - if (!fast_forward || ff_audio) { - if (use_core_fps || fast_forward) { - SND_batchSamples_fixed_rate(&(const SND_Frame){left,right}, 1); + if (rewind_st.active && !rewind_ctx.audio) return; + if (!ff.active || ff.audio) { + // Named local rather than a compound literal: g++ 8.3 silently + // miscompiles &(const SND_Frame){...} passed as a function argument. + const SND_Frame frame = {left, right}; + if (use_core_fps || ff.active) { + SND_batchSamples_fixed_rate(&frame, 1); } else { - SND_batchSamples(&(const SND_Frame){left,right}, 1); + SND_batchSamples(&frame, 1); } } } size_t audio_sample_batch_callback(const int16_t *data, size_t frames) { - if (rewinding && !rewind_ctx.audio) return frames; - if (!fast_forward || ff_audio) { - if (use_core_fps || fast_forward) { + if (rewind_st.active && !rewind_ctx.audio) return frames; + if (!ff.active || ff.audio) { + if (use_core_fps || ff.active) { return SND_batchSamples_fixed_rate((const SND_Frame*)data, frames); } else { diff --git a/workspace/all/minarch/ma_cheats.c b/workspace/all/minarch/ma_cheats.cpp similarity index 78% rename from workspace/all/minarch/ma_cheats.c rename to workspace/all/minarch/ma_cheats.cpp index 289a2a303..91c0b1dd5 100644 --- a/workspace/all/minarch/ma_cheats.c +++ b/workspace/all/minarch/ma_cheats.cpp @@ -1,10 +1,15 @@ -#include "ma_internal.h" -#include "ma_cheats.h" - #include #include #include +// Project C headers declare C-linkage symbols (cheatcodes, core, LOG_*, …) +// defined in the still-C translation units. Include them as extern "C" so this +// C++ unit links against the unmangled names. (Same pattern as ma_audio.cpp.) +extern "C" { +#include "ma_internal.h" +#include "ma_cheats.h" +} + // based on picoarch/cheat.c struct Cheats cheatcodes; @@ -115,7 +120,7 @@ static int parse_cheats(struct Cheats *cheats, FILE *file) { if (len == 0) continue; - cheat->name = calloc(len+1, sizeof(char)); + cheat->name = (char *)calloc(len+1, sizeof(char)); if (!cheat->name) goto finish; @@ -123,7 +128,7 @@ static int parse_cheats(struct Cheats *cheats, FILE *file) { truncateString((char *)cheat->name, CHEAT_MAX_DESC_LEN); if (len >= CHEAT_MAX_DESC_LEN) { - cheat->info = calloc(len+1, sizeof(char)); + cheat->info = (char *)calloc(len+1, sizeof(char)); if (!cheat->info) goto finish; @@ -141,7 +146,7 @@ static int parse_cheats(struct Cheats *cheats, FILE *file) { if (len == 0) continue; - cheat->code = calloc(len+1, sizeof(char)); + cheat->code = (char *)calloc(len+1, sizeof(char)); if (!cheat->code) goto finish; @@ -168,9 +173,9 @@ void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count) { const int sanitized_paths_count = 3; // Generate possible paths, ordered by most likely to be used (pre v6.2.3 style first) - sprintf(paths[(*count)++], "%s/%s.cht", core.cheats_dir, game.name); // /mnt/SDCARD/Cheats/GB/Super Example World..cht + snprintf(paths[(*count)++], MAX_PATH, "%s/%s.cht", core.cheats_dir, game.name); // /mnt/SDCARD/Cheats/GB/Super Example World..cht if(CFG_getUseExtractedFileName()) - sprintf(paths[(*count)++], "%s/%s.cht", core.cheats_dir, game.alt_name); // /mnt/SDCARD/Cheats/GB/Super Example World (USA)..cht + snprintf(paths[(*count)++], MAX_PATH, "%s/%s.cht", core.cheats_dir, game.alt_name); // /mnt/SDCARD/Cheats/GB/Super Example World (USA)..cht // game.alt_name, but with all extension-like stuff removed (apart from .cht) // eg. Super Example World (USA).zip -> Super Example World (USA).cht @@ -183,7 +188,7 @@ void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count) { return; } - strcpy(exts, core.extensions); + snprintf(exts, sizeof(exts), "%s", core.extensions); while ((ext = strtok(i ? NULL : exts, "|"))) { // sanitized_paths_count slots are reserved for sanitized rom name, see below if (*count >= CHEAT_MAX_PATHS - sanitized_paths_count) { @@ -198,7 +203,7 @@ void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count) { continue; } - strcpy(rom_name, game.alt_name); + snprintf(rom_name, sizeof(rom_name), "%s", game.alt_name); char* tmp = strrchr(rom_name, '.'); if (tmp != NULL && strlen(tmp) > 2 && strlen(tmp) <= 5) { tmp[0] = '\0'; @@ -206,7 +211,7 @@ void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count) { // Add length check before sprintf to prevent buffer overflow int needed_len = strlen(core.cheats_dir) + strlen(rom_name) + strlen(ext) + 10; // +10 for "/", ".", ".cht", etc. if (needed_len < MAX_PATH) { - sprintf(paths[(*count)++], "%s/%s.%s.cht", core.cheats_dir, rom_name, ext); + snprintf(paths[(*count)++], MAX_PATH, "%s/%s.%s.cht", core.cheats_dir, rom_name, ext); } else { LOG_info("Path too long, skipping: %s/%s.%s.cht\n", core.cheats_dir, rom_name, ext); } @@ -222,24 +227,25 @@ void Cheat_getPaths(char paths[CHEAT_MAX_PATHS][MAX_PATH], int* count) { // Important: update `sanitized_paths_count` if adding more sanitized variations char rom_name[MAX_PATH]; getDisplayName(game.alt_name, rom_name); - sprintf(paths[(*count)++], "%s/%s.cht", core.cheats_dir, rom_name); // /mnt/SDCARD/Cheats/GB/Super Example World.cht + snprintf(paths[(*count)++], MAX_PATH, "%s/%s.cht", core.cheats_dir, rom_name); // /mnt/SDCARD/Cheats/GB/Super Example World.cht // Respect map.txt: use alias if available // eg. 1941.zip -> 1941: Counter Attack if(getAlias(game.path, rom_name)) - sprintf(paths[(*count)++], "%s/%s.cht", core.cheats_dir, rom_name); // /mnt/SDCARD/Cheats/GB/Super Example World.cht + snprintf(paths[(*count)++], MAX_PATH, "%s/%s.cht", core.cheats_dir, rom_name); // /mnt/SDCARD/Cheats/GB/Super Example World.cht // Santitized alias, ignoring all extra cruft - including Cheat specifics like "(Game Breaker)" etc. // This is a wildcard that may match something unexpected, but also may find something when nothing else does. getAlias(game.path, rom_name); - sprintf(paths[(*count)++], "%s/%s*.cht", core.cheats_dir, rom_name); // /mnt/SDCARD/Cheats/GB/Super Example World*.cht + snprintf(paths[(*count)++], MAX_PATH, "%s/%s*.cht", core.cheats_dir, rom_name); // /mnt/SDCARD/Cheats/GB/Super Example World*.cht // Log all path candidates { - char *list = calloc(*count * (MAX_PATH + 2) + 1, 1); // path + separator for each entry + size_t list_sz = *count * (MAX_PATH + 2) + 1; + char *list = (char *)calloc(list_sz, 1); // path + separator for each entry if (list != NULL) { int i; for (i=0; i<*count; i++) { - strcat(list, paths[i]); - if (i < *count-1) strcat(list, ", "); + strncat(list, paths[i], list_sz - strlen(list) - 1); + if (i < *count-1) strncat(list, ", ", list_sz - strlen(list) - 1); } //LOG_info("Cheat paths to check: %s\n", list); free(list); @@ -278,7 +284,7 @@ bool Cheats_load() { if (strchr(paths[i],'*')) { // Use glob to handle wildcards char glob_pattern[MAX_PATH]; - strcpy(glob_pattern, paths[i]); + snprintf(glob_pattern, sizeof(glob_pattern), "%s", paths[i]); glob_t glob_results; memset(&glob_results, 0, sizeof(glob_t)); @@ -287,7 +293,7 @@ bool Cheats_load() { if (glob_ret == 0 && glob_results.gl_pathc > 0) { for (size_t gi = 0; gi < glob_results.gl_pathc; ++gi) { if (!suffixMatch(".cht", glob_results.gl_pathv[gi])) continue; - strcpy(filename, glob_results.gl_pathv[gi]); + snprintf(filename, sizeof(filename), "%s", glob_results.gl_pathv[gi]); if (exists(filename)) { LOG_info("Found potential cheat file: %s\n", filename); break; @@ -298,7 +304,7 @@ bool Cheats_load() { globfree(&glob_results); if (filename[0] == '\0') continue; // no match } else { - strcpy(filename, paths[i]); + snprintf(filename, sizeof(filename), "%s", paths[i]); if (!exists(filename)) { filename[0] = '\0'; continue; @@ -325,7 +331,7 @@ bool Cheats_load() { goto finish; } - cheatcodes.cheats = calloc(cheatcodes.count, sizeof(struct Cheat)); + cheatcodes.cheats = (struct Cheat *)calloc(cheatcodes.count, sizeof(struct Cheat)); if (!cheatcodes.cheats) { LOG_error("Couldn't allocate memory for cheats\n"); goto finish; @@ -346,4 +352,10 @@ bool Cheats_load() { if (file) fclose(file); + + // Was missing in the C original: the function fell off the end, returning a + // garbage register value (UB in C++, undefined-but-tolerated in C). The + // caller (Core_load) gates Core_applyCheats on this, so return the real + // success flag so cheats apply exactly when a file was parsed successfully. + return success; } diff --git a/workspace/all/minarch/ma_config.c b/workspace/all/minarch/ma_config.cpp similarity index 91% rename from workspace/all/minarch/ma_config.c rename to workspace/all/minarch/ma_config.cpp index 438a0a65b..859db143f 100644 --- a/workspace/all/minarch/ma_config.c +++ b/workspace/all/minarch/ma_config.cpp @@ -4,9 +4,13 @@ #include #include +// Project C headers declare C-linkage symbols defined in the still-C translation +// units; include them as extern "C" so this C++ TU links the unmangled names. +extern "C" { #include "ma_internal.h" #include "ma_options.h" #include "ma_config.h" +} static ButtonMapping button_label_mapping[] = { // used to lookup the retro_id and local btn_id from button name {"NONE", -1, BTN_ID_NONE}, @@ -71,7 +75,7 @@ void setOverclock(int i) { void Config_syncFrontend(char* key, int value) { int i = -1; if (exactMatch(key,config.frontend.options[FE_OPT_SCALING].key)) { - screen_scaling = value; + display_cfg.scaling = value; renderer.dst_p = 0; i = FE_OPT_SCALING; @@ -90,7 +94,7 @@ void Config_syncFrontend(char* key, int value) { i = FE_OPT_AMBIENT; } else if (exactMatch(key,config.frontend.options[FE_OPT_EFFECT].key)) { - screen_effect = value; + display_cfg.effect = value; GFX_setEffect(value); renderer.dst_p = 0; i = FE_OPT_EFFECT; @@ -111,12 +115,12 @@ void Config_syncFrontend(char* key, int value) { } } else if (exactMatch(key,config.frontend.options[FE_OPT_SCREENX].key)) { - cfg_screenx = value; + display_cfg.screenx = value; GFX_setOffsetX(value); i = FE_OPT_SCREENX; } else if (exactMatch(key,config.frontend.options[FE_OPT_SCREENY].key)) { - cfg_screeny = value; + display_cfg.screeny = value; GFX_setOffsetY(value); i = FE_OPT_SCREENY; } @@ -143,7 +147,7 @@ void Config_syncFrontend(char* key, int value) { i = FE_OPT_MAXFF; } else if (exactMatch(key,config.frontend.options[FE_OPT_FF_AUDIO].key)) { - ff_audio = value; + ff.audio = value; i = FE_OPT_FF_AUDIO; } else if (exactMatch(key,config.frontend.options[FE_OPT_REWIND_ENABLE].key)) { @@ -178,12 +182,12 @@ void Config_syncFrontend(char* key, int value) { parsed = strtol(sval, NULL, 10); } switch (i) { - case FE_OPT_REWIND_ENABLE: rewind_cfg_enable = parsed; break; - case FE_OPT_REWIND_BUFFER: rewind_cfg_buffer_mb = parsed; break; - case FE_OPT_REWIND_GRANULARITY: rewind_cfg_granularity = parsed; break; - case FE_OPT_REWIND_AUDIO: rewind_cfg_audio = parsed; break; - case FE_OPT_REWIND_COMPRESSION: rewind_cfg_compress = parsed; break; - case FE_OPT_REWIND_COMPRESSION_ACCEL: rewind_cfg_lz4_acceleration = parsed; break; + case FE_OPT_REWIND_ENABLE: rewind_cfg.enable = parsed; break; + case FE_OPT_REWIND_BUFFER: rewind_cfg.buffer_mb = parsed; break; + case FE_OPT_REWIND_GRANULARITY: rewind_cfg.granularity = parsed; break; + case FE_OPT_REWIND_AUDIO: rewind_cfg.audio = parsed; break; + case FE_OPT_REWIND_COMPRESSION: rewind_cfg.compress = parsed; break; + case FE_OPT_REWIND_COMPRESSION_ACCEL: rewind_cfg.lz4_acceleration = parsed; break; } // Defer Rewind_init until after the explicit init in main(), otherwise FBN crashes if (rewind_init_ready) { @@ -191,11 +195,11 @@ void Config_syncFrontend(char* key, int value) { } if (i==FE_OPT_REWIND_ENABLE) { // ensure runtime toggles don't linger when enabling/disabling feature - rewind_toggle = 0; - rewind_pressed = 0; + rewind_st.toggle = 0; + rewind_st.pressed = 0; Rewind_sync_encode_state(); - rewinding = 0; - ff_paused_by_rewind_hold = 0; + rewind_st.active = 0; + ff.paused_by_rewind_hold = 0; } } } @@ -226,7 +230,7 @@ char** list_files_in_folder(const char* folderPath, int* fileCount, const char* *fileCount = 0; if(defaultElement) { - fileList = malloc(sizeof(char* ) * 2); + fileList = (decltype(fileList))malloc(sizeof(char* ) * 2); fileList[0] = strdup(defaultElement); fileList[1] = NULL; (*fileCount)++; @@ -251,7 +255,7 @@ char** list_files_in_folder(const char* folderPath, int* fileCount, const char* } } - char** temp = realloc(fileList, sizeof(char*) * (*fileCount + 1)); + char** temp = (decltype(temp))realloc(fileList, sizeof(char*) * (*fileCount + 1)); if (!temp) { perror("realloc"); for (int i = 0; i < *fileCount; ++i) { @@ -281,7 +285,7 @@ char** list_files_in_folder(const char* folderPath, int* fileCount, const char* } // NUll terminate the list - char** temp = realloc(fileList, sizeof(char*) * (*fileCount + 1)); + char** temp = (decltype(temp))realloc(fileList, sizeof(char*) * (*fileCount + 1)); if (!temp) { perror("realloc"); for (int i = 0; i < *fileCount; ++i) { @@ -299,9 +303,9 @@ char** list_files_in_folder(const char* folderPath, int* fileCount, const char* // CONFIG_WRITE_ALL, CONFIG_WRITE_GAME defined in minarch_internal.h static void Config_getPath(char* filename, int override) { char device_tag[64] = {0}; - if (config.device_tag) sprintf(device_tag,"-%s",config.device_tag); - if (override) sprintf(filename, "%s/%s%s.cfg", core.config_dir, game.alt_name, device_tag); - else sprintf(filename, "%s/minarch%s.cfg", core.config_dir, device_tag); + if (config.device_tag) snprintf(device_tag, sizeof(device_tag), "-%s",config.device_tag); + if (override) snprintf(filename, MAX_PATH, "%s/%s%s.cfg", core.config_dir, game.alt_name, device_tag); + else snprintf(filename, MAX_PATH, "%s/minarch%s.cfg", core.config_dir, device_tag); LOG_info("Config_getPath %s\n", filename); } void Config_init(void) { @@ -360,8 +364,7 @@ void Config_init(void) { LOG_info("\tbind %s (%s) %i:%i\n", button_name, button_id, local_id, retro_id); // TODO: test this without a final line return - tmp2 = calloc(strlen(button_name)+1, sizeof(char)); - strcpy(tmp2, button_name); + tmp2 = strdup(button_name); ButtonMapping* button = &core_button_mapping[i++]; button->name = tmp2; button->retro = retro_id; @@ -458,8 +461,8 @@ static void Config_readControlsString(char* cfg) { char* tmp; for (int i=0; config.controls[i].name; i++) { ButtonMapping* mapping = &config.controls[i]; - sprintf(key, "bind %s", mapping->name); - sprintf(value, "NONE"); + snprintf(key, sizeof(key), "bind %s", mapping->name); + snprintf(value, sizeof(value), "NONE"); if (!Config_getValue(cfg, key, value, NULL)) continue; if ((tmp = strrchr(value, ':'))) *tmp = '\0'; // this is a binding artifact in default.cfg, ignore @@ -485,8 +488,8 @@ static void Config_readControlsString(char* cfg) { for (int i=0; config.shortcuts[i].name; i++) { ButtonMapping* mapping = &config.shortcuts[i]; - sprintf(key, "bind %s", mapping->name); - sprintf(value, "NONE"); + snprintf(key, sizeof(key), "bind %s", mapping->name); + snprintf(value, sizeof(value), "NONE"); if (!Config_getValue(cfg, key, value, NULL)) continue; @@ -526,7 +529,7 @@ void Config_load(void) { char* system_path = SYSTEM_PATH "/system.cfg"; char device_system_path[MAX_PATH] = {0}; - if (config.device_tag) sprintf(device_system_path, SYSTEM_PATH "/system-%s.cfg", config.device_tag); + if (config.device_tag) snprintf(device_system_path, sizeof(device_system_path), SYSTEM_PATH "/system-%s.cfg", config.device_tag); if (config.device_tag && exists(device_system_path)) { LOG_info("usng device_system_path: %s\n", device_system_path); @@ -540,15 +543,15 @@ void Config_load(void) { char default_path[MAX_PATH]; getEmuPath((char *)core.tag, default_path); char* tmp = strrchr(default_path, '/'); - strcpy(tmp,"/default.cfg"); + snprintf(tmp, sizeof(default_path) - (tmp - default_path), "%s", "/default.cfg"); char device_default_path[MAX_PATH] = {0}; if (config.device_tag) { getEmuPath((char *)core.tag, device_default_path); tmp = strrchr(device_default_path, '/'); char filename[64]; - sprintf(filename,"/default-%s.cfg", config.device_tag); - strcpy(tmp,filename); + snprintf(filename, sizeof(filename), "/default-%s.cfg", config.device_tag); + snprintf(tmp, sizeof(device_default_path) - (tmp - device_default_path), "%s", filename); } if (config.device_tag && exists(device_default_path)) { @@ -590,7 +593,7 @@ void Config_readControls(void) { } void Config_write(int override) { char path[MAX_PATH]; - // sprintf(path, "%s/%s.cfg", core.config_dir, game.alt_name); + // snprintf(path, sizeof(path), "%s/%s.cfg", core.config_dir, game.alt_name); Config_getPath(path, CONFIG_WRITE_GAME); if (!override) { @@ -654,14 +657,14 @@ void Config_write(int override) { void Config_restore(void) { char path[MAX_PATH]; if (config.loaded==CONFIG_GAME) { - if (config.device_tag) sprintf(path, "%s/%s-%s.cfg", core.config_dir, game.alt_name, config.device_tag); - else sprintf(path, "%s/%s.cfg", core.config_dir, game.alt_name); + if (config.device_tag) snprintf(path, sizeof(path), "%s/%s-%s.cfg", core.config_dir, game.alt_name, config.device_tag); + else snprintf(path, sizeof(path), "%s/%s.cfg", core.config_dir, game.alt_name); unlink(path); LOG_info("deleted game config: %s\n", path); } else if (config.loaded==CONFIG_CONSOLE) { - if (config.device_tag) sprintf(path, "%s/minarch-%s.cfg", core.config_dir, config.device_tag); - else sprintf(path, "%s/minarch.cfg", core.config_dir); + if (config.device_tag) snprintf(path, sizeof(path), "%s/minarch-%s.cfg", core.config_dir, config.device_tag); + else snprintf(path, sizeof(path), "%s/minarch.cfg", core.config_dir); unlink(path); LOG_info("deleted console config: %s\n", path); } @@ -708,7 +711,7 @@ void Config_restore(void) { void readShadersPreset(int i) { char shaderspath[MAX_PATH] = {0}; - sprintf(shaderspath, SHADERS_FOLDER "/%s", config.shaders.options[SH_SHADERS_PRESET].values[i]); + snprintf(shaderspath, sizeof(shaderspath), SHADERS_FOLDER "/%s", config.shaders.options[SH_SHADERS_PRESET].values[i]); LOG_info("read shaders preset %s\n",shaderspath); if (exists(shaderspath)) { config.shaders_preset = allocFile(shaderspath); @@ -719,7 +722,7 @@ void readShadersPreset(int i) { void loadShaderSettings(int i) { int menucount = 0; - config.shaderpragmas[i].options = calloc(32 + 1, sizeof(Option)); + config.shaderpragmas[i].options = (decltype(config.shaderpragmas[i].options))calloc(32 + 1, sizeof(Option)); ShaderParam *params = PLAT_getShaderPragmas(i); if(params == NULL) return; for (int j = 0; j < 32; j++) { @@ -739,11 +742,11 @@ void loadShaderSettings(int i) { config.shaderpragmas[i].options[menucount].default_value = params[j].def; int steps = (int)((params[j].max - params[j].min) / params[j].step) + 1; - config.shaderpragmas[i].options[menucount].values = malloc(sizeof(char *) * (steps + 1)); - config.shaderpragmas[i].options[menucount].labels = malloc(sizeof(char *) * (steps + 1)); + config.shaderpragmas[i].options[menucount].values = (decltype(config.shaderpragmas[i].options[menucount].values))malloc(sizeof(char *) * (steps + 1)); + config.shaderpragmas[i].options[menucount].labels = (decltype(config.shaderpragmas[i].options[menucount].labels))malloc(sizeof(char *) * (steps + 1)); for (int s = 0; s < steps; s++) { float val = params[j].min + s * params[j].step; - char *str = malloc(16); + char *str = (decltype(str))malloc(16); snprintf(str, 16, "%.2f", val); config.shaderpragmas[i].options[menucount].values[s] = str; config.shaderpragmas[i].options[menucount].labels[s] = str; @@ -1209,25 +1212,29 @@ ButtonMapping default_button_mapping[] = { // used if pak.cfg doesn't exist or d ButtonMapping core_button_mapping[RETRO_BUTTON_COUNT+1] = {0}; +// Positional in BTN_ID enum order (0..15); C++ rejects the sparse/out-of-order +// [BTN_ID_*]= array designators this used ("non-trivial designated initializers"). +// Order below matches the enum exactly, so indexing device_button_names[BTN_ID_X] +// still yields the same string. static const char* device_button_names[LOCAL_BUTTON_COUNT] = { - [BTN_ID_DPAD_UP] = "UP", - [BTN_ID_DPAD_DOWN] = "DOWN", - [BTN_ID_DPAD_LEFT] = "LEFT", - [BTN_ID_DPAD_RIGHT] = "RIGHT", - [BTN_ID_SELECT] = "SELECT", - [BTN_ID_START] = "START", - [BTN_ID_Y] = "Y", - [BTN_ID_X] = "X", - [BTN_ID_B] = "B", - [BTN_ID_A] = "A", - [BTN_ID_L1] = "L1", - [BTN_ID_R1] = "R1", - [BTN_ID_L2] = "L2", - [BTN_ID_R2] = "R2", - [BTN_ID_L3] = "L3", - [BTN_ID_R3] = "R3", - [BTN_ID_L4] = "L4", - [BTN_ID_R4] = "R4", + "UP", // BTN_ID_DPAD_UP (0) + "DOWN", // BTN_ID_DPAD_DOWN (1) + "LEFT", // BTN_ID_DPAD_LEFT (2) + "RIGHT", // BTN_ID_DPAD_RIGHT (3) + "A", // BTN_ID_A (4) + "B", // BTN_ID_B (5) + "X", // BTN_ID_X (6) + "Y", // BTN_ID_Y (7) + "START", // BTN_ID_START (8) + "SELECT", // BTN_ID_SELECT (9) + "L1", // BTN_ID_L1 (10) + "R1", // BTN_ID_R1 (11) + "L2", // BTN_ID_L2 (12) + "R2", // BTN_ID_R2 (13) + "L3", // BTN_ID_L3 (14) + "R3", // BTN_ID_R3 (15) + "L4", // BTN_ID_L4 (16) + "R4", // BTN_ID_R4 (17) }; @@ -1494,7 +1501,7 @@ struct Config config = { [FE_OPT_REWIND_AUDIO] = { .key = "minarch_rewind_audio", .name = "Rewind audio", - .desc = "Play or mute audio when rewinding.", + .desc = "Play or mute audio when rewind_st.active.", .default_value = MINARCH_DEFAULT_REWIND_AUDIO ? 1 : 0, .value = MINARCH_DEFAULT_REWIND_AUDIO ? 1 : 0, .count = 2, @@ -1696,7 +1703,7 @@ struct Config config = { .values = shupscale_labels, .labels = shupscale_labels, }, - {NULL} + [SH_NONE] = {NULL} }, }, .shaderpragmas = {{ @@ -1727,7 +1734,7 @@ struct Config config = { [SHORTCUT_TOGGLE_TURBO_R] = {"Toggle Turbo R", -1, BTN_ID_NONE, 0}, [SHORTCUT_TOGGLE_TURBO_R2] = {"Toggle Turbo R2", -1, BTN_ID_NONE, 0}, // ----- - {NULL} + [SHORTCUT_COUNT] = {NULL} }, }; diff --git a/workspace/all/minarch/ma_core.c b/workspace/all/minarch/ma_core.c deleted file mode 100644 index 771c8fb10..000000000 --- a/workspace/all/minarch/ma_core.c +++ /dev/null @@ -1,171 +0,0 @@ -#include -#include -#include - -#include "ma_internal.h" -#include "ma_saves.h" -#include "ma_video.h" -#include "ma_audio.h" -#include "ma_input.h" -#include "ma_cheats.h" -#include "ma_core.h" - - -void Core_getName(char* in_name, char* out_name) { - strcpy(out_name, basename(in_name)); - char* tmp = strrchr(out_name, '_'); - tmp[0] = '\0'; -} -void Core_open(const char* core_path, const char* tag_name) { - LOG_info("Core_open\n"); - core.handle = dlopen(core_path, RTLD_LAZY); - - if (!core.handle) LOG_error("%s\n", dlerror()); - - core.init = dlsym(core.handle, "retro_init"); - core.deinit = dlsym(core.handle, "retro_deinit"); - core.get_system_info = dlsym(core.handle, "retro_get_system_info"); - core.get_system_av_info = dlsym(core.handle, "retro_get_system_av_info"); - core.set_controller_port_device = dlsym(core.handle, "retro_set_controller_port_device"); - core.reset = dlsym(core.handle, "retro_reset"); - core.run = dlsym(core.handle, "retro_run"); - core.serialize_size = dlsym(core.handle, "retro_serialize_size"); - core.serialize = dlsym(core.handle, "retro_serialize"); - core.unserialize = dlsym(core.handle, "retro_unserialize"); - core.cheat_reset = dlsym(core.handle, "retro_cheat_reset"); - core.cheat_set = dlsym(core.handle, "retro_cheat_set"); - core.load_game = dlsym(core.handle, "retro_load_game"); - core.load_game_special = dlsym(core.handle, "retro_load_game_special"); - core.unload_game = dlsym(core.handle, "retro_unload_game"); - core.get_region = dlsym(core.handle, "retro_get_region"); - core.get_memory_data = dlsym(core.handle, "retro_get_memory_data"); - core.get_memory_size = dlsym(core.handle, "retro_get_memory_size"); - - void (*set_environment_callback)(retro_environment_t); - void (*set_video_refresh_callback)(retro_video_refresh_t); - void (*set_audio_sample_callback)(retro_audio_sample_t); - void (*set_audio_sample_batch_callback)(retro_audio_sample_batch_t); - void (*set_input_poll_callback)(retro_input_poll_t); - void (*set_input_state_callback)(retro_input_state_t); - - set_environment_callback = dlsym(core.handle, "retro_set_environment"); - set_video_refresh_callback = dlsym(core.handle, "retro_set_video_refresh"); - set_audio_sample_callback = dlsym(core.handle, "retro_set_audio_sample"); - set_audio_sample_batch_callback = dlsym(core.handle, "retro_set_audio_sample_batch"); - set_input_poll_callback = dlsym(core.handle, "retro_set_input_poll"); - set_input_state_callback = dlsym(core.handle, "retro_set_input_state"); - - struct retro_system_info info = {}; - core.get_system_info(&info); - - - LOG_info("Block Extract: %d\n", info.block_extract); - - Core_getName((char*)core_path, (char*)core.name); - sprintf((char*)core.version, "%s (%s)", info.library_name, info.library_version); - strcpy((char*)core.tag, tag_name); - strcpy((char*)core.extensions, info.valid_extensions); - - core.need_fullpath = info.need_fullpath; - - LOG_info("core: %s version: %s tag: %s (valid_extensions: %s need_fullpath: %i)\n", core.name, core.version, core.tag, info.valid_extensions, info.need_fullpath); - - sprintf((char*)core.config_dir, USERDATA_PATH "/%s-%s", core.tag, core.name); - sprintf((char*)core.states_dir, SHARED_USERDATA_PATH "/%s-%s", core.tag, core.name); - sprintf((char*)core.saves_dir, SDCARD_PATH "/Saves/%s", core.tag); - sprintf((char*)core.bios_dir, SDCARD_PATH "/Bios/%s", core.tag); - sprintf((char*)core.cheats_dir, SDCARD_PATH "/Cheats/%s", core.tag); - sprintf((char*)core.overlays_dir, SDCARD_PATH "/Overlays/%s", core.tag); - - char cmd[512]; - sprintf(cmd, "mkdir -p \"%s\"; mkdir -p \"%s\"", core.config_dir, core.states_dir); - system(cmd); - - set_environment_callback(environment_callback); - set_video_refresh_callback(video_refresh_callback); - set_audio_sample_callback(audio_sample_callback); - set_audio_sample_batch_callback(audio_sample_batch_callback); - set_input_poll_callback(input_poll_callback); - set_input_state_callback(input_state_callback); -} -void Core_init(void) { - LOG_info("Core_init\n"); - core.init(); - core.initialized = 1; -} - -void Core_applyCheats(struct Cheats *cheats) -{ - if (!cheats) - return; - - if (!core.cheat_reset || !core.cheat_set) - return; - - core.cheat_reset(); - for (int i = 0; i < cheats->count; i++) { - if (cheats->cheats[i].enabled) { - core.cheat_set(i, cheats->cheats[i].enabled, cheats->cheats[i].code); - } - } -} - -int Core_updateAVInfo(void) { - struct retro_system_av_info av_info = {}; - core.get_system_av_info(&av_info); - - double a = av_info.geometry.aspect_ratio; - if (a<=0) a = (double)av_info.geometry.base_width / av_info.geometry.base_height; - - int changed = (core.fps != av_info.timing.fps || core.sample_rate != av_info.timing.sample_rate || core.aspect_ratio != a); - - core.fps = av_info.timing.fps; - core.sample_rate = av_info.timing.sample_rate; - core.aspect_ratio = a; - - if (changed) LOG_info("aspect_ratio: %f (%ix%i) fps: %f\n", a, av_info.geometry.base_width,av_info.geometry.base_height, core.fps); - - return changed; -} - -void Core_load(void) { - LOG_info("Core_load\n"); - struct retro_game_info game_info; - game_info.path = game.tmp_path[0]?game.tmp_path:game.path; - game_info.data = game.data; - game_info.size = game.size; - LOG_info("game path: %s (%i)\n", game_info.path, game.size); - core.load_game(&game_info); - - if (Cheats_load()) - Core_applyCheats(&cheatcodes); - - SRAM_read(); - RTC_read(); - // NOTE: must be called after core.load_game! - core.set_controller_port_device(0, RETRO_DEVICE_JOYPAD); // set a default, may update after loading configs - Core_updateAVInfo(); -} -void Core_reset(void) { - core.reset(); - Rewind_on_state_change(); -} -void Core_unload(void) { - // Disabling this is a dumb hack for bluetooth, we should really be using - // bluealsa with --keep-alive=-1 - but SDL wont reconnect the stream on next start. - // Reenable as soon as we have a more recent SDL available, if ever. - //SND_quit(); -} -void Core_quit(void) { - if (core.initialized) { - SRAM_write(); - Cheats_free(); - RTC_write(); - core.unload_game(); - core.deinit(); - core.initialized = 0; - } -} -void Core_close(void) { - if (core.handle) dlclose(core.handle); -} diff --git a/workspace/all/minarch/ma_core.cpp b/workspace/all/minarch/ma_core.cpp new file mode 100644 index 000000000..4fab04ed2 --- /dev/null +++ b/workspace/all/minarch/ma_core.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include + +// Project C headers declare C-linkage symbols (core, cheatcodes, the retro_* +// callbacks, LOG_*, …) defined in the still-C translation units. Include them +// as extern "C" so this C++ unit links against the unmangled names. dlsym() +// returns void*, which C++ won't implicitly convert to a function pointer, so +// each core.* / set_*_callback assignment casts to the field's decltype. +// (Same header pattern as ma_audio.cpp.) +extern "C" { +#include "ma_internal.h" +#include "ma_saves.h" +#include "ma_video.h" +#include "ma_audio.h" +#include "ma_input.h" +#include "ma_cheats.h" +#include "ma_core.h" +} + + +void Core_getName(char* in_name, char* out_name, size_t out_size) { + snprintf(out_name, out_size, "%s", basename(in_name)); + char* tmp = strrchr(out_name, '_'); + tmp[0] = '\0'; +} +void Core_open(const char* core_path, const char* tag_name) { + LOG_info("Core_open\n"); + core.handle = dlopen(core_path, RTLD_LAZY); + + if (!core.handle) LOG_error("%s\n", dlerror()); + + core.init = (decltype(core.init))dlsym(core.handle, "retro_init"); + core.deinit = (decltype(core.deinit))dlsym(core.handle, "retro_deinit"); + core.get_system_info = (decltype(core.get_system_info))dlsym(core.handle, "retro_get_system_info"); + core.get_system_av_info = (decltype(core.get_system_av_info))dlsym(core.handle, "retro_get_system_av_info"); + core.set_controller_port_device = (decltype(core.set_controller_port_device))dlsym(core.handle, "retro_set_controller_port_device"); + core.reset = (decltype(core.reset))dlsym(core.handle, "retro_reset"); + core.run = (decltype(core.run))dlsym(core.handle, "retro_run"); + core.serialize_size = (decltype(core.serialize_size))dlsym(core.handle, "retro_serialize_size"); + core.serialize = (decltype(core.serialize))dlsym(core.handle, "retro_serialize"); + core.unserialize = (decltype(core.unserialize))dlsym(core.handle, "retro_unserialize"); + core.cheat_reset = (decltype(core.cheat_reset))dlsym(core.handle, "retro_cheat_reset"); + core.cheat_set = (decltype(core.cheat_set))dlsym(core.handle, "retro_cheat_set"); + core.load_game = (decltype(core.load_game))dlsym(core.handle, "retro_load_game"); + core.load_game_special = (decltype(core.load_game_special))dlsym(core.handle, "retro_load_game_special"); + core.unload_game = (decltype(core.unload_game))dlsym(core.handle, "retro_unload_game"); + core.get_region = (decltype(core.get_region))dlsym(core.handle, "retro_get_region"); + core.get_memory_data = (decltype(core.get_memory_data))dlsym(core.handle, "retro_get_memory_data"); + core.get_memory_size = (decltype(core.get_memory_size))dlsym(core.handle, "retro_get_memory_size"); + + void (*set_environment_callback)(retro_environment_t); + void (*set_video_refresh_callback)(retro_video_refresh_t); + void (*set_audio_sample_callback)(retro_audio_sample_t); + void (*set_audio_sample_batch_callback)(retro_audio_sample_batch_t); + void (*set_input_poll_callback)(retro_input_poll_t); + void (*set_input_state_callback)(retro_input_state_t); + + set_environment_callback = (decltype(set_environment_callback))dlsym(core.handle, "retro_set_environment"); + set_video_refresh_callback = (decltype(set_video_refresh_callback))dlsym(core.handle, "retro_set_video_refresh"); + set_audio_sample_callback = (decltype(set_audio_sample_callback))dlsym(core.handle, "retro_set_audio_sample"); + set_audio_sample_batch_callback = (decltype(set_audio_sample_batch_callback))dlsym(core.handle, "retro_set_audio_sample_batch"); + set_input_poll_callback = (decltype(set_input_poll_callback))dlsym(core.handle, "retro_set_input_poll"); + set_input_state_callback = (decltype(set_input_state_callback))dlsym(core.handle, "retro_set_input_state"); + + struct retro_system_info info = {}; + core.get_system_info(&info); + + + LOG_info("Block Extract: %d\n", info.block_extract); + + Core_getName((char*)core_path, (char*)core.name, sizeof(core.name)); + snprintf((char*)core.version, sizeof(core.version), "%s (%s)", info.library_name, info.library_version); + snprintf((char*)core.tag, sizeof(core.tag), "%s", tag_name); + snprintf((char*)core.extensions, sizeof(core.extensions), "%s", info.valid_extensions); + + core.need_fullpath = info.need_fullpath; + + LOG_info("core: %s version: %s tag: %s (valid_extensions: %s need_fullpath: %i)\n", core.name, core.version, core.tag, info.valid_extensions, info.need_fullpath); + + snprintf((char*)core.config_dir, sizeof(core.config_dir), USERDATA_PATH "/%s-%s", core.tag, core.name); + snprintf((char*)core.states_dir, sizeof(core.states_dir), SHARED_USERDATA_PATH "/%s-%s", core.tag, core.name); + snprintf((char*)core.saves_dir, sizeof(core.saves_dir), SDCARD_PATH "/Saves/%s", core.tag); + snprintf((char*)core.bios_dir, sizeof(core.bios_dir), SDCARD_PATH "/Bios/%s", core.tag); + snprintf((char*)core.cheats_dir, sizeof(core.cheats_dir), SDCARD_PATH "/Cheats/%s", core.tag); + snprintf((char*)core.overlays_dir, sizeof(core.overlays_dir), SDCARD_PATH "/Overlays/%s", core.tag); + + char cmd[512]; + snprintf(cmd, sizeof(cmd), "mkdir -p \"%s\"; mkdir -p \"%s\"", core.config_dir, core.states_dir); + system(cmd); + + set_environment_callback(environment_callback); + set_video_refresh_callback(video_refresh_callback); + set_audio_sample_callback(audio_sample_callback); + set_audio_sample_batch_callback(audio_sample_batch_callback); + set_input_poll_callback(input_poll_callback); + set_input_state_callback(input_state_callback); +} +void Core_init(void) { + LOG_info("Core_init\n"); + core.init(); + core.initialized = 1; +} + +void Core_applyCheats(struct Cheats *cheats) +{ + if (!cheats) + return; + + if (!core.cheat_reset || !core.cheat_set) { + LOG_info("Core_applyCheats: core does not support cheats (retro_cheat_%s unavailable)\n", + !core.cheat_set ? "set" : "reset"); + return; + } + + // libretro is a C ABI, but some cores (notably Mednafen/supafaust) throw a C++ + // exception out of retro_cheat_set when handed a cheat code they can't parse + // (e.g. RetroArch Game Genie format "CB01-54DF+..." on a core that expects raw + // address/value pairs). An uncaught throw unwinds back across the boundary into + // the frontend and hits std::terminate -> the whole emulator aborts. Guard each + // core call so a single bad cheat is skipped and logged, and the game survives. + try { + core.cheat_reset(); + } catch (const std::exception& e) { + LOG_error("Core_applyCheats: cheat_reset threw: %s\n", e.what()); + return; + } catch (...) { + LOG_error("Core_applyCheats: cheat_reset threw an unknown exception\n"); + return; + } + + for (int i = 0; i < cheats->count; i++) { + if (!cheats->cheats[i].enabled) + continue; + const char* name = cheats->cheats[i].name ? cheats->cheats[i].name : ""; + const char* code = cheats->cheats[i].code ? cheats->cheats[i].code : ""; + try { + // Most cores parse the code here; some (Mednafen/supafaust) defer to + // retro_run() — that deferred throw is caught in run_frame(). + core.cheat_set(i, cheats->cheats[i].enabled, cheats->cheats[i].code); + } catch (const std::exception& e) { + LOG_error("Core_applyCheats: core rejected cheat %d \"%s\" (%s): %s\n", i, name, code, e.what()); + } catch (...) { + LOG_error("Core_applyCheats: core rejected cheat %d \"%s\" (%s): unknown exception\n", i, name, code); + } + } +} + +int Core_updateAVInfo(void) { + struct retro_system_av_info av_info = {}; + core.get_system_av_info(&av_info); + + double a = av_info.geometry.aspect_ratio; + if (a<=0) a = (double)av_info.geometry.base_width / av_info.geometry.base_height; + + int changed = (core.fps != av_info.timing.fps || core.sample_rate != av_info.timing.sample_rate || core.aspect_ratio != a); + + core.fps = av_info.timing.fps; + core.sample_rate = av_info.timing.sample_rate; + core.aspect_ratio = a; + + if (changed) LOG_info("aspect_ratio: %f (%ix%i) fps: %f\n", a, av_info.geometry.base_width,av_info.geometry.base_height, core.fps); + + return changed; +} + +void Core_load(void) { + LOG_info("Core_load\n"); + struct retro_game_info game_info; + game_info.path = game.tmp_path[0]?game.tmp_path:game.path; + game_info.data = game.data; + game_info.size = game.size; + LOG_info("game path: %s (%i)\n", game_info.path, game.size); + try { + core.load_game(&game_info); + } catch (const std::exception& e) { + LOG_error("Core_load: retro_load_game threw: %s\n", e.what()); + exit(1); + } catch (...) { + LOG_error("Core_load: retro_load_game threw an unknown exception\n"); + exit(1); + } + + if (Cheats_load()) + Core_applyCheats(&cheatcodes); + + SRAM_read(); + RTC_read(); + // NOTE: must be called after core.load_game! + core.set_controller_port_device(0, RETRO_DEVICE_JOYPAD); // set a default, may update after loading configs + Core_updateAVInfo(); +} +void Core_reset(void) { + core.reset(); + Rewind_on_state_change(); +} +void Core_unload(void) { + // Disabling this is a dumb hack for bluetooth, we should really be using + // bluealsa with --keep-alive=-1 - but SDL wont reconnect the stream on next start. + // Reenable as soon as we have a more recent SDL available, if ever. + //SND_quit(); +} +void Core_quit(void) { + if (core.initialized) { + SRAM_write(); + Cheats_free(); + RTC_write(); + core.unload_game(); + core.deinit(); + core.initialized = 0; + } +} +void Core_close(void) { + if (core.handle) dlclose(core.handle); +} diff --git a/workspace/all/minarch/ma_core.h b/workspace/all/minarch/ma_core.h index 482e15170..b361d20b4 100644 --- a/workspace/all/minarch/ma_core.h +++ b/workspace/all/minarch/ma_core.h @@ -1,6 +1,6 @@ #pragma once -void Core_getName(char* in_name, char* out_name); +void Core_getName(char* in_name, char* out_name, size_t out_size); void Core_open(const char* core_path, const char* tag_name); void Core_init(void); void Core_applyCheats(struct Cheats *cheats); diff --git a/workspace/all/minarch/ma_environment.c b/workspace/all/minarch/ma_environment.cpp similarity index 98% rename from workspace/all/minarch/ma_environment.c rename to workspace/all/minarch/ma_environment.cpp index 3f4da0452..a656f2791 100644 --- a/workspace/all/minarch/ma_environment.c +++ b/workspace/all/minarch/ma_environment.cpp @@ -1,10 +1,15 @@ #include +// Project C headers declare C-linkage symbols (core, config, LOG_*, …) defined +// in the still-C translation units. Include them as extern "C" so this C++ unit +// links against the unmangled names. (Same pattern as ma_audio.cpp.) +extern "C" { #include "ma_internal.h" #include "ma_options.h" #include "ma_input.h" #include "ra_integration.h" #include "ma_environment.h" +} static bool set_rumble_state(unsigned port, enum retro_rumble_effect effect, uint16_t strength) { // TODO: handle other args? not sure I can diff --git a/workspace/all/minarch/ma_frontend_opts.c b/workspace/all/minarch/ma_frontend_opts.cpp similarity index 83% rename from workspace/all/minarch/ma_frontend_opts.c rename to workspace/all/minarch/ma_frontend_opts.cpp index 3c786b96c..243c277b1 100644 --- a/workspace/all/minarch/ma_frontend_opts.c +++ b/workspace/all/minarch/ma_frontend_opts.cpp @@ -1,13 +1,28 @@ +#include +#include +#include +#include + +// Project C headers declare C-linkage symbols defined in the still-C translation +// units; include them as extern "C" so this C++ TU links the unmangled names. +extern "C" { #include "ma_internal.h" #include "ma_frontend_opts.h" #include "ma_cheats.h" #include "ra_integration.h" #include "notification.h" +} -#include -#include -#include -#include +// g++ 8.3 rejects taking the address of an SDL_Rect compound literal in C++ +// ("taking address of temporary"). RectArg wraps one in a class temporary whose +// lifetime spans the enclosing call and converts to SDL_Rect*, so a former +// RectArg{ SDL_Rect{...} } becomes RectArg{ SDL_Rect{...} }. Verified on tg5040 g++ 8.3. +struct RectArg { SDL_Rect r; operator SDL_Rect*() { return &r; } }; + +// g++ 8.3 silently drops the backing storage of a (char*[]){...} temporary array +// passed as an argument, so shared button-hint lists get real static storage. +static const char* BACK_PAIR[] = { "B", "BACK", NULL }; +static const char* OKAY_PAIR[] = { "A", "OKAY", NULL }; int Menu_messageWithFont(char* message, char** pairs, TTF_Font* f) { GFX_setMode(MODE_MAIN); @@ -22,7 +37,7 @@ int Menu_messageWithFont(char* message, char** pairs, TTF_Font* f) { GFX_clear(screen); - GFX_blitMessage(f, message, screen, &(SDL_Rect){SCALE1(PADDING),SCALE1(PADDING),screen->w-SCALE1(2*PADDING),screen->h-SCALE1(PILL_SIZE+PADDING)}); + GFX_blitMessage(f, message, screen, RectArg{ SDL_Rect{SCALE1(PADDING),SCALE1(PADDING),screen->w-SCALE1(2*PADDING),screen->h-SCALE1(PILL_SIZE+PADDING)} }); GFX_blitButtonGroup(pairs, 0, screen, 1); GFX_flip(screen); dirty = 0; @@ -35,7 +50,7 @@ int Menu_messageWithFont(char* message, char** pairs, TTF_Font* f) { } int Menu_message(char* message, char** pairs) { - return Menu_messageWithFont(message, pairs, font.medium); + return Menu_messageWithFont(message, pairs, GFX_getFonts()->medium); } static int MenuList_freeItems(MenuList* list, int i) { @@ -51,8 +66,8 @@ static int OptionFrontend_optionChanged(MenuList* list, int i) { } static MenuList OptionFrontend_menu = { .type = MENU_VAR, - .on_change = OptionFrontend_optionChanged, .items = NULL, + .on_change = OptionFrontend_optionChanged, }; int OptionFrontend_openMenu(MenuList* list, int i) { if (OptionFrontend_menu.items==NULL) { @@ -63,7 +78,7 @@ int OptionFrontend_openMenu(MenuList* list, int i) { if (!config.frontend.options[i].lock) enabled_count += 1; } config.frontend.enabled_count = enabled_count; - config.frontend.enabled_options = calloc(enabled_count+1, sizeof(Option*)); + config.frontend.enabled_options = (decltype(config.frontend.enabled_options))calloc(enabled_count+1, sizeof(Option*)); int j = 0; for (int i=0; ikey); - if (option->full) return Menu_messageWithFont(option->full, (char*[]){ "B","BACK", NULL }, font.medium); + if (option->full) return Menu_messageWithFont(option->full, (char**)BACK_PAIR, GFX_getFonts()->medium); else return MENU_CALLBACK_NOP; } } @@ -156,7 +171,7 @@ int OptionEmulator_openMenu(MenuList* list, int index) { } int enabled_count = 0; - config.core.enabled_options = calloc(config.core.count + 1, sizeof(Option*)); + config.core.enabled_options = (decltype(config.core.enabled_options))calloc(config.core.count + 1, sizeof(Option*)); for (int i=0; icategory) { - Menu_message("This category has no options.", (char*[]){ "B","BACK", NULL }); + Menu_message("This category has no options.", (char**)BACK_PAIR); } else { - Menu_message("This core has no options.", (char*[]){ "B","BACK", NULL }); + Menu_message("This core has no options.", (char**)BACK_PAIR); } } @@ -286,9 +301,9 @@ static MenuList OptionControls_menu = { .desc = "Press A to set and X to clear." "\nSupports single button and MENU+button." // TODO: not supported on nano because POWER doubles as MENU , + .items = NULL, .on_confirm = OptionControls_bind, .on_change = OptionControls_unbind, - .items = NULL }; int OptionControls_openMenu(MenuList* list, int i) { @@ -297,7 +312,7 @@ int OptionControls_openMenu(MenuList* list, int i) { if (OptionControls_menu.items==NULL) { // TODO: where do I free this? - OptionControls_menu.items = calloc(RETRO_BUTTON_COUNT+1+has_custom_controllers, sizeof(MenuItem)); + OptionControls_menu.items = (decltype(OptionControls_menu.items))calloc(RETRO_BUTTON_COUNT+1+has_custom_controllers, sizeof(MenuItem)); int k = 0; if (has_custom_controllers) { @@ -387,9 +402,9 @@ static MenuList OptionShortcuts_menu = { .desc = "Press A to set and X to clear." "\nSupports single button and MENU+button." // TODO: not supported on nano because POWER doubles as MENU , + .items = NULL, .on_confirm = OptionShortcuts_bind, .on_change = OptionShortcuts_unbind, - .items = NULL }; char* getSaveDesc(void) { switch (config.loaded) { @@ -402,7 +417,7 @@ char* getSaveDesc(void) { int OptionShortcuts_openMenu(MenuList* list, int i) { if (OptionShortcuts_menu.items==NULL) { // TODO: where do I free this? I guess I don't :sweat_smile: - OptionShortcuts_menu.items = calloc(SHORTCUT_COUNT+1, sizeof(MenuItem)); + OptionShortcuts_menu.items = (decltype(OptionShortcuts_menu.items))calloc(SHORTCUT_COUNT+1, sizeof(MenuItem)); for (int j=0; config.shortcuts[j].name; j++) { ButtonMapping* button = &config.shortcuts[j]; MenuItem* item = &OptionShortcuts_menu.items[j]; @@ -447,19 +462,20 @@ static int OptionSaveChanges_onConfirm(MenuList* list, int i) { break; } } - Menu_message(message, (char*[]){ "A","OKAY", NULL }); + Menu_message(message, (char**)OKAY_PAIR); OptionSaveChanges_updateDesc(); return MENU_CALLBACK_EXIT; } +static MenuItem OptionSaveChanges_items[] = { + {"Save for console"}, + {"Save for game"}, + {"Restore defaults"}, + {NULL}, +}; static MenuList OptionSaveChanges_menu = { .type = MENU_LIST, + .items = OptionSaveChanges_items, .on_confirm = OptionSaveChanges_onConfirm, - .items = (MenuItem[]){ - {"Save for console"}, - {"Save for game"}, - {"Restore defaults"}, - {NULL}, - } }; int OptionSaveChanges_openMenu(MenuList* list, int i) { OptionSaveChanges_updateDesc(); @@ -494,34 +510,33 @@ static int OptionCheats_optionDetail(MenuList* list, int i) { MenuItem* item = &list->items[i]; struct Cheat *cheat = &cheatcodes.cheats[i]; if (cheat->info) - return Menu_message((char*)cheat->info, (char*[]){ "B","BACK", NULL }); + return Menu_message((char*)cheat->info, (char**)BACK_PAIR); else return MENU_CALLBACK_NOP; } static MenuList OptionCheats_menu = { .type = MENU_FIXED, + .items = NULL, .on_confirm = OptionCheats_optionDetail, // TODO: this needs pagination to be truly useful .on_change = OptionCheats_optionChanged, - .items = NULL, }; int OptionCheats_openMenu(MenuList* list, int i) { if (OptionCheats_menu.items == NULL) { // populate - OptionCheats_menu.items = calloc(cheatcodes.count + 1, sizeof(MenuItem)); + OptionCheats_menu.items = (decltype(OptionCheats_menu.items))calloc(cheatcodes.count + 1, sizeof(MenuItem)); for (int i = 0; iname) + 1; - item->name = calloc(len, sizeof(char)); - strcpy(item->name, cheat->name); + item->name = strdup(cheat->name); if(cheat->info) { len = strlen(cheat->info) + 1; - item->desc = calloc(len, sizeof(char)); + item->desc = (decltype(item->desc))calloc(len, sizeof(char)); strncpy(item->desc, cheat->info, len); - GFX_wrapText(font.tiny, item->desc, DEVICE_WIDTH - SCALE1(2*PADDING), 2); + GFX_wrapText(GFX_getFonts()->tiny, item->desc, DEVICE_WIDTH - SCALE1(2*PADDING), 2); } item->value = cheat->enabled; @@ -529,13 +544,13 @@ int OptionCheats_openMenu(MenuList* list, int i) { } } else { - // update + // update: mirror the data model unconditionally. run_frame's cheat-fault + // recovery can clear cheat->enabled behind the menu's back, so we must sync + // disabled cheats too (the old `if(!enabled) continue` left a stale "on" + // checkbox after a cheat was auto-disabled). for (int j = 0; j < cheatcodes.count; j++) { struct Cheat *cheat = &cheatcodes.cheats[j]; MenuItem *item = &OptionCheats_menu.items[j]; - // I guess that makes sense, nobody is changing these but us - what about state restore? - if(!cheat->enabled) - continue; item->value = cheat->enabled; } } @@ -557,7 +572,7 @@ int OptionCheats_openMenu(MenuList* list, int i) { const char* title = "No cheat file loaded.\n\n"; size_t title_len = strlen(title); - strcpy(cheats_path, title); // Use strcpy for first string + snprintf(cheats_path, sizeof(cheats_path), "%s", title); // Use strcpy for first string size_t current_len = title_len; for (int i = 0; i < count && i < CHEAT_MAX_DISPLAY_PATHS; i++) { @@ -575,21 +590,21 @@ int OptionCheats_openMenu(MenuList* list, int i) { // Check if adding this path would overflow the buffer if (current_len + p_len + newline_len >= CHEAT_MAX_LIST_LENGTH) { LOG_info("Cheats path buffer would overflow, truncating list\n"); - strcat(cheats_path, "..."); + strncat(cheats_path, "...", sizeof(cheats_path) - strlen(cheats_path) - 1); break; } // Safe to append - strcat(cheats_path, p); + strncat(cheats_path, p, sizeof(cheats_path) - strlen(cheats_path) - 1); current_len += p_len; if (i < count - 1) { - strcat(cheats_path, "\n"); + strncat(cheats_path, "\n", sizeof(cheats_path) - strlen(cheats_path) - 1); current_len += 1; } } - Menu_messageWithFont(cheats_path, (char*[]){ "B","BACK", NULL }, font.small); + Menu_messageWithFont(cheats_path, (char**)BACK_PAIR, GFX_getFonts()->small); } return MENU_CALLBACK_NOP; @@ -618,9 +633,9 @@ static int OptionPragmas_optionChanged(MenuList* list, int i) { static MenuList PragmasOptions_menu = { .type = MENU_FIXED, + .items = NULL, .on_confirm = NULL, .on_change = OptionPragmas_optionChanged, - .items = NULL }; static int OptionPragmas_openMenu(MenuList* list, int i) { int progressCount = 0; @@ -628,7 +643,7 @@ static int OptionPragmas_openMenu(MenuList* list, int i) { for (int y=0; y < config.shaders.options[SH_NROFSHADERS].value; y++) { totalcount += config.shaderpragmas[y].count; } - PragmasOptions_menu.items = calloc(totalcount + 1, sizeof(MenuItem)); + PragmasOptions_menu.items = (decltype(PragmasOptions_menu.items))calloc(totalcount + 1, sizeof(MenuItem)); for (int y=0; y < config.shaders.options[SH_NROFSHADERS].value; y++) { for (int j = 0; j < config.shaderpragmas[y].count; j++) { MenuItem* item = &PragmasOptions_menu.items[progressCount]; @@ -646,7 +661,7 @@ static int OptionPragmas_openMenu(MenuList* list, int i) { if (PragmasOptions_menu.items[0].name) { Menu_options(&PragmasOptions_menu); } else { - Menu_message("No extra settings found", (char*[]){"B", "BACK", NULL}); + Menu_message("No extra settings found", (char**)BACK_PAIR); } return MENU_CALLBACK_NOP; @@ -681,9 +696,9 @@ static int OptionShaders_optionChanged(MenuList* list, int i) { static MenuList ShaderOptions_menu = { .type = MENU_FIXED, + .items = NULL, .on_confirm = NULL, .on_change = OptionShaders_optionChanged, - .items = NULL }; int OptionShaders_openMenu(MenuList* list, int i) { @@ -692,11 +707,11 @@ int OptionShaders_openMenu(MenuList* list, int i) { // Check if folder read failed or no files found if (!filelist || filecount == 0) { - Menu_message("No shaders available\n/Shaders folder or shader files not found", (char*[]){"B", "BACK", NULL}); + Menu_message("No shaders available\n/Shaders folder or shader files not found", (char**)BACK_PAIR); return MENU_CALLBACK_NOP; } - ShaderOptions_menu.items = calloc(config.shaders.count + 1, sizeof(MenuItem)); + ShaderOptions_menu.items = (decltype(ShaderOptions_menu.items))calloc(config.shaders.count + 1, sizeof(MenuItem)); for (int i = 0; i < config.shaders.count; i++) { MenuItem* item = &ShaderOptions_menu.items[i]; Option* configitem = &config.shaders.options[i]; @@ -721,7 +736,7 @@ int OptionShaders_openMenu(MenuList* list, int i) { if (ShaderOptions_menu.items[0].name) { Menu_options(&ShaderOptions_menu); } else { - Menu_message("No shaders available\n/Shaders folder or shader files not found", (char*[]){"B", "BACK", NULL}); + Menu_message("No shaders available\n/Shaders folder or shader files not found", (char**)BACK_PAIR); } return MENU_CALLBACK_NOP; diff --git a/workspace/all/minarch/ma_game.c b/workspace/all/minarch/ma_game.cpp similarity index 80% rename from workspace/all/minarch/ma_game.c rename to workspace/all/minarch/ma_game.cpp index d971c388f..6e6b6c182 100644 --- a/workspace/all/minarch/ma_game.c +++ b/workspace/all/minarch/ma_game.cpp @@ -6,8 +6,13 @@ #include #include +// Project C headers declare C-linkage symbols (core, game, LOG_*, …) defined in +// the still-C translation units. Include them as extern "C" so this C++ unit +// links against the unmangled names. (Same pattern as ma_audio.cpp.) +extern "C" { #include "ma_internal.h" #include "ma_game.h" +} struct Game game; struct retro_disk_control_ext_callback disk_control_ext; @@ -17,9 +22,9 @@ void Game_open(char* path) { int skipzip = 0; memset(&game, 0, sizeof(game)); - strcpy((char*)game.path, path); - strcpy((char*)game.name, strrchr(path, '/')+1); - strcpy((char*)game.alt_name, game.name); // default it + snprintf(game.path, sizeof(game.path), "%s", path); + snprintf(game.name, sizeof(game.name), "%s", strrchr(path, '/')+1); + snprintf(game.alt_name, sizeof(game.alt_name), "%s", game.name); // default it // check first if the rom already is alive in tmp folder if so skip unzipping shit char tmpfldr[255]; @@ -30,11 +35,11 @@ void Game_open(char* path) { struct stat st; if (stat(tmppath, &st) == 0 && st.st_size > 0) { printf("File exists skipping unzipping and setting game.tmp_path: %s\n", tmppath); - strcpy((char*)game.tmp_path, tmppath); + snprintf(game.tmp_path, sizeof(game.tmp_path), "%s", tmppath); skipzip = 1; // Update the game name to the extracted file name instead of the zip name if (CFG_getUseExtractedFileName()) - strcpy((char*)game.alt_name, strrchr(game.tmp_path, '/')+1); + snprintf(game.alt_name, sizeof(game.alt_name), "%s", strrchr(game.tmp_path, '/')+1); } else { printf("File exists but is empty or inaccessible, will re-extract: %s\n", tmppath); } @@ -51,7 +56,7 @@ void Game_open(char* path) { char* ext; char exts[128]; char* extensions[32]; - strcpy(exts,core.extensions); + snprintf(exts, sizeof(exts), "%s", core.extensions); while ((ext=strtok(i?NULL:exts,"|"))) { extensions[i++] = ext; if (!strcmp("zip", ext)) { @@ -70,7 +75,7 @@ void Game_open(char* path) { return; // Update the game name to the extracted file name instead of the zip name if (CFG_getUseExtractedFileName()) - strcpy((char*)game.alt_name, strrchr(game.tmp_path, '/')+1); + snprintf(game.alt_name, sizeof(game.alt_name), "%s", strrchr(game.tmp_path, '/')+1); } else { LOG_info("Core can handle zip file: %s\n", game.path); @@ -109,28 +114,28 @@ void Game_open(char* path) { char base_path[256]; char dir_name[256]; - strcpy(m3u_path, game.path); + snprintf(m3u_path, sizeof(m3u_path), "%s", game.path); tmp = strrchr(m3u_path, '/') + 1; tmp[0] = '\0'; - strcpy(base_path, m3u_path); + snprintf(base_path, sizeof(base_path), "%s", m3u_path); tmp = strrchr(m3u_path, '/'); tmp[0] = '\0'; tmp = strrchr(m3u_path, '/'); - strcpy(dir_name, tmp); + snprintf(dir_name, sizeof(dir_name), "%s", tmp); tmp = m3u_path + strlen(m3u_path); - strcpy(tmp, dir_name); + snprintf(tmp, sizeof(m3u_path) - (tmp - m3u_path), "%s", dir_name); tmp = m3u_path + strlen(m3u_path); - strcpy(tmp, ".m3u"); + snprintf(tmp, sizeof(m3u_path) - (tmp - m3u_path), "%s", ".m3u"); if (exists(m3u_path)) { - strcpy(game.m3u_path, m3u_path); - strcpy((char*)game.name, strrchr(m3u_path, '/')+1); - strcpy((char*)game.alt_name, game.name); // default it + snprintf(game.m3u_path, sizeof(game.m3u_path), "%s", m3u_path); + snprintf(game.name, sizeof(game.name), "%s", strrchr(m3u_path, '/')+1); + snprintf(game.alt_name, sizeof(game.alt_name), "%s", game.name); // default it } game.is_open = 1; @@ -186,12 +191,12 @@ int extract_zip(char** extensions) if (zip_stat_index(za, i, 0, &sb) == 0) { len = strlen(sb.name); if (sb.name[len - 1] == '/') { - sprintf(game.tmp_path, "%s/%s", tmp_dirname, basename((char*)sb.name)); + snprintf(game.tmp_path, sizeof(game.tmp_path), "%s/%s", tmp_dirname, basename((char*)sb.name)); } else { int found = 0; char extension[8]; for (int e=0; extensions[e]; e++) { - sprintf(extension, ".%s", extensions[e]); + snprintf(extension, sizeof(extension), ".%s", extensions[e]); if (suffixMatch(extension, sb.name)) { found = 1; break; @@ -199,7 +204,7 @@ int extract_zip(char** extensions) } if (!found) continue; - sprintf(game.tmp_path, "%s/%s", tmp_dirname, basename((char*)sb.name)); + snprintf(game.tmp_path, sizeof(game.tmp_path), "%s/%s", tmp_dirname, basename((char*)sb.name)); // Check if file already exists and has the correct size struct stat st; diff --git a/workspace/all/minarch/ma_input.c b/workspace/all/minarch/ma_input.cpp similarity index 79% rename from workspace/all/minarch/ma_input.c rename to workspace/all/minarch/ma_input.cpp index 3233b214d..824e49ac6 100644 --- a/workspace/all/minarch/ma_input.c +++ b/workspace/all/minarch/ma_input.cpp @@ -1,14 +1,19 @@ +#include + +// Project C headers declare C-linkage symbols (core, pad, PAD_*, LOG_*, …) +// defined in the still-C translation units. Include them as extern "C" so this +// C++ unit links against the unmangled names. (Same pattern as ma_audio.cpp.) +extern "C" { #include "ma_internal.h" #include "ma_input.h" - -#include +} int setFastForward(int enable) { int val = enable ? 1 : 0; - if (fast_forward != val) { + if (ff.active != val) { LOG_info("FF state -> %i\n", val); } - fast_forward = val; + ff.active = val; return val; } @@ -45,7 +50,7 @@ void input_poll_callback(void) { } static int toggled_ff_on = 0; // this logic only works because TOGGLE_FF is before HOLD_FF in the menu... - rewind_pressed = 0; + rewind_st.pressed = 0; for (int i=0; ilocal; @@ -53,15 +58,15 @@ void input_poll_callback(void) { if (!mapping->mod || PAD_isPressed(BTN_MENU)) { if (i==SHORTCUT_TOGGLE_FF) { if (PAD_justPressed(btn)) { - toggled_ff_on = setFastForward(!fast_forward); - ff_toggled = toggled_ff_on; - ff_hold_active = 0; - if (ff_toggled && rewind_toggle) { + toggled_ff_on = setFastForward(!ff.active); + ff.toggled = toggled_ff_on; + ff.hold_active = 0; + if (ff.toggled && rewind_st.toggle) { // last toggle wins: disable rewind toggle when FF toggle is enabled - rewind_toggle = 0; - rewind_pressed = 0; + rewind_st.toggle = 0; + rewind_st.pressed = 0; Rewind_sync_encode_state(); - rewinding = 0; + rewind_st.active = 0; } if (mapping->mod) ignore_menu = 1; break; @@ -72,42 +77,42 @@ void input_poll_callback(void) { } } else if (i==SHORTCUT_HOLD_FF) { - // don't allow turn off fast_forward with a release of the hold button + // don't allow turn off ff.active with a release of the hold button // if it was initially turned on with the toggle button if (PAD_justPressed(btn) || (!toggled_ff_on && PAD_justReleased(btn))) { int pressed = PAD_isPressed(btn); - fast_forward = setFastForward(pressed); - ff_hold_active = pressed ? 1 : 0; + ff.active = setFastForward(pressed); + ff.hold_active = pressed ? 1 : 0; if (mapping->mod) ignore_menu = 1; // very unlikely but just in case } if (PAD_justReleased(btn) && toggled_ff_on) { - ff_hold_active = 0; + ff.hold_active = 0; } } else if (i==SHORTCUT_HOLD_REWIND) { - rewind_pressed = PAD_isPressed(btn) ? 1 : 0; - if (rewind_pressed != last_rewind_pressed) { - LOG_info("Rewind hotkey %s\n", rewind_pressed ? "pressed" : "released"); - last_rewind_pressed = rewind_pressed; + rewind_st.pressed = PAD_isPressed(btn) ? 1 : 0; + if (rewind_st.pressed != rewind_st.last_pressed) { + LOG_info("Rewind hotkey %s\n", rewind_st.pressed ? "pressed" : "released"); + rewind_st.last_pressed = rewind_st.pressed; } - if (rewind_pressed && ff_toggled && !ff_paused_by_rewind_hold) { - ff_paused_by_rewind_hold = 1; - fast_forward = setFastForward(0); + if (rewind_st.pressed && ff.toggled && !ff.paused_by_rewind_hold) { + ff.paused_by_rewind_hold = 1; + ff.active = setFastForward(0); } - else if (!rewind_pressed && ff_paused_by_rewind_hold) { - ff_paused_by_rewind_hold = 0; - if (ff_toggled) fast_forward = setFastForward(1); + else if (!rewind_st.pressed && ff.paused_by_rewind_hold) { + ff.paused_by_rewind_hold = 0; + if (ff.toggled) ff.active = setFastForward(1); } - if (mapping->mod && rewind_pressed) ignore_menu = 1; + if (mapping->mod && rewind_st.pressed) ignore_menu = 1; } else if (i==SHORTCUT_TOGGLE_REWIND) { if (PAD_justPressed(btn)) { - rewind_toggle = !rewind_toggle; - if (rewind_toggle && ff_toggled) { - // disable fast forward toggle when rewinding is toggled on - ff_toggled = 0; - fast_forward = setFastForward(0); - ff_paused_by_rewind_hold = 0; + rewind_st.toggle = !rewind_st.toggle; + if (rewind_st.toggle && ff.toggled) { + // disable fast forward toggle when rewind_st.active is toggled on + ff.toggled = 0; + ff.active = setFastForward(0); + ff.paused_by_rewind_hold = 0; } if (mapping->mod) ignore_menu = 1; break; @@ -160,12 +165,12 @@ void input_poll_callback(void) { putFile(GAME_SWITCHER_PERSIST_PATH, game.path + strlen(SDCARD_PATH)); break; case SHORTCUT_CYCLE_SCALE: - screen_scaling = (screen_scaling + 1) % config.frontend.options[FE_OPT_SCALING].count; - Config_syncFrontend(config.frontend.options[FE_OPT_SCALING].key, screen_scaling); + display_cfg.scaling = (display_cfg.scaling + 1) % config.frontend.options[FE_OPT_SCALING].count; + Config_syncFrontend(config.frontend.options[FE_OPT_SCALING].key, display_cfg.scaling); break; case SHORTCUT_CYCLE_EFFECT: - screen_effect = (screen_effect + 1) % config.frontend.options[FE_OPT_EFFECT].count; - Config_syncFrontend(config.frontend.options[FE_OPT_EFFECT].key, screen_effect); + display_cfg.effect = (display_cfg.effect + 1) % config.frontend.options[FE_OPT_EFFECT].count; + Config_syncFrontend(config.frontend.options[FE_OPT_EFFECT].key, display_cfg.effect); break; default: break; } @@ -224,12 +229,12 @@ int16_t input_state_callback(unsigned port, unsigned device, unsigned index, uns } else if (port==0 && device==RETRO_DEVICE_ANALOG) { if (index==RETRO_DEVICE_INDEX_ANALOG_LEFT) { - if (id==RETRO_DEVICE_ID_ANALOG_X) return pad.laxis.x; - else if (id==RETRO_DEVICE_ID_ANALOG_Y) return pad.laxis.y; + if (id==RETRO_DEVICE_ID_ANALOG_X) return PAD_getContext()->laxis.x; + else if (id==RETRO_DEVICE_ID_ANALOG_Y) return PAD_getContext()->laxis.y; } else if (index==RETRO_DEVICE_INDEX_ANALOG_RIGHT) { - if (id==RETRO_DEVICE_ID_ANALOG_X) return pad.raxis.x; - else if (id==RETRO_DEVICE_ID_ANALOG_Y) return pad.raxis.y; + if (id==RETRO_DEVICE_ID_ANALOG_X) return PAD_getContext()->raxis.x; + else if (id==RETRO_DEVICE_ID_ANALOG_Y) return PAD_getContext()->raxis.y; } } return 0; diff --git a/workspace/all/minarch/ma_internal.h b/workspace/all/minarch/ma_internal.h index 49d4a8935..63c513a5b 100644 --- a/workspace/all/minarch/ma_internal.h +++ b/workspace/all/minarch/ma_internal.h @@ -89,18 +89,37 @@ extern SDL_Surface *screen; extern int quit; extern int show_menu; extern int newScreenshot; -extern int fast_forward; -extern int rewinding; -extern int ff_audio; extern int use_core_fps; -extern int rewind_pressed; -extern int rewind_toggle; -extern int ff_toggled; -extern int ff_hold_active; -extern int ff_paused_by_rewind_hold; -extern int screen_scaling; -extern int screen_effect; +// Rewind runtime state, grouped out of the flat global soup (config lives in +// the RewindConfig struct). +typedef struct RewindState { + int active; // was rewinding + int pressed; // was rewind_pressed + int toggle; // was rewind_toggle + int last_pressed; // was last_rewind_pressed +} RewindState; +extern RewindState rewind_st; + +// Fast-forward runtime state, grouped out of the flat global soup. +typedef struct FastForward { + int active; // was fast_forward + int audio; // was ff_audio + int toggled; // was ff_toggled + int hold_active; // was ff_hold_active + int paused_by_rewind_hold; // was ff_paused_by_rewind_hold +} FastForward; +extern FastForward ff; + +// Display configuration, grouped out of the flat global soup. +typedef struct DisplayConfig { + int scaling; // screen_scaling + int effect; // screen_effect + int sharpness; // screen_sharpness + int screenx; // cfg_screenx + int screeny; // cfg_screeny +} DisplayConfig; +extern DisplayConfig display_cfg; extern int DEVICE_WIDTH; extern int DEVICE_HEIGHT; @@ -112,20 +131,21 @@ extern int shader_reset_suppressed; extern char* scaling_labels[]; extern int simple_mode; extern int resampling_quality; -extern int screen_sharpness; -extern int cfg_screenx; -extern int cfg_screeny; extern int overlay; extern int sync_ref; extern int max_ff_speed; extern int overclock; -extern int rewind_cfg_enable; -extern int rewind_cfg_buffer_mb; -extern int rewind_cfg_granularity; -extern int rewind_cfg_audio; -extern int rewind_cfg_compress; -extern int rewind_cfg_lz4_acceleration; +// Rewind configuration, grouped out of the flat global soup. +typedef struct RewindConfig { + int enable; + int buffer_mb; + int granularity; + int audio; + int compress; + int lz4_acceleration; +} RewindConfig; +extern RewindConfig rewind_cfg; extern int rewind_init_ready; #include "ma_rewind.h" @@ -198,7 +218,6 @@ extern struct Config config; extern ButtonMapping default_button_mapping[]; extern ButtonMapping core_button_mapping[]; extern int gamepad_type; -extern int last_rewind_pressed; void Config_syncFrontend(char* key, int value); diff --git a/workspace/all/minarch/ma_menu.c b/workspace/all/minarch/ma_menu.cpp similarity index 81% rename from workspace/all/minarch/ma_menu.c rename to workspace/all/minarch/ma_menu.cpp index 5a5c130d0..f2cc77c95 100644 --- a/workspace/all/minarch/ma_menu.c +++ b/workspace/all/minarch/ma_menu.cpp @@ -7,6 +7,10 @@ #include #include #include + +// Project C headers declare C-linkage symbols defined in the still-C translation +// units; include them as extern "C" so this C++ TU links the unmangled names. +extern "C" { #include #include "defines.h" #include "api.h" @@ -22,6 +26,19 @@ #include "ma_video.h" #include "ma_frontend_opts.h" #include "ma_menu.h" +} + +// g++ 8.3 rejects taking the address of an SDL_Rect compound literal in C++ +// ("taking address of temporary"), the scratch-rect idiom this file used +// throughout. RectArg wraps an SDL_Rect in a class temporary whose lifetime +// spans the enclosing call and implicitly converts to SDL_Rect*, so a former +// address-of-compound-literal becomes RectArg{ SDL_Rect{ ...fields... } }. +// Verified correct on the tg5040 g++ 8.3 toolchain at -O3 -Ofast -flto. +struct RectArg { SDL_Rect r; operator SDL_Rect*() { return &r; } }; + +// g++ 8.3 silently drops the backing storage of a (char*[]){...} temporary array +// passed as an argument, so shared button-hint lists get their own real storage. +static const char* BACK_PAIR[] = { "B", "BACK", NULL }; /////////////////////////////// @@ -49,14 +66,14 @@ void MSG_init(void) { char* c; int i = 0; while ((c = chars[i])) { - digit = TTF_RenderUTF8_Blended(font.tiny, c, COLOR_WHITE); - SDL_BlitSurface(digit, NULL, digits, &(SDL_Rect){ (i * SCALE1(DIGIT_WIDTH)) + (SCALE1(DIGIT_WIDTH) - digit->w)/2, (SCALE1(DIGIT_HEIGHT) - digit->h)/2}); + digit = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, c, COLOR_WHITE); + SDL_BlitSurface(digit, NULL, digits, RectArg{ SDL_Rect{ (i * SCALE1(DIGIT_WIDTH)) + (SCALE1(DIGIT_WIDTH) - digit->w)/2, (SCALE1(DIGIT_HEIGHT) - digit->h)/2} }); SDL_FreeSurface(digit); i += 1; } } static int MSG_blitChar(int n, int x, int y) { - if (n!=DIGIT_SPACE) SDL_BlitSurface(digits, &(SDL_Rect){n*SCALE1(DIGIT_WIDTH),0,SCALE2(DIGIT_WIDTH,DIGIT_HEIGHT)}, screen, &(SDL_Rect){x,y}); + if (n!=DIGIT_SPACE) SDL_BlitSurface(digits, RectArg{ SDL_Rect{n*SCALE1(DIGIT_WIDTH),0,SCALE2(DIGIT_WIDTH,DIGIT_HEIGHT)} }, screen, RectArg{ SDL_Rect{x,y} }); return x + SCALE1(DIGIT_WIDTH + DIGIT_TRACKING); } static int MSG_blitInt(int num, int x, int y) { @@ -152,18 +169,20 @@ static struct { int preview_exists; } menu = { .bitmap = NULL, + // Positional, in ITEM_CONT..ITEM_QUIT order (0..4); C++ has no [index]= + // array designators. Reordered ahead of the scalar fields below because + // C++ requires designated initializers in declaration order. + .items = { + "Continue", + "Save", + "Load", + "Options", + "Quit", + }, .disc = -1, .total_discs = 0, .save_exists = 0, .preview_exists = 0, - - .items = { - [ITEM_CONT] = "Continue", - [ITEM_SAVE] = "Save", - [ITEM_LOAD] = "Load", - [ITEM_OPTS] = "Options", - [ITEM_QUIT] = "Quit", - } }; void Menu_init(void) { @@ -176,17 +195,17 @@ void Menu_init(void) { char emu_name[256]; getEmuName(game.path, emu_name); - sprintf(menu.minui_dir, SHARED_USERDATA_PATH "/.minui/%s", emu_name); + snprintf(menu.minui_dir, sizeof(menu.minui_dir), SHARED_USERDATA_PATH "/.minui/%s", emu_name); mkdir(menu.minui_dir, 0755); // always sanitized/outer name, to keep main UI from having to inspect archives - sprintf(menu.slot_path, "%s/%s.txt", menu.minui_dir, game.name); + snprintf(menu.slot_path, sizeof(menu.slot_path), "%s/%s.txt", menu.minui_dir, game.name); if (simple_mode) menu.items[ITEM_OPTS] = "Reset"; if (game.m3u_path[0]) { char* tmp; - strcpy(menu.base_path, game.m3u_path); + snprintf(menu.base_path, sizeof(menu.base_path), "%s", game.m3u_path); tmp = strrchr(menu.base_path, '/') + 1; tmp[0] = '\0'; @@ -200,9 +219,9 @@ void Menu_init(void) { if (strlen(line)==0) continue; // skip empty lines char disc_path[256]; - strcpy(disc_path, menu.base_path); + snprintf(disc_path, sizeof(disc_path), "%s", menu.base_path); tmp = disc_path + strlen(disc_path); - strcpy(tmp, line); + snprintf(tmp, sizeof(disc_path) - (tmp - disc_path), "%s", line); // found a valid disc path if (exists(disc_path)) { @@ -390,11 +409,11 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { // Title centered - wrap to max 2 lines with ellipsis if needed int max_text_width = screen->w - SCALE1(PADDING * 2); - content_y = GFX_blitWrappedText(font.medium, ach->title, max_text_width, 2, COLOR_WHITE, screen, content_y); + content_y = GFX_blitWrappedText(GFX_getFonts()->medium, ach->title, max_text_width, 2, COLOR_WHITE, screen, content_y); content_y += SCALE1(2); // Spacing after title // Description - unlimited lines - content_y = GFX_blitWrappedText(font.small, ach->description, max_text_width, 0, COLOR_WHITE, screen, content_y); + content_y = GFX_blitWrappedText(GFX_getFonts()->small, ach->description, max_text_width, 0, COLOR_WHITE, screen, content_y); content_y += SCALE1(4); // Spacing after description // Points (singular/plural) - use tiny font like other metadata @@ -404,10 +423,10 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { } else { snprintf(points_str, sizeof(points_str), "%u points", ach->points); } - SDL_Surface* points_text = TTF_RenderUTF8_Blended(font.tiny, points_str, COLOR_LIGHT_TEXT); - SDL_BlitSurface(points_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* points_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, points_str, COLOR_LIGHT_TEXT); + SDL_BlitSurface(points_text, NULL, screen, RectArg{ SDL_Rect{ center_x - points_text->w / 2, content_y - }); + } }); content_y += points_text->h + SCALE1(2); SDL_FreeSurface(points_text); @@ -416,36 +435,36 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { struct tm* tm_info = localtime(&ach->unlock_time); char time_buf[64]; strftime(time_buf, sizeof(time_buf), "Unlocked %B %d %Y, %I:%M%p", tm_info); - SDL_Surface* time_text = TTF_RenderUTF8_Blended(font.tiny, time_buf, COLOR_LIGHT_TEXT); - SDL_BlitSurface(time_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* time_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, time_buf, COLOR_LIGHT_TEXT); + SDL_BlitSurface(time_text, NULL, screen, RectArg{ SDL_Rect{ center_x - time_text->w / 2, content_y - }); + } }); content_y += time_text->h + SCALE1(2); SDL_FreeSurface(time_text); } else if (ach->measured_progress[0]) { char progress_buf[64]; snprintf(progress_buf, sizeof(progress_buf), "Progress: %s", ach->measured_progress); - SDL_Surface* progress_text = TTF_RenderUTF8_Blended(font.tiny, progress_buf, COLOR_LIGHT_TEXT); - SDL_BlitSurface(progress_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* progress_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, progress_buf, COLOR_LIGHT_TEXT); + SDL_BlitSurface(progress_text, NULL, screen, RectArg{ SDL_Rect{ center_x - progress_text->w / 2, content_y - }); + } }); content_y += progress_text->h + SCALE1(2); SDL_FreeSurface(progress_text); } // Offline pending indicator if (is_offline_pending) { - SDL_Surface* offline_text = TTF_RenderUTF8_Blended(font.tiny, "Unlocked offline - pending sync", COLOR_LIGHT_TEXT); + SDL_Surface* offline_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, "Unlocked offline - pending sync", COLOR_LIGHT_TEXT); int wifi_size = SCALE1(12); int total_w = wifi_size + SCALE1(4) + offline_text->w; int icon_x = center_x - total_w / 2; int text_x = icon_x + wifi_size + SCALE1(4); int wifi_y = content_y + (offline_text->h - wifi_size) / 2; GFX_blitAssetColor(ASSET_WIFI_OFF, NULL, screen, - &(SDL_Rect){icon_x, wifi_y}, 0xCCCCCC); - SDL_BlitSurface(offline_text, NULL, screen, &(SDL_Rect){ + RectArg{ SDL_Rect{icon_x, wifi_y} }, 0xCCCCCC); + SDL_BlitSurface(offline_text, NULL, screen, RectArg{ SDL_Rect{ text_x, content_y - }); + } }); content_y += offline_text->h + SCALE1(2); SDL_FreeSurface(offline_text); } @@ -454,10 +473,10 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { if (ach->rarity > 0) { char rarity_buf[32]; snprintf(rarity_buf, sizeof(rarity_buf), "%.2f%% unlock rate", ach->rarity); - SDL_Surface* rarity_text = TTF_RenderUTF8_Blended(font.tiny, rarity_buf, COLOR_LIGHT_TEXT); - SDL_BlitSurface(rarity_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* rarity_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, rarity_buf, COLOR_LIGHT_TEXT); + SDL_BlitSurface(rarity_text, NULL, screen, RectArg{ SDL_Rect{ center_x - rarity_text->w / 2, content_y - }); + } }); content_y += rarity_text->h + SCALE1(2); SDL_FreeSurface(rarity_text); } @@ -478,17 +497,17 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { break; } if (type_str) { - SDL_Surface* type_text = TTF_RenderUTF8_Blended(font.tiny, type_str, COLOR_LIGHT_TEXT); - SDL_BlitSurface(type_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* type_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, type_str, COLOR_LIGHT_TEXT); + SDL_BlitSurface(type_text, NULL, screen, RectArg{ SDL_Rect{ center_x - type_text->w / 2, content_y - }); + } }); content_y += type_text->h + SCALE1(2); SDL_FreeSurface(type_text); } // Muted status below other info with gap before title if (is_muted) { - SDL_Surface* mute_text = TTF_RenderUTF8_Blended(font.tiny, "Muted - progress notifications silenced", COLOR_LIGHT_TEXT); + SDL_Surface* mute_text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, "Muted - progress notifications silenced", COLOR_LIGHT_TEXT); int mute_icon_w = SCALE1(10); int mute_icon_h = SCALE1(12); int total_w = mute_icon_w + SCALE1(4) + mute_text->w; @@ -497,16 +516,16 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { int icon_y = content_y + SCALE1(4) + (mute_text->h - mute_icon_h) / 2; SDL_Rect mute_src = {0, SCALE1(4), SCALE1(10), SCALE1(12)}; GFX_blitAssetColor(ASSET_VOLUME_MUTE, &mute_src, screen, - &(SDL_Rect){icon_x, icon_y}, 0xCCCCCC); - SDL_BlitSurface(mute_text, NULL, screen, &(SDL_Rect){ + RectArg{ SDL_Rect{icon_x, icon_y} }, 0xCCCCCC); + SDL_BlitSurface(mute_text, NULL, screen, RectArg{ SDL_Rect{ text_x, content_y + SCALE1(4) - }); + } }); SDL_FreeSurface(mute_text); } // Button hints - update based on current mute state - char* hints[] = {"X", is_muted ? "UNMUTE" : "MUTE", "B", "BACK", NULL}; - GFX_blitButtonGroup(hints, 0, screen, 1); + const char* hints[] = {"X", is_muted ? "UNMUTE" : "MUTE", "B", "BACK", NULL}; + GFX_blitButtonGroup((char**)hints, 0, screen, 1); GFX_flip(screen); dirty = 0; } @@ -521,7 +540,7 @@ static int OptionAchievements_showDetail(MenuList* list, int i) { static int OptionAchievements_openMenu(MenuList* list, int i) { if (!RA_isGameLoaded()) { - Menu_message("No achievements found for this game.\n\nThis ROM may need a compatibility patch\nor may not be a supported version.\n\nVisit retroachievements.org to check\nsupported game files.", (char*[]){"B","BACK", NULL}); + Menu_message("No achievements found for this game.\n\nThis ROM may need a compatibility patch\nor may not be a supported version.\n\nVisit retroachievements.org to check\nsupported game files.", (char**)BACK_PAIR); return MENU_CALLBACK_NOP; } @@ -529,7 +548,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { RA_getAchievementSummary(&unlocked, &total); if (total == 0) { - Menu_message("No achievements available for this game.\n\nThis game may not have achievements yet.\n\nVisit retroachievements.org for details.", (char*[]){"B","BACK", NULL}); + Menu_message("No achievements available for this game.\n\nThis game may not have achievements yet.\n\nVisit retroachievements.org for details.", (char**)BACK_PAIR); return MENU_CALLBACK_NOP; } @@ -549,7 +568,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE, RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE); if (!ach_menu_list) { - Menu_message("Failed to load achievements", (char*[]){"B","BACK", NULL}); + Menu_message("Failed to load achievements", (char**)BACK_PAIR); return MENU_CALLBACK_NOP; } @@ -564,12 +583,12 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { ach_menu_list = NULL; // This can happen with unsupported game versions where pseudo-achievements // are counted in the summary but not available in the achievement list - Menu_message("Achievement list not available", (char*[]){"B","BACK", NULL}); + Menu_message("Achievement list not available", (char**)BACK_PAIR); return MENU_CALLBACK_NOP; } // Create flattened array of all achievement pointers - const rc_client_achievement_t** all_achievements = calloc(total_achievements, sizeof(rc_client_achievement_t*)); + const rc_client_achievement_t** all_achievements = (const rc_client_achievement_t**)calloc(total_achievements, sizeof(rc_client_achievement_t*)); int idx = 0; for (uint32_t b = 0; b < ach_menu_list->num_buckets && idx < total_achievements; b++) { const rc_client_achievement_bucket_t* bucket = &ach_menu_list->buckets[b]; @@ -590,7 +609,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { int max_visible = (screen->h - ((SCALE1(PADDING + PILL_SIZE) * 2) + SCALE1(BUTTON_SIZE))) / SCALE1(BUTTON_SIZE); // Allocate filtered array once (max size = all achievements) - const rc_client_achievement_t** filtered = calloc(total_achievements, sizeof(rc_client_achievement_t*)); + const rc_client_achievement_t** filtered = (const rc_client_achievement_t**)calloc(total_achievements, sizeof(rc_client_achievement_t*)); int filtered_count = 0; // Hide "Unknown Emulator" warning (ID 101000001) when hardcore mode is disabled. @@ -628,7 +647,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { ach_menu_list = NULL; ach_menu_achievements = NULL; ach_menu_count = 0; - Menu_message("No achievements found", (char*[]){"B","BACK", NULL}); + Menu_message("No achievements found", (char**)BACK_PAIR); return MENU_CALLBACK_NOP; } @@ -728,11 +747,11 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { // Status text at top, aligned with hardware pill (not part of centered content) char status_text[64]; snprintf(status_text, sizeof(status_text), "%u/%u unlocked", unlocked, total); - SDL_Surface* status_surface = TTF_RenderUTF8_Blended(font.tiny, status_text, COLOR_WHITE); - SDL_BlitSurface(status_surface, NULL, screen, &(SDL_Rect){ + SDL_Surface* status_surface = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, status_text, COLOR_WHITE); + SDL_BlitSurface(status_surface, NULL, screen, RectArg{ SDL_Rect{ (screen->w - status_surface->w) / 2, SCALE1(PADDING) + (SCALE1(PILL_SIZE) - status_surface->h) / 2 // Vertically centered with pill - }); + } }); SDL_FreeSurface(status_surface); // Calculate vertical centering for list only @@ -757,17 +776,17 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { if (is_selected) { // Gray pill background for full row width (like MENU_FIXED selected) - GFX_blitPillLight(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillLight(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy + SCALE1(row * BUTTON_SIZE), mw, row_height - }); + } }); } // Draw ">" on the right side (always white) - SDL_Surface* arrow = TTF_RenderUTF8_Blended(font.small, ">", COLOR_WHITE); - SDL_BlitSurface(arrow, NULL, screen, &(SDL_Rect){ + SDL_Surface* arrow = TTF_RenderUTF8_Blended(GFX_getFonts()->small, ">", COLOR_WHITE); + SDL_BlitSurface(arrow, NULL, screen, RectArg{ SDL_Rect{ ox + mw - arrow->w - opt_pad, oy + SCALE1((row * BUTTON_SIZE) + 3) - }); + } }); SDL_FreeSurface(arrow); if (is_selected) { @@ -775,7 +794,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { // Calculate width needed for: badge + spacing + [mute icon] + [wifi icon] + title + padding int badge_display_size = SCALE1(BUTTON_SIZE - 4); // Badge sized to fit in row int title_width = 0; - TTF_SizeUTF8(font.small, ach->title, &title_width, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, ach->title, &title_width, NULL); int mute_width = 0; if (is_muted) { mute_width = SCALE1(10) + SCALE1(4); // volume-mute icon + gap @@ -786,9 +805,9 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { } int pill_width = opt_pad + badge_display_size + SCALE1(6) + mute_width + offline_width + title_width + opt_pad; - GFX_blitPillDark(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillDark(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy + SCALE1(row * BUTTON_SIZE), pill_width, row_height - }); + } }); text_color = uintToColour(THEME_COLOR5_255); // Badge icon inside the white pill @@ -811,7 +830,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { int mute_y = oy + SCALE1(row * BUTTON_SIZE) + (row_height - mute_icon_h) / 2; SDL_Rect mute_src = {0, SCALE1(4), SCALE1(10), SCALE1(12)}; GFX_blitAssetColor(ASSET_VOLUME_MUTE, &mute_src, screen, - &(SDL_Rect){text_x, mute_y}, THEME_COLOR5_255); + RectArg{ SDL_Rect{text_x, mute_y} }, THEME_COLOR5_255); text_x += mute_width; } @@ -820,16 +839,16 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { int wifi_size = SCALE1(12); int wifi_y = oy + SCALE1(row * BUTTON_SIZE) + (row_height - wifi_size) / 2; GFX_blitAssetColor(ASSET_WIFI_OFF, NULL, screen, - &(SDL_Rect){text_x, wifi_y}, THEME_COLOR5_255); + RectArg{ SDL_Rect{text_x, wifi_y} }, THEME_COLOR5_255); text_x += offline_width; } // Title text - SDL_Surface* title_text = TTF_RenderUTF8_Blended(font.small, ach->title, text_color); - SDL_BlitSurface(title_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* title_text = TTF_RenderUTF8_Blended(GFX_getFonts()->small, ach->title, text_color); + SDL_BlitSurface(title_text, NULL, screen, RectArg{ SDL_Rect{ text_x, oy + SCALE1((row * BUTTON_SIZE) + 1) - }); + } }); SDL_FreeSurface(title_text); } else { // Unselected row - just badge + title + indicators, no pills @@ -855,7 +874,7 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { int mute_y = oy + SCALE1(row * BUTTON_SIZE) + (row_height - mute_icon_h) / 2; SDL_Rect mute_src = {0, SCALE1(4), SCALE1(10), SCALE1(12)}; GFX_blitAssetColor(ASSET_VOLUME_MUTE, &mute_src, screen, - &(SDL_Rect){text_x, mute_y}, RGB_WHITE); + RectArg{ SDL_Rect{text_x, mute_y} }, RGB_WHITE); text_x += SCALE1(10) + SCALE1(4); } @@ -864,24 +883,24 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { int wifi_size = SCALE1(12); int wifi_y = oy + SCALE1(row * BUTTON_SIZE) + (row_height - wifi_size) / 2; GFX_blitAssetColor(ASSET_WIFI_OFF, NULL, screen, - &(SDL_Rect){text_x, wifi_y}, RGB_WHITE); + RectArg{ SDL_Rect{text_x, wifi_y} }, RGB_WHITE); text_x += wifi_size + SCALE1(4); } // Title text (white for unselected) - SDL_Surface* title_text = TTF_RenderUTF8_Blended(font.small, ach->title, COLOR_WHITE); - SDL_BlitSurface(title_text, NULL, screen, &(SDL_Rect){ + SDL_Surface* title_text = TTF_RenderUTF8_Blended(GFX_getFonts()->small, ach->title, COLOR_WHITE); + SDL_BlitSurface(title_text, NULL, screen, RectArg{ SDL_Rect{ text_x, oy + SCALE1((row * BUTTON_SIZE) + 1) - }); + } }); SDL_FreeSurface(title_text); } } // Button hints at bottom with dynamic Y and X button text int selected_muted = (filtered_count > 0) ? RA_isAchievementMuted(filtered[selected]->id) : 0; - char* hints[] = {"Y", ach_filter_locked_only ? "SHOW ALL" : "SHOW LOCKED", "X", selected_muted ? "UNMUTE" : "MUTE", NULL}; - GFX_blitButtonGroup(hints, 0, screen, 1); + const char* hints[] = {"Y", ach_filter_locked_only ? "SHOW ALL" : "SHOW LOCKED", "X", selected_muted ? "UNMUTE" : "MUTE", NULL}; + GFX_blitButtonGroup((char**)hints, 0, screen, 1); GFX_flip(screen); dirty = 0; @@ -901,20 +920,26 @@ static int OptionAchievements_openMenu(MenuList* list, int i) { return MENU_CALLBACK_NOP; } +// Hoisted out of options_menu's initializer: C++ has no (MenuItem[]){...} +// compound-literal array, and this table is mutated at runtime (see +// Options_updateVisibility), so it needs its own writable static storage. +// Each element is fully designated in declaration order (name, desc, on_confirm) +// because C++ forbids mixing positional and designated initializers. +static MenuItem options_menu_items[] = { + {.name="Frontend", .desc="NextUI (" BUILD_DATE " " BUILD_HASH ")", .on_confirm=OptionFrontend_openMenu}, + {.name="Emulator", .on_confirm=OptionEmulator_openMenu}, + {.name="Shaders", .on_confirm=OptionShaders_openMenu}, + {.name="Cheats", .on_confirm=OptionCheats_openMenu}, + {.name="Controls", .on_confirm=OptionControls_openMenu}, + {.name="Shortcuts", .on_confirm=OptionShortcuts_openMenu}, + {.name="Achievements", .on_confirm=OptionAchievements_openMenu}, + {.name="Save Changes", .on_confirm=OptionSaveChanges_openMenu}, + {NULL}, + {NULL}, +}; static MenuList options_menu = { .type = MENU_LIST, - .items = (MenuItem[]) { - {"Frontend", "NextUI (" BUILD_DATE " " BUILD_HASH ")",.on_confirm=OptionFrontend_openMenu}, - {"Emulator",.on_confirm=OptionEmulator_openMenu}, - {"Shaders",.on_confirm=OptionShaders_openMenu}, - {"Cheats",.on_confirm=OptionCheats_openMenu}, - {"Controls",.on_confirm=OptionControls_openMenu}, - {"Shortcuts",.on_confirm=OptionShortcuts_openMenu}, - {"Achievements",.on_confirm=OptionAchievements_openMenu}, - {"Save Changes",.on_confirm=OptionSaveChanges_openMenu}, - {NULL}, - {NULL}, - } + .items = options_menu_items, }; // Track the index of Save Changes menu item (changes based on RA visibility) @@ -967,11 +992,11 @@ bool getAlias(char* path, char* alias) { // LOG_info("alias path: %s\n", path); char* tmp; char map_path[256]; - strcpy(map_path, path); + snprintf(map_path, sizeof(map_path), "%s", path); tmp = strrchr(map_path, '/'); if (tmp) { tmp += 1; - strcpy(tmp, "map.txt"); + snprintf(tmp, sizeof(map_path) - (tmp - map_path), "%s", "map.txt"); // LOG_info("map_path: %s\n", map_path); } char* file_name = strrchr(path,'/'); @@ -993,7 +1018,7 @@ bool getAlias(char* path, char* alias) { char* key = line; char* value = tmp+1; if (exactMatch(file_name,key)) { - strcpy(alias, value); + snprintf(alias, 256, "%s", value); // alias buffer is >=256 at every caller is_alias = true; break; } @@ -1181,7 +1206,7 @@ int Menu_options(MenuList* list) { for (int i=0; iname, &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item->name, &w, NULL); w += SCALE1(OPTION_PADDING*2); if (w>mw) mw = w; } @@ -1199,24 +1224,24 @@ int Menu_options(MenuList* list) { if (j==selected_row) { // move out of conditional if centering int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item->name, &w, NULL); w += SCALE1(OPTION_PADDING*2); - GFX_blitPillDark(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillDark(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy+SCALE1(j*BUTTON_SIZE), w, SCALE1(BUTTON_SIZE) - }); + } }); text_color = uintToColour(THEME_COLOR5_255); if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->small, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ ox+SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+1) - }); + } }); SDL_FreeSurface(text); } } @@ -1236,21 +1261,21 @@ int Menu_options(MenuList* list) { if (j==selected_row) { // gray pill - GFX_blitPillLight(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillLight(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy+SCALE1(j*BUTTON_SIZE), mw, SCALE1(BUTTON_SIZE) - }); + } }); } if (item->values == NULL) { // This is a navigation item, used to displayed a specific category - text = TTF_RenderUTF8_Blended(font.small, ">", COLOR_WHITE); // always white - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->small, ">", COLOR_WHITE); // always white + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ ox + mw - text->w - SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+3) - }); + } }); SDL_FreeSurface(text); } else { @@ -1259,12 +1284,12 @@ int Menu_options(MenuList* list) { while ( item->values && item->values[count]) count++; if (item->value >= 0 && item->value < count) { const char *str = item->values[item->value]; - text = TTF_RenderUTF8_Blended(font.tiny, str ? str : "none", str ? COLOR_WHITE : COLOR_GRAY); // always white + text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, str ? str : "none", str ? COLOR_WHITE : COLOR_GRAY); // always white if (text) { - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ ox + mw - text->w - SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+3) - }); + } }); SDL_FreeSurface(text); } } @@ -1275,23 +1300,23 @@ int Menu_options(MenuList* list) { if (j==selected_row) { // white pill int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item->name, &w, NULL); w += SCALE1(OPTION_PADDING*2); - GFX_blitPillDark(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillDark(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy+SCALE1(j*BUTTON_SIZE), w, SCALE1(BUTTON_SIZE) - }); + } }); text_color = uintToColour(THEME_COLOR5_255); if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->small, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ ox+SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+1) - }); + } }); SDL_FreeSurface(text); } } @@ -1305,13 +1330,13 @@ int Menu_options(MenuList* list) { int w = 0; int lw = 0; int rw = 0; - TTF_SizeUTF8(font.small, item->name, &lw, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item->name, &lw, NULL); // every value list in an input table is the same // so only calculate rw for the first item... if (!mrw || type!=MENU_INPUT) { if(item->values) { for (int j=0; item->values[j]; j++) { - TTF_SizeUTF8(font.tiny, item->values[j], &rw, NULL); + TTF_SizeUTF8(GFX_getFonts()->tiny, item->values[j], &rw, NULL); if (lw+rw>w) w = lw+rw; if (rw>mrw) mrw = rw; } @@ -1337,32 +1362,32 @@ int Menu_options(MenuList* list) { if (j==selected_row) { // gray pill - GFX_blitPillLight(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillLight(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy+SCALE1(j*BUTTON_SIZE), mw, SCALE1(BUTTON_SIZE) - }); + } }); // white pill int w = 0; - TTF_SizeUTF8(font.small, item->name, &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item->name, &w, NULL); w += SCALE1(OPTION_PADDING*2); - GFX_blitPillDark(ASSET_BUTTON, screen, &(SDL_Rect){ + GFX_blitPillDark(ASSET_BUTTON, screen, RectArg{ SDL_Rect{ ox, oy+SCALE1(j*BUTTON_SIZE), w, SCALE1(BUTTON_SIZE) - }); + } }); text_color = uintToColour(THEME_COLOR5_255); if (item->desc) desc = item->desc; } - text = TTF_RenderUTF8_Blended(font.small, item->name, text_color); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->small, item->name, text_color); + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ ox+SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+1) - }); + } }); SDL_FreeSurface(text); if (await_input && j==selected_row) { @@ -1372,11 +1397,11 @@ int Menu_options(MenuList* list) { int count = 0; while ( item->values && item->values[count]) count++; if (item->value >= 0 && item->value < count) { - text = TTF_RenderUTF8_Blended(font.tiny, item->values[item->value], COLOR_WHITE); // always white - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, item->values[item->value], COLOR_WHITE); // always white + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ ox + mw - text->w - SCALE1(OPTION_PADDING), oy+SCALE1((j*BUTTON_SIZE)+3) - }); + } }); SDL_FreeSurface(text); } } @@ -1388,20 +1413,20 @@ int Menu_options(MenuList* list) { #define SCROLL_HEIGHT 4 int ox = (screen->w - SCALE1(SCROLL_WIDTH))/2; int oy = SCALE1((PILL_SIZE - SCROLL_HEIGHT) / 2); - if (start>0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, &(SDL_Rect){ox, SCALE1(PADDING) + oy}); - if (endh - SCALE1(PADDING + PILL_SIZE + BUTTON_SIZE) + oy}); + if (start>0) GFX_blitAsset(ASSET_SCROLL_UP, NULL, screen, RectArg{ SDL_Rect{ox, SCALE1(PADDING) + oy} }); + if (endh - SCALE1(PADDING + PILL_SIZE + BUTTON_SIZE) + oy} }); } if (!desc && list->desc) desc = list->desc; if (desc) { int w,h; - GFX_sizeText(font.tiny, desc, SCALE1(12), &w,&h); - GFX_blitText(font.tiny, desc, SCALE1(12), COLOR_WHITE, screen, &(SDL_Rect){ + GFX_sizeText(GFX_getFonts()->tiny, desc, SCALE1(12), &w,&h); + GFX_blitText(GFX_getFonts()->tiny, desc, SCALE1(12), COLOR_WHITE, screen, RectArg{ SDL_Rect{ (screen->w - w) / 2, screen->h - SCALE1(PADDING) - h, w,h - }); + } }); } GFX_flip(screen); @@ -1419,8 +1444,8 @@ int Menu_options(MenuList* list) { static void Menu_scale(SDL_Surface* src, SDL_Surface* dst) { // LOG_info("Menu_scale src: %ix%i dst: %ix%i\n", src->w,src->h,dst->w,dst->h); - uint16_t* s = src->pixels; - uint16_t* d = dst->pixels; + uint16_t* s = (uint16_t*)src->pixels; + uint16_t* d = (uint16_t*)dst->pixels; int sw = src->w; int sh = src->h; @@ -1435,7 +1460,7 @@ static void Menu_scale(SDL_Surface* src, SDL_Surface* dst) { int rw = dw; int rh = dh; - int scaling = screen_scaling; + int scaling = display_cfg.scaling; if (scaling==SCALE_CROPPED && DEVICE_WIDTH==HDMI_WIDTH) { scaling = SCALE_NATIVE; } @@ -1571,8 +1596,8 @@ static void Menu_updateState(void) { state_slot = last_slot; // always sanitized/outer name, to keep main UI from having to inspect archives - sprintf(menu.bmp_path, "%s/%s.%d.bmp", menu.minui_dir, game.name, menu.slot); - sprintf(menu.txt_path, "%s/%s.%d.txt", menu.minui_dir, game.name, menu.slot); + snprintf(menu.bmp_path, sizeof(menu.bmp_path), "%s/%s.%d.bmp", menu.minui_dir, game.name, menu.slot); + snprintf(menu.txt_path, sizeof(menu.txt_path), "%s/%s.%d.txt", menu.minui_dir, game.name, menu.slot); menu.save_exists = exists(save_path); menu.preview_exists = menu.save_exists && exists(menu.bmp_path); @@ -1629,11 +1654,11 @@ void Menu_screenshot(void) { mkdir(SDCARD_PATH "/Screenshots", 0755); char png_path[256]; - sprintf(png_path, SDCARD_PATH "/Screenshots/%s.%s.png", rom_name, buffer); + snprintf(png_path, sizeof(png_path), SDCARD_PATH "/Screenshots/%s.%s.png", rom_name, buffer); int cw, ch; unsigned char* pixels = GFX_GL_screenCapture(&cw, &ch); - SaveImageArgs* args = malloc(sizeof(SaveImageArgs)); - args->pixels = pixels; + SaveImageArgs* args = (SaveImageArgs*)malloc(sizeof(SaveImageArgs)); + args->pixels = (char*)pixels; args->w = cw; args->h = ch; args->path = SDL_strdup(png_path); @@ -1658,8 +1683,8 @@ void Menu_saveState(void) { if (newScreenshot) { int cw, ch; unsigned char* pixels = GFX_GL_screenCapture(&cw, &ch); - SaveImageArgs* args = malloc(sizeof(SaveImageArgs)); - args->pixels = pixels; + SaveImageArgs* args = (SaveImageArgs*)malloc(sizeof(SaveImageArgs)); + args->pixels = (char*)pixels; args->w = cw; args->h = ch; args->path = SDL_strdup(menu.bmp_path); @@ -1693,8 +1718,8 @@ void Menu_loadState(void) { getFile(menu.txt_path, slot_disc_name, 256); char slot_disc_path[256]; - if (slot_disc_name[0]=='/') strcpy(slot_disc_path, slot_disc_name); - else sprintf(slot_disc_path, "%s%s", menu.base_path, slot_disc_name); + if (slot_disc_name[0]=='/') snprintf(slot_disc_path, sizeof(slot_disc_path), "%s", slot_disc_name); + else snprintf(slot_disc_path, sizeof(slot_disc_path), "%s%s", menu.base_path, slot_disc_name); char* disc_path = menu.disc_paths[menu.disc]; if (!exactMatch(slot_disc_path, disc_path)) { @@ -1771,7 +1796,7 @@ void Menu_loop(void) { char disc_name[16]; if (menu.total_discs) { rom_disc = menu.disc; - sprintf(disc_name, "Disc %i", menu.disc+1); + snprintf(disc_name, sizeof(disc_name), "Disc %i", menu.disc+1); } int selected = 0; // resets every launch @@ -1808,7 +1833,7 @@ void Menu_loop(void) { menu.disc -= 1; if (menu.disc<0) menu.disc += menu.total_discs; dirty = 1; - sprintf(disc_name, "Disc %i", menu.disc+1); + snprintf(disc_name, sizeof(disc_name), "Disc %i", menu.disc+1); } else if (selected==ITEM_SAVE || selected==ITEM_LOAD) { menu.slot -= 1; @@ -1821,7 +1846,7 @@ void Menu_loop(void) { menu.disc += 1; if (menu.disc==menu.total_discs) menu.disc -= menu.total_discs; dirty = 1; - sprintf(disc_name, "Disc %i", menu.disc+1); + snprintf(disc_name, sizeof(disc_name), "Disc %i", menu.disc+1); } else if (selected==ITEM_SAVE || selected==ITEM_LOAD) { menu.slot += 1; @@ -1871,10 +1896,10 @@ void Menu_loop(void) { show_menu = 0; } else { - int old_scaling = screen_scaling; + int old_scaling = display_cfg.scaling; Options_updateVisibility(); Menu_options(&options_menu); - if (screen_scaling!=old_scaling) { + if (display_cfg.scaling!=old_scaling) { selectScaler(renderer.true_w,renderer.true_h,renderer.src_p); restore_w = screen->w; @@ -1909,31 +1934,38 @@ void Menu_loop(void) { int max_width = screen->w - SCALE1(PADDING * 2) - ow; char display_name[256]; - int text_width = GFX_truncateText(font.large, rom_name, display_name, max_width, SCALE1(BUTTON_PADDING*2)); + int text_width = GFX_truncateText(GFX_getFonts()->large, rom_name, display_name, max_width, SCALE1(BUTTON_PADDING*2)); max_width = MIN(max_width, text_width); SDL_Surface* text; - text = TTF_RenderUTF8_Blended(font.large, display_name, uintToColour(THEME_COLOR6_255)); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->large, display_name, uintToColour(THEME_COLOR6_255)); + GFX_blitPillLight(ASSET_WHITE_PILL, screen, RectArg{ SDL_Rect{ SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE) - }); - SDL_BlitSurface(text, &(SDL_Rect){ + } }); + SDL_BlitSurface(text, RectArg{ SDL_Rect{ 0, 0, max_width-SCALE1(BUTTON_PADDING*2), text->h - }, screen, &(SDL_Rect){ + } }, screen, RectArg{ SDL_Rect{ SCALE1(PADDING+BUTTON_PADDING), SCALE1(PADDING+4) - }); + } }); SDL_FreeSurface(text); if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); - else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL }, 0, screen, 0); - GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OKAY", NULL }, 1, screen, 1); + else { + // Hoisted: g++ 8.3 silently drops (char*[]){...} temp-array storage. + const char* sleep_hints[] = { BTN_SLEEP==BTN_POWER?"POWER":"MENU", "SLEEP", NULL }; + GFX_blitButtonGroup((char**)sleep_hints, 0, screen, 0); + } + { + const char* nav_hints[] = { "B","BACK", "A","OKAY", NULL }; + GFX_blitButtonGroup((char**)nav_hints, 1, screen, 1); + } // list oy = (((DEVICE_HEIGHT / FIXED_SCALE) - PADDING * 2) - (MENU_ITEM_COUNT * PILL_SIZE)) / 2; @@ -1946,39 +1978,39 @@ void Menu_loop(void) { // disc change if (menu.total_discs>1 && i==ITEM_CONT) { - GFX_blitPillDark(ASSET_WHITE_PILL, screen, &(SDL_Rect){ + GFX_blitPillDark(ASSET_WHITE_PILL, screen, RectArg{ SDL_Rect{ SCALE1(PADDING), SCALE1(oy + PADDING), screen->w - SCALE1(PADDING * 2), SCALE1(PILL_SIZE) - }); - text = TTF_RenderUTF8_Blended(font.large, disc_name, text_color); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + } }); + text = TTF_RenderUTF8_Blended(GFX_getFonts()->large, disc_name, text_color); + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ screen->w - SCALE1(PADDING + BUTTON_PADDING) - text->w, SCALE1(oy + PADDING + 4) - }); + } }); SDL_FreeSurface(text); } - TTF_SizeUTF8(font.large, item, &ow, NULL); + TTF_SizeUTF8(GFX_getFonts()->large, item, &ow, NULL); ow += SCALE1(BUTTON_PADDING*2); // pill - GFX_blitPillDark(ASSET_WHITE_PILL, screen, &(SDL_Rect){ + GFX_blitPillDark(ASSET_WHITE_PILL, screen, RectArg{ SDL_Rect{ SCALE1(PADDING), SCALE1(oy + PADDING + (i * PILL_SIZE)), ow, SCALE1(PILL_SIZE) - }); + } }); } // text - text = TTF_RenderUTF8_Blended(font.large, item, text_color); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){ + text = TTF_RenderUTF8_Blended(GFX_getFonts()->large, item, text_color); + SDL_BlitSurface(text, NULL, screen, RectArg{ SDL_Rect{ SCALE1(PADDING + BUTTON_PADDING), SCALE1(oy + PADDING + (i * PILL_SIZE) + 4) - }); + } }); SDL_FreeSurface(text); } @@ -1995,7 +2027,7 @@ void Menu_loop(void) { oy = (DEVICE_HEIGHT - ph) / 2; // window - GFX_blitRect(ASSET_STATE_BG, screen, &(SDL_Rect){ox,oy,pw,ph}); + GFX_blitRect(ASSET_STATE_BG, screen, RectArg{ SDL_Rect{ox,oy,pw,ph} }); ox += SCALE1(WINDOW_RADIUS); oy += SCALE1(WINDOW_RADIUS); @@ -2011,22 +2043,22 @@ void Menu_loop(void) { SDL_Rect preview_rect = {ox,oy,hw,hh}; SDL_FillRect(screen, &preview_rect, SDL_MapRGBA(screen->format,0,0,0,255)); SDL_BlitScaled(bmp,NULL,preview,NULL); - SDL_BlitSurface(preview, NULL, screen, &(SDL_Rect){ox,oy}); + SDL_BlitSurface(preview, NULL, screen, RectArg{ SDL_Rect{ox,oy} }); SDL_FreeSurface(bmp); } else { SDL_Rect preview_rect = {ox,oy,hw,hh}; SDL_FillRect(screen, &preview_rect, SDL_MapRGBA(screen->format,0,0,0,255)); - if (menu.save_exists) GFX_blitMessage(font.large, "No Preview", screen, &preview_rect); - else GFX_blitMessage(font.large, "Empty Slot", screen, &preview_rect); + if (menu.save_exists) GFX_blitMessage(GFX_getFonts()->large, "No Preview", screen, &preview_rect); + else GFX_blitMessage(GFX_getFonts()->large, "Empty Slot", screen, &preview_rect); } // pagination ox += (pw-SCALE1(15*MENU_SLOT_COUNT))/2; oy += hh+SCALE1(WINDOW_RADIUS); for (int i=0; i #include #include +// Project C headers declare C-linkage symbols (config, core, LOG_*, …) defined +// in the still-C translation units. Include them as extern "C" so this C++ unit +// links against the unmangled names. calloc() results are cast to (decltype(LHS)) +// because C++ won't implicitly convert void*. (Same pattern as ma_audio.cpp.) +extern "C" { +#include "ma_internal.h" +#include "ma_options.h" +} + int Option_getValueIndex(Option* item, const char* value) { if (!value || !item || !item->values) return 0; int i = 0; @@ -45,7 +51,7 @@ void OptionList_init(const struct retro_core_option_definition *defs) { config.core.count = count; config.core.categories = NULL; // There is no categories in v1 definition if (count) { - config.core.options = calloc(count+1, sizeof(Option)); + config.core.options = (decltype(config.core.options))calloc(count+1, sizeof(Option)); for (int i=0; ikey) + 1; - item->key = calloc(len, sizeof(char)); - strcpy(item->key, def->key); + item->key = strdup(def->key); len = strlen(def->desc) + 1; - item->name = calloc(len, sizeof(char)); - strcpy(item->name, getOptionNameFromKey(def->key,def->desc)); + item->name = strdup(getOptionNameFromKey(def->key,def->desc)); if (def->info) { len = strlen(def->info) + 1; - item->desc = calloc(len, sizeof(char)); + item->desc = (decltype(item->desc))calloc(len, sizeof(char)); strncpy(item->desc, def->info, len); - item->full = calloc(len, sizeof(char)); + item->full = (decltype(item->full))calloc(len, sizeof(char)); strncpy(item->full, item->desc, len); // item->desc[len-1] = '\0'; - GFX_wrapText(font.tiny, item->desc, DEVICE_WIDTH - SCALE1(2*PADDING), 2); - GFX_wrapText(font.medium, item->full, DEVICE_WIDTH - SCALE1(2*PADDING), 16); + GFX_wrapText(GFX_getFonts()->tiny, item->desc, DEVICE_WIDTH - SCALE1(2*PADDING), 2); + GFX_wrapText(GFX_getFonts()->medium, item->full, DEVICE_WIDTH - SCALE1(2*PADDING), 16); } for (count=0; def->values[count].value; count++); item->count = count; - item->values = calloc(count+1, sizeof(char*)); - item->labels = calloc(count+1, sizeof(char*)); + item->values = (decltype(item->values))calloc(count+1, sizeof(char*)); + item->labels = (decltype(item->labels))calloc(count+1, sizeof(char*)); for (int j=0; jvalues[j].value; const char* label = def->values[j].label; len = strlen(value) + 1; - item->values[j] = calloc(len, sizeof(char)); - strcpy(item->values[j], value); + item->values[j] = strdup(value); if (label) { len = strlen(label) + 1; - item->labels[j] = calloc(len, sizeof(char)); - strcpy(item->labels[j], label); + item->labels[j] = strdup(label); } else { item->labels[j] = item->values[j]; @@ -123,7 +125,7 @@ void OptionList_v2_init(const struct retro_core_options_v2 *opt_defs) { // TODO: add frontend options to this? so the can use the same override method? eg. minarch_* if (cat_count) { - config.core.categories = calloc(cat_count + 1, sizeof(OptionCategory)); + config.core.categories = (decltype(config.core.categories))calloc(cat_count + 1, sizeof(OptionCategory)); for (int i=0; idesc = strdup(def->info); item->full = strdup(item->desc); - GFX_wrapText(font.tiny, item->desc, DEVICE_WIDTH - SCALE1(2*PADDING), 2); - GFX_wrapText(font.medium, item->full, DEVICE_WIDTH - SCALE1(2*PADDING), 16); + GFX_wrapText(GFX_getFonts()->tiny, item->desc, DEVICE_WIDTH - SCALE1(2*PADDING), 2); + GFX_wrapText(GFX_getFonts()->medium, item->full, DEVICE_WIDTH - SCALE1(2*PADDING), 16); } for (count=0; def->values[count].value; count++); item->count = count; - item->values = calloc(count+1, sizeof(char*)); - item->labels = calloc(count+1, sizeof(char*)); + item->values = (decltype(item->values))calloc(count+1, sizeof(char*)); + item->labels = (decltype(item->labels))calloc(count+1, sizeof(char*)); for (int j=0; jvalues[j].value; @@ -196,7 +198,7 @@ void OptionList_vars(const struct retro_variable *vars) { config.core.count = count; if (count) { - config.core.options = calloc(count+1, sizeof(Option)); + config.core.options = (decltype(config.core.options))calloc(count+1, sizeof(Option)); for (int i=0; ikey) + 1; - item->key = calloc(len, sizeof(char)); - strcpy(item->key, var->key); + item->key = strdup(var->key); len = strlen(var->value) + 1; - item->var = calloc(len, sizeof(char)); - strcpy(item->var, var->value); + item->var = strdup(var->value); char* tmp = strchr(item->var, ';'); if (tmp && *(tmp+1)==' ') { @@ -223,8 +223,8 @@ void OptionList_vars(const struct retro_variable *vars) { count += 1; // last entry after final '|' item->count = count; - item->values = calloc(count+1, sizeof(char*)); - item->labels = calloc(count+1, sizeof(char*)); + item->values = (decltype(item->values))calloc(count+1, sizeof(char*)); + item->labels = (decltype(item->labels))calloc(count+1, sizeof(char*)); tmp = opt; int j; diff --git a/workspace/all/minarch/ma_rewind.c b/workspace/all/minarch/ma_rewind.cpp similarity index 92% rename from workspace/all/minarch/ma_rewind.c rename to workspace/all/minarch/ma_rewind.cpp index 56bc5090f..0c73aea95 100644 --- a/workspace/all/minarch/ma_rewind.c +++ b/workspace/all/minarch/ma_rewind.cpp @@ -1,10 +1,16 @@ -#include "ma_internal.h" -#include "ma_rewind.h" - #include #include #include +// Project C headers declare C-linkage symbols (core, rewind_ctx, rewind_st, ff, +// LOG_*, …) defined in the still-C translation units. Include them as extern "C" +// so this C++ unit links against the unmangled names. (Same pattern as +// ma_audio.cpp.) +extern "C" { +#include "ma_internal.h" +#include "ma_rewind.h" +} + RewindContext rewind_ctx = {0}; #define REWIND_ENTRY_SIZE_HINT 4096 // assumed avg entry size for capacity calc @@ -62,7 +68,7 @@ void Rewind_free(void) { pthread_cond_destroy(&rewind_ctx.queue_cv); } memset(&rewind_ctx, 0, sizeof(rewind_ctx)); - rewinding = 0; + rewind_st.active = 0; } void Rewind_reset(void) { @@ -99,7 +105,7 @@ void Rewind_reset(void) { } pthread_mutex_unlock(&rewind_ctx.queue_mx); } - rewinding = 0; + rewind_st.active = 0; rewind_warn_empty = 0; } @@ -264,11 +270,11 @@ static int Rewind_compress_state(const uint8_t *src, size_t *dest_len, int *is_k int Rewind_init(size_t state_size) { Rewind_free(); // pull current option values directly - int enable = rewind_cfg_enable; - int buf_mb = rewind_cfg_buffer_mb; - int gran = rewind_cfg_granularity; - int audio = rewind_cfg_audio; - int compress = rewind_cfg_compress; + int enable = rewind_cfg.enable; + int buf_mb = rewind_cfg.buffer_mb; + int gran = rewind_cfg.granularity; + int audio = rewind_cfg.audio; + int compress = rewind_cfg.compress; if (!enable) { return 0; } @@ -289,7 +295,7 @@ int Rewind_init(size_t state_size) { state_size, rewind_ctx.capacity); rewind_ctx.compress = 1; } - int accel = rewind_cfg_lz4_acceleration; + int accel = rewind_cfg.lz4_acceleration; if (accel < 1) accel = 1; if (accel > REWIND_MAX_LZ4_ACCELERATION) accel = REWIND_MAX_LZ4_ACCELERATION; rewind_ctx.lz4_acceleration = accel; @@ -301,14 +307,14 @@ int Rewind_init(size_t state_size) { LOG_info("Rewind: config enable=%i bufferMB=%i interval=%ims audio=%i compression=raw\n", enable, buf_mb, gran, audio); } - rewind_ctx.buffer = calloc(1, rewind_ctx.capacity); + rewind_ctx.buffer = (decltype(rewind_ctx.buffer))calloc(1, rewind_ctx.capacity); if (!rewind_ctx.buffer) { LOG_error("Rewind: failed to allocate buffer\n"); return 0; } rewind_ctx.state_size = state_size; - rewind_ctx.state_buf = calloc(1, state_size); + rewind_ctx.state_buf = (decltype(rewind_ctx.state_buf))calloc(1, state_size); if (!rewind_ctx.state_buf) { LOG_error("Rewind: failed to allocate state buffer\n"); Rewind_free(); @@ -317,7 +323,7 @@ int Rewind_init(size_t state_size) { rewind_ctx.scratch_size = LZ4_compressBound((int)state_size); if (!rewind_ctx.compress) rewind_ctx.scratch_size = state_size; - rewind_ctx.scratch = calloc(1, rewind_ctx.scratch_size); + rewind_ctx.scratch = (decltype(rewind_ctx.scratch))calloc(1, rewind_ctx.scratch_size); if (!rewind_ctx.scratch) { LOG_error("Rewind: failed to allocate scratch buffer\n"); Rewind_free(); @@ -325,9 +331,9 @@ int Rewind_init(size_t state_size) { } // Allocate delta compression buffers (separate for encode/decode to avoid race conditions) - rewind_ctx.prev_state_enc = calloc(1, state_size); - rewind_ctx.prev_state_dec = calloc(1, state_size); - rewind_ctx.delta_buf = calloc(1, state_size); + rewind_ctx.prev_state_enc = (decltype(rewind_ctx.prev_state_enc))calloc(1, state_size); + rewind_ctx.prev_state_dec = (decltype(rewind_ctx.prev_state_dec))calloc(1, state_size); + rewind_ctx.delta_buf = (decltype(rewind_ctx.delta_buf))calloc(1, state_size); if (!rewind_ctx.prev_state_enc || !rewind_ctx.prev_state_dec || !rewind_ctx.delta_buf) { LOG_error("Rewind: failed to allocate delta buffers\n"); Rewind_free(); @@ -339,7 +345,7 @@ int Rewind_init(size_t state_size) { int entry_cap = rewind_ctx.capacity / REWIND_ENTRY_SIZE_HINT; if (entry_cap < REWIND_MIN_ENTRIES) entry_cap = REWIND_MIN_ENTRIES; rewind_ctx.entry_capacity = entry_cap; - rewind_ctx.entries = calloc(entry_cap, sizeof(RewindEntry)); + rewind_ctx.entries = (decltype(rewind_ctx.entries))calloc(entry_cap, sizeof(RewindEntry)); if (!rewind_ctx.entries) { LOG_error("Rewind: failed to allocate entry table\n"); Rewind_free(); @@ -377,18 +383,18 @@ int Rewind_init(size_t state_size) { // Larger states need a deeper pool to avoid drops; cap to a modest size to limit RAM rewind_ctx.pool_size = (state_size > REWIND_LARGE_STATE_THRESHOLD) ? REWIND_POOL_SIZE_LARGE : REWIND_POOL_SIZE_SMALL; if (rewind_ctx.pool_size < 1) rewind_ctx.pool_size = 1; - rewind_ctx.capture_pool = calloc(rewind_ctx.pool_size, sizeof(uint8_t*)); - rewind_ctx.capture_gen = calloc(rewind_ctx.pool_size, sizeof(unsigned int)); - rewind_ctx.capture_busy = calloc(rewind_ctx.pool_size, sizeof(uint8_t)); - rewind_ctx.free_stack = calloc(rewind_ctx.pool_size, sizeof(int)); - rewind_ctx.queue = calloc(rewind_ctx.pool_size, sizeof(int)); + rewind_ctx.capture_pool = (decltype(rewind_ctx.capture_pool))calloc(rewind_ctx.pool_size, sizeof(uint8_t*)); + rewind_ctx.capture_gen = (decltype(rewind_ctx.capture_gen))calloc(rewind_ctx.pool_size, sizeof(unsigned int)); + rewind_ctx.capture_busy = (decltype(rewind_ctx.capture_busy))calloc(rewind_ctx.pool_size, sizeof(uint8_t)); + rewind_ctx.free_stack = (decltype(rewind_ctx.free_stack))calloc(rewind_ctx.pool_size, sizeof(int)); + rewind_ctx.queue = (decltype(rewind_ctx.queue))calloc(rewind_ctx.pool_size, sizeof(int)); if (!rewind_ctx.capture_pool || !rewind_ctx.capture_gen || !rewind_ctx.capture_busy || !rewind_ctx.free_stack || !rewind_ctx.queue) { LOG_error("Rewind: failed to allocate async capture buffers\n"); Rewind_free(); return 0; } for (int i = 0; i < rewind_ctx.pool_size; i++) { - rewind_ctx.capture_pool[i] = calloc(1, state_size); + rewind_ctx.capture_pool[i] = (uint8_t*)calloc(1, state_size); if (!rewind_ctx.capture_pool[i]) { LOG_error("Rewind: failed to allocate capture slot %i\n", i); Rewind_free(); @@ -598,14 +604,14 @@ int Rewind_step_back(void) { uint32_t now_ms = SDL_GetTicks(); if (rewind_ctx.playback_interval_ms > 0 && rewind_ctx.last_step_ms && (int)(now_ms - rewind_ctx.last_step_ms) < rewind_ctx.playback_interval_ms) { - // still rewinding, just waiting for cadence; don't run core, just re-render + // still rewind_st.active, just waiting for cadence; don't run core, just re-render return REWIND_STEP_CADENCE; } // On first rewind step, we need to: // 1. Wait for any pending compression to finish (so entry indices are stable) // 2. Copy the last compressed state as our delta reference - if (!rewinding && rewind_ctx.compress && rewind_ctx.prev_state_dec) { + if (!rewind_st.active && rewind_ctx.compress && rewind_ctx.prev_state_dec) { // Wait for worker to finish all pending compressions Rewind_wait_for_worker_idle(); pthread_mutex_lock(&rewind_ctx.lock); @@ -708,7 +714,7 @@ int Rewind_step_back(void) { } pthread_mutex_unlock(&rewind_ctx.lock); - rewinding = 1; + rewind_st.active = 1; rewind_ctx.last_step_ms = now_ms; return REWIND_STEP_OK; } @@ -717,13 +723,13 @@ int Rewind_step_back(void) { // Also clears old entries that were compressed with a different delta chain void Rewind_sync_encode_state(void) { if (!rewind_ctx.enabled || !rewind_ctx.compress) return; - if (!rewinding) return; // Only sync if we were actually rewinding + if (!rewind_st.active) return; // Only sync if we were actually rewind_st.active pthread_mutex_lock(&rewind_ctx.lock); // The decoder's prev_state_dec contains the state we rewound to. // Use it as the new reference for future compressions so the existing - // rewind history remains valid and we can continue rewinding further back. + // rewind history remains valid and we can continue rewind_st.active further back. if (rewind_ctx.has_prev_dec && rewind_ctx.prev_state_dec && rewind_ctx.prev_state_enc) { memcpy(rewind_ctx.prev_state_enc, rewind_ctx.prev_state_dec, rewind_ctx.state_size); rewind_ctx.has_prev_enc = 1; diff --git a/workspace/all/minarch/ma_runframe.c b/workspace/all/minarch/ma_runframe.c deleted file mode 100644 index 75984da90..000000000 --- a/workspace/all/minarch/ma_runframe.c +++ /dev/null @@ -1,105 +0,0 @@ -#include -#include "ma_internal.h" -#include "ma_rewind.h" -#include "ma_input.h" -#include "ma_config.h" -#include "ma_runframe.h" - -void chooseSyncRef(void) { - switch (sync_ref) { - case SYNC_SRC_AUTO: use_core_fps = (core.get_region() == RETRO_REGION_PAL); break; - case SYNC_SRC_SCREEN: use_core_fps = 0; break; - case SYNC_SRC_CORE: use_core_fps = 1; break; - } - LOG_info("%s: sync_ref is set to %s, game region is %s, use core fps = %s\n", - __FUNCTION__, - sync_ref_labels[sync_ref], - core.get_region() == RETRO_REGION_NTSC ? "NTSC" : "PAL", - use_core_fps ? "yes" : "no"); -} - -static void limitFF(void) { - static uint64_t ff_frame_time = 0; - static uint64_t last_time = 0; - static int last_max_speed = -1; - if (last_max_speed!=max_ff_speed) { - last_max_speed = max_ff_speed; - ff_frame_time = 1000000 / (core.fps * (max_ff_speed + 1)); - } - - uint64_t now = getMicroseconds(); - if (fast_forward && max_ff_speed) { - if (last_time == 0) last_time = now; - int elapsed = now - last_time; - if (elapsed>0 && elapsed<0x80000) { - if (elapsed0 && delay<17) { // don't allow a delay any greater than a frame - SDL_Delay(delay); - } - } - last_time += ff_frame_time; - return; - } - } - last_time = now; -} - -void run_frame(void) { - // if rewind is toggled, fast-forward toggle must stay off; fast-forward hold pauses rewind - int do_rewind = (rewind_pressed || rewind_toggle) && !(rewind_toggle && ff_hold_active); - if (do_rewind) { - int was_rewinding = rewinding; - int rewind_result = Rewind_step_back(); - if (rewind_result == REWIND_STEP_OK) { - // Actually stepped back - run one frame to render the restored state - rewinding = 1; - fast_forward = 0; - core.run(); - } - else if (rewind_result == REWIND_STEP_CADENCE) { - // Waiting for cadence - don't run core, just re-render current frame - rewinding = 1; - fast_forward = 0; - // Poll input manually since core.run() isn't called - input_poll_callback(); - // Skip core.run() entirely to avoid advancing the game - } - else { - int hold_empty = rewind_ctx.enabled && rewind_pressed && !rewind_toggle; - if (hold_empty) { - // Hold-to-rewind: freeze when empty to avoid advance/rewind oscillation. - rewinding = was_rewinding ? 1 : 0; - // Poll input manually so release is detected while core.run() is skipped - input_poll_callback(); - } else { - // Buffer empty: auto untoggle rewind, resume FF if it was paused for a hold - if (rewind_toggle) rewind_toggle = 0; - if (ff_paused_by_rewind_hold && ff_toggled) { - ff_paused_by_rewind_hold = 0; - fast_forward = setFastForward(1); - } - if (was_rewinding) { - rewinding = 1; - Rewind_sync_encode_state(); - } - rewinding = 0; - core.run(); - Rewind_push(0); - } - } - } - else { - Rewind_sync_encode_state(); - rewinding = 0; - if (ff_paused_by_rewind_hold && !rewind_pressed) { - // resume fast forward after hold rewind ends - if (ff_toggled) fast_forward = setFastForward(1); - ff_paused_by_rewind_hold = 0; - } - - core.run(); - Rewind_push(0); - } - limitFF(); -} diff --git a/workspace/all/minarch/ma_runframe.cpp b/workspace/all/minarch/ma_runframe.cpp new file mode 100644 index 000000000..5cac5e3db --- /dev/null +++ b/workspace/all/minarch/ma_runframe.cpp @@ -0,0 +1,140 @@ +#include +#include + +// Project C headers declare C-linkage symbols (core, rewind_st, ff, LOG_*, …) +// defined in the still-C translation units. Include them as extern "C" so this +// C++ unit links against the unmangled names. (Same pattern as ma_audio.cpp.) +extern "C" { +#include "ma_internal.h" +#include "ma_rewind.h" +#include "ma_input.h" +#include "ma_config.h" +#include "ma_cheats.h" +#include "ma_runframe.h" +#include "notification.h" +} + +// Recover after a core throws mid-emulation (see run_frame). The known trigger is +// a cheat the core can't parse (Mednafen/supafaust defers Game-Genie parsing to +// retro_run and throws). Clear the core's cheat state so the next frame is clean, +// clear the frontend's enabled flags so the cheat menu reflects reality, and tell +// the user once. Called only from the catch, so it runs at most once per fault. +static void recoverFromCoreFault(void) { + if (core.cheat_reset) { try { core.cheat_reset(); } catch (...) {} } + for (size_t i = 0; i < cheatcodes.count; i++) cheatcodes.cheats[i].enabled = 0; + cheatcodes.enabled = 0; + Notification_push(NOTIFICATION_SETTING, "Cheat rejected by core - cheats disabled", NULL); +} + +void chooseSyncRef(void) { + switch (sync_ref) { + case SYNC_SRC_AUTO: use_core_fps = (core.get_region() == RETRO_REGION_PAL); break; + case SYNC_SRC_SCREEN: use_core_fps = 0; break; + case SYNC_SRC_CORE: use_core_fps = 1; break; + } + LOG_info("%s: sync_ref is set to %s, game region is %s, use core fps = %s\n", + __FUNCTION__, + sync_ref_labels[sync_ref], + core.get_region() == RETRO_REGION_NTSC ? "NTSC" : "PAL", + use_core_fps ? "yes" : "no"); +} + +static void limitFF(void) { + static uint64_t ff_frame_time = 0; + static uint64_t last_time = 0; + static int last_max_speed = -1; + if (last_max_speed!=max_ff_speed) { + last_max_speed = max_ff_speed; + ff_frame_time = 1000000 / (core.fps * (max_ff_speed + 1)); + } + + uint64_t now = getMicroseconds(); + if (ff.active && max_ff_speed) { + if (last_time == 0) last_time = now; + int elapsed = now - last_time; + if (elapsed>0 && elapsed<0x80000) { + if (elapsed0 && delay<17) { // don't allow a delay any greater than a frame + SDL_Delay(delay); + } + } + last_time += ff_frame_time; + return; + } + } + last_time = now; +} + +void run_frame(void) { + // Some cores (Mednafen/supafaust) DEFER cheat parsing to the emulation frame: + // retro_cheat_set only stores the code, and retro_run() parses it and throws a + // C++ exception (Mednafen::MDFN_Error) if it can't (e.g. Game Genie format on a + // core that wants raw addresses). Uncaught, that unwinds out of core.run() into + // the frontend and hits std::terminate -> abort. Guard the whole frame so a + // throwing core is recovered by clearing cheats instead of killing the emulator. + try { + // if rewind is toggled, fast-forward toggle must stay off; fast-forward hold pauses rewind + int do_rewind = (rewind_st.pressed || rewind_st.toggle) && !(rewind_st.toggle && ff.hold_active); + if (do_rewind) { + int was_rewinding = rewind_st.active; + int rewind_result = Rewind_step_back(); + if (rewind_result == REWIND_STEP_OK) { + // Actually stepped back - run one frame to render the restored state + rewind_st.active = 1; + ff.active = 0; + core.run(); + } + else if (rewind_result == REWIND_STEP_CADENCE) { + // Waiting for cadence - don't run core, just re-render current frame + rewind_st.active = 1; + ff.active = 0; + // Poll input manually since core.run() isn't called + input_poll_callback(); + // Skip core.run() entirely to avoid advancing the game + } + else { + int hold_empty = rewind_ctx.enabled && rewind_st.pressed && !rewind_st.toggle; + if (hold_empty) { + // Hold-to-rewind: freeze when empty to avoid advance/rewind oscillation. + rewind_st.active = was_rewinding ? 1 : 0; + // Poll input manually so release is detected while core.run() is skipped + input_poll_callback(); + } else { + // Buffer empty: auto untoggle rewind, resume FF if it was paused for a hold + if (rewind_st.toggle) rewind_st.toggle = 0; + if (ff.paused_by_rewind_hold && ff.toggled) { + ff.paused_by_rewind_hold = 0; + ff.active = setFastForward(1); + } + if (was_rewinding) { + rewind_st.active = 1; + Rewind_sync_encode_state(); + } + rewind_st.active = 0; + core.run(); + Rewind_push(0); + } + } + } + else { + Rewind_sync_encode_state(); + rewind_st.active = 0; + if (ff.paused_by_rewind_hold && !rewind_st.pressed) { + // resume fast forward after hold rewind ends + if (ff.toggled) ff.active = setFastForward(1); + ff.paused_by_rewind_hold = 0; + } + + core.run(); + Rewind_push(0); + } + limitFF(); + } catch (const std::exception& e) { + LOG_error("run_frame: core threw during emulation: %s — disabling cheats to recover\n", e.what()); + recoverFromCoreFault(); + } catch (...) { + LOG_error("run_frame: core threw an unknown exception during emulation — disabling cheats to recover\n"); + recoverFromCoreFault(); + } +} diff --git a/workspace/all/minarch/ma_saves.c b/workspace/all/minarch/ma_saves.cpp similarity index 82% rename from workspace/all/minarch/ma_saves.c rename to workspace/all/minarch/ma_saves.cpp index 8fe053e01..6ed4ff560 100644 --- a/workspace/all/minarch/ma_saves.c +++ b/workspace/all/minarch/ma_saves.cpp @@ -4,6 +4,11 @@ #include #include +// Project C headers declare C-linkage symbols (core, game, LOG_*, the libretro +// streams API, …) defined in the still-C translation units. Include them as +// extern "C" so this C++ unit links against the unmangled names. (Same pattern +// as ma_audio.cpp.) +extern "C" { #include "ma_internal.h" #include "ra_integration.h" #include "notification.h" @@ -14,6 +19,7 @@ #endif #include "ma_saves.h" +} /////////////////////////////////////// @@ -22,7 +28,7 @@ static void formatSavePath(char* work_name, char* filename, const char* suffix) if (tmp != NULL && strlen(tmp) > 2 && strlen(tmp) <= 5) { tmp[0] = '\0'; } - sprintf(filename, "%s/%s%s", core.saves_dir, work_name, suffix); + snprintf(filename, MAX_PATH, "%s/%s%s", core.saves_dir, work_name, suffix); } static void SRAM_getPath(char* filename) { @@ -30,15 +36,15 @@ static void SRAM_getPath(char* filename) { if (CFG_getSaveFormat() == SAVE_FORMAT_SRM || CFG_getSaveFormat() == SAVE_FORMAT_SRM_UNCOMPRESSED) { - strcpy(work_name, game.alt_name); + snprintf(work_name, sizeof(work_name), "%s", game.alt_name); formatSavePath(work_name, filename, ".srm"); } else if (CFG_getSaveFormat() == SAVE_FORMAT_GEN) { - strcpy(work_name, game.alt_name); + snprintf(work_name, sizeof(work_name), "%s", game.alt_name); formatSavePath(work_name, filename, ".sav"); } else { - sprintf(filename, "%s/%s.sav", core.saves_dir, game.alt_name); + snprintf(filename, MAX_PATH, "%s/%s.sav", core.saves_dir, game.alt_name); } LOG_info("SRAM_getPath %s\n", filename); @@ -108,7 +114,7 @@ void SRAM_write(void) { /////////////////////////////////////// static void RTC_getPath(char* filename) { - sprintf(filename, "%s/%s.rtc", core.saves_dir, game.alt_name); + snprintf(filename, MAX_PATH, "%s/%s.rtc", core.saves_dir, game.alt_name); } void RTC_read(void) { size_t rtc_size = core.get_memory_size(RETRO_MEMORY_RTC); @@ -165,34 +171,34 @@ void State_getPath(char* filename) { // should probably be removed at some point in the future. if (CFG_getStateFormat() == STATE_FORMAT_SRM_EXTRADOT || CFG_getStateFormat() == STATE_FORMAT_SRM_UNCOMRESSED_EXTRADOT) { - strcpy(work_name, game.alt_name); + snprintf(work_name, sizeof(work_name), "%s", game.alt_name); char* tmp = strrchr(work_name, '.'); if (tmp != NULL && strlen(tmp) > 2 && strlen(tmp) <= 5) { tmp[0] = '\0'; } if(state_slot == AUTO_RESUME_SLOT) - sprintf(filename, "%s/%s.state.auto", core.states_dir, work_name); + snprintf(filename, MAX_PATH, "%s/%s.state.auto", core.states_dir, work_name); else - sprintf(filename, "%s/%s.state.%i", core.states_dir, work_name, state_slot); + snprintf(filename, MAX_PATH, "%s/%s.state.%i", core.states_dir, work_name, state_slot); } else if (CFG_getStateFormat() == STATE_FORMAT_SRM || CFG_getStateFormat() == STATE_FORMAT_SRM_UNCOMRESSED) { - strcpy(work_name, game.alt_name); + snprintf(work_name, sizeof(work_name), "%s", game.alt_name); char* tmp = strrchr(work_name, '.'); if (tmp != NULL && strlen(tmp) > 2 && strlen(tmp) <= 5) { tmp[0] = '\0'; } if(state_slot == AUTO_RESUME_SLOT) - sprintf(filename, "%s/%s.state.auto", core.states_dir, work_name); + snprintf(filename, MAX_PATH, "%s/%s.state.auto", core.states_dir, work_name); else if(state_slot == 0) - sprintf(filename, "%s/%s.state", core.states_dir, work_name); + snprintf(filename, MAX_PATH, "%s/%s.state", core.states_dir, work_name); else - sprintf(filename, "%s/%s.state%i", core.states_dir, work_name, state_slot); + snprintf(filename, MAX_PATH, "%s/%s.state%i", core.states_dir, work_name, state_slot); } else { - sprintf(filename, "%s/%s.st%i", core.states_dir, game.alt_name, state_slot); + snprintf(filename, MAX_PATH, "%s/%s.st%i", core.states_dir, game.alt_name, state_slot); } } @@ -209,8 +215,19 @@ int State_read(void) { // from picoarch size_t state_size = core.serialize_size(); if (!state_size) return 0; - int was_ff = fast_forward; - fast_forward = 0; + int was_ff = ff.active; + ff.active = 0; + + // Hoisted above the first `goto error` below: C++ forbids a goto that jumps + // over a variable's initialization into its scope, and the error: label is + // in scope for these. state_rzfile in particular MUST be NULL before any + // goto reaches error: (which reads them). filename/state need no hoist. + uint8_t rastate_header[RASTATE_HEADER_SIZE] = {0}; +#ifdef HAS_SRM + rzipstream_t *state_rzfile = NULL; +#else + FILE *state_file = NULL; +#endif void *state = calloc(1, state_size); if (!state) { @@ -221,11 +238,7 @@ int State_read(void) { // from picoarch char filename[MAX_PATH]; State_getPath(filename); - uint8_t rastate_header[RASTATE_HEADER_SIZE] = {0}; - #ifdef HAS_SRM - rzipstream_t *state_rzfile = NULL; - state_rzfile = rzipstream_open(filename, RETRO_VFS_FILE_ACCESS_READ); if(!state_rzfile) { if (state_slot!=8) { // st8 is a default state in MiniUI and may not exist, that's okay @@ -262,7 +275,7 @@ int State_read(void) { // from picoarch if (state) free(state); if (state_rzfile) rzipstream_close(state_rzfile); #else - FILE *state_file = fopen(filename, "r"); + state_file = fopen(filename, "r"); if (!state_file) { if (state_slot!=8) { // st8 is a default state in MiniUI and may not exist, that's okay LOG_error("Error opening state file: %s (%s)\n", filename, strerror(errno)); @@ -297,7 +310,7 @@ int State_read(void) { // from picoarch if (state) free(state); if (state_file) fclose(state_file); #endif - fast_forward = was_ff; + ff.active = was_ff; return success; } @@ -313,8 +326,14 @@ int State_write(void) { // from picoarch size_t state_size = core.serialize_size(); if (!state_size) return 0; - int was_ff = fast_forward; - fast_forward = 0; + int was_ff = ff.active; + ff.active = 0; + + // Hoisted above the first `goto error` below (see State_read): the error: + // label reads state_file, and C++ forbids a goto crossing its initialization. +#ifndef HAS_SRM + FILE *state_file = NULL; +#endif void *state = calloc(1, state_size); if (!state) { @@ -348,7 +367,7 @@ int State_write(void) { // from picoarch error: if (state) free(state); #else - FILE *state_file = fopen(filename, "w"); + state_file = fopen(filename, "w"); if (!state_file) { LOG_error("Error opening state file: %s (%s)\n", filename, strerror(errno)); goto error; @@ -364,7 +383,7 @@ int State_write(void) { // from picoarch #endif sync(); - fast_forward = was_ff; + ff.active = was_ff; return success; } diff --git a/workspace/all/minarch/ma_video.c b/workspace/all/minarch/ma_video.cpp similarity index 86% rename from workspace/all/minarch/ma_video.c rename to workspace/all/minarch/ma_video.cpp index edee20f21..e199cb6eb 100644 --- a/workspace/all/minarch/ma_video.c +++ b/workspace/all/minarch/ma_video.cpp @@ -1,13 +1,22 @@ #include #include +// Project C headers declare C-linkage symbols defined in the still-C translation +// units; include them as extern "C" so this C++ unit links against the unmangled +// names. ( further down is self-guarded and stays where it is.) +extern "C" { #include "ma_internal.h" #include "scaler.h" #include "ma_video.h" +} -static const char* bitmap_font[] = { - ['0'] = +// A sparse char->glyph table. Was a C99 designated array (`['0'] = ...`); g++ 8.3 +// rejects sparse/non-trivial array designators in C++, so it's a {char, rows} pair +// table with a linear lookup (see bitmap_glyph) — the debug HUD is not hot. +struct BitmapGlyph { unsigned char ch; const char* rows; }; +static const BitmapGlyph bitmap_font[] = { + { '0', " 111 " "1 1" "1 1" @@ -16,8 +25,8 @@ static const char* bitmap_font[] = { "11 1" "1 1" "1 1" - " 111 ", - ['1'] = + " 111 " }, + { '1', " 1 " " 111 " " 1 " @@ -26,8 +35,8 @@ static const char* bitmap_font[] = { " 1 " " 1 " " 1 " - " 1 ", - ['2'] = + " 1 " }, + { '2', " 111 " "1 1" " 1" @@ -36,8 +45,8 @@ static const char* bitmap_font[] = { " 1 " "1 " "1 " - "11111", - ['3'] = + "11111" }, + { '3', " 111 " "1 1" " 1" @@ -46,8 +55,8 @@ static const char* bitmap_font[] = { " 1" " 1" "1 1" - " 111 ", - ['4'] = + " 111 " }, + { '4', "1 1" "1 1" "1 1" @@ -56,8 +65,8 @@ static const char* bitmap_font[] = { "1 1" "11111" " 1" - " 1", - ['5'] = + " 1" }, + { '5', "11111" "1 " "1 " @@ -66,8 +75,8 @@ static const char* bitmap_font[] = { " 1" " 1" "1 1" - " 111 ", - ['6'] = + " 111 " }, + { '6', " 111 " "1 " "1 " @@ -76,8 +85,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - " 111 ", - ['7'] = + " 111 " }, + { '7', "11111" " 1" " 1" @@ -86,8 +95,8 @@ static const char* bitmap_font[] = { " 1 " " 1 " " 1 " - " 1 ", - ['8'] = + " 1 " }, + { '8', " 111 " "1 1" "1 1" @@ -96,8 +105,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - " 111 ", - ['9'] = + " 111 " }, + { '9', " 111 " "1 1" "1 1" @@ -106,8 +115,8 @@ static const char* bitmap_font[] = { " 1111" " 1" " 1" - " 111 ", - ['.'] = + " 111 " }, + { '.', " " " " " " @@ -116,8 +125,8 @@ static const char* bitmap_font[] = { " " " " " 11 " - " 11 ", - [','] = + " 11 " }, + { ',', " " " " " " @@ -126,8 +135,8 @@ static const char* bitmap_font[] = { " " " 1 " " 1 " - " 1 ", - [' '] = + " 1 " }, + { ' ', " " " " " " @@ -136,8 +145,8 @@ static const char* bitmap_font[] = { " " " " " " - " ", - ['('] = + " " }, + { '(', " 1 " " 1 " " 1 " @@ -146,8 +155,8 @@ static const char* bitmap_font[] = { " 1 " " 1 " " 1 " - " 1 ", - [')'] = + " 1 " }, + { ')', " 1 " " 1 " " 1 " @@ -156,8 +165,8 @@ static const char* bitmap_font[] = { " 1 " " 1 " " 1 " - " 1 ", - ['/'] = + " 1 " }, + { '/', " 1 " " 1 " " 1 " @@ -166,8 +175,8 @@ static const char* bitmap_font[] = { " 1 " " 1 " " 1 " - " 1 ", - ['x'] = + " 1 " }, + { 'x', " " " " "1 1" @@ -176,8 +185,8 @@ static const char* bitmap_font[] = { " 1 " " 1 1 " "1 1" - "1 1", - ['%'] = + "1 1" }, + { '%', " 1 " "1 1 " "1 1 1" @@ -186,8 +195,8 @@ static const char* bitmap_font[] = { " 1 1 " "1 1 1" " 1 1" - " 1 ", - ['-'] = + " 1 " }, + { '-', " " " " " " @@ -196,8 +205,8 @@ static const char* bitmap_font[] = { " " " " " " - " ", - ['c'] = + " " }, + { 'c', " " " " " 111 " @@ -206,8 +215,8 @@ static const char* bitmap_font[] = { "1 " "1 " "1 1" - " 111 ", - ['m'] = + " 111 " }, + { 'm', " " " " "11 11" @@ -216,8 +225,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1 1", - ['z'] = + "1 1" }, + { 'z', " " " " " " @@ -226,8 +235,8 @@ static const char* bitmap_font[] = { " 1 " " 1 " "1 " - "11111", - ['h'] = + "11111" }, + { 'h', " " "1 " "1 " @@ -236,8 +245,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1 1", - ['D'] = + "1 1" }, + { 'D', "1111 " "1 1" "1 1" @@ -246,8 +255,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1111 ", - ['J'] = + "1111 " }, + { 'J', " 111" " 1" " 1" @@ -256,8 +265,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - " 111 ", - ['A'] = + " 111 " }, + { 'A', " 1 " " 1 1 " "1 1" @@ -266,8 +275,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1 1", - ['M'] = + "1 1" }, + { 'M', "1 1" "11 11" "1 1 1" @@ -276,8 +285,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1 1", - [':'] = + "1 1" }, + { ':', " " " " " 1 " @@ -286,8 +295,8 @@ static const char* bitmap_font[] = { " " " 1 " " " - " ", - ['B'] = + " " }, + { 'B', "1111 " "1 1" "1 1" @@ -296,8 +305,8 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1111 ", - ['C'] = + "1111 " }, + { 'C', " 111 " "1 1" "1 " @@ -306,8 +315,8 @@ static const char* bitmap_font[] = { "1 " "1 " "1 1" - " 111 ", - ['N'] = + " 111 " }, + { 'N', "1 1" "1 1" "11 1" @@ -316,8 +325,8 @@ static const char* bitmap_font[] = { "1 1" "1 11" "1 1" - "1 1", - ['H'] = + "1 1" }, + { 'H', "1 1" "1 1" "1 1" @@ -326,9 +335,15 @@ static const char* bitmap_font[] = { "1 1" "1 1" "1 1" - "1 1", + "1 1" }, }; +static const char* bitmap_glyph(unsigned char ch) { + for (unsigned i = 0; i < sizeof(bitmap_font) / sizeof(bitmap_font[0]); i++) + if (bitmap_font[i].ch == ch) return bitmap_font[i].rows; + return NULL; +} + void drawRect(int x, int y, int w, int h, uint32_t c, uint32_t *data, int stride) { for (int _x = x; _x < x + w; _x++) { data[_x + y * stride] = c; @@ -378,8 +393,8 @@ static void blitBitmapText(char* text, int ox, int oy, uint32_t* data, int strid // uint32_t* row = data + y * stride; int current_x = 0; for (int i = 0; i < len; i++) { - const char* c = bitmap_font[(unsigned char)text[i]]; - if (!c) c = bitmap_font[' ']; + const char* c = bitmap_glyph((unsigned char)text[i]); + if (!c) c = bitmap_glyph(' '); for (int x = 0; x < DEBUG_CHAR_WIDTH; x++) { if (current_x >= w) break; @@ -446,7 +461,7 @@ void selectScaler(int src_w, int src_h, int src_p) { renderer.true_h = src_h; // TODO: this is saving non-rgb30 devices from themselves...or rather, me - int scaling = screen_scaling; + int scaling = display_cfg.scaling; if (scaling==SCALE_CROPPED && DEVICE_WIDTH==HDMI_WIDTH) { scaling = SCALE_NATIVE; } @@ -455,7 +470,7 @@ void selectScaler(int src_w, int src_h, int src_p) { // this is the same whether fit or oversized scale = MIN(DEVICE_WIDTH/src_w, DEVICE_HEIGHT/src_h); if (!scale) { - sprintf(scaler_name, "forced crop"); + snprintf(scaler_name, sizeof(scaler_name), "forced crop"); dst_w = DEVICE_WIDTH; dst_h = DEVICE_HEIGHT; dst_p = DEVICE_PITCH; @@ -478,7 +493,7 @@ void selectScaler(int src_w, int src_h, int src_p) { int scale_y = CEIL_DIV(DEVICE_HEIGHT, src_h); scale = MIN(scale_x, scale_y); - sprintf(scaler_name, "cropped"); + snprintf(scaler_name, sizeof(scaler_name), "cropped"); dst_w = DEVICE_WIDTH; dst_h = DEVICE_HEIGHT; dst_p = DEVICE_PITCH; @@ -508,7 +523,7 @@ void selectScaler(int src_w, int src_h, int src_p) { } } else { - sprintf(scaler_name, "integer"); + snprintf(scaler_name, sizeof(scaler_name), "integer"); int scaled_w = src_w * scale; int scaled_h = src_h * scale; dst_w = DEVICE_WIDTH; @@ -534,7 +549,7 @@ void selectScaler(int src_w, int src_h, int src_p) { int scaled_h = src_h * scale; if (scaling==SCALE_FULLSCREEN) { - sprintf(scaler_name, "full%i", scale); + snprintf(scaler_name, sizeof(scaler_name), "full%i", scale); // type = 'full (oversized)'; dst_w = scaled_w; dst_h = scaled_h; @@ -563,7 +578,7 @@ void selectScaler(int src_w, int src_h, int src_p) { dst_p = dst_w * FIXED_BPP; - sprintf(scaler_name, "raw%i", scale); + snprintf(scaler_name, sizeof(scaler_name), "raw%i", scale); } else { double src_aspect_ratio = ((double)src_w) / src_h; @@ -583,7 +598,7 @@ void selectScaler(int src_w, int src_h, int src_p) { // then to fixed aspect if (core_aspect>fixed_aspect) { - sprintf(scaler_name, "aspect%iL", scale); + snprintf(scaler_name, sizeof(scaler_name), "aspect%iL", scale); // letterbox // dst_w = scaled_w; // dst_h = scaled_w / fixed_aspect_ratio; @@ -596,7 +611,7 @@ void selectScaler(int src_w, int src_h, int src_p) { dst_y = (dst_h - scaled_h) / 2; } else if (core_aspect0) { - sprintf(debug_text, "%i/%ix%i/%ix%i/%ix%i", currentshaderpass, currentshadersrcw,currentshadersrch,currentshadertexw,currentshadertexh,currentshaderdstw,currentshaderdsth); + if(gfx_render.pass>0) { + snprintf(debug_text, sizeof(debug_text), "%i/%ix%i/%ix%i/%ix%i", gfx_render.pass, gfx_render.src_w,gfx_render.src_h,gfx_render.tex_w,gfx_render.tex_h,gfx_render.dst_w,gfx_render.dst_h); blitBitmapText(debug_text,x,-y - 42,(uint32_t*)data,pitch / 4, width,height); } @@ -746,7 +761,7 @@ static void video_refresh_callback_main(const void *data, unsigned width, unsign // 14 will let GB hit 10x but NES and SNES will drop to 1.5x at 30fps (not sure why) // but 10 hurts PS... // TODO: 10 was based on rg35xx, probably different results on other supported platforms - if (fast_forward && SDL_GetTicks()-last_flip_time<10) return; + if (ff.active && SDL_GetTicks()-last_flip_time<10) return; // FFVII menus // 16: 30/200 @@ -979,7 +994,7 @@ void video_refresh_callback(const void* data, unsigned width, unsigned height, s pitch = width * sizeof(Uint32); // Set ambient lighting color (if enabled) - if (ambient_mode && !fast_forward && data) { + if (ambient_mode && !ff.active && data) { GFX_setAmbientColor(data, width, height, pitch, ambient_mode); } diff --git a/workspace/all/minarch/makefile b/workspace/all/minarch/makefile index ae10f2529..57ab7c40d 100644 --- a/workspace/all/minarch/makefile +++ b/workspace/all/minarch/makefile @@ -22,20 +22,35 @@ SDL?=SDL TARGET = minarch PRODUCT= build/$(PLATFORM)/$(TARGET).elf INCDIR = -I. -I./libretro-common/include/ -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ma_cheats.c ma_rewind.c ma_audio.c ma_input.c \ - ma_options.c ma_frontend_opts.c ma_saves.c ma_video.c ma_core.c ma_game.c ma_environment.c ma_config.c ma_menu.c ma_runframe.c \ - ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c \ - ../common/notification.c ../../$(PLATFORM)/platform/platform.c + +# C and C++ translation units are listed separately. C files compile as C; +# C++ files compile as C++; everything links through g++ so the two can mix +# as modules are migrated to C++ incrementally. +CSOURCE = +CXXSOURCE = ../../$(PLATFORM)/platform/platform.cpp $(TARGET).cpp ../common/notification.cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp \ + ma_audio.cpp ma_input.cpp ma_runframe.cpp ma_cheats.cpp ma_core.cpp \ + ma_rewind.cpp ma_saves.cpp ma_game.cpp ma_environment.cpp \ + ma_options.cpp ma_video.cpp ma_menu.cpp ma_frontend_opts.cpp ma_config.cpp # RA support ifneq (,$(filter $(PLATFORM),tg5040 tg5050 my355 desktop)) -# RA source files -SOURCE += ../common/http.c ../common/ra_badges.c ../common/ra_offline.c ../common/ra_sync.c ../common/ra_event_queue.c ra_integration.c chd_reader.c +# RA source files (shared common/ helpers stay C; the two minarch-local RA units +# are C++). ra_integration sits on the rcheevos C callback boundary — kept +# mechanical, no throwing constructs on that path. +CXXSOURCE += ra_integration.cpp chd_reader.cpp ../common/ra_offline.cpp ../common/ra_sync.cpp ../common/ra_event_queue.cpp ../common/http.cpp ../common/ra_badges.cpp endif -CC = $(CROSS_COMPILE)gcc +CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +# CXXFLAGS inherits the platform CFLAGS (SDL/GL/PREFIX include paths) before the +# C-only -std is appended, so C++ TUs get the same include/define environment. +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) +CFLAGS += -std=gnu99 LDFLAGS += -lmsettings -lsamplerate ifeq ($(PROFILE), 1) @@ -94,14 +109,24 @@ BUILD_DATE!=date +%Y.%m.%d BUILD_HASH!=cat ../../hash.txt CFLAGS += -DBUILD_DATE=\"${BUILD_DATE}\" -DBUILD_HASH=\"${BUILD_HASH}\" +OBJ_DIR = build/$(PLATFORM) +OBJS = $(addprefix $(OBJ_DIR)/,$(notdir $(CSOURCE:.c=.o))) \ + $(addprefix $(OBJ_DIR)/,$(notdir $(CXXSOURCE:.cpp=.o))) +DEPS = $(OBJS:.o=.d) + +# Locate sources across their directories so single pattern rules suffice. +vpath %.c . ../common ../../$(PLATFORM)/platform +vpath %.cpp . ../common ../../$(PLATFORM)/platform + ifeq ($(PLATFORM), desktop) -all: clean libretro-common rcheevos libchdr $(PREFIX_LOCAL)/include/msettings.h - mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) +all: clean libretro-common rcheevos libchdr $(PREFIX_LOCAL)/include/msettings.h $(PRODUCT) else ifneq (,$(filter $(PLATFORM),tg5040 tg5050 my355)) # Platforms with RA support -all: clean libretro-common libsrm.a rcheevos libchdr $(PREFIX_LOCAL)/include/msettings.h - mkdir -p build/$(PLATFORM) +all: clean libretro-common libsrm.a rcheevos libchdr $(PREFIX_LOCAL)/include/msettings.h copylibs $(PRODUCT) +endif + +# Runtime shared libraries copied next to the binary on handheld platforms. +copylibs: | $(OBJ_DIR) cp -L $(PREFIX)/lib/libsamplerate.so.* build/$(PLATFORM) cp -L $(PREFIX)/lib/libcrypto.so.* build/$(PLATFORM) chmod a+r build/$(PLATFORM)/libcrypto.so.* @@ -117,8 +142,18 @@ endif cp -L $(PREFIX)/lib/libzstd.so.1 build/$(PLATFORM) cp -L $(PREFIX)/lib/liblz4.so.1 build/$(PLATFORM) cp -L libchdr/build/$(PLATFORM)/libchdr.so.0 build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) -endif + +$(PRODUCT): $(OBJS) + $(CXX) $(OBJS) -o $(PRODUCT) $(LDFLAGS) -lstdc++ + +$(OBJ_DIR)/%.o: %.c | $(OBJ_DIR) + $(CC) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/%.o: %.cpp | $(OBJ_DIR) + $(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR): + mkdir -p $(OBJ_DIR) libretro-common: git clone https://github.com/libretro/libretro-common @@ -155,6 +190,9 @@ $(OBJECTS) : libsrm.a: $(OBJECTS) cd libretro-common && $(AR) $(ARFLAGS) $@ $? +-include $(DEPS) + clean: rm -f $(PRODUCT) + rm -f $(OBJS) $(DEPS) rm -f $(OBJECTS) $(LIBRARY) \ No newline at end of file diff --git a/workspace/all/minarch/minarch.c b/workspace/all/minarch/minarch.cpp similarity index 86% rename from workspace/all/minarch/minarch.c rename to workspace/all/minarch/minarch.cpp index b3e156eda..cbd19ead0 100644 --- a/workspace/all/minarch/minarch.c +++ b/workspace/all/minarch/minarch.cpp @@ -1,8 +1,12 @@ #include -#include - #include +// Project C headers declare C-linkage symbols shared with the still-C common/ +// units and with the (now C++) sibling ma_* modules; include them as extern "C" +// so this C++ unit both links against and defines the unmangled names. +extern "C" { +#include + #include "notification.h" #include "ra_integration.h" @@ -19,6 +23,7 @@ #include "ma_environment.h" #include "ma_config.h" #include "ma_runframe.h" +} /////////////////////////////////////// @@ -30,33 +35,31 @@ int simple_mode = 0; enum retro_pixel_format fmt = RETRO_PIXEL_FORMAT_XRGB8888; // default frontend options -int screen_scaling = SCALE_ASPECT; int resampling_quality = 2; int ambient_mode = 0; -int screen_sharpness = SHARPNESS_SOFT; -int screen_effect = EFFECT_NONE; -int cfg_screenx = 64; -int cfg_screeny = 64; -int overlay = 0; +int overlay = 0; +DisplayConfig display_cfg = { + .scaling = SCALE_ASPECT, + .effect = EFFECT_NONE, + .sharpness = SHARPNESS_SOFT, + .screenx = 64, + .screeny = 64, +}; int use_core_fps = 0; int sync_ref = 0; int show_debug = 0; int max_ff_speed = 3; // 4x -int ff_audio = 0; -int fast_forward = 0; -int rewind_pressed = 0; -int rewind_toggle = 0; -int last_rewind_pressed = 0; -int ff_toggled = 0; -int ff_hold_active = 0; -int ff_paused_by_rewind_hold = 0; -int rewinding = 0; -int rewind_cfg_enable = MINARCH_DEFAULT_REWIND_ENABLE; -int rewind_cfg_buffer_mb = MINARCH_DEFAULT_REWIND_BUFFER_MB; -int rewind_cfg_granularity = MINARCH_DEFAULT_REWIND_GRANULARITY; -int rewind_cfg_audio = MINARCH_DEFAULT_REWIND_AUDIO; -int rewind_cfg_compress = 1; -int rewind_cfg_lz4_acceleration = MINARCH_DEFAULT_REWIND_LZ4_ACCELERATION; + +FastForward ff = {0}; +RewindState rewind_st = {0}; +RewindConfig rewind_cfg = { + .enable = MINARCH_DEFAULT_REWIND_ENABLE, + .buffer_mb = MINARCH_DEFAULT_REWIND_BUFFER_MB, + .granularity = MINARCH_DEFAULT_REWIND_GRANULARITY, + .audio = MINARCH_DEFAULT_REWIND_AUDIO, + .compress = 1, + .lz4_acceleration = MINARCH_DEFAULT_REWIND_LZ4_ACCELERATION, +}; int rewind_init_ready = 0; // gate Rewind_init from syncFrontend until startup is past Core_load int overclock = 0; // auto int has_custom_controllers = 0; @@ -72,7 +75,7 @@ GFX_Renderer renderer; /////////////////////////////////////// -struct Core core; +struct Core core = {}; // Core has const char[] members; C++ needs explicit zero-init (matches C static init) @@ -129,7 +132,7 @@ void hdmimon(void) { int main(int argc , char* argv[]) { //static char asoundpath[MAX_PATH]; - //sprintf(asoundpath, "%s/.asoundrc", getenv("HOME")); + //snprintf(asoundpath, sizeof(asoundpath), "%s/.asoundrc", getenv("HOME")); //LOG_info("minarch: need asoundrc at %s\n", asoundpath); //if(exists(asoundpath)) // LOG_info("asoundrc exists at %s\n", asoundpath); @@ -146,8 +149,8 @@ int main(int argc , char* argv[]) { char rom_path[MAX_PATH]; char tag_name[MAX_PATH]; - strcpy(core_path, argv[1]); - strcpy(rom_path, argv[2]); + snprintf(core_path, sizeof(core_path), "%s", argv[1]); + snprintf(rom_path, sizeof(rom_path), "%s", argv[2]); getEmuName(rom_path, tag_name); LOG_info("rom_path: %s\n", rom_path); @@ -175,7 +178,12 @@ int main(int argc , char* argv[]) { Game_open(rom_path); // nes tries to load gamegenie setting before this returns ffs if (!game.is_open) goto finish; - + // C++ forbids `goto` jumping over an initialized local that is still in scope + // at the label. Everything from here to `finish:` lives in its own block so the + // jump above lands outside it (has_pending_opt_change, rewind_initialized, + // pixels, rawSurface, converted are all scoped inside). + { + simple_mode = exists(SIMPLE_MODE_PATH); // restore options @@ -212,7 +220,7 @@ int main(int argc , char* argv[]) { // Pass ROM data if available, otherwise just path (for cores that load from file) { char* rom_path_for_ra = game.tmp_path[0] ? game.tmp_path : game.path; - RA_loadGame(rom_path_for_ra, game.data, game.size, core.tag); + RA_loadGame(rom_path_for_ra, (const uint8_t*)game.data, game.size, core.tag); } State_resume(); @@ -346,6 +354,8 @@ int main(int argc , char* argv[]) { Menu_quit(); QuitSettings(); + } // end goto-scope block + finish: Perf_setCPUMonitorEnabled(0); diff --git a/workspace/all/minarch/ra_integration.c b/workspace/all/minarch/ra_integration.cpp similarity index 99% rename from workspace/all/minarch/ra_integration.c rename to workspace/all/minarch/ra_integration.cpp index f898ebc51..8ea6ee869 100644 --- a/workspace/all/minarch/ra_integration.c +++ b/workspace/all/minarch/ra_integration.cpp @@ -1,6 +1,12 @@ #define RA_LOG_PREFIX "RA" -#include "ra_log.h" +// Project C headers declare C-linkage symbols (config, http, api, notification, +// the RA helpers, …) defined in the still-C common translation units. Include +// them as extern "C" so this C++ unit links against the unmangled names. +// (Same pattern as ma_environment.cpp / ma_saves.cpp.) +#define RA_UTIL_NEED_SDL +extern "C" { +#include "ra_log.h" #include "ra_integration.h" #include "ra_consoles.h" #include "chd_reader.h" @@ -12,10 +18,9 @@ #include "ra_sync.h" #include "defines.h" #include "api.h" - -#define RA_UTIL_NEED_SDL #include "ra_util.h" #include "ra_event_queue.h" +} #include #include @@ -210,7 +215,10 @@ typedef struct { #define RA_RESPONSE_QUEUE_SIZE 16 static RA_QueuedResponse ra_response_queue[RA_RESPONSE_QUEUE_SIZE]; -static volatile int ra_response_queue_count = 0; +// Always accessed under ra_queue_mutex (except the pre-thread init), so the +// lock — not a volatile qualifier — provides visibility/ordering. Plain int +// also avoids C++20's deprecation of ++/-- on volatile-qualified types. +static int ra_response_queue_count = 0; static int ra_response_queue_head = 0; /* next slot to read (main thread) */ static int ra_response_queue_tail = 0; /* next slot to write (worker threads) */ static SDL_mutex* ra_queue_mutex = NULL; diff --git a/workspace/all/minarch/ra_integration.h b/workspace/all/minarch/ra_integration.h index 45f5bc2ce..aadc1a0e3 100644 --- a/workspace/all/minarch/ra_integration.h +++ b/workspace/all/minarch/ra_integration.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + // See: https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration /** @@ -171,4 +175,8 @@ void RA_setMemoryMap(const void* mmap); */ void RA_initMemoryRegions(uint32_t console_id); +#ifdef __cplusplus +} +#endif + #endif // __RA_INTEGRATION_H__ diff --git a/workspace/all/minput/makefile b/workspace/all/minput/makefile index cf532818a..2a59499b3 100644 --- a/workspace/all/minput/makefile +++ b/workspace/all/minput/makefile @@ -21,18 +21,29 @@ SDL?=SDL TARGET = minput INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/api.c ../common/config.c ../common/scaler.c ../../$(PLATFORM)/platform/platform.c +# Shared common/ + platform stay C; the tool itself is C++20. Compile the C +# units to objects, then compile the .cpp and link everything through g++. +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) LDFLAGS += -lmsettings PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/minput/minput.c b/workspace/all/minput/minput.cpp similarity index 72% rename from workspace/all/minput/minput.c rename to workspace/all/minput/minput.cpp index 5656a8111..60e8f526e 100644 --- a/workspace/all/minput/minput.c +++ b/workspace/all/minput/minput.cpp @@ -1,13 +1,17 @@ -#include -#include +#include +#include #include -#include -#include +#include +// Project + libmsettings C headers declare C-linkage symbols defined in the +// still-C common/ and libmsettings translation units; include as extern "C". +extern "C" { +#include #include "sdl.h" #include "defines.h" #include "api.h" #include "utils.h" +} // full-scale SDL joystick axis magnitude, used to map raw axis values to on-screen nub travel #define STICK_AXIS_MAX 32767 @@ -15,10 +19,9 @@ static int getButtonWidth(char* label) { SDL_Surface* text; int w = 0; - if (strlen(label)<=2) w = SCALE1(BUTTON_SIZE); else { - text = TTF_RenderUTF8_Blended(font.tiny, label, ALT_BUTTON_TEXT_COLOR); + text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, label, ALT_BUTTON_TEXT_COLOR); w = SCALE1(BUTTON_SIZE) + text->w; SDL_FreeSurface(text); } @@ -36,15 +39,18 @@ static void blitButton(char* label, SDL_Surface* dst, int pressed, int x, int y, int len = strlen(label); if (len<=2) { - text = TTF_RenderUTF8_Blended(len==2?font.small:font.medium, label, text_color); + text = TTF_RenderUTF8_Blended(len==2?GFX_getFonts()->small:GFX_getFonts()->medium, label, text_color); GFX_blitAssetColor(ASSET_BUTTON, NULL, dst, &point, fill); - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){point.x+(SCALE1(BUTTON_SIZE)-text->w)/2,point.y+(SCALE1(BUTTON_SIZE)-text->h)/2}); + SDL_Rect text_dst = {point.x+(SCALE1(BUTTON_SIZE)-text->w)/2,point.y+(SCALE1(BUTTON_SIZE)-text->h)/2}; + SDL_BlitSurface(text, NULL, dst, &text_dst); } else { - text = TTF_RenderUTF8_Blended(font.tiny, label, text_color); + text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, label, text_color); w = w ? w : SCALE1(BUTTON_SIZE)/2+text->w; - GFX_blitPillColor(ASSET_BUTTON, dst, &(SDL_Rect){point.x,point.y,w,SCALE1(BUTTON_SIZE)}, fill, RGB_WHITE); - SDL_BlitSurface(text, NULL, dst, &(SDL_Rect){point.x+(w-text->w)/2,point.y+(SCALE1(BUTTON_SIZE)-text->h)/2,text->w,text->h}); + SDL_Rect pill = {point.x,point.y,w,SCALE1(BUTTON_SIZE)}; + GFX_blitPillColor(ASSET_BUTTON, dst, &pill, fill, RGB_WHITE); + SDL_Rect text_dst = {point.x+(w-text->w)/2,point.y+(SCALE1(BUTTON_SIZE)-text->h)/2,text->w,text->h}; + SDL_BlitSurface(text, NULL, dst, &text_dst); } SDL_FreeSurface(text); @@ -55,7 +61,8 @@ static void blitButton(char* label, SDL_Surface* dst, int pressed, int x, int y, static void fillCircle(SDL_Surface* dst, int cx, int cy, int radius, uint32_t color) { for (int dy = -radius; dy <= radius; dy++) { int dx = (int)(sqrt((double)radius*radius - (double)dy*dy) + 0.5); - SDL_FillRect(dst, &(SDL_Rect){cx-dx, cy+dy, dx*2, 1}, color); + SDL_Rect row = {cx-dx, cy+dy, dx*2, 1}; + SDL_FillRect(dst, &row, color); } } @@ -89,6 +96,7 @@ int main(int argc , char* argv[]) { // int show_setting = 0; PAD_Axis last_laxis = {0,0}; PAD_Axis last_raxis = {0,0}; + PAD_Context* pad = PAD_getContext(); while(!quit) { GFX_startFrame(); uint32_t frame_start = SDL_GetTicks(); @@ -96,10 +104,10 @@ int main(int argc , char* argv[]) { PAD_poll(); if (PAD_anyPressed() || PAD_anyJustReleased()) dirty = 1; - if (pad.laxis.x!=last_laxis.x || pad.laxis.y!=last_laxis.y || pad.raxis.x!=last_raxis.x || pad.raxis.y!=last_raxis.y) { + if (pad->laxis.x!=last_laxis.x || pad->laxis.y!=last_laxis.y || pad->raxis.x!=last_raxis.x || pad->raxis.y!=last_raxis.y) { dirty = 1; - last_laxis = pad.laxis; - last_raxis = pad.raxis; + last_laxis = pad->laxis; + last_raxis = pad->raxis; } if (PAD_isPressed(BTN_SELECT) && PAD_isPressed(BTN_START)) quit = 1; @@ -123,7 +131,7 @@ int main(int argc , char* argv[]) { if (has_L2) w += getButtonWidth("L2") + SCALE1(BUTTON_MARGIN); if (has_L4) w += getButtonWidth("L4") + SCALE1(BUTTON_MARGIN); if (!has_L2 && !has_L4) x += SCALE1(PILL_SIZE); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x, y, w}); + { SDL_Rect pill = {x, y, w}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("L1", screen, PAD_isPressed(BTN_L1), x + SCALE1(BUTTON_MARGIN), y+SCALE1(BUTTON_MARGIN),0); if (has_L2) { @@ -152,7 +160,7 @@ int main(int argc , char* argv[]) { x = FIXED_WIDTH - w - SCALE1(BUTTON_MARGIN + PADDING); if (!has_R2 && !has_R4) x -= SCALE1(PILL_SIZE); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,w}); + { SDL_Rect pill = {x,y,w}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } if(has_R4) { x+= SCALE1(BUTTON_MARGIN); @@ -173,24 +181,24 @@ int main(int argc , char* argv[]) { int y = oy + SCALE1(PILL_SIZE*2); int o = SCALE1(BUTTON_MARGIN); - SDL_FillRect(screen, &(SDL_Rect){x,y+SCALE1(PILL_SIZE/2),SCALE1(PILL_SIZE),SCALE1(PILL_SIZE*2)}, THEME_COLOR2); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect r = {x,y+SCALE1(PILL_SIZE/2),SCALE1(PILL_SIZE),SCALE1(PILL_SIZE*2)}; SDL_FillRect(screen, &r, THEME_COLOR2); } + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("U", screen, PAD_isPressed(BTN_DPAD_UP), x+o, y+o,0); y += SCALE1(PILL_SIZE*2); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("D", screen, PAD_isPressed(BTN_DPAD_DOWN), x+o, y+o,0); x -= SCALE1(PILL_SIZE); y -= SCALE1(PILL_SIZE); - SDL_FillRect(screen, &(SDL_Rect){x+SCALE1(PILL_SIZE/2),y,SCALE1(PILL_SIZE*2),SCALE1(PILL_SIZE)}, THEME_COLOR2); + { SDL_Rect r = {x+SCALE1(PILL_SIZE/2),y,SCALE1(PILL_SIZE*2),SCALE1(PILL_SIZE)}; SDL_FillRect(screen, &r, THEME_COLOR2); } - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("L", screen, PAD_isPressed(BTN_DPAD_LEFT), x+o, y+o,0); x += SCALE1(PILL_SIZE*2); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("R", screen, PAD_isPressed(BTN_DPAD_RIGHT), x+o, y+o,0); } @@ -201,21 +209,21 @@ int main(int argc , char* argv[]) { int y = oy + SCALE1(PILL_SIZE*2); int o = SCALE1(BUTTON_MARGIN); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("X", screen, PAD_isPressed(BTN_X), x+o, y+o,0); y += SCALE1(PILL_SIZE*2); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("B", screen, PAD_isPressed(BTN_B), x+o, y+o,0); x -= SCALE1(PILL_SIZE); y -= SCALE1(PILL_SIZE); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("Y", screen, PAD_isPressed(BTN_Y), x+o, y+o,0); x += SCALE1(PILL_SIZE*2); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,0}); + { SDL_Rect pill = {x,y,0}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } blitButton("A", screen, PAD_isPressed(BTN_A), x+o, y+o,0); } @@ -225,7 +233,7 @@ int main(int argc , char* argv[]) { int y = oy + SCALE1(PILL_SIZE); int w = SCALE1(42); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,SCALE1(98)}); + { SDL_Rect pill = {x,y,SCALE1(98)}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } x += SCALE1(BUTTON_MARGIN); y += SCALE1(BUTTON_MARGIN); blitButton("VOL. -", screen, PAD_isPressed(BTN_MINUS), x, y, w); @@ -243,7 +251,7 @@ int main(int argc , char* argv[]) { int y = oy + SCALE1(PILL_SIZE * 3); int w = SCALE1(bw); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,SCALE1(pw)}); + { SDL_Rect pill = {x,y,SCALE1(pw)}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } x += SCALE1(BUTTON_MARGIN); y += SCALE1(BUTTON_MARGIN); if (has_menu) { @@ -262,7 +270,7 @@ int main(int argc , char* argv[]) { int y = oy + SCALE1(PILL_SIZE * 5); int w = SCALE1(42); - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){x,y,SCALE1(130)}); + { SDL_Rect pill = {x,y,SCALE1(130)}; GFX_blitPillLight(ASSET_WHITE_PILL, screen, &pill); } x += SCALE1(BUTTON_MARGIN); y += SCALE1(BUTTON_MARGIN); blitButton("SELECT", screen, PAD_isPressed(BTN_SELECT), x, y, w); @@ -270,8 +278,9 @@ int main(int argc , char* argv[]) { blitButton("START", screen, PAD_isPressed(BTN_START), x, y, w); x += w + SCALE1(BUTTON_MARGIN); - SDL_Surface* text = TTF_RenderUTF8_Blended(font.tiny, "QUIT", uintToColour(THEME_COLOR6_255)); - SDL_BlitSurface(text, NULL, screen, &(SDL_Rect){x,y+(SCALE1(BUTTON_SIZE)-text->h)/2}); + SDL_Surface* text = TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, "QUIT", uintToColour(THEME_COLOR6_255)); + SDL_Rect quit_dst = {x,y+(SCALE1(BUTTON_SIZE)-text->h)/2}; + SDL_BlitSurface(text, NULL, screen, &quit_dst); SDL_FreeSurface(text); } @@ -286,8 +295,8 @@ int main(int argc , char* argv[]) { int cy = y + SCALE1(PILL_SIZE) / 2; fillCircle(screen, cx, cy, radius, THEME_COLOR2); - int dx = MAX(-travel, MIN(travel, (pad.laxis.x * travel) / STICK_AXIS_MAX)); - int dy = MAX(-travel, MIN(travel, (pad.laxis.y * travel) / STICK_AXIS_MAX)); + int dx = MAX(-travel, MIN(travel, (pad->laxis.x * travel) / STICK_AXIS_MAX)); + int dy = MAX(-travel, MIN(travel, (pad->laxis.y * travel) / STICK_AXIS_MAX)); blitButton("L3", screen, PAD_isPressed(BTN_L3), x + o + dx, y + o + dy, 0); } @@ -302,8 +311,8 @@ int main(int argc , char* argv[]) { int cy = y + SCALE1(PILL_SIZE) / 2; fillCircle(screen, cx, cy, radius, THEME_COLOR2); - int dx = MAX(-travel, MIN(travel, (pad.raxis.x * travel) / STICK_AXIS_MAX)); - int dy = MAX(-travel, MIN(travel, (pad.raxis.y * travel) / STICK_AXIS_MAX)); + int dx = MAX(-travel, MIN(travel, (pad->raxis.x * travel) / STICK_AXIS_MAX)); + int dy = MAX(-travel, MIN(travel, (pad->raxis.y * travel) / STICK_AXIS_MAX)); blitButton("R3", screen, PAD_isPressed(BTN_R3), x+o+dx, y+o+dy,0); } diff --git a/workspace/all/nextui/makefile b/workspace/all/nextui/makefile index a1f2365ed..90ba8bd67 100644 --- a/workspace/all/nextui/makefile +++ b/workspace/all/nextui/makefile @@ -21,20 +21,55 @@ SDL?=SDL TARGET = nextui INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/scaler.c ../common/utils.c ../common/config.c ../common/api.c ../../$(PLATFORM)/platform/platform.c -CC = $(CROSS_COMPILE)gcc -CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +# C and C++ translation units are listed separately. C files compile as C; +# C++ files compile as C++; everything links through g++ so the two can mix +# as modules are migrated to C++ incrementally. +CSOURCE = +CXXSOURCE = ../../$(PLATFORM)/platform/platform.cpp $(TARGET).cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp + +CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) +CFLAGS += $(OPT) +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +# CXXFLAGS inherits the platform CFLAGS (SDL/GL/PREFIX include paths) before the +# C-only -std is appended, so C++ TUs get the same include/define environment. +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) +CFLAGS += -std=gnu99 LDFLAGS += -lmsettings +OBJ_DIR = build/$(PLATFORM) +OBJS = $(addprefix $(OBJ_DIR)/,$(notdir $(CSOURCE:.c=.o))) \ + $(addprefix $(OBJ_DIR)/,$(notdir $(CXXSOURCE:.cpp=.o))) +DEPS = $(OBJS:.o=.d) + PRODUCT= build/$(PLATFORM)/$(TARGET).elf -all: $(PREFIX_LOCAL)/include/msettings.h - mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) +# Locate sources across their directories so a single pattern rule suffices. +vpath %.c . ../common ../../$(PLATFORM)/platform +vpath %.cpp . ../common ../../$(PLATFORM)/platform + +all: $(PREFIX_LOCAL)/include/msettings.h $(PRODUCT) + +$(PRODUCT): $(OBJS) + $(CXX) $(OBJS) -o $(PRODUCT) $(LDFLAGS) -lstdc++ + +$(OBJ_DIR)/%.o: %.c | $(OBJ_DIR) + $(CC) $(CFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR)/%.o: %.cpp | $(OBJ_DIR) + $(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ + +$(OBJ_DIR): + mkdir -p $(OBJ_DIR) + +-include $(DEPS) + clean: - rm -f $(PRODUCT) + rm -f $(PRODUCT) $(OBJS) $(DEPS) $(PREFIX_LOCAL)/include/msettings.h: cd ../../$(PLATFORM)/libmsettings && make diff --git a/workspace/all/nextui/nextui.c b/workspace/all/nextui/nextui.cpp similarity index 64% rename from workspace/all/nextui/nextui.c rename to workspace/all/nextui/nextui.cpp index 5ed6d612e..a1759f3bc 100644 --- a/workspace/all/nextui/nextui.c +++ b/workspace/all/nextui/nextui.cpp @@ -1,123 +1,67 @@ #include #include -#include #include #include #include #include #include #include // For dirname() -#include "defines.h" -#include "api.h" -#include "utils.h" -#include "config.h" #include #include #include #include -/////////////////////////////////////// +#include +#include +#include +#include +#include +#include "core/str.h" +#include "core/surface.h" +#include "core/thread_safe_queue.h" -typedef struct Array { - int count; - int capacity; - void** items; -} Array; - -static Array* Array_new(void) { - Array* self = malloc(sizeof(Array)); - self->count = 0; - self->capacity = 8; - self->items = malloc(sizeof(void*) * self->capacity); - return self; -} -static void Array_push(Array* self, void* item) { - if (self->count>=self->capacity) { - self->capacity *= 2; - self->items = realloc(self->items, sizeof(void*) * self->capacity); - } - self->items[self->count++] = item; -} -static void Array_unshift(Array* self, void* item) { - if (self->count==0) return Array_push(self, item); - Array_push(self, NULL); // ensures we have enough capacity - for (int i=self->count-2; i>=0; i--) { - self->items[i+1] = self->items[i]; - } - self->items[0] = item; -} -static void* Array_pop(Array* self) { - if (self->count==0) return NULL; - return self->items[--self->count]; -} -static void Array_remove(Array* self, void* item) { - if (self->count==0 || item == NULL) - return; - int i = 0; - while (self->items[i] != item) i++; - for (int j = i; j < self->count-1; j++) - self->items[j] = self->items[j+1]; - self->count--; -} -static void Array_reverse(Array* self) { - int end = self->count-1; - int mid = self->count/2; - for (int i=0; iitems[i]; - self->items[i] = self->items[end-i]; - self->items[end-i] = item; - } -} -static void Array_free(Array* self) { - free(self->items); - free(self); -} -static void Array_yoink(Array* self, Array* other) { - // append entries to self and take ownership - for (int i = 0; i < other->count; i++) - Array_push(self, other->items[i]); - Array_free(other); // `self` now owns the entries +extern "C" { +#include +#include "defines.h" +#include "api.h" +#include "utils.h" +#include "config.h" } -static int StringArray_indexOf(Array* self, char* str) { - for (int i=0; icount; i++) { - if (exactMatch(self->items[i], str)) return i; - } - return -1; -} -static void StringArray_free(Array* self) { - for (int i=0; icount; i++) { - free(self->items[i]); - } - Array_free(self); +/////////////////////////////////////// + +// g++ 8.3 miscompiles a GNU compound-literal argument -- passing +// (char**)(const char*[]){ ... } directly to a function makes the callee read +// garbage (the array's storage is dropped), so button-hint groups came back +// zero-width and drew nothing. Routing the hints through a *named* local array +// (via a statement expression) compiles correctly. Same call, safe codegen. +#define BTN_HINTS(primary, dst, align_right, ...) \ + ({ const char* _bh[] = { __VA_ARGS__ }; GFX_blitButtonGroup((char**)_bh, (primary), (dst), (align_right)); }) + +static void StringArray_free(std::vector& self) { + for (char* s : self) free(s); + self.clear(); } /////////////////////////////////////// typedef struct Hash { - Array* keys; - Array* values; -} Hash; // not really a hash + std::unordered_map map; +} Hash; static Hash* Hash_new(void) { - Hash* self = malloc(sizeof(Hash)); - self->keys = Array_new(); - self->values = Array_new(); - return self; + return new Hash(); } static void Hash_free(Hash* self) { - StringArray_free(self->keys); - StringArray_free(self->values); - free(self); + delete self; } -static void Hash_set(Hash* self, char* key, char* value) { - Array_push(self->keys, strdup(key)); - Array_push(self->values, strdup(value)); +static void Hash_set(Hash* self, const char* key, const char* value) { + self->map[key] = value; } -static char* Hash_get(Hash* self, char* key) { - int i = StringArray_indexOf(self->keys, key); - if (i==-1) return NULL; - return self->values->items[i]; +static const char* Hash_get(Hash* self, const char* key) { + auto it = self->map.find(key); + if (it==self->map.end()) return NULL; + return it->second.c_str(); } /////////////////////////////////////// @@ -139,7 +83,7 @@ typedef struct Entry { static Entry* Entry_new(char* path, int type) { char display_name[256]; getDisplayName(path, display_name); - Entry* self = malloc(sizeof(Entry)); + Entry* self = (Entry*)(malloc(sizeof(Entry))); self->path = strdup(path); self->name = strdup(display_name); self->unique = NULL; @@ -150,6 +94,7 @@ static Entry* Entry_new(char* path, int type) { static Entry* Entry_newNamed(char* path, int type, char* displayName) { Entry *self = Entry_new(path, type); + free(self->name); // Entry_new already strdup'd a display name; replace it self->name = strdup(displayName); return self; } @@ -161,47 +106,22 @@ static void Entry_free(Entry* self) { free(self); } -static int EntryArray_indexOf(Array* self, char* path) { - for (int i=0; icount; i++) { - Entry* entry = self->items[i]; - if (exactMatch(entry->path, path)) return i; +static int EntryArray_indexOf(std::vector& self, const char* path) { + for (size_t i=0; ipath, path)) return (int)i; } return -1; } -static int EntryArray_sortEntry(const void* a, const void* b) { - Entry* item1 = *(Entry**)a; - Entry* item2 = *(Entry**)b; - return strcasecmp(item1->name, item2->name); -} -static void EntryArray_sort(Array* self) { - qsort(self->items, self->count, sizeof(void*), EntryArray_sortEntry); -} - -static void EntryArray_free(Array* self) { - for (int i=0; icount; i++) { - Entry_free(self->items[i]); - } - Array_free(self); +static void EntryArray_sort(std::vector& self) { + // same not-stable, case-insensitive name ordering the old qsort gave us + std::sort(self.begin(), self.end(), [](Entry* a, Entry* b) { + return strcasecmp(a->name, b->name) < 0; + }); } -/////////////////////////////////////// - -#define INT_ARRAY_MAX 27 -typedef struct IntArray { - int count; - int items[INT_ARRAY_MAX]; -} IntArray; -static IntArray* IntArray_new(void) { - IntArray* self = malloc(sizeof(IntArray)); - self->count = 0; - memset(self->items, 0, sizeof(int) * INT_ARRAY_MAX); - return self; -} -static void IntArray_push(IntArray* self, int i) { - self->items[self->count++] = i; -} -static void IntArray_free(IntArray* self) { - free(self); +static void EntryArray_free(std::vector& self) { + for (Entry* e : self) Entry_free(e); + self.clear(); } /////////////////////////////////////// @@ -209,8 +129,8 @@ static void IntArray_free(IntArray* self) { typedef struct Directory { char* path; char* name; - Array* entries; - IntArray* alphas; + std::vector entries; + std::vector alphas; // rendering int selected; int start; @@ -225,18 +145,10 @@ static int getIndexChar(char* str) { } static void getUniqueName(Entry* entry, char* out_name) { - char* filename = strrchr(entry->path, '/')+1; + // out_name is a char[256] at every caller char emu_tag[256]; getEmuName(entry->path, emu_tag); - - char *tmp; - strcpy(out_name, entry->name); - tmp = out_name + strlen(out_name); - strcpy(tmp, " ("); - tmp = out_name + strlen(out_name); - strcpy(tmp, emu_tag); - tmp = out_name + strlen(out_name); - strcpy(tmp, ")"); + snprintf(out_name, 256, "%s (%s)", entry->name, emu_tag); } static void Directory_index(Directory* self) { @@ -245,7 +157,7 @@ static void Directory_index(Directory* self) { Hash* map = NULL; char map_path[256]; - sprintf(map_path, "%s/map.txt", is_collection ? COLLECTIONS_PATH : self->path); + core::format(map_path, "%s/map.txt", is_collection ? COLLECTIONS_PATH : self->path); if (exists(map_path)) { FILE* file = fopen(map_path, "r"); @@ -262,17 +174,17 @@ static void Directory_index(Directory* self) { tmp[0] = '\0'; char* key = line; char* value = tmp + 1; - Hash_set(map, key, strdup(value)); // Ensure strdup to store value properly + Hash_set(map, key, value); // Hash_set copies into a std::string; no strdup needed } } fclose(file); int resort = 0; int filter = 0; - for (int i = 0; i < self->entries->count; i++) { - Entry* entry = self->entries->items[i]; + for (int i = 0; i < (int)self->entries.size(); i++) { + Entry* entry = self->entries[i]; char* filename = strrchr(entry->path, '/') + 1; - char* alias = Hash_get(map, filename); + const char* alias = Hash_get(map, filename); if (alias) { free(entry->name); // Free before overwriting entry->name = strdup(alias); @@ -282,17 +194,16 @@ static void Directory_index(Directory* self) { } if (filter) { - Array* entries = Array_new(); - for (int i = 0; i < self->entries->count; i++) { - Entry* entry = self->entries->items[i]; + std::vector filtered; + for (int i = 0; i < (int)self->entries.size(); i++) { + Entry* entry = self->entries[i]; if (hide(entry->name)) { Entry_free(entry); // Ensure Entry_free handles all memory cleanup } else { - Array_push(entries, entry); + filtered.push_back(entry); } } - Array_free(self->entries); - self->entries = entries; + self->entries = std::move(filtered); } if (resort) EntryArray_sort(self->entries); } @@ -301,11 +212,11 @@ static void Directory_index(Directory* self) { Entry* prior = NULL; int alpha = -1; int index = 0; - for (int i = 0; i < self->entries->count; i++) { - Entry* entry = self->entries->items[i]; + for (int i = 0; i < (int)self->entries.size(); i++) { + Entry* entry = self->entries[i]; if (map) { char* filename = strrchr(entry->path, '/') + 1; - char* alias = Hash_get(map, filename); + const char* alias = Hash_get(map, filename); if (alias) { free(entry->name); // Free before overwriting entry->name = strdup(alias); @@ -337,8 +248,8 @@ static void Directory_index(Directory* self) { if (!skip_index) { int a = getIndexChar(entry->name); if (a != alpha) { - index = self->alphas->count; - IntArray_push(self->alphas, i); + index = (int)self->alphas.size(); + self->alphas.push_back(i); alpha = a; } entry->alpha = index; @@ -350,18 +261,18 @@ static void Directory_index(Directory* self) { if (map) Hash_free(map); // Free the map at the end } -static Array* getRoot(void); -static Array* getRoms(void); -static Array* getRecents(void); -static Array* getCollection(char* path); -static Array* getDiscs(char* path); -static Array* getEntries(char* path); +static std::vector getRoot(void); +static std::vector getRoms(void); +static std::vector getRecents(void); +static std::vector getCollection(char* path); +static std::vector getDiscs(char* path); +static std::vector getEntries(char* path); static Directory* Directory_new(char* path, int selected) { char display_name[256]; getDisplayName(path, display_name); - Directory* self = malloc(sizeof(Directory)); + Directory* self = new Directory(); self->path = strdup(path); self->name = strdup(display_name); if (exactMatch(path, SDCARD_PATH)) { @@ -382,7 +293,6 @@ static Directory* Directory_new(char* path, int selected) { else { self->entries = getEntries(path); } - self->alphas = IntArray_new(); self->selected = selected; Directory_index(self); return self; @@ -391,18 +301,17 @@ static void Directory_free(Directory* self) { free(self->path); free(self->name); EntryArray_free(self->entries); - IntArray_free(self->alphas); - free(self); + delete self; } -static void DirectoryArray_pop(Array* self) { - Directory_free(Array_pop(self)); +static void DirectoryArray_pop(std::vector& self) { + if (self.empty()) return; + Directory_free(self.back()); + self.pop_back(); } -static void DirectoryArray_free(Array* self) { - for (int i=0; icount; i++) { - Directory_free(self->items[i]); - } - Array_free(self); +static void DirectoryArray_free(std::vector& self) { + for (Directory* d : self) Directory_free(d); + self.clear(); } /////////////////////////////////////// @@ -417,10 +326,10 @@ static char* recent_alias = NULL; static int hasEmu(char* emu_name); static Recent* Recent_new(char* path, char* alias) { - Recent* self = malloc(sizeof(Recent)); + Recent* self = (Recent*)(malloc(sizeof(Recent))); char sd_path[256]; // only need to get emu name - sprintf(sd_path, "%s%s", SDCARD_PATH, path); + core::format(sd_path, "%s%s", SDCARD_PATH, path); char emu_name[256]; getEmuName(sd_path, emu_name); @@ -436,27 +345,24 @@ static void Recent_free(Recent* self) { free(self); } -static int RecentArray_indexOf(Array* self, char* str) { - for (int i=0; icount; i++) { - Recent* item = self->items[i]; - if (exactMatch(item->path, str)) return i; +static int RecentArray_indexOf(std::vector& self, const char* str) { + for (size_t i=0; ipath, str)) return (int)i; } return -1; } -static void RecentArray_free(Array* self) { - for (int i=0; icount; i++) { - Recent_free(self->items[i]); - } - Array_free(self); +static void RecentArray_free(std::vector& self) { + for (Recent* r : self) Recent_free(r); + self.clear(); } /////////////////////////////////////// static Directory* top; -static Array* stack; // DirectoryArray -static Array* recents; // RecentArray -static Array *quick; // EntryArray -static Array *quickActions; // EntryArray +static std::vector stack; +static std::vector recents; +static std::vector quick; +static std::vector quickActions; static int quit = 0; static int can_resume = 0; @@ -480,8 +386,8 @@ static int startgame = 0; static void saveRecents(void) { FILE* file = fopen(RECENT_PATH, "w"); if (file) { - for (int i=0; icount; i++) { - Recent* recent = recents->items[i]; + for (int i=0; i<(int)recents.size(); i++) { + Recent* recent = recents[i]; fputs(recent->path, file); if (recent->alias) { fputs("\t", file); @@ -496,16 +402,15 @@ static void addRecent(char* path, char* alias) { path += strlen(SDCARD_PATH); // makes paths platform agnostic int id = RecentArray_indexOf(recents, path); if (id==-1) { // add - while (recents->count>=MAX_RECENTS) { - Recent_free(Array_pop(recents)); + while ((int)recents.size()>=MAX_RECENTS) { + Recent_free(recents.back()); + recents.pop_back(); } - Array_unshift(recents, Recent_new(path, alias)); + recents.insert(recents.begin(), Recent_new(path, alias)); } else if (id>0) { // bump to top for (int i=id; i>0; i--) { - void* tmp = recents->items[i-1]; - recents->items[i-1] = recents->items[i]; - recents->items[i] = tmp; + std::swap(recents[i-1], recents[i]); } } saveRecents(); @@ -515,17 +420,17 @@ static Entry* entryFromPakName(char* pak_name) { char pak_path[256]; // Check in Tools - sprintf(pak_path, "%s/Tools/%s/%s.pak", SDCARD_PATH, PLATFORM, pak_name); + core::format(pak_path, "%s/Tools/%s/%s.pak", SDCARD_PATH, PLATFORM, pak_name); if(exists(pak_path)) return Entry_newNamed(pak_path, ENTRY_PAK, pak_name); // Check in Emus - sprintf(pak_path, "%s/Emus/%s.pak", PAKS_PATH, pak_name); + core::format(pak_path, "%s/Emus/%s.pak", PAKS_PATH, pak_name); if(exists(pak_path)) return Entry_newNamed(pak_path, ENTRY_PAK, pak_name); // Check in platform Emus - sprintf(pak_path, "%s/Emus/%s/%s.pak", SDCARD_PATH, PLATFORM, pak_name); + core::format(pak_path, "%s/Emus/%s/%s.pak", SDCARD_PATH, PLATFORM, pak_name); if(exists(pak_path)) return Entry_newNamed(pak_path, ENTRY_PAK, pak_name); @@ -534,27 +439,27 @@ static Entry* entryFromPakName(char* pak_name) static int hasEmu(char* emu_name) { char pak_path[256]; - sprintf(pak_path, "%s/Emus/%s.pak/launch.sh", PAKS_PATH, emu_name); + core::format(pak_path, "%s/Emus/%s.pak/launch.sh", PAKS_PATH, emu_name); if (exists(pak_path)) return 1; - sprintf(pak_path, "%s/Emus/%s/%s.pak/launch.sh", SDCARD_PATH, PLATFORM, emu_name); + core::format(pak_path, "%s/Emus/%s/%s.pak/launch.sh", SDCARD_PATH, PLATFORM, emu_name); return exists(pak_path); } static int hasCue(char* dir_path, char* cue_path) { // NOTE: dir_path not rom_path char* tmp = strrchr(dir_path, '/') + 1; // folder name - sprintf(cue_path, "%s/%s.cue", dir_path, tmp); + snprintf(cue_path, 256, "%s/%s.cue", dir_path, tmp); // cue_path is a char[256] at both callers return exists(cue_path); } static int hasM3u(char* rom_path, char* m3u_path) { // NOTE: rom_path not dir_path char* tmp; - strcpy(m3u_path, rom_path); + snprintf(m3u_path, 256, "%s", rom_path); // m3u_path is a char[256] at both callers tmp = strrchr(m3u_path, '/') + 1; tmp[0] = '\0'; // path to parent directory char base_path[256]; - strcpy(base_path, m3u_path); + core::format(base_path, "%s", m3u_path); tmp = strrchr(m3u_path, '/'); tmp[0] = '\0'; @@ -562,15 +467,15 @@ static int hasM3u(char* rom_path, char* m3u_path) { // NOTE: rom_path not dir_pa // get parent directory name char dir_name[256]; tmp = strrchr(m3u_path, '/'); - strcpy(dir_name, tmp); + core::format(dir_name, "%s", tmp); // dir_name is also our m3u file name tmp = m3u_path + strlen(m3u_path); - strcpy(tmp, dir_name); + snprintf(tmp, 256 - (tmp - m3u_path), "%s", dir_name); // add extension tmp = m3u_path + strlen(m3u_path); - strcpy(tmp, ".m3u"); + snprintf(tmp, 256 - (tmp - m3u_path), "%s", ".m3u"); return exists(m3u_path); } @@ -579,9 +484,8 @@ static int hasRecents(void) { LOG_info("hasRecents %s\n", RECENT_PATH); int has = 0; RecentArray_free(recents); - recents = Array_new(); - Array* parent_paths = Array_new(); + std::vector parent_paths; if (exists(CHANGE_DISC_PATH)) { char sd_path[256]; getFile(CHANGE_DISC_PATH, sd_path, 256); @@ -589,13 +493,13 @@ static int hasRecents(void) { char* disc_path = sd_path + strlen(SDCARD_PATH); // makes path platform agnostic Recent* recent = Recent_new(disc_path, NULL); if (recent->available) has += 1; - Array_push(recents, recent); + recents.push_back(recent); char parent_path[256]; - strcpy(parent_path, disc_path); + core::format(parent_path, "%s", disc_path); char* tmp = strrchr(parent_path, '/') + 1; tmp[0] = '\0'; - Array_push(parent_paths, strdup(parent_path)); + parent_paths.push_back(strdup(parent_path)); } unlink(CHANGE_DISC_PATH); } @@ -619,20 +523,20 @@ static int hasRecents(void) { } char sd_path[256]; - sprintf(sd_path, "%s%s", SDCARD_PATH, path); + core::format(sd_path, "%s%s", SDCARD_PATH, path); if (exists(sd_path)) { - if (recents->countcount; i++) { - char* path = parent_paths->items[i]; + for (int i=0; i<(int)parent_paths.size(); i++) { + char* path = parent_paths[i]; if (prefixMatch(path, parent_path)) { found = 1; break; @@ -640,14 +544,14 @@ static int hasRecents(void) { } if (found) continue; - Array_push(parent_paths, strdup(parent_path)); + parent_paths.push_back(strdup(parent_path)); } // LOG_info("path:%s alias:%s\n", path, alias); Recent* recent = Recent_new(path, alias); if (recent->available) has += 1; - Array_push(recents, recent); + recents.push_back(recent); } } } @@ -684,7 +588,7 @@ static int hasRoms(char* dir_name) { if (!hasEmu(emu_name)) return has; // check for at least one non-hidden file (we're going to assume it's a rom) - sprintf(rom_path, "%s/%s/", ROMS_PATH, dir_name); + core::format(rom_path, "%s/%s/", ROMS_PATH, dir_name); DIR *dh = opendir(rom_path); if (dh!=NULL) { struct dirent *dp; @@ -701,48 +605,47 @@ static int hasRoms(char* dir_name) { static int hasTools(void) { char tools_path[256]; - snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", SDCARD_PATH, PLATFORM); + core::format(tools_path, "%s/Tools/%s", SDCARD_PATH, PLATFORM); return exists(tools_path); } -static Array* getRoms() +static std::vector getRoms() { - Array* entries = Array_new(); + std::vector entries; DIR* dh = opendir(ROMS_PATH); if (dh) { struct dirent* dp; char full_path[256]; - snprintf(full_path, sizeof(full_path), "%s/", ROMS_PATH); + core::format(full_path, "%s/", ROMS_PATH); char* tmp = full_path + strlen(full_path); - Array* emus = Array_new(); + std::vector emus; while ((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; if (hasRoms(dp->d_name)) { - strcpy(tmp, dp->d_name); - Array_push(emus, Entry_new(full_path, ENTRY_DIR)); + snprintf(tmp, sizeof(full_path) - (tmp - full_path), "%s", dp->d_name); + emus.push_back(Entry_new(full_path, ENTRY_DIR)); } } closedir(dh); // Ensure directory is closed immediately after use EntryArray_sort(emus); Entry* prev_entry = NULL; - for (int i = 0; i < emus->count; i++) { - Entry* entry = emus->items[i]; + for (int i = 0; i < (int)emus.size(); i++) { + Entry* entry = emus[i]; if (prev_entry && exactMatch(prev_entry->name, entry->name)) { Entry_free(entry); continue; } - Array_push(entries, entry); + entries.push_back(entry); prev_entry = entry; } - Array_free(emus); // Only frees container, entries now owns the items } // Handle mapping logic char map_path[256]; - snprintf(map_path, sizeof(map_path), "%s/map.txt", ROMS_PATH); - if (entries->count > 0 && exists(map_path)) { + core::format(map_path, "%s/map.txt", ROMS_PATH); + if ((int)entries.size() > 0 && exists(map_path)) { FILE* file = fopen(map_path, "r"); if (file) { Hash* map = Hash_new(); @@ -758,16 +661,16 @@ static Array* getRoms() *tmp = '\0'; char* key = line; char* value = tmp + 1; - Hash_set(map, key, strdup(value)); // Ensure strdup + Hash_set(map, key, value); // Hash_set copies into a std::string; no strdup needed } } fclose(file); int resort = 0; - for (int i = 0; i < entries->count; i++) { - Entry* entry = entries->items[i]; + for (int i = 0; i < (int)entries.size(); i++) { + Entry* entry = entries[i]; char* filename = strrchr(entry->path, '/') + 1; - char* alias = Hash_get(map, filename); + const char* alias = Hash_get(map, filename); if (alias) { free(entry->name); // Free before overwriting entry->name = strdup(alias); @@ -782,101 +685,101 @@ static Array* getRoms() return entries; } -static Array* getCollections(void) +static std::vector getCollections(void) { DIR* dh = opendir(COLLECTIONS_PATH); if (dh) { struct dirent* dp; char full_path[256]; - snprintf(full_path, sizeof(full_path), "%s/", COLLECTIONS_PATH); + core::format(full_path, "%s/", COLLECTIONS_PATH); char* tmp = full_path + strlen(full_path); - Array* collections = Array_new(); + std::vector collections; while ((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; - strcpy(tmp, dp->d_name); - Array_push(collections, Entry_new(full_path, ENTRY_DIR)); // Collections are fake directories + snprintf(tmp, sizeof(full_path) - (tmp - full_path), "%s", dp->d_name); + collections.push_back(Entry_new(full_path, ENTRY_DIR)); // Collections are fake directories } closedir(dh); // Close immediately after use EntryArray_sort(collections); return collections; } - return NULL; + return {}; } -static Array* getQuickEntries(void) { - Array* entries = Array_new(); +static std::vector getQuickEntries(void) { + std::vector entries; // We assume Menu_init was already called and populated this - if (recents && recents->count) - Array_push(entries, Entry_newNamed(FAUX_RECENT_PATH, ENTRY_DIR, "Recents")); + if (!recents.empty()) + entries.push_back(Entry_newNamed(FAUX_RECENT_PATH, ENTRY_DIR, "Recents")); if (hasCollections()) - Array_push(entries, Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); + entries.push_back(Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); // Not sure we need this, its just a button press away (B) - Array_push(entries, Entry_newNamed(ROMS_PATH, ENTRY_DIR, "Games")); + entries.push_back(Entry_newNamed(ROMS_PATH, ENTRY_DIR, "Games")); // Add tools if applicable if (hasTools() && !simple_mode) { char tools_path[256]; - snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", SDCARD_PATH, PLATFORM); - Array_push(entries, Entry_new(tools_path, ENTRY_DIR)); + core::format(tools_path, "%s/Tools/%s", SDCARD_PATH, PLATFORM); + entries.push_back(Entry_new(tools_path, ENTRY_DIR)); } return entries; } -static Array* getQuickToggles(void) { - Array *entries = Array_new(); +static std::vector getQuickToggles(void) { + std::vector entries; Entry *settings = entryFromPakName("Settings"); if (settings) - Array_push(entries, settings); + entries.push_back(settings); Entry *store = entryFromPakName("Pak Store"); if (store) - Array_push(entries, store); + entries.push_back(store); // quick actions if(WIFI_supported()) - Array_push(entries, Entry_new("Wifi", ENTRY_DIP)); + entries.push_back(Entry_new("Wifi", ENTRY_DIP)); if(BT_supported()) - Array_push(entries, Entry_new("Bluetooth", ENTRY_DIP)); + entries.push_back(Entry_new("Bluetooth", ENTRY_DIP)); if(PLAT_supportsDeepSleep() && !simple_mode) - Array_push(entries, Entry_new("Sleep", ENTRY_DIP)); - Array_push(entries, Entry_new("Reboot", ENTRY_DIP)); - Array_push(entries, Entry_new("Poweroff", ENTRY_DIP)); + entries.push_back(Entry_new("Sleep", ENTRY_DIP)); + entries.push_back(Entry_new("Reboot", ENTRY_DIP)); + entries.push_back(Entry_new("Poweroff", ENTRY_DIP)); return entries; } -static Array* getRoot(void) { - Array* root = Array_new(); +static std::vector getRoot(void) { + std::vector root; if (hasRecents() && CFG_getShowRecents()) - Array_push(root, Entry_new(FAUX_RECENT_PATH, ENTRY_DIR)); + root.push_back(Entry_new(FAUX_RECENT_PATH, ENTRY_DIR)); - Array *entries = getRoms(); + std::vector entries = getRoms(); // Handle collections if (hasCollections() && CFG_getShowCollections()) { - if (entries->count) { - Array_push(root, Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); + if ((int)entries.size()) { + root.push_back(Entry_new(COLLECTIONS_PATH, ENTRY_DIR)); } else { // No visible systems, promote collections to root - Array *collections = getCollections(); - Array_yoink(entries, collections); + std::vector collections = getCollections(); + entries.insert(entries.end(), collections.begin(), collections.end()); collections.clear(); } } // Move entries to root - Array_yoink(root, entries); + root.insert(root.end(), entries.begin(), entries.end()); entries.clear(); // Add tools if applicable if (hasTools() && CFG_getShowTools() && !simple_mode) { char tools_path[256]; - snprintf(tools_path, sizeof(tools_path), "%s/Tools/%s", SDCARD_PATH, PLATFORM); - Array_push(root, Entry_new(tools_path, ENTRY_DIR)); + core::format(tools_path, "%s/Tools/%s", SDCARD_PATH, PLATFORM); + root.push_back(Entry_new(tools_path, ENTRY_DIR)); } return root; @@ -885,10 +788,10 @@ static Array* getRoot(void) { static Entry* entryFromRecent(Recent* recent) { if(!recent || !recent->available) - return NULL; + return {}; char sd_path[256]; - sprintf(sd_path, "%s%s", SDCARD_PATH, recent->path); + core::format(sd_path, "%s%s", SDCARD_PATH, recent->path); int type = suffixMatch(".pak", sd_path) ? ENTRY_PAK : ENTRY_ROM; // ??? Entry* entry = Entry_new(sd_path, type); if (recent->alias) { @@ -898,19 +801,19 @@ static Entry* entryFromRecent(Recent* recent) return entry; } -static Array* getRecents(void) { - Array* entries = Array_new(); - for (int i=0; icount; i++) { - Recent *recent = recents->items[i]; +static std::vector getRecents(void) { + std::vector entries; + for (int i=0; i<(int)recents.size(); i++) { + Recent* recent = recents[i]; Entry *entry = entryFromRecent(recent); if(entry) - Array_push(entries, entry); + entries.push_back(entry); } return entries; } -static Array* getCollection(char* path) { - Array* entries = Array_new(); +static std::vector getCollection(char* path) { + std::vector entries; FILE* file = fopen(path, "r"); if (file) { char line[256]; @@ -920,15 +823,15 @@ static Array* getCollection(char* path) { if (strlen(line)==0) continue; // skip empty lines char sd_path[256]; - sprintf(sd_path, "%s%s", SDCARD_PATH, line); + core::format(sd_path, "%s%s", SDCARD_PATH, line); if (exists(sd_path)) { int type = suffixMatch(".pak", sd_path) ? ENTRY_PAK : ENTRY_ROM; // ??? - Array_push(entries, Entry_new(sd_path, type)); + entries.push_back(Entry_new(sd_path, type)); // char emu_name[256]; // getEmuName(sd_path, emu_name); // if (hasEmu(emu_name)) { - // Array_push(entries, Entry_new(sd_path, ENTRY_ROM)); + // entries.push_back(Entry_new(sd_path, ENTRY_ROM)); // } } } @@ -936,14 +839,14 @@ static Array* getCollection(char* path) { } return entries; } -static Array* getDiscs(char* path){ +static std::vector getDiscs(char* path){ // TODO: does path have SDCARD_PATH prefix? - Array* entries = Array_new(); + std::vector entries; char base_path[256]; - strcpy(base_path, path); + core::format(base_path, "%s", path); char* tmp = strrchr(base_path, '/') + 1; tmp[0] = '\0'; @@ -958,16 +861,16 @@ static Array* getDiscs(char* path){ if (strlen(line)==0) continue; // skip empty lines char disc_path[256]; - sprintf(disc_path, "%s%s", base_path, line); + core::format(disc_path, "%s%s", base_path, line); if (exists(disc_path)) { disc += 1; Entry* entry = Entry_new(disc_path, ENTRY_ROM); free(entry->name); char name[16]; - sprintf(name, "Disc %i", disc); + core::format(name, "Disc %i", disc); entry->name = strdup(name); - Array_push(entries, entry); + entries.push_back(entry); } } fclose(file); @@ -978,7 +881,7 @@ static int getFirstDisc(char* m3u_path, char* disc_path) { // based on getDiscs( int found = 0; char base_path[256]; - strcpy(base_path, m3u_path); + core::format(base_path, "%s", m3u_path); char* tmp = strrchr(base_path, '/') + 1; tmp[0] = '\0'; @@ -990,7 +893,7 @@ static int getFirstDisc(char* m3u_path, char* disc_path) { // based on getDiscs( trimTrailingNewlines(line); if (strlen(line)==0) continue; // skip empty lines - sprintf(disc_path, "%s%s", base_path, line); + snprintf(disc_path, 256, "%s%s", base_path, line); // disc_path is a char[256] at both callers if (exists(disc_path)) found = 1; break; @@ -1000,17 +903,17 @@ static int getFirstDisc(char* m3u_path, char* disc_path) { // based on getDiscs( return found; } -static void addEntries(Array* entries, char* path) { +static void addEntries(std::vector& entries, char* path) { DIR *dh = opendir(path); if (dh!=NULL) { struct dirent *dp; char* tmp; char full_path[256]; - sprintf(full_path, "%s/", path); + core::format(full_path, "%s/", path); tmp = full_path + strlen(full_path); while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; - strcpy(tmp, dp->d_name); + snprintf(tmp, sizeof(full_path) - (tmp - full_path), "%s", dp->d_name); int is_dir = dp->d_type==DT_DIR; int type; if (is_dir) { @@ -1030,7 +933,7 @@ static void addEntries(Array* entries, char* path) { type = ENTRY_ROM; } } - Array_push(entries, Entry_new(full_path, type)); + entries.push_back(Entry_new(full_path, type)); } closedir(dh); } @@ -1039,19 +942,19 @@ static void addEntries(Array* entries, char* path) { static int isConsoleDir(char* path) { char* tmp; char parent_dir[256]; - strcpy(parent_dir, path); + core::format(parent_dir, "%s", path); tmp = strrchr(parent_dir, '/'); tmp[0] = '\0'; return exactMatch(parent_dir, ROMS_PATH); } -static Array* getEntries(char* path){ - Array* entries = Array_new(); +static std::vector getEntries(char* path){ + std::vector entries; if (isConsoleDir(path)) { // top-level console folder, might collate char collated_path[256]; - strcpy(collated_path, path); + core::format(collated_path, "%s", path); char* tmp = strrchr(collated_path, '('); // 1 because we want to keep the opening parenthesis to avoid collating "Game Boy Color" and "Game Boy Advance" into "Game Boy" // but conditional so we can continue to support a bare tag name as a folder name @@ -1061,13 +964,13 @@ static Array* getEntries(char* path){ if (dh!=NULL) { struct dirent *dp; char full_path[256]; - sprintf(full_path, "%s/", ROMS_PATH); + core::format(full_path, "%s/", ROMS_PATH); tmp = full_path + strlen(full_path); // while loop so we can collate paths, see above while((dp = readdir(dh)) != NULL) { if (hide(dp->d_name)) continue; if (dp->d_type!=DT_DIR) continue; - strcpy(tmp, dp->d_name); + snprintf(tmp, sizeof(full_path) - (tmp - full_path), "%s", dp->d_name); if (!prefixMatch(collated_path, full_path)) continue; addEntries(entries, full_path); @@ -1131,7 +1034,7 @@ static void readyResumePath(char* rom_path, int type) { can_resume = 0; has_preview = 0; char path[256]; - strcpy(path, rom_path); + core::format(path, "%s", rom_path); if (!prefixMatch(ROMS_PATH, path)) return; @@ -1139,17 +1042,17 @@ static void readyResumePath(char* rom_path, int type) { if (type==ENTRY_DIR) { if (!hasCue(path, auto_path)) { // no cue? tmp = strrchr(auto_path, '.') + 1; // extension - strcpy(tmp, "m3u"); // replace with m3u + snprintf(tmp, sizeof(auto_path) - (tmp - auto_path), "%s", "m3u"); // replace with m3u if (!exists(auto_path)) return; // no m3u } - strcpy(path, auto_path); // cue or m3u if one exists + core::format(path, "%s", auto_path); // cue or m3u if one exists } if (!suffixMatch(".m3u", path)) { char m3u_path[256]; if (hasM3u(path, m3u_path)) { // change path to m3u path - strcpy(path, m3u_path); + core::format(path, "%s", m3u_path); } } @@ -1158,9 +1061,9 @@ static void readyResumePath(char* rom_path, int type) { char rom_file[256]; tmp = strrchr(path, '/') + 1; - strcpy(rom_file, tmp); + core::format(rom_file, "%s", tmp); - sprintf(slot_path, "%s/.minui/%s/%s.txt", SHARED_USERDATA_PATH, emu_name, rom_file); // /.userdata/.minui//.ext.txt + core::format(slot_path, "%s/.minui/%s/%s.txt", SHARED_USERDATA_PATH, emu_name, rom_file); // /.userdata/.minui//.ext.txt can_resume = exists(slot_path); // slot_path contains a single integer representing the last used slot @@ -1168,7 +1071,7 @@ static void readyResumePath(char* rom_path, int type) { char slot[16]; getFile(slot_path, slot, 16); int s = atoi(slot); - sprintf(preview_path, "%s/.minui/%s/%s.%0d.bmp", SHARED_USERDATA_PATH, emu_name, rom_file, s); // /.userdata/.minui//.ext..bmp + core::format(preview_path, "%s/.minui/%s/%s.%0d.bmp", SHARED_USERDATA_PATH, emu_name, rom_file, s); // /.userdata/.minui//.ext..bmp has_preview = exists(preview_path); } } @@ -1191,7 +1094,7 @@ static int autoResume(void) { // make sure rom still exists char sd_path[256]; - sprintf(sd_path, "%s%s", SDCARD_PATH, path); + core::format(sd_path, "%s%s", SDCARD_PATH, path); if (!exists(sd_path)) return 0; // make sure emu still exists @@ -1206,12 +1109,12 @@ static int autoResume(void) { // putFile(LAST_PATH, FAUX_RECENT_PATH); // saveLast() will crash here because top is NULL char act[256]; - sprintf(act, "gametimectl.elf start '%s'", escapeSingleQuotes(sd_path)); + core::format(act, "gametimectl.elf start '%s'", escapeSingleQuotes(sd_path)); system(act); char cmd[256]; // dont escape sd_path again because it was already escaped for gametimectl and function modifies input str aswell - sprintf(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), sd_path); + core::format(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), sd_path); putInt(RESUME_SLOT_PATH, AUTO_RESUME_SLOT); queueNext(cmd); return 1; @@ -1226,7 +1129,7 @@ static void openPak(char* path) { saveLast(path); char cmd[256]; - sprintf(cmd, "'%s/launch.sh'", escapeSingleQuotes(path)); + core::format(cmd, "'%s/launch.sh'", escapeSingleQuotes(path)); queueNext(cmd); } @@ -1253,8 +1156,8 @@ static int runFnAction(int index) { // unlike openPak() we save where the user *is*, not the pak itself, so exiting // the pak comes back to the same spot in the list - if (top && top->entries->count>0) { - Entry* entry = top->entries->items[top->selected]; + if (top && (int)top->entries.size()>0) { + Entry* entry = top->entries[top->selected]; saveLast(entry->path); } @@ -1268,13 +1171,13 @@ static void openRom(char* path, char* last) { LOG_info("openRom(%s,%s)\n", path, last); char sd_path[256]; - strcpy(sd_path, path); + core::format(sd_path, "%s", path); char m3u_path[256]; int has_m3u = hasM3u(sd_path, m3u_path); char recent_path[256]; - strcpy(recent_path, has_m3u ? m3u_path : sd_path); + core::format(recent_path, "%s", has_m3u ? m3u_path : sd_path); if (has_m3u && suffixMatch(".m3u", sd_path)) { getFirstDisc(m3u_path, sd_path); @@ -1291,21 +1194,21 @@ static void openRom(char* path, char* last) { if (has_m3u) { char rom_file[256]; - strcpy(rom_file, strrchr(m3u_path, '/') + 1); + core::format(rom_file, "%s", strrchr(m3u_path, '/') + 1); // get disc for state char disc_path_path[256]; - sprintf(disc_path_path, "%s/.minui/%s/%s.%s.txt", SHARED_USERDATA_PATH, emu_name, rom_file, slot); // /.userdata/arm-480/.minui//.ext.0.txt + core::format(disc_path_path, "%s/.minui/%s/%s.%s.txt", SHARED_USERDATA_PATH, emu_name, rom_file, slot); // /.userdata/arm-480/.minui//.ext.0.txt if (exists(disc_path_path)) { // switch to disc path char disc_path[256]; getFile(disc_path_path, disc_path, 256); - if (disc_path[0]=='/') strcpy(sd_path, disc_path); // absolute + if (disc_path[0]=='/') core::format(sd_path, "%s", disc_path); // absolute else { // relative - strcpy(sd_path, m3u_path); + core::format(sd_path, "%s", m3u_path); char* tmp = strrchr(sd_path, '/') + 1; - strcpy(tmp, disc_path); + snprintf(tmp, sizeof(sd_path) - (tmp - sd_path), "%s", disc_path); } } } @@ -1320,11 +1223,11 @@ static void openRom(char* path, char* last) { addRecent(recent_path, recent_alias); // yiiikes saveLast(last==NULL ? sd_path : last); char act[256]; - sprintf(act, "gametimectl.elf start '%s'", escapeSingleQuotes(sd_path)); + core::format(act, "gametimectl.elf start '%s'", escapeSingleQuotes(sd_path)); system(act); char cmd[256]; // dont escape sd_path again because it was already escaped for gametimectl and function modifies input str aswell - sprintf(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), sd_path); + core::format(cmd, "'%s' '%s'", escapeSingleQuotes(emu_path), sd_path); queueNext(cmd); } @@ -1370,8 +1273,8 @@ static bool isDirectSubdirectory(const Directory* parent, const char* child_path return (levels == 1); // exactly one meaningful level deeper } -Array* pathToStack(const char* path) { - Array* array = Array_new(); +std::vector pathToStack(const char* path) { + std::vector array; if (!path || strlen(path) == 0) return array; @@ -1380,13 +1283,13 @@ Array* pathToStack(const char* path) { // Always include root directory Directory* root_dir = Directory_new(SDCARD_PATH, 0); root_dir->start = 0; - root_dir->end = (root_dir->entries->count < MAIN_ROW_COUNT) ? root_dir->entries->count : MAIN_ROW_COUNT; - Array_push(array, root_dir); + root_dir->end = ((int)root_dir->entries.size() < MAIN_ROW_COUNT) ? (int)root_dir->entries.size() : MAIN_ROW_COUNT; + array.push_back(root_dir); if (exactMatch(path, SDCARD_PATH)) return array; char temp_path[PATH_MAX]; - strcpy(temp_path, SDCARD_PATH); + core::format(temp_path, "%s", SDCARD_PATH); size_t current_len = strlen(SDCARD_PATH); const char* cursor = path + current_len; @@ -1410,28 +1313,28 @@ Array* pathToStack(const char* path) { // Append segment if (current_len + segment_len >= PATH_MAX) break; - strcat(temp_path, segment); + core::append(temp_path, segment); current_len += segment_len; if (strcmp(segment, PLATFORM) == 0) { // Merge with previous directory - if (array->count > 0) { + if (array.size() > 0) { // Remove the previous directory - Directory* last = (Directory*)array->items[array->count - 1]; - Array_pop(array); + Directory* last = array.back(); + array.pop_back(); Directory_free(last); // assuming you have a Directory_free // Replace with updated one using combined path Directory* merged = Directory_new(temp_path, 0); merged->start = 0; - merged->end = (merged->entries->count < MAIN_ROW_COUNT) ? merged->entries->count : MAIN_ROW_COUNT; - Array_push(array, merged); + merged->end = ((int)merged->entries.size() < MAIN_ROW_COUNT) ? (int)merged->entries.size() : MAIN_ROW_COUNT; + array.push_back(merged); } } else { Directory* dir = Directory_new(temp_path, 0); dir->start = 0; - dir->end = (dir->entries->count < MAIN_ROW_COUNT) ? dir->entries->count : MAIN_ROW_COUNT; - Array_push(array, dir); + dir->end = ((int)dir->entries.size() < MAIN_ROW_COUNT) ? (int)dir->entries.size() : MAIN_ROW_COUNT; + array.push_back(dir); } if (!next) break; @@ -1449,9 +1352,9 @@ static void openDirectory(char* path, int auto_launch) { } char m3u_path[256]; - strcpy(m3u_path, auto_path); + core::format(m3u_path, "%s", auto_path); char* tmp = strrchr(m3u_path, '.') + 1; // extension - strcpy(tmp, "m3u"); // replace with m3u + snprintf(tmp, sizeof(m3u_path) - (tmp - m3u_path), "%s", "m3u"); // replace with m3u if (exists(m3u_path) && auto_launch) { auto_path[0] = '\0'; if (getFirstDisc(m3u_path, auto_path)) { @@ -1471,8 +1374,8 @@ static void openDirectory(char* path, int auto_launch) { int selected = 0; int start = 0; int end = 0; - if (top && top->entries->count>0) { - if (restore_depth==stack->count && top->selected==restore_relative) { + if (top && (int)top->entries.size()>0) { + if (restore_depth==(int)stack.size() && top->selected==restore_relative) { selected = restore_selected; start = restore_start; end = restore_end; @@ -1481,20 +1384,20 @@ static void openDirectory(char* path, int auto_launch) { top = Directory_new(path, selected); top->start = start; - top->end = end ? end : ((top->entries->countentries->count : MAIN_ROW_COUNT); + top->end = end ? end : (((int)top->entries.size()entries.size() : MAIN_ROW_COUNT); - Array_push(stack, top); + stack.push_back(top); } else { // keep a copy of path, which might be a reference into stack which is about to be freed char temp_path[256]; - strcpy(temp_path, path); + core::format(temp_path, "%s", path); // construct a fresh stack by walking upwards until SDCARD_ROOT DirectoryArray_free(stack); stack = pathToStack(temp_path); - top = stack->items[stack->count - 1]; + top = stack[(int)stack.size() - 1]; } } @@ -1503,8 +1406,8 @@ static void closeDirectory(void) { restore_start = top->start; restore_end = top->end; DirectoryArray_pop(stack); - restore_depth = stack->count; - top = stack->items[stack->count-1]; + restore_depth = (int)stack.size(); + top = stack[(int)stack.size()-1]; restore_relative = top->selected; } @@ -1544,10 +1447,10 @@ static void Entry_open(Entry* self) { char filename[256]; tmp = strrchr(self->path, '/'); - if (tmp) strcpy(filename, tmp+1); + if (tmp) core::format(filename, "%s", tmp+1); char last_path[256]; - sprintf(last_path, "%s/%s", top->path, filename); + core::format(last_path, "%s/%s", top->path, filename); last = last_path; } openRom(self->path, last); @@ -1583,34 +1486,35 @@ static void loadLast(void) { // call after loading root directory getFile(LAST_PATH, last_path, 256); char full_path[256]; - strcpy(full_path, last_path); + core::format(full_path, "%s", last_path); char* tmp; char filename[256]; tmp = strrchr(last_path, '/'); - if (tmp) strcpy(filename, tmp); + if (tmp) core::format(filename, "%s", tmp); - Array* last = Array_new(); + std::vector last; while (!exactMatch(last_path, SDCARD_PATH)) { - Array_push(last, strdup(last_path)); + last.push_back(strdup(last_path)); char* slash = strrchr(last_path, '/'); last_path[(slash-last_path)] = '\0'; } - while (last->count>0) { - char* path = Array_pop(last); + while (!last.empty()) { + char* path = last.back(); + last.pop_back(); if (!exactMatch(path, ROMS_PATH)) { // romsDir is effectively root as far as restoring state after a game char collated_path[256]; collated_path[0] = '\0'; if (suffixMatch(")", path) && isConsoleDir(path)) { - strcpy(collated_path, path); + core::format(collated_path, "%s", path); tmp = strrchr(collated_path, '('); if (tmp) tmp[1] = '\0'; // 1 because we want to keep the opening parenthesis to avoid collating "Game Boy Color" and "Game Boy Advance" into "Game Boy" } - for (int i=0; ientries->count; i++) { - Entry* entry = top->entries->items[i]; + for (int i=0; i<(int)top->entries.size(); i++) { + Entry* entry = top->entries[i]; // NOTE: strlen() is required for collated_path, '\0' wasn't reading as NULL for some reason if (exactMatch(entry->path, path) || (strlen(collated_path) && prefixMatch(collated_path, entry->path)) || (prefixMatch(COLLECTIONS_PATH, full_path) && suffixMatch(filename, entry->path))) { @@ -1618,12 +1522,12 @@ static void loadLast(void) { // call after loading root directory if (i>=top->end) { top->start = i; top->end = top->start + MAIN_ROW_COUNT; - if (top->end>top->entries->count) { - top->end = top->entries->count; + if (top->end>(int)top->entries.size()) { + top->end = (int)top->entries.size(); top->start = top->end - MAIN_ROW_COUNT; } } - if (last->count==0 && !exactMatch(entry->path, FAUX_RECENT_PATH) && !(!exactMatch(entry->path, COLLECTIONS_PATH) && prefixMatch(COLLECTIONS_PATH, entry->path))) break; // don't show contents of auto-launch dirs + if (last.empty() && !exactMatch(entry->path, FAUX_RECENT_PATH) && !(!exactMatch(entry->path, COLLECTIONS_PATH) && prefixMatch(COLLECTIONS_PATH, entry->path))) break; // don't show contents of auto-launch dirs if (entry->type==ENTRY_DIR) { openDirectory(entry->path, 0); @@ -1637,8 +1541,8 @@ static void loadLast(void) { // call after loading root directory StringArray_free(last); - if (top->selected >= 0 && top->selected < top->entries->count) { - Entry *selected_entry = top->entries->items[top->selected]; + if (top->selected >= 0 && top->selected < (int)top->entries.size()) { + Entry* selected_entry = top->entries[top->selected]; readyResume(selected_entry); } } @@ -1655,9 +1559,6 @@ static void QuickMenu_quit(void) { } static void Menu_init(void) { - stack = Array_new(); // array of open Directories - recents = Array_new(); - openDirectory(SDCARD_PATH, 0); loadLast(); // restore state when available @@ -1686,29 +1587,7 @@ enum { typedef void (*BackgroundLoadedCallback)(SDL_Surface* surface); -typedef struct { - char imagePath[MAX_PATH]; - BackgroundLoadedCallback callback; - void* userData; -} LoadBackgroundTask; - -typedef struct finishedTask { - int startX; - int targetX; - int startY; - int targetY; - int targetTextY; - int move_y; - int move_w; - int move_h; - int frames; - int done; - void* userData; - char *entry_name; - SDL_Rect dst; -} finishedTask; - -typedef void (*AnimTaskCallback)(finishedTask *task); +// The pill-animation producer fills one of these and hands it to animPill(). typedef struct AnimTask { int startX; int targetX; @@ -1718,46 +1597,13 @@ typedef struct AnimTask { int move_w; int move_h; int frames; - AnimTaskCallback callback; - void* userData; - char *entry_name; - SDL_Rect dst; + char *entry_name; } AnimTask; -typedef struct TaskNode { - LoadBackgroundTask* task; - struct TaskNode* next; -} TaskNode; -typedef struct AnimTaskNode { - AnimTask* task; - struct AnimTaskNode* next; -} AnimTaskNode; - -static TaskNode* taskBGQueueHead = NULL; -static TaskNode* taskBGQueueTail = NULL; -static TaskNode* taskThumbQueueHead = NULL; -static TaskNode* taskThumbQueueTail = NULL; -static AnimTaskNode* animTaskQueueHead = NULL; -static AnimTaskNode* animTtaskQueueTail = NULL; -static SDL_mutex* bgqueueMutex = NULL; -static SDL_mutex* thumbqueueMutex = NULL; -static SDL_mutex* animqueueMutex = NULL; -static SDL_cond* bgqueueCond = NULL; -static SDL_cond* thumbqueueCond = NULL; -static SDL_cond* animqueueCond = NULL; - static SDL_mutex* bgMutex = NULL; static SDL_mutex* thumbMutex = NULL; static SDL_mutex* animMutex = NULL; -static SDL_mutex* frameMutex = NULL; static SDL_mutex* fontMutex = NULL; -static SDL_cond* flipCond = NULL; - -static SDL_Thread* bgLoadThread = NULL; -static SDL_Thread* thumbLoadThread = NULL; -static SDL_Thread* animWorkerThread = NULL; - -static SDL_atomic_t workerThreadsShutdown; // Flag to signal threads to exit (atomic for thread safety) static SDL_Surface* folderbgbmp = NULL; static SDL_Surface* thumbbmp = NULL; @@ -1781,12 +1627,11 @@ static void updatePillTextSurface(const char* entry_name, int move_w, SDL_Color if (crop_w <= 0) return; SDL_LockMutex(fontMutex); - SDL_Surface* tmp = TTF_RenderUTF8_Blended(font.large, entry_name, text_color); + core::SurfacePtr tmp{TTF_RenderUTF8_Blended(GFX_getFonts()->large, entry_name, text_color)}; SDL_UnlockMutex(fontMutex); if (!tmp) return; - SDL_Surface* converted = SDL_ConvertSurfaceFormat(tmp, screen->format->format, 0); - SDL_FreeSurface(tmp); + core::SurfacePtr converted{SDL_ConvertSurfaceFormat(tmp.get(), screen->format->format, 0)}; if (!converted) return; SDL_Rect crop_rect = { 0, 0, crop_w, converted->h }; @@ -1794,9 +1639,8 @@ static void updatePillTextSurface(const char* entry_name, int move_w, SDL_Color 0, crop_rect.w, crop_rect.h, screen->format->BitsPerPixel, screen->format->format ); if (cropped) { - SDL_BlitSurface(converted, &crop_rect, cropped, NULL); + SDL_BlitSurface(converted.get(), &crop_rect, cropped, NULL); } - SDL_FreeSurface(converted); if (!cropped) return; SDL_LockMutex(animMutex); @@ -1807,172 +1651,37 @@ static void updatePillTextSurface(const char* entry_name, int move_w, SDL_Color int folderbgchanged=0; int thumbchanged=0; -// queue a new image load task :D -#define MAX_QUEUE_SIZE 1 - -int currentBGQueueSize = 0; -int currentThumbQueueSize = 0; -int currentAnimQueueSize = 0; - -void enqueueBGTask(LoadBackgroundTask* task) { - SDL_LockMutex(bgqueueMutex); - TaskNode* node = (TaskNode*)malloc(sizeof(TaskNode)); - node->task = task; - node->next = NULL; - - // If queue is full, drop the oldest task (head) - if (currentBGQueueSize >= MAX_QUEUE_SIZE) { - TaskNode* oldNode = taskBGQueueHead; - if (oldNode) { - taskBGQueueHead = oldNode->next; - if (!taskBGQueueHead) { - taskBGQueueTail = NULL; - } - if (oldNode->task) { - free(oldNode->task); // Only if task was malloc'd - } - free(oldNode); - currentBGQueueSize--; - } - } - - // Enqueue the new task - if (taskBGQueueTail) { - taskBGQueueTail->next = node; - taskBGQueueTail = node; - } else { - taskBGQueueHead = taskBGQueueTail = node; - } - - currentBGQueueSize++; - SDL_CondSignal(bgqueueCond); - SDL_UnlockMutex(bgqueueMutex); -} -void enqueueThumbTask(LoadBackgroundTask* task) { - SDL_LockMutex(thumbqueueMutex); - TaskNode* node = (TaskNode*)malloc(sizeof(TaskNode)); - node->task = task; - node->next = NULL; - - // If queue is full, drop the oldest task (head) - if (currentThumbQueueSize >= MAX_QUEUE_SIZE) { - TaskNode* oldNode = taskThumbQueueHead; - if (oldNode) { - taskThumbQueueHead = oldNode->next; - if (!taskThumbQueueHead) { - taskThumbQueueTail = NULL; - } - if (oldNode->task) { - free(oldNode->task); // Only if task was malloc'd - } - free(oldNode); - currentThumbQueueSize--; - } - } - - // Enqueue the new task - if (taskThumbQueueTail) { - taskThumbQueueTail->next = node; - taskThumbQueueTail = node; - } else { - taskThumbQueueHead = taskThumbQueueTail = node; - } - - currentThumbQueueSize++; - SDL_CondSignal(thumbqueueCond); - SDL_UnlockMutex(thumbqueueMutex); -} - -// Worker thread -int BGLoadWorker(void* unused) { - while (!SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_LockMutex(bgqueueMutex); - while (!taskBGQueueHead && !SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_CondWait(bgqueueCond, bgqueueMutex); - } - if (SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_UnlockMutex(bgqueueMutex); - break; - } - TaskNode* node = taskBGQueueHead; - taskBGQueueHead = node->next; - if (!taskBGQueueHead) taskBGQueueTail = NULL; - SDL_UnlockMutex(bgqueueMutex); - // give processor lil space in between queue items for other shit - //SDL_Delay(100); - LoadBackgroundTask* task = node->task; - free(node); - - SDL_Surface* result = NULL; - if (access(task->imagePath, F_OK) == 0) { - SDL_Surface* image = IMG_Load(task->imagePath); - if (image) { - SDL_Surface* imageRGBA = SDL_ConvertSurfaceFormat(image, screen->format->format, 0); - SDL_FreeSurface(image); - result = imageRGBA; - } - } - - if (task->callback) { - task->callback(result); - } - free(task); - SDL_LockMutex(bgqueueMutex); - if (!taskBGQueueHead) taskBGQueueTail = NULL; - currentBGQueueSize--; // <-- add this - SDL_UnlockMutex(bgqueueMutex); - } - return 0; -} -int ThumbLoadWorker(void* unused) { - while (!SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_LockMutex(thumbqueueMutex); - while (!taskThumbQueueHead && !SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_CondWait(thumbqueueCond, thumbqueueMutex); - } - if (SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_UnlockMutex(thumbqueueMutex); - break; - } - TaskNode* node = taskThumbQueueHead; - taskThumbQueueHead = node->next; - if (!taskThumbQueueHead) taskThumbQueueTail = NULL; - SDL_UnlockMutex(thumbqueueMutex); - // give processor lil space in between queue items for other shit - //SDL_Delay(100); - LoadBackgroundTask* task = node->task; - free(node); - - SDL_Surface* result = NULL; - if (access(task->imagePath, F_OK) == 0) { - SDL_Surface* image = IMG_Load(task->imagePath); - if (image) { - SDL_Surface* imageRGBA = SDL_ConvertSurfaceFormat(image, screen->format->format, 0); - SDL_FreeSurface(image); - result = imageRGBA; - } - } - - if (task->callback) { - task->callback(result); +// --- Folder background loader ------------------------------------------------ +// One std::thread consuming a thread-safe queue. Replaces the hand-rolled +// SDL_mutex/SDL_cond linked-list queue + BGLoadWorker (whose data race the POC +// had to disable). A request is just the image path plus the callback that +// hands the decoded surface back (onBackgroundLoaded, which stores it under +// bgMutex for the main thread to draw). IMG_Load / SDL_ConvertSurfaceFormat only +// touch CPU-side pixel buffers, so they're safe off the main thread. +struct ImageLoadRequest { + std::string path; + BackgroundLoadedCallback callback; +}; +// Capacity 1: only the most recently requested background matters. If the user +// has already navigated on, the stale request is dropped before it's loaded. +static core::ThreadSafeQueue bgLoadQueue(1); +static std::thread bgLoadWorkerThread; + +static void bgLoadWorkerLoop(void) { + ImageLoadRequest req; + while (bgLoadQueue.pop(req)) { + SDL_Surface* surface = NULL; + core::SurfacePtr raw{IMG_Load(req.path.c_str())}; + if (raw) { + surface = SDL_ConvertSurfaceFormat(raw.get(), screen->format->format, 0); } - free(task); - SDL_LockMutex(thumbqueueMutex); - if (!taskThumbQueueHead) taskThumbQueueTail = NULL; - currentThumbQueueSize--; // <-- add this - SDL_UnlockMutex(thumbqueueMutex); - } - return 0; + req.callback(surface); // onBackgroundLoaded, hands off under bgMutex + } } void startLoadFolderBackground(const char* imagePath, BackgroundLoadedCallback callback, void* userData) { - LoadBackgroundTask* task = malloc(sizeof(LoadBackgroundTask)); - if (!task) return; - - snprintf(task->imagePath, sizeof(task->imagePath), "%s", imagePath); - task->callback = callback; - task->userData = userData; - enqueueBGTask(task); + (void)userData; + bgLoadQueue.push({ imagePath, callback }); } void onBackgroundLoaded(SDL_Surface* surface) { @@ -1989,14 +1698,32 @@ void onBackgroundLoaded(SDL_Surface* surface) { SDL_UnlockMutex(bgMutex); } -void startLoadThumb(const char* thumbpath, BackgroundLoadedCallback callback, void* userData) { - LoadBackgroundTask* task = malloc(sizeof(LoadBackgroundTask)); - if (!task) return; +// --- Thumbnail (box-art) loader ---------------------------------------------- +// Same shape as the folder-background loader: its own std::thread and its own +// capacity-1 queue (separate from backgrounds so a thumbnail request and a +// background request never evict each other). onThumbLoaded does the CPU-side +// scaling maths and rounded-corner pass on the decoded surface under thumbMutex, +// so it's fine to run from the worker. +static core::ThreadSafeQueue thumbLoadQueue(1); +static std::thread thumbLoadWorkerThread; + +static void thumbLoadWorkerLoop(void) { + ImageLoadRequest req; + while (thumbLoadQueue.pop(req)) { + SDL_Surface* surface = NULL; + if (access(req.path.c_str(), F_OK) == 0) { + core::SurfacePtr raw{IMG_Load(req.path.c_str())}; + if (raw) { + surface = SDL_ConvertSurfaceFormat(raw.get(), screen->format->format, 0); + } + } + req.callback(surface); // onThumbLoaded, hands off under thumbMutex + } +} - snprintf(task->imagePath, sizeof(task->imagePath), "%s", thumbpath); - task->callback = callback; - task->userData = userData; - enqueueThumbTask(task); +void startLoadThumb(const char* thumbpath, BackgroundLoadedCallback callback, void* userData) { + (void)userData; + thumbLoadQueue.push({ thumbpath, callback }); } void onThumbLoaded(SDL_Surface* surface) { SDL_LockMutex(thumbMutex); @@ -2023,9 +1750,10 @@ void onThumbLoaded(SDL_Surface* surface) { new_w = (int)(new_h / aspect_ratio); } + SDL_Rect _corner_rect = {0, 0, thumbbmp->w, thumbbmp->h}; GFX_ApplyRoundedCorners_8888( thumbbmp, - &(SDL_Rect){0, 0, thumbbmp->w, thumbbmp->h}, + &_corner_rect, SCALE1((float)CFG_getThumbnailRadius() * ((float)img_w / (float)new_w)) ); setNeedDraw(1); @@ -2035,213 +1763,120 @@ void onThumbLoaded(SDL_Surface* surface) { SDL_Rect pillRect; int pilltargetY =0; int pilltargetTextY =0; -void animcallback(finishedTask *task) { - SDL_LockMutex(animMutex); - pillRect = task->dst; - if(pillRect.w > 0 && pillRect.h > 0) { - pilltargetY = +screen->w; // move offscreen - if(task->done) { - pilltargetY = task->targetY; - pilltargetTextY = task->targetTextY; - } - setNeedDraw(1); - } - SDL_UnlockMutex(animMutex); - setAnimationDraw(1); -} -bool frameReady = true; bool pillanimdone = false; - -int animWorker(void* unused) { - while (!SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_LockMutex(animqueueMutex); - while (!animTaskQueueHead && !SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_CondWait(animqueueCond, animqueueMutex); - } - if (SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_UnlockMutex(animqueueMutex); - break; - } - AnimTaskNode* node = animTaskQueueHead; - animTaskQueueHead = node->next; - if (!animTaskQueueHead) animTtaskQueueTail = NULL; - SDL_UnlockMutex(animqueueMutex); - - AnimTask* task = node->task; - finishedTask* finaltask = (finishedTask*)malloc(sizeof(finishedTask)); - int total_frames = task->frames; - for (int frame = 0; frame <= total_frames; frame++) { - // Check for shutdown at start of each frame - if (SDL_AtomicGet(&workerThreadsShutdown)) break; - - float t = (float)frame / total_frames; - if (t > 1.0f) t = 1.0f; - - int current_x = task->startX + (int)((task->targetX - task->startX) * t); - int current_y = task->startY + (int)(( task->targetY - task->startY) * t); - - SDL_Rect moveDst = { current_x, current_y, task->move_w, task->move_h }; - finaltask->dst = moveDst; - finaltask->entry_name = task->entry_name; - finaltask->move_w = task->move_w; - finaltask->move_h = task->move_h; - finaltask->targetY = task->targetY; - finaltask->targetTextY = task->targetTextY; - finaltask->move_y = SCALE1(PADDING + task->targetY) + (task->targetTextY - task->targetY); - finaltask->done = 0; - if(frame >= total_frames) finaltask->done=1; - task->callback(finaltask); - SDL_LockMutex(frameMutex); - while (!frameReady && !SDL_AtomicGet(&workerThreadsShutdown)) { - SDL_CondWait(flipCond, frameMutex); - } - frameReady = false; - SDL_UnlockMutex(frameMutex); - +// Main-thread pill animation. The pill (the selection highlight) slides from +// its current position to the target over `frames` frames. This used to run in +// a worker thread that tweened across frames and handed each position back to +// the main thread via a callback + condition variable -- an unnecessary and +// race-prone design for what is just a UI lerp. It is now a plain linear tween +// advanced once per render frame on the main thread: no worker, no queue, no +// cross-thread handoff, so the whole class of races that crashed it is gone. +static struct PillAnim { + bool active; + int startX, startY, targetX, targetY; + int move_w, move_h, targetTextY; + int frame, frames; +} pillAnim = {0}; + +// Advance the pill tween by one render frame. Call once per main-loop iteration. +// Reproduces exactly what the old animWorker + animcallback did per frame. +static void advancePillAnim(void) { + if (!pillAnim.active) return; + float t = pillAnim.frames > 0 ? (float)pillAnim.frame / pillAnim.frames : 1.0f; + if (t > 1.0f) t = 1.0f; + pillRect.x = pillAnim.startX + (int)((pillAnim.targetX - pillAnim.startX) * t); + pillRect.y = pillAnim.startY + (int)((pillAnim.targetY - pillAnim.startY) * t); + pillRect.w = pillAnim.move_w; + pillRect.h = pillAnim.move_h; + if (pillRect.w > 0 && pillRect.h > 0) { + pilltargetY = screen->w; // hide the selected text off-screen mid-slide + if (pillAnim.frame >= pillAnim.frames) { + pilltargetY = pillAnim.targetY; + pilltargetTextY = pillAnim.targetTextY; } - SDL_LockMutex(animqueueMutex); - if (!animTaskQueueHead) animTtaskQueueTail = NULL; - currentAnimQueueSize--; // <-- add this - SDL_UnlockMutex(animqueueMutex); - - SDL_LockMutex(animMutex); + } + if (pillAnim.frame >= pillAnim.frames) { pillanimdone = true; - free(finaltask); - SDL_UnlockMutex(animMutex); + pillAnim.active = false; } -} - -void enqueueanmimtask(AnimTask* task) { - AnimTaskNode* node = (AnimTaskNode*)malloc(sizeof(AnimTaskNode)); - node->task = task; - node->next = NULL; - - SDL_LockMutex(animqueueMutex); - pillanimdone = false; - // If queue is full, drop the oldest task (head) - if (currentAnimQueueSize >= 1) { - AnimTaskNode* oldNode = animTaskQueueHead; - if (oldNode) { - animTaskQueueHead = oldNode->next; - if (!animTaskQueueHead) { - animTtaskQueueTail = NULL; - } - if (oldNode->task) { - free(oldNode->task); // Only if task was malloc'd - } - free(oldNode); - currentAnimQueueSize--; - } - } - - // Enqueue the new task - if (animTtaskQueueTail) { - animTtaskQueueTail->next = node; - animTtaskQueueTail = node; - } else { - animTaskQueueHead = animTtaskQueueTail = node; - } - - currentAnimQueueSize++; - SDL_CondSignal(animqueueCond); - SDL_UnlockMutex(animqueueMutex); + pillAnim.frame++; + setNeedDraw(1); + setAnimationDraw(1); } void animPill(AnimTask *task) { - task->callback = animcallback; - enqueueanmimtask(task); + if (task->frames <= 1) { + // No animation requested (e.g. a directory change): jump straight to the + // target, no tween. Rendering the start position for a frame first would + // flash the pill at the previous directory's row before snapping over. + pillRect.x = task->targetX; + pillRect.y = task->targetY; + pillRect.w = task->move_w; + pillRect.h = task->move_h; + pilltargetY = task->targetY; + pilltargetTextY = task->targetTextY; + pillanimdone = true; + pillAnim.active = false; + } else { + // Kick off a main-thread tween from the current position to the target. + pillAnim.startX = task->startX; + pillAnim.startY = task->startY; + pillAnim.targetX = task->targetX; + pillAnim.targetY = task->targetY; + pillAnim.move_w = task->move_w; + pillAnim.move_h = task->move_h; + pillAnim.targetTextY = task->targetTextY; + pillAnim.frames = task->frames; + pillAnim.frame = 0; + pillAnim.active = true; + pillanimdone = false; + } + setNeedDraw(1); + setAnimationDraw(1); + // The caller malloc'd the task and does not use it after this call. + if (task->entry_name) free(task->entry_name); + free(task); } void initImageLoaderPool() { - // Initialize shutdown flag to 0 - SDL_AtomicSet(&workerThreadsShutdown, 0); SDL_AtomicSet(&animationDrawAtomic, 1); SDL_AtomicSet(&needDrawAtomic, 0); - thumbqueueMutex = SDL_CreateMutex(); - bgqueueMutex = SDL_CreateMutex(); - bgqueueCond = SDL_CreateCond(); - thumbqueueCond = SDL_CreateCond(); bgMutex = SDL_CreateMutex(); thumbMutex = SDL_CreateMutex(); animMutex = SDL_CreateMutex(); - animqueueMutex = SDL_CreateMutex(); - animqueueCond = SDL_CreateCond(); - frameMutex = SDL_CreateMutex(); fontMutex = SDL_CreateMutex(); - flipCond = SDL_CreateCond(); - bgLoadThread = SDL_CreateThread(BGLoadWorker, "BGLoadWorker", NULL); - thumbLoadThread = SDL_CreateThread(ThumbLoadWorker, "ThumbLoadWorker", NULL); - animWorkerThread = SDL_CreateThread(animWorker, "animWorker", NULL); + // Folder backgrounds and thumbnails each run on their own std::thread + + // ThreadSafeQueue loader. + bgLoadWorkerThread = std::thread(bgLoadWorkerLoop); + thumbLoadWorkerThread = std::thread(thumbLoadWorkerLoop); } void cleanupImageLoaderPool() { - // Signal all worker threads to exit (atomic set for thread safety) - SDL_AtomicSet(&workerThreadsShutdown, 1); - - // Wake up all waiting threads - if (bgqueueCond) SDL_CondSignal(bgqueueCond); - if (thumbqueueCond) SDL_CondSignal(thumbqueueCond); - if (animqueueCond) SDL_CondSignal(animqueueCond); - if (flipCond) SDL_CondSignal(flipCond); // Wake up animWorker if stuck waiting for frame flip - - // Wait for all worker threads to finish - if (bgLoadThread) { - SDL_WaitThread(bgLoadThread, NULL); - bgLoadThread = NULL; - } - if (thumbLoadThread) { - SDL_WaitThread(thumbLoadThread, NULL); - thumbLoadThread = NULL; - } - if (animWorkerThread) { - SDL_WaitThread(animWorkerThread, NULL); - animWorkerThread = NULL; - } - - // Small delay to ensure llvmpipe/OpenGL threads have completed any pending operations - SDL_Delay(10); - - // Acquire and release each mutex before destroying to ensure no thread is in a critical section - // This creates a memory barrier and ensures proper synchronization - if (bgqueueMutex) { SDL_LockMutex(bgqueueMutex); SDL_UnlockMutex(bgqueueMutex); } - if (thumbqueueMutex) { SDL_LockMutex(thumbqueueMutex); SDL_UnlockMutex(thumbqueueMutex); } - if (animqueueMutex) { SDL_LockMutex(animqueueMutex); SDL_UnlockMutex(animqueueMutex); } + // Stop the image loaders: each queue's shutdown() unblocks its worker's + // pop() so it returns, then we join. + bgLoadQueue.shutdown(); + if (bgLoadWorkerThread.joinable()) bgLoadWorkerThread.join(); + thumbLoadQueue.shutdown(); + if (thumbLoadWorkerThread.joinable()) thumbLoadWorkerThread.join(); + + // Acquire and release each mutex before destroying to ensure no thread is in a + // critical section -- a memory barrier for proper synchronization. if (bgMutex) { SDL_LockMutex(bgMutex); SDL_UnlockMutex(bgMutex); } if (thumbMutex) { SDL_LockMutex(thumbMutex); SDL_UnlockMutex(thumbMutex); } if (animMutex) { SDL_LockMutex(animMutex); SDL_UnlockMutex(animMutex); } - if (frameMutex) { SDL_LockMutex(frameMutex); SDL_UnlockMutex(frameMutex); } if (fontMutex) { SDL_LockMutex(fontMutex); SDL_UnlockMutex(fontMutex); } - // Destroy mutexes and condition variables - if (bgqueueMutex) SDL_DestroyMutex(bgqueueMutex); - if (thumbqueueMutex) SDL_DestroyMutex(thumbqueueMutex); - if (animqueueMutex) SDL_DestroyMutex(animqueueMutex); if (bgMutex) SDL_DestroyMutex(bgMutex); if (thumbMutex) SDL_DestroyMutex(thumbMutex); if (animMutex) SDL_DestroyMutex(animMutex); - if (frameMutex) SDL_DestroyMutex(frameMutex); if (fontMutex) SDL_DestroyMutex(fontMutex); - if (bgqueueCond) SDL_DestroyCond(bgqueueCond); - if (thumbqueueCond) SDL_DestroyCond(thumbqueueCond); - if (animqueueCond) SDL_DestroyCond(animqueueCond); - if (flipCond) SDL_DestroyCond(flipCond); - - // Set pointers to NULL after destruction - bgqueueMutex = NULL; - thumbqueueMutex = NULL; - animqueueMutex = NULL; bgMutex = NULL; thumbMutex = NULL; animMutex = NULL; - frameMutex = NULL; fontMutex = NULL; - bgqueueCond = NULL; - thumbqueueCond = NULL; - animqueueCond = NULL; - flipCond = NULL; } /////////////////////////////////////// @@ -2274,7 +1909,7 @@ int main (int argc, char *argv[]) { int qm_col = 0; int qm_slot = 0; int qm_shift = 0; - int qm_slots = QUICK_SWITCHER_COUNT > quick->count ? quick->count : QUICK_SWITCHER_COUNT; + int qm_slots = QUICK_SWITCHER_COUNT > (int)quick.size() ? (int)quick.size() : QUICK_SWITCHER_COUNT; // LOG_info("- menu init: %lu\n", SDL_GetTicks() - main_begin); int lastScreen = SCREEN_OFF; @@ -2314,8 +1949,8 @@ int main (int argc, char *argv[]) { char folderBgPath[1024]; folderbgbmp = NULL; - SDL_Surface * blackBG = SDL_CreateRGBSurfaceWithFormat(0,screen->w,screen->h,screen->format->BitsPerPixel,screen->format->format); - SDL_FillRect(blackBG,NULL,SDL_MapRGBA(screen->format,0,0,0,255)); + core::SurfacePtr blackBG{SDL_CreateRGBSurfaceWithFormat(0,screen->w,screen->h,screen->format->BitsPerPixel,screen->format->format)}; + SDL_FillRect(blackBG.get(),NULL,SDL_MapRGBA(screen->format,0,0,0,255)); SDL_LockMutex(animMutex); globalpill = SDL_CreateRGBSurfaceWithFormat(SDL_SWSURFACE, screen->w, SCALE1(PILL_SIZE), FIXED_DEPTH, screen->format->format); @@ -2330,8 +1965,10 @@ int main (int argc, char *argv[]) { PAD_poll(); + advancePillAnim(); // step the selection-pill tween on the main thread + int selected = top->selected; - int total = top->entries->count; + int total = (int)top->entries.size(); PWR_update(&dirty, &show_setting, NULL, NULL); @@ -2362,7 +1999,7 @@ int main (int argc, char *argv[]) { int gsanimdir = ANIM_NONE; if (currentScreen == SCREEN_QUICKMENU) { - int qm_total = qm_row == 0 ? quick->count : quickActions->count; + int qm_total = qm_row == 0 ? (int)quick.size() : (int)quickActions.size(); if (PAD_justPressed(BTN_B) || PAD_tappedMenu(now)) { currentScreen = SCREEN_GAMELIST; @@ -2370,10 +2007,10 @@ int main (int argc, char *argv[]) { dirty = 1; } else if (PAD_justReleased(BTN_A)) { - Entry *selected = qm_row == 0 ? quick->items[qm_col] : quickActions->items[qm_col]; + Entry* selected = (qm_row == 0 ? quick[qm_col] : quickActions[qm_col]); if(selected->type != ENTRY_DIP) { currentScreen = SCREEN_GAMELIST; - total = top->entries->count; + total = (int)top->entries.size(); // prevent restoring list state, game list screen currently isnt our nav origin top->selected = 0; top->start = 0; @@ -2385,7 +2022,7 @@ int main (int argc, char *argv[]) { restore_end = 0; } Entry_open(selected); - if (top->entries->count > 0) readyResume(top->entries->items[top->selected]); + if (!top->entries.empty()) readyResume(top->entries[top->selected]); dirty = 1; } else if (PAD_justPressed(BTN_RIGHT)) { @@ -2458,29 +2095,29 @@ int main (int argc, char *argv[]) { dirty = 1; folderbgchanged = 1; // The background painting code is a clusterfuck, just force a repaint here } - else if (recents->count > 0 && PAD_justReleased(BTN_A)) { + else if ((int)recents.size() > 0 && PAD_justReleased(BTN_A)) { // this will drop us back into game switcher after leaving the game putFile(GAME_SWITCHER_PERSIST_PATH, "unused"); startgame = 1; - Entry *selectedEntry = entryFromRecent(recents->items[switcher_selected]); + Entry *selectedEntry = entryFromRecent(recents[switcher_selected]); should_resume = can_resume; Entry_open(selectedEntry); dirty = 1; Entry_free(selectedEntry); } - else if (recents->count > 0 && PAD_justReleased(BTN_Y)) { + else if ((int)recents.size() > 0 && PAD_justReleased(BTN_Y)) { // remove - Recent* recentEntry = recents->items[switcher_selected--]; - Array_remove(recents, recentEntry); + Recent* recentEntry = recents[switcher_selected--]; + recents.erase(std::find(recents.begin(), recents.end(), recentEntry)); Recent_free(recentEntry); saveRecents(); if(switcher_selected < 0) - switcher_selected = recents->count - 1; // wrap + switcher_selected = (int)recents.size() - 1; // wrap dirty = 1; } else if (PAD_justPressed(BTN_RIGHT)) { switcher_selected++; - if(switcher_selected == recents->count) + if(switcher_selected == (int)recents.size()) switcher_selected = 0; // wrap dirty = 1; gsanimdir = SLIDE_LEFT; @@ -2488,7 +2125,7 @@ int main (int argc, char *argv[]) { else if (PAD_justPressed(BTN_LEFT)) { switcher_selected--; if(switcher_selected < 0) - switcher_selected = recents->count - 1; // wrap + switcher_selected = (int)recents.size() - 1; // wrap dirty = 1; gsanimdir = SLIDE_RIGHT; } @@ -2575,10 +2212,10 @@ int main (int argc, char *argv[]) { } if (PAD_justRepeated(BTN_L1) && !PAD_isPressed(BTN_R1) && !PWR_ignoreSettingInput(BTN_L1, show_setting)) { // previous alpha - Entry* entry = top->entries->items[selected]; + Entry* entry = top->entries[selected]; int i = entry->alpha-1; if (i>=0) { - selected = top->alphas->items[i]; + selected = top->alphas[i]; if (total>MAIN_ROW_COUNT) { top->start = selected; top->end = top->start + MAIN_ROW_COUNT; @@ -2588,10 +2225,10 @@ int main (int argc, char *argv[]) { } } else if (PAD_justRepeated(BTN_R1) && !PAD_isPressed(BTN_L1) && !PWR_ignoreSettingInput(BTN_R1, show_setting)) { // next alpha - Entry* entry = top->entries->items[selected]; + Entry* entry = top->entries[selected]; int i = entry->alpha+1; - if (ialphas->count) { - selected = top->alphas->items[i]; + if (i<(int)top->alphas.size()) { + selected = top->alphas[i]; if (total>MAIN_ROW_COUNT) { top->start = selected; top->end = top->start + MAIN_ROW_COUNT; @@ -2606,7 +2243,7 @@ int main (int argc, char *argv[]) { dirty = 1; } - Entry* entry = top->entries->items[top->selected]; + Entry* entry = top->entries[top->selected]; if (dirty && total>0) readyResume(entry); @@ -2621,30 +2258,29 @@ int main (int argc, char *argv[]) { Entry_open(entry); if(entry->type == ENTRY_DIR) { animationdirection = SLIDE_LEFT; - total = top->entries->count; + total = (int)top->entries.size(); } dirty = 1; - if (total>0) readyResume(top->entries->items[top->selected]); + if (total>0) readyResume(top->entries[top->selected]); } - else if (PAD_justPressed(BTN_B) && stack->count>1) { + else if (PAD_justPressed(BTN_B) && (int)stack.size()>1) { closeDirectory(); animationdirection = SLIDE_RIGHT; - total = top->entries->count; + total = (int)top->entries.size(); dirty = 1; - if (total>0) readyResume(top->entries->items[top->selected]); + if (total>0) readyResume(top->entries[top->selected]); } } if(dirty) { - SDL_Surface *tmpOldScreen = NULL; - SDL_Surface * switcherSur = NULL; + core::SurfacePtr tmpOldScreen; + core::SurfacePtr switcherSur; // NOTE:22 This causes slowdown when CFG_getMenuTransitions is set to false because animationdirection turns > 0 somewhere but is never set back to 0 and so this code runs on every action, will fix later if(animationdirection != ANIM_NONE || (lastScreen==SCREEN_GAMELIST && currentScreen == SCREEN_GAMESWITCHER)) { - if(tmpOldScreen) SDL_FreeSurface(tmpOldScreen); - tmpOldScreen = GFX_captureRendererToSurface(); - SDL_SetSurfaceBlendMode(tmpOldScreen,SDL_BLENDMODE_BLEND); + tmpOldScreen.reset(GFX_captureRendererToSurface()); + SDL_SetSurfaceBlendMode(tmpOldScreen.get(),SDL_BLENDMODE_BLEND); } // clear only background layer on start @@ -2653,8 +2289,20 @@ int main (int argc, char *argv[]) { } else { GFX_clearLayers(LAYER_TRANSITION); - if(lastScreen!=SCREEN_GAMELIST) + // Clear the thumbnail when leaving a gamelist, or when the + // directory depth changed this frame (moving up/down). Without the + // depth check, going from a ROM folder up to the systems overview + // is gamelist->gamelist, so the previous ROM's box art lingered on + // LAYER_THUMBNAIL and flashed through the directory transition. + // previous_depth is only updated later in the draw block, so here + // it still holds the old depth. + if(lastScreen!=SCREEN_GAMELIST || previous_depth != (int)stack.size()) { + SDL_LockMutex(thumbMutex); + if (thumbbmp) { SDL_FreeSurface(thumbbmp); thumbbmp = NULL; } + thumbchanged = 1; + SDL_UnlockMutex(thumbMutex); GFX_clearLayers(LAYER_THUMBNAIL); + } GFX_clearLayers(LAYER_SCROLLTEXT); GFX_clearLayers(LAYER_IDK2); } @@ -2672,14 +2320,14 @@ int main (int argc, char *argv[]) { GFX_clearLayers(LAYER_THUMBNAIL); } - Entry *current = qm_row == 0 ? quick->items[qm_col] : quickActions->items[qm_col]; + Entry* current = (qm_row == 0 ? quick[qm_col] : quickActions[qm_col]); char newBgPath[MAX_PATH]; char fallbackBgPath[MAX_PATH]; - sprintf(newBgPath, SDCARD_PATH "/.media/quick_%s%s.png", current->name, + core::format(newBgPath, SDCARD_PATH "/.media/quick_%s%s.png", current->name, !strcmp(current->name,"Wifi") && !CFG_getWifi() || // wifi or wifi_off, based on state !strcmp(current->name,"Bluetooth") && !CFG_getBluetooth() ? "_off" : ""); // bluetooth or bluetooth_off, based on state - sprintf(fallbackBgPath, SDCARD_PATH "/.media/quick.png"); + core::format(fallbackBgPath, SDCARD_PATH "/.media/quick.png"); // background if(!exists(newBgPath)) @@ -2692,9 +2340,9 @@ int main (int argc, char *argv[]) { // buttons (duped and trimmed from below) if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); - else GFX_blitButtonGroup((char*[]){ BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL }, 0, screen, 0); + else BTN_HINTS(0, screen, 0, BTN_SLEEP==BTN_POWER?"POWER":"MENU","SLEEP", NULL); - GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OPEN", NULL }, 1, screen, 1); + BTN_HINTS(1, screen, 1, "B","BACK", "A","OPEN", NULL); if(CFG_getShowQuickswitcherUI()) { #define MENU_ITEM_SIZE 72 // item size, top line @@ -2725,10 +2373,10 @@ int main (int argc, char *argv[]) { // just to keep selection visible. // every display should be able to fit three items, we shift horizontally to accomodate. ox -= qm_shift * (item_size + SCALE1(MENU_ITEM_MARGIN)); - for (int c = 0; c < quick->count; c++) + for (int c = 0; c < (int)quick.size(); c++) { SDL_Rect item_rect = {ox, oy, item_size, item_size}; - Entry *item = quick->items[c]; + Entry* item = quick[c]; SDL_Color text_color = uintToColour(THEME_COLOR4_255); uint32_t item_color = THEME_COLOR3; @@ -2743,13 +2391,12 @@ int main (int argc, char *argv[]) { GFX_blitRectColor(ASSET_STATE_BG, screen, &item_rect, item_color); char icon_path[MAX_PATH]; - sprintf(icon_path, SDCARD_PATH "/.system/res/%s@%ix.png", item->name, FIXED_SCALE); - SDL_Surface* bmp = IMG_Load(icon_path); + core::format(icon_path, SDCARD_PATH "/.system/res/%s@%ix.png", item->name, FIXED_SCALE); + core::SurfacePtr bmp{IMG_Load(icon_path)}; if(bmp) { - SDL_Surface* converted = SDL_ConvertSurfaceFormat(bmp, screen->format->format, 0); + SDL_Surface* converted = SDL_ConvertSurfaceFormat(bmp.get(), screen->format->format, 0); if (converted) { - SDL_FreeSurface(bmp); - bmp = converted; + bmp.reset(converted); } } if(bmp) { @@ -2759,24 +2406,24 @@ int main (int argc, char *argv[]) { SDL_Rect destRect = { ox+x, oy+y, 0, 0 }; // width/height not required //SDL_BlitSurface(bmp, NULL, screen, &destRect); - GFX_blitSurfaceColor(bmp, NULL, screen, &destRect, icon_color); + GFX_blitSurfaceColor(bmp.get(), NULL, screen, &destRect, icon_color); } int w, h; - GFX_sizeText(font.tiny, item->name, SCALE1(FONT_TINY), &w, &h); + GFX_sizeText(GFX_getFonts()->tiny, item->name, SCALE1(FONT_TINY), &w, &h); SDL_Rect text_rect = {item_rect.x + (item_size - w) / 2, item_rect.y + item_size - h - SCALE1(BUTTON_MARGIN), w, h}; - GFX_blitText(font.tiny, item->name, SCALE1(FONT_TINY), text_color, screen, &text_rect); + GFX_blitText(GFX_getFonts()->tiny, item->name, SCALE1(FONT_TINY), text_color, screen, &text_rect); ox += item_rect.w + SCALE1(MENU_ITEM_MARGIN); } // secondary ox = SCALE1(PADDING + MENU_MARGIN_X); - ox += (screen->w - SCALE1(PADDING + MENU_MARGIN_X + MENU_MARGIN_X + PADDING) - SCALE1(quickActions->count * PILL_SIZE) - SCALE1((quickActions->count - 1) * MENU_TOGGLE_MARGIN))/2; + ox += (screen->w - SCALE1(PADDING + MENU_MARGIN_X + MENU_MARGIN_X + PADDING) - SCALE1((int)quickActions.size() * PILL_SIZE) - SCALE1(((int)quickActions.size() - 1) * MENU_TOGGLE_MARGIN))/2; oy = SCALE1(PADDING + PILL_SIZE + BUTTON_MARGIN + MENU_MARGIN_Y + MENU_LINE_MARGIN) + item_size + item_extra_y / 2; - for (int c = 0; c < quickActions->count; c++) { + for (int c = 0; c < (int)quickActions.size(); c++) { SDL_Rect item_rect = {ox, oy, SCALE1(PILL_SIZE), SCALE1(PILL_SIZE)}; - Entry *item = quickActions->items[c]; + Entry* item = quickActions[c]; SDL_Color text_color = uintToColour(THEME_COLOR4_255); uint32_t item_color = THEME_COLOR3; @@ -2813,7 +2460,7 @@ int main (int argc, char *argv[]) { x += (SCALE1(PILL_SIZE) - rect.w) / 2; y += (SCALE1(PILL_SIZE) - rect.h) / 2; - GFX_blitAssetColor(asset, NULL, screen, &(SDL_Rect){x,y}, icon_color); + { SDL_Rect _r = {x,y}; GFX_blitAssetColor(asset, NULL, screen, &_r, icon_color); } ox += item_rect.w + SCALE1(MENU_TOGGLE_MARGIN); } @@ -2826,7 +2473,7 @@ int main (int argc, char *argv[]) { GFX_clearLayers(LAYER_ALL); GFX_clear(screen); GFX_flipHidden(); - GFX_animateSurfaceOpacity(tmpOldScreen,0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 150:20,LAYER_BACKGROUND); + GFX_animateSurfaceOpacity(tmpOldScreen.get(),0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 150:20,LAYER_BACKGROUND); } else if(currentScreen == SCREEN_GAMESWITCHER) { GFX_clearLayers(LAYER_ALL); @@ -2834,54 +2481,41 @@ int main (int argc, char *argv[]) { oy = 0; // For all recents with resumable state (i.e. has savegame), show game switcher carousel - if(recents->count > 0) { - Entry *selectedEntry = entryFromRecent(recents->items[switcher_selected]); + if((int)recents.size() > 0) { + Entry *selectedEntry = entryFromRecent(recents[switcher_selected]); readyResume(selectedEntry); // title pill { int max_width = screen->w - SCALE1(PADDING * 2) - ow; char display_name[256]; - int text_width = GFX_truncateText(font.large, selectedEntry->name, display_name, max_width, SCALE1(BUTTON_PADDING*2)); + int text_width = GFX_truncateText(GFX_getFonts()->large, selectedEntry->name, display_name, max_width, SCALE1(BUTTON_PADDING*2)); max_width = MIN(max_width, text_width); - SDL_Surface* text; SDL_Color textColor = uintToColour(THEME_COLOR6_255); SDL_LockMutex(fontMutex); - text = TTF_RenderUTF8_Blended(font.large, display_name, textColor); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->large, display_name, textColor)}; SDL_UnlockMutex(fontMutex); const int text_offset_y = (SCALE1(PILL_SIZE) - text->h + 1) >> 1; - GFX_blitPillLight(ASSET_WHITE_PILL, screen, &(SDL_Rect){ - SCALE1(PADDING), - SCALE1(PADDING), - max_width, - SCALE1(PILL_SIZE) - }); - SDL_BlitSurface(text, &(SDL_Rect){ - 0, - 0, - max_width-SCALE1(BUTTON_PADDING*2), - text->h - }, screen, &(SDL_Rect){ - SCALE1(PADDING+BUTTON_PADDING), - SCALE1(PADDING) + text_offset_y, - }); - SDL_FreeSurface(text); + SDL_Rect _pill_rect = { SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE) }; + GFX_blitPillLight(ASSET_WHITE_PILL, screen, &_pill_rect); + SDL_Rect _text_src = { 0, 0, max_width-SCALE1(BUTTON_PADDING*2), text->h }; + SDL_Rect _text_dst = { SCALE1(PADDING+BUTTON_PADDING), SCALE1(PADDING) + text_offset_y }; + SDL_BlitSurface(text.get(), &_text_src, screen, &_text_dst); } - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 0, screen, 0); + BTN_HINTS(0, screen, 0, "B","BACK", NULL); - if(can_resume) GFX_blitButtonGroup((char*[]){ "Y", "REMOVE", "A","RESUME", NULL }, 1, screen, 1); - else GFX_blitButtonGroup((char*[]){ "A","OPEN", NULL }, 1, screen, 1); + if(can_resume) BTN_HINTS(1, screen, 1, "Y","REMOVE", "A","RESUME", NULL); + else BTN_HINTS(1, screen, 1, "A","OPEN", NULL); if(has_preview) { // lotta memory churn here - SDL_Surface* bmp = IMG_Load(preview_path); - SDL_Surface* raw_preview = SDL_ConvertSurfaceFormat(bmp, screen->format->format, 0); + core::SurfacePtr bmp{IMG_Load(preview_path)}; + SDL_Surface* raw_preview = SDL_ConvertSurfaceFormat(bmp.get(), screen->format->format, 0); if (raw_preview) { - SDL_FreeSurface(bmp); - bmp = raw_preview; + bmp.reset(raw_preview); } if(bmp) { int aw = screen->w; @@ -2905,72 +2539,68 @@ int main (int argc, char *argv[]) { if(lastScreen == SCREEN_GAME) { // need to flip once so streaming_texture1 is updated GFX_flipHidden(); - GFX_animateSurfaceOpacity(bmp,0,0,screen->w,screen->h,0,255,CFG_getMenuTransitions() ? 150:20,LAYER_ALL); + GFX_animateSurfaceOpacity(bmp.get(),0,0,screen->w,screen->h,0,255,CFG_getMenuTransitions() ? 150:20,LAYER_ALL); } else if(lastScreen == SCREEN_GAMELIST) { - GFX_drawOnLayer(blackBG,0,0,screen->w,screen->h,1.0f,0,LAYER_BACKGROUND); - GFX_drawOnLayer(bmp,ax,ay,aw, ah,1.0f,0,LAYER_BACKGROUND); + GFX_drawOnLayer(blackBG.get(),0,0,screen->w,screen->h,1.0f,0,LAYER_BACKGROUND); + GFX_drawOnLayer(bmp.get(),ax,ay,aw, ah,1.0f,0,LAYER_BACKGROUND); GFX_flipHidden(); - SDL_Surface *tmpNewScreen = GFX_captureRendererToSurface(); + core::SurfacePtr tmpNewScreen{GFX_captureRendererToSurface()}; GFX_clearLayers(LAYER_ALL); folderbgchanged=1; - GFX_drawOnLayer(tmpOldScreen,0,0,screen->w, screen->h,1.0f,0,LAYER_ALL); - GFX_animateSurface(tmpNewScreen,0,0-screen->h,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 100:20,255,255,LAYER_BACKGROUND); - SDL_FreeSurface(tmpNewScreen); + GFX_drawOnLayer(tmpOldScreen.get(),0,0,screen->w, screen->h,1.0f,0,LAYER_ALL); + GFX_animateSurface(tmpNewScreen.get(),0,0-screen->h,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 100:20,255,255,LAYER_BACKGROUND); } else if(lastScreen == SCREEN_GAMESWITCHER) { GFX_flipHidden(); - GFX_drawOnLayer(blackBG,0,0,screen->w, screen->h,1.0f,0,LAYER_BACKGROUND); + GFX_drawOnLayer(blackBG.get(),0,0,screen->w, screen->h,1.0f,0,LAYER_BACKGROUND); if(gsanimdir == SLIDE_LEFT) - GFX_animateSurface(bmp,ax+screen->w,ay,ax,ay,aw,ah,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); + GFX_animateSurface(bmp.get(),ax+screen->w,ay,ax,ay,aw,ah,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); else if(gsanimdir == SLIDE_RIGHT) - GFX_animateSurface(bmp,ax-screen->w,ay,ax,ay,aw,ah,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); + GFX_animateSurface(bmp.get(),ax-screen->w,ay,ax,ay,aw,ah,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); - GFX_drawOnLayer(bmp,ax,ay,aw,ah,1.0f,0,LAYER_BACKGROUND); + GFX_drawOnLayer(bmp.get(),ax,ay,aw,ah,1.0f,0,LAYER_BACKGROUND); } else if(lastScreen == SCREEN_QUICKMENU) { GFX_flipHidden(); - GFX_drawOnLayer(blackBG,0,0,screen->w, screen->h,1.0f,0,LAYER_BACKGROUND); - GFX_drawOnLayer(bmp,ax,ay,aw,ah,1.0f,0,LAYER_BACKGROUND); + GFX_drawOnLayer(blackBG.get(),0,0,screen->w, screen->h,1.0f,0,LAYER_BACKGROUND); + GFX_drawOnLayer(bmp.get(),ax,ay,aw,ah,1.0f,0,LAYER_BACKGROUND); } - SDL_FreeSurface(bmp); // Free after rendering } } else { SDL_Rect preview_rect = {ox,oy,screen->w,screen->h}; - SDL_Surface * tmpsur = SDL_CreateRGBSurfaceWithFormat(0,screen->w,screen->h,screen->format->BitsPerPixel,screen->format->format); - SDL_FillRect(tmpsur, &preview_rect, THEME_COLOR7); + core::SurfacePtr tmpsur{SDL_CreateRGBSurfaceWithFormat(0,screen->w,screen->h,screen->format->BitsPerPixel,screen->format->format)}; + SDL_FillRect(tmpsur.get(), &preview_rect, THEME_COLOR7); if(lastScreen == SCREEN_GAME) { - GFX_animateSurfaceOpacity(tmpsur,0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 150:20,LAYER_BACKGROUND); + GFX_animateSurfaceOpacity(tmpsur.get(),0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 150:20,LAYER_BACKGROUND); } else if(lastScreen == SCREEN_GAMELIST) { - GFX_animateSurface(tmpsur,0,0-screen->h,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 100:20,255,255,LAYER_ALL); + GFX_animateSurface(tmpsur.get(),0,0-screen->h,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 100:20,255,255,LAYER_ALL); } else if(lastScreen == SCREEN_GAMESWITCHER) { GFX_flipHidden(); if(gsanimdir == SLIDE_LEFT) - GFX_animateSurface(tmpsur,0+screen->w,0,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); + GFX_animateSurface(tmpsur.get(),0+screen->w,0,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); else if(gsanimdir == SLIDE_RIGHT) - GFX_animateSurface(tmpsur,0-screen->w,0,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); + GFX_animateSurface(tmpsur.get(),0-screen->w,0,0,0,screen->w,screen->h,CFG_getMenuTransitions() ? 80:20,0,255,LAYER_ALL); } - SDL_FreeSurface(tmpsur); - GFX_blitMessage(font.large, "No Preview", screen, &preview_rect); + GFX_blitMessage(GFX_getFonts()->large, "No Preview", screen, &preview_rect); } Entry_free(selectedEntry); } else { SDL_Rect preview_rect = {ox,oy,screen->w,screen->h}; SDL_FillRect(screen, &preview_rect, 0); - GFX_blitMessage(font.large, "No Recents", screen, &preview_rect); - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 1, screen, 1); + GFX_blitMessage(GFX_getFonts()->large, "No Recents", screen, &preview_rect); + BTN_HINTS(1, screen, 1, "B","BACK", NULL); } GFX_flipHidden(); - if(switcherSur) SDL_FreeSurface(switcherSur); - switcherSur = GFX_captureRendererToSurface(); + switcherSur.reset(GFX_captureRendererToSurface()); lastScreen = SCREEN_GAMESWITCHER; } else { // if currentscreen == SCREEN_GAMELIST // background and game art file path stuff - Entry* entry = top->entries->items[top->selected]; + Entry* entry = top->entries[top->selected]; assert(entry); char tmp_path[MAX_PATH]; strncpy(tmp_path, entry->path, sizeof(tmp_path) - 1); @@ -2995,11 +2625,11 @@ int main (int argc, char *argv[]) { static int lastType = -1; // this is only a choice on the root folder - list_show_entry_names = stack->count > 1 || CFG_getShowFolderNamesAtRoot(); + list_show_entry_names = (int)stack.size() > 1 || CFG_getShowFolderNamesAtRoot(); // load folder background char defaultBgPath[512]; - snprintf(defaultBgPath, sizeof(defaultBgPath), SDCARD_PATH "/bg.png"); + core::format(defaultBgPath, SDCARD_PATH "/bg.png"); if(((entry->type == ENTRY_DIR || entry->type == ENTRY_ROM) && CFG_getRomsUseFolderBackground())) { char *newBg = entry->type == ENTRY_DIR ? entry->path:rompath; @@ -3008,13 +2638,13 @@ int main (int argc, char *argv[]) { char tmppath[512]; strncpy(folderBgPath, newBg, sizeof(folderBgPath) - 1); if (entry->type == ENTRY_DIR) - snprintf(tmppath, sizeof(tmppath), "%s/.media/bg.png", folderBgPath); + core::format(tmppath, "%s/.media/bg.png", folderBgPath); else if (entry->type == ENTRY_ROM) - snprintf(tmppath, sizeof(tmppath), "%s/.media/bglist.png", folderBgPath); + core::format(tmppath, "%s/.media/bglist.png", folderBgPath); if(!exists(tmppath)) { // Safeguard: If no background is available, still render the text to leave the user a way out list_show_entry_names = true; - snprintf(tmppath, sizeof(tmppath), defaultBgPath, folderBgPath); + core::format(tmppath, defaultBgPath, folderBgPath); } startLoadFolderBackground(tmppath, onBackgroundLoaded, NULL); } @@ -3031,41 +2661,48 @@ int main (int argc, char *argv[]) { if (total > 0) { if(CFG_getShowGameArt()) { char thumbpath[1024]; - snprintf(thumbpath, sizeof(thumbpath), "%s/.media/%s.png", rompath, res_copy); + core::format(thumbpath, "%s/.media/%s.png", rompath, res_copy); had_thumb = 0; - startLoadThumb(thumbpath, onThumbLoaded, NULL); int max_w = (int)(screen->w - (screen->w * CFG_getGameArtWidth())); int max_h = (int)(screen->h * 0.6); int new_w = max_w; int new_h = max_h; if(exists(thumbpath)) { + startLoadThumb(thumbpath, onThumbLoaded, NULL); ox = (int)(max_w) - SCALE1(BUTTON_MARGIN*5); had_thumb = 1; } - else + else { + // No art for this selection (e.g. a folder, or moving up + // a directory): clear any thumbnail from the previous + // selection right away, rather than queueing a load that + // only resolves to "no art" a frame or two later -- that + // async gap is what left the old box-art on screen briefly. + onThumbLoaded(NULL); ox = screen->w; + } } } // buttons if (show_setting && !GetHDMI()) GFX_blitHardwareHints(screen, show_setting); - else if (can_resume) GFX_blitButtonGroup((char*[]){ "X","RESUME", NULL }, 0, screen, 0); - else GFX_blitButtonGroup((char*[]){ + else if (can_resume) BTN_HINTS(0, screen, 0, "X","RESUME", NULL); + else BTN_HINTS(0, screen, 0, BTN_SLEEP==BTN_POWER?"POWER":"MENU", BTN_SLEEP==BTN_POWER||simple_mode?"SLEEP":"INFO", - NULL }, 0, screen, 0); + NULL); if (total==0) { - if (stack->count>1) { - GFX_blitButtonGroup((char*[]){ "B","BACK", NULL }, 0, screen, 1); + if ((int)stack.size()>1) { + BTN_HINTS(0, screen, 1, "B","BACK", NULL); } } else { - if (stack->count>1) { - GFX_blitButtonGroup((char*[]){ "B","BACK", "A","OPEN", NULL }, 1, screen, 1); + if ((int)stack.size()>1) { + BTN_HINTS(1, screen, 1, "B","BACK", "A","OPEN", NULL); } else { - GFX_blitButtonGroup((char*[]){ "A","OPEN", NULL }, 0, screen, 1); + BTN_HINTS(0, screen, 1, "A","OPEN", NULL); } } @@ -3076,13 +2713,13 @@ int main (int argc, char *argv[]) { targetY = selected_row * PILL_SIZE; SDL_Color text_color = uintToColour(THEME_COLOR4_255); // list text color for (int i = top->start, j = 0; i < top->end; i++, j++) { - Entry* entry = top->entries->items[i]; + Entry* entry = top->entries[i]; char* entry_name = entry->name; char* entry_unique = entry->unique; int available_width = MAX(0,(had_thumb ? ox + SCALE1(BUTTON_MARGIN) : screen->w - SCALE1(BUTTON_MARGIN)) - SCALE1(PADDING * 2)); bool row_is_selected = (j == selected_row); bool row_is_top = (i == top->start); - bool row_has_moved = (previous_row != selected_row || previous_depth != stack->count); + bool row_has_moved = (previous_row != selected_row || previous_depth != (int)stack.size()); if (row_is_top && !(had_thumb)) available_width -= ow; @@ -3091,7 +2728,7 @@ int main (int argc, char *argv[]) { trimSortingMeta(&entry_unique); char display_name[256]; - int text_width = GFX_getTextWidth(font.large, entry_unique ? entry_unique : entry_name, display_name, available_width, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_getTextWidth(GFX_getFonts()->large, entry_unique ? entry_unique : entry_name, display_name, available_width, SCALE1(BUTTON_PADDING * 2)); int max_width = MIN(available_width, text_width); // This spaghetti is preventing white text on white pill when volume/color temp is shown, @@ -3104,26 +2741,26 @@ int main (int argc, char *argv[]) { } SDL_LockMutex(fontMutex); - SDL_Surface* text = TTF_RenderUTF8_Blended(font.large, entry_name, text_color); - SDL_Surface* text_unique = TTF_RenderUTF8_Blended(font.large, display_name, COLOR_DARK_TEXT); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->large, entry_name, text_color)}; + core::SurfacePtr text_unique{TTF_RenderUTF8_Blended(GFX_getFonts()->large, display_name, COLOR_DARK_TEXT)}; SDL_UnlockMutex(fontMutex); // TODO: Use actual font metrics to center, this only works in simple cases const int text_offset_y = (SCALE1(PILL_SIZE) - text->h + 1) >> 1; if (row_is_selected) { - is_scrolling = list_show_entry_names && GFX_textShouldScroll(font.large,display_name, max_width - SCALE1(BUTTON_PADDING*2), fontMutex); + is_scrolling = list_show_entry_names && GFX_textShouldScroll(GFX_getFonts()->large,display_name, max_width - SCALE1(BUTTON_PADDING*2), fontMutex); GFX_resetScrollText(); - bool should_animate = previous_depth == stack->count; + bool should_animate = previous_depth == (int)stack.size(); SDL_LockMutex(animMutex); if(globalpill) { SDL_FreeSurface(globalpill); globalpill=NULL; } globalpill = SDL_CreateRGBSurfaceWithFormat(SDL_SWSURFACE, max_width, SCALE1(PILL_SIZE), FIXED_DEPTH, screen->format->format); - GFX_blitPillDark(ASSET_WHITE_PILL, globalpill, &(SDL_Rect){0,0, max_width, SCALE1(PILL_SIZE)}); + { SDL_Rect _r = {0,0, max_width, SCALE1(PILL_SIZE)}; GFX_blitPillDark(ASSET_WHITE_PILL, globalpill, &_r); } globallpillW = max_width; SDL_UnlockMutex(animMutex); updatePillTextSurface(entry_name, max_width, uintToColour(THEME_COLOR5_255)); - AnimTask* task = malloc(sizeof(AnimTask)); + AnimTask* task = (AnimTask*)(malloc(sizeof(AnimTask))); task->startX = SCALE1(BUTTON_MARGIN); task->startY = SCALE1(previousY+PADDING); task->targetX = SCALE1(BUTTON_MARGIN); @@ -3140,11 +2777,9 @@ int main (int argc, char *argv[]) { SDL_Rect dest_rect = { SCALE1(BUTTON_MARGIN + BUTTON_PADDING), SCALE1(PADDING + (j * PILL_SIZE)) + text_offset_y }; if(list_show_entry_names) { - SDL_BlitSurface(text_unique, &text_rect, screen, &dest_rect); - SDL_BlitSurface(text, &text_rect, screen, &dest_rect); + SDL_BlitSurface(text_unique.get(), &text_rect, screen, &dest_rect); + SDL_BlitSurface(text.get(), &text_rect, screen, &dest_rect); } - SDL_FreeSurface(text_unique); // Free after use - SDL_FreeSurface(text); // Free after use } if(lastScreen==SCREEN_GAMESWITCHER) { if(switcherSur) { @@ -3153,20 +2788,20 @@ int main (int argc, char *argv[]) { folderbgchanged=1; GFX_flipHidden(); - GFX_animateSurface(switcherSur,0,0,0,0-screen->h,screen->w,screen->h,CFG_getMenuTransitions() ? 100:20,255,255,LAYER_BACKGROUND); + GFX_animateSurface(switcherSur.get(),0,0,0,0-screen->h,screen->w,screen->h,CFG_getMenuTransitions() ? 100:20,255,255,LAYER_BACKGROUND); animationdirection = ANIM_NONE; } } if(lastScreen==SCREEN_OFF) { - GFX_animateSurfaceOpacity(blackBG,0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 200:20,LAYER_THUMBNAIL); + GFX_animateSurfaceOpacity(blackBG.get(),0,0,screen->w,screen->h,255,0,CFG_getMenuTransitions() ? 200:20,LAYER_THUMBNAIL); } previous_row = selected_row; - previous_depth = stack->count; + previous_depth = (int)stack.size(); } else { // TODO: for some reason screen's dimensions end up being 0x0 in GFX_blitMessage... - GFX_blitMessage(font.large, "Empty folder", screen, &(SDL_Rect){0,0,screen->w,screen->h}); //, NULL); + { SDL_Rect _r = {0,0,screen->w,screen->h}; GFX_blitMessage(GFX_getFonts()->large, "Empty folder", screen, &_r); } //, NULL); } lastScreen = SCREEN_GAMELIST; @@ -3195,19 +2830,18 @@ int main (int argc, char *argv[]) { SDL_UnlockMutex(animMutex); } GFX_flipHidden(); - SDL_Surface *tmpNewScreen = GFX_captureRendererToSurface(); - SDL_SetSurfaceBlendMode(tmpNewScreen,SDL_BLENDMODE_BLEND); + core::SurfacePtr tmpNewScreen{GFX_captureRendererToSurface()}; + SDL_SetSurfaceBlendMode(tmpNewScreen.get(),SDL_BLENDMODE_BLEND); GFX_clearLayers(LAYER_SCROLLTEXT); // baked into tmpNewScreen; must clear or it floats above the animation GFX_clearLayers(LAYER_THUMBNAIL); { int _duration = (CFG_getMenuTransitions() == TRANSITION_COMFY) ? TRANSITION_COMFY_DURATION : TRANSITION_SNAPPY_DURATION; - if(animationdirection == SLIDE_LEFT) GFX_animateAndFadeSurface(tmpOldScreen,0,0,-250,0,FIXED_WIDTH,FIXED_HEIGHT,_duration,tmpNewScreen,FIXED_WIDTH,0,0,0,FIXED_WIDTH,FIXED_HEIGHT,255,255,LAYER_THUMBNAIL,TRANSITION_CURVE,TRANSITION_CURVE,TRANSITION_INTENSITY); - if(animationdirection == SLIDE_RIGHT) GFX_animateAndFadeSurface(tmpNewScreen,-250,0,0,0,FIXED_WIDTH,FIXED_HEIGHT,_duration,tmpOldScreen,0,0,FIXED_WIDTH,0,FIXED_WIDTH,FIXED_HEIGHT,255,255,LAYER_THUMBNAIL,TRANSITION_CURVE,TRANSITION_CURVE,TRANSITION_INTENSITY); + if(animationdirection == SLIDE_LEFT) GFX_animateAndFadeSurface(tmpOldScreen.get(),0,0,-250,0,FIXED_WIDTH,FIXED_HEIGHT,_duration,tmpNewScreen.get(),FIXED_WIDTH,0,0,0,FIXED_WIDTH,FIXED_HEIGHT,255,255,LAYER_THUMBNAIL,TRANSITION_CURVE,TRANSITION_CURVE,TRANSITION_INTENSITY); + if(animationdirection == SLIDE_RIGHT) GFX_animateAndFadeSurface(tmpNewScreen.get(),-250,0,0,0,FIXED_WIDTH,FIXED_HEIGHT,_duration,tmpOldScreen.get(),0,0,FIXED_WIDTH,0,FIXED_WIDTH,FIXED_HEIGHT,255,255,LAYER_THUMBNAIL,TRANSITION_CURVE,TRANSITION_CURVE,TRANSITION_INTENSITY); } GFX_clearLayers(LAYER_THUMBNAIL); - SDL_FreeSurface(tmpNewScreen); } // animation done animationdirection = ANIM_NONE; @@ -3327,9 +2961,9 @@ int main (int argc, char *argv[]) { } SDL_UnlockMutex(animMutex); if (currentScreen != SCREEN_GAMESWITCHER && currentScreen != SCREEN_QUICKMENU) { - if(is_scrolling && pillanimdone && currentAnimQueueSize < 1) { + if(is_scrolling && pillanimdone && !pillAnim.active) { int ow = GFX_blitHardwareGroup(screen, show_setting); - Entry* entry = top->entries->items[top->selected]; + Entry* entry = top->entries[top->selected]; trimSortingMeta(&entry->name); char* entry_text = entry->name; if (entry->unique) { @@ -3342,14 +2976,14 @@ int main (int argc, char *argv[]) { SDL_Color text_color = uintToColour(THEME_COLOR5_255); - int text_width = GFX_getTextWidth(font.large, entry_text, cached_display_name, available_width, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_getTextWidth(GFX_getFonts()->large, entry_text, cached_display_name, available_width, SCALE1(BUTTON_PADDING * 2)); int max_width = MIN(available_width, text_width); - int text_offset_y = (SCALE1(PILL_SIZE) - TTF_FontHeight(font.large) + 1) >> 1; + int text_offset_y = (SCALE1(PILL_SIZE) - TTF_FontHeight(GFX_getFonts()->large) + 1) >> 1; GFX_clearLayers(LAYER_SCROLLTEXT); if (list_show_entry_names) { GFX_scrollTextTexture( - font.large, + GFX_getFonts()->large, entry_text, SCALE1(BUTTON_MARGIN + BUTTON_PADDING), SCALE1(PADDING + previous_row * PILL_SIZE) + text_offset_y, max_width - SCALE1(BUTTON_PADDING * 2), @@ -3379,25 +3013,14 @@ int main (int argc, char *argv[]) { } else { // want to draw only if needed - SDL_LockMutex(bgqueueMutex); - SDL_LockMutex(thumbqueueMutex); - SDL_LockMutex(animqueueMutex); if(getNeedDraw()) { PLAT_GPU_Flip(); setNeedDraw(0); } else { GFX_sync(); } - SDL_UnlockMutex(animqueueMutex); - SDL_UnlockMutex(thumbqueueMutex); - SDL_UnlockMutex(bgqueueMutex); } - SDL_LockMutex(frameMutex); - frameReady = true; - SDL_CondSignal(flipCond); - SDL_UnlockMutex(frameMutex); - // animation does not carry over between loops, this should only ever be set by // input handling and directly consumed by the following render pass assert(animationdirection == ANIM_NONE); @@ -3409,7 +3032,7 @@ int main (int argc, char *argv[]) { if (has_hdmi!=had_hdmi) { had_hdmi = has_hdmi; - Entry* entry = top->entries->items[top->selected]; + Entry* entry = top->entries[top->selected]; LOG_info("restarting after HDMI change... (%s)\n", entry->path); saveLast(entry->path); // NOTE: doesn't work in Recents (by design) sleep(4); @@ -3427,7 +3050,6 @@ int main (int argc, char *argv[]) { GFX_quit(); // Cleanup video subsystem first to stop GPU threads // Now safe to free surfaces after GPU threads are stopped - if(blackBG) SDL_FreeSurface(blackBG); if (folderbgbmp) SDL_FreeSurface(folderbgbmp); if (thumbbmp) SDL_FreeSurface(thumbbmp); diff --git a/workspace/all/nextval/makefile b/workspace/all/nextval/makefile index 2467a08be..c8c537435 100644 --- a/workspace/all/nextval/makefile +++ b/workspace/all/nextval/makefile @@ -20,16 +20,22 @@ include ../../$(PLATFORM)/platform/makefile.env TARGET = nextval INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = $(TARGET).c ../common/utils.c ../common/config.c +# Common utils/config are now C++20 too; single g++ compile+link, no C sources. +CXXSOURCE = $(TARGET).cpp ../common/utils.cpp ../common/config.cpp CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) -CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=gnu99 +CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) PRODUCT= build/$(PLATFORM)/$(TARGET).elf -all: +all: mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/nextval/nextval.c b/workspace/all/nextval/nextval.cpp similarity index 81% rename from workspace/all/nextval/nextval.c rename to workspace/all/nextval/nextval.cpp index fc68bf40a..cd7614347 100644 --- a/workspace/all/nextval/nextval.c +++ b/workspace/all/nextval/nextval.cpp @@ -1,8 +1,13 @@ -#include +#include +#include +// Project C headers declare C-linkage symbols defined in the still-C common/ +// translation units; include them as extern "C". +extern "C" { #include "defines.h" #include "api.h" #include "utils.h" +} void printUsage() { @@ -11,7 +16,7 @@ void printUsage() int main(int argc, char *argv[]) { - CFG_init(NULL, NULL); + CFG_init(nullptr, nullptr); if (argc <= 1) { CFG_print(); diff --git a/workspace/all/settings/btmenu.cpp b/workspace/all/settings/btmenu.cpp index 261971230..4e7162b15 100644 --- a/workspace/all/settings/btmenu.cpp +++ b/workspace/all/settings/btmenu.cpp @@ -4,6 +4,8 @@ # include "btagent.hpp" #endif +#include "core/surface.h" + #include #include @@ -290,7 +292,6 @@ PairableItem::PairableItem(BT_device d, MenuList* submenu) void PairableItem::drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) const { SDL_Color text_color = uintToColour(THEME_COLOR4_255); - SDL_Surface *text = TTF_RenderUTF8_Blended(font.tiny, item.getLabel().c_str(), COLOR_WHITE); // always white // hack - this should be correlated to max_width int mw = dst.w; @@ -315,15 +316,14 @@ void PairableItem::drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, con { // white pill int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); GFX_blitPillDarkCPP(ASSET_BUTTON, surface, {dst.x, dst.y, w, SCALE1(BUTTON_SIZE)}); text_color = uintToColour(THEME_COLOR5_255); } - text = TTF_RenderUTF8_Blended(font.small, item.getName().c_str(), text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + SCALE1(1)}); - SDL_FreeSurface(text); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->small, item.getName().c_str(), text_color)}; + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + SCALE1(1)}); } PairedItem::PairedItem(BT_devicePaired d, MenuList* submenu) @@ -333,7 +333,6 @@ PairedItem::PairedItem(BT_devicePaired d, MenuList* submenu) void PairedItem::drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) const { SDL_Color text_color = uintToColour(THEME_COLOR4_255); - SDL_Surface *text = TTF_RenderUTF8_Blended(font.tiny, item.getLabel().c_str(), COLOR_WHITE); // always white // hack - this should be correlated to max_width int mw = dst.w; @@ -377,13 +376,12 @@ void PairedItem::drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, const { // white pill int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); GFX_blitPillDarkCPP(ASSET_BUTTON, surface, {dst.x, dst.y, w, SCALE1(BUTTON_SIZE)}); text_color = uintToColour(THEME_COLOR5_255); } - text = TTF_RenderUTF8_Blended(font.small, item.getName().c_str(), text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + SCALE1(1)}); - SDL_FreeSurface(text); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->small, item.getName().c_str(), text_color)}; + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + SCALE1(1)}); } diff --git a/workspace/all/settings/colorpickermenu.cpp b/workspace/all/settings/colorpickermenu.cpp index eb605f6e0..4b29c39df 100644 --- a/workspace/all/settings/colorpickermenu.cpp +++ b/workspace/all/settings/colorpickermenu.cpp @@ -1,5 +1,6 @@ #include "colorpickermenu.hpp" +#include "core/surface.h" #include static constexpr int NUM_SLIDERS = 4; @@ -175,12 +176,12 @@ static void drawRoundedAlphaRect(SDL_Surface *surface, const SDL_Rect &rect, int { if (rect.w <= 0 || rect.h <= 0) return; - SDL_Surface *tmp = SDL_CreateRGBSurfaceWithFormat(0, rect.w, rect.h, - 32, SDL_PIXELFORMAT_ARGB8888); + core::SurfacePtr tmp{SDL_CreateRGBSurfaceWithFormat(0, rect.w, rect.h, + 32, SDL_PIXELFORMAT_ARGB8888)}; if (!tmp) return; - SDL_SetSurfaceBlendMode(tmp, SDL_BLENDMODE_BLEND); - SDL_FillRect(tmp, nullptr, SDL_MapRGBA(tmp->format, 0, 0, 0, 0)); + SDL_SetSurfaceBlendMode(tmp.get(), SDL_BLENDMODE_BLEND); + SDL_FillRect(tmp.get(), nullptr, SDL_MapRGBA(tmp->format, 0, 0, 0, 0)); auto remap = [&](uint32_t c) -> uint32_t { uint8_t rr, gg, bb, aa; @@ -189,21 +190,20 @@ static void drawRoundedAlphaRect(SDL_Surface *surface, const SDL_Rect &rect, int }; SDL_Rect r0 = {0, 0, rect.w, rect.h}; - drawSolidRoundedRect(tmp, r0, radius, remap(outer)); + drawSolidRoundedRect(tmp.get(),r0, radius, remap(outer)); if (rect.w > 2 && rect.h > 2) { SDL_Rect r1 = {1, 1, rect.w - 2, rect.h - 2}; - drawSolidRoundedRect(tmp, r1, std::max(0, radius - 1), remap(middle)); + drawSolidRoundedRect(tmp.get(),r1, std::max(0, radius - 1), remap(middle)); } if (rect.w > 4 && rect.h > 4) { SDL_Rect r2 = {2, 2, rect.w - 4, rect.h - 4}; - drawSolidRoundedRect(tmp, r2, std::max(0, radius - 2), remap(fill)); + drawSolidRoundedRect(tmp.get(),r2, std::max(0, radius - 2), remap(fill)); } SDL_Rect dst = rect; - SDL_BlitSurface(tmp, nullptr, surface, &dst); - SDL_FreeSurface(tmp); + SDL_BlitSurface(tmp.get(), nullptr, surface, &dst); } static void drawGradientCapsule(SDL_Surface *surface, const SDL_Rect &bar, @@ -214,11 +214,11 @@ static void drawGradientCapsule(SDL_Surface *surface, const SDL_Rect &bar, // SDL_FillRect does not alpha blend into destination surfaces. Build the // gradient into an ARGB temp surface, then alpha-blit it onto destination. - SDL_Surface *grad = SDL_CreateRGBSurfaceWithFormat(0, bar.w, bar.h, - 32, SDL_PIXELFORMAT_ARGB8888); + core::SurfacePtr grad{SDL_CreateRGBSurfaceWithFormat(0, bar.w, bar.h, + 32, SDL_PIXELFORMAT_ARGB8888)}; if (!grad) return; - SDL_SetSurfaceBlendMode(grad, SDL_BLENDMODE_BLEND); - SDL_FillRect(grad, nullptr, SDL_MapRGBA(grad->format, 0, 0, 0, 0)); + SDL_SetSurfaceBlendMode(grad.get(), SDL_BLENDMODE_BLEND); + SDL_FillRect(grad.get(), nullptr, SDL_MapRGBA(grad->format, 0, 0, 0, 0)); for (int x = 0; x < bar.w; x++) { @@ -246,12 +246,11 @@ static void drawGradientCapsule(SDL_Surface *surface, const SDL_Rect &bar, int col_h = bar.h - 2 * y_inset; if (col_h <= 0) continue; SDL_Rect col_rect = {x, y_inset, 1, col_h}; - SDL_FillRect(grad, &col_rect, col); + SDL_FillRect(grad.get(), &col_rect, col); } SDL_Rect dst = bar; - SDL_BlitSurface(grad, nullptr, surface, &dst); - SDL_FreeSurface(grad); + SDL_BlitSurface(grad.get(), nullptr, surface, &dst); } void ColorPickerMenu::drawSlider(SDL_Surface *surface, const SDL_Rect &row, @@ -266,31 +265,29 @@ void ColorPickerMenu::drawSlider(SDL_Surface *surface, const SDL_Rect &row, int label_fixed_w; { int wr, wg, wb, wa; - TTF_SizeUTF8(font.small, "R", &wr, nullptr); - TTF_SizeUTF8(font.small, "G", &wg, nullptr); - TTF_SizeUTF8(font.small, "B", &wb, nullptr); - TTF_SizeUTF8(font.small, "A", &wa, nullptr); + TTF_SizeUTF8(GFX_getFonts()->small, "R", &wr, nullptr); + TTF_SizeUTF8(GFX_getFonts()->small, "G", &wg, nullptr); + TTF_SizeUTF8(GFX_getFonts()->small, "B", &wb, nullptr); + TTF_SizeUTF8(GFX_getFonts()->small, "A", &wa, nullptr); label_fixed_w = std::max({wr, wg, wb, wa}); } int hex_fixed_w; - TTF_SizeUTF8(font.tiny, "00", &hex_fixed_w, nullptr); + TTF_SizeUTF8(GFX_getFonts()->tiny, "00", &hex_fixed_w, nullptr); // Channel label ("R", "G", "B", "A") — centered in fixed-width slot - SDL_Surface *label_surf = TTF_RenderUTF8_Blended(font.small, label, text_color); + core::SurfacePtr label_surf{TTF_RenderUTF8_Blended(GFX_getFonts()->small, label, text_color)}; int label_x = row.x + SCALE1(OPTION_PADDING) + (label_fixed_w - label_surf->w) / 2; - SDL_BlitSurfaceCPP(label_surf, {}, surface, + SDL_BlitSurfaceCPP(label_surf.get(), {}, surface, {label_x, row.y + (row.h - label_surf->h) / 2}); - SDL_FreeSurface(label_surf); // Hex value right-aligned in fixed-width slot char hex_str[4]; snprintf(hex_str, sizeof(hex_str), "%02X", value); - SDL_Surface *hex_surf = TTF_RenderUTF8_Blended(font.tiny, hex_str, text_color); + core::SurfacePtr hex_surf{TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, hex_str, text_color)}; int hex_slot_x = row.x + row.w - SCALE1(OPTION_PADDING) - hex_fixed_w; - SDL_BlitSurfaceCPP(hex_surf, {}, surface, + SDL_BlitSurfaceCPP(hex_surf.get(), {}, surface, {hex_slot_x + (hex_fixed_w - hex_surf->w), row.y + (row.h - hex_surf->h) / 2}); - SDL_FreeSurface(hex_surf); // Slider bar — stable bounds derived from fixed label/hex widths int bar_x = row.x + SCALE1(OPTION_PADDING) + label_fixed_w + SCALE1(OPTION_PADDING); @@ -326,23 +323,22 @@ void ColorPickerMenu::drawSlider(SDL_Surface *surface, const SDL_Rect &row, // Draw the thumb onto a temporary ARGB surface so alpha blending works. // SDL_FillRect writes directly (no blend); blitting FROM an alpha surface does blend. - SDL_Surface *thumb = SDL_CreateRGBSurfaceWithFormat(0, circ_d, circ_d, - 32, SDL_PIXELFORMAT_ARGB8888); - SDL_SetSurfaceBlendMode(thumb, SDL_BLENDMODE_BLEND); - SDL_SetColorKey(thumb, SDL_FALSE, 0); - SDL_FillRect(thumb, nullptr, SDL_MapRGBA(thumb->format, 0, 0, 0, 0)); + core::SurfacePtr thumb{SDL_CreateRGBSurfaceWithFormat(0, circ_d, circ_d, + 32, SDL_PIXELFORMAT_ARGB8888)}; + SDL_SetSurfaceBlendMode(thumb.get(), SDL_BLENDMODE_BLEND); + SDL_SetColorKey(thumb.get(), SDL_FALSE, 0); + SDL_FillRect(thumb.get(), nullptr, SDL_MapRGBA(thumb->format, 0, 0, 0, 0)); SDL_Rect thumb_outer = {0, 0, circ_d, circ_d}; uint32_t thumbB = SDL_MapRGBA(thumb->format, 0, 0, 0, 200); - drawSolidRoundedRect(thumb, thumb_outer, circ_d / 2, thumbB); + drawSolidRoundedRect(thumb.get(),thumb_outer, circ_d / 2, thumbB); int inner_d = circ_d - circ_border * 2; SDL_Rect thumb_inner = {circ_border, circ_border, inner_d, inner_d}; uint32_t thumbF = SDL_MapRGBA(thumb->format, 255, 255, 255, 200); - drawSolidRoundedRect(thumb, thumb_inner, inner_d / 2, thumbF); + drawSolidRoundedRect(thumb.get(),thumb_inner, inner_d / 2, thumbF); SDL_Rect outer_rect = {cx - circ_d / 2, cy - circ_d / 2, circ_d, circ_d}; - SDL_BlitSurface(thumb, nullptr, surface, &outer_rect); - SDL_FreeSurface(thumb); + SDL_BlitSurface(thumb.get(), nullptr, surface, &outer_rect); } } @@ -366,19 +362,17 @@ void ColorPickerMenu::drawPreset(SDL_Surface *surface, const SDL_Rect &row, // Hex value "#RRGGBBAA" char hex_str[10]; snprintf(hex_str, sizeof(hex_str), "#%08X", preset.color); - SDL_Surface *hex_surf = TTF_RenderUTF8_Blended(font.tiny, hex_str, text_color); + core::SurfacePtr hex_surf{TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, hex_str, text_color)}; int hex_x = sq_rect.x + sq + SCALE1(OPTION_PADDING / 2 + 2); - SDL_BlitSurfaceCPP(hex_surf, {}, surface, + SDL_BlitSurfaceCPP(hex_surf.get(), {}, surface, {hex_x, row.y + (row.h - hex_surf->h) / 2}); int hex_w = hex_surf->w; - SDL_FreeSurface(hex_surf); // Name label - SDL_Surface *name_surf = TTF_RenderUTF8_Blended(font.tiny, preset.label.c_str(), text_color); - SDL_BlitSurfaceCPP(name_surf, {}, surface, + core::SurfacePtr name_surf{TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, preset.label.c_str(), text_color)}; + SDL_BlitSurfaceCPP(name_surf.get(), {}, surface, {hex_x + hex_w + SCALE1(OPTION_PADDING), row.y + (row.h - name_surf->h) / 2}); - SDL_FreeSurface(name_surf); } void ColorPickerMenu::drawCustom(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rect &dstTitle) @@ -387,12 +381,11 @@ void ColorPickerMenu::drawCustom(SDL_Surface *surface, const SDL_Rect &dst, cons if(dstTitle.h > 0 && dstTitle.w > 0) { char display_name[256]; - GFX_truncateText(font.large, label_.c_str(), display_name, + GFX_truncateText(GFX_getFonts()->large, label_.c_str(), display_name, dstTitle.w, SCALE1(OPTION_PADDING * 2)); - SDL_Surface *title_text = TTF_RenderUTF8_Blended(font.large, display_name, uintToColour(THEME_COLOR4_255)); + core::SurfacePtr title_text{TTF_RenderUTF8_Blended(GFX_getFonts()->large, display_name, uintToColour(THEME_COLOR4_255))}; // hate the hardcoded +4, but we are matching MinUI code here - SDL_BlitSurfaceCPP(title_text, {}, surface, {dstTitle.x + SCALE1(BUTTON_PADDING), dstTitle.y + 4, dstTitle.w - SCALE1(BUTTON_PADDING * 2), title_text->h}); - SDL_FreeSurface(title_text); + SDL_BlitSurfaceCPP(title_text.get(), {}, surface, {dstTitle.x + SCALE1(BUTTON_PADDING), dstTitle.y + 4, dstTitle.w - SCALE1(BUTTON_PADDING * 2), title_text->h}); } // Button hints at the bottom of the screen diff --git a/workspace/all/settings/keyboardprompt.cpp b/workspace/all/settings/keyboardprompt.cpp index 161af210e..b91cc3d19 100644 --- a/workspace/all/settings/keyboardprompt.cpp +++ b/workspace/all/settings/keyboardprompt.cpp @@ -1,5 +1,7 @@ #include "keyboardprompt.hpp" +#include "core/surface.h" + constexpr int keyboardRows = 5; constexpr int keyboardColumns = 14; // keyboard_layout_lowercase is the default keyboard layout @@ -321,26 +323,25 @@ void KeyboardPrompt::drawKeyboard(SDL_Surface *screen, const AppState &state) // draw keyboard title if (!state.keyboard.title.empty()) { - SDL_Surface *title = TTF_RenderUTF8_Blended(font.large, state.keyboard.title.c_str(), COLOR_WHITE); + core::SurfacePtr title{TTF_RenderUTF8_Blended(GFX_getFonts()->large, state.keyboard.title.c_str(), COLOR_WHITE)}; SDL_Rect title_pos = { (screen->w - title->w) / 2, // center horizontally 20, // 20px from top title->w, title->h}; - SDL_BlitSurface(title, NULL, screen, &title_pos); - SDL_FreeSurface(title); + SDL_BlitSurface(title.get(), NULL, screen, &title_pos); } // draw input field with current text // todo: use TTF_SizeUTF8 to compute the width of the input field - SDL_Surface *input_placeholder = TTF_RenderUTF8_Blended(font.medium, "p", COLOR_WHITE); - SDL_Surface *input = TTF_RenderUTF8_Blended(font.medium, state.keyboard.current_text.c_str(), COLOR_WHITE); + core::SurfacePtr input_placeholder{TTF_RenderUTF8_Blended(GFX_getFonts()->medium, "p", COLOR_WHITE)}; + core::SurfacePtr input{TTF_RenderUTF8_Blended(GFX_getFonts()->medium, state.keyboard.current_text.c_str(), COLOR_WHITE)}; SDL_Rect input_pos = { (screen->w) / 2, input_placeholder->h * 2, 0, input_placeholder->h}; - if (input != NULL) + if (input) { input_pos.x = (screen->w - input->w) / 2; input_pos.w = input->w; @@ -354,8 +355,7 @@ void KeyboardPrompt::drawKeyboard(SDL_Surface *screen, const AppState &state) screen->w - 80, input_placeholder->h}; SDL_FillRect(screen, &input_bg, SDL_MapRGB(screen->format, TRIAD_DARK_GRAY)); - SDL_BlitSurface(input, NULL, screen, &input_pos); - SDL_FreeSurface(input); + SDL_BlitSurface(input.get(), NULL, screen, &input_pos); // draw keyboard layout int start_y = input_placeholder->h * 4; @@ -372,9 +372,9 @@ void KeyboardPrompt::drawKeyboard(SDL_Surface *screen, const AppState &state) // so we need to compute their width separately // compute them here to avoid doing it conditionally for each row int shift_width, space_width, enter_width; - TTF_SizeUTF8(font.medium, "shift", &shift_width, NULL); - TTF_SizeUTF8(font.medium, "space", &space_width, NULL); - TTF_SizeUTF8(font.medium, "enter", &enter_width, NULL); + TTF_SizeUTF8(GFX_getFonts()->medium, "shift", &shift_width, NULL); + TTF_SizeUTF8(GFX_getFonts()->medium, "space", &space_width, NULL); + TTF_SizeUTF8(GFX_getFonts()->medium, "enter", &enter_width, NULL); int special_key_width = std::max(shift_width, std::max(space_width, enter_width)) + (column_spacing * 4); for (int row = 0; row < num_rows; row++) @@ -409,7 +409,7 @@ void KeyboardPrompt::drawKeyboard(SDL_Surface *screen, const AppState &state) continue; SDL_Color text_color = (row == state.keyboard.row && col == state.keyboard.col) ? COLOR_BLACK : COLOR_WHITE; - SDL_Surface *key_text = TTF_RenderUTF8_Blended(font.medium, key.c_str(), text_color); + core::SurfacePtr key_text{TTF_RenderUTF8_Blended(GFX_getFonts()->medium, key.c_str(), text_color)}; // special keys are not the same width as the other keys // so we need to compute their width separately @@ -438,8 +438,7 @@ void KeyboardPrompt::drawKeyboard(SDL_Surface *screen, const AppState &state) key_text->w, key_text->h}; - SDL_BlitSurface(key_text, NULL, screen, &text_pos); - SDL_FreeSurface(key_text); + SDL_BlitSurface(key_text.get(), NULL, screen, &text_pos); } } } diff --git a/workspace/all/settings/makefile b/workspace/all/settings/makefile index ea854f1c8..4f37494c6 100644 --- a/workspace/all/settings/makefile +++ b/workspace/all/settings/makefile @@ -21,15 +21,18 @@ SDL?=SDL TARGET = settings INCDIR = -I. -I../common/ -I../../$(PLATFORM)/platform/ -SOURCE = -c ../common/utils.c ../common/api.c ../common/config.c ../common/palette.c ../common/scaler.c ../common/http.c ../common/ra_auth.c ../common/ra_offline.c ../common/ra_sync.c ../common/ra_event_queue.c ../../$(PLATFORM)/platform/platform.c -CXXSOURCE = $(TARGET).cpp menu.cpp colorpickermenu.cpp palettemenu.cpp fnbuttonmenu.cpp wifimenu.cpp btmenu.cpp keyboardprompt.cpp -CXXSOURCE += build/$(PLATFORM)/utils.o build/$(PLATFORM)/api.o build/$(PLATFORM)/config.o build/$(PLATFORM)/palette.o build/$(PLATFORM)/scaler.o build/$(PLATFORM)/http.o build/$(PLATFORM)/ra_auth.o build/$(PLATFORM)/ra_offline.o build/$(PLATFORM)/ra_sync.o build/$(PLATFORM)/ra_event_queue.o build/$(PLATFORM)/platform.o +SOURCE = -c ../../$(PLATFORM)/platform/platform.cpp +CXXSOURCE = ../common/palette.cpp ../common/utils.cpp ../common/config.cpp ../common/scaler.cpp ../common/api.cpp ../common/ra_offline.cpp ../common/ra_sync.cpp ../common/ra_event_queue.cpp ../common/http.cpp ../common/ra_auth.cpp $(TARGET).cpp menu.cpp colorpickermenu.cpp palettemenu.cpp fnbuttonmenu.cpp wifimenu.cpp btmenu.cpp keyboardprompt.cpp +CXXSOURCE += build/$(PLATFORM)/platform.o CC = $(CROSS_COMPILE)gcc CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) CFLAGS += $(OPT) CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -CXXFLAGS += $(CFLAGS) -std=c++17 +CXXFLAGS += $(CFLAGS) -std=$(CXXSTD) CFLAGS += -fpermissive LDFLAGS += -lmsettings -lcrypto ifeq ($(PLATFORM), tg5040) @@ -49,8 +52,8 @@ PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) $(CFLAGS) $(LDFLAGS) - mv utils.o api.o config.o palette.o scaler.o http.o ra_auth.o ra_offline.o ra_sync.o ra_event_queue.o platform.o build/$(PLATFORM) + $(CXX) $(SOURCE) $(CXXFLAGS) $(LDFLAGS) + mv platform.o build/$(PLATFORM) $(CXX) $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/settings/menu.cpp b/workspace/all/settings/menu.cpp index b17a0cabb..e4a2098cb 100644 --- a/workspace/all/settings/menu.cpp +++ b/workspace/all/settings/menu.cpp @@ -7,6 +7,8 @@ extern "C" #include "utils.h" } +#include "core/surface.h" + #include #include #include @@ -497,7 +499,7 @@ SDL_Rect MenuList::itemSizeHint(const AbstractMenuItem &item) { // calculate the size of the list int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); return {0, 0, w, SCALE1(PILL_SIZE)}; } @@ -506,7 +508,7 @@ SDL_Rect MenuList::itemSizeHint(const AbstractMenuItem &item) int w = 0; int lw = 0; int rw = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &lw, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &lw, NULL); // get the width of the widest row int mrw = 0; // every value list in an input table is the same @@ -515,7 +517,7 @@ SDL_Rect MenuList::itemSizeHint(const AbstractMenuItem &item) { for (int j = 0; item.getValues().size() > j && !item.getLabels()[j].empty(); j++) { - TTF_SizeUTF8(font.tiny, item.getLabels()[j].c_str(), &rw, NULL); + TTF_SizeUTF8(GFX_getFonts()->tiny, item.getLabels()[j].c_str(), &rw, NULL); if (lw + rw > w) w = lw + rw; if (rw > mrw) @@ -532,7 +534,7 @@ SDL_Rect MenuList::itemSizeHint(const AbstractMenuItem &item) else if (type == MenuItemType::Main) { int w = 0; - TTF_SizeUTF8(font.large, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->large, item.getName().c_str(), &w, NULL); w += SCALE1(BUTTON_PADDING * 2); return {0, 0, w, SCALE1(PILL_SIZE)}; } @@ -600,8 +602,8 @@ void MenuList::draw(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rect &d { int w, h; const auto description = cur->getDesc(); - GFX_sizeText(font.tiny, description.c_str(), SCALE1(FONT_SMALL), &w, &h); - GFX_blitTextCPP(font.tiny, description.c_str(), SCALE1(FONT_SMALL), uintToColour(THEME_COLOR4_255), surface, {(dst.x + dst.w - w) / 2, dst.y + dst.h - h, w, h}); + GFX_sizeText(GFX_getFonts()->tiny, description.c_str(), SCALE1(FONT_SMALL), &w, &h); + GFX_blitTextCPP(GFX_getFonts()->tiny, description.c_str(), SCALE1(FONT_SMALL), uintToColour(THEME_COLOR4_255), surface, {(dst.x + dst.w - w) / 2, dst.y + dst.h - h, w, h}); } } @@ -644,22 +646,20 @@ void MenuList::drawList(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rec void MenuList::drawListItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) { SDL_Color text_color = uintToColour(THEME_COLOR4_255); - SDL_Surface *text; // int ox = (dst.w - w) / 2; // if we're centering these (but I don't think we should after seeing it) if (selected) { // move out of conditional if centering int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); GFX_blitPillDarkCPP(ASSET_BUTTON, surface, {dst.x, dst.y, w, SCALE1(BUTTON_SIZE)}); text_color = uintToColour(THEME_COLOR5_255); } - text = TTF_RenderUTF8_Blended(font.small, item.getName().c_str(), text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); - SDL_FreeSurface(text); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->small, item.getName().c_str(), text_color)}; + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); } void MenuList::drawFixed(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rect &dstTitle) @@ -704,7 +704,6 @@ void MenuList::drawFixedItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab { SDL_Color text_color = uintToColour(THEME_COLOR4_255); SDL_Color text_color_value = uintToColour(THEME_COLOR4_255); - SDL_Surface *text; // hack - this should be correlated to max_width int mw = dst.w; @@ -717,7 +716,7 @@ void MenuList::drawFixedItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab if (item.getValue().has_value()) { - text = TTF_RenderUTF8_Blended(font.tiny, item.getLabel().c_str(), text_color_value); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, item.getLabel().c_str(), text_color_value)}; if (item.getType() == ListItemType::Color) { @@ -739,11 +738,10 @@ void MenuList::drawFixedItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab SDL_FillRect(surface, &rect, color); #define COLOR_PADDING 4 // Rerender the label from the live hex value - SDL_FreeSurface(text); char hexLabel[12]; snprintf(hexLabel, sizeof(hexLabel), "0x%08X", rawColor); - text = TTF_RenderUTF8_Blended(font.tiny, hexLabel, text_color_value); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + mw - text->w - SCALE1(OPTION_PADDING + COLOR_PADDING + FONT_TINY), dst.y + ((dst.h - text->h) / 2)}); + text.reset(TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, hexLabel, text_color_value)); + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + mw - text->w - SCALE1(OPTION_PADDING + COLOR_PADDING + FONT_TINY), dst.y + ((dst.h - text->h) / 2)}); } else if(item.getType() == ListItemType::Button) { // dont draw anything for now, could be a button hint later @@ -752,8 +750,7 @@ void MenuList::drawFixedItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab item.drawCustomItem(surface, dst, item, selected); } else // Generic and fallback - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + mw - text->w - SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); - SDL_FreeSurface(text); + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + mw - text->w - SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); } // TODO: blit a black pill on unselected rows (to cover longer item->values?) or truncate longer item->values? @@ -761,15 +758,14 @@ void MenuList::drawFixedItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab { // white pill int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); GFX_blitPillDarkCPP(ASSET_BUTTON, surface, {dst.x, dst.y, w, SCALE1(BUTTON_SIZE)}); text_color = uintToColour(THEME_COLOR5_255); } - text = TTF_RenderUTF8_Blended(font.small, item.getName().c_str(), text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); - SDL_FreeSurface(text); + core::SurfacePtr name{TTF_RenderUTF8_Blended(GFX_getFonts()->small, item.getName().c_str(), text_color)}; + SDL_BlitSurfaceCPP(name.get(), {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + ((dst.h - name->h) / 2)}); } void MenuList::drawInput(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rect &dstTitle) @@ -805,7 +801,6 @@ void MenuList::drawInput(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Re void MenuList::drawInputItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) { SDL_Color text_color = COLOR_WHITE; - SDL_Surface *text; // hack int mw = dst.w; @@ -817,14 +812,13 @@ void MenuList::drawInputItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab // white pill int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); GFX_blitPillDarkCPP(ASSET_BUTTON, surface, {dst.x, dst.y, w, SCALE1(BUTTON_SIZE)}); text_color = COLOR_BLACK; } - text = TTF_RenderUTF8_Blended(font.small, item.getName().c_str(), text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); - SDL_FreeSurface(text); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->small, item.getName().c_str(), text_color)}; + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); if (/*await_input &&*/ selected) { @@ -832,9 +826,8 @@ void MenuList::drawInputItem(SDL_Surface *surface, const SDL_Rect &dst, const Ab } else if (item.getValue().has_value()) { - text = TTF_RenderUTF8_Blended(font.tiny, item.getLabel().c_str(), COLOR_WHITE); // always white - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + mw - text->w - SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); - SDL_FreeSurface(text); + text.reset(TTF_RenderUTF8_Blended(GFX_getFonts()->tiny, item.getLabel().c_str(), COLOR_WHITE)); // always white + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + mw - text->w - SCALE1(OPTION_PADDING), dst.y + ((dst.h - text->h) / 2)}); } } @@ -859,20 +852,19 @@ void MenuList::drawMain(SDL_Surface *surface, const SDL_Rect &dst, const SDL_Rec } else { - GFX_blitMessageCPP(font.large, "Empty folder", surface, dst); + GFX_blitMessageCPP(GFX_getFonts()->large, "Empty folder", surface, dst); } } void MenuList::drawMainItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) { SDL_Color text_color = COLOR_WHITE; - SDL_Surface *text; // TODO: unique item handling (draws grey text) const bool unique = false; char truncated[256]; - int text_width = GFX_truncateText(font.large, item.getName().c_str(), truncated, dst.w, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_truncateText(GFX_getFonts()->large, item.getName().c_str(), truncated, dst.w, SCALE1(BUTTON_PADDING * 2)); int max_width = std::min(dst.w, text_width); if (selected) @@ -884,9 +876,8 @@ void MenuList::drawMainItem(SDL_Surface *surface, const SDL_Rect &dst, const Abs { // TODO: port this over when needed. Its complete spaghetti code... } - text = TTF_RenderUTF8_Blended(font.large, truncated, text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(BUTTON_PADDING), dst.y + ((dst.h - text->h) / 2)}); - SDL_FreeSurface(text); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->large, truncated, text_color)}; + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + SCALE1(BUTTON_PADDING), dst.y + ((dst.h - text->h) / 2)}); } void MenuList::resetAllItems() @@ -955,7 +946,7 @@ static void drawOverlayLocal(SDL_Surface* screen) { SDL_Rect screenRect = {0, 0, screen->w, screen->h}; - GFX_blitMessageCPP(font.medium, overlayMessage, screen, screenRect); + GFX_blitMessageCPP(GFX_getFonts()->medium, overlayMessage, screen, screenRect); if (overlayDismissMode != OverlayDismissMode::None) { if (overlayDismissMode == OverlayDismissMode::DismissOnB) { diff --git a/workspace/all/settings/palettemenu.cpp b/workspace/all/settings/palettemenu.cpp index a40296782..3d59c1269 100644 --- a/workspace/all/settings/palettemenu.cpp +++ b/workspace/all/settings/palettemenu.cpp @@ -2,8 +2,8 @@ extern "C" { #include "config.h" -#include "palette.h" } +#include "palette.h" namespace { diff --git a/workspace/all/settings/settings.cpp b/workspace/all/settings/settings.cpp index 7a3eeda49..3e89991ff 100644 --- a/workspace/all/settings/settings.cpp +++ b/workspace/all/settings/settings.cpp @@ -10,6 +10,8 @@ extern "C" #include "ra_sync.h" } +#include "core/surface.h" + #include #include #include @@ -250,7 +252,7 @@ namespace { BrickPro, SmartPro, SmartProS, - Flip + Flip, }; enum Platform { @@ -536,7 +538,7 @@ int main(int argc, char *argv[]) if(deviceInfo.hasDisplayCal()) { const DisplayCalDefaults defaultDisplayCal = DisplayCal_getDefaultSettings( - deviceInfo.getModel() == DeviceInfo::Brick ? DISPLAYCAL_PRESET_BRICK : + deviceInfo.getModel() == DeviceInfo::Brick ? DISPLAYCAL_PRESET_BRICK : deviceInfo.getModel() == DeviceInfo::BrickPro ? DISPLAYCAL_PRESET_BRICKPRO : deviceInfo.getModel() == DeviceInfo::SmartPro ? DISPLAYCAL_PRESET_SMARTPRO : DISPLAYCAL_PRESET_DEFAULT); displayItems.push_back( @@ -1082,14 +1084,15 @@ int main(int argc, char *argv[]) ctx.menu = new MenuList(MenuItemType::List, "Main", mainItems); - SDL_Surface* bgbmp = IMG_Load(SDCARD_PATH "/bg.png"); - SDL_Surface* convertedbg = SDL_ConvertSurfaceFormat(bgbmp, SDL_PIXELFORMAT_RGB565, 0); - if (convertedbg) { - SDL_FreeSurface(bgbmp); - SDL_Surface* scaled = SDL_CreateRGBSurfaceWithFormat(0, ctx.screen->w, ctx.screen->h, 32, SDL_PIXELFORMAT_RGB565); - GFX_blitScaleToFill(convertedbg, scaled); - bgbmp = scaled; - } + core::SurfacePtr bgbmp{IMG_Load(SDCARD_PATH "/bg.png")}; + { + core::SurfacePtr convertedbg{SDL_ConvertSurfaceFormat(bgbmp.get(), SDL_PIXELFORMAT_RGB565, 0)}; + if (convertedbg) { + core::SurfacePtr scaled{SDL_CreateRGBSurfaceWithFormat(0, ctx.screen->w, ctx.screen->h, 32, SDL_PIXELFORMAT_RGB565)}; + GFX_blitScaleToFill(convertedbg.get(), scaled.get()); + bgbmp = std::move(scaled); // move-assign frees the original IMG_Load surface + } + } // convertedbg freed here (was previously leaked on the success path) // main content (list) // PADDING all around @@ -1127,7 +1130,7 @@ int main(int argc, char *argv[]) GFX_clear(ctx.screen); if(bgbmp) { SDL_Rect image_rect = {0, 0, ctx.screen->w, ctx.screen->h}; - SDL_BlitSurface(bgbmp, NULL, ctx.screen, &image_rect); + SDL_BlitSurface(bgbmp.get(), NULL, ctx.screen, &image_rect); } else { uint32_t bgc = CFG_getColor(COLOR_BACKGROUND); SDL_FillRect(ctx.screen, NULL, SDL_MapRGBA(ctx.screen->format, (bgc >> 24) & 0xFF, (bgc >> 16) & 0xFF, (bgc >> 8) & 0xFF, bgc & 0xFF)); @@ -1146,15 +1149,13 @@ int main(int argc, char *argv[]) if (ctx.appManagesTitle) { char display_name[256]; - int text_width = GFX_truncateText(font.large, "Some title", display_name, max_width, SCALE1(BUTTON_PADDING * 2)); + int text_width = GFX_truncateText(GFX_getFonts()->large, "Some title", display_name, max_width, SCALE1(BUTTON_PADDING * 2)); max_width = MIN(max_width, text_width); - SDL_Surface *text; - text = TTF_RenderUTF8_Blended(font.large, display_name, COLOR_WHITE); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->large, display_name, COLOR_WHITE)}; SDL_Rect target = {SCALE1(PADDING), SCALE1(PADDING), max_width, SCALE1(PILL_SIZE)}; GFX_blitPillLight(ASSET_WHITE_PILL, ctx.screen, &target); - SDL_BlitSurfaceCPP(text, {0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, ctx.screen, {SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); - SDL_FreeSurface(text); + SDL_BlitSurfaceCPP(text.get(), {0, 0, max_width - SCALE1(BUTTON_PADDING * 2), text->h}, ctx.screen, {SCALE1(PADDING + BUTTON_PADDING), SCALE1(PADDING + 4)}); } else { // just set the titleRect and we will pass it on to the list to populate as needed diff --git a/workspace/all/settings/wifimenu.cpp b/workspace/all/settings/wifimenu.cpp index 46f476c0f..6da89948e 100644 --- a/workspace/all/settings/wifimenu.cpp +++ b/workspace/all/settings/wifimenu.cpp @@ -1,6 +1,8 @@ #include "wifimenu.hpp" #include "keyboardprompt.hpp" +#include "core/surface.h" + #include #include @@ -240,7 +242,6 @@ NetworkItem::NetworkItem(WIFI_network n, bool connected, MenuList* submenu) void NetworkItem::drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, const AbstractMenuItem &item, bool selected) const { SDL_Color text_color = uintToColour(THEME_COLOR4_255); - SDL_Surface *text = TTF_RenderUTF8_Blended(font.tiny, item.getLabel().c_str(), COLOR_WHITE); // always white // hack - this should be correlated to max_width int mw = dst.w; @@ -283,13 +284,12 @@ void NetworkItem::drawCustomItem(SDL_Surface *surface, const SDL_Rect &dst, cons { // white pill int w = 0; - TTF_SizeUTF8(font.small, item.getName().c_str(), &w, NULL); + TTF_SizeUTF8(GFX_getFonts()->small, item.getName().c_str(), &w, NULL); w += SCALE1(OPTION_PADDING * 2); GFX_blitPillDarkCPP(ASSET_BUTTON, surface, {dst.x, dst.y, w, SCALE1(BUTTON_SIZE)}); text_color = uintToColour(THEME_COLOR5_255); } - text = TTF_RenderUTF8_Blended(font.small, item.getName().c_str(), text_color); - SDL_BlitSurfaceCPP(text, {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + SCALE1(1)}); - SDL_FreeSurface(text); + core::SurfacePtr text{TTF_RenderUTF8_Blended(GFX_getFonts()->small, item.getName().c_str(), text_color)}; + SDL_BlitSurfaceCPP(text.get(), {}, surface, {dst.x + SCALE1(OPTION_PADDING), dst.y + SCALE1(1)}); } \ No newline at end of file diff --git a/workspace/all/syncsettings/makefile b/workspace/all/syncsettings/makefile index d4f784b9d..c0b898619 100755 --- a/workspace/all/syncsettings/makefile +++ b/workspace/all/syncsettings/makefile @@ -15,17 +15,22 @@ endif ########################################################### TARGET = syncsettings -SOURCE = $(TARGET).c +# Single C++20 translation unit, no shared common/ deps; link through g++. +SOURCE = $(TARGET).cpp CC = $(CROSS_COMPILE)gcc -CFLAGS = $(OPT) -I$(PREFIX_LOCAL)/include -DPLATFORM=\"$(PLATFORM)\" +CXX = $(CROSS_COMPILE)g++ +# Prefer real C++20; fall back to gnu++2a on older toolchains (e.g. the current +# gcc 8.3 tg5040 cross-compiler, which predates the -std=gnu++20 spelling). +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) +CXXFLAGS = $(OPT) -I$(PREFIX_LOCAL)/include -DPLATFORM=\"$(PLATFORM)\" -std=$(CXXSTD) LDFLAGS = -Os -L$(PREFIX_LOCAL)/lib -lmsettings -lrt -ldl -Wl,--gc-sections -s PRODUCT= build/$(PLATFORM)/$(TARGET).elf all: $(PREFIX_LOCAL)/include/msettings.h mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(SOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/all/syncsettings/syncsettings.c b/workspace/all/syncsettings/syncsettings.cpp similarity index 60% rename from workspace/all/syncsettings/syncsettings.c rename to workspace/all/syncsettings/syncsettings.cpp index 2bf4c5934..5f93b918f 100644 --- a/workspace/all/syncsettings/syncsettings.c +++ b/workspace/all/syncsettings/syncsettings.cpp @@ -1,6 +1,10 @@ -#include +#include #include + +// libmsettings' public header is C (no extern "C" guard); include as extern "C". +extern "C" { #include "msettings.h" +} int main (int argc, char *argv[]) { InitSettings(); diff --git a/workspace/desktop/libmsettings/makefile b/workspace/desktop/libmsettings/makefile index f525df7f9..0c91d4f9f 100644 --- a/workspace/desktop/libmsettings/makefile +++ b/workspace/desktop/libmsettings/makefile @@ -11,7 +11,13 @@ TARGET=msettings .PHONY: clean CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ CFLAGS = -I../../all/common +# msettings is C++ but uses no C++ runtime; -fno-exceptions/-fno-rtti keeps the +# object free of libstdc++ so libmsettings.so still links with plain gcc. +# -Wno-write-strings: msettings passes string literals to char* helpers +# (touch/putInt); benign, tolerated elsewhere in the tree. +CXXFLAGS = $(CFLAGS) -std=gnu++2a -fno-exceptions -fno-rtti -Wno-write-strings ifeq ($(PROFILE), 1) CFLAGS += -pg @@ -23,8 +29,8 @@ LDFLAGS = -ldl OPTM=-Ofast -build: - $(CC) -c -Werror -fpic "$(TARGET).c" $(CFLAGS) -Wl,--no-as-needed $(LDFLAGS) +build: + $(CXX) -c -Werror -fpic "$(TARGET).cpp" $(CXXFLAGS) -Wl,--no-as-needed $(LDFLAGS) $(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" $(LDFLAGS) mkdir -p "$(PREFIX_LOCAL)/include" mkdir -p "$(PREFIX_LOCAL)/lib" diff --git a/workspace/desktop/libmsettings/msettings.c b/workspace/desktop/libmsettings/msettings.cpp similarity index 98% rename from workspace/desktop/libmsettings/msettings.c rename to workspace/desktop/libmsettings/msettings.cpp index d327a34d1..37e35c6ab 100644 --- a/workspace/desktop/libmsettings/msettings.c +++ b/workspace/desktop/libmsettings/msettings.cpp @@ -1,4 +1,9 @@ +// This TU implements libmsettings. Wrap its own header in extern "C" so the +// definitions below keep C linkage -- the .so must export unmangled symbols to +// stay ABI-compatible with every consumer (which includes msettings.h as C). +extern "C" { #include "msettings.h" +} #include "displaycal.h" // desktop diff --git a/workspace/desktop/platform/platform.c b/workspace/desktop/platform/platform.cpp similarity index 97% rename from workspace/desktop/platform/platform.c rename to workspace/desktop/platform/platform.cpp index 48091515e..4d3fac126 100644 --- a/workspace/desktop/platform/platform.c +++ b/workspace/desktop/platform/platform.cpp @@ -10,7 +10,11 @@ #include #include +// msettings.h is a C library header without extern "C" guards; wrap it so this +// (now C++) unit resolves libmsettings' symbols with C linkage. +extern "C" { #include +} #include "defines.h" #include "platform.h" diff --git a/workspace/tg5040/keymon/keymon.c b/workspace/tg5040/keymon/keymon.cpp similarity index 97% rename from workspace/tg5040/keymon/keymon.c rename to workspace/tg5040/keymon/keymon.cpp index 0cbf6965b..831f4ae4a 100644 --- a/workspace/tg5040/keymon/keymon.c +++ b/workspace/tg5040/keymon/keymon.cpp @@ -9,7 +9,11 @@ #include #include +// msettings.h is a C library header without extern "C" guards; wrap it so this +// (now C++) unit resolves libmsettings' symbols with C linkage. +extern "C" { #include +} #include #include diff --git a/workspace/tg5040/keymon/makefile b/workspace/tg5040/keymon/makefile index c2752ebd2..6e2e5208a 100755 --- a/workspace/tg5040/keymon/makefile +++ b/workspace/tg5040/keymon/makefile @@ -10,11 +10,12 @@ TARGET = keymon PRODUCT = $(TARGET).elf CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ CFLAGS = -Os -lmsettings -lpthread -lrt -ldl -Wl,--gc-sections -s CFLAGS += -I. -I../../all/common -I../platform/ -I$(PREFIX_LOCAL)/include -L$(PREFIX_LOCAL)/lib -DPLATFORM=\"$(UNION_PLATFORM)\" all: $(PREFIX)/include/msettings.h - $(CC) $(TARGET).c -o $(PRODUCT) $(CFLAGS) + $(CXX) $(TARGET).cpp -o $(PRODUCT) $(CFLAGS) clean: rm -rf $(PRODUCT) diff --git a/workspace/tg5040/libmsettings/makefile b/workspace/tg5040/libmsettings/makefile index e666406ff..51602fed9 100644 --- a/workspace/tg5040/libmsettings/makefile +++ b/workspace/tg5040/libmsettings/makefile @@ -11,15 +11,21 @@ TARGET=msettings .PHONY: clean CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ CFLAGS = -I../../all/common +# displaycal is C++ but uses no C++ runtime; -fno-exceptions/-fno-rtti keeps the +# object free of libstdc++ symbols so libmsettings.so still links with plain gcc. +# -Wno-write-strings: msettings passes string literals to char* helpers +# (touch/putInt); benign, tolerated elsewhere in the tree. +CXXFLAGS = $(CFLAGS) -std=gnu++2a -fno-exceptions -fno-rtti -Wno-write-strings LDFLAGS = -ltinyalsa -ldl -lrt -lm -s OPTM=-Ofast build: - $(CC) -c -Werror -fpic "$(TARGET).c" $(CFLAGS) -Wl,--no-as-needed $(LDFLAGS) - $(CC) -c -Werror -fpic "../../all/common/displaycal.c" $(CFLAGS) -Wl,--no-as-needed $(LDFLAGS) + $(CXX) -c -Werror -fpic "$(TARGET).cpp" $(CXXFLAGS) -Wl,--no-as-needed $(LDFLAGS) + $(CXX) -c -Werror -fpic "../../all/common/displaycal.cpp" $(CXXFLAGS) -Wl,--no-as-needed $(LDFLAGS) $(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" "displaycal.o" $(LDFLAGS) mkdir -p "$(PREFIX_LOCAL)/include" mkdir -p "$(PREFIX_LOCAL)/lib" diff --git a/workspace/tg5040/libmsettings/msettings.c b/workspace/tg5040/libmsettings/msettings.cpp similarity index 98% rename from workspace/tg5040/libmsettings/msettings.c rename to workspace/tg5040/libmsettings/msettings.cpp index 9b1c11edd..f92585c06 100644 --- a/workspace/tg5040/libmsettings/msettings.c +++ b/workspace/tg5040/libmsettings/msettings.cpp @@ -13,7 +13,12 @@ #include #include "displaycal.h" +// This TU implements libmsettings. Wrap its own header in extern "C" so the +// definitions below keep C linkage -- the .so must export unmangled symbols to +// stay ABI-compatible with every consumer (which includes msettings.h as C). +extern "C" { #include "msettings.h" +} /////////////////////////////////////// @@ -344,14 +349,14 @@ void InitSettings(void) { if (shm_fd==-1 && errno==EEXIST) { // already exists // puts("Settings client"); shm_fd = shm_open(SHM_KEY, O_RDWR, 0644); - settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + settings = (Settings*)mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); } else { // host // puts("Settings host"); // keymon is_host = 1; // we created it so set initial size and populate ftruncate(shm_fd, shm_size); - settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + settings = (Settings*)mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); // peek the first int from fd, it's the version int version = peekVersion(SettingsPath); @@ -1212,7 +1217,7 @@ void SetRawBrightness(int val) { // 0 - 255 int fd = open("/dev/disp", O_RDWR); if (fd) { - unsigned long param[4]={0,val,0,0}; + unsigned long param[4]={0,(unsigned long)val,0,0}; ioctl(fd, DISP_LCD_SET_BRIGHTNESS, ¶m); close(fd); } diff --git a/workspace/tg5040/platform/platform.c b/workspace/tg5040/platform/platform.cpp similarity index 52% rename from workspace/tg5040/platform/platform.c rename to workspace/tg5040/platform/platform.cpp index 0ef532ff5..baedc908c 100644 --- a/workspace/tg5040/platform/platform.c +++ b/workspace/tg5040/platform/platform.cpp @@ -11,7 +11,11 @@ #include #include +// msettings.h is a C library header without extern "C" guards; wrap it so this +// (now C++) unit resolves libmsettings' symbols with C linkage. +extern "C" { #include +} #include "defines.h" #include "platform.h" @@ -26,17 +30,86 @@ int is_brick = 0; int is_brickpro = 0; -void PLAT_initPlatform(void) { - // TODO: replace with something that doesnt bleed out of tg5040 scope - char *device = getenv("DEVICE"); + +// Shared tg5040 hardware node paths -- the same on Brick and Smart Pro. These +// are exactly the seams a different platform redefines (tg5050 would use +// cpu4/thermal_zone5/gpio236 here), so they belong to the descriptor. +#define TG5040_CPU_SPEED_PATH "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq" +#define TG5040_GPU_TEMP_PATH "/sys/devices/virtual/thermal/thermal_zone2/temp" +#define TG5040_RUMBLE_GPIO "/sys/class/gpio/gpio227/value" + +// The two tg5040 models as data. They differ only in screen geometry, menu +// layout, and a few joystick codes; the hardware paths below are shared. +static const DeviceDescriptor TG5040_BRICK = { + .scale = 3, .width = 1024, .height = 768, + .main_row_count = 7, .quick_switcher_count = 3, .padding = 5, + .joy_l3 = 9, .joy_r3 = 10, + .joy_l4 = JOY_NA, .joy_r4 = JOY_NA, .joy_menu_alt = JOY_NA, + .joy_plus = 14, .joy_minus = 13, + .cpu_speed_path = TG5040_CPU_SPEED_PATH, + .gpu_temp_path = TG5040_GPU_TEMP_PATH, + .rumble_gpio_path = TG5040_RUMBLE_GPIO, + .gpu_speed_fixed = 660, + .gpu_freq_path = NULL, + .gpu_usage_path = NULL, +}; +static const DeviceDescriptor TG5040_SMART_PRO = { + .scale = 2, .width = 1280, .height = 720, + .main_row_count = 10, .quick_switcher_count = 4, .padding = 10, + .joy_l3 = JOY_NA, .joy_r3 = JOY_NA, + .joy_l4 = JOY_NA, .joy_r4 = JOY_NA, .joy_menu_alt = JOY_NA, + .joy_plus = 128, .joy_minus = 129, + .cpu_speed_path = TG5040_CPU_SPEED_PATH, + .gpu_temp_path = TG5040_GPU_TEMP_PATH, + .rumble_gpio_path = TG5040_RUMBLE_GPIO, + .gpu_speed_fixed = 660, + .gpu_freq_path = NULL, + .gpu_usage_path = NULL, +}; +// TrimUI Brick Pro: a distinct tg5040 device (is_brickpro) sharing the Brick's +// 1024x768 screen and menu layout, but adding the Smart Pro's dual analog +// thumbsticks plus two rear trigger buttons (L4/R4, joy codes 11/12) and an +// alternate menu button (joy 15). It drives five addressable LED zones (F1, F2, +// top bar, joysticks, rear triggers) and carries its own brightness curve and +// display-cal preset (see libmsettings/msettings.c). UNVERIFIED: model string +// and joystick codes follow upstream #766; confirm against actual hardware. +static const DeviceDescriptor TG5040_BRICK_PRO = { + .scale = 3, .width = 1024, .height = 768, + .main_row_count = 7, .quick_switcher_count = 3, .padding = 5, + .joy_l3 = 9, .joy_r3 = 10, + .joy_l4 = 11, .joy_r4 = 12, .joy_menu_alt = 15, + .joy_plus = 14, .joy_minus = 13, + .cpu_speed_path = TG5040_CPU_SPEED_PATH, + .gpu_temp_path = TG5040_GPU_TEMP_PATH, + .rumble_gpio_path = TG5040_RUMBLE_GPIO, + .gpu_speed_fixed = 660, + .gpu_freq_path = NULL, + .gpu_usage_path = NULL, +}; +// Default to Smart Pro so the geometry/button macros are valid even before +// init runs; resolveDeviceModel() refines it once DEVICE is known. +const DeviceDescriptor* deviceModel = &TG5040_SMART_PRO; + +// Single source of truth for which model we're on. Sets is_brick and is_brickpro +// (Brick Pro is a distinct device, not part of the Brick family) and points +// deviceModel at the active descriptor. +static void resolveDeviceModel(void) { + const char* device = getenv("DEVICE"); is_brick = exactMatch("brick", device); is_brickpro = exactMatch("brickpro", device); + if (is_brickpro) deviceModel = &TG5040_BRICK_PRO; + else if (is_brick) deviceModel = &TG5040_BRICK; + else deviceModel = &TG5040_SMART_PRO; +} + +void PLAT_initPlatform(void) { + resolveDeviceModel(); } static SDL_Joystick **joysticks = NULL; static int num_joysticks = 0; void PLAT_initInput(void) { - PLAT_initPlatform(); + resolveDeviceModel(); if(SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) LOG_error("Failed initializing joysticks: %s\n", SDL_GetError()); num_joysticks = SDL_NumJoysticks(); @@ -49,102 +122,21 @@ void PLAT_initInput(void) { } } -void PLAT_quitInput(void) { - if (joysticks) { - for (int i = 0; i < num_joysticks; i++) { - if (SDL_JoystickGetAttached(joysticks[i])) { - LOG_info("Closing joystick %d: %s\n", i, SDL_JoystickName(joysticks[i])); - SDL_JoystickClose(joysticks[i]); - } - } - free(joysticks); - joysticks = NULL; - num_joysticks = 0; - } - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); -} - -void PLAT_updateInput(const SDL_Event *event) { - switch (event->type) { - case SDL_JOYDEVICEADDED: { - int device_index = event->jdevice.which; - SDL_Joystick *new_joy = SDL_JoystickOpen(device_index); - if (new_joy) { - joysticks = realloc(joysticks, sizeof(SDL_Joystick *) * (num_joysticks + 1)); - joysticks[num_joysticks++] = new_joy; - LOG_info("Joystick added at index %d: %s\n", device_index, SDL_JoystickName(new_joy)); - } else { - LOG_error("Failed to open added joystick at index %d: %s\n", device_index, SDL_GetError()); - } - break; - } - - case SDL_JOYDEVICEREMOVED: { - SDL_JoystickID removed_id = event->jdevice.which; - for (int i = 0; i < num_joysticks; ++i) { - if (SDL_JoystickInstanceID(joysticks[i]) == removed_id) { - LOG_info("Joystick removed: %s\n", SDL_JoystickName(joysticks[i])); - SDL_JoystickClose(joysticks[i]); - - // Shift down the remaining entries - for (int j = i; j < num_joysticks - 1; ++j) - joysticks[j] = joysticks[j + 1]; - num_joysticks--; - - if (num_joysticks == 0) { - free(joysticks); - joysticks = NULL; - } else { - joysticks = realloc(joysticks, sizeof(SDL_Joystick *) * num_joysticks); - } - break; - } - } - break; - } - - default: - break; - } -} -void PLAT_getBatteryStatus(int* is_charging, int* charge) { - PLAT_getBatteryStatusFine(is_charging, charge); - // worry less about battery and more about the game you're playing - if (*charge>80) *charge = 100; - else if (*charge>60) *charge = 80; - else if (*charge>40) *charge = 60; - else if (*charge>20) *charge = 40; - else if (*charge>10) *charge = 20; - else *charge = 10; -} -void PLAT_getCPUTemp() { - perf.cpu_temp = getInt("/sys/devices/virtual/thermal/thermal_zone0/temp")/1000; -} -void PLAT_getCPUSpeed() -{ - perf.cpu_speed = getInt("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq")/1000; -} -void PLAT_getGPUTemp() { - perf.gpu_temp = getInt("/sys/devices/virtual/thermal/thermal_zone2/temp")/1000; -} -void PLAT_getGPUSpeed() { - perf.gpu_speed = 660; // MHz -} static struct WIFI_connection connection = { .valid = false, + .ssid = {0}, + .ip = {0}, .freq = -1, + .rssi = -1, .link_speed = -1, .noise = -1, - .rssi = -1, - .ip = {0}, - .ssid = {0}, }; static inline void connection_reset(struct WIFI_connection *connection_info) @@ -160,34 +152,7 @@ static inline void connection_reset(struct WIFI_connection *connection_info) static bool bluetoothConnected = false; -void PLAT_getNetworkStatus(int* is_online) -{ - if(WIFI_enabled()) - WIFI_connectionInfo(&connection); - else - connection_reset(&connection); - - if(is_online) - *is_online = (connection.valid && connection.ssid[0] != '\0'); - - if(BT_enabled()) { - bluetoothConnected = PLAT_bluetoothConnected(); - } - else - bluetoothConnected = false; -} -void PLAT_getBatteryStatusFine(int *is_charging, int *charge) -{ - if(is_charging) { - int time_to_full = getInt("/sys/class/power_supply/axp2202-battery/time_to_full_now"); - int charger_present = getInt("/sys/class/power_supply/axp2202-usb/online"); - *is_charging = (charger_present == 1) && (time_to_full > 0); - } - if(charge) { - *charge = getInt("/sys/class/power_supply/axp2202-battery/capacity"); - } -} int PLAT_isUSBConnected(void) { @@ -232,30 +197,7 @@ void PLAT_enableBacklight(int enable) { } } -void PLAT_powerOff(int reboot) { - if (CFG_getHaptics()) { - VIB_singlePulse(VIB_bootStrength, VIB_bootDuration_ms); - } - system("rm -f /tmp/nextui_exec && sync"); - sleep(2); - - SetRawVolume(MUTE_VOLUME_RAW); - PLAT_enableBacklight(0); - SND_quit(); - VIB_quit(); - PWR_quit(); - GFX_quit(); - - system("cat /dev/zero > /dev/fb0 2>/dev/null"); - if(reboot > 0) - touch("/tmp/reboot"); - else - touch("/tmp/poweroff"); - sync(); - exit(0); -} -int PLAT_supportsDeepSleep(void) { return 1; } /////////////////////////////// @@ -277,77 +219,12 @@ static pthread_mutex_t currentcpuinfo; // a roling average for the display values of about 2 frames, otherwise they are unreadable jumping too fast up and down and stuff to read #define ROLLING_WINDOW 120 -void *PLAT_cpu_monitor(void *arg) { - if (!Perf_tryBeginCPUMonitor()) return NULL; - - double prev_real_time = get_time_sec(); - double prev_cpu_time = get_process_cpu_time_sec(); - - double cpu_usage_history[ROLLING_WINDOW] = {0}; - int history_index = 0; - int history_count = 0; - - while (Perf_isCPUMonitorEnabled()) { - double curr_real_time = get_time_sec(); - double curr_cpu_time = get_process_cpu_time_sec(); - - double elapsed_real_time = curr_real_time - prev_real_time; - double elapsed_cpu_time = curr_cpu_time - prev_cpu_time; - - if (elapsed_real_time > 0) { - double cpu_usage = (elapsed_cpu_time / elapsed_real_time) * 100.0; - pthread_mutex_lock(¤tcpuinfo); - - cpu_usage_history[history_index] = cpu_usage; - history_index = (history_index + 1) % ROLLING_WINDOW; - if (history_count < ROLLING_WINDOW) history_count++; - - double sum_cpu_usage = 0; - for (int i = 0; i < history_count; i++) sum_cpu_usage += cpu_usage_history[i]; - perf.cpu_usage = sum_cpu_usage / history_count; - - pthread_mutex_unlock(¤tcpuinfo); - } - - prev_real_time = curr_real_time; - prev_cpu_time = curr_cpu_time; - usleep(100000); - } - - Perf_endCPUMonitor(); - return NULL; -} - -void PLAT_setCPUSpeed(int speed) { - const char* mode; - switch (speed) { - case CPU_SPEED_AUTO: mode = "auto"; break; - case CPU_SPEED_PERFORMANCE: mode = "performance"; break; - case CPU_SPEED_POWERSAVE: mode = "powersave"; break; - default: return; - } - - const char* system_path = getenv("SYSTEM_PATH"); - if (!system_path) { - LOG_info("WARNING: SYSTEM_PATH not set, cannot run governor script\n"); - return; - } - char cmd[512]; - int n = snprintf(cmd, sizeof(cmd), "sh \"%s/bin/governor.sh\" \"%s\"", system_path, mode); - if (n < 0 || n >= (int)sizeof(cmd)) { - LOG_info("WARNING: SYSTEM_PATH too long for governor script path\n"); - return; - } - int ret = system(cmd); - if (ret != 0) LOG_info("WARNING: governor script exited with status %d for mode '%s'\n", ret, mode); -} #define MAX_STRENGTH 0xFFFF #define MIN_VOLTAGE 500000 // Brick Pro has a 3.3V motor driver, but its getting very annoying on higher rumble strengths, so we limit it to 2.5V for now. #define MAX_VOLTAGE (is_brickpro ? 2500000 : 3300000) -#define RUMBLE_PATH "/sys/class/gpio/gpio227/value" #define RUMBLE_VOLTAGE_PATH "/sys/class/motor/voltage" void PLAT_setRumble(int strength) { @@ -363,20 +240,10 @@ void PLAT_setRumble(int strength) { // enable rumble - removed the FN switch disabling haptics // did not make sense - putInt(RUMBLE_PATH, (strength) ? 1 : 0); + putInt(deviceModel->rumble_gpio_path, (strength) ? 1 : 0); } -int PLAT_pickSampleRate(int requested, int max) { - // bluetooth: allow limiting the maximum to improve compatibility - if(PLAT_bluetoothConnected()) - return MIN(requested, CFG_getBluetoothSamplingrateLimit()); - return MIN(requested, max); -} - -void PLAT_overrideMute(int mute) { - putInt("/sys/class/speaker/mute", mute); -} char* PLAT_getModel(void) { char* model = getenv("TRIMUI_MODEL"); @@ -384,38 +251,16 @@ char* PLAT_getModel(void) { return "Trimui Smart Pro"; } -void PLAT_getOsVersionInfo(char* output_str, size_t max_len) -{ - return getFile("/etc/version", output_str,max_len); -} -bool PLAT_btIsConnected(void) -{ - return bluetoothConnected; -} -ConnectionStrength PLAT_connectionStrength(void) { - if(!WIFI_enabled() || !connection.valid || connection.rssi == -1) - return SIGNAL_STRENGTH_OFF; - else if (connection.rssi == 0) - return SIGNAL_STRENGTH_DISCONNECTED; - else if (connection.rssi >= -60) - return SIGNAL_STRENGTH_HIGH; - else if (connection.rssi >= -70) - return SIGNAL_STRENGTH_MED; - else - return SIGNAL_STRENGTH_LOW; -} void PLAT_initDefaultLeds() { - PLAT_initPlatform(); + resolveDeviceModel(); if(is_brickpro) { lightsDefault[0] = (LightSettings) {"FN 1 key","f1",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[1] = (LightSettings) {"FN 2 key","f2",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[2] = (LightSettings) {"Topbar","m",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[3] = (LightSettings) {"Joysticks L/R","lr",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; - //lightsDefault[4] = (LightSettings) {"Joystick L","l",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; - //lightsDefault[5] = (LightSettings) {"Joystick R","r",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[4] = (LightSettings) {"Triggers L/R","rear",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; } else if(is_brick) { @@ -423,9 +268,8 @@ void PLAT_initDefaultLeds() { lightsDefault[1] = (LightSettings) {"FN 2 key","f2",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[2] = (LightSettings) {"Topbar","m",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[3] = (LightSettings) {"L/R triggers","lr",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; - //lightsDefault[3] = (LightSettings) {"L trigger","l",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; - //lightsDefault[3] = (LightSettings) {"R trigger","r",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; - } else { + } + else { lightsDefault[0] = (LightSettings) {"Joystick L","l",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[1] = (LightSettings) {"Joystick R","r",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; lightsDefault[2] = (LightSettings) {"Logo","m",4,1000,100,0xFFFFFF,0xFFFFFF,0,{},1,100,0}; @@ -433,6 +277,8 @@ void PLAT_initDefaultLeds() { } void PLAT_initLeds(LightSettings *lights) { + resolveDeviceModel(); + PLAT_initDefaultLeds(); FILE *file; if(is_brick) { @@ -526,22 +372,10 @@ void PLAT_initLeds(LightSettings *lights) } } -// Brick: Brightness for "m" (top bar) -// Brick Pro: Brightness for "m" (top bar)? -// Smart Pro/S: Global brightness for all #define LED_PATH1 "/sys/class/led_anim/max_scale" -// Brick: Brightness for "lr" (triggers) -// Brick Pro: Brightness for "lr" (joysticks)? -// Smart Pro/S: n/a #define LED_PATH2 "/sys/class/led_anim/max_scale_lr" -// Brick: Brightness for "f1f2" (buttons) -// Brick Pro: Brightness for "f1f2" (buttons)? -// Smart Pro/S: n/a #define LED_PATH3 "/sys/class/led_anim/max_scale_f1f2" -// Brick: n/a -// Brick Pro: Brightness for "rear" (triggers) -// Smart Pro/S: n/a -#define LED_PATH4 "/sys/class/led_anim/max_scale_rear" +#define LED_PATH4 "/sys/class/led_anim/max_scale_rear" void PLAT_setLedInbrightness(LightSettings *led) { @@ -595,62 +429,9 @@ void PLAT_setLedBrightness(LightSettings *led) } } } -void PLAT_setLedEffect(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%i\n", led->effect); - fclose(file); - } -} -void PLAT_setLedEffectCycles(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_cycles_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%i\n", led->cycles); - fclose(file); - } -} -void PLAT_setLedEffectSpeed(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_duration_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%i\n", led->speed); - fclose(file); - } -} -void PLAT_setLedColor(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_rgb_hex_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%06X\n", led->color1); - fclose(file); - } -} ////////////////////////////////////////////// -bool PLAT_canTurbo(void) { return true; } #define INPUTD_PATH "/tmp/trimui_inputd" @@ -685,37 +466,10 @@ int toggle_file(const char *path) { } } -int PLAT_toggleTurbo(int btn_id) -{ - // avoid extra file IO on each call - static int initialized = 0; - if (!initialized) { - mkdir(INPUTD_PATH, 0755); - initialized = 1; - } - - for (int i = 0; turbo_mapping[i].path; i++) { - if (turbo_mapping[i].brn_id == btn_id) { - return toggle_file(turbo_mapping[i].path); - } - } - return 0; -} -void PLAT_clearTurbo() { - for (int i = 0; turbo_mapping[i].path; i++) { - unlink(turbo_mapping[i].path); - } -} ////////////////////////////////////////////// -int PLAT_setDateTime(int y, int m, int d, int h, int i, int s) { - char cmd[512]; - sprintf(cmd, "date -s '%d-%d-%d %d:%d:%d'; hwclock -u -w", y,m,d,h,i,s); - system(cmd); - return 0; // why does this return an int? -} #define MAX_LINE_LENGTH 200 #define ZONE_PATH "/usr/share/zoneinfo" @@ -728,78 +482,18 @@ int compare_timezones(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } -void PLAT_initTimezones() { - if (cached_tz_count != -1) { // Already initialized - return; - } - - FILE *file = fopen(ZONE_TAB_PATH, "r"); - if (!file) { - LOG_info("Error opening file %s\n", ZONE_TAB_PATH); - return; - } - - char line[MAX_LINE_LENGTH]; - cached_tz_count = 0; - - while (fgets(line, sizeof(line), file)) { - // Skip comment lines - if (line[0] == '#' || strlen(line) < 3) { - continue; - } - - char *token = strtok(line, "\t"); // Skip country code - if (!token) continue; - - token = strtok(NULL, "\t"); // Skip latitude/longitude - if (!token) continue; - - token = strtok(NULL, "\t\n"); // Extract timezone - if (!token) continue; - - // Check for duplicates before adding - int duplicate = 0; - for (int i = 0; i < cached_tz_count; i++) { - if (strcmp(cached_timezones[i], token) == 0) { - duplicate = 1; - break; - } - } - - if (!duplicate && cached_tz_count < MAX_TIMEZONES) { - strncpy(cached_timezones[cached_tz_count], token, MAX_TZ_LENGTH - 1); - cached_timezones[cached_tz_count][MAX_TZ_LENGTH - 1] = '\0'; // Ensure null-termination - cached_tz_count++; - } - } - - fclose(file); - - // Sort the list alphabetically - qsort(cached_timezones, cached_tz_count, MAX_TZ_LENGTH, compare_timezones); -} -void PLAT_getTimezones(char timezones[MAX_TIMEZONES][MAX_TZ_LENGTH], int *tz_count) { - if (cached_tz_count == -1) { - LOG_warn("Error: Timezones not initialized. Call PLAT_initTimezones first.\n"); - *tz_count = 0; - return; - } - - memcpy(timezones, cached_timezones, sizeof(cached_timezones)); - *tz_count = cached_tz_count; -} char *PLAT_getCurrentTimezone() { char *output = (char *)malloc(256); if (!output) { - return false; + return NULL; } FILE *fp = popen("uci get system.@system[0].zonename", "r"); if (!fp) { free(output); - return false; + return NULL; } fgets(output, 256, fp); pclose(fp); @@ -879,6 +573,7 @@ void PLAT_setNetworkTimeSync(bool on) { ///////////////////////// // We use the generic video implementation here +#include "generic_platform.c" #include "generic_video.c" ///////////////////////// diff --git a/workspace/tg5040/platform/platform.h b/workspace/tg5040/platform/platform.h index 88a0250d1..0af9a8756 100644 --- a/workspace/tg5040/platform/platform.h +++ b/workspace/tg5040/platform/platform.h @@ -14,6 +14,8 @@ extern int is_brick; extern int is_brickpro; +#include "device.h" + /////////////////////////////// #define BUTTON_UP BUTTON_NA @@ -65,8 +67,8 @@ extern int is_brickpro; #define CODE_R2 CODE_NA #define CODE_L3 CODE_NA #define CODE_R3 CODE_NA -#define CODE_L4 CODE_NA -#define CODE_R4 CODE_NA +#define CODE_L4 CODE_NA +#define CODE_R4 CODE_NA #define CODE_MENU CODE_NA #define CODE_POWER 102 @@ -94,16 +96,16 @@ extern int is_brickpro; #define JOY_R1 5 #define JOY_L2 JOY_NA #define JOY_R2 JOY_NA -#define JOY_L3 (is_brick||is_brickpro?9:JOY_NA) -#define JOY_R3 (is_brick||is_brickpro?10:JOY_NA) -#define JOY_L4 (is_brickpro?11:JOY_NA) -#define JOY_R4 (is_brickpro?12:JOY_NA) +#define JOY_L3 (deviceModel->joy_l3) +#define JOY_R3 (deviceModel->joy_r3) +#define JOY_L4 (deviceModel->joy_l4) +#define JOY_R4 (deviceModel->joy_r4) #define JOY_MENU 8 -#define JOY_MENU_ALT (is_brickpro?15:JOY_NA) +#define JOY_MENU_ALT (deviceModel->joy_menu_alt) #define JOY_POWER 102 -#define JOY_PLUS (is_brick||is_brickpro?14:128) -#define JOY_MINUS (is_brick||is_brickpro?13:129) +#define JOY_PLUS (deviceModel->joy_plus) +#define JOY_MINUS (deviceModel->joy_minus) /////////////////////////////// // USER-ASSIGNABLE BUTTONS @@ -137,9 +139,9 @@ extern int is_brickpro; /////////////////////////////// -#define FIXED_SCALE (is_brick||is_brickpro?3:2) -#define FIXED_WIDTH (is_brick||is_brickpro?1024:1280) -#define FIXED_HEIGHT (is_brick||is_brickpro?768:720) +#define FIXED_SCALE (deviceModel->scale) +#define FIXED_WIDTH (deviceModel->width) +#define FIXED_HEIGHT (deviceModel->height) #define FIXED_BPP 2 #define FIXED_DEPTH (FIXED_BPP * 8) #define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) @@ -147,9 +149,9 @@ extern int is_brickpro; /////////////////////////////// -#define MAIN_ROW_COUNT (is_brick||is_brickpro ? 7 : 10) -#define QUICK_SWITCHER_COUNT (is_brick||is_brickpro ? 3 : 4) -#define PADDING (is_brick||is_brickpro ? 5 : 10) +#define MAIN_ROW_COUNT (deviceModel->main_row_count) +#define QUICK_SWITCHER_COUNT (deviceModel->quick_switcher_count) +#define PADDING (deviceModel->padding) /////////////////////////////// diff --git a/workspace/tg5040/poweroff_next/makefile b/workspace/tg5040/poweroff_next/makefile index 52d3e63ac..f1a9029a6 100644 --- a/workspace/tg5040/poweroff_next/makefile +++ b/workspace/tg5040/poweroff_next/makefile @@ -20,19 +20,27 @@ include ../platform/makefile.env TARGET = poweroff_next INCDIR = -I. -I../../all/common -I../platform -SOURCE = $(TARGET).c ../../all/common/utils.c ../../all/common/config.c +# poweroff_next and the common/ utils + config units are all C++20; compile them +# and link through g++. +CSOURCE = -c $(TARGET).cpp +CXXSOURCE = ../../all/common/utils.cpp ../../all/common/config.cpp CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ +CXXSTD := $(shell printf 'int main(){return 0;}' | $(CXX) -std=gnu++20 -x c++ - -o /dev/null >/dev/null 2>&1 && echo gnu++20 || echo gnu++2a) # Override CFLAGS and LDFLAGS to not include SDL2 dependencies CFLAGS = $(OPT) -g -std=gnu11 -Wall -Wextra CFLAGS += $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" +CXXFLAGS = $(OPT) -g $(INCDIR) -DPLATFORM=\"$(PLATFORM)\" -std=$(CXXSTD) LDFLAGS = -lpthread -ldl PRODUCT = build/$(PLATFORM)/$(TARGET).elf all: mkdir -p build/$(PLATFORM) - $(CC) $(SOURCE) -o $(PRODUCT) $(CFLAGS) $(LDFLAGS) + $(CXX) $(CSOURCE) $(CXXFLAGS) + mv $(TARGET).o build/$(PLATFORM) + $(CXX) build/$(PLATFORM)/$(TARGET).o $(CXXSOURCE) -o $(PRODUCT) $(CXXFLAGS) $(LDFLAGS) -lstdc++ clean: rm -f $(PRODUCT) diff --git a/workspace/tg5040/poweroff_next/poweroff_next.c b/workspace/tg5040/poweroff_next/poweroff_next.cpp similarity index 100% rename from workspace/tg5040/poweroff_next/poweroff_next.c rename to workspace/tg5040/poweroff_next/poweroff_next.cpp diff --git a/workspace/tg5040/rfkill/makefile b/workspace/tg5040/rfkill/makefile index 55c8ade45..b0cecbb32 100644 --- a/workspace/tg5040/rfkill/makefile +++ b/workspace/tg5040/rfkill/makefile @@ -5,10 +5,11 @@ endif TARGET = rfkill PRODUCT = $(TARGET).elf -CC = $(CROSS_COMPILE)gcc +CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ FLAGS = -Os -lrt -ldl -Wl,--gc-sections -s all: - $(CC) $(TARGET).c -o $(PRODUCT) $(FLAGS) + $(CXX) $(TARGET).cpp -o $(PRODUCT) $(FLAGS) clean: rm -rf $(PRODUCT) \ No newline at end of file diff --git a/workspace/tg5040/rfkill/rfkill.c b/workspace/tg5040/rfkill/rfkill.cpp similarity index 100% rename from workspace/tg5040/rfkill/rfkill.c rename to workspace/tg5040/rfkill/rfkill.cpp diff --git a/workspace/tg5050/keymon/keymon.c b/workspace/tg5050/keymon/keymon.cpp similarity index 96% rename from workspace/tg5050/keymon/keymon.c rename to workspace/tg5050/keymon/keymon.cpp index 5135397b3..b79d94ade 100644 --- a/workspace/tg5050/keymon/keymon.c +++ b/workspace/tg5050/keymon/keymon.cpp @@ -9,7 +9,11 @@ #include #include +// msettings.h is a C library header without extern "C" guards; wrap it so this +// (now C++) unit resolves libmsettings' symbols with C linkage. +extern "C" { #include +} #include #include diff --git a/workspace/tg5050/keymon/makefile b/workspace/tg5050/keymon/makefile index c2752ebd2..6e2e5208a 100755 --- a/workspace/tg5050/keymon/makefile +++ b/workspace/tg5050/keymon/makefile @@ -10,11 +10,12 @@ TARGET = keymon PRODUCT = $(TARGET).elf CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ CFLAGS = -Os -lmsettings -lpthread -lrt -ldl -Wl,--gc-sections -s CFLAGS += -I. -I../../all/common -I../platform/ -I$(PREFIX_LOCAL)/include -L$(PREFIX_LOCAL)/lib -DPLATFORM=\"$(UNION_PLATFORM)\" all: $(PREFIX)/include/msettings.h - $(CC) $(TARGET).c -o $(PRODUCT) $(CFLAGS) + $(CXX) $(TARGET).cpp -o $(PRODUCT) $(CFLAGS) clean: rm -rf $(PRODUCT) diff --git a/workspace/tg5050/libmsettings/makefile b/workspace/tg5050/libmsettings/makefile index 804df7d52..348b8a2b2 100644 --- a/workspace/tg5050/libmsettings/makefile +++ b/workspace/tg5050/libmsettings/makefile @@ -11,15 +11,21 @@ TARGET=msettings .PHONY: clean CC = $(CROSS_COMPILE)gcc +CXX = $(CROSS_COMPILE)g++ CFLAGS = -I../../all/common +# msettings is C++ but uses no C++ runtime; -fno-exceptions/-fno-rtti keeps the +# object free of libstdc++ so libmsettings.so still links with plain gcc. +# -Wno-write-strings: msettings passes string literals to char* helpers +# (touch/putInt); benign, tolerated elsewhere in the tree. +CXXFLAGS = $(CFLAGS) -std=gnu++2a -fno-exceptions -fno-rtti -Wno-write-strings LDFLAGS = -ltinyalsa -ldl -lrt -lm -s #LDFLAGS = -ldl -lrt -s OPTM=-Ofast -build: - $(CC) -c -Werror -fpic "$(TARGET).c" $(CFLAGS) -Wl,--no-as-needed $(LDFLAGS) +build: + $(CXX) -c -Werror -fpic "$(TARGET).cpp" $(CXXFLAGS) -Wl,--no-as-needed $(LDFLAGS) $(CC) -shared -o "lib$(TARGET).so" "$(TARGET).o" $(LDFLAGS) mkdir -p "$(PREFIX_LOCAL)/include" mkdir -p "$(PREFIX_LOCAL)/lib" diff --git a/workspace/tg5050/libmsettings/msettings.c b/workspace/tg5050/libmsettings/msettings.cpp similarity index 98% rename from workspace/tg5050/libmsettings/msettings.c rename to workspace/tg5050/libmsettings/msettings.cpp index a8ddea691..15bbc1d25 100644 --- a/workspace/tg5050/libmsettings/msettings.c +++ b/workspace/tg5050/libmsettings/msettings.cpp @@ -12,7 +12,12 @@ #include #include +// This TU implements libmsettings. Wrap its own header in extern "C" so the +// definitions below keep C linkage -- the .so must export unmangled symbols to +// stay ABI-compatible with every consumer (which includes msettings.h as C). +extern "C" { #include "msettings.h" +} #include "displaycal.h" /////////////////////////////////////// @@ -195,14 +200,14 @@ void InitSettings(void) { if (shm_fd==-1 && errno==EEXIST) { // already exists // puts("Settings client"); shm_fd = shm_open(SHM_KEY, O_RDWR, 0644); - settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + settings = (Settings*)mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); } else { // host // puts("Settings host"); // keymon is_host = 1; // we created it so set initial size and populate ftruncate(shm_fd, shm_size); - settings = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); + settings = (Settings*)mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); // peek the first int from fd, it's the version int version = peekVersion(SettingsPath); diff --git a/workspace/tg5050/platform/platform.c b/workspace/tg5050/platform/platform.cpp similarity index 51% rename from workspace/tg5050/platform/platform.c rename to workspace/tg5050/platform/platform.cpp index 4cd03c15e..279ac04b2 100644 --- a/workspace/tg5050/platform/platform.c +++ b/workspace/tg5050/platform/platform.cpp @@ -13,10 +13,36 @@ #include #include +// msettings.h is a C library header without extern "C" guards; wrap it so this +// (now C++) unit resolves libmsettings' symbols with C linkage. +extern "C" { #include +} #include "defines.h" #include "platform.h" + +// Smart Pro S device descriptor. Single model -- no runtime variant selector +// (unlike tg5040, which picks Brick vs Smart Pro from DEVICE=brick). +#define TG5050_CPU_SPEED_PATH "/sys/devices/system/cpu/cpu4/cpufreq/scaling_cur_freq" +#define TG5050_GPU_TEMP_PATH "/sys/devices/virtual/thermal/thermal_zone5/temp" +#define TG5050_GPU_FREQ_PATH "/sys/devices/platform/soc@3000000/1800000.gpu/devfreq/1800000.gpu/cur_freq" +#define TG5050_GPU_USAGE_PATH "/sys/devices/platform/soc@3000000/1800000.gpu/sunxi_gpu/sunxi_gpu_freq" +#define TG5050_RUMBLE_GPIO "/sys/class/gpio/gpio236/value" +static const DeviceDescriptor TG5050 = { + .scale = 2, .width = 1280, .height = 720, + .main_row_count = 10, .quick_switcher_count = 4, .padding = 10, + .joy_l3 = 9, .joy_r3 = 10, + .joy_l4 = JOY_NA, .joy_r4 = JOY_NA, .joy_menu_alt = JOY_NA, + .joy_plus = 128, .joy_minus = 129, + .cpu_speed_path = TG5050_CPU_SPEED_PATH, + .gpu_temp_path = TG5050_GPU_TEMP_PATH, + .rumble_gpio_path = TG5050_RUMBLE_GPIO, + .gpu_speed_fixed = 0, + .gpu_freq_path = TG5050_GPU_FREQ_PATH, + .gpu_usage_path = TG5050_GPU_USAGE_PATH, +}; +const DeviceDescriptor* deviceModel = &TG5050; #include "api.h" #include "utils.h" @@ -41,95 +67,14 @@ void PLAT_initInput(void) { } } -void PLAT_quitInput(void) { - if (joysticks) { - for (int i = 0; i < num_joysticks; i++) { - if (SDL_JoystickGetAttached(joysticks[i])) { - LOG_info("Closing joystick %d: %s\n", i, SDL_JoystickName(joysticks[i])); - SDL_JoystickClose(joysticks[i]); - } - } - free(joysticks); - joysticks = NULL; - num_joysticks = 0; - } - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); -} - -void PLAT_updateInput(const SDL_Event *event) { - switch (event->type) { - case SDL_JOYDEVICEADDED: { - int device_index = event->jdevice.which; - SDL_Joystick *new_joy = SDL_JoystickOpen(device_index); - if (new_joy) { - joysticks = realloc(joysticks, sizeof(SDL_Joystick *) * (num_joysticks + 1)); - joysticks[num_joysticks++] = new_joy; - LOG_info("Joystick added at index %d: %s\n", device_index, SDL_JoystickName(new_joy)); - } else { - LOG_error("Failed to open added joystick at index %d: %s\n", device_index, SDL_GetError()); - } - break; - } - - case SDL_JOYDEVICEREMOVED: { - SDL_JoystickID removed_id = event->jdevice.which; - for (int i = 0; i < num_joysticks; ++i) { - if (SDL_JoystickInstanceID(joysticks[i]) == removed_id) { - LOG_info("Joystick removed: %s\n", SDL_JoystickName(joysticks[i])); - SDL_JoystickClose(joysticks[i]); - - // Shift down the remaining entries - for (int j = i; j < num_joysticks - 1; ++j) - joysticks[j] = joysticks[j + 1]; - num_joysticks--; - - if (num_joysticks == 0) { - free(joysticks); - joysticks = NULL; - } else { - joysticks = realloc(joysticks, sizeof(SDL_Joystick *) * num_joysticks); - } - break; - } - } - break; - } - default: - break; - } -} /////////////////////////////// -void PLAT_getBatteryStatus(int* is_charging, int* charge) { - PLAT_getBatteryStatusFine(is_charging, charge); - - // worry less about battery and more about the game you're playing - if (*charge>80) *charge = 100; - else if (*charge>60) *charge = 80; - else if (*charge>40) *charge = 60; - else if (*charge>20) *charge = 40; - else if (*charge>10) *charge = 20; - else *charge = 10; -} -void PLAT_getCPUTemp() { - perf.cpu_temp = getInt("/sys/devices/virtual/thermal/thermal_zone0/temp")/1000; -} -void PLAT_getCPUSpeed() -{ - perf.cpu_speed = getInt("/sys/devices/system/cpu/cpu4/cpufreq/scaling_cur_freq")/1000; -} -void PLAT_getGPUTemp() { - perf.gpu_temp = getInt("/sys/devices/virtual/thermal/thermal_zone5/temp")/1000; -} -void PLAT_getGPUSpeed() { - perf.gpu_speed = getInt("/sys/devices/platform/soc@3000000/1800000.gpu/devfreq/1800000.gpu/cur_freq")/1000000; -} void PLAT_getGPUUsage() { // cat /sys/devices/platform/soc@3000000/1800000.gpu/sunxi_gpu/sunxi_gpu_freq | grep -o '[0-9]*%' | tr -d '%' @@ -165,12 +110,12 @@ void PLAT_getGPUUsage() { static struct WIFI_connection connection = { .valid = false, + .ssid = {0}, + .ip = {0}, .freq = -1, + .rssi = -1, .link_speed = -1, .noise = -1, - .rssi = -1, - .ip = {0}, - .ssid = {0}, }; static inline void connection_reset(struct WIFI_connection *connection_info) @@ -186,33 +131,6 @@ static inline void connection_reset(struct WIFI_connection *connection_info) static bool bluetoothConnected = false; -void PLAT_getNetworkStatus(int* is_online) -{ - if(WIFI_enabled()) - WIFI_connectionInfo(&connection); - else - connection_reset(&connection); - - if(is_online) - *is_online = (connection.valid && connection.ssid[0] != '\0'); - - if(BT_enabled()) { - bluetoothConnected = PLAT_bluetoothConnected(); - } - else - bluetoothConnected = false; -} -void PLAT_getBatteryStatusFine(int *is_charging, int *charge) -{ - if(is_charging) { - int time_to_full = getInt("/sys/class/power_supply/axp2202-battery/time_to_full_now"); - int charger_present = getInt("/sys/class/power_supply/axp2202-usb/online"); - *is_charging = (charger_present == 1) && (time_to_full > 0); - } - if(charge) { - *charge = getInt("/sys/class/power_supply/axp2202-battery/capacity"); - } -} int PLAT_isUSBConnected(void) { @@ -229,30 +147,7 @@ void PLAT_enableBacklight(int enable) { } } -void PLAT_powerOff(int reboot) { - if (CFG_getHaptics()) { - VIB_singlePulse(VIB_bootStrength, VIB_bootDuration_ms); - } - system("rm -f /tmp/nextui_exec && sync"); - sleep(2); - - SetRawVolume(MUTE_VOLUME_RAW); - PLAT_enableBacklight(0); - SND_quit(); - VIB_quit(); - PWR_quit(); - GFX_quit(); - - system("cat /dev/zero > /dev/fb0 2>/dev/null"); - if(reboot > 0) - touch("/tmp/reboot"); - else - touch("/tmp/poweroff"); - sync(); - exit(0); -} -int PLAT_supportsDeepSleep(void) { return 1; } /////////////////////////////// @@ -294,71 +189,7 @@ void PLAT_pinToCores(int core_type) LOG_error("Failed to pin: Are all cores sleeping?\n"); } -void *PLAT_cpu_monitor(void *arg) { - if (!Perf_tryBeginCPUMonitor()) return NULL; - - double prev_real_time = get_time_sec(); - double prev_cpu_time = get_process_cpu_time_sec(); - - double cpu_usage_history[ROLLING_WINDOW] = {0}; - int history_index = 0; - int history_count = 0; - - while (Perf_isCPUMonitorEnabled()) { - double curr_real_time = get_time_sec(); - double curr_cpu_time = get_process_cpu_time_sec(); - - double elapsed_real_time = curr_real_time - prev_real_time; - double elapsed_cpu_time = curr_cpu_time - prev_cpu_time; - - if (elapsed_real_time > 0) { - double cpu_usage = (elapsed_cpu_time / elapsed_real_time) * 100.0; - - pthread_mutex_lock(¤tcpuinfo); - - cpu_usage_history[history_index] = cpu_usage; - history_index = (history_index + 1) % ROLLING_WINDOW; - if (history_count < ROLLING_WINDOW) history_count++; - double sum_cpu_usage = 0; - for (int i = 0; i < history_count; i++) sum_cpu_usage += cpu_usage_history[i]; - perf.cpu_usage = sum_cpu_usage / history_count; - - pthread_mutex_unlock(¤tcpuinfo); - } - - prev_real_time = curr_real_time; - prev_cpu_time = curr_cpu_time; - usleep(100000); - } - - Perf_endCPUMonitor(); - return NULL; -} - -void PLAT_setCPUSpeed(int speed) { - const char* mode; - switch (speed) { - case CPU_SPEED_AUTO: mode = "auto"; break; - case CPU_SPEED_PERFORMANCE: mode = "performance"; break; - case CPU_SPEED_POWERSAVE: mode = "powersave"; break; - default: return; - } - - const char* system_path = getenv("SYSTEM_PATH"); - if (!system_path) { - LOG_info("WARNING: SYSTEM_PATH not set, cannot run governor script\n"); - return; - } - char cmd[512]; - int n = snprintf(cmd, sizeof(cmd), "sh \"%s/bin/governor.sh\" \"%s\"", system_path, mode); - if (n < 0 || n >= (int)sizeof(cmd)) { - LOG_info("WARNING: SYSTEM_PATH too long for governor script path\n"); - return; - } - int ret = system(cmd); - if (ret != 0) LOG_info("WARNING: governor script exited with status %d for mode '%s'\n", ret, mode); -} #define MAX_STRENGTH 0xFFFF #define RUMBLE_PATH "/sys/class/gpio/gpio236/value" @@ -375,17 +206,7 @@ void PLAT_setRumble(int strength) { putInt(RUMBLE_PATH, (strength) ? 1 : 0); } -int PLAT_pickSampleRate(int requested, int max) { - // bluetooth: allow limiting the maximum to improve compatibility - if(PLAT_bluetoothConnected()) - return MIN(requested, CFG_getBluetoothSamplingrateLimit()); - return MIN(requested, max); -} - -void PLAT_overrideMute(int mute) { - putInt("/sys/class/speaker/mute", mute); -} char* PLAT_getModel(void) { char* model = getenv("TRIMUI_MODEL"); @@ -393,28 +214,8 @@ char* PLAT_getModel(void) { return "Trimui Smart Pro S"; } -void PLAT_getOsVersionInfo(char* output_str, size_t max_len) -{ - return getFile("/etc/version", output_str,max_len); -} -bool PLAT_btIsConnected(void) -{ - return bluetoothConnected; -} -ConnectionStrength PLAT_connectionStrength(void) { - if(!WIFI_enabled() || !connection.valid || connection.rssi == -1) - return SIGNAL_STRENGTH_OFF; - else if (connection.rssi == 0) - return SIGNAL_STRENGTH_DISCONNECTED; - else if (connection.rssi >= -60) - return SIGNAL_STRENGTH_HIGH; - else if (connection.rssi >= -70) - return SIGNAL_STRENGTH_MED; - else - return SIGNAL_STRENGTH_LOW; -} void PLAT_initDefaultLeds() { lightsDefault[0] = (LightSettings) { @@ -582,62 +383,9 @@ void PLAT_setLedBrightness(LightSettings *led) } } } -void PLAT_setLedEffect(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%i\n", led->effect); - fclose(file); - } -} -void PLAT_setLedEffectCycles(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_cycles_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%i\n", led->cycles); - fclose(file); - } -} -void PLAT_setLedEffectSpeed(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_duration_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%i\n", led->speed); - fclose(file); - } -} -void PLAT_setLedColor(LightSettings *led) -{ - char filepath[256]; - FILE *file; - // first set brightness - snprintf(filepath, sizeof(filepath), "/sys/class/led_anim/effect_rgb_hex_%s", led->filename); - file = fopen(filepath, "w"); - if (file != NULL) - { - fprintf(file, "%06X\n", led->color1); - fclose(file); - } -} ////////////////////////////////////////////// -bool PLAT_canTurbo(void) { return true; } #define INPUTD_PATH "/tmp/trimui_inputd" @@ -672,37 +420,10 @@ int toggle_file(const char *path) { } } -int PLAT_toggleTurbo(int btn_id) -{ - // avoid extra file IO on each call - static int initialized = 0; - if (!initialized) { - mkdir(INPUTD_PATH, 0755); - initialized = 1; - } - - for (int i = 0; turbo_mapping[i].path; i++) { - if (turbo_mapping[i].brn_id == btn_id) { - return toggle_file(turbo_mapping[i].path); - } - } - return 0; -} -void PLAT_clearTurbo() { - for (int i = 0; turbo_mapping[i].path; i++) { - unlink(turbo_mapping[i].path); - } -} ////////////////////////////////////////////// -int PLAT_setDateTime(int y, int m, int d, int h, int i, int s) { - char cmd[512]; - sprintf(cmd, "date -s '%d-%d-%d %d:%d:%d'; hwclock -u -w", y,m,d,h,i,s); - system(cmd); - return 0; // why does this return an int? -} #define MAX_LINE_LENGTH 200 #define ZONE_PATH "/usr/share/zoneinfo" @@ -715,67 +436,7 @@ int compare_timezones(const void *a, const void *b) { return strcmp((const char *)a, (const char *)b); } -void PLAT_initTimezones() { - if (cached_tz_count != -1) { // Already initialized - return; - } - - FILE *file = fopen(ZONE_TAB_PATH, "r"); - if (!file) { - LOG_info("Error opening file %s\n", ZONE_TAB_PATH); - return; - } - - char line[MAX_LINE_LENGTH]; - cached_tz_count = 0; - - while (fgets(line, sizeof(line), file)) { - // Skip comment lines - if (line[0] == '#' || strlen(line) < 3) { - continue; - } - - char *token = strtok(line, "\t"); // Skip country code - if (!token) continue; - - token = strtok(NULL, "\t"); // Skip latitude/longitude - if (!token) continue; - - token = strtok(NULL, "\t\n"); // Extract timezone - if (!token) continue; - - // Check for duplicates before adding - int duplicate = 0; - for (int i = 0; i < cached_tz_count; i++) { - if (strcmp(cached_timezones[i], token) == 0) { - duplicate = 1; - break; - } - } - - if (!duplicate && cached_tz_count < MAX_TIMEZONES) { - strncpy(cached_timezones[cached_tz_count], token, MAX_TZ_LENGTH - 1); - cached_timezones[cached_tz_count][MAX_TZ_LENGTH - 1] = '\0'; // Ensure null-termination - cached_tz_count++; - } - } - - fclose(file); - - // Sort the list alphabetically - qsort(cached_timezones, cached_tz_count, MAX_TZ_LENGTH, compare_timezones); -} -void PLAT_getTimezones(char timezones[MAX_TIMEZONES][MAX_TZ_LENGTH], int *tz_count) { - if (cached_tz_count == -1) { - LOG_warn("Error: Timezones not initialized. Call PLAT_initTimezones first.\n"); - *tz_count = 0; - return; - } - - memcpy(timezones, cached_timezones, sizeof(cached_timezones)); - *tz_count = cached_tz_count; -} char *PLAT_getCurrentTimezone() { // easy enough, get current index from config and return the string @@ -858,6 +519,7 @@ void PLAT_setNetworkTimeSync(bool on) { ///////////////////////// // We use the generic video implementation here +#include "generic_platform.c" #include "generic_video.c" ///////////////////////// diff --git a/workspace/tg5050/platform/platform.h b/workspace/tg5050/platform/platform.h index 06067be63..1e411e508 100644 --- a/workspace/tg5050/platform/platform.h +++ b/workspace/tg5050/platform/platform.h @@ -2,6 +2,7 @@ #ifndef PLATFORM_H #define PLATFORM_H +#include "device.h" /////////////////////////////// @@ -90,15 +91,15 @@ #define JOY_R1 5 #define JOY_L2 JOY_NA #define JOY_R2 JOY_NA -#define JOY_L3 9 -#define JOY_R3 10 +#define JOY_L3 (deviceModel->joy_l3) +#define JOY_R3 (deviceModel->joy_r3) #define JOY_L4 JOY_NA #define JOY_R4 JOY_NA #define JOY_MENU 8 #define JOY_POWER 102 -#define JOY_PLUS 128 -#define JOY_MINUS 129 +#define JOY_PLUS (deviceModel->joy_plus) +#define JOY_MINUS (deviceModel->joy_minus) /////////////////////////////// // USER-ASSIGNABLE BUTTONS @@ -132,9 +133,9 @@ /////////////////////////////// -#define FIXED_SCALE 2 -#define FIXED_WIDTH 1280 -#define FIXED_HEIGHT 720 +#define FIXED_SCALE (deviceModel->scale) +#define FIXED_WIDTH (deviceModel->width) +#define FIXED_HEIGHT (deviceModel->height) #define FIXED_BPP 2 #define FIXED_DEPTH (FIXED_BPP * 8) #define FIXED_PITCH (FIXED_WIDTH * FIXED_BPP) @@ -142,9 +143,9 @@ /////////////////////////////// -#define MAIN_ROW_COUNT 10 -#define QUICK_SWITCHER_COUNT 4 -#define PADDING 10 +#define MAIN_ROW_COUNT (deviceModel->main_row_count) +#define QUICK_SWITCHER_COUNT (deviceModel->quick_switcher_count) +#define PADDING (deviceModel->padding) ///////////////////////////////