From 03f40b1d8e873b57eab68dc66ae9892aa5e39f89 Mon Sep 17 00:00:00 2001 From: Fred Hallock Date: Mon, 18 Dec 2023 01:04:14 -0500 Subject: [PATCH] ui: Support controller peripherals and XMU devices (#1315) * Added XMU Settings to the Input Screen * Added Peripherals to config * Prevent overwriting existing XMUs * Added blockdev.h to try to fix the MacOS build * Fixed some issues that antangelo pointed out * Moved the peripheralType and param vars into the loop * Moved fatx.h and fatx.c to ui\thirdparty\fatx * Added Validation for Peripheral Settings * Fixed some nits that were pointed out * don't pass NULL into xemu_settings_set_string * Changes following Matt's recommendations * Changes to XMU FilePicker * XMU image auto-bind logic refactor * renamed peripheralType to peripheral_type * removed unnecessary calls to g_strdup_printf and g_free * Cleaned up some comments, removed an unnecessary variable * handle overwrite prompt in Windows * Fixed some code format and style inconsistencies * More formatting fixes * Fixed a few memory leaks * qemu_access: check for Read and Write access * Run clang-format * Remove unused xemu_new_xmu declaration * Fix use after free in rebind code --- config_spec.yml | 21 + data/meson.build | 1 + data/xmu_mask.png | Bin 0 -> 9950 bytes ui/meson.build | 2 +- ui/thirdparty/fatx/fatx.c | 51 ++ ui/thirdparty/fatx/fatx.h | 16 + ui/thirdparty/meson.build | 3 + .../noc_file_dialog/noc_file_dialog.h | 3 + ui/xemu-input.c | 258 ++++++++++- ui/xemu-input.h | 19 + ui/xui/gl-helpers.cc | 99 ++-- ui/xui/gl-helpers.hh | 4 +- ui/xui/main-menu.cc | 434 ++++++++++++++---- 13 files changed, 769 insertions(+), 142 deletions(-) create mode 100644 data/xmu_mask.png create mode 100644 ui/thirdparty/fatx/fatx.c create mode 100644 ui/thirdparty/fatx/fatx.h diff --git a/config_spec.yml b/config_spec.yml index cae05c17ec..b858606e68 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -25,6 +25,27 @@ input: port2: string port3: string port4: string + peripherals: + port1: + peripheral_type_0: integer + peripheral_param_0: string + peripheral_type_1: integer + peripheral_param_1: string + port2: + peripheral_type_0: integer + peripheral_param_0: string + peripheral_type_1: integer + peripheral_param_1: string + port3: + peripheral_type_0: integer + peripheral_param_0: string + peripheral_type_1: integer + peripheral_param_1: string + port4: + peripheral_type_0: integer + peripheral_param_0: string + peripheral_type_1: integer + peripheral_param_1: string gamecontrollerdb_path: string auto_bind: type: bool diff --git a/data/meson.build b/data/meson.build index 89e6123d90..e1a7ebedf4 100644 --- a/data/meson.build +++ b/data/meson.build @@ -1,5 +1,6 @@ pfiles = [ 'controller_mask.png', + 'xmu_mask.png', 'logo_sdf.png', 'xemu_64x64.png', 'abxy.ttf', diff --git a/data/xmu_mask.png b/data/xmu_mask.png new file mode 100644 index 0000000000000000000000000000000000000000..1ff5a7489ed4b333e56fbca03d07cb55d0703e53 GIT binary patch literal 9950 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 = INPUT_DEVICE_SDL_KEYBOARD; new_con->name = "Keyboard"; new_con->bound = -1; + new_con->peripheral_types[0] = PERIPHERAL_NONE; + new_con->peripheral_types[1] = PERIPHERAL_NONE; + new_con->peripherals[0] = NULL; + new_con->peripherals[1] = NULL; sdl_kbd_scancode_map[0] = g_config.input.keyboard_controller_scancode_map.a; sdl_kbd_scancode_map[1] = g_config.input.keyboard_controller_scancode_map.b; @@ -154,6 +184,7 @@ void xemu_input_init(void) char buf[128]; snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1); xemu_queue_notification(buf); + xemu_input_rebind_xmu(port); } QTAILQ_INSERT_TAIL(&available_controllers, new_con, entry); @@ -177,6 +208,24 @@ int xemu_input_get_controller_default_bind_port(ControllerState *state, int star return -1; } +void xemu_save_peripheral_settings(int player_index, int peripheral_index, + int peripheral_type, + const char *peripheral_parameter) +{ + int *peripheral_type_ptr = + peripheral_types_settings_map[player_index][peripheral_index]; + const char **peripheral_param_ptr = + peripheral_params_settings_map[player_index][peripheral_index]; + + assert(peripheral_type_ptr); + assert(peripheral_param_ptr); + + *peripheral_type_ptr = peripheral_type; + xemu_settings_set_string( + peripheral_param_ptr, + peripheral_parameter == NULL ? "" : peripheral_parameter); +} + void xemu_input_process_sdl_events(const SDL_Event *event) { if (event->type == SDL_CONTROLLERDEVICEADDED) { @@ -201,6 +250,10 @@ void xemu_input_process_sdl_events(const SDL_Event *event) new_con->sdl_joystick_id = SDL_JoystickInstanceID(new_con->sdl_joystick); new_con->sdl_joystick_guid = SDL_JoystickGetGUID(new_con->sdl_joystick); new_con->bound = -1; + new_con->peripheral_types[0] = PERIPHERAL_NONE; + new_con->peripheral_types[1] = PERIPHERAL_NONE; + new_con->peripherals[0] = NULL; + new_con->peripherals[1] = NULL; char guid_buf[35] = { 0 }; SDL_JoystickGetGUIDString(new_con->sdl_joystick_guid, guid_buf, sizeof(guid_buf)); @@ -254,6 +307,7 @@ void xemu_input_process_sdl_events(const SDL_Event *event) char buf[128]; snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1); xemu_queue_notification(buf); + xemu_input_rebind_xmu(port); } } else if (event->type == SDL_CONTROLLERDEVICEREMOVED) { DPRINTF("Controller Removed: %d\n", event->cdevice.which); @@ -286,6 +340,11 @@ void xemu_input_process_sdl_events(const SDL_Event *event) if (iter->sdl_gamecontroller) { SDL_GameControllerClose(iter->sdl_gamecontroller); } + + for (int i = 0; i < 2; i++) { + if (iter->peripherals[i]) + g_free(iter->peripherals[i]); + } free(iter); handled = 1; @@ -380,12 +439,9 @@ void xemu_input_update_sdl_controller_state(ControllerState *state) } const SDL_GameControllerAxis sdl_axis_map[6] = { - SDL_CONTROLLER_AXIS_TRIGGERLEFT, - SDL_CONTROLLER_AXIS_TRIGGERRIGHT, - SDL_CONTROLLER_AXIS_LEFTX, - SDL_CONTROLLER_AXIS_LEFTY, - SDL_CONTROLLER_AXIS_RIGHTX, - SDL_CONTROLLER_AXIS_RIGHTY, + SDL_CONTROLLER_AXIS_TRIGGERLEFT, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, + SDL_CONTROLLER_AXIS_LEFTX, SDL_CONTROLLER_AXIS_LEFTY, + SDL_CONTROLLER_AXIS_RIGHTX, SDL_CONTROLLER_AXIS_RIGHTY, }; for (int i = 0; i < 6; i++) { @@ -429,6 +485,22 @@ void xemu_input_bind(int index, ControllerState *state, int save) if (bound_controllers[index]) { assert(bound_controllers[index]->device != NULL); Error *err = NULL; + + // Unbind any XMUs + for (int i = 0; i < 2; i++) { + if (bound_controllers[index]->peripherals[i]) { + // If this was an XMU, unbind the XMU + if (bound_controllers[index]->peripheral_types[i] == + PERIPHERAL_XMU) + xemu_input_unbind_xmu(index, i); + + // Free up the XmuState and set the peripheral type to none + g_free(bound_controllers[index]->peripherals[i]); + bound_controllers[index]->peripherals[i] = NULL; + bound_controllers[index]->peripheral_types[i] = PERIPHERAL_NONE; + } + } + qdev_unplug((DeviceState *)bound_controllers[index]->device, &err); assert(err == NULL); @@ -460,7 +532,6 @@ void xemu_input_bind(int index, ControllerState *state, int save) bound_controllers[index] = state; bound_controllers[index]->bound = index; - const int port_map[4] = {3, 4, 1, 2}; char *tmp; // Create controller's internal USB hub. @@ -506,6 +577,179 @@ void xemu_input_bind(int index, ControllerState *state, int save) } } +bool xemu_input_bind_xmu(int player_index, int expansion_slot_index, + const char *filename, bool is_rebind) +{ + assert(player_index >= 0 && player_index < 4); + assert(expansion_slot_index >= 0 && expansion_slot_index < 2); + + ControllerState *player = bound_controllers[player_index]; + enum peripheral_type peripheral_type = + player->peripheral_types[expansion_slot_index]; + if (peripheral_type != PERIPHERAL_XMU) + return false; + + XmuState *xmu = (XmuState *)player->peripherals[expansion_slot_index]; + + // Unbind existing XMU + if (xmu->dev != NULL) { + xemu_input_unbind_xmu(player_index, expansion_slot_index); + } + + if (filename == NULL) + return false; + + // Look for any other XMUs that are using this file, and unbind them + for (int player_i = 0; player_i < 4; player_i++) { + ControllerState *state = bound_controllers[player_i]; + if (state != NULL) { + for (int peripheral_i = 0; peripheral_i < 2; peripheral_i++) { + if (state->peripheral_types[peripheral_i] == PERIPHERAL_XMU) { + XmuState *xmu_i = + (XmuState *)state->peripherals[peripheral_i]; + assert(xmu_i); + + if (xmu_i->filename != NULL && + strcmp(xmu_i->filename, filename) == 0) { + char *buf = + g_strdup_printf("This XMU is already mounted on " + "player %d slot %c\r\n", + player_i + 1, 'A' + peripheral_i); + xemu_queue_notification(buf); + g_free(buf); + return false; + } + } + } + } + } + + xmu->filename = g_strdup(filename); + + const int xmu_map[2] = { 2, 3 }; + char *tmp; + + static int id_counter = 0; + tmp = g_strdup_printf("xmu_%d", id_counter++); + + // Add the file as a drive + QDict *qdict1 = qdict_new(); + qdict_put_str(qdict1, "id", tmp); + qdict_put_str(qdict1, "format", "raw"); + qdict_put_str(qdict1, "file", filename); + + QemuOpts *drvopts = + qemu_opts_from_qdict(qemu_find_opts("drive"), qdict1, &error_abort); + + DriveInfo *dinfo = drive_new(drvopts, 0, &error_abort); + assert(dinfo); + + // Create the usb-storage device + QDict *qdict2 = qdict_new(); + + // Specify device driver + qdict_put_str(qdict2, "driver", "usb-storage"); + + // Specify device identifier + qdict_put_str(qdict2, "drive", tmp); + g_free(tmp); + + // Specify index/port + tmp = g_strdup_printf("1.%d.%d", port_map[player_index], + xmu_map[expansion_slot_index]); + qdict_put_str(qdict2, "port", tmp); + g_free(tmp); + + // Create the device + QemuOpts *opts = + qemu_opts_from_qdict(qemu_find_opts("device"), qdict2, &error_abort); + + DeviceState *dev = qdev_device_add(opts, &error_abort); + assert(dev); + + xmu->dev = (void *)dev; + + // Unref for eventual cleanup + qobject_unref(qdict1); + qobject_unref(qdict2); + + if (!is_rebind) { + xemu_save_peripheral_settings(player_index, expansion_slot_index, + peripheral_type, xmu->filename); + } + + return true; +} + +void xemu_input_unbind_xmu(int player_index, int expansion_slot_index) +{ + assert(player_index >= 0 && player_index < 4); + assert(expansion_slot_index >= 0 && expansion_slot_index < 2); + + ControllerState *state = bound_controllers[player_index]; + if (state->peripheral_types[expansion_slot_index] != PERIPHERAL_XMU) + return; + + XmuState *xmu = (XmuState *)state->peripherals[expansion_slot_index]; + if (xmu != NULL) { + if (xmu->dev != NULL) { + qdev_unplug((DeviceState *)xmu->dev, &error_abort); + object_unref(OBJECT(xmu->dev)); + xmu->dev = NULL; + } + + g_free((void *)xmu->filename); + xmu->filename = NULL; + } +} + +void xemu_input_rebind_xmu(int port) +{ + // Try to bind peripherals back to controller + for (int i = 0; i < 2; i++) { + enum peripheral_type peripheral_type = + (enum peripheral_type)(*peripheral_types_settings_map[port][i]); + + // If peripheralType is out of range, change the settings for this + // controller and peripheral port to default + if (peripheral_type < PERIPHERAL_NONE || + peripheral_type >= PERIPHERAL_TYPE_COUNT) { + xemu_save_peripheral_settings(port, i, PERIPHERAL_NONE, NULL); + peripheral_type = PERIPHERAL_NONE; + } + + const char *param = *peripheral_params_settings_map[port][i]; + + if (peripheral_type == PERIPHERAL_XMU) { + if (param != NULL && strlen(param) > 0) { + // This is an XMU and needs to be bound to this controller + if (qemu_access(param, R_OK | W_OK) == 0) { + bound_controllers[port]->peripheral_types[i] = + peripheral_type; + bound_controllers[port]->peripherals[i] = + g_malloc(sizeof(XmuState)); + memset(bound_controllers[port]->peripherals[i], 0, + sizeof(XmuState)); + bool did_bind = xemu_input_bind_xmu(port, i, param, true); + if (did_bind) { + char *buf = + g_strdup_printf("Connected XMU %s to port %d%c", + param, port + 1, 'A' + i); + xemu_queue_notification(buf); + g_free(buf); + } + } else { + char *buf = + g_strdup_printf("Unable to bind XMU at %s to port %d%c", + param, port + 1, 'A' + i); + xemu_queue_error_message(buf); + g_free(buf); + } + } + } + } +} + void xemu_input_set_test_mode(int enabled) { test_mode = enabled; diff --git a/ui/xemu-input.h b/ui/xemu-input.h index 8a8ba6544e..330ae58a7c 100644 --- a/ui/xemu-input.h +++ b/ui/xemu-input.h @@ -26,6 +26,8 @@ #define XEMU_INPUT_H #include +#include + #include "qemu/queue.h" enum controller_state_buttons_mask { @@ -63,6 +65,13 @@ enum controller_input_device_type { INPUT_DEVICE_SDL_GAMECONTROLLER, }; +enum peripheral_type { PERIPHERAL_NONE, PERIPHERAL_XMU, PERIPHERAL_TYPE_COUNT }; + +typedef struct XmuState { + const char *filename; + void *dev; +} XmuState; + typedef struct ControllerState { QTAILQ_ENTRY(ControllerState) entry; @@ -88,6 +97,9 @@ typedef struct ControllerState { SDL_JoystickID sdl_joystick_id; SDL_JoystickGUID sdl_joystick_guid; + enum peripheral_type peripheral_types[2]; + void *peripherals[2]; + int bound; // Which port this input device is bound to void *device; // DeviceState opaque } ControllerState; @@ -109,7 +121,14 @@ void xemu_input_update_sdl_controller_state(ControllerState *state); void xemu_input_update_rumble(ControllerState *state); ControllerState *xemu_input_get_bound(int index); void xemu_input_bind(int index, ControllerState *state, int save); +bool xemu_input_bind_xmu(int player_index, int peripheral_port_index, + const char *filename, bool is_rebind); +void xemu_input_rebind_xmu(int port); +void xemu_input_unbind_xmu(int player_index, int peripheral_port_index); int xemu_input_get_controller_default_bind_port(ControllerState *state, int start); +void xemu_save_peripheral_settings(int player_index, int peripheral_index, + int peripheral_type, + const char *peripheral_parameter); void xemu_input_set_test_mode(int enabled); int xemu_input_get_test_mode(void); diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index 4155ff2097..6c07d087b4 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -16,25 +16,24 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -#include "common.hh" -#include -#include -#include -#include +#include "ui/xemu-widescreen.h" #include "gl-helpers.hh" -#include "stb_image.h" +#include "common.hh" #include "data/controller_mask.png.h" #include "data/logo_sdf.png.h" -#include "ui/shader/xemu-logo-frag.h" #include "data/xemu_64x64.png.h" +#include "data/xmu_mask.png.h" #include "notifications.hh" -#include "ui/xemu-widescreen.h" +#include "stb_image.h" +#include +#include +#include +#include -Fbo *controller_fbo, - *logo_fbo; -GLuint g_controller_tex, - g_logo_tex, - g_icon_tex; +#include "ui/shader/xemu-logo-frag.h" + +Fbo *controller_fbo, *xmu_fbo, *logo_fbo; +GLuint g_controller_tex, g_logo_tex, g_icon_tex, g_xmu_tex; enum class ShaderType { Blit, @@ -186,9 +185,11 @@ static GLuint Shader(GLenum type, const char *src) glGetShaderiv(shader, GL_COMPILE_STATUS, &status); if (status != GL_TRUE) { glGetShaderInfoLog(shader, sizeof(err_buf), NULL, err_buf); - fprintf(stderr, "Shader compilation failed: %s\n\n" - "[Shader Source]\n" - "%s\n", err_buf, src); + fprintf(stderr, + "Shader compilation failed: %s\n\n" + "[Shader Source]\n" + "%s\n", + err_buf, src); assert(0); } @@ -222,15 +223,15 @@ void main() { GLuint vert = Shader(GL_VERTEX_SHADER, vert_src); assert(vert != 0); -// const char *image_frag_src = R"( -// #version 150 core -// uniform sampler2D tex; -// in vec2 Texcoord; -// out vec4 out_Color; -// void main() { -// out_Color.rgba = texture(tex, Texcoord); -// } -// )"; + // const char *image_frag_src = R"( + // #version 150 core + // uniform sampler2D tex; + // in vec2 Texcoord; + // out vec4 out_Color; + // void main() { + // out_Color.rgba = texture(tex, Texcoord); + // } + // )"; const char *image_gamma_frag_src = R"( #version 400 core @@ -370,7 +371,7 @@ void RenderDecal(DecalShader *s, float x, float y, float w, float h, glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i); float tw = tw_i, th = th_i; - #define COL(color, c) (float)(((color)>>((c)*8)) & 0xff)/255.0 +#define COL(color, c) (float)(((color) >> ((c)*8)) & 0xff) / 255.0 if (s->flipy_loc >= 0) { glUniform1i(s->flipy_loc, s->flip); } @@ -403,7 +404,7 @@ void RenderDecal(DecalShader *s, float x, float y, float w, float h, if (s->scale_loc >= 0) { glUniform1f(s->scale_loc, s->scale); } - #undef COL +#undef COL glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL); } @@ -412,14 +413,15 @@ struct rect { }; static const struct rect tex_items[] = { - { 0, 148, 467, 364 }, // obj_controller - { 0, 81, 67, 67 }, // obj_lstick - { 0, 14, 67, 67 }, // obj_rstick - { 67, 104, 68, 44 }, // obj_port_socket - { 67, 76, 28, 28 }, // obj_port_lbl_1 - { 67, 48, 28, 28 }, // obj_port_lbl_2 - { 67, 20, 28, 28 }, // obj_port_lbl_3 - { 95, 76, 28, 28 }, // obj_port_lbl_4 + { 0, 148, 467, 364 }, // obj_controller + { 0, 81, 67, 67 }, // obj_lstick + { 0, 14, 67, 67 }, // obj_rstick + { 67, 104, 68, 44 }, // obj_port_socket + { 67, 76, 28, 28 }, // obj_port_lbl_1 + { 67, 48, 28, 28 }, // obj_port_lbl_2 + { 67, 20, 28, 28 }, // obj_port_lbl_3 + { 95, 76, 28, 28 }, // obj_port_lbl_4 + { 0, 0, 512, 512 } // obj_xmu }; enum tex_item_names { @@ -431,6 +433,7 @@ enum tex_item_names { obj_port_lbl_2, obj_port_lbl_3, obj_port_lbl_4, + obj_xmu }; void InitCustomRendering(void) @@ -441,6 +444,9 @@ void InitCustomRendering(void) g_decal_shader = NewDecalShader(ShaderType::Mask); controller_fbo = new Fbo(512, 512); + g_xmu_tex = LoadTextureFromMemory(xmu_mask_data, xmu_mask_size); + xmu_fbo = new Fbo(512, 256); + g_logo_tex = LoadTextureFromMemory(logo_sdf_data, logo_sdf_size); g_logo_shader = NewDecalShader(ShaderType::Logo); logo_fbo = new Fbo(512, 512); @@ -646,6 +652,26 @@ void RenderControllerPort(float frame_x, float frame_y, int i, glUseProgram(0); } +void RenderXmu(float frame_x, float frame_y, uint32_t primary_color, + uint32_t secondary_color) +{ + glUseProgram(g_decal_shader->prog); + glBindVertexArray(g_decal_shader->vao); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, g_xmu_tex); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_ONE, GL_ZERO); + + // Render xmu + RenderDecal(g_decal_shader, frame_x, frame_y, 256, 256, + tex_items[obj_xmu].x, tex_items[obj_xmu].y, + tex_items[obj_xmu].w, tex_items[obj_xmu].h, primary_color, + secondary_color, 0); + + glBindVertexArray(0); + glUseProgram(0); +} + void RenderLogo(uint32_t time) { uint32_t color = 0x62ca13ff; @@ -801,8 +827,7 @@ void SaveScreenshot(GLuint tex, bool flip) time_t t = time(NULL); struct tm *tmp = localtime(&t); if (tmp) { - strftime(fname, sizeof(fname), "xemu-%Y-%m-%d-%H-%M-%S.png", - tmp); + strftime(fname, sizeof(fname), "xemu-%Y-%m-%d-%H-%M-%S.png", tmp); } else { strcpy(fname, "xemu.png"); } diff --git a/ui/xui/gl-helpers.hh b/ui/xui/gl-helpers.hh index 7401540816..82da963e6b 100644 --- a/ui/xui/gl-helpers.hh +++ b/ui/xui/gl-helpers.hh @@ -38,7 +38,7 @@ public: void Restore(); }; -extern Fbo *controller_fbo, *logo_fbo; +extern Fbo *controller_fbo, *xmu_fbo, *logo_fbo; extern GLuint g_icon_tex; void InitCustomRendering(void); @@ -47,6 +47,8 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state); void RenderControllerPort(float frame_x, float frame_y, int i, uint32_t port_color); +void RenderXmu(float frame_x, float frame_y, uint32_t primary_color, + uint32_t secondary_color); void RenderFramebuffer(GLint tex, int width, int height, bool flip); void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2]); bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector &png, int max_width = 0, int max_height = 0); diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index eb7749a51b..75b88cafb6 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -40,6 +40,10 @@ #include "../xemu-os-utils.h" #include "../xemu-xbe.h" +#include "../thirdparty/fatx/fatx.h" + +#define DEFAULT_XMU_SIZE 8388608 + MainMenuScene g_main_menu; MainMenuTabView::~MainMenuTabView() {} @@ -86,6 +90,9 @@ void MainMenuInputView::Draw() // Dimensions of controller (rendered at origin) float controller_width = 477.0f; float controller_height = 395.0f; + // Dimensions of XMU + float xmu_x = 0, xmu_x_stride = 256, xmu_y = 0; + float xmu_w = 256, xmu_h = 256; // Setup rendering to fbo for controller and port images controller_fbo->Target(); @@ -120,14 +127,14 @@ void MainMenuInputView::Draw() // uses the texture as a unique ID. Push a new ID now to resolve // the conflict. ImGui::PushID(i); - float x = b_x+i*b_x_stride; - ImGui::PushStyleColor(ImGuiCol_Button, is_selected ? - color_active : - color_inactive); - bool activated = ImGui::ImageButton(id, - ImVec2(b_w*g_viewport_mgr.m_scale,b_h*g_viewport_mgr.m_scale), - ImVec2(x/t_w, (b_y+b_h)/t_h), - ImVec2((x+b_w)/t_w, b_y/t_h), + float x = b_x + i * b_x_stride; + ImGui::PushStyleColor(ImGuiCol_Button, + is_selected ? color_active : color_inactive); + bool activated = ImGui::ImageButton( + id, + ImVec2(b_w * g_viewport_mgr.m_scale, b_h * g_viewport_mgr.m_scale), + ImVec2(x / t_w, (b_y + b_h) / t_h), + ImVec2((x + b_w) / t_w, b_y / t_h), port_padding * g_viewport_mgr.m_scale); ImGui::PopStyleColor(); @@ -193,6 +200,16 @@ void MainMenuInputView::Draw() } if (ImGui::Selectable(selectable_label, is_selected)) { xemu_input_bind(active, iter, 1); + + // FIXME: We want to bind the XMU here, but we can't because we + // just unbound it and we need to wait for Qemu to release the + // file + + // If we previously had no controller connected, we can rebind + // the XMU + if (bound_state == NULL) + xemu_input_rebind_xmu(active); + bound_state = iter; } if (is_selected) { @@ -260,6 +277,168 @@ void MainMenuInputView::Draw() ImGui::PopFont(); ImGui::SetCursorPos(pos); + if (bound_state) { + SectionTitle("Expansion Slots"); + // Begin a 2-column layout to render the expansion slots + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, + g_viewport_mgr.Scale(ImVec2(0, 12))); + ImGui::Columns(2, "mixed", false); + + xmu_fbo->Target(); + id = (ImTextureID)(intptr_t)xmu_fbo->Texture(); + + const char *img_file_filters = ".img Files\0*.img\0All Files\0*.*\0"; + const char *comboLabels[2] = { "###ExpansionSlotA", + "###ExpansionSlotB" }; + for (int i = 0; i < 2; i++) { + // Display a combo box to allow the user to choose the type of + // peripheral they want to use + enum peripheral_type selected_type = + bound_state->peripheral_types[i]; + const char *peripheral_type_names[2] = { "None", "Memory Unit" }; + const char *selected_peripheral_type = + peripheral_type_names[selected_type]; + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::BeginCombo(comboLabels[i], selected_peripheral_type, + ImGuiComboFlags_NoArrowButton)) { + // Handle all available peripheral types + for (int j = 0; j < 2; j++) { + bool is_selected = selected_type == j; + ImGui::PushID(j); + const char *selectable_label = peripheral_type_names[j]; + + if (ImGui::Selectable(selectable_label, is_selected)) { + // Free any existing peripheral + if (bound_state->peripherals[i] != NULL) { + if (bound_state->peripheral_types[i] == + PERIPHERAL_XMU) { + // Another peripheral was already bound. + // Unplugging + xemu_input_unbind_xmu(active, i); + } + + // Free the existing state + g_free((void *)bound_state->peripherals[i]); + bound_state->peripherals[i] = NULL; + } + + // Change the peripheral type to the newly selected type + bound_state->peripheral_types[i] = + (enum peripheral_type)j; + + // Allocate state for the new peripheral + if (j == PERIPHERAL_XMU) { + bound_state->peripherals[i] = + g_malloc(sizeof(XmuState)); + memset(bound_state->peripherals[i], 0, + sizeof(XmuState)); + } + + xemu_save_peripheral_settings( + active, i, bound_state->peripheral_types[i], NULL); + } + + if (is_selected) { + ImGui::SetItemDefaultFocus(); + } + + ImGui::PopID(); + } + + ImGui::EndCombo(); + } + DrawComboChevron(); + + // Set an X offset to center the image button within the column + ImGui::SetCursorPosX( + ImGui::GetCursorPosX() + + (int)((ImGui::GetColumnWidth() - + xmu_w * g_viewport_mgr.m_scale - + 2 * port_padding * g_viewport_mgr.m_scale) / + 2)); + + selected_type = bound_state->peripheral_types[i]; + if (selected_type == PERIPHERAL_XMU) { + float x = xmu_x + i * xmu_x_stride; + float y = xmu_y; + + XmuState *xmu = (XmuState *)bound_state->peripherals[i]; + if (xmu->filename != NULL && strlen(xmu->filename) > 0) { + RenderXmu(x, y, 0x81dc8a00, 0x0f0f0f00); + + } else { + RenderXmu(x, y, 0x1f1f1f00, 0x0f0f0f00); + } + + ImVec2 xmu_display_size; + if (ImGui::GetContentRegionMax().x < + xmu_h * g_viewport_mgr.m_scale) { + xmu_display_size.x = ImGui::GetContentRegionMax().x / 2; + xmu_display_size.y = xmu_display_size.x * xmu_h / xmu_w; + } else { + xmu_display_size = ImVec2(xmu_w * g_viewport_mgr.m_scale, + xmu_h * g_viewport_mgr.m_scale); + } + + ImGui::SetCursorPosX( + ImGui::GetCursorPosX() + + (int)((ImGui::GetColumnWidth() - xmu_display_size.x) / + 2.0)); + + ImGui::Image(id, xmu_display_size, ImVec2(0.5f * i, 1), + ImVec2(0.5f * (i + 1), 0)); + ImVec2 pos = ImGui::GetCursorPos(); + + ImGui::SetCursorPos(pos); + + // Button to generate a new XMU + ImGui::PushID(i); + if (ImGui::Button("New Image", ImVec2(250, 0))) { + int flags = NOC_FILE_DIALOG_SAVE | + NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION; + const char *new_path = PausedFileOpen( + flags, img_file_filters, NULL, "xmu.img"); + + if (new_path) { + if (create_fatx_image(new_path, DEFAULT_XMU_SIZE)) { + // XMU was created successfully. Bind it + xemu_input_bind_xmu(active, i, new_path, false); + } else { + // Show alert message + char *msg = g_strdup_printf( + "Unable to create XMU image at %s", new_path); + xemu_queue_error_message(msg); + g_free(msg); + } + } + } + + const char *xmu_port_path = NULL; + if (xmu->filename == NULL) + xmu_port_path = g_strdup(""); + else + xmu_port_path = g_strdup(xmu->filename); + if (FilePicker("Image", &xmu_port_path, img_file_filters)) { + if (strlen(xmu_port_path) == 0) { + xemu_input_unbind_xmu(active, i); + } else { + xemu_input_bind_xmu(active, i, xmu_port_path, false); + } + } + g_free((void *)xmu_port_path); + + ImGui::PopID(); + } + + ImGui::NextColumn(); + } + + xmu_fbo->Restore(); + + ImGui::PopStyleVar(); // ItemSpacing + ImGui::Columns(1); + } + SectionTitle("Options"); Toggle("Auto-bind controllers", &g_config.input.auto_bind, "Bind newly connected controllers to any open port"); @@ -625,8 +804,9 @@ void MainMenuNetworkView::DrawNatOptions(bool appearing) void MainMenuNetworkView::DrawUdpOptions(bool appearing) { if (appearing) { - strncpy(remote_addr, g_config.net.udp.remote_addr, sizeof(remote_addr)-1); - strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr)-1); + strncpy(remote_addr, g_config.net.udp.remote_addr, + sizeof(remote_addr) - 1); + strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr) - 1); } float size_ratio = 0.5; @@ -663,7 +843,9 @@ MainMenuSnapshotsView::~MainMenuSnapshotsView() g_free(m_search_regex); } -bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding) +bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, + XemuSnapshotData *data, + int current_snapshot_binding) { ImGuiStyle &style = ImGui::GetStyle(); ImDrawList *draw_list = ImGui::GetWindowDrawList(); @@ -673,18 +855,27 @@ bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSn ImGui::PopFont(); ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.Scale(ImVec2(5, 5))); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, + g_viewport_mgr.Scale(ImVec2(5, 5))); ImGui::PushFont(g_font_mgr.m_menu_font_medium); ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name); - ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)); + ImVec2 thumbnail_size = g_viewport_mgr.Scale( + ImVec2(XEMU_SNAPSHOT_THUMBNAIL_WIDTH, XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)); ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y); - ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y); - ImVec2 title_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x); - ImVec2 date_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x); - ImVec2 binding_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x); - ImVec2 button_size(-FLT_MIN, fmax(thumbnail_size.y + style.FramePadding.y * 2, ts_title.y + ts_sub.y + style.FramePadding.y * 3)); + ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + + style.FramePadding.x * 2, + thumbnail_pos.y); + ImVec2 title_pos(name_pos.x, + name_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 date_pos(name_pos.x, + title_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 binding_pos(name_pos.x, + date_pos.y + ts_title.y + style.FramePadding.x); + ImVec2 button_size(-FLT_MIN, + fmax(thumbnail_size.y + style.FramePadding.y * 2, + ts_title.y + ts_sub.y + style.FramePadding.y * 3)); bool load = ImGui::Button("###button", button_size); @@ -699,42 +890,55 @@ bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSn int thumbnail_width, thumbnail_height; glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, thumbnail); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &thumbnail_width); - glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &thumbnail_height); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, + &thumbnail_width); + glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, + &thumbnail_height); // Draw black background behind thumbnail ImVec2 thumbnail_min(p0.x + thumbnail_pos.x, p0.y + thumbnail_pos.y); - ImVec2 thumbnail_max(thumbnail_min.x + thumbnail_size.x, thumbnail_min.y + thumbnail_size.y); + ImVec2 thumbnail_max(thumbnail_min.x + thumbnail_size.x, + thumbnail_min.y + thumbnail_size.y); draw_list->AddRectFilled(thumbnail_min, thumbnail_max, IM_COL32_BLACK); // Draw centered thumbnail image int scaled_width, scaled_height; - ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, thumbnail_size.y, &scaled_width, &scaled_height); - ImVec2 img_min = ImVec2(thumbnail_min.x + (thumbnail_size.x - scaled_width) / 2, - thumbnail_min.y + (thumbnail_size.y - scaled_height) / 2); - ImVec2 img_max = ImVec2(img_min.x + scaled_width, img_min.y + scaled_height); + ScaleDimensions(thumbnail_width, thumbnail_height, thumbnail_size.x, + thumbnail_size.y, &scaled_width, &scaled_height); + ImVec2 img_min = + ImVec2(thumbnail_min.x + (thumbnail_size.x - scaled_width) / 2, + thumbnail_min.y + (thumbnail_size.y - scaled_height) / 2); + ImVec2 img_max = + ImVec2(img_min.x + scaled_width, img_min.y + scaled_height); draw_list->AddImage((ImTextureID)(uint64_t)thumbnail, img_min, img_max); // Snapshot title ImGui::PushFont(g_font_mgr.m_menu_font_medium); - draw_list->AddText(ImVec2(p0.x + name_pos.x, p0.y + name_pos.y), IM_COL32(255, 255, 255, 255), snapshot->name); + draw_list->AddText(ImVec2(p0.x + name_pos.x, p0.y + name_pos.y), + IM_COL32(255, 255, 255, 255), snapshot->name); ImGui::PopFont(); // Snapshot XBE title name ImGui::PushFont(g_font_mgr.m_menu_font_small); - const char *title_name = data->xbe_title_name ? data->xbe_title_name : "(Unknown XBE Title Name)"; - draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name); + const char *title_name = data->xbe_title_name ? data->xbe_title_name : + "(Unknown XBE Title Name)"; + draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), + IM_COL32(255, 255, 255, 200), title_name); // Snapshot date - g_autoptr(GDateTime) date = g_date_time_new_from_unix_local(snapshot->date_sec); + g_autoptr(GDateTime) date = + g_date_time_new_from_unix_local(snapshot->date_sec); char *date_buf = g_date_time_format(date, "%Y-%m-%d %H:%M:%S"); - draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y), IM_COL32(255, 255, 255, 200), date_buf); + draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y), + IM_COL32(255, 255, 255, 200), date_buf); g_free(date_buf); // Snapshot keyboard binding if (current_snapshot_binding != -1) { - char *binding_text = g_strdup_printf("Bound to F%d", current_snapshot_binding + 5); - draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), binding_text); + char *binding_text = + g_strdup_printf("Bound to F%d", current_snapshot_binding + 5); + draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), + IM_COL32(255, 255, 255, 200), binding_text); g_free(binding_text); } @@ -758,7 +962,7 @@ void MainMenuSnapshotsView::ClearSearch() int MainMenuSnapshotsView::OnSearchTextUpdate(ImGuiInputTextCallbackData *data) { GError *gerr = NULL; - MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData; + MainMenuSnapshotsView *win = (MainMenuSnapshotsView *)data->UserData; if (win->m_search_regex) { g_free(win->m_search_regex); @@ -770,7 +974,8 @@ int MainMenuSnapshotsView::OnSearchTextUpdate(ImGuiInputTextCallbackData *data) } char *buf = g_strdup_printf("(.*)%s(.*)", data->Buf); - win->m_search_regex = g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr); + win->m_search_regex = + g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr); g_free(buf); if (gerr) { win->m_search_regex = NULL; @@ -785,14 +990,17 @@ void MainMenuSnapshotsView::Draw() g_snapshot_mgr.Refresh(); SectionTitle("Snapshots"); - Toggle("Filter by current title", &g_config.general.snapshots.filter_current_game, - "Only display snapshots created while running the currently running XBE"); + Toggle("Filter by current title", + &g_config.general.snapshots.filter_current_game, + "Only display snapshots created while running the currently running " + "XBE"); if (g_config.general.snapshots.filter_current_game) { struct xbe *xbe = xemu_get_xbe_info(); if (xbe && xbe->cert) { if (xbe->cert->m_titleid != m_current_title_id) { - char *title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL); + char *title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, + NULL, NULL, NULL); if (title_name) { m_current_title_name = title_name; g_free(title_name); @@ -816,7 +1024,8 @@ void MainMenuSnapshotsView::Draw() bool snapshot_with_create_name_exists = false; for (int i = 0; i < g_snapshot_mgr.m_snapshots_len; ++i) { - if (g_strcmp0(m_search_buf.c_str(), g_snapshot_mgr.m_snapshots[i].name) == 0) { + if (g_strcmp0(m_search_buf.c_str(), + g_snapshot_mgr.m_snapshots[i].name) == 0) { snapshot_with_create_name_exists = true; break; } @@ -828,8 +1037,10 @@ void MainMenuSnapshotsView::Draw() ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1, 0, 0, 1)); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1, 0, 0, 1)); } - if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create", ImVec2(-FLT_MIN, 0))) { - xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), NULL); + if (ImGui::Button(snapshot_with_create_name_exists ? "Replace" : "Create", + ImVec2(-FLT_MIN, 0))) { + xemu_snapshots_save(m_search_buf.empty() ? NULL : m_search_buf.c_str(), + NULL); ClearSearch(); } if (snapshot_with_create_name_exists) { @@ -837,28 +1048,36 @@ void MainMenuSnapshotsView::Draw() } if (snapshot_with_create_name_exists && ImGui::IsItemHovered()) { - ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. This button will overwrite the existing snapshot.", m_search_buf.c_str()); + ImGui::SetTooltip("A snapshot with the name \"%s\" already exists. " + "This button will overwrite the existing snapshot.", + m_search_buf.c_str()); } ImGui::PopFont(); bool at_least_one_snapshot_displayed = false; for (int i = g_snapshot_mgr.m_snapshots_len - 1; i >= 0; i--) { - if (g_config.general.snapshots.filter_current_game && g_snapshot_mgr.m_extra_data[i].xbe_title_name && - m_current_title_name.size() && strcmp(m_current_title_name.c_str(), g_snapshot_mgr.m_extra_data[i].xbe_title_name)) { + if (g_config.general.snapshots.filter_current_game && + g_snapshot_mgr.m_extra_data[i].xbe_title_name && + m_current_title_name.size() && + strcmp(m_current_title_name.c_str(), + g_snapshot_mgr.m_extra_data[i].xbe_title_name)) { continue; } if (m_search_regex) { GMatchInfo *match; bool keep_entry = false; - - g_regex_match(m_search_regex, g_snapshot_mgr.m_snapshots[i].name, (GRegexMatchFlags)0, &match); + + g_regex_match(m_search_regex, g_snapshot_mgr.m_snapshots[i].name, + (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_match_info_free(match); if (g_snapshot_mgr.m_extra_data[i].xbe_title_name) { - g_regex_match(m_search_regex, g_snapshot_mgr.m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match); + g_regex_match(m_search_regex, + g_snapshot_mgr.m_extra_data[i].xbe_title_name, + (GRegexMatchFlags)0, &match); keep_entry |= g_match_info_matches(match); g_free(match); } @@ -873,7 +1092,8 @@ void MainMenuSnapshotsView::Draw() int current_snapshot_binding = -1; for (int i = 0; i < 4; ++i) { - if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), snapshot->name) == 0) { + if (g_strcmp0(*(g_snapshot_shortcut_index_key_map[i]), + snapshot->name) == 0) { assert(current_snapshot_binding == -1); current_snapshot_binding = i; } @@ -885,13 +1105,14 @@ void MainMenuSnapshotsView::Draw() bool load = BigSnapshotButton(snapshot, data, current_snapshot_binding); // FIXME: Provide context menu control annotation - if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) { + if (ImGui::IsItemHovered() && + ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) { ImGui::SetNextWindowPos(pos); ImGui::OpenPopup("Snapshot Options"); } - + DrawSnapshotContextMenu(snapshot, data, current_snapshot_binding); - + ImGui::PopID(); if (load) { @@ -915,12 +1136,14 @@ void MainMenuSnapshotsView::Draw() } ImVec2 dim = ImGui::CalcTextSize(msg); ImVec2 cur = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(cur.x + (ImGui::GetColumnWidth()-dim.x)/2); + ImGui::SetCursorPosX(cur.x + (ImGui::GetColumnWidth() - dim.x) / 2); ImGui::TextColored(ImVec4(0.94f, 0.94f, 0.94f, 0.70f), "%s", msg); } } -void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding) +void MainMenuSnapshotsView::DrawSnapshotContextMenu( + QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, + int current_snapshot_binding) { if (!ImGui::BeginPopupContextItem("Snapshot Options")) { return; @@ -936,9 +1159,12 @@ void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, if (ImGui::MenuItem(item_name)) { if (current_snapshot_binding >= 0) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + xemu_settings_set_string(g_snapshot_shortcut_index_key_map + [current_snapshot_binding], + ""); } - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], snapshot->name); + xemu_settings_set_string(g_snapshot_shortcut_index_key_map[i], + snapshot->name); current_snapshot_binding = i; ImGui::CloseCurrentPopup(); @@ -949,13 +1175,15 @@ void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, if (current_snapshot_binding >= 0) { if (ImGui::MenuItem("Unbind")) { - xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], ""); + xemu_settings_set_string( + g_snapshot_shortcut_index_key_map[current_snapshot_binding], + ""); current_snapshot_binding = -1; } } ImGui::EndMenu(); } - + ImGui::Separator(); Error *err = NULL; @@ -982,11 +1210,13 @@ MainMenuSystemView::MainMenuSystemView() : m_dirty(false) void MainMenuSystemView::Draw() { - const char *rom_file_filters = ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0"; + const char *rom_file_filters = + ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0"; const char *qcow_file_filters = ".qcow2 Files\0*.qcow2\0All Files\0*.*\0"; if (m_dirty) { - ImGui::TextColored(ImVec4(1,0,0,1), "Application restart required to apply settings"); + ImGui::TextColored(ImVec4(1, 0, 0, 1), + "Application restart required to apply settings"); } if ((int)g_config.sys.avpack == CONFIG_SYS_AVPACK_NONE) { @@ -997,7 +1227,8 @@ void MainMenuSystemView::Draw() if (ChevronCombo( "System Memory", &g_config.sys.mem_limit, - "64 MiB (Default)\0""128 MiB\0", + "64 MiB (Default)\0" + "128 MiB\0", "Increase to 128 MiB for debug or homebrew applications")) { m_dirty = true; } @@ -1030,8 +1261,9 @@ void MainMenuSystemView::Draw() } } -MainMenuAboutView::MainMenuAboutView(): m_config_info_text{NULL} -{} +MainMenuAboutView::MainMenuAboutView() : m_config_info_text{ NULL } +{ +} void MainMenuAboutView::UpdateConfigInfoText() { @@ -1039,20 +1271,21 @@ void MainMenuAboutView::UpdateConfigInfoText() g_free(m_config_info_text); } - gchar *bootrom_checksum = GetFileMD5Checksum(g_config.sys.files.bootrom_path); + gchar *bootrom_checksum = + GetFileMD5Checksum(g_config.sys.files.bootrom_path); if (!bootrom_checksum) { bootrom_checksum = g_strdup("None"); } - gchar *flash_rom_checksum = GetFileMD5Checksum(g_config.sys.files.flashrom_path); + gchar *flash_rom_checksum = + GetFileMD5Checksum(g_config.sys.files.flashrom_path); if (!flash_rom_checksum) { flash_rom_checksum = g_strdup("None"); } - m_config_info_text = g_strdup_printf( - "MCPX Boot ROM MD5 Hash: %s\n" - "Flash ROM (BIOS) MD5 Hash: %s", - bootrom_checksum, flash_rom_checksum); + m_config_info_text = g_strdup_printf("MCPX Boot ROM MD5 Hash: %s\n" + "Flash ROM (BIOS) MD5 Hash: %s", + bootrom_checksum, flash_rom_checksum); g_free(bootrom_checksum); g_free(flash_rom_checksum); } @@ -1061,22 +1294,25 @@ void MainMenuAboutView::Draw() { static const char *build_info_text = NULL; if (build_info_text == NULL) { - build_info_text = g_strdup_printf( - "Version: %s\nBranch: %s\nCommit: %s\nDate: %s", - xemu_version, xemu_branch, xemu_commit, xemu_date); + build_info_text = + g_strdup_printf("Version: %s\nBranch: %s\nCommit: " + "%s\nDate: %s", + xemu_version, xemu_branch, xemu_commit, xemu_date); } static const char *sys_info_text = NULL; if (sys_info_text == NULL) { - const char *gl_shader_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); - const char *gl_version = (const char*)glGetString(GL_VERSION); - const char *gl_renderer = (const char*)glGetString(GL_RENDERER); - const char *gl_vendor = (const char*)glGetString(GL_VENDOR); + const char *gl_shader_version = + (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION); + const char *gl_version = (const char *)glGetString(GL_VERSION); + const char *gl_renderer = (const char *)glGetString(GL_RENDERER); + const char *gl_vendor = (const char *)glGetString(GL_VENDOR); sys_info_text = g_strdup_printf( - "CPU: %s\nOS Platform: %s\nOS Version: %s\nManufacturer: %s\n" + "CPU: %s\nOS Platform: %s\nOS Version: " + "%s\nManufacturer: %s\n" "GPU Model: %s\nDriver: %s\nShader: %s", - xemu_get_cpu_info(), xemu_get_os_platform(), xemu_get_os_info(), gl_vendor, - gl_renderer, gl_version, gl_shader_version); + xemu_get_cpu_info(), xemu_get_os_platform(), xemu_get_os_info(), + gl_vendor, gl_renderer, gl_version, gl_shader_version); } if (m_config_info_text == NULL) { @@ -1121,7 +1357,7 @@ void MainMenuAboutView::Draw() } MainMenuTabButton::MainMenuTabButton(std::string text, std::string icon) -: m_icon(icon), m_text(text) + : m_icon(icon), m_text(text) { } @@ -1134,8 +1370,10 @@ bool MainMenuTabButton::Draw(bool selected) IM_COL32(0, 0, 0, 0); ImGui::PushStyleColor(ImGuiCol_Button, col); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, selected ? col : IM_COL32(32, 32, 32, 255)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, selected ? col : IM_COL32(32, 32, 32, 255)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, + selected ? col : IM_COL32(32, 32, 32, 255)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, + selected ? col : IM_COL32(32, 32, 32, 255)); int p = ImGui::GetTextLineHeight() * 0.5; ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(p, p)); ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0); @@ -1154,15 +1392,14 @@ bool MainMenuTabButton::Draw(bool selected) } MainMenuScene::MainMenuScene() -: m_animation(0.12, 0.12), - m_general_button("General", ICON_FA_GEARS), - m_input_button("Input", ICON_FA_GAMEPAD), - m_display_button("Display", ICON_FA_TV), - m_audio_button("Audio", ICON_FA_VOLUME_HIGH), - m_network_button("Network", ICON_FA_NETWORK_WIRED), - m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT), - m_system_button("System", ICON_FA_MICROCHIP), - m_about_button("About", ICON_FA_CIRCLE_INFO) + : m_animation(0.12, 0.12), m_general_button("General", ICON_FA_GEARS), + m_input_button("Input", ICON_FA_GAMEPAD), + m_display_button("Display", ICON_FA_TV), + m_audio_button("Audio", ICON_FA_VOLUME_HIGH), + m_network_button("Network", ICON_FA_NETWORK_WIRED), + m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT), + m_system_button("System", ICON_FA_MICROCHIP), + m_about_button("About", ICON_FA_CIRCLE_INFO) { m_had_focus_last_frame = false; m_focus_view = false; @@ -1249,11 +1486,12 @@ void MainMenuScene::HandleInput() bool focus = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows | ImGuiFocusedFlags_NoPopupHierarchy); - // XXX: Ensure we have focus for two frames. If a user cancels a popup window, we do not want to cancel main + // XXX: Ensure we have focus for two frames. If a user cancels a popup + // window, we do not want to cancel main // window as well. if (nofocus || (focus && m_had_focus_last_frame && - (ImGui::IsKeyDown(ImGuiKey_GamepadFaceRight) - || ImGui::IsKeyDown(ImGuiKey_Escape)))) { + (ImGui::IsKeyDown(ImGuiKey_GamepadFaceRight) || + ImGui::IsKeyDown(ImGuiKey_Escape)))) { Hide(); return; } @@ -1313,9 +1551,10 @@ bool MainMenuScene::Draw() float nav_width = width * 0.3; float content_width = width - nav_width; - ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(26,26,26,255)); + ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(26, 26, 26, 255)); - ImGui::BeginChild("###MainWindowNav", ImVec2(nav_width, -1), true, ImGuiWindowFlags_NavFlattened); + ImGui::BeginChild("###MainWindowNav", ImVec2(nav_width, -1), true, + ImGuiWindowFlags_NavFlattened); bool move_focus_to_tab = false; if (m_current_view_index != m_next_view_index) { @@ -1349,7 +1588,8 @@ bool MainMenuScene::Draw() int s = ImGui::GetTextLineHeight() * 0.75; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(s, s)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(s, s)); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6*g_viewport_mgr.m_scale); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, + 6 * g_viewport_mgr.m_scale); ImGui::PushID(m_current_view_index); ImGui::BeginChild("###MainWindowContent", ImVec2(content_width, -1), @@ -1364,7 +1604,9 @@ bool MainMenuScene::Draw() ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128)); ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS); ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - style.FramePadding.x * 2.0f - ImGui::GetTextLineHeight()); + ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - + style.FramePadding.x * 2.0f - + ImGui::GetTextLineHeight()); if (ImGui::Button(ICON_FA_XMARK)) { Hide(); }