From 8f6bc5cb1b5ba9f2624b8ee860b30c48168c8193 Mon Sep 17 00:00:00 2001
From: redphx <96280+redphx@users.noreply.github.com>
Date: Tue, 1 Oct 2024 17:22:33 +0700
Subject: [PATCH] Detach VIRTUAL_GAMEPAD_ID from EmulatedMkbHandler
---
dist/better-xcloud.lite.user.js | 1340 ++++++++------------
dist/better-xcloud.user.js | 1161 +++++++++--------
src/modules/controller-shortcut.ts | 4 +-
src/modules/mkb/mkb-handler.ts | 5 +-
src/modules/ui/dialog/navigation-dialog.ts | 4 +-
src/modules/ui/dialog/settings-dialog.ts | 2 +-
src/utils/gamepad.ts | 4 +-
src/utils/xhome-interceptor.ts | 2 -
8 files changed, 1109 insertions(+), 1413 deletions(-)
diff --git a/dist/better-xcloud.lite.user.js b/dist/better-xcloud.lite.user.js
index 3488c43..d473586 100644
--- a/dist/better-xcloud.lite.user.js
+++ b/dist/better-xcloud.lite.user.js
@@ -1732,6 +1732,208 @@ var MouseMapTo;
MouseMapTo2[MouseMapTo2.LS = 1] = "LS";
MouseMapTo2[MouseMapTo2.RS = 2] = "RS";
})(MouseMapTo ||= {});
+class Toast {
+ static #$wrapper;
+ static #$msg;
+ static #$status;
+ static #stack = [];
+ static #isShowing = !1;
+ static #timeout;
+ static #DURATION = 3000;
+ static show(msg, status, options = {}) {
+ options = options || {};
+ const args = Array.from(arguments);
+ if (options.instant) Toast.#stack = [args], Toast.#showNext();
+ else Toast.#stack.push(args), !Toast.#isShowing && Toast.#showNext();
+ }
+ static #showNext() {
+ if (!Toast.#stack.length) {
+ Toast.#isShowing = !1;
+ return;
+ }
+ Toast.#isShowing = !0, Toast.#timeout && clearTimeout(Toast.#timeout), Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
+ const [msg, status, options] = Toast.#stack.shift();
+ if (options && options.html) Toast.#$msg.innerHTML = msg;
+ else Toast.#$msg.textContent = msg;
+ if (status) Toast.#$status.classList.remove("bx-gone"), Toast.#$status.textContent = status;
+ else Toast.#$status.classList.add("bx-gone");
+ const classList = Toast.#$wrapper.classList;
+ classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-show");
+ }
+ static #hide() {
+ Toast.#timeout = null;
+ const classList = Toast.#$wrapper.classList;
+ classList.remove("bx-show"), classList.add("bx-hide");
+ }
+ static setup() {
+ Toast.#$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.#$msg = CE("span", { class: "bx-toast-msg" }), Toast.#$status = CE("span", { class: "bx-toast-status" })), Toast.#$wrapper.addEventListener("transitionend", (e) => {
+ const classList = Toast.#$wrapper.classList;
+ if (classList.contains("bx-hide")) classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-offscreen"), Toast.#showNext();
+ }), document.documentElement.appendChild(Toast.#$wrapper);
+ }
+}
+function ceilToNearest(value, interval) {
+ return Math.ceil(value / interval) * interval;
+}
+function floorToNearest(value, interval) {
+ return Math.floor(value / interval) * interval;
+}
+async function copyToClipboard(text, showToast = !0) {
+ try {
+ return await navigator.clipboard.writeText(text), showToast && Toast.show("Copied to clipboard", "", { instant: !0 }), !0;
+ } catch (err) {
+ console.error("Failed to copy: ", err), showToast && Toast.show("Failed to copy", "", { instant: !0 });
+ }
+ return !1;
+}
+function productTitleToSlug(title) {
+ return title.replace(/[;,/?:@&=+_`~$%#^*()!^™\xae\xa9]/g, "").replace(/\|/g, "-").replace(/ {2,}/g, " ").trim().substr(0, 50).replace(/ /g, "-").toLowerCase();
+}
+class SoundShortcut {
+ static adjustGainNodeVolume(amount) {
+ if (!getPref("audio_enable_volume_control")) return 0;
+ const currentValue = getPref("audio_volume");
+ let nearestValue;
+ if (amount > 0) nearestValue = ceilToNearest(currentValue, amount);
+ else nearestValue = floorToNearest(currentValue, -1 * amount);
+ let newValue;
+ if (currentValue !== nearestValue) newValue = nearestValue;
+ else newValue = currentValue + amount;
+ return newValue = setPref("audio_volume", newValue, !0), SoundShortcut.setGainNodeVolume(newValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, newValue + "%", { instant: !0 }), newValue;
+ }
+ static setGainNodeVolume(value) {
+ STATES.currentStream.audioGainNode && (STATES.currentStream.audioGainNode.gain.value = value / 100);
+ }
+ static muteUnmute() {
+ if (getPref("audio_enable_volume_control") && STATES.currentStream.audioGainNode) {
+ const gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume");
+ let targetValue;
+ if (settingValue === 0) targetValue = 100, setPref("audio_volume", targetValue, !0);
+ else if (gainValue === 0) targetValue = settingValue;
+ else targetValue = 0;
+ let status;
+ if (targetValue === 0) status = t("muted");
+ else status = targetValue + "%";
+ SoundShortcut.setGainNodeVolume(targetValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
+ speakerState: targetValue === 0 ? 1 : 0
+ });
+ return;
+ }
+ let $media;
+ if ($media = document.querySelector("div[data-testid=media-container] audio"), !$media) $media = document.querySelector("div[data-testid=media-container] video");
+ if ($media) {
+ $media.muted = !$media.muted;
+ const status = $media.muted ? t("muted") : t("unmuted");
+ Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
+ speakerState: $media.muted ? 1 : 0
+ });
+ }
+ }
+}
+class BxSelectElement {
+ static wrap($select) {
+ $select.removeAttribute("tabindex");
+ const $btnPrev = createButton({
+ label: "<",
+ style: 32
+ }), $btnNext = createButton({
+ label: ">",
+ style: 32
+ }), isMultiple = $select.multiple;
+ let $checkBox, $label, visibleIndex = $select.selectedIndex, $content;
+ if (isMultiple) $content = CE("button", {
+ class: "bx-select-value bx-focusable",
+ tabindex: 0
+ }, $checkBox = CE("input", { type: "checkbox" }), $label = CE("span", {}, "")), $content.addEventListener("click", (e) => {
+ $checkBox.click();
+ }), $checkBox.addEventListener("input", (e) => {
+ const $option = getOptionAtIndex(visibleIndex);
+ $option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");
+ });
+ else $content = CE("div", {}, $label = CE("label", { for: $select.id + "_checkbox" }, ""));
+ const getOptionAtIndex = (index) => {
+ return Array.from($select.querySelectorAll("option"))[index];
+ }, render = (e) => {
+ if (e && e.manualTrigger) visibleIndex = $select.selectedIndex;
+ visibleIndex = normalizeIndex(visibleIndex);
+ const $option = getOptionAtIndex(visibleIndex);
+ let content = "";
+ if ($option) if (content = $option.textContent || "", content && $option.parentElement.tagName === "OPTGROUP") {
+ $label.innerHTML = "";
+ const fragment = document.createDocumentFragment();
+ fragment.appendChild(CE("span", {}, $option.parentElement.label)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment);
+ } else $label.textContent = content;
+ else $label.textContent = content;
+ if ($label.classList.toggle("bx-line-through", $option && $option.disabled), isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);
+ const disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1;
+ $btnPrev.classList.toggle("bx-inactive", disablePrev), $btnNext.classList.toggle("bx-inactive", disableNext), disablePrev && !disableNext && document.activeElement === $btnPrev && $btnNext.focus(), disableNext && !disablePrev && document.activeElement === $btnNext && $btnPrev.focus();
+ }, normalizeIndex = (index) => {
+ return Math.min(Math.max(index, 0), $select.querySelectorAll("option").length - 1);
+ }, onPrevNext = (e) => {
+ if (!e.target) return;
+ const goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex;
+ let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
+ if (newIndex = normalizeIndex(newIndex), visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex;
+ if (isMultiple) render();
+ else BxEvent.dispatch($select, "input");
+ };
+ $select.addEventListener("input", render), $btnPrev.addEventListener("click", onPrevNext), $btnNext.addEventListener("click", onPrevNext), new MutationObserver((mutationList, observer2) => {
+ mutationList.forEach((mutation) => {
+ if (mutation.type === "childList" || mutation.type === "attributes") render();
+ });
+ }).observe($select, {
+ subtree: !0,
+ childList: !0,
+ attributes: !0
+ }), render();
+ const $div = CE("div", {
+ class: "bx-select",
+ _nearby: {
+ orientation: "horizontal",
+ focus: $btnNext
+ }
+ }, $select, $btnPrev, $content, $btnNext);
+ return Object.defineProperty($div, "value", {
+ get() {
+ return $select.value;
+ },
+ set(value) {
+ $div.setValue(value);
+ }
+ }), $div.addEventListener = function() {
+ $select.addEventListener.apply($select, arguments);
+ }, $div.removeEventListener = function() {
+ $select.removeEventListener.apply($select, arguments);
+ }, $div.dispatchEvent = function() {
+ return $select.dispatchEvent.apply($select, arguments);
+ }, $div.setValue = (value) => {
+ if ("setValue" in $select) $select.setValue(value);
+ else $select.value = value;
+ }, $div;
+ }
+}
+function onChangeVideoPlayerType() {
+ const playerType = getPref("video_player_type"), $videoProcessing = document.getElementById("bx_setting_video_processing"), $videoSharpness = document.getElementById("bx_setting_video_sharpness"), $videoPowerPreference = document.getElementById("bx_setting_video_power_preference");
+ if (!$videoProcessing) return;
+ let isDisabled = !1;
+ const $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`);
+ if (playerType === "webgl2") $optCas && ($optCas.disabled = !1);
+ else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0;
+ $videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), updateVideoPlayer();
+}
+function updateVideoPlayer() {
+ const streamPlayer = STATES.currentStream.streamPlayer;
+ if (!streamPlayer) return;
+ const options = {
+ processing: getPref("video_processing"),
+ sharpness: getPref("video_sharpness"),
+ saturation: getPref("video_saturation"),
+ contrast: getPref("video_contrast"),
+ brightness: getPref("video_brightness")
+ };
+ streamPlayer.setPlayerType(getPref("video_player_type")), streamPlayer.updateOptions(options), streamPlayer.refreshPlayer();
+}
+window.addEventListener("resize", updateVideoPlayer);
class MkbPreset {
static MOUSE_SETTINGS = {
map_to: {
@@ -1829,46 +2031,6 @@ class MkbPreset {
return console.log(obj), obj;
}
}
-class Toast {
- static #$wrapper;
- static #$msg;
- static #$status;
- static #stack = [];
- static #isShowing = !1;
- static #timeout;
- static #DURATION = 3000;
- static show(msg, status, options = {}) {
- options = options || {};
- const args = Array.from(arguments);
- if (options.instant) Toast.#stack = [args], Toast.#showNext();
- else Toast.#stack.push(args), !Toast.#isShowing && Toast.#showNext();
- }
- static #showNext() {
- if (!Toast.#stack.length) {
- Toast.#isShowing = !1;
- return;
- }
- Toast.#isShowing = !0, Toast.#timeout && clearTimeout(Toast.#timeout), Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
- const [msg, status, options] = Toast.#stack.shift();
- if (options && options.html) Toast.#$msg.innerHTML = msg;
- else Toast.#$msg.textContent = msg;
- if (status) Toast.#$status.classList.remove("bx-gone"), Toast.#$status.textContent = status;
- else Toast.#$status.classList.add("bx-gone");
- const classList = Toast.#$wrapper.classList;
- classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-show");
- }
- static #hide() {
- Toast.#timeout = null;
- const classList = Toast.#$wrapper.classList;
- classList.remove("bx-show"), classList.add("bx-hide");
- }
- static setup() {
- Toast.#$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.#$msg = CE("span", { class: "bx-toast-msg" }), Toast.#$status = CE("span", { class: "bx-toast-status" })), Toast.#$wrapper.addEventListener("transitionend", (e) => {
- const classList = Toast.#$wrapper.classList;
- if (classList.contains("bx-hide")) classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-offscreen"), Toast.#showNext();
- }), document.documentElement.appendChild(Toast.#$wrapper);
- }
-}
class LocalDb {
static #instance;
static get INSTANCE() {
@@ -2229,28 +2391,324 @@ class NativeMkbHandler extends MkbHandler {
});
}
}
-function onChangeVideoPlayerType() {
- const playerType = getPref("video_player_type"), $videoProcessing = document.getElementById("bx_setting_video_processing"), $videoSharpness = document.getElementById("bx_setting_video_sharpness"), $videoPowerPreference = document.getElementById("bx_setting_video_power_preference");
- if (!$videoProcessing) return;
- let isDisabled = !1;
- const $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`);
- if (playerType === "webgl2") $optCas && ($optCas.disabled = !1);
- else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0;
- $videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), updateVideoPlayer();
+var LOG_TAG2 = "MkbHandler", PointerToMouseButton = {
+ 1: 0,
+ 2: 2,
+ 4: 1
+}, VIRTUAL_GAMEPAD_ID = "Xbox 360 Controller";
+class WebSocketMouseDataProvider extends MouseDataProvider {
+ #pointerClient;
+ #connected = !1;
+ init() {
+ this.#pointerClient = PointerClient.getInstance(), this.#connected = !1;
+ try {
+ this.#pointerClient.start(STATES.pointerServerPort, this.mkbHandler), this.#connected = !0;
+ } catch (e) {
+ Toast.show("Cannot enable Mouse & Keyboard feature");
+ }
+ }
+ start() {
+ this.#connected && AppInterface.requestPointerCapture();
+ }
+ stop() {
+ this.#connected && AppInterface.releasePointerCapture();
+ }
+ destroy() {
+ this.#connected && this.#pointerClient?.stop();
+ }
}
-function updateVideoPlayer() {
- const streamPlayer = STATES.currentStream.streamPlayer;
- if (!streamPlayer) return;
- const options = {
- processing: getPref("video_processing"),
- sharpness: getPref("video_sharpness"),
- saturation: getPref("video_saturation"),
- contrast: getPref("video_contrast"),
- brightness: getPref("video_brightness")
+class PointerLockMouseDataProvider extends MouseDataProvider {
+ init() {}
+ start() {
+ window.addEventListener("mousemove", this.#onMouseMoveEvent), window.addEventListener("mousedown", this.#onMouseEvent), window.addEventListener("mouseup", this.#onMouseEvent), window.addEventListener("wheel", this.#onWheelEvent, { passive: !1 }), window.addEventListener("contextmenu", this.#disableContextMenu);
+ }
+ stop() {
+ document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("mousemove", this.#onMouseMoveEvent), window.removeEventListener("mousedown", this.#onMouseEvent), window.removeEventListener("mouseup", this.#onMouseEvent), window.removeEventListener("wheel", this.#onWheelEvent), window.removeEventListener("contextmenu", this.#disableContextMenu);
+ }
+ destroy() {}
+ #onMouseMoveEvent = (e) => {
+ this.mkbHandler.handleMouseMove({
+ movementX: e.movementX,
+ movementY: e.movementY
+ });
};
- streamPlayer.setPlayerType(getPref("video_player_type")), streamPlayer.updateOptions(options), streamPlayer.refreshPlayer();
+ #onMouseEvent = (e) => {
+ e.preventDefault();
+ const isMouseDown = e.type === "mousedown", data = {
+ mouseButton: e.button,
+ pressed: isMouseDown
+ };
+ this.mkbHandler.handleMouseClick(data);
+ };
+ #onWheelEvent = (e) => {
+ if (!KeyHelper.getKeyFromEvent(e)) return;
+ const data = {
+ vertical: e.deltaY,
+ horizontal: e.deltaX
+ };
+ if (this.mkbHandler.handleMouseWheel(data)) e.preventDefault();
+ };
+ #disableContextMenu = (e) => e.preventDefault();
+}
+class EmulatedMkbHandler extends MkbHandler {
+ static #instance;
+ static getInstance() {
+ if (!EmulatedMkbHandler.#instance) EmulatedMkbHandler.#instance = new EmulatedMkbHandler;
+ return EmulatedMkbHandler.#instance;
+ }
+ #CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
+ static DEFAULT_PANNING_SENSITIVITY = 0.001;
+ static DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
+ static MAXIMUM_STICK_RANGE = 1.1;
+ #VIRTUAL_GAMEPAD = {
+ id: VIRTUAL_GAMEPAD_ID,
+ index: 3,
+ connected: !1,
+ hapticActuators: null,
+ mapping: "standard",
+ axes: [0, 0, 0, 0],
+ buttons: new Array(17).fill(null).map(() => ({ pressed: !1, value: 0 })),
+ timestamp: performance.now(),
+ vibrationActuator: null
+ };
+ #nativeGetGamepads = window.navigator.getGamepads.bind(window.navigator);
+ #enabled = !1;
+ #mouseDataProvider;
+ #isPolling = !1;
+ #prevWheelCode = null;
+ #wheelStoppedTimeout;
+ #detectMouseStoppedTimeout;
+ #$message;
+ #escKeyDownTime = -1;
+ #STICK_MAP;
+ #LEFT_STICK_X = [];
+ #LEFT_STICK_Y = [];
+ #RIGHT_STICK_X = [];
+ #RIGHT_STICK_Y = [];
+ constructor() {
+ super();
+ this.#STICK_MAP = {
+ 102: [this.#LEFT_STICK_X, 0, -1],
+ 103: [this.#LEFT_STICK_X, 0, 1],
+ 100: [this.#LEFT_STICK_Y, 1, -1],
+ 101: [this.#LEFT_STICK_Y, 1, 1],
+ 202: [this.#RIGHT_STICK_X, 2, -1],
+ 203: [this.#RIGHT_STICK_X, 2, 1],
+ 200: [this.#RIGHT_STICK_Y, 3, -1],
+ 201: [this.#RIGHT_STICK_Y, 3, 1]
+ };
+ }
+ isEnabled = () => this.#enabled;
+ #patchedGetGamepads = () => {
+ const gamepads = this.#nativeGetGamepads() || [];
+ return gamepads[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD, gamepads;
+ };
+ #getVirtualGamepad = () => this.#VIRTUAL_GAMEPAD;
+ #updateStick(stick, x, y) {
+ const virtualGamepad = this.#getVirtualGamepad();
+ virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now();
+ }
+ #vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2);
+ #resetGamepad = () => {
+ const gamepad = this.#getVirtualGamepad();
+ gamepad.axes = [0, 0, 0, 0];
+ for (let button of gamepad.buttons)
+ button.pressed = !1, button.value = 0;
+ gamepad.timestamp = performance.now();
+ };
+ #pressButton = (buttonIndex, pressed) => {
+ const virtualGamepad = this.#getVirtualGamepad();
+ if (buttonIndex >= 100) {
+ let [valueArr, axisIndex] = this.#STICK_MAP[buttonIndex];
+ valueArr = valueArr, axisIndex = axisIndex;
+ for (let i = valueArr.length - 1;i >= 0; i--)
+ if (valueArr[i] === buttonIndex) valueArr.splice(i, 1);
+ pressed && valueArr.push(buttonIndex);
+ let value;
+ if (valueArr.length) value = this.#STICK_MAP[valueArr[valueArr.length - 1]][2];
+ else value = 0;
+ virtualGamepad.axes[axisIndex] = value;
+ } else virtualGamepad.buttons[buttonIndex].pressed = pressed, virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0;
+ virtualGamepad.timestamp = performance.now();
+ };
+ #onKeyboardEvent = (e) => {
+ const isKeyDown = e.type === "keydown";
+ if (e.code === "F8") {
+ if (!isKeyDown) e.preventDefault(), this.toggle();
+ return;
+ }
+ if (e.code === "Escape") {
+ if (e.preventDefault(), this.#enabled && isKeyDown) {
+ if (this.#escKeyDownTime === -1) this.#escKeyDownTime = performance.now();
+ else if (performance.now() - this.#escKeyDownTime >= 1000) this.stop();
+ } else this.#escKeyDownTime = -1;
+ return;
+ }
+ if (!this.#isPolling) return;
+ const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key];
+ if (typeof buttonIndex === "undefined") return;
+ if (e.repeat) return;
+ e.preventDefault(), this.#pressButton(buttonIndex, isKeyDown);
+ };
+ #onMouseStopped = () => {
+ this.#detectMouseStoppedTimeout = null;
+ const analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1;
+ this.#updateStick(analog, 0, 0);
+ };
+ handleMouseClick = (data) => {
+ let mouseButton;
+ if (typeof data.mouseButton !== "undefined") mouseButton = data.mouseButton;
+ else if (typeof data.pointerButton !== "undefined") mouseButton = PointerToMouseButton[data.pointerButton];
+ const keyCode = "Mouse" + mouseButton, key = {
+ code: keyCode,
+ name: KeyHelper.codeToKeyName(keyCode)
+ };
+ if (!key.name) return;
+ const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
+ if (typeof buttonIndex === "undefined") return;
+ this.#pressButton(buttonIndex, data.pressed);
+ };
+ handleMouseMove = (data) => {
+ const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"];
+ if (mouseMapTo === 0) return;
+ this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout), this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50);
+ const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"];
+ let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y);
+ if (length !== 0 && length < deadzoneCounterweight) x *= deadzoneCounterweight / length, y *= deadzoneCounterweight / length;
+ else if (length > EmulatedMkbHandler.MAXIMUM_STICK_RANGE) x *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length, y *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length;
+ const analog = mouseMapTo === 1 ? 0 : 1;
+ this.#updateStick(analog, x, y);
+ };
+ handleMouseWheel = (data) => {
+ let code = "";
+ if (data.vertical < 0) code = "ScrollUp";
+ else if (data.vertical > 0) code = "ScrollDown";
+ else if (data.horizontal < 0) code = "ScrollLeft";
+ else if (data.horizontal > 0) code = "ScrollRight";
+ if (!code) return !1;
+ const key = {
+ code,
+ name: KeyHelper.codeToKeyName(code)
+ }, buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
+ if (typeof buttonIndex === "undefined") return !1;
+ if (this.#prevWheelCode === null || this.#prevWheelCode === key.code) this.#wheelStoppedTimeout && clearTimeout(this.#wheelStoppedTimeout), this.#pressButton(buttonIndex, !0);
+ return this.#wheelStoppedTimeout = window.setTimeout(() => {
+ this.#prevWheelCode = null, this.#pressButton(buttonIndex, !1);
+ }, 20), !0;
+ };
+ toggle = (force) => {
+ if (typeof force !== "undefined") this.#enabled = force;
+ else this.#enabled = !this.#enabled;
+ if (this.#enabled) document.body.requestPointerLock();
+ else document.pointerLockElement && document.exitPointerLock();
+ };
+ #getCurrentPreset = () => {
+ return new Promise((resolve) => {
+ const presetId = getPref("mkb_default_preset_id");
+ LocalDb.INSTANCE.getPreset(presetId).then((preset) => {
+ resolve(preset);
+ });
+ });
+ };
+ refreshPresetData = () => {
+ this.#getCurrentPreset().then((preset) => {
+ this.#CURRENT_PRESET_DATA = MkbPreset.convert(preset ? preset.data : MkbPreset.DEFAULT_PRESET), this.#resetGamepad();
+ });
+ };
+ waitForMouseData = (wait) => {
+ this.#$message && this.#$message.classList.toggle("bx-gone", !wait);
+ };
+ #onPollingModeChanged = (e) => {
+ if (!this.#$message) return;
+ if (e.mode === "none") this.#$message.classList.remove("bx-offscreen");
+ else this.#$message.classList.add("bx-offscreen");
+ };
+ #onDialogShown = () => {
+ document.pointerLockElement && document.exitPointerLock();
+ };
+ #initMessage = () => {
+ if (!this.#$message) this.#$message = CE("div", { class: "bx-mkb-pointer-lock-msg bx-gone" }, CE("div", {}, CE("p", {}, t("virtual-controller")), CE("p", {}, t("press-key-to-toggle-mkb", { key: "F8" }))), CE("div", { "data-type": "virtual" }, createButton({
+ style: 1 | 256 | 64,
+ label: t("activate"),
+ onClick: ((e) => {
+ e.preventDefault(), e.stopPropagation(), this.toggle(!0);
+ }).bind(this)
+ }), CE("div", {}, createButton({
+ label: t("ignore"),
+ style: 4,
+ onClick: (e) => {
+ e.preventDefault(), e.stopPropagation(), this.toggle(!1), this.waitForMouseData(!1);
+ }
+ }), createButton({
+ label: t("edit"),
+ onClick: (e) => {
+ e.preventDefault(), e.stopPropagation();
+ const dialog = SettingsNavigationDialog.getInstance();
+ dialog.focusTab("mkb"), NavigationDialogManager.getInstance().show(dialog);
+ }
+ }))));
+ if (!this.#$message.isConnected) document.documentElement.appendChild(this.#$message);
+ };
+ #onPointerLockChange = () => {
+ if (document.pointerLockElement) this.start();
+ else this.stop();
+ };
+ #onPointerLockError = (e) => {
+ console.log(e), this.stop();
+ };
+ #onPointerLockRequested = () => {
+ this.start();
+ };
+ #onPointerLockExited = () => {
+ this.#mouseDataProvider?.stop();
+ };
+ handleEvent(event) {
+ switch (event.type) {
+ case BxEvent.POINTER_LOCK_REQUESTED:
+ this.#onPointerLockRequested();
+ break;
+ case BxEvent.POINTER_LOCK_EXITED:
+ this.#onPointerLockExited();
+ break;
+ }
+ }
+ init = () => {
+ if (this.refreshPresetData(), this.#enabled = !1, AppInterface) this.#mouseDataProvider = new WebSocketMouseDataProvider(this);
+ else this.#mouseDataProvider = new PointerLockMouseDataProvider(this);
+ if (this.#mouseDataProvider.init(), window.addEventListener("keydown", this.#onKeyboardEvent), window.addEventListener("keyup", this.#onKeyboardEvent), window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), AppInterface) window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
+ else document.addEventListener("pointerlockchange", this.#onPointerLockChange), document.addEventListener("pointerlockerror", this.#onPointerLockError);
+ if (this.#initMessage(), this.#$message?.classList.add("bx-gone"), AppInterface) Toast.show(t("press-key-to-toggle-mkb", { key: "F8" }), t("virtual-controller"), { html: !0 }), this.waitForMouseData(!1);
+ else this.waitForMouseData(!0);
+ };
+ destroy = () => {
+ if (this.#isPolling = !1, this.#enabled = !1, this.stop(), this.waitForMouseData(!1), document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("keydown", this.#onKeyboardEvent), window.removeEventListener("keyup", this.#onKeyboardEvent), AppInterface) window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
+ else document.removeEventListener("pointerlockchange", this.#onPointerLockChange), document.removeEventListener("pointerlockerror", this.#onPointerLockError);
+ window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), this.#mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged);
+ };
+ start = () => {
+ if (!this.#enabled) this.#enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
+ this.#isPolling = !0, this.#escKeyDownTime = -1, this.#resetGamepad(), window.navigator.getGamepads = this.#patchedGetGamepads, this.waitForMouseData(!1), this.#mouseDataProvider?.start();
+ const virtualGamepad = this.#getVirtualGamepad();
+ virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", {
+ gamepad: virtualGamepad
+ }), window.BX_EXPOSED.stopTakRendering = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
+ };
+ stop = () => {
+ this.#enabled = !1, this.#isPolling = !1, this.#escKeyDownTime = -1;
+ const virtualGamepad = this.#getVirtualGamepad();
+ if (virtualGamepad.connected) this.#resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", {
+ gamepad: virtualGamepad
+ }), window.navigator.getGamepads = this.#nativeGetGamepads;
+ this.waitForMouseData(!0), this.#mouseDataProvider?.stop();
+ };
+ static setupEvents() {
+ window.addEventListener(BxEvent.STREAM_PLAYING, () => {
+ if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
+ if (AppInterface && getPref("native_mkb_enabled") === "on") AppInterface && NativeMkbHandler.getInstance().init();
+ } else if (getPref("mkb_enabled") && (AppInterface || !UserAgent.isMobile())) BxLogger.info(LOG_TAG2, "Emulate MKB"), EmulatedMkbHandler.getInstance().init();
+ });
+ }
}
-window.addEventListener("resize", updateVideoPlayer);
class NavigationDialog {
dialogManager;
constructor() {
@@ -2376,7 +2834,7 @@ class NavigationDialogManager {
const gamepads = window.navigator.getGamepads();
for (let gamepad of gamepads) {
if (!gamepad || !gamepad.connected) continue;
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) continue;
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue;
const { axes, buttons } = gamepad;
let releasedButton = null, heldButton = null, lastState = this.gamepadLastStates[gamepad.index], lastTimestamp, lastKey, lastKeyPressed;
if (lastState) [lastTimestamp, lastKey, lastKeyPressed] = lastState;
@@ -2568,363 +3026,6 @@ var BxIcon = {
UPLOAD: "",
AUDIO: ""
};
-class Dialog {
- $dialog;
- $title;
- $content;
- $overlay;
- onClose;
- constructor(options) {
- const {
- title,
- className,
- content,
- hideCloseButton,
- onClose,
- helpUrl
- } = options, $overlay = document.querySelector(".bx-dialog-overlay");
- if (!$overlay) this.$overlay = CE("div", { class: "bx-dialog-overlay bx-gone" }), this.$overlay.addEventListener("contextmenu", (e) => e.preventDefault()), document.documentElement.appendChild(this.$overlay);
- else this.$overlay = $overlay;
- let $close;
- this.onClose = onClose, this.$dialog = CE("div", { class: `bx-dialog ${className || ""} bx-gone` }, this.$title = CE("h2", {}, CE("b", {}, title), helpUrl && createButton({
- icon: BxIcon.QUESTION,
- style: 4,
- title: t("help"),
- url: helpUrl
- })), this.$content = CE("div", { class: "bx-dialog-content" }, content), !hideCloseButton && ($close = CE("button", { type: "button" }, t("close")))), $close && $close.addEventListener("click", (e) => {
- this.hide(e);
- }), !title && this.$title.classList.add("bx-gone"), !content && this.$content.classList.add("bx-gone"), this.$dialog.addEventListener("contextmenu", (e) => e.preventDefault()), document.documentElement.appendChild(this.$dialog);
- }
- show(newOptions) {
- if (document.activeElement && document.activeElement.blur(), newOptions && newOptions.title) this.$title.querySelector("b").textContent = newOptions.title, this.$title.classList.remove("bx-gone");
- this.$dialog.classList.remove("bx-gone"), this.$overlay.classList.remove("bx-gone"), document.body.classList.add("bx-no-scroll");
- }
- hide(e) {
- this.$dialog.classList.add("bx-gone"), this.$overlay.classList.add("bx-gone"), document.body.classList.remove("bx-no-scroll"), this.onClose && this.onClose(e);
- }
- toggle() {
- this.$dialog.classList.toggle("bx-gone"), this.$overlay.classList.toggle("bx-gone");
- }
-}
-class MkbRemapper {
- #BUTTON_ORDERS = [
- 12,
- 13,
- 14,
- 15,
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 16,
- 10,
- 100,
- 101,
- 102,
- 103,
- 11,
- 200,
- 201,
- 202,
- 203
- ];
- static #instance;
- static get INSTANCE() {
- if (!MkbRemapper.#instance) MkbRemapper.#instance = new MkbRemapper;
- return MkbRemapper.#instance;
- }
- #STATE = {
- currentPresetId: 0,
- presets: {},
- editingPresetData: null,
- isEditing: !1
- };
- #$ = {
- wrapper: null,
- presetsSelect: null,
- activateButton: null,
- currentBindingKey: null,
- allKeyElements: [],
- allMouseElements: {}
- };
- bindingDialog;
- constructor() {
- this.#STATE.currentPresetId = getPref("mkb_default_preset_id"), this.bindingDialog = new Dialog({
- className: "bx-binding-dialog",
- content: CE("div", {}, CE("p", {}, t("press-to-bind")), CE("i", {}, t("press-esc-to-cancel"))),
- hideCloseButton: !0
- });
- }
- #clearEventListeners = () => {
- window.removeEventListener("keydown", this.#onKeyDown), window.removeEventListener("mousedown", this.#onMouseDown), window.removeEventListener("wheel", this.#onWheel);
- };
- #bindKey = ($elm, key) => {
- const buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot"));
- if ($elm.getAttribute("data-key-code") === key.code) return;
- for (let $otherElm of this.#$.allKeyElements)
- if ($otherElm.getAttribute("data-key-code") === key.code) this.#unbindKey($otherElm);
- this.#STATE.editingPresetData.mapping[buttonIndex][keySlot] = key.code, $elm.textContent = key.name, $elm.setAttribute("data-key-code", key.code);
- };
- #unbindKey = ($elm) => {
- const buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot"));
- this.#STATE.editingPresetData.mapping[buttonIndex][keySlot] = null, $elm.textContent = "", $elm.removeAttribute("data-key-code");
- };
- #onWheel = (e) => {
- e.preventDefault(), this.#clearEventListeners(), this.#bindKey(this.#$.currentBindingKey, KeyHelper.getKeyFromEvent(e)), window.setTimeout(() => this.bindingDialog.hide(), 200);
- };
- #onMouseDown = (e) => {
- e.preventDefault(), this.#clearEventListeners(), this.#bindKey(this.#$.currentBindingKey, KeyHelper.getKeyFromEvent(e)), window.setTimeout(() => this.bindingDialog.hide(), 200);
- };
- #onKeyDown = (e) => {
- if (e.preventDefault(), e.stopPropagation(), this.#clearEventListeners(), e.code !== "Escape") this.#bindKey(this.#$.currentBindingKey, KeyHelper.getKeyFromEvent(e));
- window.setTimeout(() => this.bindingDialog.hide(), 200);
- };
- #onBindingKey = (e) => {
- if (!this.#STATE.isEditing || e.button !== 0) return;
- console.log(e), this.#$.currentBindingKey = e.target, window.addEventListener("keydown", this.#onKeyDown), window.addEventListener("mousedown", this.#onMouseDown), window.addEventListener("wheel", this.#onWheel), this.bindingDialog.show({ title: this.#$.currentBindingKey.getAttribute("data-prompt") });
- };
- #onContextMenu = (e) => {
- if (e.preventDefault(), !this.#STATE.isEditing) return;
- this.#unbindKey(e.target);
- };
- #getPreset = (presetId) => {
- return this.#STATE.presets[presetId];
- };
- #getCurrentPreset = () => {
- return this.#getPreset(this.#STATE.currentPresetId);
- };
- #switchPreset = (presetId) => {
- this.#STATE.currentPresetId = presetId;
- const presetData = this.#getCurrentPreset().data;
- for (let $elm of this.#$.allKeyElements) {
- const buttonIndex = parseInt($elm.getAttribute("data-button-index")), keySlot = parseInt($elm.getAttribute("data-key-slot")), buttonKeys = presetData.mapping[buttonIndex];
- if (buttonKeys && buttonKeys[keySlot]) $elm.textContent = KeyHelper.codeToKeyName(buttonKeys[keySlot]), $elm.setAttribute("data-key-code", buttonKeys[keySlot]);
- else $elm.textContent = "", $elm.removeAttribute("data-key-code");
- }
- let key;
- for (key in this.#$.allMouseElements) {
- const $elm = this.#$.allMouseElements[key];
- let value = presetData.mouse[key];
- if (typeof value === "undefined") value = MkbPreset.MOUSE_SETTINGS[key].default;
- "setValue" in $elm && $elm.setValue(value);
- }
- const activated = getPref("mkb_default_preset_id") === this.#STATE.currentPresetId;
- this.#$.activateButton.disabled = activated, this.#$.activateButton.querySelector("span").textContent = activated ? t("activated") : t("activate");
- };
- #refresh() {
- while (this.#$.presetsSelect.firstChild)
- this.#$.presetsSelect.removeChild(this.#$.presetsSelect.firstChild);
- LocalDb.INSTANCE.getPresets().then((presets) => {
- this.#STATE.presets = presets;
- const $fragment = document.createDocumentFragment();
- let defaultPresetId;
- if (this.#STATE.currentPresetId === 0) this.#STATE.currentPresetId = parseInt(Object.keys(presets)[0]), defaultPresetId = this.#STATE.currentPresetId, setPref("mkb_default_preset_id", defaultPresetId), EmulatedMkbHandler.getInstance().refreshPresetData();
- else defaultPresetId = getPref("mkb_default_preset_id");
- for (let id in presets) {
- let name = presets[id].name;
- if (id === defaultPresetId) name = "🎮 " + name;
- const $options = CE("option", { value: id }, name);
- $options.selected = parseInt(id) === this.#STATE.currentPresetId, $fragment.appendChild($options);
- }
- this.#$.presetsSelect.appendChild($fragment);
- const activated = defaultPresetId === this.#STATE.currentPresetId;
- this.#$.activateButton.disabled = activated, this.#$.activateButton.querySelector("span").textContent = activated ? t("activated") : t("activate"), !this.#STATE.isEditing && this.#switchPreset(this.#STATE.currentPresetId);
- });
- }
- #toggleEditing = (force) => {
- if (this.#STATE.isEditing = typeof force !== "undefined" ? force : !this.#STATE.isEditing, this.#$.wrapper.classList.toggle("bx-editing", this.#STATE.isEditing), this.#STATE.isEditing) this.#STATE.editingPresetData = deepClone(this.#getCurrentPreset().data);
- else this.#STATE.editingPresetData = null;
- const childElements = this.#$.wrapper.querySelectorAll("select, button, input");
- for (let $elm of Array.from(childElements)) {
- if ($elm.parentElement.parentElement.classList.contains("bx-mkb-action-buttons")) continue;
- let disable = !this.#STATE.isEditing;
- if ($elm.parentElement.classList.contains("bx-mkb-preset-tools")) disable = !disable;
- $elm.disabled = disable;
- }
- };
- render() {
- this.#$.wrapper = CE("div", { class: "bx-mkb-settings" }), this.#$.presetsSelect = CE("select", { tabindex: -1 }), this.#$.presetsSelect.addEventListener("change", (e) => {
- this.#switchPreset(parseInt(e.target.value));
- });
- const promptNewName = (value) => {
- let newName = "";
- while (!newName) {
- if (newName = prompt(t("prompt-preset-name"), value), newName === null) return !1;
- newName = newName.trim();
- }
- return newName ? newName : !1;
- }, $header = CE("div", { class: "bx-mkb-preset-tools" }, this.#$.presetsSelect, createButton({
- title: t("rename"),
- icon: BxIcon.CURSOR_TEXT,
- tabIndex: -1,
- onClick: (e) => {
- const preset = this.#getCurrentPreset();
- let newName = promptNewName(preset.name);
- if (!newName || newName === preset.name) return;
- preset.name = newName, LocalDb.INSTANCE.updatePreset(preset).then((id) => this.#refresh());
- }
- }), createButton({
- icon: BxIcon.NEW,
- title: t("new"),
- tabIndex: -1,
- onClick: (e) => {
- let newName = promptNewName("");
- if (!newName) return;
- LocalDb.INSTANCE.newPreset(newName, MkbPreset.DEFAULT_PRESET).then((id) => {
- this.#STATE.currentPresetId = id, this.#refresh();
- });
- }
- }), createButton({
- icon: BxIcon.COPY,
- title: t("copy"),
- tabIndex: -1,
- onClick: (e) => {
- const preset = this.#getCurrentPreset();
- let newName = promptNewName(`${preset.name} (2)`);
- if (!newName) return;
- LocalDb.INSTANCE.newPreset(newName, preset.data).then((id) => {
- this.#STATE.currentPresetId = id, this.#refresh();
- });
- }
- }), createButton({
- icon: BxIcon.TRASH,
- style: 2,
- title: t("delete"),
- tabIndex: -1,
- onClick: (e) => {
- if (!confirm(t("confirm-delete-preset"))) return;
- LocalDb.INSTANCE.deletePreset(this.#STATE.currentPresetId).then((id) => {
- this.#STATE.currentPresetId = 0, this.#refresh();
- });
- }
- }));
- this.#$.wrapper.appendChild($header);
- const $rows = CE("div", { class: "bx-mkb-settings-rows" }, CE("i", { class: "bx-mkb-note" }, t("right-click-to-unbind"))), keysPerButton = 2;
- for (let buttonIndex of this.#BUTTON_ORDERS) {
- const [buttonName, buttonPrompt] = GamepadKeyName[buttonIndex];
- let $elm;
- const $fragment = document.createDocumentFragment();
- for (let i = 0;i < keysPerButton; i++)
- $elm = CE("button", {
- type: "button",
- "data-prompt": buttonPrompt,
- "data-button-index": buttonIndex,
- "data-key-slot": i
- }, " "), $elm.addEventListener("mouseup", this.#onBindingKey), $elm.addEventListener("contextmenu", this.#onContextMenu), $fragment.appendChild($elm), this.#$.allKeyElements.push($elm);
- const $keyRow = CE("div", { class: "bx-mkb-key-row" }, CE("label", { title: buttonName }, buttonPrompt), $fragment);
- $rows.appendChild($keyRow);
- }
- $rows.appendChild(CE("i", { class: "bx-mkb-note" }, t("mkb-adjust-ingame-settings")));
- const $mouseSettings = document.createDocumentFragment();
- for (let key in MkbPreset.MOUSE_SETTINGS) {
- const setting = MkbPreset.MOUSE_SETTINGS[key], value = setting.default;
- let $elm;
- const onChange = (e, value2) => {
- this.#STATE.editingPresetData.mouse[key] = value2;
- }, $row = CE("label", {
- class: "bx-settings-row",
- for: `bx_setting_${key}`
- }, CE("span", { class: "bx-settings-label" }, setting.label), $elm = SettingElement.render(setting.type, key, setting, value, onChange, setting.params));
- $mouseSettings.appendChild($row), this.#$.allMouseElements[key] = $elm;
- }
- $rows.appendChild($mouseSettings), this.#$.wrapper.appendChild($rows);
- const $actionButtons = CE("div", { class: "bx-mkb-action-buttons" }, CE("div", {}, createButton({
- label: t("edit"),
- tabIndex: -1,
- onClick: (e) => this.#toggleEditing(!0)
- }), this.#$.activateButton = createButton({
- label: t("activate"),
- style: 1,
- tabIndex: -1,
- onClick: (e) => {
- setPref("mkb_default_preset_id", this.#STATE.currentPresetId), EmulatedMkbHandler.getInstance().refreshPresetData(), this.#refresh();
- }
- })), CE("div", {}, createButton({
- label: t("cancel"),
- style: 4,
- tabIndex: -1,
- onClick: (e) => {
- this.#switchPreset(this.#STATE.currentPresetId), this.#toggleEditing(!1);
- }
- }), createButton({
- label: t("save"),
- style: 1,
- tabIndex: -1,
- onClick: (e) => {
- const updatedPreset = deepClone(this.#getCurrentPreset());
- updatedPreset.data = this.#STATE.editingPresetData, LocalDb.INSTANCE.updatePreset(updatedPreset).then((id) => {
- if (id === getPref("mkb_default_preset_id")) EmulatedMkbHandler.getInstance().refreshPresetData();
- this.#toggleEditing(!1), this.#refresh();
- });
- }
- })));
- return this.#$.wrapper.appendChild($actionButtons), this.#toggleEditing(!1), this.#refresh(), this.#$.wrapper;
- }
-}
-function ceilToNearest(value, interval) {
- return Math.ceil(value / interval) * interval;
-}
-function floorToNearest(value, interval) {
- return Math.floor(value / interval) * interval;
-}
-async function copyToClipboard(text, showToast = !0) {
- try {
- return await navigator.clipboard.writeText(text), showToast && Toast.show("Copied to clipboard", "", { instant: !0 }), !0;
- } catch (err) {
- console.error("Failed to copy: ", err), showToast && Toast.show("Failed to copy", "", { instant: !0 });
- }
- return !1;
-}
-function productTitleToSlug(title) {
- return title.replace(/[;,/?:@&=+_`~$%#^*()!^™\xae\xa9]/g, "").replace(/\|/g, "-").replace(/ {2,}/g, " ").trim().substr(0, 50).replace(/ /g, "-").toLowerCase();
-}
-class SoundShortcut {
- static adjustGainNodeVolume(amount) {
- if (!getPref("audio_enable_volume_control")) return 0;
- const currentValue = getPref("audio_volume");
- let nearestValue;
- if (amount > 0) nearestValue = ceilToNearest(currentValue, amount);
- else nearestValue = floorToNearest(currentValue, -1 * amount);
- let newValue;
- if (currentValue !== nearestValue) newValue = nearestValue;
- else newValue = currentValue + amount;
- return newValue = setPref("audio_volume", newValue, !0), SoundShortcut.setGainNodeVolume(newValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, newValue + "%", { instant: !0 }), newValue;
- }
- static setGainNodeVolume(value) {
- STATES.currentStream.audioGainNode && (STATES.currentStream.audioGainNode.gain.value = value / 100);
- }
- static muteUnmute() {
- if (getPref("audio_enable_volume_control") && STATES.currentStream.audioGainNode) {
- const gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume");
- let targetValue;
- if (settingValue === 0) targetValue = 100, setPref("audio_volume", targetValue, !0);
- else if (gainValue === 0) targetValue = settingValue;
- else targetValue = 0;
- let status;
- if (targetValue === 0) status = t("muted");
- else status = targetValue + "%";
- SoundShortcut.setGainNodeVolume(targetValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
- speakerState: targetValue === 0 ? 1 : 0
- });
- return;
- }
- let $media;
- if ($media = document.querySelector("div[data-testid=media-container] audio"), !$media) $media = document.querySelector("div[data-testid=media-container] video");
- if ($media) {
- $media.muted = !$media.muted;
- const status = $media.muted ? t("muted") : t("unmuted");
- Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
- speakerState: $media.muted ? 1 : 0
- });
- }
- }
-}
var VIBRATION_DATA_MAP = {
gamepadIndex: 8,
leftMotorPercent: 8,
@@ -2998,88 +3099,6 @@ class VibrationManager {
});
}
}
-class BxSelectElement {
- static wrap($select) {
- $select.removeAttribute("tabindex");
- const $btnPrev = createButton({
- label: "<",
- style: 32
- }), $btnNext = createButton({
- label: ">",
- style: 32
- }), isMultiple = $select.multiple;
- let $checkBox, $label, visibleIndex = $select.selectedIndex, $content;
- if (isMultiple) $content = CE("button", {
- class: "bx-select-value bx-focusable",
- tabindex: 0
- }, $checkBox = CE("input", { type: "checkbox" }), $label = CE("span", {}, "")), $content.addEventListener("click", (e) => {
- $checkBox.click();
- }), $checkBox.addEventListener("input", (e) => {
- const $option = getOptionAtIndex(visibleIndex);
- $option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");
- });
- else $content = CE("div", {}, $label = CE("label", { for: $select.id + "_checkbox" }, ""));
- const getOptionAtIndex = (index) => {
- return Array.from($select.querySelectorAll("option"))[index];
- }, render = (e) => {
- if (e && e.manualTrigger) visibleIndex = $select.selectedIndex;
- visibleIndex = normalizeIndex(visibleIndex);
- const $option = getOptionAtIndex(visibleIndex);
- let content = "";
- if ($option) if (content = $option.textContent || "", content && $option.parentElement.tagName === "OPTGROUP") {
- $label.innerHTML = "";
- const fragment = document.createDocumentFragment();
- fragment.appendChild(CE("span", {}, $option.parentElement.label)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment);
- } else $label.textContent = content;
- else $label.textContent = content;
- if ($label.classList.toggle("bx-line-through", $option && $option.disabled), isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);
- const disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1;
- $btnPrev.classList.toggle("bx-inactive", disablePrev), $btnNext.classList.toggle("bx-inactive", disableNext), disablePrev && !disableNext && document.activeElement === $btnPrev && $btnNext.focus(), disableNext && !disablePrev && document.activeElement === $btnNext && $btnPrev.focus();
- }, normalizeIndex = (index) => {
- return Math.min(Math.max(index, 0), $select.querySelectorAll("option").length - 1);
- }, onPrevNext = (e) => {
- if (!e.target) return;
- const goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex;
- let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
- if (newIndex = normalizeIndex(newIndex), visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex;
- if (isMultiple) render();
- else BxEvent.dispatch($select, "input");
- };
- $select.addEventListener("input", render), $btnPrev.addEventListener("click", onPrevNext), $btnNext.addEventListener("click", onPrevNext), new MutationObserver((mutationList, observer2) => {
- mutationList.forEach((mutation) => {
- if (mutation.type === "childList" || mutation.type === "attributes") render();
- });
- }).observe($select, {
- subtree: !0,
- childList: !0,
- attributes: !0
- }), render();
- const $div = CE("div", {
- class: "bx-select",
- _nearby: {
- orientation: "horizontal",
- focus: $btnNext
- }
- }, $select, $btnPrev, $content, $btnNext);
- return Object.defineProperty($div, "value", {
- get() {
- return $select.value;
- },
- set(value) {
- $div.setValue(value);
- }
- }), $div.addEventListener = function() {
- $select.addEventListener.apply($select, arguments);
- }, $div.removeEventListener = function() {
- $select.removeEventListener.apply($select, arguments);
- }, $div.dispatchEvent = function() {
- return $select.dispatchEvent.apply($select, arguments);
- }, $div.setValue = (value) => {
- if ("setValue" in $select) $select.setValue(value);
- else $select.value = value;
- }, $div;
- }
-}
var FeatureGates = {
PwaPrompt: !1,
EnableWifiWarnings: !1,
@@ -3422,7 +3441,7 @@ class SettingsNavigationDialog extends NavigationDialog {
group: "mkb",
label: t("virtual-controller"),
helpUrl: "https://better-xcloud.github.io/mouse-and-keyboard/",
- content: MkbRemapper.INSTANCE.render()
+ content: !1
}];
TAB_NATIVE_MKB_ITEMS = [{
requiredVariants: "full",
@@ -3994,325 +4013,6 @@ class SettingsNavigationDialog extends NavigationDialog {
return handled;
}
}
-var LOG_TAG2 = "MkbHandler", PointerToMouseButton = {
- 1: 0,
- 2: 2,
- 4: 1
-};
-class WebSocketMouseDataProvider extends MouseDataProvider {
- #pointerClient;
- #connected = !1;
- init() {
- this.#pointerClient = PointerClient.getInstance(), this.#connected = !1;
- try {
- this.#pointerClient.start(STATES.pointerServerPort, this.mkbHandler), this.#connected = !0;
- } catch (e) {
- Toast.show("Cannot enable Mouse & Keyboard feature");
- }
- }
- start() {
- this.#connected && AppInterface.requestPointerCapture();
- }
- stop() {
- this.#connected && AppInterface.releasePointerCapture();
- }
- destroy() {
- this.#connected && this.#pointerClient?.stop();
- }
-}
-class PointerLockMouseDataProvider extends MouseDataProvider {
- init() {}
- start() {
- window.addEventListener("mousemove", this.#onMouseMoveEvent), window.addEventListener("mousedown", this.#onMouseEvent), window.addEventListener("mouseup", this.#onMouseEvent), window.addEventListener("wheel", this.#onWheelEvent, { passive: !1 }), window.addEventListener("contextmenu", this.#disableContextMenu);
- }
- stop() {
- document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("mousemove", this.#onMouseMoveEvent), window.removeEventListener("mousedown", this.#onMouseEvent), window.removeEventListener("mouseup", this.#onMouseEvent), window.removeEventListener("wheel", this.#onWheelEvent), window.removeEventListener("contextmenu", this.#disableContextMenu);
- }
- destroy() {}
- #onMouseMoveEvent = (e) => {
- this.mkbHandler.handleMouseMove({
- movementX: e.movementX,
- movementY: e.movementY
- });
- };
- #onMouseEvent = (e) => {
- e.preventDefault();
- const isMouseDown = e.type === "mousedown", data = {
- mouseButton: e.button,
- pressed: isMouseDown
- };
- this.mkbHandler.handleMouseClick(data);
- };
- #onWheelEvent = (e) => {
- if (!KeyHelper.getKeyFromEvent(e)) return;
- const data = {
- vertical: e.deltaY,
- horizontal: e.deltaX
- };
- if (this.mkbHandler.handleMouseWheel(data)) e.preventDefault();
- };
- #disableContextMenu = (e) => e.preventDefault();
-}
-class EmulatedMkbHandler extends MkbHandler {
- static #instance;
- static getInstance() {
- if (!EmulatedMkbHandler.#instance) EmulatedMkbHandler.#instance = new EmulatedMkbHandler;
- return EmulatedMkbHandler.#instance;
- }
- #CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
- static DEFAULT_PANNING_SENSITIVITY = 0.001;
- static DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
- static MAXIMUM_STICK_RANGE = 1.1;
- static VIRTUAL_GAMEPAD_ID = "Xbox 360 Controller";
- #VIRTUAL_GAMEPAD = {
- id: EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID,
- index: 3,
- connected: !1,
- hapticActuators: null,
- mapping: "standard",
- axes: [0, 0, 0, 0],
- buttons: new Array(17).fill(null).map(() => ({ pressed: !1, value: 0 })),
- timestamp: performance.now(),
- vibrationActuator: null
- };
- #nativeGetGamepads = window.navigator.getGamepads.bind(window.navigator);
- #enabled = !1;
- #mouseDataProvider;
- #isPolling = !1;
- #prevWheelCode = null;
- #wheelStoppedTimeout;
- #detectMouseStoppedTimeout;
- #$message;
- #escKeyDownTime = -1;
- #STICK_MAP;
- #LEFT_STICK_X = [];
- #LEFT_STICK_Y = [];
- #RIGHT_STICK_X = [];
- #RIGHT_STICK_Y = [];
- constructor() {
- super();
- this.#STICK_MAP = {
- 102: [this.#LEFT_STICK_X, 0, -1],
- 103: [this.#LEFT_STICK_X, 0, 1],
- 100: [this.#LEFT_STICK_Y, 1, -1],
- 101: [this.#LEFT_STICK_Y, 1, 1],
- 202: [this.#RIGHT_STICK_X, 2, -1],
- 203: [this.#RIGHT_STICK_X, 2, 1],
- 200: [this.#RIGHT_STICK_Y, 3, -1],
- 201: [this.#RIGHT_STICK_Y, 3, 1]
- };
- }
- isEnabled = () => this.#enabled;
- #patchedGetGamepads = () => {
- const gamepads = this.#nativeGetGamepads() || [];
- return gamepads[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD, gamepads;
- };
- #getVirtualGamepad = () => this.#VIRTUAL_GAMEPAD;
- #updateStick(stick, x, y) {
- const virtualGamepad = this.#getVirtualGamepad();
- virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now();
- }
- #vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2);
- #resetGamepad = () => {
- const gamepad = this.#getVirtualGamepad();
- gamepad.axes = [0, 0, 0, 0];
- for (let button of gamepad.buttons)
- button.pressed = !1, button.value = 0;
- gamepad.timestamp = performance.now();
- };
- #pressButton = (buttonIndex, pressed) => {
- const virtualGamepad = this.#getVirtualGamepad();
- if (buttonIndex >= 100) {
- let [valueArr, axisIndex] = this.#STICK_MAP[buttonIndex];
- valueArr = valueArr, axisIndex = axisIndex;
- for (let i = valueArr.length - 1;i >= 0; i--)
- if (valueArr[i] === buttonIndex) valueArr.splice(i, 1);
- pressed && valueArr.push(buttonIndex);
- let value;
- if (valueArr.length) value = this.#STICK_MAP[valueArr[valueArr.length - 1]][2];
- else value = 0;
- virtualGamepad.axes[axisIndex] = value;
- } else virtualGamepad.buttons[buttonIndex].pressed = pressed, virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0;
- virtualGamepad.timestamp = performance.now();
- };
- #onKeyboardEvent = (e) => {
- const isKeyDown = e.type === "keydown";
- if (e.code === "F8") {
- if (!isKeyDown) e.preventDefault(), this.toggle();
- return;
- }
- if (e.code === "Escape") {
- if (e.preventDefault(), this.#enabled && isKeyDown) {
- if (this.#escKeyDownTime === -1) this.#escKeyDownTime = performance.now();
- else if (performance.now() - this.#escKeyDownTime >= 1000) this.stop();
- } else this.#escKeyDownTime = -1;
- return;
- }
- if (!this.#isPolling) return;
- const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key];
- if (typeof buttonIndex === "undefined") return;
- if (e.repeat) return;
- e.preventDefault(), this.#pressButton(buttonIndex, isKeyDown);
- };
- #onMouseStopped = () => {
- this.#detectMouseStoppedTimeout = null;
- const analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1;
- this.#updateStick(analog, 0, 0);
- };
- handleMouseClick = (data) => {
- let mouseButton;
- if (typeof data.mouseButton !== "undefined") mouseButton = data.mouseButton;
- else if (typeof data.pointerButton !== "undefined") mouseButton = PointerToMouseButton[data.pointerButton];
- const keyCode = "Mouse" + mouseButton, key = {
- code: keyCode,
- name: KeyHelper.codeToKeyName(keyCode)
- };
- if (!key.name) return;
- const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
- if (typeof buttonIndex === "undefined") return;
- this.#pressButton(buttonIndex, data.pressed);
- };
- handleMouseMove = (data) => {
- const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"];
- if (mouseMapTo === 0) return;
- this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout), this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50);
- const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"];
- let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y);
- if (length !== 0 && length < deadzoneCounterweight) x *= deadzoneCounterweight / length, y *= deadzoneCounterweight / length;
- else if (length > EmulatedMkbHandler.MAXIMUM_STICK_RANGE) x *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length, y *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length;
- const analog = mouseMapTo === 1 ? 0 : 1;
- this.#updateStick(analog, x, y);
- };
- handleMouseWheel = (data) => {
- let code = "";
- if (data.vertical < 0) code = "ScrollUp";
- else if (data.vertical > 0) code = "ScrollDown";
- else if (data.horizontal < 0) code = "ScrollLeft";
- else if (data.horizontal > 0) code = "ScrollRight";
- if (!code) return !1;
- const key = {
- code,
- name: KeyHelper.codeToKeyName(code)
- }, buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
- if (typeof buttonIndex === "undefined") return !1;
- if (this.#prevWheelCode === null || this.#prevWheelCode === key.code) this.#wheelStoppedTimeout && clearTimeout(this.#wheelStoppedTimeout), this.#pressButton(buttonIndex, !0);
- return this.#wheelStoppedTimeout = window.setTimeout(() => {
- this.#prevWheelCode = null, this.#pressButton(buttonIndex, !1);
- }, 20), !0;
- };
- toggle = (force) => {
- if (typeof force !== "undefined") this.#enabled = force;
- else this.#enabled = !this.#enabled;
- if (this.#enabled) document.body.requestPointerLock();
- else document.pointerLockElement && document.exitPointerLock();
- };
- #getCurrentPreset = () => {
- return new Promise((resolve) => {
- const presetId = getPref("mkb_default_preset_id");
- LocalDb.INSTANCE.getPreset(presetId).then((preset) => {
- resolve(preset);
- });
- });
- };
- refreshPresetData = () => {
- this.#getCurrentPreset().then((preset) => {
- this.#CURRENT_PRESET_DATA = MkbPreset.convert(preset ? preset.data : MkbPreset.DEFAULT_PRESET), this.#resetGamepad();
- });
- };
- waitForMouseData = (wait) => {
- this.#$message && this.#$message.classList.toggle("bx-gone", !wait);
- };
- #onPollingModeChanged = (e) => {
- if (!this.#$message) return;
- if (e.mode === "none") this.#$message.classList.remove("bx-offscreen");
- else this.#$message.classList.add("bx-offscreen");
- };
- #onDialogShown = () => {
- document.pointerLockElement && document.exitPointerLock();
- };
- #initMessage = () => {
- if (!this.#$message) this.#$message = CE("div", { class: "bx-mkb-pointer-lock-msg bx-gone" }, CE("div", {}, CE("p", {}, t("virtual-controller")), CE("p", {}, t("press-key-to-toggle-mkb", { key: "F8" }))), CE("div", { "data-type": "virtual" }, createButton({
- style: 1 | 256 | 64,
- label: t("activate"),
- onClick: ((e) => {
- e.preventDefault(), e.stopPropagation(), this.toggle(!0);
- }).bind(this)
- }), CE("div", {}, createButton({
- label: t("ignore"),
- style: 4,
- onClick: (e) => {
- e.preventDefault(), e.stopPropagation(), this.toggle(!1), this.waitForMouseData(!1);
- }
- }), createButton({
- label: t("edit"),
- onClick: (e) => {
- e.preventDefault(), e.stopPropagation();
- const dialog = SettingsNavigationDialog.getInstance();
- dialog.focusTab("mkb"), NavigationDialogManager.getInstance().show(dialog);
- }
- }))));
- if (!this.#$message.isConnected) document.documentElement.appendChild(this.#$message);
- };
- #onPointerLockChange = () => {
- if (document.pointerLockElement) this.start();
- else this.stop();
- };
- #onPointerLockError = (e) => {
- console.log(e), this.stop();
- };
- #onPointerLockRequested = () => {
- this.start();
- };
- #onPointerLockExited = () => {
- this.#mouseDataProvider?.stop();
- };
- handleEvent(event) {
- switch (event.type) {
- case BxEvent.POINTER_LOCK_REQUESTED:
- this.#onPointerLockRequested();
- break;
- case BxEvent.POINTER_LOCK_EXITED:
- this.#onPointerLockExited();
- break;
- }
- }
- init = () => {
- if (this.refreshPresetData(), this.#enabled = !1, AppInterface) this.#mouseDataProvider = new WebSocketMouseDataProvider(this);
- else this.#mouseDataProvider = new PointerLockMouseDataProvider(this);
- if (this.#mouseDataProvider.init(), window.addEventListener("keydown", this.#onKeyboardEvent), window.addEventListener("keyup", this.#onKeyboardEvent), window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), AppInterface) window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
- else document.addEventListener("pointerlockchange", this.#onPointerLockChange), document.addEventListener("pointerlockerror", this.#onPointerLockError);
- if (this.#initMessage(), this.#$message?.classList.add("bx-gone"), AppInterface) Toast.show(t("press-key-to-toggle-mkb", { key: "F8" }), t("virtual-controller"), { html: !0 }), this.waitForMouseData(!1);
- else this.waitForMouseData(!0);
- };
- destroy = () => {
- if (this.#isPolling = !1, this.#enabled = !1, this.stop(), this.waitForMouseData(!1), document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("keydown", this.#onKeyboardEvent), window.removeEventListener("keyup", this.#onKeyboardEvent), AppInterface) window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
- else document.removeEventListener("pointerlockchange", this.#onPointerLockChange), document.removeEventListener("pointerlockerror", this.#onPointerLockError);
- window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), this.#mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged);
- };
- start = () => {
- if (!this.#enabled) this.#enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
- this.#isPolling = !0, this.#escKeyDownTime = -1, this.#resetGamepad(), window.navigator.getGamepads = this.#patchedGetGamepads, this.waitForMouseData(!1), this.#mouseDataProvider?.start();
- const virtualGamepad = this.#getVirtualGamepad();
- virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", {
- gamepad: virtualGamepad
- }), window.BX_EXPOSED.stopTakRendering = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
- };
- stop = () => {
- this.#enabled = !1, this.#isPolling = !1, this.#escKeyDownTime = -1;
- const virtualGamepad = this.#getVirtualGamepad();
- if (virtualGamepad.connected) this.#resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", {
- gamepad: virtualGamepad
- }), window.navigator.getGamepads = this.#nativeGetGamepads;
- this.waitForMouseData(!0), this.#mouseDataProvider?.stop();
- };
- static setupEvents() {
- window.addEventListener(BxEvent.STREAM_PLAYING, () => {
- if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
- if (AppInterface && getPref("native_mkb_enabled") === "on") AppInterface && NativeMkbHandler.getInstance().init();
- } else if (getPref("mkb_enabled") && (AppInterface || !UserAgent.isMobile())) BxLogger.info(LOG_TAG2, "Emulate MKB"), EmulatedMkbHandler.getInstance().init();
- });
- }
-}
var BxExposed = {
getTitleInfo: () => STATES.currentStream.titleInfo,
modifyTitleInfo: !1,
@@ -5189,7 +4889,7 @@ function interceptHttpRequests() {
};
}
function showGamepadToast(gamepad) {
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) return;
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) return;
BxLogger.info("Gamepad", gamepad);
let text = "🎮";
if (getPref("local_co_op_enabled")) text += ` #${gamepad.index + 1}`;
diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js
index 5a5c0eb..a1844d4 100644
--- a/dist/better-xcloud.user.js
+++ b/dist/better-xcloud.user.js
@@ -1795,6 +1795,254 @@ var MouseMapTo;
MouseMapTo2[MouseMapTo2.LS = 1] = "LS";
MouseMapTo2[MouseMapTo2.RS = 2] = "RS";
})(MouseMapTo ||= {});
+class Toast {
+ static #$wrapper;
+ static #$msg;
+ static #$status;
+ static #stack = [];
+ static #isShowing = !1;
+ static #timeout;
+ static #DURATION = 3000;
+ static show(msg, status, options = {}) {
+ options = options || {};
+ const args = Array.from(arguments);
+ if (options.instant) Toast.#stack = [args], Toast.#showNext();
+ else Toast.#stack.push(args), !Toast.#isShowing && Toast.#showNext();
+ }
+ static #showNext() {
+ if (!Toast.#stack.length) {
+ Toast.#isShowing = !1;
+ return;
+ }
+ Toast.#isShowing = !0, Toast.#timeout && clearTimeout(Toast.#timeout), Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
+ const [msg, status, options] = Toast.#stack.shift();
+ if (options && options.html) Toast.#$msg.innerHTML = msg;
+ else Toast.#$msg.textContent = msg;
+ if (status) Toast.#$status.classList.remove("bx-gone"), Toast.#$status.textContent = status;
+ else Toast.#$status.classList.add("bx-gone");
+ const classList = Toast.#$wrapper.classList;
+ classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-show");
+ }
+ static #hide() {
+ Toast.#timeout = null;
+ const classList = Toast.#$wrapper.classList;
+ classList.remove("bx-show"), classList.add("bx-hide");
+ }
+ static setup() {
+ Toast.#$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.#$msg = CE("span", { class: "bx-toast-msg" }), Toast.#$status = CE("span", { class: "bx-toast-status" })), Toast.#$wrapper.addEventListener("transitionend", (e) => {
+ const classList = Toast.#$wrapper.classList;
+ if (classList.contains("bx-hide")) classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-offscreen"), Toast.#showNext();
+ }), document.documentElement.appendChild(Toast.#$wrapper);
+ }
+}
+class MicrophoneShortcut {
+ static toggle(showToast = !0) {
+ if (!window.BX_EXPOSED.streamSession) return !1;
+ const enableMic = window.BX_EXPOSED.streamSession._microphoneState === "Enabled" ? !1 : !0;
+ try {
+ return window.BX_EXPOSED.streamSession.tryEnableChatAsync(enableMic), showToast && Toast.show(t("microphone"), t(enableMic ? "unmuted" : "muted"), { instant: !0 }), enableMic;
+ } catch (e) {
+ console.log(e);
+ }
+ return !1;
+ }
+}
+class StreamUiShortcut {
+ static showHideStreamMenu() {
+ window.BX_EXPOSED.showStreamMenu && window.BX_EXPOSED.showStreamMenu();
+ }
+}
+function checkForUpdate() {
+ if (SCRIPT_VERSION.includes("beta")) return;
+ const CHECK_INTERVAL_SECONDS = 7200, currentVersion = getPref("version_current"), lastCheck = getPref("version_last_check"), now = Math.round(+new Date / 1000);
+ if (currentVersion === SCRIPT_VERSION && now - lastCheck < CHECK_INTERVAL_SECONDS) return;
+ setPref("version_last_check", now), fetch("https://api.github.com/repos/redphx/better-xcloud/releases/latest").then((response) => response.json()).then((json) => {
+ setPref("version_latest", json.tag_name.substring(1)), setPref("version_current", SCRIPT_VERSION);
+ }), Translations.updateTranslations(currentVersion === SCRIPT_VERSION);
+}
+function disablePwa() {
+ if (!(window.navigator.orgUserAgent || window.navigator.userAgent || "").toLowerCase()) return;
+ if (!!AppInterface || UserAgent.isSafariMobile()) Object.defineProperty(window.navigator, "standalone", {
+ value: !0
+ });
+}
+function hashCode(str) {
+ let hash = 0;
+ for (let i = 0, len = str.length;i < len; i++) {
+ const chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr, hash |= 0;
+ }
+ return hash;
+}
+function renderString(str, obj) {
+ return str.replace(/\$\{.+?\}/g, (match) => {
+ const key = match.substring(2, match.length - 1);
+ if (key in obj) return obj[key];
+ return match;
+ });
+}
+function ceilToNearest(value, interval) {
+ return Math.ceil(value / interval) * interval;
+}
+function floorToNearest(value, interval) {
+ return Math.floor(value / interval) * interval;
+}
+async function copyToClipboard(text, showToast = !0) {
+ try {
+ return await navigator.clipboard.writeText(text), showToast && Toast.show("Copied to clipboard", "", { instant: !0 }), !0;
+ } catch (err) {
+ console.error("Failed to copy: ", err), showToast && Toast.show("Failed to copy", "", { instant: !0 });
+ }
+ return !1;
+}
+function productTitleToSlug(title) {
+ return title.replace(/[;,/?:@&=+_`~$%#^*()!^™\xae\xa9]/g, "").replace(/\|/g, "-").replace(/ {2,}/g, " ").trim().substr(0, 50).replace(/ /g, "-").toLowerCase();
+}
+class SoundShortcut {
+ static adjustGainNodeVolume(amount) {
+ if (!getPref("audio_enable_volume_control")) return 0;
+ const currentValue = getPref("audio_volume");
+ let nearestValue;
+ if (amount > 0) nearestValue = ceilToNearest(currentValue, amount);
+ else nearestValue = floorToNearest(currentValue, -1 * amount);
+ let newValue;
+ if (currentValue !== nearestValue) newValue = nearestValue;
+ else newValue = currentValue + amount;
+ return newValue = setPref("audio_volume", newValue, !0), SoundShortcut.setGainNodeVolume(newValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, newValue + "%", { instant: !0 }), newValue;
+ }
+ static setGainNodeVolume(value) {
+ STATES.currentStream.audioGainNode && (STATES.currentStream.audioGainNode.gain.value = value / 100);
+ }
+ static muteUnmute() {
+ if (getPref("audio_enable_volume_control") && STATES.currentStream.audioGainNode) {
+ const gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume");
+ let targetValue;
+ if (settingValue === 0) targetValue = 100, setPref("audio_volume", targetValue, !0);
+ else if (gainValue === 0) targetValue = settingValue;
+ else targetValue = 0;
+ let status;
+ if (targetValue === 0) status = t("muted");
+ else status = targetValue + "%";
+ SoundShortcut.setGainNodeVolume(targetValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
+ speakerState: targetValue === 0 ? 1 : 0
+ });
+ return;
+ }
+ let $media;
+ if ($media = document.querySelector("div[data-testid=media-container] audio"), !$media) $media = document.querySelector("div[data-testid=media-container] video");
+ if ($media) {
+ $media.muted = !$media.muted;
+ const status = $media.muted ? t("muted") : t("unmuted");
+ Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
+ speakerState: $media.muted ? 1 : 0
+ });
+ }
+ }
+}
+class BxSelectElement {
+ static wrap($select) {
+ $select.removeAttribute("tabindex");
+ const $btnPrev = createButton({
+ label: "<",
+ style: 32
+ }), $btnNext = createButton({
+ label: ">",
+ style: 32
+ }), isMultiple = $select.multiple;
+ let $checkBox, $label, visibleIndex = $select.selectedIndex, $content;
+ if (isMultiple) $content = CE("button", {
+ class: "bx-select-value bx-focusable",
+ tabindex: 0
+ }, $checkBox = CE("input", { type: "checkbox" }), $label = CE("span", {}, "")), $content.addEventListener("click", (e) => {
+ $checkBox.click();
+ }), $checkBox.addEventListener("input", (e) => {
+ const $option = getOptionAtIndex(visibleIndex);
+ $option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");
+ });
+ else $content = CE("div", {}, $label = CE("label", { for: $select.id + "_checkbox" }, ""));
+ const getOptionAtIndex = (index) => {
+ return Array.from($select.querySelectorAll("option"))[index];
+ }, render = (e) => {
+ if (e && e.manualTrigger) visibleIndex = $select.selectedIndex;
+ visibleIndex = normalizeIndex(visibleIndex);
+ const $option = getOptionAtIndex(visibleIndex);
+ let content = "";
+ if ($option) if (content = $option.textContent || "", content && $option.parentElement.tagName === "OPTGROUP") {
+ $label.innerHTML = "";
+ const fragment = document.createDocumentFragment();
+ fragment.appendChild(CE("span", {}, $option.parentElement.label)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment);
+ } else $label.textContent = content;
+ else $label.textContent = content;
+ if ($label.classList.toggle("bx-line-through", $option && $option.disabled), isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);
+ const disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1;
+ $btnPrev.classList.toggle("bx-inactive", disablePrev), $btnNext.classList.toggle("bx-inactive", disableNext), disablePrev && !disableNext && document.activeElement === $btnPrev && $btnNext.focus(), disableNext && !disablePrev && document.activeElement === $btnNext && $btnPrev.focus();
+ }, normalizeIndex = (index) => {
+ return Math.min(Math.max(index, 0), $select.querySelectorAll("option").length - 1);
+ }, onPrevNext = (e) => {
+ if (!e.target) return;
+ const goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex;
+ let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
+ if (newIndex = normalizeIndex(newIndex), visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex;
+ if (isMultiple) render();
+ else BxEvent.dispatch($select, "input");
+ };
+ $select.addEventListener("input", render), $btnPrev.addEventListener("click", onPrevNext), $btnNext.addEventListener("click", onPrevNext), new MutationObserver((mutationList, observer2) => {
+ mutationList.forEach((mutation) => {
+ if (mutation.type === "childList" || mutation.type === "attributes") render();
+ });
+ }).observe($select, {
+ subtree: !0,
+ childList: !0,
+ attributes: !0
+ }), render();
+ const $div = CE("div", {
+ class: "bx-select",
+ _nearby: {
+ orientation: "horizontal",
+ focus: $btnNext
+ }
+ }, $select, $btnPrev, $content, $btnNext);
+ return Object.defineProperty($div, "value", {
+ get() {
+ return $select.value;
+ },
+ set(value) {
+ $div.setValue(value);
+ }
+ }), $div.addEventListener = function() {
+ $select.addEventListener.apply($select, arguments);
+ }, $div.removeEventListener = function() {
+ $select.removeEventListener.apply($select, arguments);
+ }, $div.dispatchEvent = function() {
+ return $select.dispatchEvent.apply($select, arguments);
+ }, $div.setValue = (value) => {
+ if ("setValue" in $select) $select.setValue(value);
+ else $select.value = value;
+ }, $div;
+ }
+}
+function onChangeVideoPlayerType() {
+ const playerType = getPref("video_player_type"), $videoProcessing = document.getElementById("bx_setting_video_processing"), $videoSharpness = document.getElementById("bx_setting_video_sharpness"), $videoPowerPreference = document.getElementById("bx_setting_video_power_preference");
+ if (!$videoProcessing) return;
+ let isDisabled = !1;
+ const $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`);
+ if (playerType === "webgl2") $optCas && ($optCas.disabled = !1);
+ else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0;
+ $videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), updateVideoPlayer();
+}
+function updateVideoPlayer() {
+ const streamPlayer = STATES.currentStream.streamPlayer;
+ if (!streamPlayer) return;
+ const options = {
+ processing: getPref("video_processing"),
+ sharpness: getPref("video_sharpness"),
+ saturation: getPref("video_saturation"),
+ contrast: getPref("video_contrast"),
+ brightness: getPref("video_brightness")
+ };
+ streamPlayer.setPlayerType(getPref("video_player_type")), streamPlayer.updateOptions(options), streamPlayer.refreshPlayer();
+}
+window.addEventListener("resize", updateVideoPlayer);
class MkbPreset {
static MOUSE_SETTINGS = {
map_to: {
@@ -1892,46 +2140,6 @@ class MkbPreset {
return console.log(obj), obj;
}
}
-class Toast {
- static #$wrapper;
- static #$msg;
- static #$status;
- static #stack = [];
- static #isShowing = !1;
- static #timeout;
- static #DURATION = 3000;
- static show(msg, status, options = {}) {
- options = options || {};
- const args = Array.from(arguments);
- if (options.instant) Toast.#stack = [args], Toast.#showNext();
- else Toast.#stack.push(args), !Toast.#isShowing && Toast.#showNext();
- }
- static #showNext() {
- if (!Toast.#stack.length) {
- Toast.#isShowing = !1;
- return;
- }
- Toast.#isShowing = !0, Toast.#timeout && clearTimeout(Toast.#timeout), Toast.#timeout = window.setTimeout(Toast.#hide, Toast.#DURATION);
- const [msg, status, options] = Toast.#stack.shift();
- if (options && options.html) Toast.#$msg.innerHTML = msg;
- else Toast.#$msg.textContent = msg;
- if (status) Toast.#$status.classList.remove("bx-gone"), Toast.#$status.textContent = status;
- else Toast.#$status.classList.add("bx-gone");
- const classList = Toast.#$wrapper.classList;
- classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-show");
- }
- static #hide() {
- Toast.#timeout = null;
- const classList = Toast.#$wrapper.classList;
- classList.remove("bx-show"), classList.add("bx-hide");
- }
- static setup() {
- Toast.#$wrapper = CE("div", { class: "bx-toast bx-offscreen" }, Toast.#$msg = CE("span", { class: "bx-toast-msg" }), Toast.#$status = CE("span", { class: "bx-toast-status" })), Toast.#$wrapper.addEventListener("transitionend", (e) => {
- const classList = Toast.#$wrapper.classList;
- if (classList.contains("bx-hide")) classList.remove("bx-offscreen", "bx-hide"), classList.add("bx-offscreen"), Toast.#showNext();
- }), document.documentElement.appendChild(Toast.#$wrapper);
- }
-}
class LocalDb {
static #instance;
static get INSTANCE() {
@@ -2292,28 +2500,324 @@ class NativeMkbHandler extends MkbHandler {
});
}
}
-function onChangeVideoPlayerType() {
- const playerType = getPref("video_player_type"), $videoProcessing = document.getElementById("bx_setting_video_processing"), $videoSharpness = document.getElementById("bx_setting_video_sharpness"), $videoPowerPreference = document.getElementById("bx_setting_video_power_preference");
- if (!$videoProcessing) return;
- let isDisabled = !1;
- const $optCas = $videoProcessing.querySelector(`option[value=${"cas"}]`);
- if (playerType === "webgl2") $optCas && ($optCas.disabled = !1);
- else if ($videoProcessing.value = "usm", setPref("video_processing", "usm"), $optCas && ($optCas.disabled = !0), UserAgent.isSafari()) isDisabled = !0;
- $videoProcessing.disabled = isDisabled, $videoSharpness.dataset.disabled = isDisabled.toString(), $videoPowerPreference.closest(".bx-settings-row").classList.toggle("bx-gone", playerType !== "webgl2"), updateVideoPlayer();
+var LOG_TAG2 = "MkbHandler", PointerToMouseButton = {
+ 1: 0,
+ 2: 2,
+ 4: 1
+}, VIRTUAL_GAMEPAD_ID = "Xbox 360 Controller";
+class WebSocketMouseDataProvider extends MouseDataProvider {
+ #pointerClient;
+ #connected = !1;
+ init() {
+ this.#pointerClient = PointerClient.getInstance(), this.#connected = !1;
+ try {
+ this.#pointerClient.start(STATES.pointerServerPort, this.mkbHandler), this.#connected = !0;
+ } catch (e) {
+ Toast.show("Cannot enable Mouse & Keyboard feature");
+ }
+ }
+ start() {
+ this.#connected && AppInterface.requestPointerCapture();
+ }
+ stop() {
+ this.#connected && AppInterface.releasePointerCapture();
+ }
+ destroy() {
+ this.#connected && this.#pointerClient?.stop();
+ }
}
-function updateVideoPlayer() {
- const streamPlayer = STATES.currentStream.streamPlayer;
- if (!streamPlayer) return;
- const options = {
- processing: getPref("video_processing"),
- sharpness: getPref("video_sharpness"),
- saturation: getPref("video_saturation"),
- contrast: getPref("video_contrast"),
- brightness: getPref("video_brightness")
+class PointerLockMouseDataProvider extends MouseDataProvider {
+ init() {}
+ start() {
+ window.addEventListener("mousemove", this.#onMouseMoveEvent), window.addEventListener("mousedown", this.#onMouseEvent), window.addEventListener("mouseup", this.#onMouseEvent), window.addEventListener("wheel", this.#onWheelEvent, { passive: !1 }), window.addEventListener("contextmenu", this.#disableContextMenu);
+ }
+ stop() {
+ document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("mousemove", this.#onMouseMoveEvent), window.removeEventListener("mousedown", this.#onMouseEvent), window.removeEventListener("mouseup", this.#onMouseEvent), window.removeEventListener("wheel", this.#onWheelEvent), window.removeEventListener("contextmenu", this.#disableContextMenu);
+ }
+ destroy() {}
+ #onMouseMoveEvent = (e) => {
+ this.mkbHandler.handleMouseMove({
+ movementX: e.movementX,
+ movementY: e.movementY
+ });
};
- streamPlayer.setPlayerType(getPref("video_player_type")), streamPlayer.updateOptions(options), streamPlayer.refreshPlayer();
+ #onMouseEvent = (e) => {
+ e.preventDefault();
+ const isMouseDown = e.type === "mousedown", data = {
+ mouseButton: e.button,
+ pressed: isMouseDown
+ };
+ this.mkbHandler.handleMouseClick(data);
+ };
+ #onWheelEvent = (e) => {
+ if (!KeyHelper.getKeyFromEvent(e)) return;
+ const data = {
+ vertical: e.deltaY,
+ horizontal: e.deltaX
+ };
+ if (this.mkbHandler.handleMouseWheel(data)) e.preventDefault();
+ };
+ #disableContextMenu = (e) => e.preventDefault();
+}
+class EmulatedMkbHandler extends MkbHandler {
+ static #instance;
+ static getInstance() {
+ if (!EmulatedMkbHandler.#instance) EmulatedMkbHandler.#instance = new EmulatedMkbHandler;
+ return EmulatedMkbHandler.#instance;
+ }
+ #CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
+ static DEFAULT_PANNING_SENSITIVITY = 0.001;
+ static DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
+ static MAXIMUM_STICK_RANGE = 1.1;
+ #VIRTUAL_GAMEPAD = {
+ id: VIRTUAL_GAMEPAD_ID,
+ index: 3,
+ connected: !1,
+ hapticActuators: null,
+ mapping: "standard",
+ axes: [0, 0, 0, 0],
+ buttons: new Array(17).fill(null).map(() => ({ pressed: !1, value: 0 })),
+ timestamp: performance.now(),
+ vibrationActuator: null
+ };
+ #nativeGetGamepads = window.navigator.getGamepads.bind(window.navigator);
+ #enabled = !1;
+ #mouseDataProvider;
+ #isPolling = !1;
+ #prevWheelCode = null;
+ #wheelStoppedTimeout;
+ #detectMouseStoppedTimeout;
+ #$message;
+ #escKeyDownTime = -1;
+ #STICK_MAP;
+ #LEFT_STICK_X = [];
+ #LEFT_STICK_Y = [];
+ #RIGHT_STICK_X = [];
+ #RIGHT_STICK_Y = [];
+ constructor() {
+ super();
+ this.#STICK_MAP = {
+ 102: [this.#LEFT_STICK_X, 0, -1],
+ 103: [this.#LEFT_STICK_X, 0, 1],
+ 100: [this.#LEFT_STICK_Y, 1, -1],
+ 101: [this.#LEFT_STICK_Y, 1, 1],
+ 202: [this.#RIGHT_STICK_X, 2, -1],
+ 203: [this.#RIGHT_STICK_X, 2, 1],
+ 200: [this.#RIGHT_STICK_Y, 3, -1],
+ 201: [this.#RIGHT_STICK_Y, 3, 1]
+ };
+ }
+ isEnabled = () => this.#enabled;
+ #patchedGetGamepads = () => {
+ const gamepads = this.#nativeGetGamepads() || [];
+ return gamepads[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD, gamepads;
+ };
+ #getVirtualGamepad = () => this.#VIRTUAL_GAMEPAD;
+ #updateStick(stick, x, y) {
+ const virtualGamepad = this.#getVirtualGamepad();
+ virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now();
+ }
+ #vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2);
+ #resetGamepad = () => {
+ const gamepad = this.#getVirtualGamepad();
+ gamepad.axes = [0, 0, 0, 0];
+ for (let button of gamepad.buttons)
+ button.pressed = !1, button.value = 0;
+ gamepad.timestamp = performance.now();
+ };
+ #pressButton = (buttonIndex, pressed) => {
+ const virtualGamepad = this.#getVirtualGamepad();
+ if (buttonIndex >= 100) {
+ let [valueArr, axisIndex] = this.#STICK_MAP[buttonIndex];
+ valueArr = valueArr, axisIndex = axisIndex;
+ for (let i = valueArr.length - 1;i >= 0; i--)
+ if (valueArr[i] === buttonIndex) valueArr.splice(i, 1);
+ pressed && valueArr.push(buttonIndex);
+ let value;
+ if (valueArr.length) value = this.#STICK_MAP[valueArr[valueArr.length - 1]][2];
+ else value = 0;
+ virtualGamepad.axes[axisIndex] = value;
+ } else virtualGamepad.buttons[buttonIndex].pressed = pressed, virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0;
+ virtualGamepad.timestamp = performance.now();
+ };
+ #onKeyboardEvent = (e) => {
+ const isKeyDown = e.type === "keydown";
+ if (e.code === "F8") {
+ if (!isKeyDown) e.preventDefault(), this.toggle();
+ return;
+ }
+ if (e.code === "Escape") {
+ if (e.preventDefault(), this.#enabled && isKeyDown) {
+ if (this.#escKeyDownTime === -1) this.#escKeyDownTime = performance.now();
+ else if (performance.now() - this.#escKeyDownTime >= 1000) this.stop();
+ } else this.#escKeyDownTime = -1;
+ return;
+ }
+ if (!this.#isPolling) return;
+ const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key];
+ if (typeof buttonIndex === "undefined") return;
+ if (e.repeat) return;
+ e.preventDefault(), this.#pressButton(buttonIndex, isKeyDown);
+ };
+ #onMouseStopped = () => {
+ this.#detectMouseStoppedTimeout = null;
+ const analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1;
+ this.#updateStick(analog, 0, 0);
+ };
+ handleMouseClick = (data) => {
+ let mouseButton;
+ if (typeof data.mouseButton !== "undefined") mouseButton = data.mouseButton;
+ else if (typeof data.pointerButton !== "undefined") mouseButton = PointerToMouseButton[data.pointerButton];
+ const keyCode = "Mouse" + mouseButton, key = {
+ code: keyCode,
+ name: KeyHelper.codeToKeyName(keyCode)
+ };
+ if (!key.name) return;
+ const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
+ if (typeof buttonIndex === "undefined") return;
+ this.#pressButton(buttonIndex, data.pressed);
+ };
+ handleMouseMove = (data) => {
+ const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"];
+ if (mouseMapTo === 0) return;
+ this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout), this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50);
+ const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"];
+ let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y);
+ if (length !== 0 && length < deadzoneCounterweight) x *= deadzoneCounterweight / length, y *= deadzoneCounterweight / length;
+ else if (length > EmulatedMkbHandler.MAXIMUM_STICK_RANGE) x *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length, y *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length;
+ const analog = mouseMapTo === 1 ? 0 : 1;
+ this.#updateStick(analog, x, y);
+ };
+ handleMouseWheel = (data) => {
+ let code = "";
+ if (data.vertical < 0) code = "ScrollUp";
+ else if (data.vertical > 0) code = "ScrollDown";
+ else if (data.horizontal < 0) code = "ScrollLeft";
+ else if (data.horizontal > 0) code = "ScrollRight";
+ if (!code) return !1;
+ const key = {
+ code,
+ name: KeyHelper.codeToKeyName(code)
+ }, buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
+ if (typeof buttonIndex === "undefined") return !1;
+ if (this.#prevWheelCode === null || this.#prevWheelCode === key.code) this.#wheelStoppedTimeout && clearTimeout(this.#wheelStoppedTimeout), this.#pressButton(buttonIndex, !0);
+ return this.#wheelStoppedTimeout = window.setTimeout(() => {
+ this.#prevWheelCode = null, this.#pressButton(buttonIndex, !1);
+ }, 20), !0;
+ };
+ toggle = (force) => {
+ if (typeof force !== "undefined") this.#enabled = force;
+ else this.#enabled = !this.#enabled;
+ if (this.#enabled) document.body.requestPointerLock();
+ else document.pointerLockElement && document.exitPointerLock();
+ };
+ #getCurrentPreset = () => {
+ return new Promise((resolve) => {
+ const presetId = getPref("mkb_default_preset_id");
+ LocalDb.INSTANCE.getPreset(presetId).then((preset) => {
+ resolve(preset);
+ });
+ });
+ };
+ refreshPresetData = () => {
+ this.#getCurrentPreset().then((preset) => {
+ this.#CURRENT_PRESET_DATA = MkbPreset.convert(preset ? preset.data : MkbPreset.DEFAULT_PRESET), this.#resetGamepad();
+ });
+ };
+ waitForMouseData = (wait) => {
+ this.#$message && this.#$message.classList.toggle("bx-gone", !wait);
+ };
+ #onPollingModeChanged = (e) => {
+ if (!this.#$message) return;
+ if (e.mode === "none") this.#$message.classList.remove("bx-offscreen");
+ else this.#$message.classList.add("bx-offscreen");
+ };
+ #onDialogShown = () => {
+ document.pointerLockElement && document.exitPointerLock();
+ };
+ #initMessage = () => {
+ if (!this.#$message) this.#$message = CE("div", { class: "bx-mkb-pointer-lock-msg bx-gone" }, CE("div", {}, CE("p", {}, t("virtual-controller")), CE("p", {}, t("press-key-to-toggle-mkb", { key: "F8" }))), CE("div", { "data-type": "virtual" }, createButton({
+ style: 1 | 256 | 64,
+ label: t("activate"),
+ onClick: ((e) => {
+ e.preventDefault(), e.stopPropagation(), this.toggle(!0);
+ }).bind(this)
+ }), CE("div", {}, createButton({
+ label: t("ignore"),
+ style: 4,
+ onClick: (e) => {
+ e.preventDefault(), e.stopPropagation(), this.toggle(!1), this.waitForMouseData(!1);
+ }
+ }), createButton({
+ label: t("edit"),
+ onClick: (e) => {
+ e.preventDefault(), e.stopPropagation();
+ const dialog = SettingsNavigationDialog.getInstance();
+ dialog.focusTab("mkb"), NavigationDialogManager.getInstance().show(dialog);
+ }
+ }))));
+ if (!this.#$message.isConnected) document.documentElement.appendChild(this.#$message);
+ };
+ #onPointerLockChange = () => {
+ if (document.pointerLockElement) this.start();
+ else this.stop();
+ };
+ #onPointerLockError = (e) => {
+ console.log(e), this.stop();
+ };
+ #onPointerLockRequested = () => {
+ this.start();
+ };
+ #onPointerLockExited = () => {
+ this.#mouseDataProvider?.stop();
+ };
+ handleEvent(event) {
+ switch (event.type) {
+ case BxEvent.POINTER_LOCK_REQUESTED:
+ this.#onPointerLockRequested();
+ break;
+ case BxEvent.POINTER_LOCK_EXITED:
+ this.#onPointerLockExited();
+ break;
+ }
+ }
+ init = () => {
+ if (this.refreshPresetData(), this.#enabled = !1, AppInterface) this.#mouseDataProvider = new WebSocketMouseDataProvider(this);
+ else this.#mouseDataProvider = new PointerLockMouseDataProvider(this);
+ if (this.#mouseDataProvider.init(), window.addEventListener("keydown", this.#onKeyboardEvent), window.addEventListener("keyup", this.#onKeyboardEvent), window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), AppInterface) window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
+ else document.addEventListener("pointerlockchange", this.#onPointerLockChange), document.addEventListener("pointerlockerror", this.#onPointerLockError);
+ if (this.#initMessage(), this.#$message?.classList.add("bx-gone"), AppInterface) Toast.show(t("press-key-to-toggle-mkb", { key: "F8" }), t("virtual-controller"), { html: !0 }), this.waitForMouseData(!1);
+ else this.waitForMouseData(!0);
+ };
+ destroy = () => {
+ if (this.#isPolling = !1, this.#enabled = !1, this.stop(), this.waitForMouseData(!1), document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("keydown", this.#onKeyboardEvent), window.removeEventListener("keyup", this.#onKeyboardEvent), AppInterface) window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
+ else document.removeEventListener("pointerlockchange", this.#onPointerLockChange), document.removeEventListener("pointerlockerror", this.#onPointerLockError);
+ window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), this.#mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged);
+ };
+ start = () => {
+ if (!this.#enabled) this.#enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
+ this.#isPolling = !0, this.#escKeyDownTime = -1, this.#resetGamepad(), window.navigator.getGamepads = this.#patchedGetGamepads, this.waitForMouseData(!1), this.#mouseDataProvider?.start();
+ const virtualGamepad = this.#getVirtualGamepad();
+ virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", {
+ gamepad: virtualGamepad
+ }), window.BX_EXPOSED.stopTakRendering = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
+ };
+ stop = () => {
+ this.#enabled = !1, this.#isPolling = !1, this.#escKeyDownTime = -1;
+ const virtualGamepad = this.#getVirtualGamepad();
+ if (virtualGamepad.connected) this.#resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", {
+ gamepad: virtualGamepad
+ }), window.navigator.getGamepads = this.#nativeGetGamepads;
+ this.waitForMouseData(!0), this.#mouseDataProvider?.stop();
+ };
+ static setupEvents() {
+ window.addEventListener(BxEvent.STREAM_PLAYING, () => {
+ if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
+ if (AppInterface && getPref("native_mkb_enabled") === "on") AppInterface && NativeMkbHandler.getInstance().init();
+ } else if (getPref("mkb_enabled") && (AppInterface || !UserAgent.isMobile())) BxLogger.info(LOG_TAG2, "Emulate MKB"), EmulatedMkbHandler.getInstance().init();
+ });
+ }
}
-window.addEventListener("resize", updateVideoPlayer);
class NavigationDialog {
dialogManager;
constructor() {
@@ -2439,7 +2943,7 @@ class NavigationDialogManager {
const gamepads = window.navigator.getGamepads();
for (let gamepad of gamepads) {
if (!gamepad || !gamepad.connected) continue;
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) continue;
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue;
const { axes, buttons } = gamepad;
let releasedButton = null, heldButton = null, lastState = this.gamepadLastStates[gamepad.index], lastTimestamp, lastKey, lastKeyPressed;
if (lastState) [lastTimestamp, lastKey, lastKeyPressed] = lastState;
@@ -2930,94 +3434,7 @@ class MkbRemapper {
return this.#$.wrapper.appendChild($actionButtons), this.#toggleEditing(!1), this.#refresh(), this.#$.wrapper;
}
}
-function checkForUpdate() {
- if (SCRIPT_VERSION.includes("beta")) return;
- const CHECK_INTERVAL_SECONDS = 7200, currentVersion = getPref("version_current"), lastCheck = getPref("version_last_check"), now = Math.round(+new Date / 1000);
- if (currentVersion === SCRIPT_VERSION && now - lastCheck < CHECK_INTERVAL_SECONDS) return;
- setPref("version_last_check", now), fetch("https://api.github.com/repos/redphx/better-xcloud/releases/latest").then((response) => response.json()).then((json) => {
- setPref("version_latest", json.tag_name.substring(1)), setPref("version_current", SCRIPT_VERSION);
- }), Translations.updateTranslations(currentVersion === SCRIPT_VERSION);
-}
-function disablePwa() {
- if (!(window.navigator.orgUserAgent || window.navigator.userAgent || "").toLowerCase()) return;
- if (!!AppInterface || UserAgent.isSafariMobile()) Object.defineProperty(window.navigator, "standalone", {
- value: !0
- });
-}
-function hashCode(str) {
- let hash = 0;
- for (let i = 0, len = str.length;i < len; i++) {
- const chr = str.charCodeAt(i);
- hash = (hash << 5) - hash + chr, hash |= 0;
- }
- return hash;
-}
-function renderString(str, obj) {
- return str.replace(/\$\{.+?\}/g, (match) => {
- const key = match.substring(2, match.length - 1);
- if (key in obj) return obj[key];
- return match;
- });
-}
-function ceilToNearest(value, interval) {
- return Math.ceil(value / interval) * interval;
-}
-function floorToNearest(value, interval) {
- return Math.floor(value / interval) * interval;
-}
-async function copyToClipboard(text, showToast = !0) {
- try {
- return await navigator.clipboard.writeText(text), showToast && Toast.show("Copied to clipboard", "", { instant: !0 }), !0;
- } catch (err) {
- console.error("Failed to copy: ", err), showToast && Toast.show("Failed to copy", "", { instant: !0 });
- }
- return !1;
-}
-function productTitleToSlug(title) {
- return title.replace(/[;,/?:@&=+_`~$%#^*()!^™\xae\xa9]/g, "").replace(/\|/g, "-").replace(/ {2,}/g, " ").trim().substr(0, 50).replace(/ /g, "-").toLowerCase();
-}
-class SoundShortcut {
- static adjustGainNodeVolume(amount) {
- if (!getPref("audio_enable_volume_control")) return 0;
- const currentValue = getPref("audio_volume");
- let nearestValue;
- if (amount > 0) nearestValue = ceilToNearest(currentValue, amount);
- else nearestValue = floorToNearest(currentValue, -1 * amount);
- let newValue;
- if (currentValue !== nearestValue) newValue = nearestValue;
- else newValue = currentValue + amount;
- return newValue = setPref("audio_volume", newValue, !0), SoundShortcut.setGainNodeVolume(newValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, newValue + "%", { instant: !0 }), newValue;
- }
- static setGainNodeVolume(value) {
- STATES.currentStream.audioGainNode && (STATES.currentStream.audioGainNode.gain.value = value / 100);
- }
- static muteUnmute() {
- if (getPref("audio_enable_volume_control") && STATES.currentStream.audioGainNode) {
- const gainValue = STATES.currentStream.audioGainNode.gain.value, settingValue = getPref("audio_volume");
- let targetValue;
- if (settingValue === 0) targetValue = 100, setPref("audio_volume", targetValue, !0);
- else if (gainValue === 0) targetValue = settingValue;
- else targetValue = 0;
- let status;
- if (targetValue === 0) status = t("muted");
- else status = targetValue + "%";
- SoundShortcut.setGainNodeVolume(targetValue), Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
- speakerState: targetValue === 0 ? 1 : 0
- });
- return;
- }
- let $media;
- if ($media = document.querySelector("div[data-testid=media-container] audio"), !$media) $media = document.querySelector("div[data-testid=media-container] video");
- if ($media) {
- $media.muted = !$media.muted;
- const status = $media.muted ? t("muted") : t("unmuted");
- Toast.show(`${t("stream")} ❯ ${t("volume")}`, status, { instant: !0 }), BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
- speakerState: $media.muted ? 1 : 0
- });
- }
- }
-}
-var LOG_TAG2 = "TouchController";
+var LOG_TAG3 = "TouchController";
class TouchController {
static #EVENT_SHOW_DEFAULT_CONTROLLER = new MessageEvent("message", {
data: JSON.stringify({
@@ -3113,12 +3530,12 @@ class TouchController {
}
const xboxTitleId = TouchController.#xboxTitleId;
if (!xboxTitleId) {
- BxLogger.error(LOG_TAG2, "Invalid xboxTitleId");
+ BxLogger.error(LOG_TAG3, "Invalid xboxTitleId");
return;
}
if (!layoutId) layoutId = TouchController.#customLayouts[xboxTitleId]?.default_layout || null;
if (!layoutId) {
- BxLogger.error(LOG_TAG2, "Invalid layoutId, show default controller"), TouchController.#enabled && TouchController.#showDefault();
+ BxLogger.error(LOG_TAG3, "Invalid layoutId, show default controller"), TouchController.#enabled && TouchController.#showDefault();
return;
}
const layoutChanged = TouchController.#currentLayoutId !== layoutId;
@@ -3201,7 +3618,7 @@ class TouchController {
TouchController.setXboxTitleId(parseInt(json.titleid, 16).toString());
}
} catch (e2) {
- BxLogger.error(LOG_TAG2, "Load custom layout", e2);
+ BxLogger.error(LOG_TAG3, "Load custom layout", e2);
}
});
});
@@ -3280,88 +3697,6 @@ class VibrationManager {
});
}
}
-class BxSelectElement {
- static wrap($select) {
- $select.removeAttribute("tabindex");
- const $btnPrev = createButton({
- label: "<",
- style: 32
- }), $btnNext = createButton({
- label: ">",
- style: 32
- }), isMultiple = $select.multiple;
- let $checkBox, $label, visibleIndex = $select.selectedIndex, $content;
- if (isMultiple) $content = CE("button", {
- class: "bx-select-value bx-focusable",
- tabindex: 0
- }, $checkBox = CE("input", { type: "checkbox" }), $label = CE("span", {}, "")), $content.addEventListener("click", (e) => {
- $checkBox.click();
- }), $checkBox.addEventListener("input", (e) => {
- const $option = getOptionAtIndex(visibleIndex);
- $option && ($option.selected = e.target.checked), BxEvent.dispatch($select, "input");
- });
- else $content = CE("div", {}, $label = CE("label", { for: $select.id + "_checkbox" }, ""));
- const getOptionAtIndex = (index) => {
- return Array.from($select.querySelectorAll("option"))[index];
- }, render = (e) => {
- if (e && e.manualTrigger) visibleIndex = $select.selectedIndex;
- visibleIndex = normalizeIndex(visibleIndex);
- const $option = getOptionAtIndex(visibleIndex);
- let content = "";
- if ($option) if (content = $option.textContent || "", content && $option.parentElement.tagName === "OPTGROUP") {
- $label.innerHTML = "";
- const fragment = document.createDocumentFragment();
- fragment.appendChild(CE("span", {}, $option.parentElement.label)), fragment.appendChild(document.createTextNode(content)), $label.appendChild(fragment);
- } else $label.textContent = content;
- else $label.textContent = content;
- if ($label.classList.toggle("bx-line-through", $option && $option.disabled), isMultiple) $checkBox.checked = $option?.selected || !1, $checkBox.classList.toggle("bx-gone", !content);
- const disablePrev = visibleIndex <= 0, disableNext = visibleIndex === $select.querySelectorAll("option").length - 1;
- $btnPrev.classList.toggle("bx-inactive", disablePrev), $btnNext.classList.toggle("bx-inactive", disableNext), disablePrev && !disableNext && document.activeElement === $btnPrev && $btnNext.focus(), disableNext && !disablePrev && document.activeElement === $btnNext && $btnPrev.focus();
- }, normalizeIndex = (index) => {
- return Math.min(Math.max(index, 0), $select.querySelectorAll("option").length - 1);
- }, onPrevNext = (e) => {
- if (!e.target) return;
- const goNext = e.target.closest("button") === $btnNext, currentIndex = visibleIndex;
- let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
- if (newIndex = normalizeIndex(newIndex), visibleIndex = newIndex, !isMultiple && newIndex !== currentIndex) $select.selectedIndex = newIndex;
- if (isMultiple) render();
- else BxEvent.dispatch($select, "input");
- };
- $select.addEventListener("input", render), $btnPrev.addEventListener("click", onPrevNext), $btnNext.addEventListener("click", onPrevNext), new MutationObserver((mutationList, observer2) => {
- mutationList.forEach((mutation) => {
- if (mutation.type === "childList" || mutation.type === "attributes") render();
- });
- }).observe($select, {
- subtree: !0,
- childList: !0,
- attributes: !0
- }), render();
- const $div = CE("div", {
- class: "bx-select",
- _nearby: {
- orientation: "horizontal",
- focus: $btnNext
- }
- }, $select, $btnPrev, $content, $btnNext);
- return Object.defineProperty($div, "value", {
- get() {
- return $select.value;
- },
- set(value) {
- $div.setValue(value);
- }
- }), $div.addEventListener = function() {
- $select.addEventListener.apply($select, arguments);
- }, $div.removeEventListener = function() {
- $select.removeEventListener.apply($select, arguments);
- }, $div.dispatchEvent = function() {
- return $select.dispatchEvent.apply($select, arguments);
- }, $div.setValue = (value) => {
- if ("setValue" in $select) $select.setValue(value);
- else $select.value = value;
- }, $div;
- }
-}
var controller_shortcuts_default = "if (window.BX_EXPOSED.disableGamepadPolling) {\n this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(50) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, 50);\n return;\n}\n\nconst currentGamepad = ${gamepadVar};\n\n// Share button on XS controller\nif (currentGamepad.buttons[17] && currentGamepad.buttons[17].pressed) {\n window.dispatchEvent(new Event(BxEvent.CAPTURE_SCREENSHOT));\n}\n\nconst btnHome = currentGamepad.buttons[16];\nif (btnHome) {\n if (!this.bxHomeStates) {\n this.bxHomeStates = {};\n }\n\n let intervalMs = 0;\n let hijack = false;\n\n if (btnHome.pressed) {\n hijack = true;\n intervalMs = 16;\n this.gamepadIsIdle.set(currentGamepad.index, false);\n\n if (this.bxHomeStates[currentGamepad.index]) {\n const lastTimestamp = this.bxHomeStates[currentGamepad.index].timestamp;\n\n if (currentGamepad.timestamp !== lastTimestamp) {\n this.bxHomeStates[currentGamepad.index].timestamp = currentGamepad.timestamp;\n\n const handled = window.BX_EXPOSED.handleControllerShortcut(currentGamepad);\n if (handled) {\n this.bxHomeStates[currentGamepad.index].shortcutPressed += 1;\n }\n }\n } else {\n // First time pressing > save current timestamp\n window.BX_EXPOSED.resetControllerShortcut(currentGamepad.index);\n this.bxHomeStates[currentGamepad.index] = {\n shortcutPressed: 0,\n timestamp: currentGamepad.timestamp,\n };\n }\n } else if (this.bxHomeStates[currentGamepad.index]) {\n hijack = true;\n const info = structuredClone(this.bxHomeStates[currentGamepad.index]);\n\n // Home button released\n this.bxHomeStates[currentGamepad.index] = null;\n\n if (info.shortcutPressed === 0) {\n const fakeGamepadMappings = [{\n GamepadIndex: currentGamepad.index,\n A: 0,\n B: 0,\n X: 0,\n Y: 0,\n LeftShoulder: 0,\n RightShoulder: 0,\n LeftTrigger: 0,\n RightTrigger: 0,\n View: 0,\n Menu: 0,\n LeftThumb: 0,\n RightThumb: 0,\n DPadUp: 0,\n DPadDown: 0,\n DPadLeft: 0,\n DPadRight: 0,\n Nexus: 1,\n LeftThumbXAxis: 0,\n LeftThumbYAxis: 0,\n RightThumbXAxis: 0,\n RightThumbYAxis: 0,\n PhysicalPhysicality: 0,\n VirtualPhysicality: 0,\n Dirty: true,\n Virtual: false,\n }];\n\n const isLongPress = (currentGamepad.timestamp - info.timestamp) >= 500;\n intervalMs = isLongPress ? 500 : 100;\n\n this.inputSink.onGamepadInput(performance.now() - intervalMs, fakeGamepadMappings);\n } else {\n intervalMs = 4;\n }\n }\n\n if (hijack && intervalMs) {\n // Listen to next button press\n this.inputConfiguration.useIntervalWorkerThreadForInput && this.intervalWorker ? this.intervalWorker.scheduleTimer(intervalMs) : this.pollGamepadssetTimeoutTimerID = setTimeout(this.pollGamepads, intervalMs);\n\n // Hijack this button\n return;\n }\n}\n";
var expose_stream_session_default = "window.BX_EXPOSED.streamSession = this;\n\nconst orgSetMicrophoneState = this.setMicrophoneState.bind(this);\nthis.setMicrophoneState = state => {\n orgSetMicrophoneState(state);\n\n const evt = new Event(BxEvent.MICROPHONE_STATE_CHANGED);\n evt.microphoneState = state;\n\n window.dispatchEvent(evt);\n};\n\nwindow.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));\n\n// Patch updateDimensions() to make native touch work correctly with WebGL2\nlet updateDimensionsStr = this.updateDimensions.toString();\n\nif (updateDimensionsStr.startsWith('function ')) {\n updateDimensionsStr = updateDimensionsStr.substring(9);\n}\n\n// if(r){\nconst renderTargetVar = updateDimensionsStr.match(/if\\((\\w+)\\){/)[1];\n\nupdateDimensionsStr = updateDimensionsStr.replaceAll(renderTargetVar + '.scroll', 'scroll');\n\nupdateDimensionsStr = updateDimensionsStr.replace(`if(${renderTargetVar}){`, `\nif (${renderTargetVar}) {\n const scrollWidth = ${renderTargetVar}.dataset.width ? parseInt(${renderTargetVar}.dataset.width) : ${renderTargetVar}.scrollWidth;\n const scrollHeight = ${renderTargetVar}.dataset.height ? parseInt(${renderTargetVar}.dataset.height) : ${renderTargetVar}.scrollHeight;\n`);\n\neval(`this.updateDimensions = function ${updateDimensionsStr}`);\n";
var local_co_op_enable_default = "let match;\nlet onGamepadChangedStr = this.onGamepadChanged.toString();\n\nif (onGamepadChangedStr.startsWith('function ')) {\n onGamepadChangedStr = onGamepadChangedStr.substring(9);\n}\n\nonGamepadChangedStr = onGamepadChangedStr.replaceAll('0', 'arguments[1]');\neval(`this.onGamepadChanged = function ${onGamepadChangedStr}`);\n\nlet onGamepadInputStr = this.onGamepadInput.toString();\n\nmatch = onGamepadInputStr.match(/(\\w+\\.GamepadIndex)/);\nif (match) {\n const gamepadIndexVar = match[0];\n onGamepadInputStr = onGamepadInputStr.replace('this.gamepadStates.get(', `this.gamepadStates.get(${gamepadIndexVar},`);\n eval(`this.onGamepadInput = function ${onGamepadInputStr}`);\n BxLogger.info('supportLocalCoOp', '✅ Successfully patched local co-op support');\n} else {\n BxLogger.error('supportLocalCoOp', '❌ Unable to patch local co-op support');\n}\n";
@@ -3396,7 +3731,7 @@ class PatcherUtils {
return txt.substring(0, index) + toString + txt.substring(index + fromString.length);
}
}
-var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG3 = "Patcher", PATCHES = {
+var ENDING_CHUNKS_PATCH_NAME = "loadingEndingChunks", LOG_TAG4 = "Patcher", PATCHES = {
disableAiTrack(str) {
let text = ".track=function(";
const index = str.indexOf(text);
@@ -3528,7 +3863,7 @@ logFunc(logTag, '//', logMessage);
loadingEndingChunks(str) {
let text = '"FamilySagaManager"';
if (!str.includes(text)) return !1;
- return BxLogger.info(LOG_TAG3, "Remaining patches:", PATCH_ORDERS), PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS), str;
+ return BxLogger.info(LOG_TAG4, "Remaining patches:", PATCH_ORDERS), PATCH_ORDERS = PATCH_ORDERS.concat(PLAYING_PATCH_ORDERS), str;
},
disableStreamGate(str) {
const index = str.indexOf('case"partially-ready":');
@@ -3897,7 +4232,7 @@ class Patcher {
if (arguments[1] === 0 || typeof arguments[1] === "function") valid = !0;
}
if (!valid) return nativeBind.apply(this, arguments);
- if (PatcherCache.init(), typeof arguments[1] === "function") BxLogger.info(LOG_TAG3, "Restored Function.prototype.bind()"), Function.prototype.bind = nativeBind;
+ if (PatcherCache.init(), typeof arguments[1] === "function") BxLogger.info(LOG_TAG4, "Restored Function.prototype.bind()"), Function.prototype.bind = nativeBind;
const orgFunc = this, newFunc = (a, item2) => {
Patcher.patch(item2), orgFunc(a, item2);
};
@@ -3921,12 +4256,12 @@ class Patcher {
if (!PATCHES[patchName]) continue;
const tmpStr = PATCHES[patchName].call(null, patchedFuncStr);
if (!tmpStr) continue;
- modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG3, `✅ ${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName);
+ modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG4, `✅ ${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName);
}
if (modified) try {
item[1][id] = eval(patchedFuncStr);
} catch (e) {
- if (e instanceof Error) BxLogger.error(LOG_TAG3, "Error", appliedPatches, e.message, patchedFuncStr);
+ if (e instanceof Error) BxLogger.error(LOG_TAG4, "Error", appliedPatches, e.message, patchedFuncStr);
}
if (appliedPatches.length) patchesMap[id] = appliedPatches;
}
@@ -3950,8 +4285,8 @@ class PatcherCache {
}
static checkSignature() {
const storedSig = window.localStorage.getItem(PatcherCache.#KEY_SIGNATURE) || 0, currentSig = PatcherCache.#getSignature();
- if (currentSig !== parseInt(storedSig)) BxLogger.warning(LOG_TAG3, "Signature changed"), window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString()), PatcherCache.clear();
- else BxLogger.info(LOG_TAG3, "Signature unchanged");
+ if (currentSig !== parseInt(storedSig)) BxLogger.warning(LOG_TAG4, "Signature changed"), window.localStorage.setItem(PatcherCache.#KEY_SIGNATURE, currentSig.toString()), PatcherCache.clear();
+ else BxLogger.info(LOG_TAG4, "Signature unchanged");
}
static #cleanupPatches(patches) {
return patches.filter((item2) => {
@@ -3975,9 +4310,9 @@ class PatcherCache {
}
static init() {
if (PatcherCache.#isInitialized) return;
- if (PatcherCache.#isInitialized = !0, PatcherCache.checkSignature(), PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || "{}"), BxLogger.info(LOG_TAG3, PatcherCache.#CACHE), window.location.pathname.includes("/play/")) PATCH_ORDERS.push(...PLAYING_PATCH_ORDERS);
+ if (PatcherCache.#isInitialized = !0, PatcherCache.checkSignature(), PatcherCache.#CACHE = JSON.parse(window.localStorage.getItem(PatcherCache.#KEY_CACHE) || "{}"), BxLogger.info(LOG_TAG4, PatcherCache.#CACHE), window.location.pathname.includes("/play/")) PATCH_ORDERS.push(...PLAYING_PATCH_ORDERS);
else PATCH_ORDERS.push(ENDING_CHUNKS_PATCH_NAME);
- PATCH_ORDERS = PatcherCache.#cleanupPatches(PATCH_ORDERS), PLAYING_PATCH_ORDERS = PatcherCache.#cleanupPatches(PLAYING_PATCH_ORDERS), BxLogger.info(LOG_TAG3, PATCH_ORDERS.slice(0)), BxLogger.info(LOG_TAG3, PLAYING_PATCH_ORDERS.slice(0));
+ PATCH_ORDERS = PatcherCache.#cleanupPatches(PATCH_ORDERS), PLAYING_PATCH_ORDERS = PatcherCache.#cleanupPatches(PLAYING_PATCH_ORDERS), BxLogger.info(LOG_TAG4, PATCH_ORDERS.slice(0)), BxLogger.info(LOG_TAG4, PLAYING_PATCH_ORDERS.slice(0));
}
}
class FullscreenText {
@@ -4927,342 +5262,6 @@ class SettingsNavigationDialog extends NavigationDialog {
return handled;
}
}
-var LOG_TAG4 = "MkbHandler", PointerToMouseButton = {
- 1: 0,
- 2: 2,
- 4: 1
-};
-class WebSocketMouseDataProvider extends MouseDataProvider {
- #pointerClient;
- #connected = !1;
- init() {
- this.#pointerClient = PointerClient.getInstance(), this.#connected = !1;
- try {
- this.#pointerClient.start(STATES.pointerServerPort, this.mkbHandler), this.#connected = !0;
- } catch (e) {
- Toast.show("Cannot enable Mouse & Keyboard feature");
- }
- }
- start() {
- this.#connected && AppInterface.requestPointerCapture();
- }
- stop() {
- this.#connected && AppInterface.releasePointerCapture();
- }
- destroy() {
- this.#connected && this.#pointerClient?.stop();
- }
-}
-class PointerLockMouseDataProvider extends MouseDataProvider {
- init() {}
- start() {
- window.addEventListener("mousemove", this.#onMouseMoveEvent), window.addEventListener("mousedown", this.#onMouseEvent), window.addEventListener("mouseup", this.#onMouseEvent), window.addEventListener("wheel", this.#onWheelEvent, { passive: !1 }), window.addEventListener("contextmenu", this.#disableContextMenu);
- }
- stop() {
- document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("mousemove", this.#onMouseMoveEvent), window.removeEventListener("mousedown", this.#onMouseEvent), window.removeEventListener("mouseup", this.#onMouseEvent), window.removeEventListener("wheel", this.#onWheelEvent), window.removeEventListener("contextmenu", this.#disableContextMenu);
- }
- destroy() {}
- #onMouseMoveEvent = (e) => {
- this.mkbHandler.handleMouseMove({
- movementX: e.movementX,
- movementY: e.movementY
- });
- };
- #onMouseEvent = (e) => {
- e.preventDefault();
- const isMouseDown = e.type === "mousedown", data = {
- mouseButton: e.button,
- pressed: isMouseDown
- };
- this.mkbHandler.handleMouseClick(data);
- };
- #onWheelEvent = (e) => {
- if (!KeyHelper.getKeyFromEvent(e)) return;
- const data = {
- vertical: e.deltaY,
- horizontal: e.deltaX
- };
- if (this.mkbHandler.handleMouseWheel(data)) e.preventDefault();
- };
- #disableContextMenu = (e) => e.preventDefault();
-}
-class EmulatedMkbHandler extends MkbHandler {
- static #instance;
- static getInstance() {
- if (!EmulatedMkbHandler.#instance) EmulatedMkbHandler.#instance = new EmulatedMkbHandler;
- return EmulatedMkbHandler.#instance;
- }
- #CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
- static DEFAULT_PANNING_SENSITIVITY = 0.001;
- static DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
- static MAXIMUM_STICK_RANGE = 1.1;
- static VIRTUAL_GAMEPAD_ID = "Xbox 360 Controller";
- #VIRTUAL_GAMEPAD = {
- id: EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID,
- index: 3,
- connected: !1,
- hapticActuators: null,
- mapping: "standard",
- axes: [0, 0, 0, 0],
- buttons: new Array(17).fill(null).map(() => ({ pressed: !1, value: 0 })),
- timestamp: performance.now(),
- vibrationActuator: null
- };
- #nativeGetGamepads = window.navigator.getGamepads.bind(window.navigator);
- #enabled = !1;
- #mouseDataProvider;
- #isPolling = !1;
- #prevWheelCode = null;
- #wheelStoppedTimeout;
- #detectMouseStoppedTimeout;
- #$message;
- #escKeyDownTime = -1;
- #STICK_MAP;
- #LEFT_STICK_X = [];
- #LEFT_STICK_Y = [];
- #RIGHT_STICK_X = [];
- #RIGHT_STICK_Y = [];
- constructor() {
- super();
- this.#STICK_MAP = {
- 102: [this.#LEFT_STICK_X, 0, -1],
- 103: [this.#LEFT_STICK_X, 0, 1],
- 100: [this.#LEFT_STICK_Y, 1, -1],
- 101: [this.#LEFT_STICK_Y, 1, 1],
- 202: [this.#RIGHT_STICK_X, 2, -1],
- 203: [this.#RIGHT_STICK_X, 2, 1],
- 200: [this.#RIGHT_STICK_Y, 3, -1],
- 201: [this.#RIGHT_STICK_Y, 3, 1]
- };
- }
- isEnabled = () => this.#enabled;
- #patchedGetGamepads = () => {
- const gamepads = this.#nativeGetGamepads() || [];
- return gamepads[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD, gamepads;
- };
- #getVirtualGamepad = () => this.#VIRTUAL_GAMEPAD;
- #updateStick(stick, x, y) {
- const virtualGamepad = this.#getVirtualGamepad();
- virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now();
- }
- #vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2);
- #resetGamepad = () => {
- const gamepad = this.#getVirtualGamepad();
- gamepad.axes = [0, 0, 0, 0];
- for (let button of gamepad.buttons)
- button.pressed = !1, button.value = 0;
- gamepad.timestamp = performance.now();
- };
- #pressButton = (buttonIndex, pressed) => {
- const virtualGamepad = this.#getVirtualGamepad();
- if (buttonIndex >= 100) {
- let [valueArr, axisIndex] = this.#STICK_MAP[buttonIndex];
- valueArr = valueArr, axisIndex = axisIndex;
- for (let i = valueArr.length - 1;i >= 0; i--)
- if (valueArr[i] === buttonIndex) valueArr.splice(i, 1);
- pressed && valueArr.push(buttonIndex);
- let value;
- if (valueArr.length) value = this.#STICK_MAP[valueArr[valueArr.length - 1]][2];
- else value = 0;
- virtualGamepad.axes[axisIndex] = value;
- } else virtualGamepad.buttons[buttonIndex].pressed = pressed, virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0;
- virtualGamepad.timestamp = performance.now();
- };
- #onKeyboardEvent = (e) => {
- const isKeyDown = e.type === "keydown";
- if (e.code === "F8") {
- if (!isKeyDown) e.preventDefault(), this.toggle();
- return;
- }
- if (e.code === "Escape") {
- if (e.preventDefault(), this.#enabled && isKeyDown) {
- if (this.#escKeyDownTime === -1) this.#escKeyDownTime = performance.now();
- else if (performance.now() - this.#escKeyDownTime >= 1000) this.stop();
- } else this.#escKeyDownTime = -1;
- return;
- }
- if (!this.#isPolling) return;
- const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key];
- if (typeof buttonIndex === "undefined") return;
- if (e.repeat) return;
- e.preventDefault(), this.#pressButton(buttonIndex, isKeyDown);
- };
- #onMouseStopped = () => {
- this.#detectMouseStoppedTimeout = null;
- const analog = this.#CURRENT_PRESET_DATA.mouse["map_to"] === 1 ? 0 : 1;
- this.#updateStick(analog, 0, 0);
- };
- handleMouseClick = (data) => {
- let mouseButton;
- if (typeof data.mouseButton !== "undefined") mouseButton = data.mouseButton;
- else if (typeof data.pointerButton !== "undefined") mouseButton = PointerToMouseButton[data.pointerButton];
- const keyCode = "Mouse" + mouseButton, key = {
- code: keyCode,
- name: KeyHelper.codeToKeyName(keyCode)
- };
- if (!key.name) return;
- const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
- if (typeof buttonIndex === "undefined") return;
- this.#pressButton(buttonIndex, data.pressed);
- };
- handleMouseMove = (data) => {
- const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse["map_to"];
- if (mouseMapTo === 0) return;
- this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout), this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50);
- const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse["deadzone_counterweight"];
- let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse["sensitivity_x"], y = data.movementY * this.#CURRENT_PRESET_DATA.mouse["sensitivity_y"], length = this.#vectorLength(x, y);
- if (length !== 0 && length < deadzoneCounterweight) x *= deadzoneCounterweight / length, y *= deadzoneCounterweight / length;
- else if (length > EmulatedMkbHandler.MAXIMUM_STICK_RANGE) x *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length, y *= EmulatedMkbHandler.MAXIMUM_STICK_RANGE / length;
- const analog = mouseMapTo === 1 ? 0 : 1;
- this.#updateStick(analog, x, y);
- };
- handleMouseWheel = (data) => {
- let code = "";
- if (data.vertical < 0) code = "ScrollUp";
- else if (data.vertical > 0) code = "ScrollDown";
- else if (data.horizontal < 0) code = "ScrollLeft";
- else if (data.horizontal > 0) code = "ScrollRight";
- if (!code) return !1;
- const key = {
- code,
- name: KeyHelper.codeToKeyName(code)
- }, buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code];
- if (typeof buttonIndex === "undefined") return !1;
- if (this.#prevWheelCode === null || this.#prevWheelCode === key.code) this.#wheelStoppedTimeout && clearTimeout(this.#wheelStoppedTimeout), this.#pressButton(buttonIndex, !0);
- return this.#wheelStoppedTimeout = window.setTimeout(() => {
- this.#prevWheelCode = null, this.#pressButton(buttonIndex, !1);
- }, 20), !0;
- };
- toggle = (force) => {
- if (typeof force !== "undefined") this.#enabled = force;
- else this.#enabled = !this.#enabled;
- if (this.#enabled) document.body.requestPointerLock();
- else document.pointerLockElement && document.exitPointerLock();
- };
- #getCurrentPreset = () => {
- return new Promise((resolve) => {
- const presetId = getPref("mkb_default_preset_id");
- LocalDb.INSTANCE.getPreset(presetId).then((preset) => {
- resolve(preset);
- });
- });
- };
- refreshPresetData = () => {
- this.#getCurrentPreset().then((preset) => {
- this.#CURRENT_PRESET_DATA = MkbPreset.convert(preset ? preset.data : MkbPreset.DEFAULT_PRESET), this.#resetGamepad();
- });
- };
- waitForMouseData = (wait) => {
- this.#$message && this.#$message.classList.toggle("bx-gone", !wait);
- };
- #onPollingModeChanged = (e) => {
- if (!this.#$message) return;
- if (e.mode === "none") this.#$message.classList.remove("bx-offscreen");
- else this.#$message.classList.add("bx-offscreen");
- };
- #onDialogShown = () => {
- document.pointerLockElement && document.exitPointerLock();
- };
- #initMessage = () => {
- if (!this.#$message) this.#$message = CE("div", { class: "bx-mkb-pointer-lock-msg bx-gone" }, CE("div", {}, CE("p", {}, t("virtual-controller")), CE("p", {}, t("press-key-to-toggle-mkb", { key: "F8" }))), CE("div", { "data-type": "virtual" }, createButton({
- style: 1 | 256 | 64,
- label: t("activate"),
- onClick: ((e) => {
- e.preventDefault(), e.stopPropagation(), this.toggle(!0);
- }).bind(this)
- }), CE("div", {}, createButton({
- label: t("ignore"),
- style: 4,
- onClick: (e) => {
- e.preventDefault(), e.stopPropagation(), this.toggle(!1), this.waitForMouseData(!1);
- }
- }), createButton({
- label: t("edit"),
- onClick: (e) => {
- e.preventDefault(), e.stopPropagation();
- const dialog = SettingsNavigationDialog.getInstance();
- dialog.focusTab("mkb"), NavigationDialogManager.getInstance().show(dialog);
- }
- }))));
- if (!this.#$message.isConnected) document.documentElement.appendChild(this.#$message);
- };
- #onPointerLockChange = () => {
- if (document.pointerLockElement) this.start();
- else this.stop();
- };
- #onPointerLockError = (e) => {
- console.log(e), this.stop();
- };
- #onPointerLockRequested = () => {
- this.start();
- };
- #onPointerLockExited = () => {
- this.#mouseDataProvider?.stop();
- };
- handleEvent(event) {
- switch (event.type) {
- case BxEvent.POINTER_LOCK_REQUESTED:
- this.#onPointerLockRequested();
- break;
- case BxEvent.POINTER_LOCK_EXITED:
- this.#onPointerLockExited();
- break;
- }
- }
- init = () => {
- if (this.refreshPresetData(), this.#enabled = !1, AppInterface) this.#mouseDataProvider = new WebSocketMouseDataProvider(this);
- else this.#mouseDataProvider = new PointerLockMouseDataProvider(this);
- if (this.#mouseDataProvider.init(), window.addEventListener("keydown", this.#onKeyboardEvent), window.addEventListener("keyup", this.#onKeyboardEvent), window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), AppInterface) window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
- else document.addEventListener("pointerlockchange", this.#onPointerLockChange), document.addEventListener("pointerlockerror", this.#onPointerLockError);
- if (this.#initMessage(), this.#$message?.classList.add("bx-gone"), AppInterface) Toast.show(t("press-key-to-toggle-mkb", { key: "F8" }), t("virtual-controller"), { html: !0 }), this.waitForMouseData(!1);
- else this.waitForMouseData(!0);
- };
- destroy = () => {
- if (this.#isPolling = !1, this.#enabled = !1, this.stop(), this.waitForMouseData(!1), document.pointerLockElement && document.exitPointerLock(), window.removeEventListener("keydown", this.#onKeyboardEvent), window.removeEventListener("keyup", this.#onKeyboardEvent), AppInterface) window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this), window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
- else document.removeEventListener("pointerlockchange", this.#onPointerLockChange), document.removeEventListener("pointerlockerror", this.#onPointerLockError);
- window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged), window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.#onDialogShown), this.#mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged);
- };
- start = () => {
- if (!this.#enabled) this.#enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
- this.#isPolling = !0, this.#escKeyDownTime = -1, this.#resetGamepad(), window.navigator.getGamepads = this.#patchedGetGamepads, this.waitForMouseData(!1), this.#mouseDataProvider?.start();
- const virtualGamepad = this.#getVirtualGamepad();
- virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", {
- gamepad: virtualGamepad
- }), window.BX_EXPOSED.stopTakRendering = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
- };
- stop = () => {
- this.#enabled = !1, this.#isPolling = !1, this.#escKeyDownTime = -1;
- const virtualGamepad = this.#getVirtualGamepad();
- if (virtualGamepad.connected) this.#resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", {
- gamepad: virtualGamepad
- }), window.navigator.getGamepads = this.#nativeGetGamepads;
- this.waitForMouseData(!0), this.#mouseDataProvider?.stop();
- };
- static setupEvents() {
- window.addEventListener(BxEvent.STREAM_PLAYING, () => {
- if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
- if (AppInterface && getPref("native_mkb_enabled") === "on") AppInterface && NativeMkbHandler.getInstance().init();
- } else if (getPref("mkb_enabled") && (AppInterface || !UserAgent.isMobile())) BxLogger.info(LOG_TAG4, "Emulate MKB"), EmulatedMkbHandler.getInstance().init();
- });
- }
-}
-class MicrophoneShortcut {
- static toggle(showToast = !0) {
- if (!window.BX_EXPOSED.streamSession) return !1;
- const enableMic = window.BX_EXPOSED.streamSession._microphoneState === "Enabled" ? !1 : !0;
- try {
- return window.BX_EXPOSED.streamSession.tryEnableChatAsync(enableMic), showToast && Toast.show(t("microphone"), t(enableMic ? "unmuted" : "muted"), { instant: !0 }), enableMic;
- } catch (e) {
- console.log(e);
- }
- return !1;
- }
-}
-class StreamUiShortcut {
- static showHideStreamMenu() {
- window.BX_EXPOSED.showStreamMenu && window.BX_EXPOSED.showStreamMenu();
- }
-}
class ControllerShortcut {
static #STORAGE_KEY = "better_xcloud_controller_shortcuts";
static #buttonsCache = {};
@@ -5345,7 +5344,7 @@ class ControllerShortcut {
let hasGamepad = !1;
for (let gamepad of gamepads) {
if (!gamepad || !gamepad.connected) continue;
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) continue;
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) continue;
hasGamepad = !0;
const $option = CE("option", { value: gamepad.id }, gamepad.id);
$fragment.appendChild($option);
@@ -5459,7 +5458,7 @@ class ControllerShortcut {
}
var BxExposed = {
getTitleInfo: () => STATES.currentStream.titleInfo,
- modifyTitleInfo: (titleInfo) => {
+ modifyTitleInfo: function(titleInfo) {
titleInfo = deepClone(titleInfo);
let supportedInputTypes = titleInfo.details.supportedInputTypes;
if (BX_FLAGS.ForceNativeMkbTitles?.includes(titleInfo.details.productId)) supportedInputTypes.push("MKB");
@@ -6583,7 +6582,7 @@ function interceptHttpRequests() {
};
}
function showGamepadToast(gamepad) {
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) return;
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) return;
BxLogger.info("Gamepad", gamepad);
let text = "🎮";
if (getPref("local_co_op_enabled")) text += ` #${gamepad.index + 1}`;
diff --git a/src/modules/controller-shortcut.ts b/src/modules/controller-shortcut.ts
index d1a98e4..b42d481 100644
--- a/src/modules/controller-shortcut.ts
+++ b/src/modules/controller-shortcut.ts
@@ -3,7 +3,6 @@ import { GamepadKey } from "@enums/mkb";
import { PrompFont } from "@enums/prompt-font";
import { CE, removeChildElements } from "@utils/html";
import { t } from "@utils/translation";
-import { EmulatedMkbHandler } from "./mkb/mkb-handler";
import { StreamStats } from "./stream/stream-stats";
import { MicrophoneShortcut } from "./shortcuts/shortcut-microphone";
import { StreamUiShortcut } from "./shortcuts/shortcut-stream-ui";
@@ -15,6 +14,7 @@ import { setNearby } from "@/utils/navigation-utils";
import { PrefKey } from "@/enums/pref-keys";
import { getPref } from "@/utils/settings-storages/global-settings-storage";
import { SettingsNavigationDialog } from "./ui/dialog/settings-dialog";
+import { VIRTUAL_GAMEPAD_ID } from "./mkb/mkb-handler";
const enum ShortcutAction {
BETTER_XCLOUD_SETTINGS_SHOW = 'bx-settings-show',
@@ -185,7 +185,7 @@ export class ControllerShortcut {
}
// Ignore emulated gamepad
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) {
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) {
continue;
}
diff --git a/src/modules/mkb/mkb-handler.ts b/src/modules/mkb/mkb-handler.ts
index 22b6d99..84ef697 100644
--- a/src/modules/mkb/mkb-handler.ts
+++ b/src/modules/mkb/mkb-handler.ts
@@ -26,6 +26,7 @@ const PointerToMouseButton = {
4: 1,
}
+export const VIRTUAL_GAMEPAD_ID = 'Xbox 360 Controller';
class WebSocketMouseDataProvider extends MouseDataProvider {
#pointerClient: PointerClient | undefined
@@ -136,10 +137,8 @@ export class EmulatedMkbHandler extends MkbHandler {
static readonly DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
static readonly MAXIMUM_STICK_RANGE = 1.1;
- static VIRTUAL_GAMEPAD_ID = 'Xbox 360 Controller';
-
#VIRTUAL_GAMEPAD = {
- id: EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID,
+ id: VIRTUAL_GAMEPAD_ID,
index: 3,
connected: false,
hapticActuators: null,
diff --git a/src/modules/ui/dialog/navigation-dialog.ts b/src/modules/ui/dialog/navigation-dialog.ts
index 788029b..d1b2c5c 100644
--- a/src/modules/ui/dialog/navigation-dialog.ts
+++ b/src/modules/ui/dialog/navigation-dialog.ts
@@ -1,6 +1,6 @@
import { GamepadKey } from "@/enums/mkb";
import { PrefKey } from "@/enums/pref-keys";
-import { EmulatedMkbHandler } from "@/modules/mkb/mkb-handler";
+import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
import { BxEvent } from "@/utils/bx-event";
import { STATES } from "@/utils/global";
import { CE, isElementVisible } from "@/utils/html";
@@ -263,7 +263,7 @@ export class NavigationDialogManager {
}
// Ignore virtual controller
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) {
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) {
continue;
}
diff --git a/src/modules/ui/dialog/settings-dialog.ts b/src/modules/ui/dialog/settings-dialog.ts
index ea42fc0..355593e 100644
--- a/src/modules/ui/dialog/settings-dialog.ts
+++ b/src/modules/ui/dialog/settings-dialog.ts
@@ -509,7 +509,7 @@ export class SettingsNavigationDialog extends NavigationDialog {
group: 'mkb',
label: t('virtual-controller'),
helpUrl: 'https://better-xcloud.github.io/mouse-and-keyboard/',
- content: MkbRemapper.INSTANCE.render(),
+ content: isFullVersion() && MkbRemapper.INSTANCE.render(),
}];
private readonly TAB_NATIVE_MKB_ITEMS: Array = [{
diff --git a/src/utils/gamepad.ts b/src/utils/gamepad.ts
index 561da74..dd4c2ef 100644
--- a/src/utils/gamepad.ts
+++ b/src/utils/gamepad.ts
@@ -1,4 +1,4 @@
-import { EmulatedMkbHandler } from "@modules/mkb/mkb-handler";
+import { VIRTUAL_GAMEPAD_ID } from "@modules/mkb/mkb-handler";
import { t } from "@utils/translation";
import { Toast } from "@utils/toast";
import { BxLogger } from "@utils/bx-logger";
@@ -8,7 +8,7 @@ import { getPref } from "./settings-storages/global-settings-storage";
// Show a toast when connecting/disconecting controller
export function showGamepadToast(gamepad: Gamepad) {
// Don't show Toast for virtual controller
- if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) {
+ if (gamepad.id === VIRTUAL_GAMEPAD_ID) {
return;
}
diff --git a/src/utils/xhome-interceptor.ts b/src/utils/xhome-interceptor.ts
index 385a9ca..c39073a 100644
--- a/src/utils/xhome-interceptor.ts
+++ b/src/utils/xhome-interceptor.ts
@@ -1,5 +1,3 @@
-import { isFullVersion } from "@macros/build" with {type: "macro"};
-
import { TouchController } from "@/modules/touch-controller";
import { BxEvent } from "./bx-event";
import { SupportedInputType } from "./bx-exposed";