diff --git a/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java b/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java index fef603f..bd054c3 100644 --- a/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java +++ b/android/app/src/main/java/com/mattkc/vanilla/VanillaActivity.java @@ -2,6 +2,7 @@ package com.mattkc.vanilla; import android.graphics.SurfaceTexture; import android.view.Surface; +import android.view.WindowManager; import java.util.concurrent.atomic.AtomicBoolean; @@ -56,4 +57,18 @@ public class VanillaActivity extends SDLActivity { videoSurfaceTexture = null; } } + + public static boolean setScreenBrightness(float brightness) { + final VanillaActivity activity = (VanillaActivity) mSingleton; + if (activity == null) { + return false; + } + + activity.runOnUiThread(() -> { + WindowManager.LayoutParams attributes = activity.getWindow().getAttributes(); + attributes.screenBrightness = Math.max(0.0f, Math.min(1.0f, brightness)); + activity.getWindow().setAttributes(attributes); + }); + return true; + } } diff --git a/buildroot/board/nx/rootfs/usr/bin/vanilla-nx-power-btn b/buildroot/board/nx/rootfs/usr/bin/vanilla-nx-power-btn deleted file mode 100755 index 1f41a54..0000000 --- a/buildroot/board/nx/rootfs/usr/bin/vanilla-nx-power-btn +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh - -# Listen for power button presses, in which case quit Vanilla (which should in turn reboot the Switch) - -DEV=/dev/input/event0 -APP=vanilla - -evtest "$DEV" 2>/dev/null | while read -r line; do - case "$line" in - *KEY_SLEEP*,\ value\ 1*) - pid="$(pidof "$APP" | awk '{print $1}')" - [ -n "$pid" ] && kill -TERM "$pid" - ;; - esac -done diff --git a/buildroot/board/nx/rootfs/usr/bin/vanilla-nx-volume-btn b/buildroot/board/nx/rootfs/usr/bin/vanilla-nx-volume-btn deleted file mode 100755 index d830e8f..0000000 --- a/buildroot/board/nx/rootfs/usr/bin/vanilla-nx-volume-btn +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# Listen for volume button presses, in which case increase/decrease volume in ALSA - -DEV=/dev/input/event5 - -evtest "$DEV" 2>/dev/null | while read -r line; do - case "$line" in - *KEY_VOLUMEDOWN*,\ value\ 1*) - amixer sset 'x Speaker' 5%- >/dev/null - ;; - *KEY_VOLUMEUP*,\ value\ 1*) - amixer sset 'x Speaker' 5%+ unmute >/dev/null - ;; - esac -done diff --git a/buildroot/board/nx/rootfs/usr/bin/vanilla-wrapper b/buildroot/board/nx/rootfs/usr/bin/vanilla-wrapper index 66eaebb..4f2ddee 100755 --- a/buildroot/board/nx/rootfs/usr/bin/vanilla-wrapper +++ b/buildroot/board/nx/rootfs/usr/bin/vanilla-wrapper @@ -5,19 +5,6 @@ export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/lib/aarch64-linux-gnu/tegra:/usr/l export DISPLAY=:0 export SDL_VIDEODRIVER=x11 -# Listen for power button presses -/usr/bin/vanilla-nx-power-btn & -POWER_BTN_WATCHER_PID=$! -/usr/bin/vanilla-nx-volume-btn & -VOLUME_BTN_WATCHER_PID=$! - -# Ensure watcher is closed -cleanup() { - kill "$POWER_BTN_WATCHER_PID" 2>/dev/null - kill "$VOLUME_BTN_WATCHER_PID" 2>/dev/null -} -trap cleanup EXIT - # Start X X "$DISPLAY" -nolisten tcp vt1 & X_PID=$! diff --git a/buildroot/configs/vanilla_nintendoswitch_defconfig b/buildroot/configs/vanilla_nintendoswitch_defconfig index 8fb601e..59e43a4 100644 --- a/buildroot/configs/vanilla_nintendoswitch_defconfig +++ b/buildroot/configs/vanilla_nintendoswitch_defconfig @@ -7,7 +7,7 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_TARGET_GENERIC_ROOT_PASSWD="root" # BR2_TARGET_GENERIC_GETTY is not set # BR2_TARGET_GENERIC_REMOUNT_ROOTFS_RW is not set -BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/nx/rootfs/" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/nx/rootfs/ $(BR2_EXTERNAL_VANILLA_PATH)/rootfs/" BR2_ROOTFS_POST_BUILD_SCRIPT="$(BR2_EXTERNAL_VANILLA_PATH)/board/nx/post_build.sh" BR2_LINUX_KERNEL=y BR2_LINUX_KERNEL_IMAGEGZ=y diff --git a/buildroot/configs/vanilla_orangepi_zero2w_defconfig b/buildroot/configs/vanilla_orangepi_zero2w_defconfig index 2463b42..90b47d0 100644 --- a/buildroot/configs/vanilla_orangepi_zero2w_defconfig +++ b/buildroot/configs/vanilla_orangepi_zero2w_defconfig @@ -7,7 +7,7 @@ BR2_GLOBAL_PATCH_DIR="board/orangepi/orangepi-zero2w/patches $(BR2_EXTERNAL_VAN BR2_TARGET_GENERIC_ISSUE="Welcome to Orangepi Zero2W" BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y # BR2_TARGET_GENERIC_GETTY is not set -BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/orangepizero3/rootfs/" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/orangepizero3/rootfs/ $(BR2_EXTERNAL_VANILLA_PATH)/rootfs/" BR2_ROOTFS_POST_BUILD_SCRIPT="board/orangepi/common/post-build.sh $(BR2_EXTERNAL_VANILLA_PATH)/board/orangepizero3/post_build.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="support/scripts/genimage.sh" BR2_ROOTFS_POST_SCRIPT_ARGS="-c $(BINARIES_DIR)/genimage.cfg" diff --git a/buildroot/configs/vanilla_orangepi_zero3_defconfig b/buildroot/configs/vanilla_orangepi_zero3_defconfig index f1b11e6..308455d 100644 --- a/buildroot/configs/vanilla_orangepi_zero3_defconfig +++ b/buildroot/configs/vanilla_orangepi_zero3_defconfig @@ -8,7 +8,7 @@ BR2_TARGET_GENERIC_ISSUE="Welcome to Orangepi Zero3" BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y # BR2_TARGET_GENERIC_GETTY is not set BR2_SYSTEM_DHCP="eth0" -BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/orangepizero3/rootfs/" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/orangepizero3/rootfs/ $(BR2_EXTERNAL_VANILLA_PATH)/rootfs/" BR2_ROOTFS_POST_BUILD_SCRIPT="board/orangepi/common/post-build.sh $(BR2_EXTERNAL_VANILLA_PATH)/board/orangepizero3/post_build.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="support/scripts/genimage.sh" BR2_ROOTFS_POST_SCRIPT_ARGS="-c $(BINARIES_DIR)/genimage.cfg" diff --git a/buildroot/configs/vanilla_raspberrypi0w_defconfig b/buildroot/configs/vanilla_raspberrypi0w_defconfig index e8b98ee..07f2e50 100644 --- a/buildroot/configs/vanilla_raspberrypi0w_defconfig +++ b/buildroot/configs/vanilla_raspberrypi0w_defconfig @@ -5,7 +5,7 @@ BR2_GLOBAL_PATCH_DIR="board/raspberrypi/patches" BR2_DOWNLOAD_FORCE_CHECK_HASHES=y BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y # BR2_TARGET_GENERIC_GETTY is not set -#BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/raspberrypi/rootfs" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/rootfs/" BR2_ROOTFS_POST_BUILD_SCRIPT="board/raspberrypi0w/post-build.sh $(BR2_EXTERNAL_VANILLA_PATH)/board/raspberrypi/post_build.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="board/raspberrypi0w/post-image.sh" BR2_LINUX_KERNEL=y diff --git a/buildroot/configs/vanilla_raspberrypi_64_defconfig b/buildroot/configs/vanilla_raspberrypi_64_defconfig index bf0eb0d..873b3a1 100644 --- a/buildroot/configs/vanilla_raspberrypi_64_defconfig +++ b/buildroot/configs/vanilla_raspberrypi_64_defconfig @@ -6,7 +6,7 @@ BR2_GLOBAL_PATCH_DIR="board/raspberrypi/patches" BR2_DOWNLOAD_FORCE_CHECK_HASHES=y BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y # BR2_TARGET_GENERIC_GETTY is not set -#BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/board/raspberrypi/rootfs" +BR2_ROOTFS_OVERLAY="$(BR2_EXTERNAL_VANILLA_PATH)/rootfs/" BR2_ROOTFS_POST_BUILD_SCRIPT="board/raspberrypi/post-build.sh $(BR2_EXTERNAL_VANILLA_PATH)/board/raspberrypi/post_build.sh" BR2_ROOTFS_POST_IMAGE_SCRIPT="board/raspberrypi/post-image.sh" BR2_LINUX_KERNEL=y diff --git a/buildroot/rootfs/usr/libexec/vanilla-volume b/buildroot/rootfs/usr/libexec/vanilla-volume new file mode 100755 index 0000000..4bb9186 --- /dev/null +++ b/buildroot/rootfs/usr/libexec/vanilla-volume @@ -0,0 +1,13 @@ +#!/bin/sh + +case "$1" in + up) + exec amixer -q sset 'x Speaker' 5%+ unmute + ;; + down) + exec amixer -q sset 'x Speaker' 5%- + ;; + *) + exit 2 + ;; +esac diff --git a/docker/android/Dockerfile b/docker/android/Dockerfile index 17c1142..fd4a2a5 100644 --- a/docker/android/Dockerfile +++ b/docker/android/Dockerfile @@ -2,7 +2,7 @@ FROM docker.io/ubuntu:24.04@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab30 # Install build dependencies RUN apt update && \ - apt -y install wget unzip git openjdk-17-jdk make && \ + apt -y install wget unzip git openjdk-17-jdk make autoconf automake libtool pkg-config && \ apt-get clean && \ apt-get autoremove --yes && \ rm -rf /var/lib/{apt,dpkg,cache,log}/ diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt index 0d8b822..199b844 100644 --- a/gui/CMakeLists.txt +++ b/gui/CMakeLists.txt @@ -309,6 +309,10 @@ if (ANDROID) endif() 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) diff --git a/gui/config.c b/gui/config.c index 1ddc0e7..d0203ac 100644 --- a/gui/config.c +++ b/gui/config.c @@ -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 = 0.5f; // 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){ diff --git a/gui/config.h b/gui/config.h index fa7b807..fbcd8eb 100644 --- a/gui/config.h +++ b/gui/config.h @@ -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; diff --git a/gui/main.c b/gui/main.c index eca0608..83fffbe 100644 --- a/gui/main.c +++ b/gui/main.c @@ -97,6 +97,14 @@ int SDL_main(int argc, const char **argv) goto exit; } +#ifndef ANDROID + // On Linux/Buildroot platforms like Raspberry Pi and Nintendo Switch, it + // is desirable for the brightness to be restored from config. On Android + // though, the brightness will be set externally so changing it here is + // probably not desirable. + vui_brightness_set(vui, vpi_config.screen_brightness); +#endif + vpi_menu_init(vui); while (vui_update_sdl(vui)) { diff --git a/gui/menu/menu.c b/gui/menu/menu.c index 88aa612..4b12202 100644 --- a/gui/menu/menu.c +++ b/gui/menu/menu.c @@ -13,6 +13,20 @@ #include "menu_main.h" #include "platform.h" +#if defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__) +#include +#include + +// Calling system() is synchronous and causes a noticeable pause during +// gameplay. This allows us to thunk from a separate detached thread so it +// doesn't interrupt the main thread. +static void *run_volume_helper(void *command) +{ + (void) system((const char *) command); + return NULL; +} +#endif + void vpi_mic_callback(void *userdata, const uint8_t *data, size_t len) { vanilla_send_audio(data, len); @@ -80,6 +94,31 @@ void vpi_menu_action(vui_context_t *vui, vpi_extra_action_t action) vpi_config.fullscreen = !vpi_config.fullscreen; vpi_config_save(); vui_set_fullscreen(vui, vpi_config.fullscreen); + break; + } + case VPI_ACTION_VOLUME_UP: + case VPI_ACTION_VOLUME_DOWN: + { +#if defined(__linux__) && !defined(ANDROID) && !defined(__ANDROID__) + // On some platforms (specifically our Buildroot platforms like the + // Raspberry Pi and Nintendo Switch), there's no built-in handler for + // the volume keys. So here we handle them ourselves. + const char *helper = "/usr/libexec/vanilla-volume"; + if (access(helper, X_OK) == 0) { + const char *command = action == VPI_ACTION_VOLUME_UP + ? "/usr/libexec/vanilla-volume up" + : "/usr/libexec/vanilla-volume down"; + pthread_t thread; + int error = pthread_create(&thread, NULL, run_volume_helper, (void *) command); + if (error == 0) { + error = pthread_detach(thread); + } + if (error != 0) { + vpilog("Failed to start volume helper thread: %s\n", strerror(error)); + } + } +#endif + break; } } } diff --git a/gui/menu/menu.h b/gui/menu/menu.h index 75c5b38..c7b157a 100644 --- a/gui/menu/menu.h +++ b/gui/menu/menu.h @@ -9,6 +9,8 @@ typedef enum { VPI_ACTION_TOGGLE_RECORDING, VPI_ACTION_DISCONNECT, VPI_ACTION_TOGGLE_FULLSCREEN, + VPI_ACTION_VOLUME_UP, + VPI_ACTION_VOLUME_DOWN } vpi_extra_action_t; void vpi_menu_init(vui_context_t *vui); diff --git a/gui/menu/menu_game.c b/gui/menu/menu_game.c index 3425a68..d9f9312 100644 --- a/gui/menu/menu_game.c +++ b/gui/menu/menu_game.c @@ -1088,6 +1088,21 @@ 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; + float b = (value - 1) / 4.0f; + if (b < 0.01f) b = 0.01f; + + vpi_config.screen_brightness = b; + vpi_config_save(); + vui_brightness_set(vui, b); + break; + } } vanilla_free_event(&event); diff --git a/gui/ui/ui.c b/gui/ui/ui.c index e93eb06..f37864e 100644 --- a/gui/ui/ui.c +++ b/gui/ui/ui.c @@ -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); + } +} diff --git a/gui/ui/ui.h b/gui/ui/ui.h index d1c62a6..8983567 100644 --- a/gui/ui/ui.h +++ b/gui/ui/ui.h @@ -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 diff --git a/gui/ui/ui_priv.h b/gui/ui/ui_priv.h index 71ad536..6ba5edd 100644 --- a/gui/ui/ui_priv.h +++ b/gui/ui/ui_priv.h @@ -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]; diff --git a/gui/ui/ui_sdl.c b/gui/ui/ui_sdl.c index 2e9eedc..e74d6f7 100644 --- a/gui/ui/ui_sdl.c +++ b/gui/ui/ui_sdl.c @@ -49,6 +49,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 @@ -187,6 +193,10 @@ void init_gamepad(vui_context_t *ctx) ctx->default_key_map[SDL_SCANCODE_F12] = VPI_ACTION_SCREENSHOT; ctx->default_key_map[SDL_SCANCODE_F11] = VPI_ACTION_TOGGLE_FULLSCREEN; ctx->default_key_map[SDL_SCANCODE_ESCAPE] = VPI_ACTION_DISCONNECT; + + ctx->default_key_map[SDL_SCANCODE_SLEEP] = VPI_ACTION_DISCONNECT; // Nintendo Switch power button + ctx->default_key_map[SDL_SCANCODE_VOLUMEUP] = VPI_ACTION_VOLUME_UP; + ctx->default_key_map[SDL_SCANCODE_VOLUMEDOWN] = VPI_ACTION_VOLUME_DOWN; } void find_valid_controller(vui_sdl_context_t *sdl_ctx) @@ -354,6 +364,26 @@ 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) +{ + vui_sdl_context_t *sdl_ctx = (vui_sdl_context_t *) ctx->platform_data; + + vpilog("SETTING BRIGHTNESS TO %.1f\n", brightness); + +#if defined(ANDROID) || defined(__ANDROID__) + if (vui_sdl_android_set_brightness(brightness) == 0) { + return; + } +#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) { @@ -537,7 +567,7 @@ int vui_sdl_event_thread(void *data) break; case SDL_CONTROLLERBUTTONDOWN: case SDL_CONTROLLERBUTTONUP: - if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { + if (sdl_ctx->controller && ev.cdevice.which == SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(sdl_ctx->controller))) { int btn_idx = ev.cbutton.button; int vanilla_btn; @@ -880,6 +910,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; diff --git a/gui/ui/ui_sdl_android.c b/gui/ui/ui_sdl_android.c index 1638657..d879365 100644 --- a/gui/ui/ui_sdl_android.c +++ b/gui/ui/ui_sdl_android.c @@ -12,6 +12,7 @@ static jobject video_surface; static jmethodID create_surface_method; static jmethodID update_texture_method; static jmethodID destroy_surface_method; +static jmethodID set_brightness_method; static int handle_jni_exception(JNIEnv *env, const char *operation) { @@ -53,7 +54,8 @@ static int init_jni(JNIEnv *env) create_surface_method = (*env)->GetStaticMethodID(env, video_class, "createVideoSurface", "(III)Landroid/view/Surface;"); update_texture_method = (*env)->GetStaticMethodID(env, video_class, "updateVideoSurfaceTexture", "()[F"); destroy_surface_method = (*env)->GetStaticMethodID(env, video_class, "destroyVideoSurface", "()V"); - if (!create_surface_method || !update_texture_method || !destroy_surface_method || + set_brightness_method = (*env)->GetStaticMethodID(env, video_class, "setScreenBrightness", "(F)Z"); + if (!create_surface_method || !update_texture_method || !destroy_surface_method || !set_brightness_method || handle_jni_exception(env, "looking up video surface methods") < 0) { (*env)->DeleteGlobalRef(env, video_class); video_class = NULL; @@ -69,6 +71,7 @@ static int init_jni(JNIEnv *env) create_surface_method = NULL; update_texture_method = NULL; destroy_surface_method = NULL; + set_brightness_method = NULL; return -1; } @@ -144,6 +147,21 @@ void *vui_sdl_android_get_video_surface(void) return video_surface; } +int vui_sdl_android_set_brightness(float brightness) +{ + JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv(); + if (!env || init_jni(env) < 0) { + return -1; + } + + jboolean dispatched = (*env)->CallStaticBooleanMethod(env, video_class, set_brightness_method, + (jfloat) brightness); + if (handle_jni_exception(env, "setting screen brightness") < 0) { + return -1; + } + return dispatched ? 0 : -1; +} + int vui_sdl_android_update_video_texture(float transform[16]) { JNIEnv *env = (JNIEnv *) SDL_AndroidGetJNIEnv(); @@ -192,4 +210,5 @@ void vui_sdl_android_destroy_video_texture(void) create_surface_method = NULL; update_texture_method = NULL; destroy_surface_method = NULL; + set_brightness_method = NULL; } diff --git a/gui/ui/ui_sdl_android.h b/gui/ui/ui_sdl_android.h index ce7af18..25ec263 100644 --- a/gui/ui/ui_sdl_android.h +++ b/gui/ui/ui_sdl_android.h @@ -8,5 +8,6 @@ int vui_sdl_android_create_video_texture(SDL_Renderer *renderer, int width, void *vui_sdl_android_get_video_surface(void); int vui_sdl_android_update_video_texture(float transform[16]); void vui_sdl_android_destroy_video_texture(void); +int vui_sdl_android_set_brightness(float brightness); #endif diff --git a/gui/ui/ui_sdl_linux.c b/gui/ui/ui_sdl_linux.c new file mode 100644 index 0000000..ee95184 --- /dev/null +++ b/gui/ui/ui_sdl_linux.c @@ -0,0 +1,103 @@ +#include "ui_sdl_linux.h" + +#include +#include +#include +#include +#include + +#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; +} diff --git a/gui/ui/ui_sdl_linux.h b/gui/ui/ui_sdl_linux.h new file mode 100644 index 0000000..f7d994c --- /dev/null +++ b/gui/ui/ui_sdl_linux.h @@ -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 diff --git a/gui/ui/ui_util.h b/gui/ui/ui_util.h index 7b6935c..56e48b6 100644 --- a/gui/ui/ui_util.h +++ b/gui/ui/ui_util.h @@ -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; } diff --git a/lib/gamepad/command.c b/lib/gamepad/command.c index 758b0d9..743d81a 100644 --- a/lib/gamepad/command.c +++ b/lib/gamepad/command.c @@ -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: diff --git a/lib/gamepad/video.c b/lib/gamepad/video.c index 526a6dc..a674242 100644 --- a/lib/gamepad/video.c +++ b/lib/gamepad/video.c @@ -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 -#define htobe32(x) OSSwapHostToBigInt32(x) -#endif // __APPLE__ - typedef struct { unsigned magic : 4; diff --git a/lib/vanilla.h b/lib/vanilla.h index 769329f..0f092f8 100644 --- a/lib/vanilla.h +++ b/lib/vanilla.h @@ -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