mirror of https://github.com/xemu-project/xemu.git
ui: Add snapshot management UI
This commit is contained in:
parent
fe5160e859
commit
9c9f1e83eb
6
block.c
6
block.c
|
@ -2599,6 +2599,12 @@ static void bdrv_default_perms_for_storage(BlockDriverState *bs, BdrvChild *c,
|
||||||
shared |= BLK_PERM_WRITE | BLK_PERM_RESIZE;
|
shared |= BLK_PERM_WRITE | BLK_PERM_RESIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XBOX
|
||||||
|
if (bs->open_flags & BDRV_O_RO_WRITE_SHARE) {
|
||||||
|
shared |= BLK_PERM_WRITE | BLK_PERM_RESIZE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
*nperm = perm;
|
*nperm = perm;
|
||||||
*nshared = shared;
|
*nshared = shared;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "qapi/qmp/qstring.h"
|
#include "qapi/qmp/qstring.h"
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <winioctl.h>
|
#include <winioctl.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#define FTYPE_FILE 0
|
#define FTYPE_FILE 0
|
||||||
#define FTYPE_CD 1
|
#define FTYPE_CD 1
|
||||||
|
@ -341,6 +342,9 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
bool use_aio;
|
bool use_aio;
|
||||||
OnOffAuto locking;
|
OnOffAuto locking;
|
||||||
int ret;
|
int ret;
|
||||||
|
#ifdef XBOX
|
||||||
|
int sharing_flags;
|
||||||
|
#endif
|
||||||
|
|
||||||
s->type = FTYPE_FILE;
|
s->type = FTYPE_FILE;
|
||||||
|
|
||||||
|
@ -396,9 +400,21 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
|
||||||
if (!filename) {
|
if (!filename) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XBOX
|
||||||
|
sharing_flags = FILE_SHARE_READ;
|
||||||
|
if (flags & BDRV_O_RO_WRITE_SHARE) {
|
||||||
|
assert(access_flags == GENERIC_READ);
|
||||||
|
sharing_flags = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||||
|
}
|
||||||
|
s->hfile = CreateFileW(wfilename, access_flags,
|
||||||
|
sharing_flags, NULL,
|
||||||
|
OPEN_EXISTING, overlapped, NULL);
|
||||||
|
#else
|
||||||
s->hfile = CreateFileW(wfilename, access_flags,
|
s->hfile = CreateFileW(wfilename, access_flags,
|
||||||
FILE_SHARE_READ, NULL,
|
FILE_SHARE_READ, NULL,
|
||||||
OPEN_EXISTING, overlapped, NULL);
|
OPEN_EXISTING, overlapped, NULL);
|
||||||
|
#endif
|
||||||
g_free(wfilename);
|
g_free(wfilename);
|
||||||
if (s->hfile == INVALID_HANDLE_VALUE) {
|
if (s->hfile == INVALID_HANDLE_VALUE) {
|
||||||
int err = GetLastError();
|
int err = GetLastError();
|
||||||
|
|
|
@ -11,6 +11,13 @@ general:
|
||||||
# throttle_io: bool
|
# throttle_io: bool
|
||||||
last_viewed_menu_index: integer
|
last_viewed_menu_index: integer
|
||||||
user_token: string
|
user_token: string
|
||||||
|
snapshots:
|
||||||
|
shortcuts:
|
||||||
|
f5: string
|
||||||
|
f6: string
|
||||||
|
f7: string
|
||||||
|
f8: string
|
||||||
|
filter_current_game: bool
|
||||||
|
|
||||||
input:
|
input:
|
||||||
bindings:
|
bindings:
|
||||||
|
|
|
@ -123,6 +123,10 @@ typedef struct HDGeometry {
|
||||||
#define BDRV_O_AUTO_RDONLY 0x20000 /* degrade to read-only if opening read-write fails */
|
#define BDRV_O_AUTO_RDONLY 0x20000 /* degrade to read-only if opening read-write fails */
|
||||||
#define BDRV_O_IO_URING 0x40000 /* use io_uring instead of the thread pool */
|
#define BDRV_O_IO_URING 0x40000 /* use io_uring instead of the thread pool */
|
||||||
|
|
||||||
|
#ifdef XBOX
|
||||||
|
#define BDRV_O_RO_WRITE_SHARE 0x80000 /* allow the file to open RO alongside an existing RW handle */
|
||||||
|
#endif
|
||||||
|
|
||||||
#define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_NO_FLUSH)
|
#define BDRV_O_CACHE_MASK (BDRV_O_NOCACHE | BDRV_O_NO_FLUSH)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,8 @@
|
||||||
#include "qemu/yank.h"
|
#include "qemu/yank.h"
|
||||||
#include "yank_functions.h"
|
#include "yank_functions.h"
|
||||||
|
|
||||||
|
#include "ui/xemu-snapshots.h"
|
||||||
|
|
||||||
const unsigned int postcopy_ram_discard_version;
|
const unsigned int postcopy_ram_discard_version;
|
||||||
|
|
||||||
/* Subcommands for QEMU_VM_COMMAND */
|
/* Subcommands for QEMU_VM_COMMAND */
|
||||||
|
@ -1570,6 +1572,9 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp)
|
||||||
ms->to_dst_file = f;
|
ms->to_dst_file = f;
|
||||||
|
|
||||||
qemu_mutex_unlock_iothread();
|
qemu_mutex_unlock_iothread();
|
||||||
|
#ifdef XBOX
|
||||||
|
xemu_snapshots_save_extra_data(f);
|
||||||
|
#endif
|
||||||
qemu_savevm_state_header(f);
|
qemu_savevm_state_header(f);
|
||||||
qemu_savevm_state_setup(f);
|
qemu_savevm_state_setup(f);
|
||||||
qemu_mutex_lock_iothread();
|
qemu_mutex_lock_iothread();
|
||||||
|
@ -2691,6 +2696,12 @@ int qemu_loadvm_state(QEMUFile *f)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XBOX
|
||||||
|
if (!xemu_snapshots_offset_extra_data(f)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
ret = qemu_loadvm_state_header(f);
|
ret = qemu_loadvm_state_header(f);
|
||||||
if (ret) {
|
if (ret) {
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -3086,6 +3097,10 @@ bool delete_snapshot(const char *name, bool has_devices,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef XBOX
|
||||||
|
xemu_snapshots_mark_dirty();
|
||||||
|
#endif
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@ xemu_ss.add(files(
|
||||||
|
|
||||||
'xemu.c',
|
'xemu.c',
|
||||||
'xemu-data.c',
|
'xemu-data.c',
|
||||||
|
'xemu-snapshots.c',
|
||||||
|
'xemu-thumbnail.cc',
|
||||||
))
|
))
|
||||||
|
|
||||||
subdir('xui')
|
subdir('xui')
|
||||||
|
|
|
@ -5,6 +5,7 @@ imgui_files = files(
|
||||||
'imgui/imgui_widgets.cpp',
|
'imgui/imgui_widgets.cpp',
|
||||||
'imgui/backends/imgui_impl_sdl.cpp',
|
'imgui/backends/imgui_impl_sdl.cpp',
|
||||||
'imgui/backends/imgui_impl_opengl3.cpp',
|
'imgui/backends/imgui_impl_opengl3.cpp',
|
||||||
|
'imgui/misc/cpp/imgui_stdlib.cpp',
|
||||||
#'imgui/imgui_demo.cpp',
|
#'imgui/imgui_demo.cpp',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -58,6 +59,7 @@ void xemu_settings_save(void);
|
||||||
|
|
||||||
static inline void xemu_settings_set_string(const char **str, const char *new_str)
|
static inline void xemu_settings_set_string(const char **str, const char *new_str)
|
||||||
{
|
{
|
||||||
|
assert(new_str);
|
||||||
free((char*)*str);
|
free((char*)*str);
|
||||||
*str = strdup(new_str);
|
*str = strdup(new_str);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,319 @@
|
||||||
|
/*
|
||||||
|
* xemu User Interface
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* 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 "xemu-snapshots.h"
|
||||||
|
#include "xemu-settings.h"
|
||||||
|
#include "xemu-xbe.h"
|
||||||
|
|
||||||
|
#include <SDL2/SDL.h>
|
||||||
|
#include <epoxy/gl.h>
|
||||||
|
|
||||||
|
#include "block/aio.h"
|
||||||
|
#include "block/block_int.h"
|
||||||
|
#include "block/qapi.h"
|
||||||
|
#include "block/qdict.h"
|
||||||
|
#include "migration/qemu-file.h"
|
||||||
|
#include "migration/snapshot.h"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
#include "sysemu/runstate.h"
|
||||||
|
#include "qemu-common.h"
|
||||||
|
|
||||||
|
#include "ui/console.h"
|
||||||
|
#include "ui/input.h"
|
||||||
|
|
||||||
|
static QEMUSnapshotInfo *xemu_snapshots_metadata = NULL;
|
||||||
|
static XemuSnapshotData *xemu_snapshots_extra_data = NULL;
|
||||||
|
static int xemu_snapshots_len = 0;
|
||||||
|
static bool xemu_snapshots_dirty = true;
|
||||||
|
|
||||||
|
const char **g_snapshot_shortcut_index_key_map[] = {
|
||||||
|
&g_config.general.snapshots.shortcuts.f5,
|
||||||
|
&g_config.general.snapshots.shortcuts.f6,
|
||||||
|
&g_config.general.snapshots.shortcuts.f7,
|
||||||
|
&g_config.general.snapshots.shortcuts.f8,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool xemu_snapshots_load_thumbnail(BlockDriverState *bs_ro,
|
||||||
|
XemuSnapshotData *data,
|
||||||
|
int64_t *offset)
|
||||||
|
{
|
||||||
|
int res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->thumbnail, *offset,
|
||||||
|
sizeof(TextureBuffer) -
|
||||||
|
sizeof(data->thumbnail.buffer));
|
||||||
|
if (res != sizeof(TextureBuffer) - sizeof(data->thumbnail.buffer))
|
||||||
|
return false;
|
||||||
|
*offset += res;
|
||||||
|
|
||||||
|
data->thumbnail.buffer = g_malloc(data->thumbnail.size);
|
||||||
|
|
||||||
|
res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->thumbnail.buffer, *offset,
|
||||||
|
data->thumbnail.size);
|
||||||
|
if (res != data->thumbnail.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*offset += res;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xemu_snapshots_load_data(BlockDriverState *bs_ro,
|
||||||
|
QEMUSnapshotInfo *info,
|
||||||
|
XemuSnapshotData *data, Error **err)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
XemuSnapshotHeader header;
|
||||||
|
int64_t offset = 0;
|
||||||
|
|
||||||
|
data->xbe_title_present = false;
|
||||||
|
data->thumbnail_present = false;
|
||||||
|
res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err);
|
||||||
|
if (res < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
res = bdrv_load_vmstate(bs_ro, (uint8_t *)&header, offset, sizeof(header));
|
||||||
|
if (res != sizeof(header))
|
||||||
|
goto error;
|
||||||
|
offset += res;
|
||||||
|
|
||||||
|
if (header.magic != XEMU_SNAPSHOT_DATA_MAGIC)
|
||||||
|
goto error;
|
||||||
|
|
||||||
|
res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->xbe_title_len, offset,
|
||||||
|
sizeof(data->xbe_title_len));
|
||||||
|
if (res != sizeof(data->xbe_title_len))
|
||||||
|
goto error;
|
||||||
|
offset += res;
|
||||||
|
|
||||||
|
data->xbe_title = (char *)g_malloc(data->xbe_title_len);
|
||||||
|
|
||||||
|
res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->xbe_title, offset,
|
||||||
|
data->xbe_title_len);
|
||||||
|
if (res != data->xbe_title_len)
|
||||||
|
goto error;
|
||||||
|
offset += res;
|
||||||
|
|
||||||
|
data->xbe_title_present = (offset <= sizeof(header) + header.size);
|
||||||
|
|
||||||
|
if (offset == sizeof(header) + header.size)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!xemu_snapshots_load_thumbnail(bs_ro, data, &offset)) {
|
||||||
|
goto error;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->thumbnail_present = (offset <= sizeof(header) + header.size);
|
||||||
|
|
||||||
|
if (data->thumbnail_present) {
|
||||||
|
glGenTextures(1, &data->gl_thumbnail);
|
||||||
|
xemu_snapshots_render_thumbnail(data->gl_thumbnail, &data->thumbnail);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
error:
|
||||||
|
g_free(data->xbe_title);
|
||||||
|
data->xbe_title_present = false;
|
||||||
|
|
||||||
|
g_free(data->thumbnail.buffer);
|
||||||
|
data->thumbnail_present = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info,
|
||||||
|
XemuSnapshotData **data,
|
||||||
|
int snapshots_len, Error **err)
|
||||||
|
{
|
||||||
|
BlockDriverState *bs_ro;
|
||||||
|
QDict *opts = qdict_new();
|
||||||
|
|
||||||
|
assert(info && data);
|
||||||
|
|
||||||
|
if (*data) {
|
||||||
|
for (int i = 0; i < xemu_snapshots_len; ++i) {
|
||||||
|
if ((*data)[i].xbe_title_present) {
|
||||||
|
g_free((*data)[i].xbe_title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*data)[i].thumbnail_present) {
|
||||||
|
g_free((*data)[i].thumbnail.buffer);
|
||||||
|
glDeleteTextures(1, &((*data)[i].gl_thumbnail));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_free(*data);
|
||||||
|
}
|
||||||
|
|
||||||
|
*data =
|
||||||
|
(XemuSnapshotData *)g_malloc(sizeof(XemuSnapshotData) * snapshots_len);
|
||||||
|
memset(*data, 0, sizeof(XemuSnapshotData) * snapshots_len);
|
||||||
|
|
||||||
|
qdict_put_bool(opts, BDRV_OPT_READ_ONLY, true);
|
||||||
|
bs_ro = bdrv_open(g_config.sys.files.hdd_path, NULL, opts,
|
||||||
|
BDRV_O_RO_WRITE_SHARE | BDRV_O_AUTO_RDONLY, err);
|
||||||
|
if (!bs_ro) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < snapshots_len; ++i) {
|
||||||
|
xemu_snapshots_load_data(bs_ro, (*info) + i, (*data) + i, err);
|
||||||
|
if (*err) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bdrv_flush(bs_ro);
|
||||||
|
bdrv_drain(bs_ro);
|
||||||
|
bdrv_unref(bs_ro);
|
||||||
|
assert(bs_ro->refcnt == 0);
|
||||||
|
if (!(*err))
|
||||||
|
xemu_snapshots_dirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data,
|
||||||
|
Error **err)
|
||||||
|
{
|
||||||
|
BlockDriverState *bs;
|
||||||
|
AioContext *aio_context;
|
||||||
|
int snapshots_len;
|
||||||
|
assert(err);
|
||||||
|
|
||||||
|
if (!xemu_snapshots_dirty && xemu_snapshots_extra_data &&
|
||||||
|
xemu_snapshots_metadata) {
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xemu_snapshots_metadata)
|
||||||
|
g_free(xemu_snapshots_metadata);
|
||||||
|
|
||||||
|
bs = bdrv_all_find_vmstate_bs(NULL, false, NULL, err);
|
||||||
|
if (!bs) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
aio_context = bdrv_get_aio_context(bs);
|
||||||
|
|
||||||
|
aio_context_acquire(aio_context);
|
||||||
|
snapshots_len = bdrv_snapshot_list(bs, &xemu_snapshots_metadata);
|
||||||
|
aio_context_release(aio_context);
|
||||||
|
xemu_snapshots_all_load_data(&xemu_snapshots_metadata,
|
||||||
|
&xemu_snapshots_extra_data, snapshots_len,
|
||||||
|
err);
|
||||||
|
if (*err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
xemu_snapshots_len = snapshots_len;
|
||||||
|
|
||||||
|
done:
|
||||||
|
if (info) {
|
||||||
|
*info = xemu_snapshots_metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extra_data) {
|
||||||
|
*extra_data = xemu_snapshots_extra_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return xemu_snapshots_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_snapshots_load(const char *vm_name, Error **err)
|
||||||
|
{
|
||||||
|
bool vm_running = runstate_is_running();
|
||||||
|
vm_stop(RUN_STATE_RESTORE_VM);
|
||||||
|
if (load_snapshot(vm_name, NULL, false, NULL, err) && vm_running) {
|
||||||
|
vm_start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_snapshots_save(const char *vm_name, Error **err)
|
||||||
|
{
|
||||||
|
save_snapshot(vm_name, true, NULL, false, NULL, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_snapshots_delete(const char *vm_name, Error **err)
|
||||||
|
{
|
||||||
|
delete_snapshot(vm_name, false, NULL, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_snapshots_save_extra_data(QEMUFile *f)
|
||||||
|
{
|
||||||
|
struct xbe *xbe_data = xemu_get_xbe_info();
|
||||||
|
|
||||||
|
int64_t xbe_title_len = 0;
|
||||||
|
char *xbe_title = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL,
|
||||||
|
&xbe_title_len, NULL);
|
||||||
|
xbe_title_len++;
|
||||||
|
|
||||||
|
XemuSnapshotHeader header = { XEMU_SNAPSHOT_DATA_MAGIC, 0 };
|
||||||
|
|
||||||
|
header.size += sizeof(xbe_title_len);
|
||||||
|
header.size += xbe_title_len;
|
||||||
|
|
||||||
|
TextureBuffer *thumbnail = xemu_snapshots_extract_thumbnail();
|
||||||
|
if (thumbnail && thumbnail->buffer) {
|
||||||
|
header.size += sizeof(TextureBuffer) - sizeof(thumbnail->buffer);
|
||||||
|
header.size += thumbnail->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
qemu_put_buffer(f, (const uint8_t *)&header, sizeof(header));
|
||||||
|
qemu_put_buffer(f, (const uint8_t *)&xbe_title_len, sizeof(xbe_title_len));
|
||||||
|
qemu_put_buffer(f, (const uint8_t *)xbe_title, xbe_title_len);
|
||||||
|
|
||||||
|
if (thumbnail && thumbnail->buffer) {
|
||||||
|
qemu_put_buffer(f, (const uint8_t *)thumbnail,
|
||||||
|
sizeof(TextureBuffer) - sizeof(thumbnail->buffer));
|
||||||
|
qemu_put_buffer(f, (const uint8_t *)thumbnail->buffer, thumbnail->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(xbe_title);
|
||||||
|
|
||||||
|
if (thumbnail && thumbnail->buffer) {
|
||||||
|
g_free(thumbnail->buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(thumbnail);
|
||||||
|
|
||||||
|
xemu_snapshots_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool xemu_snapshots_offset_extra_data(QEMUFile *f)
|
||||||
|
{
|
||||||
|
size_t ret;
|
||||||
|
XemuSnapshotHeader header;
|
||||||
|
ret = qemu_get_buffer(f, (uint8_t *)&header, sizeof(header));
|
||||||
|
if (ret != sizeof(header)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.magic == XEMU_SNAPSHOT_DATA_MAGIC) {
|
||||||
|
/*
|
||||||
|
* qemu_file_skip only works if you aren't skipping past its buffer.
|
||||||
|
* Unfortunately, it's not usable here.
|
||||||
|
*/
|
||||||
|
void *buf = g_malloc(header.size);
|
||||||
|
qemu_get_buffer(f, buf, header.size);
|
||||||
|
g_free(buf);
|
||||||
|
} else {
|
||||||
|
qemu_file_skip(f, -((int)sizeof(header)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_snapshots_mark_dirty(void)
|
||||||
|
{
|
||||||
|
xemu_snapshots_dirty = true;
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* xemu User Interface
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XEMU_SNAPSHOTS_H
|
||||||
|
#define XEMU_SNAPSHOTS_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "qemu/osdep.h"
|
||||||
|
#include "block/snapshot.h"
|
||||||
|
|
||||||
|
#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75
|
||||||
|
#define XEMU_SNAPSHOT_HEIGHT 120
|
||||||
|
#define XEMU_SNAPSHOT_WIDTH 160
|
||||||
|
|
||||||
|
extern const char **g_snapshot_shortcut_index_key_map[];
|
||||||
|
|
||||||
|
#pragma pack(1)
|
||||||
|
typedef struct TextureBuffer {
|
||||||
|
int channels;
|
||||||
|
unsigned long size;
|
||||||
|
void *buffer;
|
||||||
|
} TextureBuffer;
|
||||||
|
#pragma pack()
|
||||||
|
|
||||||
|
typedef struct XemuSnapshotHeader {
|
||||||
|
uint32_t magic;
|
||||||
|
uint32_t size;
|
||||||
|
} XemuSnapshotHeader;
|
||||||
|
|
||||||
|
typedef struct XemuSnapshotData {
|
||||||
|
int64_t xbe_title_len;
|
||||||
|
char *xbe_title;
|
||||||
|
bool xbe_title_present;
|
||||||
|
TextureBuffer thumbnail;
|
||||||
|
bool thumbnail_present;
|
||||||
|
unsigned int gl_thumbnail;
|
||||||
|
} XemuSnapshotData;
|
||||||
|
|
||||||
|
// Implemented in xemu-snapshots.c
|
||||||
|
int xemu_snapshots_list(QEMUSnapshotInfo **info, XemuSnapshotData **extra_data,
|
||||||
|
Error **err);
|
||||||
|
void xemu_snapshots_load(const char *vm_name, Error **err);
|
||||||
|
void xemu_snapshots_save(const char *vm_name, Error **err);
|
||||||
|
void xemu_snapshots_delete(const char *vm_name, Error **err);
|
||||||
|
|
||||||
|
void xemu_snapshots_save_extra_data(QEMUFile *f);
|
||||||
|
bool xemu_snapshots_offset_extra_data(QEMUFile *f);
|
||||||
|
void xemu_snapshots_mark_dirty(void);
|
||||||
|
|
||||||
|
// Implemented in xemu-thumbnail.cc
|
||||||
|
void xemu_snapshots_set_framebuffer_texture(unsigned int tex, bool flip);
|
||||||
|
void xemu_snapshots_render_thumbnail(unsigned int tex,
|
||||||
|
TextureBuffer *thumbnail);
|
||||||
|
TextureBuffer *xemu_snapshots_extract_thumbnail(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* xemu User Interface
|
||||||
|
*
|
||||||
|
* Copyright (C) 2020-2022 Matt Borgerson
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* 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 <cstdint>
|
||||||
|
#include <fpng.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xemu-snapshots.h"
|
||||||
|
#include "xui/gl-helpers.hh"
|
||||||
|
|
||||||
|
static GLuint display_tex = 0;
|
||||||
|
static bool display_flip = false;
|
||||||
|
|
||||||
|
void xemu_snapshots_set_framebuffer_texture(GLuint tex, bool flip)
|
||||||
|
{
|
||||||
|
display_tex = tex;
|
||||||
|
display_flip = flip;
|
||||||
|
}
|
||||||
|
|
||||||
|
void xemu_snapshots_render_thumbnail(GLuint tex, TextureBuffer *thumbnail)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> pixels;
|
||||||
|
unsigned int width, height, channels;
|
||||||
|
if (fpng::fpng_decode_memory(
|
||||||
|
thumbnail->buffer, thumbnail->size, pixels, width, height, channels,
|
||||||
|
thumbnail->channels) != fpng::FPNG_DECODE_SUCCESS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
|
||||||
|
GL_UNSIGNED_BYTE, pixels.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureBuffer *xemu_snapshots_extract_thumbnail()
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Avoids crashing if a snapshot is made on a thread with no GL context
|
||||||
|
* Normally, this is not an issue, but it is better to fail safe than assert
|
||||||
|
* here.
|
||||||
|
* FIXME: Allow for dispatching a thumbnail request to the UI thread to
|
||||||
|
* remove this altogether.
|
||||||
|
*/
|
||||||
|
if (!SDL_GL_GetCurrentContext() || display_tex == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render at 2x the base size to account for potential UI scaling
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, display_tex);
|
||||||
|
int thumbnail_width = XEMU_SNAPSHOT_WIDTH * 2;
|
||||||
|
int tex_width;
|
||||||
|
int tex_height;
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tex_width);
|
||||||
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &tex_height);
|
||||||
|
int thumbnail_height = (int)(((float)tex_height / (float)tex_width) * (float)thumbnail_width);
|
||||||
|
|
||||||
|
std::vector<uint8_t> png;
|
||||||
|
if (!ExtractFramebufferPixels(display_tex, display_flip, png, thumbnail_width, thumbnail_height)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureBuffer *thumbnail = (TextureBuffer *)g_malloc(sizeof(TextureBuffer));
|
||||||
|
thumbnail->buffer = g_malloc(png.size() * sizeof(uint8_t));
|
||||||
|
|
||||||
|
thumbnail->channels = 3;
|
||||||
|
thumbnail->size = png.size() * sizeof(uint8_t);
|
||||||
|
memcpy(thumbnail->buffer, png.data(), thumbnail->size);
|
||||||
|
return thumbnail;
|
||||||
|
}
|
|
@ -47,6 +47,7 @@
|
||||||
#include "xemu-input.h"
|
#include "xemu-input.h"
|
||||||
#include "xemu-settings.h"
|
#include "xemu-settings.h"
|
||||||
// #include "xemu-shaders.h"
|
// #include "xemu-shaders.h"
|
||||||
|
#include "xemu-snapshots.h"
|
||||||
#include "xemu-version.h"
|
#include "xemu-version.h"
|
||||||
#include "xemu-os-utils.h"
|
#include "xemu-os-utils.h"
|
||||||
|
|
||||||
|
@ -1199,6 +1200,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
|
||||||
|
|
||||||
glClearColor(0, 0, 0, 0);
|
glClearColor(0, 0, 0, 0);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
xemu_snapshots_set_framebuffer_texture(tex, flip_required);
|
||||||
xemu_hud_set_framebuffer_texture(tex, flip_required);
|
xemu_hud_set_framebuffer_texture(tex, flip_required);
|
||||||
xemu_hud_render();
|
xemu_hud_render();
|
||||||
|
|
||||||
|
@ -1548,6 +1550,7 @@ int main(int argc, char **argv)
|
||||||
|
|
||||||
while (1) {
|
while (1) {
|
||||||
sdl2_gl_refresh(&sdl2_console[0].dcl);
|
sdl2_gl_refresh(&sdl2_console[0].dcl);
|
||||||
|
assert(glGetError() == GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// rcu_unregister_thread();
|
// rcu_unregister_thread();
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
#include "common.hh"
|
#include "common.hh"
|
||||||
#include "misc.hh"
|
#include "misc.hh"
|
||||||
#include "xemu-hud.h"
|
#include "xemu-hud.h"
|
||||||
|
#include "../xemu-snapshots.h"
|
||||||
|
#include "../xemu-notifications.h"
|
||||||
|
|
||||||
void ActionEjectDisc(void)
|
void ActionEjectDisc(void)
|
||||||
{
|
{
|
||||||
|
@ -63,3 +65,27 @@ void ActionScreenshot(void)
|
||||||
{
|
{
|
||||||
g_screenshot_pending = true;
|
g_screenshot_pending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActionActivateBoundSnapshot(int slot, bool save)
|
||||||
|
{
|
||||||
|
assert(slot < 4 && slot >= 0);
|
||||||
|
const char *snapshot_name = *(g_snapshot_shortcut_index_key_map[slot]);
|
||||||
|
if (!snapshot_name || !(snapshot_name[0])) {
|
||||||
|
char *msg = g_strdup_printf("F%d is not bound to a snapshot", slot + 5);
|
||||||
|
xemu_queue_notification(msg);
|
||||||
|
g_free(msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Error *err = NULL;
|
||||||
|
if (save) {
|
||||||
|
xemu_snapshots_save(snapshot_name, &err);
|
||||||
|
} else {
|
||||||
|
xemu_snapshots_load(snapshot_name, &err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
|
error_free(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,3 +24,4 @@ void ActionTogglePause();
|
||||||
void ActionReset();
|
void ActionReset();
|
||||||
void ActionShutdown();
|
void ActionShutdown();
|
||||||
void ActionScreenshot();
|
void ActionScreenshot();
|
||||||
|
void ActionActivateBoundSnapshot(int slot, bool save);
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <imgui_impl_sdl.h>
|
#include <imgui_impl_sdl.h>
|
||||||
#include <imgui_impl_opengl3.h>
|
#include <imgui_impl_opengl3.h>
|
||||||
#include <implot.h>
|
#include <implot.h>
|
||||||
|
#include <misc/cpp/imgui_stdlib.h>
|
||||||
#include <stb_image.h>
|
#include <stb_image.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
|
@ -26,12 +26,14 @@
|
||||||
#include "data/controller_mask.png.h"
|
#include "data/controller_mask.png.h"
|
||||||
#include "data/logo_sdf.png.h"
|
#include "data/logo_sdf.png.h"
|
||||||
#include "ui/shader/xemu-logo-frag.h"
|
#include "ui/shader/xemu-logo-frag.h"
|
||||||
|
#include "data/xemu_64x64.png.h"
|
||||||
#include "notifications.hh"
|
#include "notifications.hh"
|
||||||
|
|
||||||
Fbo *controller_fbo,
|
Fbo *controller_fbo,
|
||||||
*logo_fbo;
|
*logo_fbo;
|
||||||
GLuint g_controller_tex,
|
GLuint g_controller_tex,
|
||||||
g_logo_tex;
|
g_logo_tex,
|
||||||
|
g_icon_tex;
|
||||||
|
|
||||||
enum class ShaderType {
|
enum class ShaderType {
|
||||||
Blit,
|
Blit,
|
||||||
|
@ -155,10 +157,10 @@ static GLuint InitTexture(unsigned char *data, int width, int height,
|
||||||
return tex;
|
return tex;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GLuint LoadTextureFromMemory(const unsigned char *buf, unsigned int size)
|
static GLuint LoadTextureFromMemory(const unsigned char *buf, unsigned int size, bool flip=true)
|
||||||
{
|
{
|
||||||
// Flip vertically so textures are loaded according to GL convention.
|
// Flip vertically so textures are loaded according to GL convention.
|
||||||
stbi_set_flip_vertically_on_load(1);
|
stbi_set_flip_vertically_on_load(flip);
|
||||||
|
|
||||||
int width, height, channels = 0;
|
int width, height, channels = 0;
|
||||||
unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
|
unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
|
||||||
|
@ -442,6 +444,8 @@ void InitCustomRendering(void)
|
||||||
g_logo_shader = NewDecalShader(ShaderType::Logo);
|
g_logo_shader = NewDecalShader(ShaderType::Logo);
|
||||||
logo_fbo = new Fbo(512, 512);
|
logo_fbo = new Fbo(512, 512);
|
||||||
|
|
||||||
|
g_icon_tex = LoadTextureFromMemory(xemu_64x64_data, xemu_64x64_size, false);
|
||||||
|
|
||||||
g_framebuffer_shader = NewDecalShader(ShaderType::BlitGamma);
|
g_framebuffer_shader = NewDecalShader(ShaderType::BlitGamma);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -657,7 +661,7 @@ void RenderLogo(uint32_t time)
|
||||||
glUseProgram(0);
|
glUseProgram(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderFramebuffer(GLint tex, int width, int height, bool flip)
|
void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor)
|
||||||
{
|
{
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, tex);
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
@ -668,7 +672,10 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
|
||||||
|
|
||||||
// Calculate scaling factors
|
// Calculate scaling factors
|
||||||
float scale[2];
|
float scale[2];
|
||||||
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
|
if (!apply_scaling_factor) {
|
||||||
|
scale[0] = 1.0;
|
||||||
|
scale[1] = 1.0;
|
||||||
|
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
|
||||||
// Stretch to fit
|
// Stretch to fit
|
||||||
scale[0] = 1.0;
|
scale[0] = 1.0;
|
||||||
scale[1] = 1.0;
|
scale[1] = 1.0;
|
||||||
|
@ -720,20 +727,28 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
|
||||||
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SaveScreenshot(GLuint tex, bool flip)
|
bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector<uint8_t> &png, int width, int height)
|
||||||
{
|
{
|
||||||
int width, height;
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
glBindTexture(GL_TEXTURE_2D, tex);
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
assert((width == 0 && height == 0) || (width > 0 && height > 0));
|
||||||
|
|
||||||
|
bool params_from_tex = false;
|
||||||
|
if (width <= 0 && height <= 0) {
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
|
||||||
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
|
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
|
||||||
|
params_from_tex = true;
|
||||||
|
}
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
assert(width > 0 && height > 0);
|
||||||
|
|
||||||
|
if (params_from_tex) {
|
||||||
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
|
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
|
||||||
width = height * (16.0f / 9.0f);
|
width = height * (16.0f / 9.0f);
|
||||||
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
|
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
|
||||||
width = height * (4.0f / 3.0f);
|
width = height * (4.0f / 3.0f);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> pixels;
|
std::vector<uint8_t> pixels;
|
||||||
pixels.resize(width * height * 3);
|
pixels.resize(width * height * 3);
|
||||||
|
@ -742,7 +757,7 @@ void SaveScreenshot(GLuint tex, bool flip)
|
||||||
fbo.Target();
|
fbo.Target();
|
||||||
bool blend = glIsEnabled(GL_BLEND);
|
bool blend = glIsEnabled(GL_BLEND);
|
||||||
if (blend) glDisable(GL_BLEND);
|
if (blend) glDisable(GL_BLEND);
|
||||||
RenderFramebuffer(tex, width, height, !flip);
|
RenderFramebuffer(tex, width, height, !flip, params_from_tex);
|
||||||
if (blend) glEnable(GL_BLEND);
|
if (blend) glEnable(GL_BLEND);
|
||||||
glPixelStorei(GL_PACK_ROW_LENGTH, width);
|
glPixelStorei(GL_PACK_ROW_LENGTH, width);
|
||||||
glPixelStorei(GL_PACK_IMAGE_HEIGHT, height);
|
glPixelStorei(GL_PACK_IMAGE_HEIGHT, height);
|
||||||
|
@ -750,10 +765,15 @@ void SaveScreenshot(GLuint tex, bool flip)
|
||||||
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
|
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
|
||||||
fbo.Restore();
|
fbo.Restore();
|
||||||
|
|
||||||
char fname[128];
|
return fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SaveScreenshot(GLuint tex, bool flip)
|
||||||
|
{
|
||||||
Error *err = NULL;
|
Error *err = NULL;
|
||||||
|
char fname[128];
|
||||||
std::vector<uint8_t> png;
|
std::vector<uint8_t> png;
|
||||||
if (fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png)) {
|
if (ExtractFramebufferPixels(tex, flip, png)) {
|
||||||
time_t t = time(NULL);
|
time_t t = time(NULL);
|
||||||
struct tm *tmp = localtime(&t);
|
struct tm *tmp = localtime(&t);
|
||||||
if (tmp) {
|
if (tmp) {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include <vector>
|
||||||
#include "common.hh"
|
#include "common.hh"
|
||||||
#include "../xemu-input.h"
|
#include "../xemu-input.h"
|
||||||
|
|
||||||
|
@ -38,6 +39,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Fbo *controller_fbo, *logo_fbo;
|
extern Fbo *controller_fbo, *logo_fbo;
|
||||||
|
extern GLuint g_icon_tex;
|
||||||
|
|
||||||
void InitCustomRendering(void);
|
void InitCustomRendering(void);
|
||||||
void RenderLogo(uint32_t time);
|
void RenderLogo(uint32_t time);
|
||||||
|
@ -45,5 +47,6 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
|
||||||
uint32_t secondary_color, ControllerState *state);
|
uint32_t secondary_color, ControllerState *state);
|
||||||
void RenderControllerPort(float frame_x, float frame_y, int i,
|
void RenderControllerPort(float frame_x, float frame_y, int i,
|
||||||
uint32_t port_color);
|
uint32_t port_color);
|
||||||
void RenderFramebuffer(GLint tex, int width, int height, bool flip);
|
void RenderFramebuffer(GLint tex, int width, int height, bool flip, bool apply_scaling_factor = true);
|
||||||
|
bool ExtractFramebufferPixels(GLuint tex, bool flip, std::vector<uint8_t> &png, int width = 0, int height = 0);
|
||||||
void SaveScreenshot(GLuint tex, bool flip);
|
void SaveScreenshot(GLuint tex, bool flip);
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "misc.hh"
|
#include "misc.hh"
|
||||||
#include "gl-helpers.hh"
|
#include "gl-helpers.hh"
|
||||||
#include "reporting.hh"
|
#include "reporting.hh"
|
||||||
|
#include "qapi/error.h"
|
||||||
|
|
||||||
#include "../xemu-input.h"
|
#include "../xemu-input.h"
|
||||||
#include "../xemu-notifications.h"
|
#include "../xemu-notifications.h"
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
#include "../xemu-monitor.h"
|
#include "../xemu-monitor.h"
|
||||||
#include "../xemu-version.h"
|
#include "../xemu-version.h"
|
||||||
#include "../xemu-net.h"
|
#include "../xemu-net.h"
|
||||||
|
#include "../xemu-snapshots.h"
|
||||||
#include "../xemu-os-utils.h"
|
#include "../xemu-os-utils.h"
|
||||||
#include "../xemu-xbe.h"
|
#include "../xemu-xbe.h"
|
||||||
|
|
||||||
|
@ -639,84 +641,159 @@ void MainMenuNetworkView::DrawUdpOptions(bool appearing)
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
void MainMenuSnapshotsView::Load()
|
||||||
class MainMenuSnapshotsView : public virtual MainMenuTabView
|
|
||||||
{
|
{
|
||||||
protected:
|
Error *err = NULL;
|
||||||
GLuint screenshot;
|
|
||||||
|
|
||||||
public:
|
if (!m_load_failed) {
|
||||||
void initScreenshot()
|
m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err);
|
||||||
{
|
}
|
||||||
if (screenshot == 0) {
|
|
||||||
glGenTextures(1, &screenshot);
|
if (err) {
|
||||||
int w, h, n;
|
m_load_failed = true;
|
||||||
stbi_set_flip_vertically_on_load(0);
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
unsigned char *data = stbi_load("./data/screenshot.png", &w, &h, &n, 4);
|
error_free(err);
|
||||||
assert(n == 4);
|
m_snapshots_len = 0;
|
||||||
glActiveTexture(GL_TEXTURE0);
|
}
|
||||||
glBindTexture(GL_TEXTURE_2D, screenshot);
|
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
|
struct xbe *xbe = xemu_get_xbe_info();
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
if (xbe && xbe->cert->m_titleid != m_current_title_id) {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
g_free(m_current_title_name);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
m_current_title_name = g_utf16_to_utf8(xbe->cert->m_title_name, 40, NULL, NULL, NULL);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
m_current_title_id = xbe->cert->m_titleid;
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
|
}
|
||||||
stbi_image_free(data);
|
}
|
||||||
|
|
||||||
|
MainMenuSnapshotsView::MainMenuSnapshotsView(): MainMenuTabView()
|
||||||
|
{
|
||||||
|
xemu_snapshots_mark_dirty();
|
||||||
|
m_load_failed = false;
|
||||||
|
|
||||||
|
m_search_regex = NULL;
|
||||||
|
m_current_title_name = NULL;
|
||||||
|
m_current_title_id = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
MainMenuSnapshotsView::~MainMenuSnapshotsView()
|
||||||
|
{
|
||||||
|
g_free(m_snapshots);
|
||||||
|
g_free(m_extra_data);
|
||||||
|
xemu_snapshots_mark_dirty();
|
||||||
|
|
||||||
|
g_free(m_current_title_name);
|
||||||
|
g_free(m_search_regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainMenuSnapshotsView::SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name, GLuint screenshot)
|
||||||
|
{
|
||||||
|
Error *err = NULL;
|
||||||
|
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) {
|
||||||
|
assert(current_snapshot_binding == -1);
|
||||||
|
current_snapshot_binding = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void snapshotBigButton(const char *name, const char *title_name, GLuint screenshot)
|
|
||||||
{
|
|
||||||
ImGuiStyle &style = ImGui::GetStyle();
|
ImGuiStyle &style = ImGui::GetStyle();
|
||||||
ImVec2 pos = ImGui::GetCursorPos();
|
ImVec2 pos = ImGui::GetCursorPos();
|
||||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||||
|
|
||||||
ImGui::PushFont(g_font_mgr.m_menuFont);
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
const char *icon = ICON_FA_CIRCLE_XMARK;
|
const char *close_icon = ICON_FA_CIRCLE_XMARK;
|
||||||
ImVec2 ts_icon = ImGui::CalcTextSize(icon);
|
ImVec2 ts_close_icon = ImGui::CalcTextSize(close_icon);
|
||||||
ts_icon.x += 2*style.FramePadding.x;
|
ts_close_icon.x += 2*style.FramePadding.x;
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
|
||||||
ImGui::PushFont(g_font_mgr.m_menuFontSmall);
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
ImVec2 ts_sub = ImGui::CalcTextSize(name);
|
const char *save_icon = ICON_FA_FLOPPY_DISK;
|
||||||
|
ImVec2 ts_save_icon = ImGui::CalcTextSize(save_icon);
|
||||||
|
ts_save_icon.x += 2*style.FramePadding.x;
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
const char *binding_icon = ICON_FA_KEYBOARD;
|
||||||
|
ImVec2 ts_binding_icon = ImGui::CalcTextSize(binding_icon);
|
||||||
|
ts_binding_icon.x += 2*style.FramePadding.x;
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
ImVec2 ts_sub = ImGui::CalcTextSize(snapshot->name);
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
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_menuFontMedium);
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
|
|
||||||
ImVec2 ts_title = ImGui::CalcTextSize(name);
|
ImVec2 ts_title = ImGui::CalcTextSize(snapshot->name);
|
||||||
ImVec2 thumbnail_size = g_viewport_mgr.scale(ImVec2(160, 120));
|
ImVec2 thumbnail_size = g_viewport_mgr.Scale(ImVec2(XEMU_SNAPSHOT_WIDTH, XEMU_SNAPSHOT_HEIGHT));
|
||||||
ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y);
|
ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y);
|
||||||
ImVec2 text_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y);
|
ImVec2 name_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y);
|
||||||
ImVec2 subtext_pos(text_pos.x, text_pos.y + ts_title.y + style.FramePadding.x);
|
ImVec2 date_pos(name_pos.x, name_pos.y + ts_title.y + style.FramePadding.x);
|
||||||
|
ImVec2 title_pos(name_pos.x, date_pos.y + ts_title.y + style.FramePadding.x);
|
||||||
|
ImVec2 binding_pos(name_pos.x, title_pos.y + ts_title.y + style.FramePadding.x);
|
||||||
|
|
||||||
ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2,
|
bool load = ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2,
|
||||||
ts_title.y + ts_sub.y + style.FramePadding.y * 3)));
|
ts_title.y + ts_sub.y + style.FramePadding.y * 3)));
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
const ImVec2 sz = ImGui::GetItemRectSize();
|
const ImVec2 sz = ImGui::GetItemRectSize();
|
||||||
const ImVec2 p0 = ImGui::GetItemRectMin();
|
const ImVec2 p0 = ImGui::GetItemRectMin();
|
||||||
const ImVec2 p1 = ImGui::GetItemRectMax();
|
const ImVec2 p1 = ImGui::GetItemRectMax();
|
||||||
ts_icon.y = sz.y;
|
ts_close_icon.y = sz.y / 1;
|
||||||
|
ts_save_icon.y = sz.y / 1;
|
||||||
|
ts_binding_icon.y = sz.y / 1;
|
||||||
|
|
||||||
|
if (load) {
|
||||||
|
xemu_snapshots_load(snapshot->name, &err);
|
||||||
|
if (err) {
|
||||||
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
|
error_free(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot thumbnail
|
// Snapshot thumbnail
|
||||||
ImGui::SetItemAllowOverlap();
|
ImGui::SetItemAllowOverlap();
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetCursorPosX(pos.x + thumbnail_pos.x);
|
ImGui::SetCursorPosX(pos.x + thumbnail_pos.x);
|
||||||
ImGui::SetCursorPosY(pos.y + thumbnail_pos.y);
|
ImGui::SetCursorPosY(pos.y + thumbnail_pos.y);
|
||||||
ImGui::Image((ImTextureID)screenshot, thumbnail_size, ImVec2(0,0), ImVec2(1,1));
|
if (screenshot > 0) {
|
||||||
|
ImGui::Image((ImTextureID)(uint64_t)screenshot, thumbnail_size);
|
||||||
|
} else {
|
||||||
|
ImGui::Image((ImTextureID)(uint64_t)g_icon_tex, thumbnail_size);
|
||||||
|
}
|
||||||
|
|
||||||
draw_list->PushClipRect(p0, p1, true);
|
draw_list->PushClipRect(p0, p1, true);
|
||||||
|
|
||||||
// Snapshot title
|
// Snapshot title
|
||||||
ImGui::PushFont(g_font_mgr.m_menuFontMedium);
|
ImGui::PushFont(g_font_mgr.m_menu_font_medium);
|
||||||
draw_list->AddText(ImVec2(p0.x + text_pos.x, p0.y + text_pos.y), IM_COL32(255, 255, 255, 255), 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();
|
ImGui::PopFont();
|
||||||
|
|
||||||
// Snapshot subtitle
|
// Snapshot date
|
||||||
ImGui::PushFont(g_font_mgr.m_menuFontSmall);
|
g_autoptr(GDateTime) date = g_date_time_new_from_unix_local(snapshot->date_sec);
|
||||||
draw_list->AddText(ImVec2(p0.x + subtext_pos.x, p0.y + subtext_pos.y), IM_COL32(255, 255, 255, 200), title_name);
|
char *date_buf = g_date_time_format(date, "%Y-%m-%d %H:%M:%S");
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
draw_list->AddText(ImVec2(p0.x + date_pos.x, p0.y + date_pos.y), IM_COL32(255, 255, 255, 200), date_buf);
|
||||||
|
ImGui::PopFont();
|
||||||
|
g_free(date_buf);
|
||||||
|
|
||||||
|
// Snapshot title
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
draw_list->AddText(ImVec2(p0.x + title_pos.x, p0.y + title_pos.y), IM_COL32(255, 255, 255, 200), title_name);
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
// Snapshot binding
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
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);
|
||||||
|
g_free(binding_text);
|
||||||
|
} else {
|
||||||
|
ImGui::Text("Not Bound");
|
||||||
|
draw_list->AddText(ImVec2(p0.x + binding_pos.x, p0.y + binding_pos.y), IM_COL32(255, 255, 255, 200), "Not Bound");
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
|
|
||||||
draw_list->PopClipRect();
|
draw_list->PopClipRect();
|
||||||
|
@ -724,30 +801,196 @@ public:
|
||||||
// Delete button
|
// Delete button
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::SetCursorPosY(pos.y);
|
ImGui::SetCursorPosY(pos.y);
|
||||||
ImGui::SetCursorPosX(pos.x + sz.x - ts_icon.x);
|
ImGui::SetCursorPosX(pos.x + sz.x - ts_close_icon.x);
|
||||||
ImGui::PushFont(g_font_mgr.m_menuFont);
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
ImGui::Button(icon, ts_icon);
|
if (ImGui::Button(close_icon, ts_close_icon)) {
|
||||||
|
xemu_snapshots_delete(snapshot->name, &err);
|
||||||
|
if (err) {
|
||||||
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
|
error_free(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
ImGui::PopStyleColor();
|
ImGui::PopStyleColor();
|
||||||
ImGui::PopStyleVar(1);
|
ImGui::PopStyleVar(1);
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
ImGui::PopStyleVar(2);
|
|
||||||
|
// Save button
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosY(pos.y);
|
||||||
|
ImGui::SetCursorPosX(pos.x + sz.x - ts_save_icon.x - ts_close_icon.x);
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
|
if (ImGui::Button(save_icon, ts_save_icon)) {
|
||||||
|
xemu_snapshots_save(snapshot->name, &err);
|
||||||
|
if (err) {
|
||||||
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
|
error_free(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
// Bind button
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::SetCursorPosY(pos.y);
|
||||||
|
ImGui::SetCursorPosX(pos.x + sz.x - ts_binding_icon.x - ts_save_icon.x - ts_close_icon.x);
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
|
||||||
|
if (ImGui::Button(binding_icon, ts_binding_icon)) {
|
||||||
|
ImGui::OpenPopup("Bind Snapshot Key");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Draw()
|
if (ImGui::BeginPopupContextItem("Bind Snapshot Key")) {
|
||||||
{
|
for (int i = 0; i < 4; ++i) {
|
||||||
initScreenshot();
|
char *item_name = g_strdup_printf("Bind to F%d", i + 5);
|
||||||
for (int i = 0; i < 15; i++) {
|
|
||||||
char buf[64];
|
if (ImGui::MenuItem(item_name)) {
|
||||||
snprintf(buf, sizeof(buf), "%s", "Apr 9 2022 19:44");
|
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[i], snapshot->name);
|
||||||
|
current_snapshot_binding = i;
|
||||||
|
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(item_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_snapshot_binding >= 0) {
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("Unbind")) {
|
||||||
|
xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], "");
|
||||||
|
current_snapshot_binding = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
ImGui::PopStyleVar(1);
|
||||||
|
ImGui::PopFont();
|
||||||
|
|
||||||
|
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int MainMenuSnapshotsViewUpdateSearchBox(ImGuiInputTextCallbackData *data)
|
||||||
|
{
|
||||||
|
GError *gerr = NULL;
|
||||||
|
MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData;
|
||||||
|
|
||||||
|
if (win->m_search_regex) g_free(win->m_search_regex);
|
||||||
|
if (data->BufTextLen == 0) {
|
||||||
|
win->m_search_regex = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *buf = g_strdup_printf("(.*)%s(.*)", data->Buf);
|
||||||
|
|
||||||
|
win->m_search_regex = g_regex_new(buf, (GRegexCompileFlags)0, (GRegexMatchFlags)0, &gerr);
|
||||||
|
g_free(buf);
|
||||||
|
if (gerr) {
|
||||||
|
win->m_search_regex = NULL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainMenuSnapshotsView::Draw()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
SectionTitle("Snapshots");
|
||||||
|
ImGui::Checkbox("Filter by current title", &g_config.general.snapshots.filter_current_game);
|
||||||
|
ImGui::InputTextWithHint("##search", "Filter by name...", &m_search_buf, ImGuiInputTextFlags_CallbackEdit,
|
||||||
|
&MainMenuSnapshotsViewUpdateSearchBox, this);
|
||||||
|
|
||||||
|
ImGui::InputTextWithHint("##create", "Create new snapshot", &m_create_buf);
|
||||||
|
|
||||||
|
bool snapshot_with_create_name_exists = false;
|
||||||
|
for (int i = 0; i < m_snapshots_len; ++i) {
|
||||||
|
if (g_strcmp0(m_create_buf.c_str(), m_snapshots[i].name) == 0) {
|
||||||
|
snapshot_with_create_name_exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button(snapshot_with_create_name_exists ? "Save" : "Create") && !m_create_buf.empty()) {
|
||||||
|
xemu_snapshots_save(m_create_buf.c_str(), NULL);
|
||||||
|
m_create_buf.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
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_create_buf.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool search_buf_equal = false;
|
||||||
|
for (int i = m_snapshots_len - 1; i >= 0; i--) {
|
||||||
|
if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_present &&
|
||||||
|
(strcmp(m_current_title_name, m_extra_data[i].xbe_title) != 0)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_search_regex) {
|
||||||
|
GMatchInfo *match;
|
||||||
|
bool keep_entry = false;
|
||||||
|
|
||||||
|
g_regex_match(m_search_regex, m_snapshots[i].name, (GRegexMatchFlags)0, &match);
|
||||||
|
keep_entry |= g_match_info_matches(match);
|
||||||
|
g_match_info_free(match);
|
||||||
|
|
||||||
|
if (m_extra_data[i].xbe_title_present) {
|
||||||
|
g_regex_match(m_search_regex, m_extra_data[i].xbe_title, (GRegexMatchFlags)0, &match);
|
||||||
|
keep_entry |= g_match_info_matches(match);
|
||||||
|
g_free(match);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep_entry) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
search_buf_equal |= g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0;
|
||||||
|
|
||||||
ImGui::PushID(i);
|
ImGui::PushID(i);
|
||||||
snapshotBigButton(buf, "Halo: Combat Evolved", screenshot);
|
GLuint thumbnail = 0;
|
||||||
|
if (m_extra_data[i].thumbnail_present) {
|
||||||
|
thumbnail = m_extra_data[i].gl_thumbnail;
|
||||||
|
}
|
||||||
|
SnapshotBigButton(
|
||||||
|
m_snapshots + i,
|
||||||
|
m_extra_data[i].xbe_title_present ? m_extra_data[i].xbe_title : "Unknown",
|
||||||
|
thumbnail
|
||||||
|
);
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Snapshot names are unique, don't give option to create new one if it exists already */
|
||||||
|
if (!search_buf_equal && !m_search_buf.empty()) {
|
||||||
|
char *new_snapshot = g_strdup_printf("Create Snapshot '%s'", m_search_buf.c_str());
|
||||||
|
ImGui::PushFont(g_font_mgr.m_menu_font_small);
|
||||||
|
ImVec2 new_snapshot_size = ImGui::CalcTextSize(new_snapshot);
|
||||||
|
ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x - new_snapshot_size.x)/2);
|
||||||
|
if (ImGui::Button(new_snapshot)) {
|
||||||
|
Error *err = NULL;
|
||||||
|
xemu_snapshots_save(m_search_buf.c_str(), &err);
|
||||||
|
if (err) {
|
||||||
|
xemu_queue_error_message(error_get_pretty(err));
|
||||||
|
error_free(err);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
#endif
|
ImGui::PopFont();
|
||||||
|
g_free(new_snapshot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MainMenuSystemView::MainMenuSystemView() : m_dirty(false)
|
MainMenuSystemView::MainMenuSystemView() : m_dirty(false)
|
||||||
{
|
{
|
||||||
|
@ -929,7 +1172,7 @@ MainMenuScene::MainMenuScene()
|
||||||
m_display_button("Display", ICON_FA_TV),
|
m_display_button("Display", ICON_FA_TV),
|
||||||
m_audio_button("Audio", ICON_FA_VOLUME_HIGH),
|
m_audio_button("Audio", ICON_FA_VOLUME_HIGH),
|
||||||
m_network_button("Network", ICON_FA_NETWORK_WIRED),
|
m_network_button("Network", ICON_FA_NETWORK_WIRED),
|
||||||
// m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT),
|
m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT),
|
||||||
m_system_button("System", ICON_FA_MICROCHIP),
|
m_system_button("System", ICON_FA_MICROCHIP),
|
||||||
m_about_button("About", ICON_FA_CIRCLE_INFO)
|
m_about_button("About", ICON_FA_CIRCLE_INFO)
|
||||||
{
|
{
|
||||||
|
@ -940,7 +1183,7 @@ MainMenuScene::MainMenuScene()
|
||||||
m_tabs.push_back(&m_display_button);
|
m_tabs.push_back(&m_display_button);
|
||||||
m_tabs.push_back(&m_audio_button);
|
m_tabs.push_back(&m_audio_button);
|
||||||
m_tabs.push_back(&m_network_button);
|
m_tabs.push_back(&m_network_button);
|
||||||
// m_tabs.push_back(&m_snapshots_button);
|
m_tabs.push_back(&m_snapshots_button);
|
||||||
m_tabs.push_back(&m_system_button);
|
m_tabs.push_back(&m_system_button);
|
||||||
m_tabs.push_back(&m_about_button);
|
m_tabs.push_back(&m_about_button);
|
||||||
|
|
||||||
|
@ -949,7 +1192,7 @@ MainMenuScene::MainMenuScene()
|
||||||
m_views.push_back(&m_display_view);
|
m_views.push_back(&m_display_view);
|
||||||
m_views.push_back(&m_audio_view);
|
m_views.push_back(&m_audio_view);
|
||||||
m_views.push_back(&m_network_view);
|
m_views.push_back(&m_network_view);
|
||||||
// m_views.push_back(&m_snapshots_view);
|
m_views.push_back(&m_snapshots_view);
|
||||||
m_views.push_back(&m_system_view);
|
m_views.push_back(&m_system_view);
|
||||||
m_views.push_back(&m_about_view);
|
m_views.push_back(&m_about_view);
|
||||||
|
|
||||||
|
@ -977,15 +1220,18 @@ void MainMenuScene::ShowNetwork()
|
||||||
{
|
{
|
||||||
SetNextViewIndexWithFocus(4);
|
SetNextViewIndexWithFocus(4);
|
||||||
}
|
}
|
||||||
// void MainMenuScene::showSnapshots() { SetNextViewIndexWithFocus(5); }
|
void MainMenuScene::ShowSnapshots()
|
||||||
void MainMenuScene::ShowSystem()
|
|
||||||
{
|
{
|
||||||
SetNextViewIndexWithFocus(5);
|
SetNextViewIndexWithFocus(5);
|
||||||
}
|
}
|
||||||
void MainMenuScene::ShowAbout()
|
void MainMenuScene::ShowSystem()
|
||||||
{
|
{
|
||||||
SetNextViewIndexWithFocus(6);
|
SetNextViewIndexWithFocus(6);
|
||||||
}
|
}
|
||||||
|
void MainMenuScene::ShowAbout()
|
||||||
|
{
|
||||||
|
SetNextViewIndexWithFocus(7);
|
||||||
|
}
|
||||||
|
|
||||||
void MainMenuScene::SetNextViewIndexWithFocus(int i)
|
void MainMenuScene::SetNextViewIndexWithFocus(int i)
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "widgets.hh"
|
#include "widgets.hh"
|
||||||
#include "scene.hh"
|
#include "scene.hh"
|
||||||
#include "scene-components.hh"
|
#include "scene-components.hh"
|
||||||
|
#include "../xemu-snapshots.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include "net/pcap.h"
|
#include "net/pcap.h"
|
||||||
|
@ -102,8 +103,24 @@ public:
|
||||||
|
|
||||||
class MainMenuSnapshotsView : public virtual MainMenuTabView
|
class MainMenuSnapshotsView : public virtual MainMenuTabView
|
||||||
{
|
{
|
||||||
|
protected:
|
||||||
|
QEMUSnapshotInfo *m_snapshots;
|
||||||
|
XemuSnapshotData *m_extra_data;
|
||||||
|
int m_snapshots_len;
|
||||||
|
uint32_t m_current_title_id;
|
||||||
|
char *m_current_title_name;
|
||||||
|
std::string m_search_buf;
|
||||||
|
std::string m_create_buf;
|
||||||
|
bool m_load_failed;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Load();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void SnapshotBigButton(const char *name, const char *title_name,
|
GRegex *m_search_regex;
|
||||||
|
MainMenuSnapshotsView();
|
||||||
|
~MainMenuSnapshotsView();
|
||||||
|
void SnapshotBigButton(QEMUSnapshotInfo *snapshot, const char *title_name,
|
||||||
GLuint screenshot);
|
GLuint screenshot);
|
||||||
void Draw() override;
|
void Draw() override;
|
||||||
};
|
};
|
||||||
|
@ -153,7 +170,7 @@ protected:
|
||||||
m_display_button,
|
m_display_button,
|
||||||
m_audio_button,
|
m_audio_button,
|
||||||
m_network_button,
|
m_network_button,
|
||||||
// m_snapshots_button,
|
m_snapshots_button,
|
||||||
m_system_button,
|
m_system_button,
|
||||||
m_about_button;
|
m_about_button;
|
||||||
std::vector<MainMenuTabView*> m_views;
|
std::vector<MainMenuTabView*> m_views;
|
||||||
|
@ -162,7 +179,7 @@ protected:
|
||||||
MainMenuDisplayView m_display_view;
|
MainMenuDisplayView m_display_view;
|
||||||
MainMenuAudioView m_audio_view;
|
MainMenuAudioView m_audio_view;
|
||||||
MainMenuNetworkView m_network_view;
|
MainMenuNetworkView m_network_view;
|
||||||
// MainMenuSnapshotsView m_snapshots_view;
|
MainMenuSnapshotsView m_snapshots_view;
|
||||||
MainMenuSystemView m_system_view;
|
MainMenuSystemView m_system_view;
|
||||||
MainMenuAboutView m_about_view;
|
MainMenuAboutView m_about_view;
|
||||||
|
|
||||||
|
@ -174,7 +191,7 @@ public:
|
||||||
void ShowDisplay();
|
void ShowDisplay();
|
||||||
void ShowAudio();
|
void ShowAudio();
|
||||||
void ShowNetwork();
|
void ShowNetwork();
|
||||||
// void ShowSnapshots();
|
void ShowSnapshots();
|
||||||
void ShowSystem();
|
void ShowSystem();
|
||||||
void ShowAbout();
|
void ShowAbout();
|
||||||
void SetNextViewIndexWithFocus(int i);
|
void SetNextViewIndexWithFocus(int i);
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "actions.hh"
|
||||||
#include "common.hh"
|
#include "common.hh"
|
||||||
#include "xemu-hud.h"
|
#include "xemu-hud.h"
|
||||||
#include "misc.hh"
|
#include "misc.hh"
|
||||||
|
@ -277,6 +278,14 @@ void xemu_hud_render(void)
|
||||||
!ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) {
|
!ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) {
|
||||||
g_scene_mgr.PushScene(g_popup_menu);
|
g_scene_mgr.PushScene(g_popup_menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool mod_key_down = ImGui::IsKeyDown(ImGuiKey_ModShift);
|
||||||
|
for (int f_key = 0; f_key < 4; ++f_key) {
|
||||||
|
if (ImGui::IsKeyPressed(f_key + ImGuiKey_F5)) {
|
||||||
|
ActionActivateBoundSnapshot(f_key, mod_key_down);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
first_boot_window.Draw();
|
first_boot_window.Draw();
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
#include "ui/xemu-notifications.h"
|
||||||
#include "common.hh"
|
#include "common.hh"
|
||||||
#include "main-menu.hh"
|
#include "main-menu.hh"
|
||||||
#include "menubar.hh"
|
#include "menubar.hh"
|
||||||
|
@ -88,6 +89,48 @@ void ShowMainMenu()
|
||||||
if (ImGui::MenuItem(running ? "Pause" : "Resume", SHORTCUT_MENU_TEXT(P))) ActionTogglePause();
|
if (ImGui::MenuItem(running ? "Pause" : "Resume", SHORTCUT_MENU_TEXT(P))) ActionTogglePause();
|
||||||
if (ImGui::MenuItem("Screenshot", "F12")) ActionScreenshot();
|
if (ImGui::MenuItem("Screenshot", "F12")) ActionScreenshot();
|
||||||
|
|
||||||
|
if (ImGui::BeginMenu("Snapshot")) {
|
||||||
|
if (ImGui::MenuItem("Create Snapshot")) {
|
||||||
|
xemu_snapshots_save(NULL, NULL);
|
||||||
|
xemu_queue_notification("Created new snapshot");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
char *hotkey = g_strdup_printf("Shift+F%d", i + 5);
|
||||||
|
|
||||||
|
char *load_name;
|
||||||
|
char *save_name;
|
||||||
|
|
||||||
|
assert(g_snapshot_shortcut_index_key_map[i]);
|
||||||
|
bool bound = *(g_snapshot_shortcut_index_key_map[i]) &&
|
||||||
|
(**(g_snapshot_shortcut_index_key_map[i]) != 0);
|
||||||
|
|
||||||
|
if (bound) {
|
||||||
|
load_name = g_strdup_printf("Load '%s'", *(g_snapshot_shortcut_index_key_map[i]));
|
||||||
|
save_name = g_strdup_printf("Save '%s'", *(g_snapshot_shortcut_index_key_map[i]));
|
||||||
|
} else {
|
||||||
|
load_name = g_strdup_printf("Load F%d (Unbound)", i + 5);
|
||||||
|
save_name = g_strdup_printf("Save F%d (Unbound)", i + 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Separator();
|
||||||
|
|
||||||
|
if (ImGui::MenuItem(load_name, hotkey + sizeof("Shift+") - 1, false, bound)) {
|
||||||
|
ActionActivateBoundSnapshot(i, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::MenuItem(save_name, hotkey, false, bound)) {
|
||||||
|
ActionActivateBoundSnapshot(i, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free(hotkey);
|
||||||
|
g_free(load_name);
|
||||||
|
g_free(save_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndMenu();
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::MenuItem("Eject Disc", SHORTCUT_MENU_TEXT(E))) ActionEjectDisc();
|
if (ImGui::MenuItem("Eject Disc", SHORTCUT_MENU_TEXT(E))) ActionEjectDisc();
|
||||||
|
@ -101,6 +144,7 @@ void ShowMainMenu()
|
||||||
if (ImGui::MenuItem(" Display")) g_main_menu.ShowDisplay();
|
if (ImGui::MenuItem(" Display")) g_main_menu.ShowDisplay();
|
||||||
if (ImGui::MenuItem(" Audio")) g_main_menu.ShowAudio();
|
if (ImGui::MenuItem(" Audio")) g_main_menu.ShowAudio();
|
||||||
if (ImGui::MenuItem(" Network")) g_main_menu.ShowNetwork();
|
if (ImGui::MenuItem(" Network")) g_main_menu.ShowNetwork();
|
||||||
|
if (ImGui::MenuItem(" Snapshots")) g_main_menu.ShowSnapshots();
|
||||||
if (ImGui::MenuItem(" System")) g_main_menu.ShowSystem();
|
if (ImGui::MenuItem(" System")) g_main_menu.ShowSystem();
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
//
|
//
|
||||||
|
#include "ui/xemu-notifications.h"
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "misc.hh"
|
#include "misc.hh"
|
||||||
|
@ -27,6 +28,8 @@
|
||||||
#include "input-manager.hh"
|
#include "input-manager.hh"
|
||||||
#include "xemu-hud.h"
|
#include "xemu-hud.h"
|
||||||
#include "IconsFontAwesome6.h"
|
#include "IconsFontAwesome6.h"
|
||||||
|
#include "../xemu-snapshots.h"
|
||||||
|
#include "main-menu.hh"
|
||||||
|
|
||||||
PopupMenuItemDelegate::~PopupMenuItemDelegate() {}
|
PopupMenuItemDelegate::~PopupMenuItemDelegate() {}
|
||||||
void PopupMenuItemDelegate::PushMenu(PopupMenu &menu) {}
|
void PopupMenuItemDelegate::PushMenu(PopupMenu &menu) {}
|
||||||
|
@ -269,7 +272,7 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Scene g_main_menu;
|
extern MainMenuScene g_main_menu;
|
||||||
|
|
||||||
class SettingsPopupMenu : public virtual PopupMenu {
|
class SettingsPopupMenu : public virtual PopupMenu {
|
||||||
protected:
|
protected:
|
||||||
|
@ -292,6 +295,11 @@ public:
|
||||||
nav.PushFocus();
|
nav.PushFocus();
|
||||||
nav.PushMenu(display_mode);
|
nav.PushMenu(display_mode);
|
||||||
}
|
}
|
||||||
|
if (PopupMenuButton("Snapshots...", ICON_FA_CLOCK_ROTATE_LEFT)) {
|
||||||
|
nav.ClearMenuStack();
|
||||||
|
g_scene_mgr.PushScene(g_main_menu);
|
||||||
|
g_main_menu.ShowSnapshots();
|
||||||
|
}
|
||||||
if (PopupMenuButton("All settings...", ICON_FA_SLIDERS)) {
|
if (PopupMenuButton("All settings...", ICON_FA_SLIDERS)) {
|
||||||
nav.ClearMenuStack();
|
nav.ClearMenuStack();
|
||||||
g_scene_mgr.PushScene(g_main_menu);
|
g_scene_mgr.PushScene(g_main_menu);
|
||||||
|
@ -338,6 +346,11 @@ public:
|
||||||
ActionScreenshot();
|
ActionScreenshot();
|
||||||
pop = true;
|
pop = true;
|
||||||
}
|
}
|
||||||
|
if (PopupMenuButton("Save Snapshot", ICON_FA_DOWNLOAD)) {
|
||||||
|
xemu_snapshots_save(NULL, NULL);
|
||||||
|
xemu_queue_notification("Created new snapshot");
|
||||||
|
pop = true;
|
||||||
|
}
|
||||||
if (PopupMenuButton("Eject Disc", ICON_FA_EJECT)) {
|
if (PopupMenuButton("Eject Disc", ICON_FA_EJECT)) {
|
||||||
ActionEjectDisc();
|
ActionEjectDisc();
|
||||||
pop = true;
|
pop = true;
|
||||||
|
|
Loading…
Reference in New Issue