Merge remote-tracking branch 'origin/master' into sync/qemu-7.2.0

This commit is contained in:
Matt Borgerson 2023-06-18 01:19:02 -07:00
commit 976319b391
46 changed files with 1729 additions and 266 deletions

View File

@ -359,6 +359,13 @@ jobs:
dist/xemu-macos-universal-debug/xemu-macos-universal-debug.zip
dist/xemu-ubuntu-release/xemu/xemu-v${{ env.XEMU_VERSION }}-x86_64.AppImage
dist/xemu-ubuntu-debug/xemu/xemu-v${{ env.XEMU_VERSION }}-dbg-x86_64.AppImage
- name: Trigger website update
uses: benc-uk/workflow-dispatch@v1.2.2
with:
workflow: build.yml
repo: xemu-project/xemu-website
token: ${{ secrets.XEMU_ROBOT_TOKEN }}
ref: master
# Sync archive version of source (including submodule code) to the
# ppa-snapshot branch to work around limitations of the Launchpad platform,

View File

@ -1,17 +0,0 @@
name: Trigger website update
on:
release:
types: [published]
jobs:
trigger-website-update:
name: Trigger website update
runs-on: ubuntu-latest
steps:
- uses: benc-uk/workflow-dispatch@v1.2.2
with:
workflow: build.yml
repo: xemu-project/xemu-website
token: ${{ secrets.XEMU_WEBSITE_TOKEN }}
ref: master

View File

@ -2782,6 +2782,12 @@ static void bdrv_default_perms_for_storage(BlockDriverState *bs, BdrvChild *c,
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;
*nshared = shared;
}

View File

@ -36,6 +36,7 @@
#include "qapi/qmp/qstring.h"
#include <windows.h>
#include <winioctl.h>
#include <assert.h>
#define FTYPE_FILE 0
#define FTYPE_CD 1
@ -345,6 +346,9 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
bool use_aio;
OnOffAuto locking;
int ret;
#ifdef XBOX
int sharing_flags;
#endif
s->type = FTYPE_FILE;
@ -400,9 +404,21 @@ static int raw_open(BlockDriverState *bs, QDict *options, int flags,
if (!filename) {
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,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
OPEN_EXISTING, overlapped, NULL);
#endif
g_free(wfilename);
if (s->hfile == INVALID_HANDLE_VALUE) {
int err = GetLastError();

View File

@ -77,6 +77,9 @@ package_macos() {
cp Info.plist dist/xemu.app/Contents/
plutil -replace CFBundleShortVersionString -string $(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-) dist/xemu.app/Contents/Info.plist
plutil -replace CFBundleVersion -string $(cat ${project_source_dir}/XEMU_VERSION | cut -f1 -d-) dist/xemu.app/Contents/Info.plist
codesign --force --deep --preserve-metadata=entitlements,requirements,flags,runtime --sign - "${exe_path}"
python3 ./scripts/gen-license.py --version-file=macos-libs/$target_arch/INSTALLED > dist/LICENSE.txt
}
@ -170,6 +173,32 @@ else
opts="--enable-lto"
fi
most_recent_macosx_sdk_ver () {
local min_ver="${1}"
local macos_sdk_base=/Library/Developer/CommandLineTools/SDKs
local sdks=("${macos_sdk_base}"/MacOSX[0-9]*.[0-9]*.sdk)
for i in "${!sdks[@]}"; do
local newval="${sdks[i]##${macos_sdk_base}/MacOSX}"
sdks[$i]="${newval%%.sdk}"
done
IFS=$'\n' sdks=($(sort -nr <<<"${sdks[*]}"))
unset IFS
local newest_sdk_ver="${sdks[0]}"
local sdk_path="${macos_sdk_base}/MacOSX${newest_sdk_ver}.sdk"
if ! test -d "${sdk_path}"; then
echo ""
return
fi
if ! LC_ALL=C awk 'BEGIN {exit ('${newest_sdk_ver}' < '${min_ver}')}'; then
echo ""
return
fi
echo "${sdk_path}"
}
case "$platform" in # Adjust compilation options based on platform
Linux)
@ -180,47 +209,21 @@ case "$platform" in # Adjust compilation options based on platform
;;
Darwin)
echo "Compiling for MacOS for $target_arch..."
sdk_base=/Library/Developer/CommandLineTools/SDKs/
sdk_macos_10_14="${sdk_base}/MacOSX10.14.sdk"
sdk_macos_10_15="${sdk_base}/MacOSX10.15.sdk"
sdk_macos_11_1="${sdk_base}/MacOSX11.1.sdk"
sdk_macos_11_3="${sdk_base}/MacOSX11.3.sdk"
sdk_macos_12_0="${sdk_base}/MacOSX12.0.sdk"
sdk_macos_12_1="${sdk_base}/MacOSX12.1.sdk"
if [ "$target_arch" == "arm64" ]; then
macos_min_ver=11.3
if test -d "$sdk_macos_12_1"; then
sdk="$sdk_macos_12_1"
elif test -d "$sdk_macos_12_0"; then
sdk="$sdk_macos_12_0"
elif test -d "$sdk_macos_11_3"; then
sdk="$sdk_macos_11_3"
else
echo "SDK not found. Install Xcode Command Line Tools"
exit 1
fi
elif [ "$target_arch" == "x86_64" ]; then
macos_min_ver=10.13
if test -d "$sdk_macos_12_1"; then
sdk="$sdk_macos_12_1"
elif test -d "$sdk_macos_12_0"; then
sdk="$sdk_macos_12_0"
elif test -d "$sdk_macos_11_3"; then
sdk="$sdk_macos_11_3"
elif test -d "$sdk_macos_11_1"; then
sdk="$sdk_macos_11_1"
elif test -d "$sdk_macos_10_15"; then
sdk="$sdk_macos_10_15"
elif test -d "$sdk_macos_10_14"; then
sdk="$sdk_macos_10_14"
else
echo "SDK not found. Install Xcode Command Line Tools"
exit 1
fi
else
echo "Unsupported arch $target_arch"
exit 1
fi
sdk="$(most_recent_macosx_sdk_ver ${macos_min_ver})"
if [[ -z "${sdk}" ]]; then
echo "SDK >= ${macos_min_ver} not found. Install Xcode Command Line Tools"
exit 1
fi
python3 ./scripts/download-macos-libs.py ${target_arch}
lib_prefix=${PWD}/macos-libs/${target_arch}/opt/local
export CFLAGS="-arch ${target_arch} \

View File

@ -11,6 +11,13 @@ general:
# throttle_io: bool
last_viewed_menu_index: integer
user_token: string
snapshots:
shortcuts:
f5: string
f6: string
f7: string
f8: string
filter_current_game: bool
input:
bindings:
@ -108,6 +115,7 @@ display:
default: 1
window:
fullscreen_on_startup: bool
fullscreen_exclusive: bool
startup_size:
type: enum
values: [last_used, 640x480, 1280x720, 1280x800, 1280x960, 1920x1080, 2560x1440, 2560x1600, 2560x1920, 3840x2160]
@ -130,8 +138,12 @@ display:
default: true
fit:
type: enum
values: [center, scale, scale_16_9, scale_4_3, stretch]
values: [center, scale, stretch]
default: scale
aspect_ratio:
type: enum
values: [native, auto, 4x3, 16x9]
default: auto
scale:
type: integer
default: 1

View File

@ -30,6 +30,7 @@
#include "hw/xbox/xbox_pci.h"
#include "hw/xbox/acpi_xbox.h"
#include "migration/vmstate.h"
#include "ui/xemu-widescreen.h"
// #define DEBUG
#ifdef DEBUG
@ -44,6 +45,8 @@
#define XBOX_PM_GPIO_BASE 0xC0
#define XBOX_PM_GPIO_LEN 26
#define XBOX_PM_GPIO_ASPECT_RATIO 0x16
static int field_pin;
static uint64_t xbox_pm_gpio_read(void *opaque, hwaddr addr, unsigned width)
@ -66,6 +69,12 @@ static void xbox_pm_gpio_write(void *opaque, hwaddr addr, uint64_t val,
unsigned width)
{
XBOX_DPRINTF("pm gpio write [0x%llx] = 0x%llx\n", addr, val);
if (addr == XBOX_PM_GPIO_ASPECT_RATIO) {
xemu_set_widescreen(val == 5);
}
// FIXME: Add GPIO to VM state
}
static const MemoryRegionOps xbox_pm_gpio_ops = {

View File

@ -27,5 +27,6 @@ int nv2a_get_framebuffer_surface(void);
void nv2a_set_surface_scale_factor(unsigned int scale);
unsigned int nv2a_get_surface_scale_factor(void);
const uint8_t *nv2a_get_dac_palette(void);
int nv2a_get_screen_off(void);
#endif

View File

@ -335,6 +335,8 @@
# define NV_PGRAPH_CHEOPS_OFFSET_PROG_LD_PTR 0x000000FF
# define NV_PGRAPH_CHEOPS_OFFSET_CONST_LD_PTR 0x0000FF00
#define NV_PGRAPH_DMA_STATE 0x00001034
#define NV_PGRAPH_ANTIALIASING 0x00001800
# define NV_PGRAPH_ANTIALIASING_ENABLE (1 << 0)
#define NV_PGRAPH_BLEND 0x00001804
# define NV_PGRAPH_BLEND_EQN 0x00000007
# define NV_PGRAPH_BLEND_EN (1 << 3)
@ -1220,6 +1222,8 @@
# define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN 0x000000F0
# define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN_CULL 0
# define NV097_SET_ZMIN_MAX_CONTROL_ZCLAMP_EN_CLAMP 1
# define NV097_SET_ANTI_ALIASING_CONTROL 0x00001D7C
# define NV097_SET_ANTI_ALIASING_CONTROL_ENABLE (1 << 0)
# define NV097_SET_ZSTENCIL_CLEAR_VALUE 0x00001D8C
# define NV097_SET_COLOR_CLEAR_VALUE 0x00001D90
# define NV097_CLEAR_SURFACE 0x00001D94

View File

@ -27,10 +27,6 @@ uint64_t pfb_read(void *opaque, hwaddr addr, unsigned int size)
uint64_t r = 0;
switch (addr) {
case NV_PFB_CFG0:
/* 3-4 memory partitions. The debug bios checks this. */
r = 3;
break;
case NV_PFB_CSTATUS:
r = memory_region_size(d->vram);
break;

View File

@ -2841,12 +2841,25 @@ DEF_METHOD(NV097, SET_BEGIN_END)
bool depth_test = control_0 & NV_PGRAPH_CONTROL_0_ZENABLE;
bool stencil_test =
pg->regs[NV_PGRAPH_CONTROL_1] & NV_PGRAPH_CONTROL_1_STENCIL_TEST_ENABLE;
bool is_nop_draw = !(color_write || depth_test || stencil_test);
if (parameter == NV097_SET_BEGIN_END_OP_END) {
if (pg->primitive_mode == PRIM_TYPE_INVALID) {
NV2A_DPRINTF("End without Begin!\n");
}
nv2a_profile_inc_counter(NV2A_PROF_BEGIN_ENDS);
if (is_nop_draw) {
// FIXME: Check PGRAPH register 0x880.
// HW uses bit 11 in 0x880 to enable or disable a color/zeta limit
// check that will raise an exception in the case that a draw should
// modify the color and/or zeta buffer but the target(s) are masked
// off. This check only seems to trigger during the fragment
// processing, it is legal to attempt a draw that is entirely
// clipped regardless of 0x880. See xemu#635 for context.
return;
}
pgraph_flush_draw(d);
/* End of visibility testing */
@ -2878,14 +2891,7 @@ DEF_METHOD(NV097, SET_BEGIN_END)
pgraph_update_surface(d, true, true, depth_test || stencil_test);
pgraph_reset_inline_buffers(pg);
if (!(color_write || depth_test || stencil_test)) {
// FIXME: Check PGRAPH register 0x880.
// HW uses bit 11 in 0x880 to enable or disable a color/zeta limit
// check that will raise an exception in the case that a draw should
// modify the color and/or zeta buffer but the target(s) are masked
// off. This check only seems to trigger during the fragment
// processing, it is legal to attempt a draw that is entirely
// clipped regardless of 0x880. See xemu#635 for context.
if (is_nop_draw) {
return;
}
@ -3042,14 +3048,16 @@ DEF_METHOD(NV097, SET_BEGIN_END)
glEnable(GL_PROGRAM_POINT_SIZE);
bool anti_aliasing = GET_MASK(pg->regs[NV_PGRAPH_ANTIALIASING], NV_PGRAPH_ANTIALIASING_ENABLE);
/* Edge Antialiasing */
if (pg->regs[NV_PGRAPH_SETUPRASTER] &
if (!anti_aliasing && pg->regs[NV_PGRAPH_SETUPRASTER] &
NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) {
glEnable(GL_LINE_SMOOTH);
} else {
glDisable(GL_LINE_SMOOTH);
}
if (pg->regs[NV_PGRAPH_SETUPRASTER] &
if (!anti_aliasing && pg->regs[NV_PGRAPH_SETUPRASTER] &
NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) {
glEnable(GL_POLYGON_SMOOTH);
} else {
@ -3451,6 +3459,13 @@ DEF_METHOD(NV097, SET_ZMIN_MAX_CONTROL)
}
}
DEF_METHOD(NV097, SET_ANTI_ALIASING_CONTROL)
{
SET_MASK(pg->regs[NV_PGRAPH_ANTIALIASING], NV_PGRAPH_ANTIALIASING_ENABLE,
GET_MASK(parameter, NV097_SET_ANTI_ALIASING_CONTROL_ENABLE));
// FIXME: Handle the remaining bits (observed values 0xFFFF0000, 0xFFFF0001)
}
DEF_METHOD(NV097, SET_ZSTENCIL_CLEAR_VALUE)
{
pg->regs[NV_PGRAPH_ZSTENCILCLEARVALUE] = parameter;
@ -5335,6 +5350,11 @@ const uint8_t *nv2a_get_dac_palette(void)
return g_nv2a->puserdac.palette;
}
int nv2a_get_screen_off(void)
{
return g_nv2a->vga.sr[VGA_SEQ_CLOCK_MODE] & VGA_SR01_SCREEN_OFF;
}
int nv2a_get_framebuffer_surface(void)
{
NV2AState *d = g_nv2a;

View File

@ -169,6 +169,7 @@ DEF_METHOD_RANGE(NV097, SET_VERTEX_DATA4S_M, 32)
DEF_METHOD(NV097, SET_SEMAPHORE_OFFSET)
DEF_METHOD(NV097, BACK_END_WRITE_SEMAPHORE_RELEASE)
DEF_METHOD(NV097, SET_ZMIN_MAX_CONTROL)
DEF_METHOD(NV097, SET_ANTI_ALIASING_CONTROL)
DEF_METHOD(NV097, SET_ZSTENCIL_CLEAR_VALUE)
DEF_METHOD(NV097, SET_COLOR_CLEAR_VALUE)
DEF_METHOD(NV097, CLEAR_SURFACE)

View File

@ -636,7 +636,16 @@ static const char* vsh_header =
"#define MUL(dest, mask, src0, src1) dest.mask = _MUL(_in(src0), _in(src1)).mask\n"
"vec4 _MUL(vec4 src0, vec4 src1)\n"
"{\n"
" return src0 * src1;\n"
// Unfortunately mix() falls victim to the same handling of exceptional
// (inf/NaN) handling as a multiply, so per-component comparisons are used
// to guarantee HW behavior (anything * 0 must == 0).
" vec4 zero_components = sign(src0) * sign(src1);\n"
" vec4 ret = src0 * src1;\n"
" if (zero_components.x == 0.0) { ret.x = 0.0; }\n"
" if (zero_components.y == 0.0) { ret.y = 0.0; }\n"
" if (zero_components.z == 0.0) { ret.z = 0.0; }\n"
" if (zero_components.w == 0.0) { ret.w = 0.0; }\n"
" return ret;\n"
"}\n"
"\n"
"#define ADD(dest, mask, src0, src1) dest.mask = _ADD(_in(src0), _in(src1)).mask\n"
@ -648,7 +657,7 @@ static const char* vsh_header =
"#define MAD(dest, mask, src0, src1, src2) dest.mask = _MAD(_in(src0), _in(src1), _in(src2)).mask\n"
"vec4 _MAD(vec4 src0, vec4 src1, vec4 src2)\n"
"{\n"
" return src0 * src1 + src2;\n"
" return _MUL(src0, src1) + src2;\n"
"}\n"
"\n"
"#define DP3(dest, mask, src0, src1) dest.mask = _DP3(_in(src0), _in(src1)).mask\n"

View File

@ -78,6 +78,8 @@
#define SMC_REG_BOARDTEMP 0x0a
#define SMC_REG_TRAYEJECT 0x0c
#define SMC_REG_INTACK 0x0d
#define SMC_REG_ERROR_WRITE 0x0e
#define SMC_REG_ERROR_READ 0x0f
#define SMC_REG_INTSTATUS 0x11
#define SMC_REG_INTSTATUS_POWER 0x01
#define SMC_REG_INTSTATUS_TRAYCLOSED 0x02
@ -102,6 +104,7 @@ typedef struct SMBusSMCDevice {
uint8_t avpack_reg;
uint8_t intstatus_reg;
uint8_t scratch_reg;
uint8_t error_reg;
} SMBusSMCDevice;
static void smc_quick_cmd(SMBusDevice *dev, uint8_t read)
@ -137,6 +140,10 @@ static int smc_write_data(SMBusDevice *dev, uint8_t *buf, uint8_t len)
}
break;
case SMC_REG_ERROR_WRITE:
smc->error_reg = buf[0];
break;
case SMC_REG_SCRATCH:
smc->scratch_reg = buf[0];
break;
@ -177,6 +184,9 @@ static uint8_t smc_receive_byte(SMBusDevice *dev)
case SMC_REG_AVPACK:
return smc->avpack_reg;
case SMC_REG_ERROR_READ:
return smc->error_reg;
case SMC_REG_INTSTATUS: {
uint8_t r = smc->intstatus_reg;
smc->intstatus_reg = 0; // FIXME: Confirm clear on read
@ -252,6 +262,7 @@ static void smbus_smc_realize(DeviceState *dev, Error **errp)
smc->intstatus_reg = 0;
smc->scratch_reg = 0;
smc->cmd = 0;
smc->error_reg = 0;
if (object_property_get_bool(qdev_get_machine(), "short-animation", NULL)) {
smc->scratch_reg = SMC_REG_SCRATCH_SHORT_ANIMATION;

View File

@ -154,6 +154,10 @@ typedef enum {
read-write fails */
#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)

View File

@ -67,6 +67,8 @@
#include "qemu/yank.h"
#include "yank_functions.h"
#include "ui/xemu-snapshots.h"
const unsigned int postcopy_ram_discard_version;
/* Subcommands for QEMU_VM_COMMAND */
@ -1537,6 +1539,9 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp)
ms->to_dst_file = f;
qemu_mutex_unlock_iothread();
#ifdef XBOX
xemu_snapshots_save_extra_data(f);
#endif
qemu_savevm_state_header(f);
qemu_savevm_state_setup(f);
qemu_mutex_lock_iothread();
@ -2698,6 +2703,12 @@ int qemu_loadvm_state(QEMUFile *f)
return -EINVAL;
}
#ifdef XBOX
if (!xemu_snapshots_offset_extra_data(f)) {
return -EINVAL;
}
#endif
ret = qemu_loadvm_state_header(f);
if (ret) {
return ret;
@ -3095,6 +3106,10 @@ bool delete_snapshot(const char *name, bool has_devices,
return false;
}
#ifdef XBOX
xemu_snapshots_mark_dirty();
#endif
return true;
}

View File

@ -71,6 +71,15 @@ RUN V=1 MXE_VERBOSE=1 make -C /opt/mxe \
MXE_PLUGIN_DIRS=plugins/gcc10 \
sdl2
COPY libslirp.mk /opt/mxe/src/libslirp.mk
COPY libslirp-1.patch /opt/mxe/src/libslirp-1.patch
RUN V=1 MXE_VERBOSE=1 make -C /opt/mxe \
MXE_TARGETS=x86_64-w64-mingw32.static \
MXE_PLUGIN_DIRS=plugins/gcc10 \
libslirp
RUN find /opt/mxe/usr -executable -type f -exec chmod a+x {} \;
ENV CROSSPREFIX=x86_64-w64-mingw32.static-
ENV CROSSAR=${CROSSPREFIX}gcc-ar
ENV PATH="/opt/mxe/.ccache/bin:/opt/mxe/usr/x86_64-pc-linux-gnu/bin:/opt/mxe/usr/bin:${PATH}"

View File

@ -0,0 +1,34 @@
From d1417aed0b335d03c67c998b17b72933764ccbe3 Mon Sep 17 00:00:00 2001
From: Matt Borgerson <contact@mborgerson.com>
Date: Mon, 23 Jan 2023 03:06:46 -0700
Subject: [PATCH] Rename pingtest's slirp_inet_aton
---
test/pingtest.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/test/pingtest.c b/test/pingtest.c
index 3bb0488..04b63ce 100644
--- a/test/pingtest.c
+++ b/test/pingtest.c
@@ -24,7 +24,7 @@
#ifdef _WIN32
//#include <sys/select.h>
#include <winsock2.h>
-int slirp_inet_aton(const char *cp, struct in_addr *ia)
+int pingtest_inet_aton(const char *cp, struct in_addr *ia)
{
uint32_t addr = inet_addr(cp);
if (addr == 0xffffffff) {
@@ -33,7 +33,7 @@ int slirp_inet_aton(const char *cp, struct in_addr *ia)
ia->s_addr = addr;
return 1;
}
-#define inet_aton slirp_inet_aton
+#define inet_aton pingtest_inet_aton
#else
#include <poll.h>
#endif
--
2.34.1

View File

@ -0,0 +1,19 @@
# This file is part of MXE. See LICENSE.md for licensing information.
PKG := libslirp
$(PKG)_WEBSITE := https://gitlab.freedesktop.org/slirp/libslirp
$(PKG)_IGNORE :=
$(PKG)_VERSION := 4.7.0
$(PKG)_SUBDIR := libslirp-v$($(PKG)_VERSION)
$(PKG)_FILE := libslirp-v$($(PKG)_VERSION).tar.gz
$(PKG)_CHECKSUM := 9398f0ec5a581d4e1cd6856b88ae83927e458d643788c3391a39e61b75db3d3b
$(PKG)_URL := https://gitlab.freedesktop.org/slirp/$(PKG)/-/archive/v$($(PKG)_VERSION)/$($(PKG)_FILE)
$(PKG)_DEPS := cc glib meson-wrapper
define $(PKG)_BUILD
'$(MXE_MESON_WRAPPER)' $(MXE_MESON_OPTS) \
$(PKG_MESON_OPTS) \
--buildtype=plain \
'$(BUILD_DIR)' '$(SOURCE_DIR)'
'$(MXE_NINJA)' -C '$(BUILD_DIR)' -j '$(JOBS)' install
endef

View File

@ -6,8 +6,9 @@ $(PKG)_DESCR := SDL2
$(PKG)_IGNORE :=
$(PKG)_VERSION := 2.26.2
$(PKG)_SUBDIR := SDL2-$($(PKG)_VERSION)
$(PKG)_URL := https://github.com/libsdl-org/SDL/releases/download/release-$($(PKG)_VERSION)/SDL2-$($(PKG)_VERSION).tar.gz
$(PKG)_FILE := SDL2-$($(PKG)_VERSION).tar.gz
$(PKG)_CHECKSUM := 95d39bc3de037fbdfa722623737340648de4f180a601b0afad27645d150b99e0
$(PKG)_URL := https://github.com/libsdl-org/SDL/releases/download/release-$($(PKG)_VERSION)/$($(PKG)_FILE)
$(PKG)_GH_CONF := libsdl-org/SDL/releases/tag,release-,,
$(PKG)_DEPS := cc libiconv libsamplerate

View File

@ -28,6 +28,9 @@ xemu_ss.add(files(
'xemu.c',
'xemu-data.c',
'xemu-snapshots.c',
'xemu-thumbnail.cc',
'xemu-widescreen.c',
))
subdir('xui')

View File

@ -5,6 +5,7 @@ imgui_files = files(
'imgui/imgui_widgets.cpp',
'imgui/backends/imgui_impl_sdl.cpp',
'imgui/backends/imgui_impl_opengl3.cpp',
'imgui/misc/cpp/imgui_stdlib.cpp',
#'imgui/imgui_demo.cpp',
)

View File

@ -19,12 +19,48 @@
#include "xemu-os-utils.h"
#include <windows.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <wchar.h>
static const char *get_windows_build_info(void)
{
WCHAR current_build[1024], product_name[1024];
WCHAR build_size = 1024, product_size = 1024;
if (RegGetValueW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
L"ProductName", RRF_RT_REG_SZ, (LPVOID)NULL, &product_name,
(LPDWORD)&product_size) != ERROR_SUCCESS) {
return "Windows";
}
if ((RegGetValueW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
L"DisplayVersion", RRF_RT_REG_SZ, (LPVOID)NULL,
&current_build, (LPDWORD)&build_size) == ERROR_SUCCESS) ||
(RegGetValueW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
L"CSDVersion", RRF_RT_REG_SZ, (LPVOID)NULL,
&current_build, (LPDWORD)&build_size) == ERROR_SUCCESS)) {
return g_strdup_printf("%ls %ls", product_name, current_build);
}
return g_strdup_printf("%ls", product_name);
}
const char *xemu_get_os_info(void)
{
return "Windows";
static const char *buffer = NULL;
if (buffer == NULL) {
buffer = get_windows_build_info();
}
return buffer;
}
void xemu_open_web_browser(const char *url)
{
ShellExecute(0, "open", url, 0, 0, SW_SHOW);

View File

@ -26,6 +26,7 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef __cplusplus
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)
{
assert(new_str);
free((char*)*str);
*str = strdup(new_str);
}

341
ui/xemu-snapshots.c Normal file
View File

@ -0,0 +1,341 @@
/*
* 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 "qapi/qapi-commands-block.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 void xemu_snapshots_load_data(BlockDriverState *bs_ro,
QEMUSnapshotInfo *info,
XemuSnapshotData *data, Error **err)
{
data->disc_path = NULL;
data->xbe_title_name = NULL;
data->gl_thumbnail = 0;
int res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err);
if (res < 0) {
return;
}
uint32_t header[3];
int64_t offset = 0;
res = bdrv_load_vmstate(bs_ro, (uint8_t *)&header, offset, sizeof(header));
if (res != sizeof(header)) {
return;
}
offset += res;
if (be32_to_cpu(header[0]) != XEMU_SNAPSHOT_DATA_MAGIC ||
be32_to_cpu(header[1]) != XEMU_SNAPSHOT_DATA_VERSION) {
return;
}
size_t size = be32_to_cpu(header[2]);
uint8_t *buf = g_malloc(size);
res = bdrv_load_vmstate(bs_ro, buf, offset, size);
if (res != size) {
g_free(buf);
return;
}
assert(size >= 9);
offset = 0;
const size_t disc_path_size = be32_to_cpu(*(uint32_t *)&buf[offset]);
offset += 4;
if (disc_path_size) {
data->disc_path = (char *)g_malloc(disc_path_size + 1);
assert(size >= (offset + disc_path_size));
memcpy(data->disc_path, &buf[offset], disc_path_size);
data->disc_path[disc_path_size] = 0;
offset += disc_path_size;
}
assert(size >= (offset + 4));
const size_t xbe_title_name_size = buf[offset];
offset += 1;
if (xbe_title_name_size) {
data->xbe_title_name = (char *)g_malloc(xbe_title_name_size + 1);
assert(size >= (offset + xbe_title_name_size));
memcpy(data->xbe_title_name, &buf[offset], xbe_title_name_size);
data->xbe_title_name[xbe_title_name_size] = 0;
offset += xbe_title_name_size;
}
const size_t thumbnail_size = be32_to_cpu(*(uint32_t *)&buf[offset]);
offset += 4;
if (thumbnail_size) {
GLuint thumbnail;
glGenTextures(1, &thumbnail);
assert(size >= (offset + thumbnail_size));
if (xemu_snapshots_load_png_to_texture(thumbnail, &buf[offset],
thumbnail_size)) {
data->gl_thumbnail = thumbnail;
} else {
glDeleteTextures(1, &thumbnail);
}
offset += thumbnail_size;
}
g_free(buf);
}
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) {
g_free((*data)[i].xbe_title_name);
if ((*data)[i].gl_thumbnail) {
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;
}
char *xemu_get_currently_loaded_disc_path(void)
{
char *file = NULL;
BlockInfoList *block_list, *info;
block_list = qmp_query_block(NULL);
for (info = block_list; info; info = info->next) {
if (strcmp("ide0-cd1", info->value->device)) {
continue;
}
if (info->value->has_inserted) {
BlockDeviceInfo *inserted = info->value->inserted;
if (inserted->has_node_name) {
file = g_strdup(inserted->file);
}
}
}
qapi_free_BlockInfoList(block_list);
return file;
}
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)
{
char *path = xemu_get_currently_loaded_disc_path();
size_t path_size = path ? strlen(path) : 0;
size_t xbe_title_name_size = 0;
char *xbe_title_name = NULL;
struct xbe *xbe_data = xemu_get_xbe_info();
if (xbe_data && xbe_data->cert) {
glong items_written = 0;
xbe_title_name = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL, &items_written, NULL);
if (xbe_title_name) {
xbe_title_name_size = items_written;
}
}
size_t thumbnail_size = 0;
void *thumbnail_buf = xemu_snapshots_create_framebuffer_thumbnail_png(&thumbnail_size);
qemu_put_be32(f, XEMU_SNAPSHOT_DATA_MAGIC);
qemu_put_be32(f, XEMU_SNAPSHOT_DATA_VERSION);
qemu_put_be32(f, 4 + path_size + 1 + xbe_title_name_size + 4 + thumbnail_size);
qemu_put_be32(f, path_size);
if (path_size) {
qemu_put_buffer(f, (const uint8_t *)path, path_size);
g_free(path);
}
qemu_put_byte(f, xbe_title_name_size);
if (xbe_title_name_size) {
qemu_put_buffer(f, (const uint8_t *)xbe_title_name, xbe_title_name_size);
g_free(xbe_title_name);
}
qemu_put_be32(f, thumbnail_size);
if (thumbnail_size) {
qemu_put_buffer(f, (const uint8_t *)thumbnail_buf, thumbnail_size);
g_free(thumbnail_buf);
}
xemu_snapshots_dirty = true;
}
bool xemu_snapshots_offset_extra_data(QEMUFile *f)
{
unsigned int v;
uint32_t version;
uint32_t size;
v = qemu_get_be32(f);
if (v != XEMU_SNAPSHOT_DATA_MAGIC) {
qemu_file_skip(f, -4);
return true;
}
version = qemu_get_be32(f);
(void)version;
/* qemu_file_skip only works if you aren't skipping past internal buffer limit.
* Unfortunately, it's not usable here.
*/
size = qemu_get_be32(f);
void *buf = g_malloc(size);
qemu_get_buffer(f, buf, size);
g_free(buf);
return true;
}
void xemu_snapshots_mark_dirty(void)
{
xemu_snapshots_dirty = true;
}

66
ui/xemu-snapshots.h Normal file
View File

@ -0,0 +1,66 @@
/*
* 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"
#include <epoxy/gl.h>
#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75 // 'xemu'
#define XEMU_SNAPSHOT_DATA_VERSION 1
#define XEMU_SNAPSHOT_THUMBNAIL_WIDTH 160
#define XEMU_SNAPSHOT_THUMBNAIL_HEIGHT 120
extern const char **g_snapshot_shortcut_index_key_map[];
typedef struct XemuSnapshotData {
char *disc_path;
char *xbe_title_name;
GLuint gl_thumbnail;
} XemuSnapshotData;
// Implemented in xemu-snapshots.c
char *xemu_get_currently_loaded_disc_path(void);
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(GLuint tex, bool flip);
bool xemu_snapshots_load_png_to_texture(GLuint tex, void *buf, size_t size);
void *xemu_snapshots_create_framebuffer_thumbnail_png(size_t *size);
#ifdef __cplusplus
}
#endif
#endif

82
ui/xemu-thumbnail.cc Normal file
View File

@ -0,0 +1,82 @@
/*
* 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;
}
bool xemu_snapshots_load_png_to_texture(GLuint tex, void *buf, size_t size)
{
std::vector<uint8_t> pixels;
unsigned int width, height, channels;
if (fpng::fpng_decode_memory(buf, size, pixels, width, height, channels,
3) != fpng::FPNG_DECODE_SUCCESS) {
return false;
}
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());
return true;
}
void *xemu_snapshots_create_framebuffer_thumbnail_png(size_t *size)
{
/*
* 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;
}
std::vector<uint8_t> png;
if (!RenderFramebufferToPng(display_tex, display_flip, png,
2 * XEMU_SNAPSHOT_THUMBNAIL_WIDTH,
2 * XEMU_SNAPSHOT_THUMBNAIL_HEIGHT)) {
return NULL;
}
void *buf = g_malloc(png.size());
memcpy(buf, png.data(), png.size());
*size = png.size();
return buf;
}

37
ui/xemu-widescreen.c Normal file
View File

@ -0,0 +1,37 @@
/*
* xemu wide screen handler
*
* Copyright (c) 2023 Matt Borgerson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "xemu-widescreen.h"
static bool g_widescreen = false;
void xemu_set_widescreen(bool widescreen)
{
g_widescreen = widescreen;
}
bool xemu_get_widescreen(void)
{
return g_widescreen;
}

40
ui/xemu-widescreen.h Normal file
View File

@ -0,0 +1,40 @@
/*
* xemu wide screen handler
*
* Copyright (c) 2023 Matt Borgerson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef XEMU_WIDESCREEN
#define XEMU_WIDESCREEN
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
void xemu_set_widescreen(bool widescreen);
bool xemu_get_widescreen(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -47,6 +47,7 @@
#include "xemu-input.h"
#include "xemu-settings.h"
// #include "xemu-shaders.h"
#include "xemu-snapshots.h"
#include "xemu-version.h"
#include "xemu-os-utils.h"
@ -54,6 +55,7 @@
#include "hw/xbox/smbus.h" // For eject, drive tray
#include "hw/xbox/nv2a/nv2a.h"
#include "ui/xemu-notifications.h"
#include <stb_image.h>
@ -382,7 +384,9 @@ static void set_full_screen(struct sdl2_console *scon, bool set)
if (gui_fullscreen) {
SDL_SetWindowFullscreen(scon->real_window,
SDL_WINDOW_FULLSCREEN_DESKTOP);
(g_config.display.window.fullscreen_exclusive ?
SDL_WINDOW_FULLSCREEN :
SDL_WINDOW_FULLSCREEN_DESKTOP));
gui_saved_grab = gui_grab;
sdl_grab_start(scon);
} else {
@ -1194,6 +1198,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
xemu_snapshots_set_framebuffer_texture(tex, flip_required);
xemu_hud_set_framebuffer_texture(tex, flip_required);
xemu_hud_render();
@ -1543,31 +1548,44 @@ int main(int argc, char **argv)
while (1) {
sdl2_gl_refresh(&sdl2_console[0].dcl);
assert(glGetError() == GL_NO_ERROR);
}
// rcu_unregister_thread();
}
void xemu_eject_disc(void)
void xemu_eject_disc(Error **errp)
{
Error *error = NULL;
xbox_smc_eject_button();
xemu_settings_set_string(&g_config.sys.files.dvd_path, "");
// Xbox software may request that the drive open, but do it now anyway
Error *err = NULL;
qmp_eject(true, "ide0-cd1", false, NULL, true, false, &err);
qmp_eject(true, "ide0-cd1", false, NULL, true, false, &error);
if (error) {
error_propagate(errp, error);
}
xbox_smc_update_tray_state();
}
void xemu_load_disc(const char *path)
void xemu_load_disc(const char *path, Error **errp)
{
Error *error = NULL;
// Ensure an eject sequence is always triggered so Xbox software reloads
xbox_smc_eject_button();
xemu_settings_set_string(&g_config.sys.files.dvd_path, "");
Error *err = NULL;
qmp_blockdev_change_medium(true, "ide0-cd1", false, NULL, path,
false, "", false, false, false, 0,
&err);
&error);
if (error) {
error_propagate(errp, error);
} else {
xemu_settings_set_string(&g_config.sys.files.dvd_path, path);
}
xbox_smc_update_tray_state();
}

View File

@ -17,17 +17,27 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include "common.hh"
#include "actions.hh"
#include "misc.hh"
#include "xemu-hud.h"
#include "../xemu-snapshots.h"
#include "../xemu-notifications.h"
#include "snapshot-manager.hh"
void ActionEjectDisc(void)
{
xemu_settings_set_string(&g_config.sys.files.dvd_path, "");
xemu_eject_disc();
Error *err = NULL;
xemu_eject_disc(&err);
if (err) {
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
}
}
void ActionLoadDisc(void)
{
Error *err = NULL;
const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0";
const char *new_disc_path =
PausedFileOpen(NOC_FILE_DIALOG_OPEN, iso_file_filters,
@ -36,8 +46,12 @@ void ActionLoadDisc(void)
/* Cancelled */
return;
}
xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path);
xemu_load_disc(new_disc_path);
xemu_load_disc(new_disc_path, &err);
if (err) {
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
}
}
void ActionTogglePause(void)
@ -63,3 +77,32 @@ void ActionScreenshot(void)
{
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 {
ActionLoadSnapshotChecked(snapshot_name);
}
if (err) {
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
}
}
void ActionLoadSnapshotChecked(const char *name)
{
g_snapshot_mgr.LoadSnapshotChecked(name);
}

View File

@ -24,3 +24,5 @@ void ActionTogglePause();
void ActionReset();
void ActionShutdown();
void ActionScreenshot();
void ActionActivateBoundSnapshot(int slot, bool save);
void ActionLoadSnapshotChecked(const char *name);

View File

@ -28,6 +28,7 @@
#include <imgui_impl_sdl.h>
#include <imgui_impl_opengl3.h>
#include <implot.h>
#include <misc/cpp/imgui_stdlib.h>
#include <stb_image.h>
extern "C" {

View File

@ -26,12 +26,15 @@
#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 "notifications.hh"
#include "ui/xemu-widescreen.h"
Fbo *controller_fbo,
*logo_fbo;
GLuint g_controller_tex,
g_logo_tex;
g_logo_tex,
g_icon_tex;
enum class ShaderType {
Blit,
@ -155,10 +158,10 @@ static GLuint InitTexture(unsigned char *data, int width, int height,
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.
stbi_set_flip_vertically_on_load(1);
stbi_set_flip_vertically_on_load(flip);
int width, height, channels = 0;
unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
@ -442,6 +445,8 @@ void InitCustomRendering(void)
g_logo_shader = NewDecalShader(ShaderType::Logo);
logo_fbo = new Fbo(512, 512);
g_icon_tex = LoadTextureFromMemory(xemu_64x64_data, xemu_64x64_size, false);
g_framebuffer_shader = NewDecalShader(ShaderType::BlitGamma);
}
@ -657,47 +662,26 @@ void RenderLogo(uint32_t time)
glUseProgram(0);
}
void RenderFramebuffer(GLint tex, int width, int height, bool flip)
// Scale <src> proportionally to fit in <max>
void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height)
{
float w_ratio = (float)max_width/(float)max_height;
float t_ratio = (float)src_width/(float)src_height;
if (w_ratio >= t_ratio) {
*out_width = (float)max_width * t_ratio/w_ratio;
*out_height = max_height;
} else {
*out_width = max_width;
*out_height = (float)max_height * w_ratio/t_ratio;
}
}
void RenderFramebuffer(GLint tex, int width, int height, bool flip, float scale[2])
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
int tw, th;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
// Calculate scaling factors
float scale[2];
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
// Stretch to fit
scale[0] = 1.0;
scale[1] = 1.0;
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) {
// Centered
scale[0] = (float)tw/(float)width;
scale[1] = (float)th/(float)height;
} else {
float t_ratio;
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
// Scale to fit window using a fixed 16:9 aspect ratio
t_ratio = 16.0f/9.0f;
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
t_ratio = 4.0f/3.0f;
} else {
// Scale to fit, preserving framebuffer aspect ratio
t_ratio = (float)tw/(float)th;
}
float w_ratio = (float)width/(float)height;
if (w_ratio >= t_ratio) {
scale[0] = t_ratio/w_ratio;
scale[1] = 1.0;
} else {
scale[0] = 1.0;
scale[1] = w_ratio/t_ratio;
}
}
DecalShader *s = g_framebuffer_shader;
s->flip = flip;
glViewport(0, 0, width, height);
@ -717,23 +701,76 @@ void RenderFramebuffer(GLint tex, int width, int height, bool flip)
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
if (!nv2a_get_screen_off()) {
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
}
}
void SaveScreenshot(GLuint tex, bool flip)
float GetDisplayAspectRatio(int width, int height)
{
switch (g_config.display.ui.aspect_ratio) {
case CONFIG_DISPLAY_UI_ASPECT_RATIO_NATIVE:
return (float)width/(float)height;
case CONFIG_DISPLAY_UI_ASPECT_RATIO_16X9:
return 16.0f/9.0f;
case CONFIG_DISPLAY_UI_ASPECT_RATIO_4X3:
return 4.0f/3.0f;
case CONFIG_DISPLAY_UI_ASPECT_RATIO_AUTO:
default:
return xemu_get_widescreen() ? 16.0f/9.0f : 4.0f/3.0f;
}
}
void RenderFramebuffer(GLint tex, int width, int height, bool flip)
{
int tw, th;
float scale[2];
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
// Calculate scaling factors
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
// Stretch to fit
scale[0] = 1.0;
scale[1] = 1.0;
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) {
// Centered
float t_ratio = GetDisplayAspectRatio(tw, th);
scale[0] = t_ratio*(float)th/(float)width;
scale[1] = (float)th/(float)height;
} else {
float t_ratio = GetDisplayAspectRatio(tw, th);
float w_ratio = (float)width/(float)height;
if (w_ratio >= t_ratio) {
scale[0] = t_ratio/w_ratio;
scale[1] = 1.0;
} else {
scale[0] = 1.0;
scale[1] = w_ratio/t_ratio;
}
}
RenderFramebuffer(tex, width, height, flip, scale);
}
bool RenderFramebufferToPng(GLuint tex, bool flip, std::vector<uint8_t> &png, int max_width, int max_height)
{
int width, height;
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
glBindTexture(GL_TEXTURE_2D, 0);
if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
width = height * (16.0f / 9.0f);
} else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
width = height * (4.0f / 3.0f);
}
width = height * GetDisplayAspectRatio(width, height);
if (!max_width) max_width = width;
if (!max_height) max_height = height;
ScaleDimensions(width, height, max_width, max_height, &width, &height);
std::vector<uint8_t> pixels;
pixels.resize(width * height * 3);
@ -742,7 +779,8 @@ void SaveScreenshot(GLuint tex, bool flip)
fbo.Target();
bool blend = glIsEnabled(GL_BLEND);
if (blend) glDisable(GL_BLEND);
RenderFramebuffer(tex, width, height, !flip);
float scale[2] = {1.0, 1.0};
RenderFramebuffer(tex, width, height, !flip, scale);
if (blend) glEnable(GL_BLEND);
glPixelStorei(GL_PACK_ROW_LENGTH, width);
glPixelStorei(GL_PACK_IMAGE_HEIGHT, height);
@ -750,10 +788,16 @@ void SaveScreenshot(GLuint tex, bool flip)
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
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;
char fname[128];
std::vector<uint8_t> png;
if (fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png)) {
if (RenderFramebufferToPng(tex, flip, png)) {
time_t t = time(NULL);
struct tm *tmp = localtime(&t);
if (tmp) {

View File

@ -17,6 +17,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#pragma once
#include <vector>
#include "common.hh"
#include "../xemu-input.h"
@ -38,6 +39,7 @@ public:
};
extern Fbo *controller_fbo, *logo_fbo;
extern GLuint g_icon_tex;
void InitCustomRendering(void);
void RenderLogo(uint32_t time);
@ -46,4 +48,7 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color,
void RenderControllerPort(float frame_x, float frame_y, int i,
uint32_t port_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);
void SaveScreenshot(GLuint tex, bool flip);
void ScaleDimensions(int src_width, int src_height, int max_width, int max_height, int *out_width, int *out_height);

View File

@ -22,11 +22,14 @@
#include "main-menu.hh"
#include "font-manager.hh"
#include "input-manager.hh"
#include "snapshot-manager.hh"
#include "viewport-manager.hh"
#include "xemu-hud.h"
#include "misc.hh"
#include "gl-helpers.hh"
#include "reporting.hh"
#include "qapi/error.h"
#include "actions.hh"
#include "../xemu-input.h"
#include "../xemu-notifications.h"
@ -337,10 +340,14 @@ void MainMenuDisplayView::Draw()
ChevronCombo("Display mode", &g_config.display.ui.fit,
"Center\0"
"Scale\0"
"Scale (Widescreen 16:9)\0"
"Scale (4:3)\0"
"Stretch\0",
"Select how the framebuffer should fit or scale into the window");
ChevronCombo("Aspect ratio", &g_config.display.ui.aspect_ratio,
"Native\0"
"Auto (Default)\0"
"4:3\0"
"16:9\0",
"Select the displayed aspect ratio");
}
void MainMenuAudioView::Draw()
@ -639,115 +646,331 @@ void MainMenuNetworkView::DrawUdpOptions(bool appearing)
ImGui::PopFont();
}
#if 0
class MainMenuSnapshotsView : public virtual MainMenuTabView
MainMenuSnapshotsView::MainMenuSnapshotsView() : MainMenuTabView()
{
protected:
GLuint screenshot;
xemu_snapshots_mark_dirty();
public:
void initScreenshot()
{
if (screenshot == 0) {
glGenTextures(1, &screenshot);
int w, h, n;
stbi_set_flip_vertically_on_load(0);
unsigned char *data = stbi_load("./data/screenshot.png", &w, &h, &n, 4);
assert(n == 4);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, screenshot);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
}
m_search_regex = NULL;
m_current_title_id = 0;
}
void snapshotBigButton(const char *name, const char *title_name, GLuint screenshot)
MainMenuSnapshotsView::~MainMenuSnapshotsView()
{
g_free(m_search_regex);
}
bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding)
{
ImGuiStyle &style = ImGui::GetStyle();
ImVec2 pos = ImGui::GetCursorPos();
ImDrawList *draw_list = ImGui::GetWindowDrawList();
ImGui::PushFont(g_font_mgr.m_menuFont);
const char *icon = ICON_FA_CIRCLE_XMARK;
ImVec2 ts_icon = ImGui::CalcTextSize(icon);
ts_icon.x += 2*style.FramePadding.x;
ImGui::PopFont();
ImGui::PushFont(g_font_mgr.m_menuFontSmall);
ImVec2 ts_sub = ImGui::CalcTextSize(name);
ImGui::PushFont(g_font_mgr.m_menu_font_small);
ImVec2 ts_sub = ImGui::CalcTextSize(snapshot->name);
ImGui::PopFont();
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.scale(ImVec2(5, 5)));
ImGui::PushFont(g_font_mgr.m_menuFontMedium);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.Scale(ImVec2(5, 5)));
ImVec2 ts_title = ImGui::CalcTextSize(name);
ImVec2 thumbnail_size = g_viewport_mgr.scale(ImVec2(160, 120));
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_pos(style.FramePadding.x, style.FramePadding.y);
ImVec2 text_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 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);
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)));
ImGui::PopFont();
const ImVec2 sz = ImGui::GetItemRectSize();
const ImVec2 p0 = ImGui::GetItemRectMin();
const ImVec2 p1 = ImGui::GetItemRectMax();
ts_icon.y = sz.y;
// Snapshot thumbnail
ImGui::SetItemAllowOverlap();
ImGui::SameLine();
ImGui::SetCursorPosX(pos.x + thumbnail_pos.x);
ImGui::SetCursorPosY(pos.y + thumbnail_pos.y);
ImGui::Image((ImTextureID)screenshot, thumbnail_size, ImVec2(0,0), ImVec2(1,1));
draw_list->PushClipRect(p0, p1, true);
// Snapshot thumbnail
GLuint thumbnail = data->gl_thumbnail ? data->gl_thumbnail : g_icon_tex;
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);
// 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);
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);
draw_list->AddImage((ImTextureID)(uint64_t)thumbnail, img_min, img_max);
// Snapshot title
ImGui::PushFont(g_font_mgr.m_menuFontMedium);
draw_list->AddText(ImVec2(p0.x + text_pos.x, p0.y + text_pos.y), IM_COL32(255, 255, 255, 255), name);
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);
ImGui::PopFont();
// Snapshot subtitle
ImGui::PushFont(g_font_mgr.m_menuFontSmall);
draw_list->AddText(ImVec2(p0.x + subtext_pos.x, p0.y + subtext_pos.y), IM_COL32(255, 255, 255, 200), title_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);
// Snapshot date
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);
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);
g_free(binding_text);
}
ImGui::PopFont();
draw_list->PopClipRect();
// Delete button
ImGui::SameLine();
ImGui::SetCursorPosY(pos.y);
ImGui::SetCursorPosX(pos.x + sz.x - ts_icon.x);
ImGui::PushFont(g_font_mgr.m_menuFont);
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
ImGui::Button(icon, ts_icon);
ImGui::PopStyleColor();
ImGui::PopStyleVar(1);
ImGui::PopFont();
ImGui::PopStyleVar(2);
return load;
}
void Draw()
void MainMenuSnapshotsView::ClearSearch()
{
initScreenshot();
for (int i = 0; i < 15; i++) {
char buf[64];
snprintf(buf, sizeof(buf), "%s", "Apr 9 2022 19:44");
m_search_buf.clear();
if (m_search_regex) {
g_free(m_search_regex);
m_search_regex = NULL;
}
}
int MainMenuSnapshotsView::OnSearchTextUpdate(ImGuiInputTextCallbackData *data)
{
GError *gerr = NULL;
MainMenuSnapshotsView *win = (MainMenuSnapshotsView*)data->UserData;
if (win->m_search_regex) {
g_free(win->m_search_regex);
win->m_search_regex = NULL;
}
if (data->BufTextLen == 0) {
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()
{
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");
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);
if (title_name) {
m_current_title_name = title_name;
g_free(title_name);
} else {
m_current_title_name.clear();
}
m_current_title_id = xbe->cert->m_titleid;
}
} else {
m_current_title_name.clear();
m_current_title_id = 0;
}
}
ImGui::SetNextItemWidth(ImGui::GetColumnWidth() * 0.8);
ImGui::PushFont(g_font_mgr.m_menu_font_small);
ImGui::InputTextWithHint("##search", "Search or name new snapshot...",
&m_search_buf, ImGuiInputTextFlags_CallbackEdit,
&OnSearchTextUpdate, this);
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) {
snapshot_with_create_name_exists = true;
break;
}
}
ImGui::SameLine();
if (snapshot_with_create_name_exists) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8, 0, 0, 1));
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);
ClearSearch();
}
if (snapshot_with_create_name_exists) {
ImGui::PopStyleColor(3);
}
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::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)) {
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);
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);
keep_entry |= g_match_info_matches(match);
g_free(match);
}
if (!keep_entry) {
continue;
}
}
QEMUSnapshotInfo *snapshot = &g_snapshot_mgr.m_snapshots[i];
XemuSnapshotData *data = &g_snapshot_mgr.m_extra_data[i];
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;
}
}
ImGui::PushID(i);
snapshotBigButton(buf, "Halo: Combat Evolved", screenshot);
ImVec2 pos = ImGui::GetCursorScreenPos();
bool load = BigSnapshotButton(snapshot, data, current_snapshot_binding);
// FIXME: Provide context menu control annotation
if (ImGui::IsItemHovered() && ImGui::IsKeyPressed(ImGuiKey_GamepadFaceLeft)) {
ImGui::SetNextWindowPos(pos);
ImGui::OpenPopup("Snapshot Options");
}
DrawSnapshotContextMenu(snapshot, data, current_snapshot_binding);
ImGui::PopID();
if (load) {
ActionLoadSnapshotChecked(snapshot->name);
}
at_least_one_snapshot_displayed = true;
}
if (!at_least_one_snapshot_displayed) {
ImGui::Dummy(g_viewport_mgr.Scale(ImVec2(0, 16)));
const char *msg;
if (g_snapshot_mgr.m_snapshots_len) {
if (!m_search_buf.empty()) {
msg = "Press Create to create new snapshot";
} else {
msg = "No snapshots match filter criteria";
}
} else {
msg = "No snapshots to display";
}
ImVec2 dim = ImGui::CalcTextSize(msg);
ImVec2 cur = ImGui::GetCursorPos();
ImGui::SetCursorPosX(cur.x + (ImGui::GetColumnWidth()-dim.x)/2);
ImGui::TextColored(ImVec4(0.94f, 0.94f, 0.94f, 0.70f), "%s", msg);
}
}
};
#endif
void MainMenuSnapshotsView::DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding)
{
if (!ImGui::BeginPopupContextItem("Snapshot Options")) {
return;
}
if (ImGui::MenuItem("Load")) {
ActionLoadSnapshotChecked(snapshot->name);
}
if (ImGui::BeginMenu("Keybinding")) {
for (int i = 0; i < 4; ++i) {
char *item_name = g_strdup_printf("Bind to F%d", i + 5);
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[i], snapshot->name);
current_snapshot_binding = i;
ImGui::CloseCurrentPopup();
}
g_free(item_name);
}
if (current_snapshot_binding >= 0) {
if (ImGui::MenuItem("Unbind")) {
xemu_settings_set_string(g_snapshot_shortcut_index_key_map[current_snapshot_binding], "");
current_snapshot_binding = -1;
}
}
ImGui::EndMenu();
}
ImGui::Separator();
Error *err = NULL;
if (ImGui::MenuItem("Replace")) {
xemu_snapshots_save(snapshot->name, &err);
}
if (ImGui::MenuItem("Delete")) {
xemu_snapshots_delete(snapshot->name, &err);
}
if (err) {
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
}
ImGui::EndPopup();
}
MainMenuSystemView::MainMenuSystemView() : m_dirty(false)
{
@ -929,7 +1152,7 @@ MainMenuScene::MainMenuScene()
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_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT),
m_system_button("System", ICON_FA_MICROCHIP),
m_about_button("About", ICON_FA_CIRCLE_INFO)
{
@ -940,7 +1163,7 @@ MainMenuScene::MainMenuScene()
m_tabs.push_back(&m_display_button);
m_tabs.push_back(&m_audio_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_about_button);
@ -949,7 +1172,7 @@ MainMenuScene::MainMenuScene()
m_views.push_back(&m_display_view);
m_views.push_back(&m_audio_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_about_view);
@ -977,15 +1200,18 @@ void MainMenuScene::ShowNetwork()
{
SetNextViewIndexWithFocus(4);
}
// void MainMenuScene::showSnapshots() { SetNextViewIndexWithFocus(5); }
void MainMenuScene::ShowSystem()
void MainMenuScene::ShowSnapshots()
{
SetNextViewIndexWithFocus(5);
}
void MainMenuScene::ShowAbout()
void MainMenuScene::ShowSystem()
{
SetNextViewIndexWithFocus(6);
}
void MainMenuScene::ShowAbout()
{
SetNextViewIndexWithFocus(7);
}
void MainMenuScene::SetNextViewIndexWithFocus(int i)
{

View File

@ -24,6 +24,7 @@
#include "widgets.hh"
#include "scene.hh"
#include "scene-components.hh"
#include "../xemu-snapshots.h"
extern "C" {
#include "net/pcap.h"
@ -102,10 +103,22 @@ public:
class MainMenuSnapshotsView : public virtual MainMenuTabView
{
protected:
GRegex *m_search_regex;
uint32_t m_current_title_id;
std::string m_current_title_name;
std::string m_search_buf;
void ClearSearch();
void DrawSnapshotContextMenu(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding);
bool BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding);
static int OnSearchTextUpdate(ImGuiInputTextCallbackData *data);
public:
void SnapshotBigButton(const char *name, const char *title_name,
GLuint screenshot);
MainMenuSnapshotsView();
~MainMenuSnapshotsView();
void Draw() override;
};
class MainMenuSystemView : public virtual MainMenuTabView
@ -153,7 +166,7 @@ protected:
m_display_button,
m_audio_button,
m_network_button,
// m_snapshots_button,
m_snapshots_button,
m_system_button,
m_about_button;
std::vector<MainMenuTabView*> m_views;
@ -162,7 +175,7 @@ protected:
MainMenuDisplayView m_display_view;
MainMenuAudioView m_audio_view;
MainMenuNetworkView m_network_view;
// MainMenuSnapshotsView m_snapshots_view;
MainMenuSnapshotsView m_snapshots_view;
MainMenuSystemView m_system_view;
MainMenuAboutView m_about_view;
@ -174,7 +187,7 @@ public:
void ShowDisplay();
void ShowAudio();
void ShowNetwork();
// void ShowSnapshots();
void ShowSnapshots();
void ShowSystem();
void ShowAbout();
void SetNextViewIndexWithFocus(int i);

View File

@ -31,11 +31,13 @@
#include <string>
#include <memory>
#include "actions.hh"
#include "common.hh"
#include "xemu-hud.h"
#include "misc.hh"
#include "gl-helpers.hh"
#include "input-manager.hh"
#include "snapshot-manager.hh"
#include "viewport-manager.hh"
#include "font-manager.hh"
#include "scene.hh"
@ -53,6 +55,8 @@
#endif
bool g_screenshot_pending;
const char *g_snapshot_pending_load_name;
float g_main_menu_height;
static ImGuiStyle g_base_style;
@ -277,6 +281,14 @@ void xemu_hud_render(void)
!ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) {
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();
@ -289,6 +301,7 @@ void xemu_hud_render(void)
#endif
g_scene_mgr.Draw();
if (!first_boot_window.is_open) notification_manager.Draw();
g_snapshot_mgr.Draw();
// static bool show_demo = true;
// if (show_demo) ImGui::ShowDemoWindow(&show_demo);

View File

@ -16,6 +16,7 @@
// 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 "ui/xemu-notifications.h"
#include "common.hh"
#include "main-menu.hh"
#include "menubar.hh"
@ -88,6 +89,48 @@ void ShowMainMenu()
if (ImGui::MenuItem(running ? "Pause" : "Resume", SHORTCUT_MENU_TEXT(P))) ActionTogglePause();
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();
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(" Audio")) g_main_menu.ShowAudio();
if (ImGui::MenuItem(" Network")) g_main_menu.ShowNetwork();
if (ImGui::MenuItem(" Snapshots")) g_main_menu.ShowSnapshots();
if (ImGui::MenuItem(" System")) g_main_menu.ShowSystem();
ImGui::Separator();
@ -147,11 +191,12 @@ void ShowMainMenu()
}
ImGui::Combo("Display Mode", &g_config.display.ui.fit,
"Center\0Scale\0Scale (Widescreen 16:9)\0Scale "
"(4:3)\0Stretch\0");
"Center\0Scale\0Stretch\0");
ImGui::SameLine();
HelpMarker("Controls how the rendered content should be scaled "
"into the window");
ImGui::Combo("Aspect Ratio", &g_config.display.ui.aspect_ratio,
"Native\0Auto\0""4:3\0""16:9\0");
if (ImGui::MenuItem("Fullscreen", SHORTCUT_MENU_TEXT(Alt + F),
xemu_is_fullscreen(), true)) {
xemu_toggle_fullscreen();

View File

@ -16,6 +16,7 @@ xemu_ss.add(files(
'scene-components.cc',
'scene-manager.cc',
'scene.cc',
'snapshot-manager.cc',
'viewport-manager.cc',
'welcome.cc',
'widgets.cc',

View File

@ -16,6 +16,7 @@
// 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 "ui/xemu-notifications.h"
#include <string>
#include <vector>
#include "misc.hh"
@ -27,6 +28,8 @@
#include "input-manager.hh"
#include "xemu-hud.h"
#include "IconsFontAwesome6.h"
#include "../xemu-snapshots.h"
#include "main-menu.hh"
PopupMenuItemDelegate::~PopupMenuItemDelegate() {}
void PopupMenuItemDelegate::PushMenu(PopupMenu &menu) {}
@ -255,7 +258,7 @@ public:
bool DrawItems(PopupMenuItemDelegate &nav) override
{
const char *values[] = {
"Center", "Scale", "Scale (Widescreen 16:9)", "Scale (4:3)", "Stretch"
"Center", "Scale", "Stretch"
};
for (int i = 0; i < CONFIG_DISPLAY_UI_FIT__COUNT; i++) {
@ -269,11 +272,34 @@ public:
}
};
extern Scene g_main_menu;
class AspectRatioPopupMenu : public virtual PopupMenu {
public:
bool DrawItems(PopupMenuItemDelegate &nav) override
{
const char *values[] = {
"Native",
"Auto (Default)",
"4:3",
"16:9"
};
for (int i = 0; i < CONFIG_DISPLAY_UI_ASPECT_RATIO__COUNT; i++) {
bool selected = g_config.display.ui.aspect_ratio == i;
if (m_focus && selected) ImGui::SetKeyboardFocusHere();
if (PopupMenuCheck(values[i], "", selected))
g_config.display.ui.aspect_ratio = i;
}
return false;
}
};
extern MainMenuScene g_main_menu;
class SettingsPopupMenu : public virtual PopupMenu {
protected:
DisplayModePopupMenu display_mode;
AspectRatioPopupMenu aspect_ratio;
public:
bool DrawItems(PopupMenuItemDelegate &nav) override
@ -292,6 +318,15 @@ public:
nav.PushFocus();
nav.PushMenu(display_mode);
}
if (PopupMenuSubmenuButton("Aspect Ratio", ICON_FA_EXPAND)) {
nav.PushFocus();
nav.PushMenu(aspect_ratio);
}
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)) {
nav.ClearMenuStack();
g_scene_mgr.PushScene(g_main_menu);
@ -338,6 +373,11 @@ public:
ActionScreenshot();
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)) {
ActionEjectDisc();
pop = true;

163
ui/xui/snapshot-manager.cc Normal file
View File

@ -0,0 +1,163 @@
//
// 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 "common.hh"
#include "notifications.hh"
#include "snapshot-manager.hh"
#include "xemu-hud.h"
SnapshotManager g_snapshot_mgr;
SnapshotManager::SnapshotManager()
{
m_snapshots = NULL;
m_extra_data = NULL;
m_load_failed = false;
m_open_pending = false;
m_snapshots_len = 0;
}
SnapshotManager::~SnapshotManager()
{
g_free(m_snapshots);
g_free(m_extra_data);
xemu_snapshots_mark_dirty();
}
void SnapshotManager::Refresh()
{
Error *err = NULL;
if (!m_load_failed) {
m_snapshots_len = xemu_snapshots_list(&m_snapshots, &m_extra_data, &err);
}
if (err) {
m_load_failed = true;
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
m_snapshots_len = 0;
}
}
void SnapshotManager::LoadSnapshotChecked(const char *name)
{
Refresh();
XemuSnapshotData *data = NULL;
for (int i = 0; i < m_snapshots_len; i++) {
if (!strcmp(m_snapshots[i].name, name)) {
data = &m_extra_data[i];
break;
}
}
if (data == NULL) {
return;
}
char *current_disc_path = xemu_get_currently_loaded_disc_path();
if (data->disc_path && (!current_disc_path || strcmp(current_disc_path, data->disc_path))) {
if (current_disc_path) {
m_current_disc_path = current_disc_path;
} else {
m_current_disc_path.clear();
}
m_target_disc_path = data->disc_path;
m_pending_load_name = name;
m_open_pending = true;
} else {
if (!data->disc_path) {
xemu_eject_disc(NULL);
}
LoadSnapshot(name);
}
if (current_disc_path) {
g_free(current_disc_path);
}
}
void SnapshotManager::LoadSnapshot(const char *name)
{
Error *err = NULL;
xemu_snapshots_load(name, &err);
if (err) {
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
}
}
void SnapshotManager::Draw()
{
DrawSnapshotDiscLoadDialog();
}
void SnapshotManager::DrawSnapshotDiscLoadDialog()
{
if (m_open_pending) {
ImGui::OpenPopup("DVD Drive Image");
m_open_pending = false;
}
if (!ImGui::BeginPopupModal("DVD Drive Image", NULL, ImGuiWindowFlags_AlwaysAutoResize)) {
return;
}
ImGui::Text("The DVD drive disc image mounted when the snapshot was created does not appear to be loaded:");
ImGui::Spacing();
ImGui::Indent();
ImGui::Text("Current Image: %s", m_current_disc_path.length() ? m_current_disc_path.c_str() : "(None)");
ImGui::Text("Expected Image: %s", m_target_disc_path.length() ? m_target_disc_path.c_str() : "(None)");
ImGui::Unindent();
ImGui::Spacing();
ImGui::Text("Would you like to load it now?");
ImGui::Dummy(ImVec2(0,16));
if (ImGui::Button("Yes", ImVec2(120, 0))) {
xemu_eject_disc(NULL);
Error *err = NULL;
xemu_load_disc(m_target_disc_path.c_str(), &err);
if (err) {
xemu_queue_error_message(error_get_pretty(err));
error_free(err);
} else {
LoadSnapshot(m_pending_load_name.c_str());
}
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("No", ImVec2(120, 0))) {
LoadSnapshot(m_pending_load_name.c_str());
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}

View File

@ -0,0 +1,47 @@
//
// 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/>.
//
#pragma once
#include <string>
#include "../xemu-snapshots.h"
class SnapshotManager
{
public:
QEMUSnapshotInfo *m_snapshots;
XemuSnapshotData *m_extra_data;
bool m_load_failed;
bool m_open_pending;
int m_snapshots_len;
std::string m_pending_load_name;
std::string m_current_disc_path;
std::string m_target_disc_path;
SnapshotManager();
~SnapshotManager();
void Refresh();
void LoadSnapshot(const char *name);
void LoadSnapshotChecked(const char *name);
void Draw();
void DrawSnapshotDiscLoadDialog();
};
extern SnapshotManager g_snapshot_mgr;

View File

@ -306,12 +306,14 @@ bool FilePicker(const char *str_id, const char **buf, const char *filters,
ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
ImGuiStyle &style = ImGui::GetStyle();
ImVec2 p = ImGui::GetCursorScreenPos();
ImVec2 cursor = ImGui::GetCursorPos();
const char *desc = strlen(*buf) ? *buf : "(None Selected)";
ImVec2 bb(ImGui::GetColumnWidth(),
GetWidgetTitleDescriptionHeight(str_id, desc));
ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
ImGui::PushID(str_id);
bool status = ImGui::Button("###file_button", bb);
ImGui::SetItemAllowOverlap();
if (status) {
int flags = NOC_FILE_DIALOG_OPEN;
if (dir) flags |= NOC_FILE_DIALOG_DIR;
@ -320,6 +322,7 @@ bool FilePicker(const char *str_id, const char **buf, const char *filters,
if (new_path) {
free((void*)*buf);
*buf = strdup(new_path);
desc = *buf;
changed = true;
}
}
@ -336,9 +339,32 @@ bool FilePicker(const char *str_id, const char **buf, const char *filters,
ImGui::PushFont(g_font_mgr.m_menu_font);
const char *icon = dir ? ICON_FA_FOLDER : ICON_FA_FILE;
ImVec2 ts_icon = ImGui::CalcTextSize(icon);
draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
p0.y + (p1.y - p0.y - ts_icon.y) / 2),
ImGui::GetColorU32(ImGuiCol_Text), icon);
ImVec2 icon_pos = ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
p0.y + (p1.y - p0.y - ts_icon.y) / 2);
draw_list->AddText(icon_pos, ImGui::GetColorU32(ImGuiCol_Text), icon);
ImVec2 ts_clear_icon = ImGui::CalcTextSize(ICON_FA_XMARK);
ts_clear_icon.x += 2 * style.FramePadding.x;
ImVec2 clear_icon_pos = ImVec2(cursor.x + bb.x - ts_icon.x - ts_clear_icon.x, cursor.y);
auto prev_pos = ImGui::GetCursorPos();
ImGui::SetCursorPos(clear_icon_pos);
char *clear_button_id = g_strdup_printf("%s_clear", str_id);
ImGui::PushID(clear_button_id);
bool clear = ImGui::Button(ICON_FA_XMARK, ImVec2(ts_clear_icon.x, bb.y));
if (clear) {
free((void*)*buf);
*buf = strdup("");
changed = true;
}
ImGui::PopID();
g_free(clear_button_id);
ImGui::SetCursorPos(prev_pos);
ImGui::PopFont();
ImGui::PopStyleColor();

View File

@ -34,8 +34,8 @@ extern "C" {
int xemu_is_fullscreen(void);
void xemu_monitor_init(void);
void xemu_toggle_fullscreen(void);
void xemu_eject_disc(void);
void xemu_load_disc(const char *path);
void xemu_eject_disc(Error **errp);
void xemu_load_disc(const char *path, Error **errp);
// Implemented in xemu_hud.cc
void xemu_hud_init(SDL_Window *window, void *sdl_gl_context);

View File

@ -20,6 +20,15 @@
</p>
</description>
<screenshots>
<screenshot type="default"><image>https://xemu.app/screenshots/0.png</image></screenshot>
<screenshot><image>https://xemu.app/screenshots/1.png</image></screenshot>
<screenshot><image>https://xemu.app/screenshots/2.png</image></screenshot>
<screenshot><image>https://xemu.app/screenshots/3.png</image></screenshot>
<screenshot><image>https://xemu.app/screenshots/4.png</image></screenshot>
<screenshot><image>https://xemu.app/screenshots/5.png</image></screenshot>
</screenshots>
<categories>
<category>Game</category>
<category>Emulator</category>
@ -37,5 +46,4 @@
<provides>
<binary>xemu</binary>
</provides>
</component>