mirror of
https://github.com/xemu-project/xemu.git
synced 2024-11-23 03:29:43 +00:00
Merge remote-tracking branch 'origin/master' into sync/qemu-7.2.0
This commit is contained in:
commit
976319b391
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -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,
|
||||
|
17
.github/workflows/trigger-website-update.yml
vendored
17
.github/workflows/trigger-website-update.yml
vendored
@ -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
|
6
block.c
6
block.c
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
69
build.sh
69
build.sh
@ -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} \
|
||||
|
@ -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
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,15 +3048,17 @@ 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] &
|
||||
NV_PGRAPH_SETUPRASTER_LINESMOOTHENABLE) {
|
||||
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] &
|
||||
NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) {
|
||||
if (!anti_aliasing && pg->regs[NV_PGRAPH_SETUPRASTER] &
|
||||
NV_PGRAPH_SETUPRASTER_POLYSMOOTHENABLE) {
|
||||
glEnable(GL_POLYGON_SMOOTH);
|
||||
} else {
|
||||
glDisable(GL_POLYGON_SMOOTH);
|
||||
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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}"
|
||||
|
34
ubuntu-win64-cross/libslirp-1.patch
Normal file
34
ubuntu-win64-cross/libslirp-1.patch
Normal 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
|
||||
|
19
ubuntu-win64-cross/libslirp.mk
Normal file
19
ubuntu-win64-cross/libslirp.mk
Normal 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
|
@ -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
|
||||
|
||||
|
@ -28,6 +28,9 @@ xemu_ss.add(files(
|
||||
|
||||
'xemu.c',
|
||||
'xemu-data.c',
|
||||
'xemu-snapshots.c',
|
||||
'xemu-thumbnail.cc',
|
||||
'xemu-widescreen.c',
|
||||
))
|
||||
|
||||
subdir('xui')
|
||||
|
1
ui/thirdparty/meson.build
vendored
1
ui/thirdparty/meson.build
vendored
@ -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',
|
||||
)
|
||||
|
||||
|
@ -19,13 +19,49 @@
|
||||
|
||||
#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,
|
||||
¤t_build, (LPDWORD)&build_size) == ERROR_SUCCESS) ||
|
||||
(RegGetValueW(HKEY_LOCAL_MACHINE,
|
||||
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
|
||||
L"CSDVersion", RRF_RT_REG_SZ, (LPVOID)NULL,
|
||||
¤t_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);
|
||||
ShellExecute(0, "open", url, 0, 0, SW_SHOW);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@ -58,8 +59,9 @@ void xemu_settings_save(void);
|
||||
|
||||
static inline void xemu_settings_set_string(const char **str, const char *new_str)
|
||||
{
|
||||
free((char*)*str);
|
||||
*str = strdup(new_str);
|
||||
assert(new_str);
|
||||
free((char*)*str);
|
||||
*str = strdup(new_str);
|
||||
}
|
||||
|
||||
void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol);
|
||||
|
341
ui/xemu-snapshots.c
Normal file
341
ui/xemu-snapshots.c
Normal 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
66
ui/xemu-snapshots.h
Normal 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
82
ui/xemu-thumbnail.cc
Normal 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
37
ui/xemu-widescreen.c
Normal 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
40
ui/xemu-widescreen.h
Normal 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
|
32
ui/xemu.c
32
ui/xemu.c
@ -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();
|
||||
}
|
||||
|
@ -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)
|
||||
@ -62,4 +76,33 @@ void ActionShutdown(void)
|
||||
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);
|
||||
}
|
||||
|
@ -24,3 +24,5 @@ void ActionTogglePause();
|
||||
void ActionReset();
|
||||
void ActionShutdown();
|
||||
void ActionScreenshot();
|
||||
void ActionActivateBoundSnapshot(int slot, bool save);
|
||||
void ActionLoadSnapshotChecked(const char *name);
|
||||
|
@ -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" {
|
||||
|
@ -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);
|
||||
glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
|
||||
|
||||
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) {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
MainMenuSnapshotsView::~MainMenuSnapshotsView()
|
||||
{
|
||||
g_free(m_search_regex);
|
||||
}
|
||||
|
||||
bool MainMenuSnapshotsView::BigSnapshotButton(QEMUSnapshotInfo *snapshot, XemuSnapshotData *data, int current_snapshot_binding)
|
||||
{
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||
|
||||
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_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 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::PopFont();
|
||||
|
||||
const ImVec2 p0 = ImGui::GetItemRectMin();
|
||||
const ImVec2 p1 = ImGui::GetItemRectMax();
|
||||
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_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 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();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
return load;
|
||||
}
|
||||
|
||||
void MainMenuSnapshotsView::ClearSearch()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void snapshotBigButton(const char *name, const char *title_name, GLuint screenshot)
|
||||
{
|
||||
ImGuiStyle &style = ImGui::GetStyle();
|
||||
ImVec2 pos = ImGui::GetCursorPos();
|
||||
ImDrawList *draw_list = ImGui::GetWindowDrawList();
|
||||
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);
|
||||
|
||||
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::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);
|
||||
|
||||
ImVec2 ts_title = ImGui::CalcTextSize(name);
|
||||
ImVec2 thumbnail_size = g_viewport_mgr.scale(ImVec2(160, 120));
|
||||
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);
|
||||
|
||||
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 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::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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void Draw()
|
||||
{
|
||||
initScreenshot();
|
||||
for (int i = 0; i < 15; i++) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "%s", "Apr 9 2022 19:44");
|
||||
ImGui::PushID(i);
|
||||
snapshotBigButton(buf, "Halo: Combat Evolved", screenshot);
|
||||
ImGui::PopID();
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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',
|
||||
|
@ -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
163
ui/xui/snapshot-manager.cc
Normal 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();
|
||||
}
|
47
ui/xui/snapshot-manager.hh
Normal file
47
ui/xui/snapshot-manager.hh
Normal 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;
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -1,32 +1,41 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>app.xemu.xemu</id>
|
||||
|
||||
|
||||
<name>xemu</name>
|
||||
<summary>Original Xbox Emulator</summary>
|
||||
|
||||
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-2.0-only</project_license>
|
||||
|
||||
|
||||
<supports>
|
||||
<control>pointing</control>
|
||||
<control>keyboard</control>
|
||||
<control>gamepad</control>
|
||||
</supports>
|
||||
|
||||
|
||||
<description>
|
||||
<p>
|
||||
A free and open-source application that emulates the original Microsoft Xbox game console. Supports connecting up to 4 controllers for local play, networking for multiplayer, resolution scaling, and more.
|
||||
</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>
|
||||
</categories>
|
||||
|
||||
<content_rating type="oars-1.1"/>
|
||||
|
||||
|
||||
<url type="homepage">https://xemu.app</url>
|
||||
<url type="bugtracker">https://github.com/xemu-project/xemu/issues</url>
|
||||
<url type="help">https://xemu.app/docs</url>
|
||||
@ -37,5 +46,4 @@
|
||||
<provides>
|
||||
<binary>xemu</binary>
|
||||
</provides>
|
||||
|
||||
</component>
|
||||
|
Loading…
Reference in New Issue
Block a user