update display brightness - initial implementation

This commit is contained in:
itsmattkc committed 2026-08-23 16:58:09 -07:00
1 parent ff354e4176
commit b21181c675
16 files changed
+315 -98

No files matched your search

+4
View File
@@ -297,6 +297,10 @@ list(APPEND VANILLA_GUI_SRC
)
if (LINUX)
list(APPEND VANILLA_GUI_SRC
ui/ui_sdl_linux.c
)
# Link with DRM if available
option(VANILLA_BUILD_USE_DRM "Enable DRM video output" ON)
if (VANILLA_BUILD_USE_DRM)
+6
View File
@@ -93,6 +93,9 @@ void vpi_config_save()
sprintf(buf, "%i", vpi_config.force_software_decode);
xmlTextWriterWriteElement(writer, BAD_CAST "swdec", BAD_CAST buf);
sprintf(buf, "%f", vpi_config.screen_brightness);
xmlTextWriterWriteElement(writer, BAD_CAST "brightness", BAD_CAST buf);
xmlTextWriterStartElement(writer, BAD_CAST "controls");
if (vpi_config.keymap) {
xmlTextWriterStartElement(writer, BAD_CAST "keys");
@@ -184,6 +187,7 @@ void vpi_config_init()
vpi_config_reset_default_controls_internal();
vpi_config.cursor_in_fullscreen = 0;
vpi_config.autoconnect = -1;
vpi_config.screen_brightness = 1.0f;
// Load from file
char config_fn[1024];
@@ -250,6 +254,8 @@ void vpi_config_init()
vpi_config.fullscreen = atoi((const char *) child->children->content);
} else if (!strcmp((const char *) child->name, "swdec")) {
vpi_config.force_software_decode = atoi((const char *) child->children->content);
} else if (!strcmp((const char *) child->name, "brightness")) {
vpi_config.screen_brightness = atof((const char *) child->children->content);
} else if (!strcmp((const char *) child->name, "controls")) {
xmlNodePtr section = child->children;
while(section){
+1
View File
@@ -33,6 +33,7 @@ typedef struct {
int fast_drm;
int force_software_decode;
int autoconnect;
float screen_brightness;
} vpi_config_t;
extern vpi_config_t vpi_config;
+1
View File
@@ -97,6 +97,7 @@ int SDL_main(int argc, const char **argv)
goto exit;
}
vui_brightness_set(vui, vpi_config.screen_brightness);
vpi_menu_init(vui);
while (vui_update_sdl(vui)) {
+13
View File
@@ -1041,6 +1041,19 @@ void *vpi_event_loop(void *arg)
case VANILLA_EVENT_MIC:
vui_mic_enabled_set(vui, event.data[0]);
break;
case VANILLA_EVENT_BRIGHTNESS:
{
// Clamp to valid values from console
uint8_t value = CLAMP(event.data[0], 1, 5);
// Normalize to 0.0 - 1.0
float b = value / 5.0f;
vpi_config.screen_brightness = b;
vpi_config_save();
vui_brightness_set(vui, b);
break;
}
}
vanilla_free_event(&event);
+7
View File
@@ -1041,3 +1041,10 @@ void vui_mic_callback_set(vui_context_t *ctx, vui_mic_callback_t callback, void
ctx->mic_callback = callback;
ctx->mic_callback_data = userdata;
}
void vui_brightness_set(vui_context_t *ctx, float brightness)
{
if (ctx->brightness_set_handler) {
ctx->brightness_set_handler(ctx, brightness, ctx->brightness_set_handler_data);
}
}
+5
View File
@@ -182,4 +182,9 @@ vui_power_state_t vui_power_state_get(vui_context_t *ctx, int *percent);
void vui_mic_enabled_set(vui_context_t *ctx, int enabled);
void vui_mic_callback_set(vui_context_t *ctx, vui_mic_callback_t callback, void *userdata);
/**
* Miscellaneous functions
*/
void vui_brightness_set(vui_context_t *ctx, float brightness); // Expects brightness between 0.0f and 1.0f
#endif // VANILLA_PI_UI_H
+3
View File
@@ -104,6 +104,7 @@ typedef int (*vui_font_height_handler_t)(vui_font_size_t size, void *userdata);
typedef void (*vui_text_open_handler_t)(vui_context_t *ctx, int textedit, int open, void *userdata);
typedef vui_power_state_t (*vui_power_state_handler_t)(vui_context_t *ctx, int *percent);
typedef void (*vui_audio_enabled_handler_t)(vui_context_t *ctx, int enabled, void *userdata);
typedef void (*vui_brightness_set_handler_t)(vui_context_t *ctx, float value, void *userdata);
typedef struct vui_context_t {
void *platform_data;
@@ -155,6 +156,8 @@ typedef struct vui_context_t {
void *audio_enabled_handler_data;
vui_bool_callback_t fullscreen_enabled_handler;
void *fullscreen_enabled_handler_data;
vui_brightness_set_handler_t brightness_set_handler;
void *brightness_set_handler_data;
int default_button_map[VPI_CONFIG_BUTTONMAP_SIZE];
int default_axis_map[VPI_CONFIG_AXISMAP_SIZE];
+121 -82
View File
@@ -45,6 +45,12 @@
#include "platform_nx_imu.h"
#endif
#if defined(ANDROID) || defined(__ANDROID__)
// TODO
#elif defined(__linux__)
#include "ui_sdl_linux.h"
#endif
#define MIN(a,b) (((a)<(b))?(a):(b))
#define PW_CHAR_SIZE 20
#define PW_CHAR_PAD 2
@@ -332,6 +338,24 @@ void vui_sdl_fullscreen_enabled_handler(vui_context_t *ctx, int enabled, void *u
SDL_ShowCursor(vpi_config.cursor_in_fullscreen || !enabled);
}
void vui_sdl_brightness_set_handler(vui_context_t *ctx, float brightness, void *userdata)
{
vpilog("SETTING BRIGHTNESS TO %.1f\n", brightness);
vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) ctx->platform_data;
#if defined(ANDROID) || defined(__ANDROID__)
// TODO
#elif defined(__linux__)
if (vui_sdl_linux_set_brightness(brightness) == 0) {
return;
}
#endif
// Fallback
SDL_SetWindowBrightness(sdl_ctx->window, brightness);
}
void vui_sdl_get_key_mapping_handler(vui_context_t *ctx, int vanilla_btn, void *userdata)
{
@@ -841,6 +865,8 @@ int vui_init_sdl(vui_context_t *ctx, int fullscreen)
ctx->fullscreen_enabled_handler = vui_sdl_fullscreen_enabled_handler;
ctx->brightness_set_handler = vui_sdl_brightness_set_handler;
if (TTF_Init()) {
vpilog("Failed to TTF_Init\n");
return -1;
@@ -1894,30 +1920,6 @@ int vui_update_sdl(vui_context_t *vui)
}
#endif // VANILLA_CUDA_AVAILABLE
// Draw vui to a custom texture
vui_draw_sdl(vui, renderer);
// Flatten layers
int el[MAX_BUTTON_COUNT];
int el_count = 0;
for (int i = 0; i < vui->layers; i++) {
if (vui->layer_enabled[i]) {
el[el_count] = i;
el_count++;
}
}
for (int i = el_count - 1; i > 0; i--) {
SDL_Texture *bg = sdl_ctx->layer_data[el[i-1]];
SDL_Texture *fg = sdl_ctx->layer_data[el[i]];
SDL_SetRenderTarget(renderer, bg);
SDL_SetTextureColorMod(fg, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF);
SDL_SetTextureAlphaMod(fg, vui->layer_opacity[i] * 0xFF);
SDL_RenderCopy(renderer, fg, NULL, NULL);
}
main_tex = sdl_ctx->layer_data[0];
} else {
pthread_mutex_lock(&vpi_present_frame_mutex);
if (vpi_present_frame
@@ -2013,78 +2015,115 @@ int vui_update_sdl(vui_context_t *vui)
}
if (handle_final_blit) {
// Draw toast on screen if necessary
const int TOAST_PADDING = 12;
int cur_toast;
vpi_get_toast(&cur_toast, 0, 0, 0);
if (cur_toast != sdl_ctx->last_shown_toast) {
// Get toast information
char toast_str[VPI_TOAST_MAX_LEN];
vpi_get_toast(&cur_toast, toast_str, sizeof(toast_str), &sdl_ctx->toast_expiry);
// Draw vui to a custom texture
if (!vui->game_mode) {
vui_draw_sdl(vui, renderer);
sdl_ctx->last_shown_toast = cur_toast;
// Flatten layers
{
int el[MAX_BUTTON_COUNT];
int el_count = 0;
for (int i = 0; i < vui->layers; i++) {
if (vui->layer_enabled[i]) {
el[el_count] = i;
el_count++;
}
}
const int toast_w = vui->screen_width/2;
for (int i = el_count - 1; i > 0; i--) {
SDL_Texture *bg = sdl_ctx->layer_data[el[i-1]];
SDL_Texture *fg = sdl_ctx->layer_data[el[i]];
SDL_Color c;
c.r = c.g = c.b = 0x40;
c.a = 0xFF;
SDL_SetRenderTarget(renderer, bg);
SDL_SetTextureColorMod(fg, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF, vui->layer_opacity[i] * 0xFF);
SDL_SetTextureAlphaMod(fg, vui->layer_opacity[i] * 0xFF);
SDL_RenderCopy(renderer, fg, NULL, NULL);
}
SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped(sdl_ctx->sysfont_small, toast_str, c, toast_w - TOAST_PADDING - TOAST_PADDING);
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
const int toast_h = surface->h + TOAST_PADDING + TOAST_PADDING;
sdl_ctx->toast_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, toast_w, toast_h);
SDL_SetTextureBlendMode(sdl_ctx->toast_tex, SDL_BLENDMODE_BLEND);
SDL_SetRenderTarget(renderer, sdl_ctx->toast_tex);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xF0);
const int TOAST_BORDER_RADIUS = TOAST_PADDING;
for (int y = 0; y < toast_h; y++) {
const int x_inset = calculate_x_inset(y, toast_h, TOAST_BORDER_RADIUS);
SDL_RenderDrawLine(renderer, x_inset, y, toast_w - x_inset, y);
if (vui->game_mode) {
// TODO: Composite onto game texture
// SDL_SetRenderTarget(renderer, main_tex);
// SDL_RenderCopy(renderer, sdl_ctx->layer_data[0], NULL, NULL);
} else {
// Just return our texture
main_tex = sdl_ctx->layer_data[0];
}
}
SDL_Rect dst_rect;
dst_rect.x = dst_rect.y = TOAST_PADDING;
dst_rect.w = surface->w;
dst_rect.h = surface->h;
SDL_RenderCopy(renderer, texture, 0, &dst_rect);
SDL_DestroyTexture(texture);
SDL_FreeSurface(surface);
}
if (sdl_ctx->toast_tex) {
// Draw toast to screen
SDL_SetRenderTarget(renderer, main_tex);
// Get texture size
int toast_w, toast_h;
SDL_QueryTexture(sdl_ctx->toast_tex, 0, 0, &toast_w, &toast_h);
// Draw toast on screen if necessary
{
const int TOAST_PADDING = 12;
int cur_toast;
vpi_get_toast(&cur_toast, 0, 0, 0);
if (cur_toast != sdl_ctx->last_shown_toast) {
// Get toast information
char toast_str[VPI_TOAST_MAX_LEN];
vpi_get_toast(&cur_toast, toast_str, sizeof(toast_str), &sdl_ctx->toast_expiry);
// Calculate dst rect
SDL_Rect dst_rect;
dst_rect.w = toast_w;
dst_rect.h = toast_h;
dst_rect.y = vui->screen_height - dst_rect.h - TOAST_PADDING - TOAST_PADDING;
dst_rect.x = vui->screen_width/2 - toast_w/2;
sdl_ctx->last_shown_toast = cur_toast;
SDL_RenderCopy(renderer, sdl_ctx->toast_tex, 0, &dst_rect);
const int toast_w = vui->screen_width/2;
// Handle expiry
struct timeval now;
gettimeofday(&now, 0);
if (now.tv_sec*1000000+now.tv_usec >= sdl_ctx->toast_expiry.tv_sec*1000000+sdl_ctx->toast_expiry.tv_usec) {
SDL_DestroyTexture(sdl_ctx->toast_tex);
sdl_ctx->toast_tex = 0;
SDL_Color c;
c.r = c.g = c.b = 0x40;
c.a = 0xFF;
SDL_Surface *surface = TTF_RenderUTF8_Blended_Wrapped(sdl_ctx->sysfont_small, toast_str, c, toast_w - TOAST_PADDING - TOAST_PADDING);
SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
const int toast_h = surface->h + TOAST_PADDING + TOAST_PADDING;
sdl_ctx->toast_tex = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_TARGET, toast_w, toast_h);
SDL_SetTextureBlendMode(sdl_ctx->toast_tex, SDL_BLENDMODE_BLEND);
SDL_SetRenderTarget(renderer, sdl_ctx->toast_tex);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
SDL_SetRenderDrawColor(renderer, 0xFF, 0xFF, 0xFF, 0xF0);
const int TOAST_BORDER_RADIUS = TOAST_PADDING;
for (int y = 0; y < toast_h; y++) {
const int x_inset = calculate_x_inset(y, toast_h, TOAST_BORDER_RADIUS);
SDL_RenderDrawLine(renderer, x_inset, y, toast_w - x_inset, y);
}
SDL_Rect dst_rect;
dst_rect.x = dst_rect.y = TOAST_PADDING;
dst_rect.w = surface->w;
dst_rect.h = surface->h;
SDL_RenderCopy(renderer, texture, 0, &dst_rect);
SDL_DestroyTexture(texture);
SDL_FreeSurface(surface);
}
if (sdl_ctx->toast_tex) {
// Draw toast to screen
SDL_SetRenderTarget(renderer, main_tex);
// Get texture size
int toast_w, toast_h;
SDL_QueryTexture(sdl_ctx->toast_tex, 0, 0, &toast_w, &toast_h);
// Calculate dst rect
SDL_Rect dst_rect;
dst_rect.w = toast_w;
dst_rect.h = toast_h;
dst_rect.y = vui->screen_height - dst_rect.h - TOAST_PADDING - TOAST_PADDING;
dst_rect.x = vui->screen_width/2 - toast_w/2;
SDL_RenderCopy(renderer, sdl_ctx->toast_tex, 0, &dst_rect);
// Handle expiry
struct timeval now;
gettimeofday(&now, 0);
if (now.tv_sec*1000000+now.tv_usec >= sdl_ctx->toast_expiry.tv_sec*1000000+sdl_ctx->toast_expiry.tv_usec) {
SDL_DestroyTexture(sdl_ctx->toast_tex);
sdl_ctx->toast_tex = 0;
}
}
}
// Calculate our destination rectangle
SDL_SetRenderTarget(renderer, NULL);
int out_w, out_h;
+103
View File
@@ -0,0 +1,103 @@
#include "ui_sdl_linux.h"
#include <dirent.h>
#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <unistd.h>
#include "ui_util.h"
#define VUI_LINUX_BACKLIGHT_ROOT "/sys/class/backlight"
#define VUI_LINUX_BACKLIGHT_DEFAULT "backlight"
#define VUI_LINUX_BACKLIGHT_PATH_SIZE 4096
static int vui_sdl_linux_set_backlight_device(const char *device, float brightness)
{
char path[VUI_LINUX_BACKLIGHT_PATH_SIZE];
unsigned int max_brightness;
unsigned int value;
int path_length;
int write_failed;
int close_failed;
FILE *file;
if (!isfinite(brightness)) {
errno = EINVAL;
return -1;
}
brightness = CLAMP(brightness, 0.0f, 1.0f);
// Attempt to read max_brightness information
path_length = snprintf(path, sizeof(path), "%s/%s/max_brightness", VUI_LINUX_BACKLIGHT_ROOT, device);
if (path_length < 0 || path_length >= (int) sizeof(path)) {
errno = ENAMETOOLONG;
return -1;
}
file = fopen(path, "r");
if (!file) {
return -1;
}
if (fscanf(file, "%u", &max_brightness) != 1 || !max_brightness) {
fclose(file);
errno = EINVAL;
return -1;
}
fclose(file);
// Now that we have max_brightness, attempt to set actual brightness to a percentage of max brightness
value = lroundf(brightness * max_brightness);
path_length = snprintf(path, sizeof(path), "%s/%s/brightness", VUI_LINUX_BACKLIGHT_ROOT, device);
if (path_length < 0 || path_length >= (int) sizeof(path)) {
errno = ENAMETOOLONG;
return -1;
}
file = fopen(path, "w");
if (!file) {
return -1;
}
write_failed = fprintf(file, "%u\n", value) < 0;
close_failed = fclose(file) != 0;
if (write_failed || close_failed) {
return -1;
}
return 0;
}
int vui_sdl_linux_set_brightness(float brightness)
{
DIR *directory;
struct dirent *entry;
// See if "backlight" device exists (should exist on Nintendo Switch)
if (access(VUI_LINUX_BACKLIGHT_ROOT "/" VUI_LINUX_BACKLIGHT_DEFAULT, F_OK) == 0) {
return vui_sdl_linux_set_backlight_device(VUI_LINUX_BACKLIGHT_DEFAULT, brightness);
}
// Otherwise, iterate over directory to find next best device to set brightness on
directory = opendir(VUI_LINUX_BACKLIGHT_ROOT);
if (!directory) {
return -1;
}
while ((entry = readdir(directory))) {
if (entry->d_name[0] == '.') {
continue;
}
if (vui_sdl_linux_set_backlight_device(entry->d_name, brightness) == 0) {
closedir(directory);
return 0;
}
}
closedir(directory);
return -1;
}
+6
View File
@@ -0,0 +1,6 @@
#ifndef VANILLA_PI_UI_SDL_LINUX_H
#define VANILLA_PI_UI_SDL_LINUX_H
int vui_sdl_linux_set_brightness(float brightness);
#endif // VANILLA_PI_UI_SDL_LINUX_H
+2
View File
@@ -9,6 +9,8 @@ void vui_strncpy(char *dst, const char *src, size_t max_dst_size);
size_t vui_utf8_cp_len(const char *s);
char *vui_utf8_advance(char *s);
#define CLAMP(x, low, high) (((x) < (low)) ? (low) : ((x) > (high)) ? (high) : (x))
static inline int intmin(int a, int b) { return a < b ? a : b; }
static inline int intmax(int a, int b) { return a > b ? a : b; }
static inline int64_t int64min(int64_t a, int64_t b) { return a < b ? a : b; }
+31 -5
View File
@@ -60,7 +60,7 @@ void send_generic_response(int skt, CmdHeader *response)
void handle_generic_packet(gamepad_context_t *info, int skt, GenericPacket *request)
{
GenericCmdHeader *gen_cmd = &request->generic_cmd_header;
vanilla_log("magic: %x, flags: %x, service ID: %u, method ID: %u", gen_cmd->magic_0x7E, gen_cmd->flags, gen_cmd->service_id, gen_cmd->method_id);
// vanilla_log("magic: %x, flags: %x, service ID: %u, method ID: %u", gen_cmd->magic_0x7E, gen_cmd->flags, gen_cmd->service_id, gen_cmd->method_id);
// Prepare response
GenericPacket response;
@@ -170,13 +170,39 @@ void handle_generic_packet(gamepad_context_t *info, int skt, GenericPacket *requ
}
case METHOD_ID_PERIPHERAL_UPDATE_EEPROM:
{
vanilla_log("5,12 - index: %u, length: %u", response.payload[0], response.payload[1]);
response.generic_cmd_header.error_code = 0;
uint8_t index = request->payload[0];
uint8_t length = request->payload[1];
vanilla_log("5,12 - index: %u, length: %u", index, length);
uint16_t our_crc = crc16(request->payload + 2, length - 2);
uint16_t their_crc = le16toh(*(uint16_t *)(request->payload + 2 + length - 2));
if (our_crc != their_crc) {
// TODO: Report some kind of error to the console?
} else {
// Update our EEPROM
switch (index) {
case 0x10:
{
// Console wants to change our brightness
// vanilla_log("Change brightness to %x", request->payload[2]);
uint8_t b = request->payload[2];
push_event(info->event_loop, VANILLA_EVENT_BRIGHTNESS, &b, sizeof(b));
break;
}
default:
vanilla_log("Console attempted write to unknown EEPROM offset: 0x%x", index);
}
// Report success
response.generic_cmd_header.error_code = 0;
}
break;
}
case METHOD_ID_PERIPHERAL_SET_REMOCON:
{
vanilla_log("5,24 - str1: %s, str2: %s", response.payload, response.payload + 5);
vanilla_log("5,24 - str1: %s, str2: %s", request->payload, request->payload + 5);
response.generic_cmd_header.error_code = 0;
break;
}
@@ -227,7 +253,7 @@ void handle_time_packet(int skt, TimePacket *request)
void handle_command_packet(gamepad_context_t *info, int skt, CmdHeader *request)
{
vanilla_log("packet_type: %u, query_type: %u, payload_size: 0x%X", request->packet_type, request->query_type, request->payload_size);
// vanilla_log("packet_type: %u, query_type: %u, payload_size: 0x%X", request->packet_type, request->query_type, request->payload_size);
switch (request->packet_type)
{
case PACKET_TYPE_REQUEST:
-9
View File
@@ -20,15 +20,6 @@
#include "vanilla.h"
#include "util.h"
#if defined(_WIN32)
// Windows has no htobe32, and is always virtually little-endiam
#define htobe32(x) htonl(x)
#elif defined(__APPLE__)
// macOS has no htobe32
#include <libkern/OSByteOrder.h>
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif // __APPLE__
typedef struct
{
unsigned magic : 4;
+10 -1
View File
@@ -10,6 +10,15 @@
#define MAX(a,b) (((a)>(b))?(a):(b))
#define CLAMP(x, min, max) (MIN(MAX(x, min), max))
#if defined(_WIN32)
// Windows has no htobe32, and is always virtually little-endiam
#define htobe32(x) htonl(x)
#elif defined(__APPLE__)
// macOS has no htobe32
#include <libkern/OSByteOrder.h>
#define htobe32(x) OSSwapHostToBigInt32(x)
#endif // __APPLE__
size_t read_line_from_fd(int fd, char *output, size_t max_output_size);
size_t read_line_from_file(FILE *file, char *output, size_t max_output_size);
size_t get_max_path_length();
@@ -26,4 +35,4 @@ uint16_t crc16(const void* data, size_t len);
void print_hex(const void *data, size_t len);
#endif // VANILLA_UTIL_H
#endif // VANILLA_UTIL_H
+2 -1
View File
@@ -74,7 +74,8 @@ enum VanillaEvent
VANILLA_EVENT_VIBRATE,
VANILLA_EVENT_SYNC,
VANILLA_EVENT_ERROR,
VANILLA_EVENT_MIC
VANILLA_EVENT_MIC,
VANILLA_EVENT_BRIGHTNESS
};
enum VanillaRegion