Reduce Virtual Controller's input latency

This commit is contained in:
redphx 2025-01-08 21:16:07 +07:00
parent 8d7fbf2804
commit 153873e034
10 changed files with 300 additions and 229 deletions

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud (Lite) // @name Better xCloud (Lite)
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 6.2.0 // @version 6.2.1-beta
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -105,7 +105,7 @@ class UserAgent {
}); });
} }
} }
var SCRIPT_VERSION = "6.2.0", SCRIPT_VARIANT = "lite", AppInterface = window.AppInterface; var SCRIPT_VERSION = "6.2.1-beta", SCRIPT_VARIANT = "lite", AppInterface = window.AppInterface;
UserAgent.init(); UserAgent.init();
var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = { var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {
supportedRegion: !0, supportedRegion: !0,
@ -2792,17 +2792,37 @@ function hasGamepad() {
if (gamepad?.connected) return !0; if (gamepad?.connected) return !0;
return !1; return !1;
} }
class StreamSettings { function generateVirtualControllerMapping(index, override = {}) {
static settings = { return Object.assign({}, {
settings: {}, GamepadIndex: index,
xCloudPollingMode: "all", A: 0,
deviceVibrationIntensity: 0, B: 0,
controllerPollingRate: 4, X: 0,
controllers: {}, Y: 0,
mkbPreset: null, LeftShoulder: 0,
keyboardShortcuts: {} RightShoulder: 0,
}; LeftTrigger: 0,
static CONTROLLER_CUSTOMIZATION_MAPPING = { RightTrigger: 0,
View: 0,
Menu: 0,
LeftThumb: 0,
RightThumb: 0,
DPadUp: 0,
DPadDown: 0,
DPadLeft: 0,
DPadRight: 0,
Nexus: 0,
LeftThumbXAxis: 0,
LeftThumbYAxis: 0,
RightThumbXAxis: 0,
RightThumbYAxis: 0,
PhysicalPhysicality: 0,
VirtualPhysicality: 0,
Dirty: !1,
Virtual: !1
}, override);
}
var XCLOUD_GAMEPAD_KEY_MAPPING = {
0: "A", 0: "A",
1: "B", 1: "B",
2: "X", 2: "X",
@ -2821,7 +2841,29 @@ class StreamSettings {
204: "RightStickAxes", 204: "RightStickAxes",
8: "View", 8: "View",
9: "Menu", 9: "Menu",
17: "Share" 16: "Nexus",
17: "Share",
102: "LeftThumbXAxis",
103: "LeftThumbXAxis",
100: "LeftThumbYAxis",
101: "LeftThumbYAxis",
202: "RightThumbXAxis",
203: "RightThumbXAxis",
200: "RightThumbYAxis",
201: "RightThumbYAxis"
};
function toXcloudGamepadKey(gamepadKey) {
return XCLOUD_GAMEPAD_KEY_MAPPING[gamepadKey];
}
class StreamSettings {
static settings = {
settings: {},
xCloudPollingMode: "all",
deviceVibrationIntensity: 0,
controllerPollingRate: 4,
controllers: {},
mkbPreset: null,
keyboardShortcuts: {}
}; };
static getPref(key) { static getPref(key) {
return getPref(key); return getPref(key);
@ -2853,10 +2895,10 @@ class StreamSettings {
vibrationIntensity: 1 vibrationIntensity: 1
}, gamepadKey; }, gamepadKey;
for (gamepadKey in customization.mapping) { for (gamepadKey in customization.mapping) {
let gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey]; let gamepadStr = toXcloudGamepadKey(gamepadKey);
if (!gamepadStr) continue; if (!gamepadStr) continue;
let mappedKey = customization.mapping[gamepadKey]; let mappedKey = customization.mapping[gamepadKey];
if (typeof mappedKey === "number") converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey]; if (typeof mappedKey === "number") converted.mapping[gamepadStr] = toXcloudGamepadKey(mappedKey);
else converted.mapping[gamepadStr] = !1; else converted.mapping[gamepadStr] = !1;
} }
return StreamSettings.preCalculateControllerRange(converted.ranges, "LeftTrigger", customization.settings.leftTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "RightTrigger", customization.settings.rightTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "LeftThumb", customization.settings.leftStickDeadzone), StreamSettings.preCalculateControllerRange(converted.ranges, "RightThumb", customization.settings.rightStickDeadzone), converted.vibrationIntensity = customization.settings.vibrationIntensity / 100, converted; return StreamSettings.preCalculateControllerRange(converted.ranges, "LeftTrigger", customization.settings.leftTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "RightTrigger", customization.settings.rightTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "LeftThumb", customization.settings.leftStickDeadzone), StreamSettings.preCalculateControllerRange(converted.ranges, "RightThumb", customization.settings.rightStickDeadzone), converted.vibrationIntensity = customization.settings.vibrationIntensity / 100, converted;
@ -2982,7 +3024,7 @@ class NativeMkbHandler extends MkbHandler {
mouseButtonsPressed = 0; mouseButtonsPressed = 0;
mouseVerticalMultiply = 0; mouseVerticalMultiply = 0;
mouseHorizontalMultiply = 0; mouseHorizontalMultiply = 0;
inputSink; inputChannel;
popup; popup;
constructor() { constructor() {
super(); super();
@ -3024,7 +3066,7 @@ class NativeMkbHandler extends MkbHandler {
} }
} }
init() { init() {
this.pointerClient = PointerClient.getInstance(), this.inputSink = window.BX_EXPOSED.inputSink, this.updateInputConfigurationAsync(!1); this.pointerClient = PointerClient.getInstance(), this.inputChannel = window.BX_EXPOSED.inputChannel, this.updateInputConfigurationAsync(!1);
try { try {
this.pointerClient.start(STATES.pointerServerPort, this); this.pointerClient.start(STATES.pointerServerPort, this);
} catch (e) { } catch (e) {
@ -3109,7 +3151,7 @@ class NativeMkbHandler extends MkbHandler {
return this.enabled; return this.enabled;
} }
sendMouseInput(data) { sendMouseInput(data) {
data.Type = 0, this.inputSink?.onMouseInput(data); data.Type = 0, this.inputChannel?.queueMouseInput(data);
} }
resetMouseInput() { resetMouseInput() {
this.mouseButtonsPressed = 0, this.sendMouseInput({ this.mouseButtonsPressed = 0, this.sendMouseInput({
@ -3202,6 +3244,7 @@ class EmulatedMkbHandler extends MkbHandler {
vibrationActuator: null vibrationActuator: null
}; };
nativeGetGamepads; nativeGetGamepads;
xCloudGamepad = generateVirtualControllerMapping(0);
initialized = !1; initialized = !1;
enabled = !1; enabled = !1;
mouseDataProvider; mouseDataProvider;
@ -3216,14 +3259,14 @@ class EmulatedMkbHandler extends MkbHandler {
RIGHT_STICK_Y = []; RIGHT_STICK_Y = [];
popup; popup;
STICK_MAP = { STICK_MAP = {
102: [this.LEFT_STICK_X, 0, -1], 102: [this.LEFT_STICK_X, -1],
103: [this.LEFT_STICK_X, 0, 1], 103: [this.LEFT_STICK_X, 1],
100: [this.LEFT_STICK_Y, 1, -1], 100: [this.LEFT_STICK_Y, 1],
101: [this.LEFT_STICK_Y, 1, 1], 101: [this.LEFT_STICK_Y, -1],
202: [this.RIGHT_STICK_X, 2, -1], 202: [this.RIGHT_STICK_X, -1],
203: [this.RIGHT_STICK_X, 2, 1], 203: [this.RIGHT_STICK_X, 1],
200: [this.RIGHT_STICK_Y, 3, -1], 200: [this.RIGHT_STICK_Y, 1],
201: [this.RIGHT_STICK_Y, 3, 1] 201: [this.RIGHT_STICK_Y, -1]
}; };
constructor() { constructor() {
super(); super();
@ -3236,31 +3279,32 @@ class EmulatedMkbHandler extends MkbHandler {
}; };
getVirtualGamepad = () => this.VIRTUAL_GAMEPAD; getVirtualGamepad = () => this.VIRTUAL_GAMEPAD;
updateStick(stick, x, y) { updateStick(stick, x, y) {
let virtualGamepad = this.getVirtualGamepad(); let gamepad = this.xCloudGamepad;
virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now(); if (stick === 0) gamepad.LeftThumbXAxis = x, gamepad.LeftThumbYAxis = -y;
else gamepad.RightThumbXAxis = x, gamepad.RightThumbYAxis = -y;
window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
} }
vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2); vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2);
resetGamepad() { resetXcloudGamepads() {
let gamepad = this.getVirtualGamepad(); let index = getPref("mkb.p1.slot") - 1;
gamepad.axes = [0, 0, 0, 0]; this.xCloudGamepad = generateVirtualControllerMapping(0, {
for (let button of gamepad.buttons) GamepadIndex: getPref("localCoOp.enabled") ? index : 0,
button.pressed = !1, button.value = 0; Dirty: !0
gamepad.timestamp = performance.now(); }), this.VIRTUAL_GAMEPAD.index = index;
} }
pressButton(buttonIndex, pressed) { pressButton(buttonIndex, pressed) {
let virtualGamepad = this.getVirtualGamepad(); let xCloudKey = toXcloudGamepadKey(buttonIndex);
if (buttonIndex >= 100) { if (buttonIndex >= 100) {
let [valueArr, axisIndex] = this.STICK_MAP[buttonIndex]; let [valueArr] = this.STICK_MAP[buttonIndex];
valueArr = valueArr, axisIndex = axisIndex;
for (let i = valueArr.length - 1;i >= 0; i--) for (let i = valueArr.length - 1;i >= 0; i--)
if (valueArr[i] === buttonIndex) valueArr.splice(i, 1); if (valueArr[i] === buttonIndex) valueArr.splice(i, 1);
pressed && valueArr.push(buttonIndex); pressed && valueArr.push(buttonIndex);
let value; let value;
if (valueArr.length) value = this.STICK_MAP[valueArr[valueArr.length - 1]][2]; if (valueArr.length) value = this.STICK_MAP[valueArr[valueArr.length - 1]][1];
else value = 0; else value = 0;
virtualGamepad.axes[axisIndex] = value; this.xCloudGamepad[xCloudKey] = value;
} else virtualGamepad.buttons[buttonIndex].pressed = pressed, virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0; } else this.xCloudGamepad[xCloudKey] = pressed ? 1 : 0;
virtualGamepad.timestamp = performance.now(); window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
} }
onKeyboardEvent = (e) => { onKeyboardEvent = (e) => {
let isKeyDown = e.type === "keydown"; let isKeyDown = e.type === "keydown";
@ -3336,7 +3380,7 @@ class EmulatedMkbHandler extends MkbHandler {
else document.pointerLockElement && document.exitPointerLock(); else document.pointerLockElement && document.exitPointerLock();
} }
refreshPresetData() { refreshPresetData() {
this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset, this.resetGamepad(); this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset, this.resetXcloudGamepads();
} }
waitForMouseData(showPopup) { waitForMouseData(showPopup) {
this.popup.toggleVisibility(showPopup); this.popup.toggleVisibility(showPopup);
@ -3395,12 +3439,9 @@ class EmulatedMkbHandler extends MkbHandler {
else document.removeEventListener("pointerlockchange", this.onPointerLockChange), document.removeEventListener("pointerlockerror", this.onPointerLockError); else document.removeEventListener("pointerlockchange", this.onPointerLockChange), document.removeEventListener("pointerlockerror", this.onPointerLockError);
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged), BxEventBus.Script.off("dialog.shown", this.onDialogShown), this.mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged); window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged), BxEventBus.Script.off("dialog.shown", this.onDialogShown), this.mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
} }
updateGamepadSlots() {
this.VIRTUAL_GAMEPAD.index = getPref("mkb.p1.slot") - 1;
}
start() { start() {
if (!this.enabled) this.enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 }); if (!this.enabled) this.enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
this.isPolling = !0, this.escKeyDownTime = -1, this.resetGamepad(), this.updateGamepadSlots(), window.navigator.getGamepads = this.patchedGetGamepads, this.waitForMouseData(!1), this.mouseDataProvider?.start(); this.isPolling = !0, this.escKeyDownTime = -1, window.BX_EXPOSED.toggleLocalCoOp(getPref("localCoOp.enabled")), this.resetXcloudGamepads(), window.navigator.getGamepads = this.patchedGetGamepads, this.waitForMouseData(!1), this.mouseDataProvider?.start();
let virtualGamepad = this.getVirtualGamepad(); let virtualGamepad = this.getVirtualGamepad();
virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", { virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", {
gamepad: virtualGamepad gamepad: virtualGamepad
@ -3409,7 +3450,7 @@ class EmulatedMkbHandler extends MkbHandler {
stop() { stop() {
this.enabled = !1, this.isPolling = !1, this.escKeyDownTime = -1; this.enabled = !1, this.isPolling = !1, this.escKeyDownTime = -1;
let virtualGamepad = this.getVirtualGamepad(); let virtualGamepad = this.getVirtualGamepad();
if (virtualGamepad.connected) this.resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", { if (virtualGamepad.connected) this.resetXcloudGamepads(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", {
gamepad: virtualGamepad gamepad: virtualGamepad
}), window.navigator.getGamepads = this.nativeGetGamepads; }), window.navigator.getGamepads = this.nativeGetGamepads;
this.waitForMouseData(!0), this.mouseDataProvider?.stop(); this.waitForMouseData(!0), this.mouseDataProvider?.stop();

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 6.2.0 // @version 6.2.1-beta
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -107,7 +107,7 @@ class UserAgent {
}); });
} }
} }
var SCRIPT_VERSION = "6.2.0", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface; var SCRIPT_VERSION = "6.2.1-beta", SCRIPT_VARIANT = "full", AppInterface = window.AppInterface;
UserAgent.init(); UserAgent.init();
var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = { var userAgent = window.navigator.userAgent.toLowerCase(), isTv = userAgent.includes("smart-tv") || userAgent.includes("smarttv") || /\baft.*\b/.test(userAgent), isVr = window.navigator.userAgent.includes("VR") && window.navigator.userAgent.includes("OculusBrowser"), browserHasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0, userAgentHasTouchSupport = !isTv && !isVr && browserHasTouchSupport, STATES = {
supportedRegion: !0, supportedRegion: !0,
@ -2908,9 +2908,9 @@ function hasGamepad() {
if (gamepad?.connected) return !0; if (gamepad?.connected) return !0;
return !1; return !1;
} }
function generateVirtualControllerMapping(override = {}) { function generateVirtualControllerMapping(index, override = {}) {
return Object.assign({}, { return Object.assign({}, {
GamepadIndex: 0, GamepadIndex: index,
A: 0, A: 0,
B: 0, B: 0,
X: 0, X: 0,
@ -2941,17 +2941,7 @@ function generateVirtualControllerMapping(override = {}) {
function getGamepadPrompt(gamepadKey) { function getGamepadPrompt(gamepadKey) {
return GamepadKeyName[gamepadKey][1]; return GamepadKeyName[gamepadKey][1];
} }
class StreamSettings { var XCLOUD_GAMEPAD_KEY_MAPPING = {
static settings = {
settings: {},
xCloudPollingMode: "all",
deviceVibrationIntensity: 0,
controllerPollingRate: 4,
controllers: {},
mkbPreset: null,
keyboardShortcuts: {}
};
static CONTROLLER_CUSTOMIZATION_MAPPING = {
0: "A", 0: "A",
1: "B", 1: "B",
2: "X", 2: "X",
@ -2970,7 +2960,29 @@ class StreamSettings {
204: "RightStickAxes", 204: "RightStickAxes",
8: "View", 8: "View",
9: "Menu", 9: "Menu",
17: "Share" 16: "Nexus",
17: "Share",
102: "LeftThumbXAxis",
103: "LeftThumbXAxis",
100: "LeftThumbYAxis",
101: "LeftThumbYAxis",
202: "RightThumbXAxis",
203: "RightThumbXAxis",
200: "RightThumbYAxis",
201: "RightThumbYAxis"
};
function toXcloudGamepadKey(gamepadKey) {
return XCLOUD_GAMEPAD_KEY_MAPPING[gamepadKey];
}
class StreamSettings {
static settings = {
settings: {},
xCloudPollingMode: "all",
deviceVibrationIntensity: 0,
controllerPollingRate: 4,
controllers: {},
mkbPreset: null,
keyboardShortcuts: {}
}; };
static getPref(key) { static getPref(key) {
return getPref(key); return getPref(key);
@ -3002,10 +3014,10 @@ class StreamSettings {
vibrationIntensity: 1 vibrationIntensity: 1
}, gamepadKey; }, gamepadKey;
for (gamepadKey in customization.mapping) { for (gamepadKey in customization.mapping) {
let gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey]; let gamepadStr = toXcloudGamepadKey(gamepadKey);
if (!gamepadStr) continue; if (!gamepadStr) continue;
let mappedKey = customization.mapping[gamepadKey]; let mappedKey = customization.mapping[gamepadKey];
if (typeof mappedKey === "number") converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey]; if (typeof mappedKey === "number") converted.mapping[gamepadStr] = toXcloudGamepadKey(mappedKey);
else converted.mapping[gamepadStr] = !1; else converted.mapping[gamepadStr] = !1;
} }
return StreamSettings.preCalculateControllerRange(converted.ranges, "LeftTrigger", customization.settings.leftTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "RightTrigger", customization.settings.rightTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "LeftThumb", customization.settings.leftStickDeadzone), StreamSettings.preCalculateControllerRange(converted.ranges, "RightThumb", customization.settings.rightStickDeadzone), converted.vibrationIntensity = customization.settings.vibrationIntensity / 100, converted; return StreamSettings.preCalculateControllerRange(converted.ranges, "LeftTrigger", customization.settings.leftTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "RightTrigger", customization.settings.rightTriggerRange), StreamSettings.preCalculateControllerRange(converted.ranges, "LeftThumb", customization.settings.leftStickDeadzone), StreamSettings.preCalculateControllerRange(converted.ranges, "RightThumb", customization.settings.rightStickDeadzone), converted.vibrationIntensity = customization.settings.vibrationIntensity / 100, converted;
@ -3131,7 +3143,7 @@ class NativeMkbHandler extends MkbHandler {
mouseButtonsPressed = 0; mouseButtonsPressed = 0;
mouseVerticalMultiply = 0; mouseVerticalMultiply = 0;
mouseHorizontalMultiply = 0; mouseHorizontalMultiply = 0;
inputSink; inputChannel;
popup; popup;
constructor() { constructor() {
super(); super();
@ -3173,7 +3185,7 @@ class NativeMkbHandler extends MkbHandler {
} }
} }
init() { init() {
this.pointerClient = PointerClient.getInstance(), this.inputSink = window.BX_EXPOSED.inputSink, this.updateInputConfigurationAsync(!1); this.pointerClient = PointerClient.getInstance(), this.inputChannel = window.BX_EXPOSED.inputChannel, this.updateInputConfigurationAsync(!1);
try { try {
this.pointerClient.start(STATES.pointerServerPort, this); this.pointerClient.start(STATES.pointerServerPort, this);
} catch (e) { } catch (e) {
@ -3258,7 +3270,7 @@ class NativeMkbHandler extends MkbHandler {
return this.enabled; return this.enabled;
} }
sendMouseInput(data) { sendMouseInput(data) {
data.Type = 0, this.inputSink?.onMouseInput(data); data.Type = 0, this.inputChannel?.queueMouseInput(data);
} }
resetMouseInput() { resetMouseInput() {
this.mouseButtonsPressed = 0, this.sendMouseInput({ this.mouseButtonsPressed = 0, this.sendMouseInput({
@ -3351,6 +3363,7 @@ class EmulatedMkbHandler extends MkbHandler {
vibrationActuator: null vibrationActuator: null
}; };
nativeGetGamepads; nativeGetGamepads;
xCloudGamepad = generateVirtualControllerMapping(0);
initialized = !1; initialized = !1;
enabled = !1; enabled = !1;
mouseDataProvider; mouseDataProvider;
@ -3365,14 +3378,14 @@ class EmulatedMkbHandler extends MkbHandler {
RIGHT_STICK_Y = []; RIGHT_STICK_Y = [];
popup; popup;
STICK_MAP = { STICK_MAP = {
102: [this.LEFT_STICK_X, 0, -1], 102: [this.LEFT_STICK_X, -1],
103: [this.LEFT_STICK_X, 0, 1], 103: [this.LEFT_STICK_X, 1],
100: [this.LEFT_STICK_Y, 1, -1], 100: [this.LEFT_STICK_Y, 1],
101: [this.LEFT_STICK_Y, 1, 1], 101: [this.LEFT_STICK_Y, -1],
202: [this.RIGHT_STICK_X, 2, -1], 202: [this.RIGHT_STICK_X, -1],
203: [this.RIGHT_STICK_X, 2, 1], 203: [this.RIGHT_STICK_X, 1],
200: [this.RIGHT_STICK_Y, 3, -1], 200: [this.RIGHT_STICK_Y, 1],
201: [this.RIGHT_STICK_Y, 3, 1] 201: [this.RIGHT_STICK_Y, -1]
}; };
constructor() { constructor() {
super(); super();
@ -3385,31 +3398,32 @@ class EmulatedMkbHandler extends MkbHandler {
}; };
getVirtualGamepad = () => this.VIRTUAL_GAMEPAD; getVirtualGamepad = () => this.VIRTUAL_GAMEPAD;
updateStick(stick, x, y) { updateStick(stick, x, y) {
let virtualGamepad = this.getVirtualGamepad(); let gamepad = this.xCloudGamepad;
virtualGamepad.axes[stick * 2] = x, virtualGamepad.axes[stick * 2 + 1] = y, virtualGamepad.timestamp = performance.now(); if (stick === 0) gamepad.LeftThumbXAxis = x, gamepad.LeftThumbYAxis = -y;
else gamepad.RightThumbXAxis = x, gamepad.RightThumbYAxis = -y;
window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
} }
vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2); vectorLength = (x, y) => Math.sqrt(x ** 2 + y ** 2);
resetGamepad() { resetXcloudGamepads() {
let gamepad = this.getVirtualGamepad(); let index = getPref("mkb.p1.slot") - 1;
gamepad.axes = [0, 0, 0, 0]; this.xCloudGamepad = generateVirtualControllerMapping(0, {
for (let button of gamepad.buttons) GamepadIndex: getPref("localCoOp.enabled") ? index : 0,
button.pressed = !1, button.value = 0; Dirty: !0
gamepad.timestamp = performance.now(); }), this.VIRTUAL_GAMEPAD.index = index;
} }
pressButton(buttonIndex, pressed) { pressButton(buttonIndex, pressed) {
let virtualGamepad = this.getVirtualGamepad(); let xCloudKey = toXcloudGamepadKey(buttonIndex);
if (buttonIndex >= 100) { if (buttonIndex >= 100) {
let [valueArr, axisIndex] = this.STICK_MAP[buttonIndex]; let [valueArr] = this.STICK_MAP[buttonIndex];
valueArr = valueArr, axisIndex = axisIndex;
for (let i = valueArr.length - 1;i >= 0; i--) for (let i = valueArr.length - 1;i >= 0; i--)
if (valueArr[i] === buttonIndex) valueArr.splice(i, 1); if (valueArr[i] === buttonIndex) valueArr.splice(i, 1);
pressed && valueArr.push(buttonIndex); pressed && valueArr.push(buttonIndex);
let value; let value;
if (valueArr.length) value = this.STICK_MAP[valueArr[valueArr.length - 1]][2]; if (valueArr.length) value = this.STICK_MAP[valueArr[valueArr.length - 1]][1];
else value = 0; else value = 0;
virtualGamepad.axes[axisIndex] = value; this.xCloudGamepad[xCloudKey] = value;
} else virtualGamepad.buttons[buttonIndex].pressed = pressed, virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0; } else this.xCloudGamepad[xCloudKey] = pressed ? 1 : 0;
virtualGamepad.timestamp = performance.now(); window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
} }
onKeyboardEvent = (e) => { onKeyboardEvent = (e) => {
let isKeyDown = e.type === "keydown"; let isKeyDown = e.type === "keydown";
@ -3485,7 +3499,7 @@ class EmulatedMkbHandler extends MkbHandler {
else document.pointerLockElement && document.exitPointerLock(); else document.pointerLockElement && document.exitPointerLock();
} }
refreshPresetData() { refreshPresetData() {
this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset, this.resetGamepad(); this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset, this.resetXcloudGamepads();
} }
waitForMouseData(showPopup) { waitForMouseData(showPopup) {
this.popup.toggleVisibility(showPopup); this.popup.toggleVisibility(showPopup);
@ -3544,12 +3558,9 @@ class EmulatedMkbHandler extends MkbHandler {
else document.removeEventListener("pointerlockchange", this.onPointerLockChange), document.removeEventListener("pointerlockerror", this.onPointerLockError); else document.removeEventListener("pointerlockchange", this.onPointerLockChange), document.removeEventListener("pointerlockerror", this.onPointerLockError);
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged), BxEventBus.Script.off("dialog.shown", this.onDialogShown), this.mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged); window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged), BxEventBus.Script.off("dialog.shown", this.onDialogShown), this.mouseDataProvider?.destroy(), window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
} }
updateGamepadSlots() {
this.VIRTUAL_GAMEPAD.index = getPref("mkb.p1.slot") - 1;
}
start() { start() {
if (!this.enabled) this.enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 }); if (!this.enabled) this.enabled = !0, Toast.show(t("virtual-controller"), t("enabled"), { instant: !0 });
this.isPolling = !0, this.escKeyDownTime = -1, this.resetGamepad(), this.updateGamepadSlots(), window.navigator.getGamepads = this.patchedGetGamepads, this.waitForMouseData(!1), this.mouseDataProvider?.start(); this.isPolling = !0, this.escKeyDownTime = -1, window.BX_EXPOSED.toggleLocalCoOp(getPref("localCoOp.enabled")), this.resetXcloudGamepads(), window.navigator.getGamepads = this.patchedGetGamepads, this.waitForMouseData(!1), this.mouseDataProvider?.start();
let virtualGamepad = this.getVirtualGamepad(); let virtualGamepad = this.getVirtualGamepad();
virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", { virtualGamepad.connected = !0, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepadconnected", {
gamepad: virtualGamepad gamepad: virtualGamepad
@ -3558,7 +3569,7 @@ class EmulatedMkbHandler extends MkbHandler {
stop() { stop() {
this.enabled = !1, this.isPolling = !1, this.escKeyDownTime = -1; this.enabled = !1, this.isPolling = !1, this.escKeyDownTime = -1;
let virtualGamepad = this.getVirtualGamepad(); let virtualGamepad = this.getVirtualGamepad();
if (virtualGamepad.connected) this.resetGamepad(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", { if (virtualGamepad.connected) this.resetXcloudGamepads(), virtualGamepad.connected = !1, virtualGamepad.timestamp = performance.now(), BxEvent.dispatch(window, "gamepaddisconnected", {
gamepad: virtualGamepad gamepad: virtualGamepad
}), window.navigator.getGamepads = this.nativeGetGamepads; }), window.navigator.getGamepads = this.nativeGetGamepads;
this.waitForMouseData(!0), this.mouseDataProvider?.stop(); this.waitForMouseData(!0), this.mouseDataProvider?.stop();
@ -4690,11 +4701,11 @@ true` + text;
if (!str.includes(text)) return !1; if (!str.includes(text)) return !1;
return str = str.replace(text, text + "return true;"), str; return str = str.replace(text, text + "return true;"), str;
}, },
exposeInputSink(str) { exposeInputChannel(str) {
let text = "this.controlChannel=null,this.inputChannel=null"; let index = str.indexOf("this.flushData=");
if (!str.includes(text)) return !1; if (index < 0) return !1;
let newCode = "window.BX_EXPOSED.inputSink = this;"; let newCode = "window.BX_EXPOSED.inputChannel = this,";
return str = str.replace(text, newCode + text), str; return str = PatcherUtils.insertAt(str, index, newCode), str;
}, },
disableNativeRequestPointerLock(str) { disableNativeRequestPointerLock(str) {
let text = "async requestPointerLock(){"; let text = "async requestPointerLock(){";
@ -4892,7 +4903,6 @@ ${subsVar} = subs;
}, PATCH_ORDERS = PatcherUtils.filterPatches([ }, PATCH_ORDERS = PatcherUtils.filterPatches([
...AppInterface && getPref("nativeMkb.mode") === "on" ? [ ...AppInterface && getPref("nativeMkb.mode") === "on" ? [
"enableNativeMkb", "enableNativeMkb",
"exposeInputSink",
"disableAbsoluteMouse" "disableAbsoluteMouse"
] : [], ] : [],
"exposeReactCreateComponent", "exposeReactCreateComponent",
@ -4952,6 +4962,7 @@ ${subsVar} = subs;
STATES.browser.capabilities.touch && hideSections.includes("touch") && "ignorePlayWithTouchSection", STATES.browser.capabilities.touch && hideSections.includes("touch") && "ignorePlayWithTouchSection",
hideSections.some((value) => ["native-mkb", "most-popular"].includes(value)) && "ignoreSiglSections" hideSections.some((value) => ["native-mkb", "most-popular"].includes(value)) && "ignoreSiglSections"
]), STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([ ]), STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
"exposeInputChannel",
"patchXcloudTitleInfo", "patchXcloudTitleInfo",
"disableGamepadDisconnectedScreen", "disableGamepadDisconnectedScreen",
"patchStreamHud", "patchStreamHud",
@ -5024,11 +5035,15 @@ class Patcher {
if (!tmpStr) continue; if (!tmpStr) continue;
modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG2, `${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName), BxLogger.info(LOG_TAG2, "Remaining patches", PATCH_ORDERS); modified = !0, patchedFuncStr = tmpStr, BxLogger.info(LOG_TAG2, `${patchName}`), appliedPatches.push(patchName), patchesToCheck.splice(patchIndex, 1), patchIndex--, PATCH_ORDERS = PATCH_ORDERS.filter((item2) => item2 != patchName), BxLogger.info(LOG_TAG2, "Remaining patches", PATCH_ORDERS);
} }
if (modified) try { if (modified) {
BX_FLAGS.Debug && console.time(LOG_TAG2);
try {
chunkData[chunkId] = eval(patchedFuncStr); chunkData[chunkId] = eval(patchedFuncStr);
} catch (e) { } catch (e) {
if (e instanceof Error) BxLogger.error(LOG_TAG2, "Error", appliedPatches, e.message, patchedFuncStr); if (e instanceof Error) BxLogger.error(LOG_TAG2, "Error", appliedPatches, e.message, patchedFuncStr);
} }
BX_FLAGS.Debug && console.timeEnd(LOG_TAG2);
}
if (appliedPatches.length) patchesMap[chunkId] = appliedPatches; if (appliedPatches.length) patchesMap[chunkId] = appliedPatches;
} }
if (Object.keys(patchesMap).length) patcherCache.saveToCache(patchesMap); if (Object.keys(patchesMap).length) patcherCache.saveToCache(patchesMap);
@ -6341,7 +6356,7 @@ class MkbExtraSettings extends HTMLElement {
}) })
})), { multiLines: !0 }), })), { multiLines: !0 }),
createSettingRow(t("virtual-controller-slot"), SettingElement.fromPref("mkb.p1.slot", STORAGE.Global, () => { createSettingRow(t("virtual-controller-slot"), SettingElement.fromPref("mkb.p1.slot", STORAGE.Global, () => {
EmulatedMkbHandler.getInstance()?.updateGamepadSlots(); EmulatedMkbHandler.getInstance()?.resetXcloudGamepads();
})) }))
] : [], createSettingRow(t("in-game-keyboard-shortcuts"), CE("div", { ] : [], createSettingRow(t("in-game-keyboard-shortcuts"), CE("div", {
class: "bx-preset-row", class: "bx-preset-row",
@ -7385,7 +7400,7 @@ class VirtualControllerShortcut {
static pressXboxButton() { static pressXboxButton() {
let streamSession = window.BX_EXPOSED.streamSession; let streamSession = window.BX_EXPOSED.streamSession;
if (!streamSession) return; if (!streamSession) return;
let released = generateVirtualControllerMapping(), pressed = generateVirtualControllerMapping({ let released = generateVirtualControllerMapping(0), pressed = generateVirtualControllerMapping(0, {
Nexus: 1, Nexus: 1,
VirtualPhysicality: 1024 VirtualPhysicality: 1024
}); });

View File

@ -19,6 +19,7 @@ import type { MkbConvertedPresetData } from "@/types/presets";
import { StreamSettings } from "@/utils/stream-settings"; import { StreamSettings } from "@/utils/stream-settings";
import { ShortcutAction } from "@/enums/shortcut-actions"; import { ShortcutAction } from "@/enums/shortcut-actions";
import { BxEventBus } from "@/utils/bx-event-bus"; import { BxEventBus } from "@/utils/bx-event-bus";
import { generateVirtualControllerMapping, toXcloudGamepadKey } from "@/utils/gamepad";
const PointerToMouseButton = { const PointerToMouseButton = {
1: 0, 1: 0,
@ -152,6 +153,8 @@ export class EmulatedMkbHandler extends MkbHandler {
}; };
private nativeGetGamepads: Navigator['getGamepads']; private nativeGetGamepads: Navigator['getGamepads'];
private xCloudGamepad: XcloudGamepad = generateVirtualControllerMapping(0);
private initialized = false; private initialized = false;
private enabled = false; private enabled = false;
private mouseDataProvider: MouseDataProvider | undefined; private mouseDataProvider: MouseDataProvider | undefined;
@ -171,16 +174,16 @@ export class EmulatedMkbHandler extends MkbHandler {
private popup: MkbPopup; private popup: MkbPopup;
private STICK_MAP: { [key in GamepadKey]?: [GamepadKey[], number, number] } = { private STICK_MAP: { [key in GamepadKey]?: [GamepadKey[], number] } = {
[GamepadKey.LS_LEFT]: [this.LEFT_STICK_X, 0, -1], [GamepadKey.LS_LEFT]: [this.LEFT_STICK_X, -1],
[GamepadKey.LS_RIGHT]: [this.LEFT_STICK_X, 0, 1], [GamepadKey.LS_RIGHT]: [this.LEFT_STICK_X, 1],
[GamepadKey.LS_UP]: [this.LEFT_STICK_Y, 1, -1], [GamepadKey.LS_UP]: [this.LEFT_STICK_Y, 1],
[GamepadKey.LS_DOWN]: [this.LEFT_STICK_Y, 1, 1], [GamepadKey.LS_DOWN]: [this.LEFT_STICK_Y, -1],
[GamepadKey.RS_LEFT]: [this.RIGHT_STICK_X, 2, -1], [GamepadKey.RS_LEFT]: [this.RIGHT_STICK_X, -1],
[GamepadKey.RS_RIGHT]: [this.RIGHT_STICK_X, 2, 1], [GamepadKey.RS_RIGHT]: [this.RIGHT_STICK_X, 1],
[GamepadKey.RS_UP]: [this.RIGHT_STICK_Y, 3, -1], [GamepadKey.RS_UP]: [this.RIGHT_STICK_Y, 1],
[GamepadKey.RS_DOWN]: [this.RIGHT_STICK_Y, 3, 1], [GamepadKey.RS_DOWN]: [this.RIGHT_STICK_Y, -1],
}; };
private constructor() { private constructor() {
@ -205,11 +208,16 @@ export class EmulatedMkbHandler extends MkbHandler {
private getVirtualGamepad = () => this.VIRTUAL_GAMEPAD; private getVirtualGamepad = () => this.VIRTUAL_GAMEPAD;
private updateStick(stick: GamepadStick, x: number, y: number) { private updateStick(stick: GamepadStick, x: number, y: number) {
const virtualGamepad = this.getVirtualGamepad(); const gamepad = this.xCloudGamepad;
virtualGamepad.axes[stick * 2] = x; if (stick === GamepadStick.LEFT) {
virtualGamepad.axes[stick * 2 + 1] = y; gamepad.LeftThumbXAxis = x;
gamepad.LeftThumbYAxis = -y;
} else {
gamepad.RightThumbXAxis = x;
gamepad.RightThumbYAxis = -y;
}
virtualGamepad.timestamp = performance.now(); window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
} }
/* /*
@ -224,29 +232,20 @@ export class EmulatedMkbHandler extends MkbHandler {
private vectorLength = (x: number, y: number): number => Math.sqrt(x ** 2 + y ** 2); private vectorLength = (x: number, y: number): number => Math.sqrt(x ** 2 + y ** 2);
private resetGamepad() { resetXcloudGamepads() {
const gamepad = this.getVirtualGamepad(); const index = getPref(PrefKey.MKB_P1_SLOT) - 1;
// Reset axes this.xCloudGamepad = generateVirtualControllerMapping(0, {
gamepad.axes = [0, 0, 0, 0]; GamepadIndex: getPref(PrefKey.LOCAL_CO_OP_ENABLED) ? index : 0,
Dirty: true,
// Reset buttons });
for (const button of gamepad.buttons) { this.VIRTUAL_GAMEPAD.index = index;
button.pressed = false;
button.value = 0;
}
gamepad.timestamp = performance.now();
} }
private pressButton(buttonIndex: GamepadKey, pressed: boolean) { private pressButton(buttonIndex: GamepadKey, pressed: boolean) {
const virtualGamepad = this.getVirtualGamepad(); const xCloudKey = toXcloudGamepadKey(buttonIndex)!;
if (buttonIndex >= 100) { if (buttonIndex >= 100) {
let [valueArr, axisIndex] = this.STICK_MAP[buttonIndex]!; let [valueArr]: [GamepadKey[], number] = this.STICK_MAP[buttonIndex]!;
valueArr = valueArr as number[];
axisIndex = axisIndex as number;
// Remove old index of the array // Remove old index of the array
for (let i = valueArr.length - 1; i >= 0; i--) { for (let i = valueArr.length - 1; i >= 0; i--) {
if (valueArr[i] === buttonIndex) { if (valueArr[i] === buttonIndex) {
@ -259,18 +258,19 @@ export class EmulatedMkbHandler extends MkbHandler {
let value; let value;
if (valueArr.length) { if (valueArr.length) {
// Get value of the last key of the axis // Get value of the last key of the axis
value = this.STICK_MAP[valueArr[valueArr.length - 1]]![2] as number; value = this.STICK_MAP[valueArr[valueArr.length - 1]]![1] as number;
} else { } else {
value = 0; value = 0;
} }
virtualGamepad.axes[axisIndex] = value; // @ts-ignore
this.xCloudGamepad[xCloudKey] = value;
} else { } else {
virtualGamepad.buttons[buttonIndex].pressed = pressed; // @ts-ignore
virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0; this.xCloudGamepad[xCloudKey] = pressed ? 1 : 0;
} }
virtualGamepad.timestamp = performance.now(); window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
} }
private onKeyboardEvent = (e: KeyboardEvent) => { private onKeyboardEvent = (e: KeyboardEvent) => {
@ -453,7 +453,7 @@ export class EmulatedMkbHandler extends MkbHandler {
refreshPresetData() { refreshPresetData() {
this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset; this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset;
this.resetGamepad(); this.resetXcloudGamepads();
} }
waitForMouseData(showPopup: boolean) { waitForMouseData(showPopup: boolean) {
@ -581,11 +581,6 @@ export class EmulatedMkbHandler extends MkbHandler {
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged); window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
} }
updateGamepadSlots() {
// Set gamepad slot
this.VIRTUAL_GAMEPAD.index = getPref(PrefKey.MKB_P1_SLOT) - 1;
}
start() { start() {
if (!this.enabled) { if (!this.enabled) {
this.enabled = true; this.enabled = true;
@ -595,8 +590,8 @@ export class EmulatedMkbHandler extends MkbHandler {
this.isPolling = true; this.isPolling = true;
this.escKeyDownTime = -1; this.escKeyDownTime = -1;
this.resetGamepad(); window.BX_EXPOSED.toggleLocalCoOp(getPref(PrefKey.LOCAL_CO_OP_ENABLED));
this.updateGamepadSlots(); this.resetXcloudGamepads();
window.navigator.getGamepads = this.patchedGetGamepads; window.navigator.getGamepads = this.patchedGetGamepads;
this.waitForMouseData(false); this.waitForMouseData(false);
@ -625,7 +620,7 @@ export class EmulatedMkbHandler extends MkbHandler {
const virtualGamepad = this.getVirtualGamepad(); const virtualGamepad = this.getVirtualGamepad();
if (virtualGamepad.connected) { if (virtualGamepad.connected) {
// Dispatch "gamepaddisconnected" event // Dispatch "gamepaddisconnected" event
this.resetGamepad(); this.resetXcloudGamepads();
virtualGamepad.connected = false; virtualGamepad.connected = false;
virtualGamepad.timestamp = performance.now(); virtualGamepad.timestamp = performance.now();

View File

@ -13,19 +13,7 @@ import { StreamSettings } from "@/utils/stream-settings";
import { ShortcutAction } from "@/enums/shortcut-actions"; import { ShortcutAction } from "@/enums/shortcut-actions";
import { NativeMkbMode } from "@/enums/pref-values"; import { NativeMkbMode } from "@/enums/pref-values";
import { BxEventBus } from "@/utils/bx-event-bus"; import { BxEventBus } from "@/utils/bx-event-bus";
import type { NativeMouseData, XcloudInputChannel } from "@/utils/gamepad";
type NativeMouseData = {
X: number,
Y: number,
Buttons: number,
WheelX: number,
WheelY: number,
Type?: 0, // 0: Relative, 1: Absolute
}
type XcloudInputSink = {
onMouseInput: (data: NativeMouseData) => void;
}
export class NativeMkbHandler extends MkbHandler { export class NativeMkbHandler extends MkbHandler {
private static instance: NativeMkbHandler | null | undefined; private static instance: NativeMkbHandler | null | undefined;
@ -54,7 +42,7 @@ export class NativeMkbHandler extends MkbHandler {
private mouseVerticalMultiply = 0; private mouseVerticalMultiply = 0;
private mouseHorizontalMultiply = 0; private mouseHorizontalMultiply = 0;
private inputSink: XcloudInputSink | undefined; private inputChannel: XcloudInputChannel | undefined;
private popup!: MkbPopup; private popup!: MkbPopup;
@ -114,7 +102,7 @@ export class NativeMkbHandler extends MkbHandler {
init() { init() {
this.pointerClient = PointerClient.getInstance(); this.pointerClient = PointerClient.getInstance();
this.inputSink = window.BX_EXPOSED.inputSink; this.inputChannel = window.BX_EXPOSED.inputChannel;
// Stop keyboard input at startup // Stop keyboard input at startup
this.updateInputConfigurationAsync(false); this.updateInputConfigurationAsync(false);
@ -274,7 +262,7 @@ export class NativeMkbHandler extends MkbHandler {
private sendMouseInput(data: NativeMouseData) { private sendMouseInput(data: NativeMouseData) {
data.Type = 0; // Relative data.Type = 0; // Relative
this.inputSink?.onMouseInput(data); this.inputChannel?.queueMouseInput(data);
} }
private resetMouseInput() { private resetMouseInput() {

View File

@ -643,15 +643,14 @@ true` + text;
return str; return str;
}, },
exposeInputSink(str: string) { exposeInputChannel(str: string) {
let text = 'this.controlChannel=null,this.inputChannel=null'; let index = str.indexOf('this.flushData=');
if (!str.includes(text)) { if (index < 0) {
return false; return false;
} }
const newCode = 'window.BX_EXPOSED.inputSink = this;'; const newCode = 'window.BX_EXPOSED.inputChannel = this,';
str = PatcherUtils.insertAt(str, index, newCode);
str = str.replace(text, newCode + text);
return str; return str;
}, },
@ -1120,7 +1119,6 @@ ${subsVar} = subs;
let PATCH_ORDERS = PatcherUtils.filterPatches([ let PATCH_ORDERS = PatcherUtils.filterPatches([
...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [ ...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
'enableNativeMkb', 'enableNativeMkb',
'exposeInputSink',
'disableAbsoluteMouse', 'disableAbsoluteMouse',
] : []), ] : []),
@ -1208,6 +1206,8 @@ let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
// TODO: check this // TODO: check this
// @ts-ignore // @ts-ignore
let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
'exposeInputChannel',
'patchXcloudTitleInfo', 'patchXcloudTitleInfo',
'disableGamepadDisconnectedScreen', 'disableGamepadDisconnectedScreen',
'patchStreamHud', 'patchStreamHud',
@ -1377,6 +1377,7 @@ export class Patcher {
// Apply patched functions // Apply patched functions
if (modified) { if (modified) {
BX_FLAGS.Debug && console.time(LOG_TAG);
try { try {
chunkData[chunkId] = eval(patchedFuncStr); chunkData[chunkId] = eval(patchedFuncStr);
} catch (e: unknown) { } catch (e: unknown) {
@ -1384,6 +1385,7 @@ export class Patcher {
BxLogger.error(LOG_TAG, 'Error', appliedPatches, e.message, patchedFuncStr); BxLogger.error(LOG_TAG, 'Error', appliedPatches, e.message, patchedFuncStr);
} }
} }
BX_FLAGS.Debug && console.timeEnd(LOG_TAG);
} }
// Save to cache // Save to cache

View File

@ -7,8 +7,8 @@ export class VirtualControllerShortcut {
return; return;
} }
const released = generateVirtualControllerMapping(); const released = generateVirtualControllerMapping(0);
const pressed = generateVirtualControllerMapping({ const pressed = generateVirtualControllerMapping(0, {
Nexus: 1, Nexus: 1,
VirtualPhysicality: 1024, // Home VirtualPhysicality: 1024, // Home
}); });

View File

@ -69,7 +69,7 @@ export class MkbExtraSettings extends HTMLElement {
createSettingRow( createSettingRow(
t('virtual-controller-slot'), t('virtual-controller-slot'),
SettingElement.fromPref(PrefKey.MKB_P1_SLOT, STORAGE.Global, () => { SettingElement.fromPref(PrefKey.MKB_P1_SLOT, STORAGE.Global, () => {
EmulatedMkbHandler.getInstance()?.updateGamepadSlots(); EmulatedMkbHandler.getInstance()?.resetXcloudGamepads();
}), }),
), ),
] : []), ] : []),

View File

@ -5,6 +5,7 @@ import type { StreamSettings, type StreamSettingsData } from "@/utils/stream-set
import type { BxEvent } from "@/utils/bx-event"; import type { BxEvent } from "@/utils/bx-event";
import type { BxEventBus } from "@/utils/bx-event-bus"; import type { BxEventBus } from "@/utils/bx-event-bus";
import type { BxLogger } from "@/utils/bx-logger"; import type { BxLogger } from "@/utils/bx-logger";
import type { XcloudInputChannel } from "@/utils/gamepad";
export {}; export {};
@ -20,7 +21,7 @@ declare global {
closeAll: () => void; closeAll: () => void;
}; };
showStreamMenu: () => void; showStreamMenu: () => void;
inputSink: any; inputChannel: XcloudInputChannel | undefined;
streamSession: any; streamSession: any;
touchLayoutManager: any; touchLayoutManager: any;
}>; }>;

View File

@ -4,7 +4,21 @@ import { Toast } from "@utils/toast";
import { BxLogger } from "@utils/bx-logger"; import { BxLogger } from "@utils/bx-logger";
import { PrefKey } from "@/enums/pref-keys"; import { PrefKey } from "@/enums/pref-keys";
import { getPref } from "./settings-storages/global-settings-storage"; import { getPref } from "./settings-storages/global-settings-storage";
import { GamepadKeyName, type GamepadKey } from "@/enums/gamepad"; import { GamepadKey, GamepadKeyName } from "@/enums/gamepad";
export type NativeMouseData = {
X: number,
Y: number,
Buttons: number,
WheelX: number,
WheelY: number,
Type?: 0, // 0: Relative, 1: Absolute
}
export type XcloudInputChannel = {
sendGamepadInput: (timestamp: number, gamepads: XcloudGamepad[]) => void;
queueMouseInput: (data: NativeMouseData) => void;
}
// Show a toast when connecting/disconecting controller // Show a toast when connecting/disconecting controller
export function showGamepadToast(gamepad: Gamepad) { export function showGamepadToast(gamepad: Gamepad) {
@ -59,9 +73,9 @@ export function hasGamepad() {
return false; return false;
} }
export function generateVirtualControllerMapping(override: {}={}) { export function generateVirtualControllerMapping(index: number, override: Partial<XcloudGamepad>={}) {
const mapping = { const mapping = {
GamepadIndex: 0, GamepadIndex: index,
A: 0, A: 0,
B: 0, B: 0,
X: 0, X: 0,
@ -95,3 +109,44 @@ export function generateVirtualControllerMapping(override: {}={}) {
export function getGamepadPrompt(gamepadKey: GamepadKey): string { export function getGamepadPrompt(gamepadKey: GamepadKey): string {
return GamepadKeyName[gamepadKey][1]; return GamepadKeyName[gamepadKey][1];
} }
const XCLOUD_GAMEPAD_KEY_MAPPING: { [key in GamepadKey]?: keyof XcloudGamepad } = {
[GamepadKey.A]: 'A',
[GamepadKey.B]: 'B',
[GamepadKey.X]: 'X',
[GamepadKey.Y]: 'Y',
[GamepadKey.UP]: 'DPadUp',
[GamepadKey.RIGHT]: 'DPadRight',
[GamepadKey.DOWN]: 'DPadDown',
[GamepadKey.LEFT]: 'DPadLeft',
[GamepadKey.LB]: 'LeftShoulder',
[GamepadKey.RB]: 'RightShoulder',
[GamepadKey.LT]: 'LeftTrigger',
[GamepadKey.RT]: 'RightTrigger',
[GamepadKey.L3]: 'LeftThumb',
[GamepadKey.R3]: 'RightThumb',
[GamepadKey.LS]: 'LeftStickAxes',
[GamepadKey.RS]: 'RightStickAxes',
[GamepadKey.SELECT]: 'View',
[GamepadKey.START]: 'Menu',
[GamepadKey.HOME]: 'Nexus',
[GamepadKey.SHARE]: 'Share',
[GamepadKey.LS_LEFT]: 'LeftThumbXAxis',
[GamepadKey.LS_RIGHT]: 'LeftThumbXAxis',
[GamepadKey.LS_UP]: 'LeftThumbYAxis',
[GamepadKey.LS_DOWN]: 'LeftThumbYAxis',
[GamepadKey.RS_LEFT]: 'RightThumbXAxis',
[GamepadKey.RS_RIGHT]: 'RightThumbXAxis',
[GamepadKey.RS_UP]: 'RightThumbYAxis',
[GamepadKey.RS_DOWN]: 'RightThumbYAxis',
};
export function toXcloudGamepadKey(gamepadKey: GamepadKey) {
return XCLOUD_GAMEPAD_KEY_MAPPING[gamepadKey];
}

View File

@ -6,7 +6,7 @@ import type { ControllerCustomizationConvertedPresetData, ControllerCustomizatio
import { STATES } from "./global"; import { STATES } from "./global";
import { DeviceVibrationMode } from "@/enums/pref-values"; import { DeviceVibrationMode } from "@/enums/pref-values";
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler"; import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
import { hasGamepad } from "./gamepad"; import { hasGamepad, toXcloudGamepadKey } from "./gamepad";
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table"; import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
import { GamepadKey } from "@/enums/gamepad"; import { GamepadKey } from "@/enums/gamepad";
import { MkbPresetKey, MouseConstant } from "@/enums/mkb"; import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
@ -51,32 +51,6 @@ export class StreamSettings {
keyboardShortcuts: {}, keyboardShortcuts: {},
}; };
private static CONTROLLER_CUSTOMIZATION_MAPPING: { [key in GamepadKey]?: keyof XcloudGamepad } = {
[GamepadKey.A]: 'A',
[GamepadKey.B]: 'B',
[GamepadKey.X]: 'X',
[GamepadKey.Y]: 'Y',
[GamepadKey.UP]: 'DPadUp',
[GamepadKey.RIGHT]: 'DPadRight',
[GamepadKey.DOWN]: 'DPadDown',
[GamepadKey.LEFT]: 'DPadLeft',
[GamepadKey.LB]: 'LeftShoulder',
[GamepadKey.RB]: 'RightShoulder',
[GamepadKey.LT]: 'LeftTrigger',
[GamepadKey.RT]: 'RightTrigger',
[GamepadKey.L3]: 'LeftThumb',
[GamepadKey.R3]: 'RightThumb',
[GamepadKey.LS]: 'LeftStickAxes',
[GamepadKey.RS]: 'RightStickAxes',
[GamepadKey.SELECT]: 'View',
[GamepadKey.START]: 'Menu',
[GamepadKey.SHARE]: 'Share',
};
static getPref<T extends keyof PrefTypeMap>(key: T) { static getPref<T extends keyof PrefTypeMap>(key: T) {
return getPref<T>(key); return getPref<T>(key);
} }
@ -146,14 +120,14 @@ export class StreamSettings {
// Swap GamepadKey.A with "A" // Swap GamepadKey.A with "A"
let gamepadKey: unknown; let gamepadKey: unknown;
for (gamepadKey in customization.mapping) { for (gamepadKey in customization.mapping) {
const gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey as GamepadKey]; const gamepadStr = toXcloudGamepadKey(gamepadKey as GamepadKey);
if (!gamepadStr) { if (!gamepadStr) {
continue; continue;
} }
const mappedKey = customization.mapping[gamepadKey as GamepadKey]; const mappedKey = customization.mapping[gamepadKey as GamepadKey];
if (typeof mappedKey === 'number') { if (typeof mappedKey === 'number') {
converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey as GamepadKey]; converted.mapping[gamepadStr] = toXcloudGamepadKey(mappedKey as GamepadKey);
} else { } else {
converted.mapping[gamepadStr] = false; converted.mapping[gamepadStr] = false;
} }