From 20ef0667b0b59a24d54b61d7401adbca58d3cb58 Mon Sep 17 00:00:00 2001 From: twinaphex Date: Wed, 11 Aug 2021 20:04:11 +0200 Subject: [PATCH] Add initial webOS port courtesy of informatic/webosbrew/mariotaku --- Makefile.common | 4 + Makefile.webos | 242 ++++++++++++++++++++++++++++++ config.def.h | 19 ++- configuration.c | 6 +- frontend/drivers/platform_unix.c | 19 ++- gfx/drivers_context/sdl_gl_ctx.c | 43 ++++-- input/drivers/sdl_input.c | 102 ++++++++++++- input/drivers_joypad/sdl_joypad.c | 11 ++ retroarch.c | 5 +- webos/.gitignore | 3 + webos/README.md | 14 ++ webos/appinfo.json | 11 ++ webos/build-icon.sh | 3 + webos/icon160.png | Bin 0 -> 10710 bytes 14 files changed, 456 insertions(+), 26 deletions(-) create mode 100644 Makefile.webos create mode 100644 webos/.gitignore create mode 100644 webos/README.md create mode 100644 webos/appinfo.json create mode 100644 webos/build-icon.sh create mode 100644 webos/icon160.png diff --git a/Makefile.common b/Makefile.common index 6d3aa84f8f..894960f6f2 100644 --- a/Makefile.common +++ b/Makefile.common @@ -2437,6 +2437,10 @@ ifeq ($(HAVE_VITAGLES), 1) OBJ += $(patsubst %.c,%.o,$(foreach dir,$(SOURCES), $(wildcard $(dir)/*.c))) endif +ifeq ($(HAVE_WEBOS), 1) + DEFINES += -DWEBOS +endif + ##################################### ### Android Play Feature Delivery ### ### (Play Store build core ### diff --git a/Makefile.webos b/Makefile.webos new file mode 100644 index 0000000000..1e5c4091fc --- /dev/null +++ b/Makefile.webos @@ -0,0 +1,242 @@ +include version.all + +$(call assert,$(call seq,$(TARGET_PREFIX),arm-webos-linux-gnueabi-),webOS SDK isn't setup properly. See https://github.com/webosbrew/meta-lg-webos-ndk#compile-program-by-command-line) + +WEBOS_FREETYPE_CONFIG ?= $(SDKTARGETSYSROOT)/usr/bin/freetype-config + +WEBOS_INC_DIR ?= $(SDKTARGETSYSROOT)/usr/include +WEBOS_LIB_DIR ?= $(SDKTARGETSYSROOT)/usr/lib + +######################### +######################### + +PACKAGE_NAME = com.retroarch +PACKAGE_VERSION := $(patsubst "%",%,$(RARCH_VERSION)) + +DEBUG ?= 0 + +HAVE_SCREENSHOTS = 0 +HAVE_REWIND = 1 +HAVE_7ZIP = 1 +HAVE_ACCESSIBILITY = 1 +HAVE_AL = 0 +# ALSA freezes when switching back from menu +HAVE_ALSA = 0 +HAVE_ANGLE = 0 +HAVE_AUDIOIO = 0 +HAVE_AUDIOMIXER = 1 +HAVE_BLISSBOX = 0 +HAVE_BSV_MOVIE = 1 +HAVE_BUILTINBEARSSL = 0 +HAVE_BUILTINFLAC = 1 +HAVE_DSP_FILTER = 1 +HAVE_VIDEO_FILTER = 1 +HAVE_STATIC_VIDEO_FILTERS = 1 +HAVE_STATIC_AUDIO_FILTERS = 1 +HAVE_FILTERS_BUILTIN = 1 +HAVE_BUILTINMBEDTLS = 1 +HAVE_BUILTINZLIB = 1 +HAVE_C99 = 1 +HAVE_CC = 1 +HAVE_CC_RESAMPLER = 1 +HAVE_NEAREST_RESAMPLER = 1 +HAVE_CHD = 1 +HAVE_COMMAND = 1 +HAVE_CXX = 1 +HAVE_DR_MP3 = 1 +HAVE_DYNAMIC = 1 +HAVE_DYLIB = 1 +HAVE_EGL = 0 +HAVE_FREETYPE = 0 +HAVE_GDI = 1 +HAVE_GETADDRINFO = 1 +HAVE_GETOPT_LONG = 1 +HAVE_GLSL = 1 +HAVE_GLSLANG = 0 +HAVE_GLSLANG_HLSL = 0 +HAVE_GLSLANG_OGLCOMPILER = 0 +HAVE_GLSLANG_OSDEPENDENT = 0 +HAVE_GLSLANG_SPIRV = 0 +HAVE_GLSLANG_SPIRV_TOOLS = 0 +HAVE_GLSLANG_SPIRV_TOOLS_OPT = 0 +HAVE_HID = 1 +HAVE_IBXM = 1 +HAVE_IMAGEVIEWER = 1 +HAVE_LANGEXTRA = 1 +HAVE_LIBRETRODB = 1 +HAVE_MENU = 1 +HAVE_MENU_COMMON = 1 +HAVE_NEON = 1 +HAVE_NETPLAYDISCOVERY = 1 +HAVE_NETPLAYDISCOVERY = 1 +HAVE_NETWORK_CMD = 1 +HAVE_NETWORKGAMEPAD = 1 +HAVE_NETWORKING = 1 +HAVE_GFX_WIDGETS = 1 +HAVE_MMAP = 1 +HAVE_ONLINE_UPDATER = 1 +HAVE_OPENDINGUX_FBDEV = 0 +HAVE_OPENGL = 0 +HAVE_OPENGL1 = 0 +HAVE_OPENGL_CORE = 0 +HAVE_OPENGLES = 1 +HAVE_OPENGLES3 = 0 +HAVE_OPENGLES3_1 = 0 +HAVE_OPENGLES3_2 = 0 +HAVE_OPENSSL = 0 +HAVE_OVERLAY = 1 +HAVE_PULSE = 1 +HAVE_RBMP = 1 +HAVE_RJPEG = 1 +HAVE_RPILED = 0 +HAVE_RPNG = 1 +HAVE_RUNAHEAD = 1 +HAVE_SDL = 0 +HAVE_SDL2 = 1 +HAVE_SHADERPIPELINE = 1 +HAVE_STB_FONT = 1 +HAVE_STB_IMAGE = 1 +HAVE_STB_VORBIS = 1 +HAVE_STDIN_CMD = 0 +HAVE_STRCASESTR = 1 +HAVE_THREADS = 1 +HAVE_UDEV = 0 +HAVE_RGUI = 1 +HAVE_MATERIALUI = 0 +HAVE_XMB = 1 +HAVE_OZONE = 1 +HAVE_ZLIB = 1 +HAVE_CONFIGFILE = 1 +HAVE_PATCH = 1 +HAVE_CHEATS = 1 +HAVE_CHEEVOS = 1 +HAVE_LIBSHAKE = 1 +HAVE_UPDATE_ASSETS = 1 +HAVE_UPDATE_CORES = 1 + +OS = Linux +TARGET = retroarch + +OBJ := +LINK := $(CXX) +DEF_FLAGS += -ffunction-sections -fdata-sections +DEF_FLAGS += -I. -Ideps -Ideps/stb -DWEBOS=1 -MMD +DEF_FLAGS += -Wall -Wno-unused-variable +LIBS := -ldl -lz -lrt -pthread +CFLAGS := +CXXFLAGS := -fno-exceptions -fno-rtti -std=c++11 -D__STDC_CONSTANT_MACROS +ASFLAGS := +LDFLAGS := -Wl,--gc-sections +INCLUDE_DIRS = -I$(WEBOS_INC_DIR) +LIBRARY_DIRS = -L$(WEBOS_LIB_DIR) +DEFINES := -DRARCH_INTERNAL -D_FILE_OFFSET_BITS=64 -UHAVE_STATIC_DUMMY +DEFINES += -DHAVE_C99=1 -DHAVE_CXX=1 -D_GNU_SOURCE +DEFINES += -DHAVE_GETOPT_LONG=1 -DHAVE_STRCASESTR=1 -DHAVE_DYNAMIC=1 +DEFINES += -DHAVE_FILTERS_BUILTIN +DEFINES += -DHAVE_SDL2 +DEFINES += -DHAVE_PULSE +DEFINES += -DHAVE_NETWORKING -DHAVE_ONLINE_UPDATER -DHAVE_UPDATE_ASSETS -DHAVE_UPDATE_CORES + +SDL2_CFLAGS := $(shell pkg-config --cflags sdl2) +SDL2_LIBS := $(shell pkg-config --libs sdl2) +OPENGLES_LIBS = -lGLESv2 +PULSE_LIBS = $(shell pkg-config --libs libpulse) +MMAP_LIBS = -lc +NEON_CFLAGS = -mfpu=neon +NEON_ASFLAGS = -mfpu=neon +NETWORKING_LIBS = -lc + +OBJDIR_BASE := obj-unix + +ifeq ($(DEBUG), 1) + OBJDIR := $(OBJDIR_BASE)/debug + DEF_FLAGS += -O0 -g -DDEBUG -D_DEBUG +else + OBJDIR := $(OBJDIR_BASE)/release + DEF_FLAGS += -O2 -DNDEBUG +endif + +include Makefile.common + +DEF_FLAGS += $(INCLUDE_DIRS) +LDFLAGS += $(CFLAGS) $(CXXFLAGS) $(DEF_FLAGS) +CFLAGS += $(DEF_FLAGS) +CXXFLAGS += $(DEF_FLAGS) + +HEADERS = $(wildcard */*/*.h) $(wildcard */*.h) $(wildcard *.h) + +Q := @ + +RARCH_OBJ := $(addprefix $(OBJDIR)/,$(OBJ)) + +define APPINFO +{ + "id": "$(PACKAGE_NAME)", + "version": "$(PACKAGE_VERSION)", + "vendor": "webosbrew.org", + "title": "RetroArch", + "icon": "icon160.png", + "main": "retroarch", + "iconColor": "#333333", + "type": "native", + "appDescription": "Emulation frontend" +} +endef +export APPINFO + +all: $(TARGET) ipk + +-include $(RARCH_OBJ:.o=.d) + +SYMBOL_MAP := -Wl,-Map=output.map + +$(TARGET): $(RARCH_OBJ) + @$(if $(Q), $(shell echo echo LD $@),) + $(Q)$(LINK) -o $@ $(RARCH_OBJ) $(LIBS) $(LDFLAGS) $(LIBRARY_DIRS) + +$(OBJDIR)/%.o: %.c + @mkdir -p $(dir $@) + @$(if $(Q), $(shell echo echo CC $<),) + $(Q)$(CC) $(CPPFLAGS) $(CFLAGS) $(DEFINES) -c -o $@ $< + +$(OBJDIR)/%.o: %.cpp + @mkdir -p $(dir $@) + @$(if $(Q), $(shell echo echo CXX $<),) + $(Q)$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(DEFINES) -MMD -c -o $@ $< + +$(OBJDIR)/%.o: %.m + @mkdir -p $(dir $@) + @$(if $(Q), $(shell echo echo OBJC $<),) + $(Q)$(CXX) $(OBJCFLAGS) $(DEFINES) -MMD -c -o $@ $< + +$(OBJDIR)/%.o: %.S $(HEADERS) + @mkdir -p $(dir $@) + @$(if $(Q), $(shell echo echo AS $<),) + $(Q)$(CC) $(CFLAGS) $(ASFLAGS) $(DEFINES) -c -o $@ $< + +clean: + rm -rf $(OBJDIR_BASE) + rm -f $(TARGET) + rm -f *.d + rm -rf webos/*.ipk + rm -rf webos/dist + +ipk: $(TARGET) + rm -rf webos/dist + mkdir -p webos/dist/lib + echo "$$APPINFO" > webos/dist/appinfo.json + cp -t webos/dist -vf $(TARGET) webos/icon160.png + cp -t webos/dist/lib -vf $(WEBOS_LIB_DIR)/libstdc++.so.6 + $(STRIP) webos/dist/$(TARGET) + cd webos && ares-package dist + +install: ipk + ares-install webos/$(PACKAGE_NAME)_$(PACKAGE_VERSION)_$(ARCH).ipk + +launch: install + ares-launch com.retroarch + +.PHONY: all clean ipk + +print-%: + @echo '$*=$($*)' diff --git a/config.def.h b/config.def.h index f6fecbece3..5eca3a88a8 100644 --- a/config.def.h +++ b/config.def.h @@ -224,8 +224,13 @@ /* Window */ /* Window size. A value of 0 uses window scale * multiplied by the core framebuffer size. */ +#if defined(WEBOS) +#define DEFAULT_WINDOW_WIDTH 1920 +#define DEFAULT_WINDOW_HEIGHT 1080 +#else #define DEFAULT_WINDOW_WIDTH 1280 #define DEFAULT_WINDOW_HEIGHT 720 +#endif /* Fullscreen resolution. A value of 0 uses the desktop * resolution. */ @@ -251,8 +256,18 @@ */ #define DEFAULT_WINDOW_OPACITY 100 -/* Whether to show the usual window decorations like border, titlebar etc. */ +/* DEFAULT_WINDOW_DECORATIONS: + Whether to show the usual window decorations like border, titlebar etc. */ +/* DEFAULT_WINDOW_SAVE_POSITIONS: + Whether to remember window positions + */ +#ifdef WEBOS +#define DEFAULT_WINDOW_DECORATIONS false +#define DEFAULT_WINDOW_SAVE_POSITIONS true +#else #define DEFAULT_WINDOW_DECORATIONS true +#define DEFAULT_WINDOW_SAVE_POSITIONS false +#endif #if defined(RARCH_CONSOLE) || defined(__APPLE__) #define DEFAULT_LOAD_DUMMY_ON_CORE_SHUTDOWN false @@ -1061,7 +1076,7 @@ static const bool audio_enable_menu_bgm = false; #define DEFAULT_REWIND_GRANULARITY 1 #endif /* Pause gameplay when gameplay loses focus. */ -#ifdef EMSCRIPTEN +#if defined(EMSCRIPTEN) || defined(WEBOS) #define DEFAULT_PAUSE_NONACTIVE false #else #define DEFAULT_PAUSE_NONACTIVE true diff --git a/configuration.c b/configuration.c index 89ddae6af4..805abc443c 100644 --- a/configuration.c +++ b/configuration.c @@ -427,6 +427,8 @@ static const enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_XINPUT; static const enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_ANDROID; #elif defined(EMSCRIPTEN) && defined(HAVE_SDL2) static const enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_SDL2; +#elif defined(WEBOS) && defined(HAVE_SDL2) +static const enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_SDL2; #elif defined(EMSCRIPTEN) static const enum input_driver_enum INPUT_DEFAULT_DRIVER = INPUT_RWEBINPUT; #elif defined(_WIN32) && defined(HAVE_DINPUT) @@ -479,6 +481,8 @@ static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_XINPUT; static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_GX; #elif defined(WIIU) static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_WIIU; +#elif defined(WEBOS) +static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_SDL; #elif defined(_XBOX) static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_XDK; #elif defined(PS2) @@ -1840,7 +1844,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("video_msg_bgcolor_enable", &settings->bools.video_msg_bgcolor_enable, true, message_bgcolor_enable, false); SETTING_BOOL("video_window_show_decorations", &settings->bools.video_window_show_decorations, true, DEFAULT_WINDOW_DECORATIONS, false); - SETTING_BOOL("video_window_save_positions", &settings->bools.video_window_save_positions, true, false, false); + SETTING_BOOL("video_window_save_positions", &settings->bools.video_window_save_positions, true, DEFAULT_WINDOW_SAVE_POSITIONS, false); SETTING_BOOL("sustained_performance_mode", &settings->bools.sustained_performance_mode, true, sustained_performance_mode, false); diff --git a/frontend/drivers/platform_unix.c b/frontend/drivers/platform_unix.c index 44946f9c93..70ec2d362c 100644 --- a/frontend/drivers/platform_unix.c +++ b/frontend/drivers/platform_unix.c @@ -2123,34 +2123,41 @@ static int frontend_unix_parse_drive_list(void *data, bool load_content) FILE_TYPE_DIRECTORY, 0, 0); } else - { menu_entries_append_enum(list, "/storage/emulated/0", msg_hash_to_str(MSG_REMOVABLE_STORAGE), enum_idx, FILE_TYPE_DIRECTORY, 0, 0); - } + menu_entries_append_enum(list, "/storage", msg_hash_to_str(MSG_REMOVABLE_STORAGE), enum_idx, FILE_TYPE_DIRECTORY, 0, 0); if (!string_is_empty(internal_storage_app_path)) - { menu_entries_append_enum(list, internal_storage_app_path, msg_hash_to_str(MSG_EXTERNAL_APPLICATION_DIR), enum_idx, FILE_TYPE_DIRECTORY, 0, 0); - } if (!string_is_empty(app_dir)) - { menu_entries_append_enum(list, app_dir, msg_hash_to_str(MSG_APPLICATION_DIR), enum_idx, FILE_TYPE_DIRECTORY, 0, 0); - } +#elif defined(WEBOS) + if (path_is_directory("/media/internal")) + menu_entries_append_enum(list, "/media/internal", + msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR), + enum_idx, + FILE_TYPE_DIRECTORY, 0, 0); + + if (path_is_directory("/tmp/usb")) + menu_entries_append_enum(list, "/tmp/usb", + msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR), + enum_idx, + FILE_TYPE_DIRECTORY, 0, 0); #else char base_path[PATH_MAX] = {0}; char udisks_media_path[PATH_MAX] = {0}; diff --git a/gfx/drivers_context/sdl_gl_ctx.c b/gfx/drivers_context/sdl_gl_ctx.c index b8eb56e6f6..68cf7b4320 100644 --- a/gfx/drivers_context/sdl_gl_ctx.c +++ b/gfx/drivers_context/sdl_gl_ctx.c @@ -32,6 +32,10 @@ #include "../common/sdl2_common.h" #endif +#if defined(WEBOS) && defined(HAVE_SDL2) +#include +#endif + typedef struct gfx_ctx_sdl_data { int width; @@ -41,6 +45,7 @@ typedef struct gfx_ctx_sdl_data bool full; bool resized; + bool subsystem_inited; #ifdef HAVE_SDL2 SDL_Window *win; @@ -73,6 +78,19 @@ static void sdl_ctx_destroy_resources(gfx_ctx_sdl_data_t *sdl) sdl->win = NULL; } +static void sdl_ctx_destroy(void *data) +{ + gfx_ctx_sdl_data_t *sdl = (gfx_ctx_sdl_data_t*)data; + + if (!sdl) + return; + + sdl_ctx_destroy_resources(sdl); + if (sdl->subsystem_inited) + SDL_QuitSubSystem(SDL_INIT_VIDEO); + free(sdl); +} + static void *sdl_ctx_init(void *video_driver) { gfx_ctx_sdl_data_t *sdl = (gfx_ctx_sdl_data_t*) @@ -86,6 +104,12 @@ static void *sdl_ctx_init(void *video_driver) XInitThreads(); #endif +#ifdef WEBOS + SDL_SetHint(SDL_HINT_WEBOS_ACCESS_POLICY_KEYS_BACK, "true"); + SDL_SetHint(SDL_HINT_WEBOS_ACCESS_POLICY_KEYS_EXIT, "true"); + SDL_SetHint(SDL_HINT_WEBOS_CURSOR_SLEEP_TIME, "1000"); +#endif + /* Initialise graphics subsystem, if required */ if (sdl_subsystem_flags == 0) { @@ -96,6 +120,7 @@ static void *sdl_ctx_init(void *video_driver) { if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) goto error; + sdl->subsystem_inited = true; } RARCH_LOG("[SDL_GL] SDL %i.%i.%i gfx context driver initialized.\n", @@ -107,24 +132,11 @@ error: RARCH_WARN("[SDL_GL]: Failed to initialize SDL gfx context driver: %s\n", SDL_GetError()); - sdl_ctx_destroy_resources(sdl); - - if (sdl) - free(sdl); + sdl_ctx_destroy(sdl); return NULL; } -static void sdl_ctx_destroy(void *data) -{ - gfx_ctx_sdl_data_t *sdl = (gfx_ctx_sdl_data_t*)data; - - if (!sdl) - return; - - sdl_ctx_destroy_resources(sdl); - free(sdl); -} static enum gfx_ctx_api sdl_ctx_get_api(void *data) { return sdl_api; } @@ -201,7 +213,8 @@ static bool sdl_ctx_set_video_mode(void *data, { unsigned display = video_monitor_index; - sdl->win = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED_DISPLAY(display), + sdl->win = SDL_CreateWindow("RetroArch", + SDL_WINDOWPOS_UNDEFINED_DISPLAY(display), SDL_WINDOWPOS_UNDEFINED_DISPLAY(display), width, height, SDL_WINDOW_OPENGL | fsflag); } diff --git a/input/drivers/sdl_input.c b/input/drivers/sdl_input.c index e6a64be02c..d0961411ac 100644 --- a/input/drivers/sdl_input.c +++ b/input/drivers/sdl_input.c @@ -35,6 +35,11 @@ #include "../../gfx/common/sdl2_common.h" #endif +#ifdef WEBOS +#include +#include +#endif + /* TODO/FIXME - * fix game focus toggle */ @@ -55,6 +60,15 @@ typedef struct sdl_input int mouse_wr; } sdl_input_t; +#ifdef WEBOS +enum sdl_webos_special_key { + sdl_webos_spkey_back, + sdl_webos_spkey_size, +}; + +static uint8_t sdl_webos_special_keymap[sdl_webos_spkey_size] = {0}; +#endif + static void *sdl_input_init(const char *joypad_driver) { sdl_input_t *sdl = (sdl_input_t*)calloc(1, sizeof(*sdl)); @@ -77,6 +91,18 @@ static bool sdl_key_pressed(int key) unsigned sym = rarch_keysym_lut[(enum retro_key)key]; #endif +#ifdef WEBOS + if ( (key == RETROK_BACKSPACE ) + && sdl_webos_special_keymap[sdl_webos_spkey_back]) + { + /* Reset to unpressed state */ + sdl_webos_special_keymap[sdl_webos_spkey_back] = 0; + return true; + } + if (key == RETROK_F1 && keymap[SDL_WEBOS_SCANCODE_EXIT]) + return true; +#endif + if (sym >= (unsigned)num_keys) return false; @@ -164,6 +190,27 @@ static int16_t sdl_input_state( return sdl->mouse_l; case RETRO_DEVICE_ID_MOUSE_RIGHT: return sdl->mouse_r; +#ifdef WEBOS + case RETRO_DEVICE_ID_MOUSE_WHEELUP: + /* Note: webOS wheel is reversed */ + if (sdl->mouse_wd != 0) + { + sdl->mouse_wd = 0; + return 1; + } + return 0; + case RETRO_DEVICE_ID_MOUSE_WHEELDOWN: + if (sdl->mouse_wu != 0) + { + sdl->mouse_wu = 0; + return 1; + } + return 0; + case RETRO_DEVICE_ID_MOUSE_X: + return sdl->mouse_abs_x; + case RETRO_DEVICE_ID_MOUSE_Y: + return sdl->mouse_abs_y; +#else case RETRO_DEVICE_ID_MOUSE_WHEELUP: return sdl->mouse_wu; case RETRO_DEVICE_ID_MOUSE_WHEELDOWN: @@ -172,6 +219,7 @@ static int16_t sdl_input_state( return sdl->mouse_x; case RETRO_DEVICE_ID_MOUSE_Y: return sdl->mouse_y; +#endif case RETRO_DEVICE_ID_MOUSE_MIDDLE: return sdl->mouse_m; case RETRO_DEVICE_ID_MOUSE_BUTTON_4: @@ -330,9 +378,41 @@ static void sdl_input_poll(void *data) { if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) { - uint16_t mod = 0; + uint16_t mod = 0; unsigned code = input_keymaps_translate_keysym_to_rk( event.key.keysym.sym); +#ifdef WEBOS + switch ((int) event.key.keysym.scancode) + { + case SDL_WEBOS_SCANCODE_BACK: + /* Because webOS is sending DOWN/UP at the same time, + we save this flag for later */ + sdl_webos_special_keymap[sdl_webos_spkey_back] |= event.type == SDL_KEYDOWN; + code = RETROK_BACKSPACE; + break; + case SDL_WEBOS_SCANCODE_RED: + code = RETROK_x; + break; + case SDL_WEBOS_SCANCODE_GREEN: + code = RETROK_z; + break; + case SDL_WEBOS_SCANCODE_YELLOW: + code = RETROK_s; + break; + case SDL_WEBOS_SCANCODE_BLUE: + code = RETROK_a; + break; + case SDL_WEBOS_SCANCODE_EXIT: + code = RETROK_F1; + break; + default: + break; + } + + /* Disable cursor when using the buttons */ + if (code && code != RETROK_RETURN) + SDL_webOSCursorVisibility(0); +#endif if (event.key.keysym.mod & KMOD_SHIFT) mod |= RETROKMOD_SHIFT; @@ -396,3 +476,23 @@ input_driver_t input_sdl = { #endif NULL }; + +#ifdef WEBOS +SDL_bool SDL_webOSCursorVisibility(SDL_bool visible) +{ + static SDL_bool (*fn)(SDL_bool visible) = NULL; + static bool dlsym_called = false; + if (!dlsym_called) + { + fn = dlsym(RTLD_NEXT, "SDL_webOSCursorVisibility"); + dlsym_called = true; + } + if (!fn) + { + SDL_ShowCursor(SDL_DISABLE); + SDL_ShowCursor(SDL_ENABLE); + return SDL_TRUE; + } + return fn(visible); +} +#endif diff --git a/input/drivers_joypad/sdl_joypad.c b/input/drivers_joypad/sdl_joypad.c index 4d272a32e4..2d48aa3e20 100644 --- a/input/drivers_joypad/sdl_joypad.c +++ b/input/drivers_joypad/sdl_joypad.c @@ -138,6 +138,17 @@ static void sdl_pad_connect(unsigned id) vendor = guid_ptr[0]; product = guid_ptr[1]; #endif +#ifdef WEBOS + if (vendor == 0x9999 && product == 0x9999) + { + RARCH_WARN("[SDL_JOYPAD]: Ignoring pad #%d (vendor: %d; product: %d)\n", id, vendor, product); + if (pad->joypad) + SDL_JoystickClose(pad->joypad); + + pad->joypad = NULL; + return; + } +#endif #endif input_autoconfigure_connect( diff --git a/retroarch.c b/retroarch.c index db80f15d8c..0e9b86dbbf 100644 --- a/retroarch.c +++ b/retroarch.c @@ -34467,9 +34467,12 @@ static bool retroarch_parse_input_and_config( optstring = "hs:fvS:A:U:DN:d:" BSV_MOVIE_ARG NETPLAY_ARG DYNAMIC_ARG FFMPEG_RECORD_ARG CONFIG_FILE_ARG; -#ifdef ORBIS +#if defined(ORBIS) argv = &(argv[2]); argc = argc - 2; +#elif defined(WEBOS) + argv = &(argv[1]); + argc = argc - 1; #endif #ifndef HAVE_MENU diff --git a/webos/.gitignore b/webos/.gitignore new file mode 100644 index 0000000000..9b51aa2c69 --- /dev/null +++ b/webos/.gitignore @@ -0,0 +1,3 @@ +dist/ +prefix/ +*.ipk diff --git a/webos/README.md b/webos/README.md new file mode 100644 index 0000000000..47a9dc7762 --- /dev/null +++ b/webos/README.md @@ -0,0 +1,14 @@ +## Building +```sh +make -f Makefile.webos clean +make -f Makefile.webos -j$(ncpu --all) ipk +``` + +## Testing +```sh +# Install and launch via ares-launch +make -f Makefile.webos launch + +# Start installed application via SSH +XDG_RUNTIME_DIR=/tmp/xdg /usr/bin/jailer -t native_devmode -i com.retroarch -p /media/developer/apps/usr/palm/applications/com.retroarch /media/developer/apps/usr/palm/applications/com.retroarch/retroarch --verbose --verbose +``` diff --git a/webos/appinfo.json b/webos/appinfo.json new file mode 100644 index 0000000000..79ab0fae82 --- /dev/null +++ b/webos/appinfo.json @@ -0,0 +1,11 @@ +{ + "id": "com.retroarch", + "version": "1.9.8", + "vendor": "libretro.com", + "title": "RetroArch", + "icon": "icon160.png", + "main": "retroarch", + "iconColor": "#333333", + "type": "native", + "appDescription": "Official Libretro frontend" +} diff --git a/webos/build-icon.sh b/webos/build-icon.sh new file mode 100644 index 0000000000..0ae88daca9 --- /dev/null +++ b/webos/build-icon.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +convert -background none media/retroarch.svg -resize 160x160 -negate webos/icon160.png diff --git a/webos/icon160.png b/webos/icon160.png new file mode 100644 index 0000000000000000000000000000000000000000..e03560b1d435d4ee30b8f1e6373308a745aaf2a2 GIT binary patch literal 10710 zcmd6NS5OmP@NOCj5UTW^P^3eocL*J60qH$}^rnD-QWCl}gNg_u0wM+w2}n~~5R@he z3P>j)Md`wiBK7ZmybpKgKK>u}e7oQ7H?uEi&Y7LPkFhYKr{$&v008tR#`@O(u;l+3 z6Xf4{%PxH7A3%K3=4b$*?g9J^OZ6{Cco|!p0|0lg0sy#V0N~^wh5H2nM4|wIbq@eQ z?I{4j5mxlUO5Vef5*fiGaUm0l4RaB4-NCFO3>VIr~IJy)iTm;{dn%r!~tJLBVafk0r_aK3w+T- zxa|?ZG%ayoCgI*IY%xgLGObrI_g8>N5Y5@l4>GQbYBNbnZtI1vkFmHD&r+1e$&`?fT+)y*N1Qvpp!E>%>~tFZKx71#2bb- zl)h0Y*|xt!e8huX>&jr+t!K@#9u6_8F;0$x z>aK!``<{`~l%1iXyj72_o^ZnY`v;OX!kq!(DBv&Iq53d-f?E zf|@T_9siU@f0}al-tQJjQ=jdxJF z*`1jx-R=1akp$zNCP8#<`flqz`8WKG?bW)U_;p?f)}Y^CrHZyPXi7xpVUGeXo?pfs z&j~)5ldf;9NEg2QR{uya_oleuoU0)I#r=^lTo9gwB$#@s;B@{?cp}KA2NX@wJochf zUW?-#Fm|ph#|SC&&F3+hmUYUh?);l%IqRU#v9kP9XKbA}HA_&4JCk=^c4>OOR4%+hJG_|H|G9)`B~sV&DCqb%(|U0#)0x^xqk^O@ z3qK!*?GZ+^I8kKl{gmbp7sqk~gWQ5}_`s8uMVJ>`znX+xS%>kNF*89v>=zBEB42=y z%ivOL1r;?F8&97p<{j9q2CmAdBYf*s-|Nmy-nh{lEX4DES6R8!@LcG*fmG*!xb%;| z<|XICHaI@8Wu9T61EM2dY*3Oo0rnWa!&K+69Q}exOHd(O`pnM3ZZjLjT~OgW=;k%_k5p2$VmjnQ z&zzQ%_kr@fq{HV7Z+fPfgxBd_H?cWJ&g5k&Vj$3KUy>b>dcm}lDC?|u2bA8^d%sQH zTv*;*|3kFwd|_Cwy0t-KvGkW){ZcLaLXTsZ4Y^%6mRNw1GMM7g)i+tM$xqVj+o%;l z3@0js;XKCLDz1z<5xBOyONTz)tTat7k7Kk+G%DLKvjG$%0+wl-`n@Q8JN%mbUROzN zOydr0x7N>Cb>pw+QRgB?E4GEU_jw*F@~Qwag!($$!+bx~3|TiCBGsyL=tIDTQZ>-l5Q9fD&fdY7{*(6Nm+nC?d8htJRn()?;{M2@xm+sO=BCF>EEFD+>*1jyKL(m;@I*ivq^jD zO(REf#Cn=#Bai&h6|3ftQ}>CD!5*IiRL%xJfuu;xaH}Kq!VM-ZwpnR>x0V{clX^6C zFRd(q9%m?&TPScXwP3V=Bvk6ZYJjaC?nu++Ih}tnb~X)Fsp_rB$|Jj4g#W9FJyJuZ z0-juX3_~!w^db-0{nQ_D)bt8XRB5)`cXdK>UfiVlZd))#hc@;xRA7z{b_CY@MXlca zv9`8*I2L6NNEC}LUgEJOlQ?r4QttC0_B+W_nH%mb9?@#EYBcZTA&?H*y+;p}mUJ2u zyAM2;gb0cP!<{@X8H^x>PI&f0N~oE~4Bhq=Wy7VlRGt?&KM#Mjo&4W|%q<5Xq_Ffp zm)3*ifL5|*H3YBu;zo%No)+@?B}$Osjfa`Q8IGva*I>A~Ql3X!qT`jXg!hwbo~>}uawJb67l)rK=xdC&TJC&|)|{FxcE>)oAl zh>+nZmn=M%0qglrh9;jr^cd$M%nXOeP1Y}7ct@XcP&en5Xt8ptp$%Ps6*G1+K-VaO z&Rt8VC}w57&OgL%SL!QPJNG&D^yy?61)9?r8`2A%ed$%$<%PBkW1V8%=noneW`NFPxPCU}eSGV;($P}ELXQOdLmQA~$ zxoEYjvY*&DM>V$1RnBj{;1`z*tmuqP~LW+XkI~4xAQu@-jFIWW_1MO)ioGob(cw#8cY}84X_L$Y6Oc&Ak|`e$9KpW$w=wQEXk4$$gdWb zy z7tW_~hjx?uM^2?UZ{Qx(YCtODP_Jpsb{O6YNc{;ixsW{zqn2xTxm6D>KfwKDK|ZW< zE&c7|H%c`oNjx`?yaJB`a6pFAZ5bOcJU^D{4#x;x4(O&RC&lq&~l1v z%x*swLsAgz5chkk>=7&~F*O_chmjCba=|uj3ASYfea2--s8#;HM$}{vhyvzeFo(4E z{GdVmuss=vb{07|Qp3;U$0;7cz%MN-HPm9CkbQO}Nw!Vw_Bnp)5b*1-Bqua6!m#?@ z2*eK*j^KUQJrJbwU#KY;ZY(`RySSsI0XN(kIU@1z zj&xGN_3YqE)vJ>wh|nY>o&@Ov+8tnHP(VdYhY+vljjrRc%AN~eX?$9oX;m45q&V_{ zb@OmfvyvNvPP+d6cX$s&+HsV}rS{Ni%Jfz0T6)$x2@P33r4B&<559Pf!v79S>Jeeu zOg}#vD)fXVy%Ar9bp4S|ZiFhaHSV=NH8;Vhb=~39&+^SEE%c(A8xb+fRpC1z8(jShT(WD)(%1`% z3aIH`o9`2Kt_ugCYGum8axyv99ueC4BeUm@kcv`bmGM;-e%NVIwViAN&n6s!<)o4a zNHFnsvdtAOZ~a)85v4|~u?D%=dEZEE=TNc0Pt%onBI0Q#1K4ffdOGRcvkwa*SNK31 z(h3>U)iyLE=d~t}RhD}=;=`9&Cg-_|inpi{O;E~lBIEdfB@4(9&SY)d)q_9HF9!xO z;aoPcG*2rl^v)bepYC;j^v7-SQNl!UMRufH!K0w8Vt6Be63(Thv=Ep>Bj)UBwK3EV z#{lYT!M&yx2*uWuYw0K4HeNdgD>aD+KasvHdr+fv2sNF$4_XCcnD6lWV_^%Ax$GKeV^|wo=#0eL`y$H5fE1gs5P7 zYEh+1K!NINB?BgouK*>et}|DjS5JGV)4v;|BrVG#BXmX?ii;R$49WLb0lzg7!7@~E z5m^!za1&v7njb7^;~wDu)mdB}(a@eCv9^PYR1~HMnXrJ)|~nxr{~CpU|OZQN-3U9ol9>Xtjy zA9#X{kYi9IMqsF0(R?OgT*~;Edb1hq;N)S%5?BBQvN!yXzY(xDzl53-J2so?To_ag)#)%5o$7%bC3oV8zeFzKp7J!S3AEwy zxJS0=zaPvf0OG9?3Oc&P=r1$nj%4hCr9(K(82YKnJK@u!A`t^uYfAjwjn=e}ILYuu zb}-m*TOHH&970tlx(DhoB%{B&spMj@K2GW{!|I>6vjfuu$NO6WlI;vMM}ZpyjQ&c7 zFL~n@e^}|vWqx;;(5ZVp?{2U61M5r|5nm8CxKdhwX5O~ zF^FI3aOc?-D?g*Y{V4Qz&3?(MCgFmu=(m3@6-_ldF5%XO4JNcxERWIKg%Q^A_59U( z7X8sR8DAz!jjRgu$0&4mAz_r~_UX(TBIi_RNT!L(21?eIE&_Vx7Pcd*$_{vp@4`VL z(2N!;flBC1DsI2?8`ZZbx}Uu*e?JDIzA;9nkZM;UIUn1WF5(Ji>e13u{X4Z4c5L zepUI3*`XN6-Hp8c7)y$IU8zW%!@Y}k1}f{^wsa7H`lh>pZbj)(hQdBHTbu38dX!&6 z{PDw^eoetvyp`5j+0ICDHl;Ya0)4)u!btKr&fy%ZjI8}&muZk%HXq_7hBzT$hMdSI z%)+E{=jn?zz^8{FN8=F--?_dOr)1T+$%Z}KD>+eS3I^EDd|-W83pRc9(6)#dlGQ7K zdr{amqfQA!aB9A>{lW)mrcYDu=<&NOiV#Zi+P&S4Mv%8~LTQxj2nTK|V4#foFE#o_ zMl$8|NL@@_Knu00v~e;0#MLmsfv|oj?Ra2Uj^_N=y92*~{*2eRW_Gl0bG(f+2@4Rk z+1&VKs39zyCLp1EwZf>&2VaFIDYLn7t-U@#QP(*j-yDP%!4YW*FM@kr|n77gC;;JQ7TPIPjjJz%3=BN}!{S|Fg{>wuCiapiR4q z8||Kb5{U8+0j}w=f^P-1&JVCzAaFZ~1UB!9)bp#TJNuMDClz^ai&)rO2@zERI0+ZOG3II6h zA;p;)>{8`9$`6Fe7~=rQgeqWhri+r@ka69 zoH?>wyI~z-yG@nO2~*sG>`Ok5~;4Q z9x$OuhZzB*k3KbW%+}yT$QvTpJlx=or+Rrm@Rk62+;6tfYZFnURu7P9z4Vx6o_ zJol{0w@*6L;(gIzUbiPz1+6+C9f{wFyF44rHhb(26CeD5w73;RV+PLVXeemf%V4H{dAxFoClBfLZ+8M>n7#o#-60!rg1m+Tx06)TBsU@7edL*j z0JudNJ+|TT3&H2tp~&&=L9}uy6n7xj6HA4BK)$a~&1}K|8x+Z$xwt}k|H_K3HYxcU z)9vOOO%*vETd)RA7>oov+{QnT#A^Xk(&c;>3uTm``|${nIc`% z)y7q)Y*es+r$%sRMk`L1S5*6c1Z1IBs)>Ahc<(7jFwX zVF6TQ!6gR+48h}eW~W)eq8daksE7za?)-Gb$&;^f1$lW4Ug>s8cVld_njV_@Yu~(C z=1huYe>etW+tv8!=X^7L2f8lek(~B%mVu31cSm|e0!n8frF@hel(s8MZekd#dgxjm z{+EdH8?#q;^rh@3%Cni`PNi(*^>l<9a&V$N@`!lGNw|@O1cgetlCRucBcb3z6&NtM z^FU{hp~}&T0BpKoI$~@93us!gd=WyvMUMH=*hpf9EfN=*I6FTz)BMUDb(*O}uGYBAeErIz(=T4afSG+b`y7EsJdz#ESHk>OB~G%=isD#A+AiEs$+%MW`Pc8X z5?gx7hrB2{q7RLTP$hG~=ySq>{lY<+6=@vQuaxTaHgl8u(8=S@;&~G{AOOJ$JGnTU z721;7|DXf7VPumRm3#LkqV<6_lyT5;e?q5bn~4O*qJO_VkQD&|tX`7On8$3w5N6`0 zou(`wE~&8YC4z;BRT_t<4xY}26x*!_b>>`u!zG`Fg6|>peaNN$Pd?LKH!*mlx+W^$ z^%qPaX)y_uRy?y*DeF=qQ8ua*a5a|10RWo?+LQXf_x!{E!Pw&cuiJxcUNGiGQIx)j zjs-s3)_y}a8E4Y{ptrVGf|lUaJQi#((FF+4ApD@nd?dLVjrR$vq;P^A1HUNK*LPN3hkgPN5UNM!#-=Kf|xVth=W7 zgU4w(Uf$#)EQg8?BelW7IMzrWh-K>VJM?tSpI0J8R{a9JffVpBBgW!~)pP*vCy&GX zi4ojx`m-)Aiy6_|KC~_)u!c^08fUa-xl&R$L({!;*H~Ey9!Zm>h_1QnIA_dl9cJ^` z_oG$YpeKrs>L+BY5w${f6WlF^vgld5D7KldvKbPnPhrsJVqV) zjaGc)Z0nP%^?aJ>X4uY$M7y~k{<%k8|1phC!`JN-?kyG&O)!D#kWIWi{W#r(+BZy7 z51fh!*mP#$V?WPH-3 zhbf)cqu!*(Z8it#z)cS~3`w$ za;fYJCaOiCIag`I}^r9~6Rq53t3O9nF=kS^K8pW)Qv z)il?ick<<}>O9|KYAf00aTcsPjDPHOQ|AxQKpn&8Gq4=8=}IyT7<$|~sdg#1F|#^Su<0)!7=M>`ks}gA$$$2hQD6n@xCc%VG3LiyzQG5cx{)=wiXRc zg%XYg(w#xbuX*S@p1FRd@b0tOTU;zzUV%<4Q}vqkyE}z#m*Iab8tA zZc>9Z0mh+XL%F;52=vG_O>+YNj#;Y3Uk^skY?Lqc8Hsi*qQqBxH38mtOJ!e&%xRruRg8>f)0(k{y>NYzd%yHM1Kk=Rg*!A`_r=A^&yW)p1yuq z!ru8H^)bw$d;Cvi(N-9Nj7>YauhZB;9|!%n@%o!$A) zAKxWG;uw_S3tO(7YHJfKY^IpxgWgj!bl_D|p4(_}2@z70Qk&yb~zlLl33Lfia) z(})LWYCZXHK>RlkB5tYeC}9@f=s6~}f0o?nQS18T?XSns!yL*n?V+W-FTAl;FOOAh zJxJCWb@?7AwE0^KW z!oIl1V#>1>%nK8DAXS?&b+QL#zePFMKF$U-9wi9ly#(Ro|7A>J0$G#um+8rB@udTg zFs+)9PIwUJ-@D5Ybh9a?+Ux|!edte#?l~qFa!#i&PK{$Xg!}F_J$^HRI}2e7!c9Kt zD#{2oYL!lX)Q#A@`TF*}U1b+i64Pu*;v$?W9tbjeokLzyCy%cJ(SbL8vS4)$4t6 zid4%O~6-otIDfd^i%o50NTA?)7k_ zTu-LgT>S9>`C6LOT}NGfi55ni8W6S%X!#5Twt=rnlAl0J9@{m)QsorSWZyMOuYwYNVGIrfnpAwsewaV)^hMeHULY+tW-5%GGak{sXsz=vxI;dItDtQ!lL$9HERac3~ z$QBIMNuwZj_4OLo*E#wG=y1%#3tzMgvjnK7D#<4_G9s88zDMeIjWplBI_NZ!s=TH< zltSM1eCq}ujz(yQFN*}?UU93k4KNM&~p^R`tD1-@e2 zRDpUWQf4YB@qgsxX~7{7tC0YShTnA8UJl`eKqu{VHLZy;rJ)5|3SF>g*uQgOn==3Y z?L?{rLS7nr9&8;qSUKiEw|=c<1@0H9G9g;LV)+r~KeF(vyIPk!F^0Y&9(wzp&hQrL z0I+ONHTj)7-hoE@4kVJ>fy!kyQ=CqEk!(Hrr`WFf@jCkM%@L3%RAhQ^yCrS#-cAGX zlQn85v0Q9<^!>;pJQ5Xam|comzAQP_B2Is7{=(2ke}kcG<{fsyHI{N$+*d0$#2}8u z(Bv)j5bR=Kh(EwDpdCwdz$ZgVZkGyL9Cn>25&Q|U(AhkgZCsJECJEf}oPH-B+^U{* zZ<1N^%M{Jan%%W4VNE*fdqZ(tD;nTf`l`1RoD`{X8voqAqtTOlpF4=tFJ68g`{st1 z_OxV)DZ6j%Lpc5%n|tH0;G;X~vtuUu<9Yb7Rkq_Mty!%$jpN!|8`8Vu4k~k|BG+4= zeKWYKDSrEQX8M=Ip>aJP{bD!SE05fs?q6_t{HIN327hkbqf{GRp1T6RE`e>bU+wPP zeOkW_$|c$4CQR$g@w49&`HK;f)Zc5$kdn)cD6%2kOL_O4vKBm2#^ZA^{Z~`ylDd}s zBO?|zRF7RjN)#z38K0&K!Y`yB{;=0)XWe-l*wTl0H{hQ~THo};v+XYy?0$i!Z#eo5 zE*vt2b1yerRDW;ne&6ttWRI%7aBzCxT%du36x--Y znX@a=!W5GSf~Rapb<(G3w-8)x)=mgb)U$A~HrwwflJ)O1)8)t01YA-_f^FU#u;XC>gwxAbfp?Cy1zq+r6nFI{~LbX7SYMV#aZAwmW851EbHDMGn0+DUnSH@FVbEzjzBwMZ{@WwSp;&@a%PFSAb*C&S z^rQ24#JLGm<=xD_Y8L&&t%JGCchB|N4KgFnbjv>q-PV0aDwCPL$1-qR)-Xhtu_oa! zGXenIKpq%69IL2=GDe%0-xAH`n$yn_Ur2iK-NO5utRRi1z8T|yjY=Klvp`bnJ9Nrp zqd%^kFgBnvU9#ne?mtmkuf1a zVVkt(pirTe&C^acJmx)Au02ZpRxeY`=~v-+@h^-!&N`$+^FX~Ewt;7XvP~i>2E?eddl^U z2h{0!EMI<@r2&Em^hl=c9H#2O6nHNtEv)A+3#Kz>HBG%^yqKFGe8+6(3HI`{aIuL# zba>h-?hB1|hH=N*@>eVkpi6F4k!NDUj_tIJA2XMC#u?Q?h2L zl?L6WJKRJc%q7z<*t&%%i##xo=A2#NyfXy*%{*;a{?#luc+g-E9NQ+ZA@#7%##>)B z_bH?P)71{DoXw=8k0o8dI$r_c6*}?ypK9>=Y9EVj6wS69nwVl@8~Z-b#L|TT3w-s9 zFY}ELVp7--&mpzTx?_iXYL_d|06;&MBqV;AyKOW5=|@R$93rgLE}JzJYRF!A*TJi!i>d%g4i>?7%VnL2O0=Iv*FzMUC8 zj{1`lps59N&ksyvbEz5fN7=CqYw0bmu}!+40?9&+e$rRo8GGyA$n73CWv@P%Gq|fM zqjQ3m9NRKuRHoW`zja>!oqTp69czI*z~h9W<5yrkc0xnB|UgY59ZErCPh z6gFl7@{;Nc^9W@wc#k$uzDbX5V`++o507tCJMc5QFJwDCPce=Iyf5nAS%Kt!q~ia^ z5Ko^NI)nbtd6*}(I#;Qi+$)dxMdPOlD3>(|C6Vx0)L0SVt4!)z@b32)i2FGLl+lDT zIp?l=F_m0LJ9Uc2t#Js^U`&H9q!45|j!&NQI2lu72>6nkB}6zEAx@FaXs?=SV5pW| zRkR?hE98eved>jmlI~j#FSQ~O+U8wbC(8j;q%V3Ef9uS}IY`I+)b~B@8m|qv{=6&Y z?wJAKrh*kGUH_WMO&2{A^^So^EypxG44nb}8$rsU43dO+VG<;vOduYT5*tXFMnnYD|mDPt0e!TY9C>Ol2H&7?dO> z|2qu9uIPh`#inK{*RI&r_BluZX1TNMazshsn_oO z(wJ0+|jLqF8&6OM9SE_=Mv7AT(~mL_3CQZ9)gn}KI4{l7TwZx8fQ>SSF*K_T1gaQKZq4* z36>MQ1o^mP4?{2}gmLzY8SJvB3Lo@8S<~#t!2%c-3RhT&q1F_5VGDL}mDIp`+fsK{ z&q`FH8()jOym7q1=3W{){Xp%K&3FcCIj*bAfWxv-)r_-!qnpKsl>rB0z?jZAaS}7^ zn82UwI9lBnhGk|7O~&@h-wdEJxaqQ%PUZUbF1`vS3T5xfjQbZy3Li$G?3jL;mGnv~ zVeF