Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3dc835d
feat: Add RetroAchievements integration and in-game notifications
clintonium-119 Feb 3, 2026
c7129b3
In CI, disable debug symbols to avoid dsymutil memory issues on macOS
clintonium-119 Feb 3, 2026
3eb971a
Merge branch 'main' into feature/retroachievements-notifications
clintonium-119 Feb 4, 2026
46573b9
Update makefile
clintonium-119 Feb 4, 2026
4eea18d
Merge branch 'main' into feature/retroachievements-notifications
clintonium-119 Feb 5, 2026
de6c2ab
Fix notification artifacts persisting after dismissal
clintonium-119 Feb 5, 2026
81297e3
Address PR #633 feedback: API cleanup and code improvements
clintonium-119 Feb 5, 2026
2bc7602
Move RA data to shared userdata directory
clintonium-119 Feb 5, 2026
1edbda1
Hide Achievements menu when RetroAchievements disabled
clintonium-119 Feb 5, 2026
31735b2
Optimize badge downloading with rate-limited queue and lazy loading
clintonium-119 Feb 5, 2026
d4c6a7d
Merge branch 'main' into feature/retroachievements-notifications
clintonium-119 Feb 5, 2026
d151f78
Remove obsolete FPS tracking code
clintonium-119 Feb 5, 2026
dfc52a5
Update workspace/all/common/config.c
clintonium-119 Feb 5, 2026
616e104
Update workspace/all/common/config.c
clintonium-119 Feb 5, 2026
89b3d8b
Update workspace/all/common/config.c
clintonium-119 Feb 5, 2026
fbde380
Refactor RA code for readability per PR feedback
clintonium-119 Feb 5, 2026
19bb639
Extract magic numbers to named constants in notification.c
clintonium-119 Feb 5, 2026
04787b1
Merge branch 'feature/retroachievements-notifications' of github.com:…
clintonium-119 Feb 5, 2026
614218b
Break up Notification_renderToLayer() into smaller helpers
clintonium-119 Feb 5, 2026
d1b5b74
Consolidate duplicate RA makefile blocks for tg5040/tg5050/desktop
clintonium-119 Feb 5, 2026
17f0afc
Move http.c/http.h from common/ to minarch/
clintonium-119 Feb 5, 2026
b13e02f
Use container-provided toolchain for libchdr cross-compilation
clintonium-119 Feb 5, 2026
faddf04
Move http.c/http.h from common/ to minarch/
clintonium-119 Feb 5, 2026
b160957
Hide hardcore mode setting from UI
clintonium-119 Feb 5, 2026
aff5d9b
Refactor notification overlay statics into struct (generic_video.c)
clintonium-119 Feb 5, 2026
18385e1
Move findFileInDir from generic_video.c to utils.c
clintonium-119 Feb 5, 2026
ff6ae66
Clean up notification.c per PR feedback
clintonium-119 Feb 5, 2026
5cbba51
Merge branch 'main' into feature/retroachievements-notifications
clintonium-119 Feb 9, 2026
5b560ff
Remove unneeded dirent.h include
clintonium-119 Feb 9, 2026
882a1f8
Revert "Move http.c/http.h from common/ to minarch/"
clintonium-119 Feb 9, 2026
3510c8b
fix(retroachievements): Add 15s timeout to badge loading notification
clintonium-119 Feb 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:

- name: Install dependencies
run: |
brew install gcc make sdl2_image sdl2_ttf libzip libsamplerate
brew install gcc make sdl2_image sdl2_ttf libzip libsamplerate cmake
sudo ./workspace/desktop/macos_create_gcc_symlinks.sh

- name: Setup
Expand All @@ -48,6 +48,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y libsdl2-image-dev libsdl2-ttf-dev
sudo apt-get install -y libzip-dev liblzma-dev libzstd-dev libbz2-dev zlib1g-dev
sudo apt-get install -y cmake

- name: Setup
run: make setup
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ build
releases
toolchains
libretro-common
libchdr

# rcheevos source (cloned at build time)
**/rcheevos/src

**/cores/src
**/cores/output
Expand Down Expand Up @@ -36,4 +40,4 @@ audiomon.elf
**/tmp

workspace/hash.txt
workspace/readmes
workspace/readmes
6 changes: 4 additions & 2 deletions makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ endif
###########################################################

BUILD_HASH:=$(shell git rev-parse --short HEAD)
BUILD_BRANCH:=$(shell git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD)
BUILD_BRANCH:=$(shell (git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD) | sed 's/\//-/g')
RELEASE_TIME:=$(shell TZ=GMT date +%Y%m%d)
ifeq ($(BUILD_BRANCH),main)
RELEASE_BETA :=
Expand Down Expand Up @@ -103,7 +103,6 @@ endif
cp ./workspace/all/clock/build/$(PLATFORM)/clock.elf ./build/EXTRAS/Tools/$(PLATFORM)/Clock.pak/
cp ./workspace/all/minput/build/$(PLATFORM)/minput.elf ./build/EXTRAS/Tools/$(PLATFORM)/Input.pak/
cp ./workspace/all/settings/build/$(PLATFORM)/settings.elf ./build/EXTRAS/Tools/$(PLATFORM)/Settings.pak/

ifneq (,$(filter $(PLATFORM),tg5040 tg5050))
cp ./workspace/all/ledcontrol/build/$(PLATFORM)/ledcontrol.elf ./build/EXTRAS/Tools/$(PLATFORM)/LedControl.pak/
cp ./workspace/all/bootlogo/build/$(PLATFORM)/bootlogo.elf ./build/EXTRAS/Tools/$(PLATFORM)/Bootlogo.pak/
Expand All @@ -120,6 +119,9 @@ endif
cp ./workspace/all/minarch/build/$(PLATFORM)/liblzma.* ./build/SYSTEM/$(PLATFORM)/lib/
cp ./workspace/all/minarch/build/$(PLATFORM)/libzstd.* ./build/SYSTEM/$(PLATFORM)/lib/

# libchdr for RetroAchievements CHD hashing (use -L to dereference symlinks)
cp -L ./workspace/all/minarch/build/$(PLATFORM)/libchdr.so.0 ./build/SYSTEM/$(PLATFORM)/lib/

ifeq ($(PLATFORM), tg5040)
# liblz4 for Rewind support
cp -L ./workspace/all/minarch/build/$(PLATFORM)/liblz4.so.1 ./build/SYSTEM/$(PLATFORM)/lib/
Expand Down
210 changes: 162 additions & 48 deletions workspace/all/common/api.c
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,91 @@ int GFX_wrapText(TTF_Font *font, char *str, int max_width, int max_lines)
return max_line_width;
}

int GFX_blitWrappedText(TTF_Font *font, const char *text, int max_width, int max_lines, SDL_Color color, SDL_Surface *surface, int y)
{
if (!text || !text[0])
return y;

int center_x = surface->w / 2;

char *text_copy = strdup(text);
if (!text_copy)
return y;

char *words[256];
int word_count = 0;

// Split text into words
char *token = strtok(text_copy, " ");
while (token && word_count < 256) {
words[word_count++] = token;
token = strtok(NULL, " ");
}

// Build and render lines
char line[512] = "";
int line_num = 0;
for (int w = 0; w < word_count; w++) {
char test_line[512];
if (line[0] == '\0') {
snprintf(test_line, sizeof(test_line), "%s", words[w]);
} else {
snprintf(test_line, sizeof(test_line), "%s %s", line, words[w]);
}

int test_width, test_height;
TTF_SizeUTF8(font, test_line, &test_width, &test_height);

if (test_width > max_width && line[0] != '\0') {
// Current line is full
if (!max_lines || line_num < max_lines - 1) {
// 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
});
y += line_surface->h;
SDL_FreeSurface(line_surface);
}
line_num++;
snprintf(line, sizeof(line), "%s", words[w]);
} else {
// Last allowed line with more words remaining - add ellipsis
char truncated[512];
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
});
y += line_surface->h;
SDL_FreeSurface(line_surface);
}
line[0] = '\0'; // Mark as rendered
break;
}
} else {
snprintf(line, sizeof(line), "%s", test_line);
}
}

// Render any remaining text
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
});
y += line_surface->h;
SDL_FreeSurface(line_surface);
}
}

free(text_copy);
return y;
}

///////////////////////////////

// scale_blend (and supporting logic) from picoarch
Expand Down Expand Up @@ -1785,65 +1870,94 @@ void GFX_blitBatteryAtPosition(SDL_Surface *dst, int x, int y)
}
}

// Helper function to render a hardware indicator (volume/brightness/colortemp) at a specific position.
// This is the reusable core extracted from GFX_blitHardwareGroup for use in notifications.
int GFX_blitHardwareIndicator(SDL_Surface *dst, int x, int y, IndicatorType indicator_type)
{
int setting_value;
int setting_min;
int setting_max;
int asset;

int ow = SCALE1(PILL_SIZE + SETTINGS_WIDTH + 10 + 4);
int ox = x;
int oy = y;

// Draw the pill background
GFX_blitPillLight(ASSET_WHITE_PILL, dst, &(SDL_Rect){ox, oy, ow, SCALE1(PILL_SIZE)});

// Determine which setting to display
if (indicator_type == INDICATOR_BRIGHTNESS)
{
setting_value = GetBrightness();
setting_min = BRIGHTNESS_MIN;
setting_max = BRIGHTNESS_MAX;
asset = ASSET_BRIGHTNESS;
}
else if (indicator_type == INDICATOR_COLORTEMP)
{
setting_value = GetColortemp();
setting_min = COLORTEMP_MIN;
setting_max = COLORTEMP_MAX;
asset = ASSET_COLORTEMP;
}
else // INDICATOR_VOLUME
{
setting_value = GetVolume();
setting_min = VOLUME_MIN;
setting_max = VOLUME_MAX;
if(GetAudioSink() == AUDIO_SINK_BLUETOOTH)
asset = (setting_value > 0 ? ASSET_BLUETOOTH : ASSET_BLUETOOTH_OFF);
else
asset = (setting_value > 0 ? ASSET_VOLUME : ASSET_VOLUME_MUTE);
}

// Draw the icon
SDL_Rect asset_rect;
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);

// 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);

// Draw the progress bar fill
float percent = ((float)(setting_value - setting_min) / (setting_max - setting_min));
if (indicator_type == 1 || indicator_type == 3 || setting_value > 0)
{
GFX_blitPillDark(ASSET_BAR, dst, &(SDL_Rect){ox, bar_y, SCALE1(SETTINGS_WIDTH) * percent, SCALE1(SETTINGS_SIZE)});
}

return ow;
}

SDL_Surface* GFX_createScreenFormatSurface(int width, int height)
{
if (!gfx.screen) return NULL;
return SDL_CreateRGBSurfaceWithFormat(
SDL_SWSURFACE, width, height,
gfx.screen->format->BitsPerPixel,
gfx.screen->format->format
);
}

int GFX_blitHardwareGroup(SDL_Surface *dst, int show_setting)
{
int ox;
int oy;
int ow = 0;

int setting_value;
int setting_min;
int setting_max;

if (show_setting && !GetHDMI())
{
int asset;
// Use the helper function to render the indicator at the standard position
ow = SCALE1(PILL_SIZE + SETTINGS_WIDTH + 10 + 4);
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);

if (show_setting == 1)
{
setting_value = GetBrightness();
setting_min = BRIGHTNESS_MIN;
setting_max = BRIGHTNESS_MAX;
asset = ASSET_BRIGHTNESS;
}
else if (show_setting == 3)
{
setting_value = GetColortemp();
setting_min = COLORTEMP_MIN;
setting_max = COLORTEMP_MAX;
asset = ASSET_COLORTEMP;
}
else
{
setting_value = GetVolume();
setting_min = VOLUME_MIN;
setting_max = VOLUME_MAX;
if(GetAudioSink() == AUDIO_SINK_BLUETOOTH)
asset = (setting_value > 0 ? ASSET_BLUETOOTH : ASSET_BLUETOOTH_OFF);
else
asset = (setting_value > 0 ? ASSET_VOLUME : ASSET_VOLUME_MUTE);
}

SDL_Rect asset_rect;
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);

ox += SCALE1(PILL_SIZE);
oy += SCALE1((PILL_SIZE - SETTINGS_SIZE) / 2);
GFX_blitPillColor(gfx.mode == MODE_MAIN ? ASSET_BAR_BG : ASSET_BAR_BG_MENU, dst, &(SDL_Rect){ox, oy, SCALE1(SETTINGS_WIDTH), SCALE1(SETTINGS_SIZE)},
THEME_COLOR3, RGB_WHITE);

float percent = ((float)(setting_value - setting_min) / (setting_max - setting_min));
if (show_setting == 1 || show_setting == 3 || setting_value > 0)
{
GFX_blitPillDark(ASSET_BAR, dst, &(SDL_Rect){ox, oy, SCALE1(SETTINGS_WIDTH) * percent, SCALE1(SETTINGS_SIZE)});
}
GFX_blitHardwareIndicator(dst, ox, oy, (IndicatorType)show_setting);
}
else
{
Expand Down
36 changes: 35 additions & 1 deletion workspace/all/common/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ void GFX_scrollTextSurface(TTF_Font* font, const char* in_name, SDL_Surface** ou
int GFX_getTextWidth(TTF_Font* font, const char* in_name, char* out_name, int max_width, int padding); // returns final width
int GFX_getTextHeight(TTF_Font* font, const char* in_name, char* out_name, int max_width, int padding); // returns final width
int GFX_wrapText(TTF_Font* font, char* str, int max_width, int max_lines);
int GFX_blitWrappedText(TTF_Font* font, const char* text, int max_width, int max_lines, SDL_Color color, SDL_Surface* surface, int y); // returns new y position

#define GFX_getScaler PLAT_getScaler // scaler_t:(GFX_Renderer* renderer)
#define GFX_blitRenderer PLAT_blitRenderer // void:(GFX_Renderer* renderer)
Expand Down Expand Up @@ -352,6 +353,34 @@ void GFX_blitMessage(TTF_Font* font, char* msg, SDL_Surface* dst, SDL_Rect* dst_

int GFX_blitHardwareGroup(SDL_Surface* dst, int show_setting);
void GFX_blitHardwareHints(SDL_Surface* dst, int show_setting);

typedef enum {
INDICATOR_BRIGHTNESS = 1,
INDICATOR_VOLUME = 2,
INDICATOR_COLORTEMP = 3,
} IndicatorType;

/**
* Render a hardware indicator (volume/brightness/colortemp) at a specific position.
* This is the reusable helper extracted from GFX_blitHardwareGroup for in-game use.
* @param dst The destination surface
* @param x X position for the indicator
* @param y Y position for the indicator
* @param indicator_type Which indicator to display
* @return The width of the rendered indicator
*/
int GFX_blitHardwareIndicator(SDL_Surface* dst, int x, int y, IndicatorType indicator_type);

/**
* Create a surface with the same pixel format as gfx.screen.
* This is needed when rendering theme-colored content that will later be
* converted to another format (e.g., RGBA for GL overlays).
* @param width Surface width
* @param height Surface height
* @return A new SDL_Surface, or NULL on failure. Caller must free with SDL_FreeSurface.
*/
SDL_Surface* GFX_createScreenFormatSurface(int width, int height);

int GFX_blitButtonGroup(char** hints, int primary, SDL_Surface* dst, int align_right);

void GFX_assetRect(int asset, SDL_Rect* dst_rect);
Expand Down Expand Up @@ -566,7 +595,6 @@ void PLAT_initPlatform(void); // *actual* platform-specific init

FILE *PLAT_OpenSettings(const char *filename);
FILE *PLAT_WriteSettings(const char *filename);
char* PLAT_findFileInDir(const char *directory, const char *filename);
void PLAT_initInput(void);
void PLAT_updateInput(const SDL_Event *event);
void PLAT_quitInput(void);
Expand All @@ -589,6 +617,11 @@ void PLAT_setOffsetY(int y);
void PLAT_drawOnLayer(SDL_Surface *inputSurface, int x, int y, int w, int h, float brightness, bool maintainAspectRatio,int layer);
void PLAT_clearLayers(int layer);
SDL_Surface* PLAT_captureRendererToSurface();

// Notification overlay for GL rendering (rendered on top of game during PLAT_GL_Swap)
void PLAT_setNotificationSurface(SDL_Surface* surface, int x, int y);
void PLAT_clearNotificationSurface(void);

void PLAT_animateSurface(
SDL_Surface *inputSurface,
int x, int y,
Expand Down Expand Up @@ -632,6 +665,7 @@ void PLAT_resetShaders();
void PLAT_clearShaders();
void PLAT_updateShader(int i, const char *filename, int *scale, int *filter, int *scaletype, int *inputtype);
void PLAT_initShaders();
void PLAT_initNotificationTexture(void);
ShaderParam* PLAT_getShaderPragmas(int i);
int PLAT_supportsOverscan(void);

Expand Down
Loading
Loading