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
This commit is contained in:
Fred Hallock 2023-12-18 01:04:14 -05:00 committed by GitHub
parent 800eb468a4
commit 03f40b1d8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 769 additions and 142 deletions

View file

@ -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

View file

@ -1,5 +1,6 @@
pfiles = [
'controller_mask.png',
'xmu_mask.png',
'logo_sdf.png',
'xemu_64x64.png',
'abxy.ttf',

BIN
data/xmu_mask.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

View file

@ -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)

51
ui/thirdparty/fatx/fatx.c vendored Normal file
View file

@ -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;
}

16
ui/thirdparty/fatx/fatx.h vendored Normal file
View file

@ -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

View file

@ -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)

View file

@ -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 {

View file

@ -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;

View file

@ -26,6 +26,8 @@
#define XEMU_INPUT_H
#include <SDL2/SDL.h>
#include <stdbool.h>
#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);

View file

@ -16,25 +16,24 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "common.hh"
#include <stdio.h>
#include <math.h>
#include <vector>
#include <fpng.h>
#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 <fpng.h>
#include <math.h>
#include <stdio.h>
#include <vector>
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");
}

View file

@ -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<uint8_t> &png, int max_width = 0, int max_height = 0);

View file

@ -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();
}