mirror of
https://github.com/rommapp/romm.git
synced 2026-07-01 08:16:21 +00:00
Merge pull request #3431 from rommapp/copilot/fix-fullscreen-emulation-ios
Add iOS pseudo-fullscreen shim for EmulatorJS player
This commit is contained in:
@@ -20,6 +20,7 @@ import type { Events } from "@/types/emitter";
|
||||
import { getSupportedEJSCores } from "@/utils";
|
||||
import CacheDialog from "@/views/Player/EmulatorJS/CacheDialog.vue";
|
||||
import Player from "@/views/Player/EmulatorJS/Player.vue";
|
||||
import { installIOSFullscreenShim } from "./utils";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { xs, mdAndUp, smAndDown } = useDisplay();
|
||||
@@ -38,6 +39,7 @@ const selectedDisc = ref<number | null>(null);
|
||||
const selectedCore = ref<string | null>(null);
|
||||
const selectedFirmware = ref<FirmwareSchema | null>(null);
|
||||
const supportedCores = ref<string[]>([]);
|
||||
const removeIOSFullscreenShim = ref<(() => void) | null>(null);
|
||||
const gameRunning = ref(false);
|
||||
const fullScreenOnPlay = useLocalStorage("emulation.fullScreenOnPlay", true);
|
||||
|
||||
@@ -58,6 +60,9 @@ const compatibleStates = computed(
|
||||
);
|
||||
|
||||
async function onPlay() {
|
||||
removeIOSFullscreenShim.value?.();
|
||||
removeIOSFullscreenShim.value = installIOSFullscreenShim();
|
||||
|
||||
if (rom.value && auth.scopes.includes("roms.user.write")) {
|
||||
romApi.updateUserRomProps({
|
||||
romId: rom.value.id,
|
||||
@@ -102,6 +107,8 @@ async function onPlay() {
|
||||
playing.value = true;
|
||||
fullScreen.value = fullScreenOnPlay.value;
|
||||
} catch (err) {
|
||||
removeIOSFullscreenShim.value?.();
|
||||
removeIOSFullscreenShim.value = null;
|
||||
console.error("[Play] Emulator load failure:", err);
|
||||
}
|
||||
}
|
||||
@@ -261,6 +268,8 @@ onMounted(async () => {
|
||||
|
||||
onBeforeUnmount(async () => {
|
||||
window.EJS_emulator?.callEvent("exit");
|
||||
removeIOSFullscreenShim.value?.();
|
||||
removeIOSFullscreenShim.value = null;
|
||||
emitter?.off("saveSelected", selectSave);
|
||||
emitter?.off("stateSelected", selectState);
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Bowser from "bowser";
|
||||
import { type SaveSchema } from "@/__generated__";
|
||||
import { type StateSchema } from "@/__generated__";
|
||||
import saveApi from "@/services/api/save";
|
||||
@@ -145,6 +146,116 @@ export function loadEmulatorJSState(state: Uint8Array) {
|
||||
window.EJS_emulator.gameManager.loadState(state);
|
||||
}
|
||||
|
||||
const IOS_FULLSCREEN_NAV_SELECTOR =
|
||||
".v-app-bar, .v-bottom-navigation, .v-navigation-drawer";
|
||||
const IOS_FULLSCREEN_STYLE = `
|
||||
[data-ios-fullscreen-active] {
|
||||
position: fixed !important;
|
||||
inset: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100svh !important;
|
||||
z-index: 99999 !important;
|
||||
background: #000 !important;
|
||||
}
|
||||
[data-ios-fullscreen-hidden] { display: none !important; }
|
||||
`;
|
||||
|
||||
function isIOSFullscreenShimRequired() {
|
||||
const osName = Bowser.getParser(navigator.userAgent).getOSName(true);
|
||||
return (
|
||||
osName === "ios" ||
|
||||
// iPadOS 13+ reports as macOS with touch support, so fall back to that check.
|
||||
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1)
|
||||
);
|
||||
}
|
||||
|
||||
export function installIOSFullscreenShim() {
|
||||
if (!isIOSFullscreenShimRequired()) {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
const proto = HTMLElement.prototype;
|
||||
const overrides: Array<{
|
||||
target: object;
|
||||
key: PropertyKey;
|
||||
prev?: PropertyDescriptor;
|
||||
}> = [];
|
||||
const override = (
|
||||
target: object,
|
||||
key: PropertyKey,
|
||||
descriptor: PropertyDescriptor,
|
||||
) => {
|
||||
overrides.push({
|
||||
target,
|
||||
key,
|
||||
prev: Object.getOwnPropertyDescriptor(target, key),
|
||||
});
|
||||
Object.defineProperty(target, key, { configurable: true, ...descriptor });
|
||||
};
|
||||
|
||||
const styleEl = document.createElement("style");
|
||||
styleEl.textContent = IOS_FULLSCREEN_STYLE;
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
let fullscreenElement: HTMLElement | null = null;
|
||||
|
||||
const dispatchChange = (target: HTMLElement) => {
|
||||
document.dispatchEvent(new Event("fullscreenchange"));
|
||||
target.dispatchEvent(new Event("fullscreenchange"));
|
||||
};
|
||||
|
||||
const enter = (el: HTMLElement) => {
|
||||
if (fullscreenElement === el) return Promise.resolve();
|
||||
if (fullscreenElement) void exit();
|
||||
|
||||
el.setAttribute("data-ios-fullscreen-active", "");
|
||||
document
|
||||
.querySelectorAll<HTMLElement>(IOS_FULLSCREEN_NAV_SELECTOR)
|
||||
.forEach((nav) => nav.setAttribute("data-ios-fullscreen-hidden", ""));
|
||||
fullscreenElement = el;
|
||||
dispatchChange(el);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const exit = () => {
|
||||
const el = fullscreenElement;
|
||||
if (!el) return Promise.resolve();
|
||||
el.removeAttribute("data-ios-fullscreen-active");
|
||||
document
|
||||
.querySelectorAll<HTMLElement>("[data-ios-fullscreen-hidden]")
|
||||
.forEach((nav) => nav.removeAttribute("data-ios-fullscreen-hidden"));
|
||||
fullscreenElement = null;
|
||||
dispatchChange(el);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
override(document, "fullscreenEnabled", { get: () => true });
|
||||
override(document, "fullscreenElement", { get: () => fullscreenElement });
|
||||
override(document, "exitFullscreen", { value: exit, writable: true });
|
||||
override(proto, "requestFullscreen", {
|
||||
value: function (this: HTMLElement) {
|
||||
return enter(this);
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
override(proto, "webkitRequestFullscreen", {
|
||||
value: function (this: HTMLElement) {
|
||||
void enter(this);
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
|
||||
return () => {
|
||||
void exit();
|
||||
styleEl.remove();
|
||||
while (overrides.length) {
|
||||
const { target, key, prev } = overrides.pop()!;
|
||||
if (prev) Object.defineProperty(target, key, prev);
|
||||
else Reflect.deleteProperty(target, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function createQuickLoadButton(): HTMLButtonElement {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
|
||||
Reference in New Issue
Block a user