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 0000000000..1ff5a7489e Binary files /dev/null and b/data/xmu_mask.png differ diff --git a/ui/meson.build b/ui/meson.build index d09be2dbc0..18bb7c97c1 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -47,7 +47,7 @@ endif xemu_ss.add(when: 'CONFIG_LINUX', if_true: [gtk, files('xemu-os-utils-linux.c')]) xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c')) xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m')) -xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib) +xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib, fatx) softmmu_ss.add_all(xemu_ss) diff --git a/ui/thirdparty/fatx/fatx.c b/ui/thirdparty/fatx/fatx.c new file mode 100644 index 0000000000..20e934b0f7 --- /dev/null +++ b/ui/thirdparty/fatx/fatx.c @@ -0,0 +1,51 @@ +#include "fatx.h" + +#include "qemu/bswap.h" + +#define FATX_SIGNATURE 0x58544146 + +// This is from libfatx +#pragma pack(1) +struct fatx_superblock { + uint32_t signature; + uint32_t volume_id; + uint32_t sectors_per_cluster; + uint32_t root_cluster; + uint16_t unknown1; + uint8_t padding[4078]; +}; +#pragma pack() + +bool create_fatx_image(const char *filename, unsigned int size) +{ + unsigned int empty_fat = cpu_to_le32(0xfffffff8); + unsigned char zero = 0; + + FILE *fp = qemu_fopen(filename, "wb"); + if (fp != NULL) { + struct fatx_superblock superblock; + memset(&superblock, 0xff, sizeof(struct fatx_superblock)); + + superblock.signature = cpu_to_le32(FATX_SIGNATURE); + superblock.sectors_per_cluster = cpu_to_le32(4); + superblock.volume_id = (uint32_t)rand(); + superblock.root_cluster = cpu_to_le32(1); + superblock.unknown1 = 0; + + // Write the fatx superblock. + fwrite(&superblock, sizeof(superblock), 1, fp); + + // Write the FAT + fwrite(&empty_fat, sizeof(empty_fat), 1, fp); + + fseek(fp, size - sizeof(unsigned char), SEEK_SET); + fwrite(&zero, sizeof(unsigned char), 1, fp); + + fflush(fp); + fclose(fp); + + return true; + } + + return false; +} diff --git a/ui/thirdparty/fatx/fatx.h b/ui/thirdparty/fatx/fatx.h new file mode 100644 index 0000000000..cb4fb05324 --- /dev/null +++ b/ui/thirdparty/fatx/fatx.h @@ -0,0 +1,16 @@ +#ifndef FATX_H +#define FATX_H + +#include "qemu/osdep.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bool create_fatx_image(const char *filename, unsigned int size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ui/thirdparty/meson.build b/ui/thirdparty/meson.build index 6b11d3a9c1..ad01b58932 100644 --- a/ui/thirdparty/meson.build +++ b/ui/thirdparty/meson.build @@ -62,3 +62,6 @@ fpng = declare_dependency(include_directories: 'fpng', link_with: libfpng) json = declare_dependency(include_directories: 'json') httplib = declare_dependency(include_directories: 'httplib') + +libfatx = static_library('fatx', sources: 'fatx/fatx.c') +fatx = declare_dependency(include_directories: 'fatx', link_with: libfatx) diff --git a/ui/thirdparty/noc_file_dialog/noc_file_dialog.h b/ui/thirdparty/noc_file_dialog/noc_file_dialog.h index c41f86b678..3d8af29c3a 100644 --- a/ui/thirdparty/noc_file_dialog/noc_file_dialog.h +++ b/ui/thirdparty/noc_file_dialog/noc_file_dialog.h @@ -285,6 +285,9 @@ const char *noc_file_dialog_open(int flags, ofn.lpstrInitialDir = initialDir; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR; + if (flags & NOC_FILE_DIALOG_OVERWRITE_CONFIRMATION) + ofn.Flags |= OFN_OVERWRITEPROMPT; + if (flags & NOC_FILE_DIALOG_OPEN) { ret = GetOpenFileNameW(&ofn); } else { diff --git a/ui/xemu-input.c b/ui/xemu-input.c index aa93283096..d9181fe2a6 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -32,6 +32,8 @@ #include "xemu-notifications.h" #include "xemu-settings.h" +#include "sysemu/blockdev.h" + // #define DEBUG_INPUT #ifdef DEBUG_INPUT @@ -93,8 +95,32 @@ static const char **port_index_to_settings_key_map[] = { &g_config.input.bindings.port4, }; +static int *peripheral_types_settings_map[4][2] = { + { &g_config.input.peripherals.port1.peripheral_type_0, + &g_config.input.peripherals.port1.peripheral_type_1 }, + { &g_config.input.peripherals.port2.peripheral_type_0, + &g_config.input.peripherals.port2.peripheral_type_1 }, + { &g_config.input.peripherals.port3.peripheral_type_0, + &g_config.input.peripherals.port3.peripheral_type_1 }, + { &g_config.input.peripherals.port4.peripheral_type_0, + &g_config.input.peripherals.port4.peripheral_type_1 } +}; + +static const char **peripheral_params_settings_map[4][2] = { + { &g_config.input.peripherals.port1.peripheral_param_0, + &g_config.input.peripherals.port1.peripheral_param_1 }, + { &g_config.input.peripherals.port2.peripheral_param_0, + &g_config.input.peripherals.port2.peripheral_param_1 }, + { &g_config.input.peripherals.port3.peripheral_param_0, + &g_config.input.peripherals.port3.peripheral_param_1 }, + { &g_config.input.peripherals.port4.peripheral_param_0, + &g_config.input.peripherals.port4.peripheral_param_1 } +}; + static int sdl_kbd_scancode_map[25]; +static const int port_map[4] = { 3, 4, 1, 2 }; + void xemu_input_init(void) { if (g_config.input.background_input_capture) { @@ -112,6 +138,10 @@ void xemu_input_init(void) new_con->type = 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(); }