mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-06-07 08:07:18 +02:00
Support emulated MKB in Android app
commit ad365d4ee854971122f0e8cb9157ed44b3aac0d8 Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 17:19:57 2024 +0700 Fix not able to reconnect to WebSocket server when switching game commit ca9369318d4cbb831650e8ca631e7997dc7706cb Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 17:19:23 2024 +0700 Stop emulated MKB when losing pointer capture commit 8cca1a0554c46b8f61455e79d5b16f1dff9a8014 Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 17:17:42 2024 +0700 Allow fine-tuning maximum video bitrate commit 763d414d560d9d2aa6710fd60e3f80bf43a534d6 Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 08:13:56 2024 +0700 Update mouse settings commit d65c5ab4e4a33ed8ad13acf0a15c4bb5ace870eb Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 08:10:49 2024 +0700 Increase MKB dialog's bg opacity commit 3e72f2ad2700737c8148ef47629528954a606578 Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 08:02:57 2024 +0700 Show/hide MKB dialog properly commit e7786f36508e3aa843604d9886861930bada5d60 Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 07:47:21 2024 +0700 Fix connecting to WebSocket server when it's not ready commit 512d8c227a057e5c0399bf128bc1c52a88fcf853 Author: redphx <96280+redphx@users.noreply.github.com> Date: Wed May 29 07:18:06 2024 +0700 Fix arrow keys not working in Android app commit 0ce90f47f37d057d5a4fab0003e2bec8960d1eee Author: redphx <96280+redphx@users.noreply.github.com> Date: Tue May 28 17:36:56 2024 +0700 Set mouse's default sensitivities to 50 commit 16eb48660dd44497e16ca22343a880d9a2e53a30 Author: redphx <96280+redphx@users.noreply.github.com> Date: Tue May 28 17:33:37 2024 +0700 Allow emulated MKB feature in Android app commit c3d0e64f8502e19cd4f167fea4cdbdfc2e14b65e Author: redphx <96280+redphx@users.noreply.github.com> Date: Tue May 28 17:32:49 2024 +0700 Remove stick decay settings commit d289d2a0dea61a440c1bc6b9392920b8e6ab6298 Author: redphx <96280+redphx@users.noreply.github.com> Date: Tue May 28 17:21:39 2024 +0700 Remove stick decaying feature commit 76bd001d98bac53f757f4ae793b2850aad055007 Author: redphx <96280+redphx@users.noreply.github.com> Date: Tue May 28 17:21:14 2024 +0700 Update data structure commit c5d3c87da9e6624ebefb288f6d7c8d06dc00916b Author: redphx <96280+redphx@users.noreply.github.com> Date: Tue May 28 08:14:27 2024 +0700 Fix not toggling the MKB feature correctly commit 9615535cf0e4d4372e201aefb6f1231ddbc22536 Author: redphx <96280+redphx@users.noreply.github.com> Date: Mon May 27 20:51:57 2024 +0700 Handle mouse data from the app
This commit is contained in:
parent
228c2ad008
commit
0f48cb891f
@ -25,7 +25,7 @@
|
|||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateX(-50%) translateY(-50%);
|
transform: translateX(-50%) translateY(-50%);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
background: #000000e5;
|
background: #000000b3;
|
||||||
z-index: var(--bx-mkb-pointer-lock-msg-z-index);
|
z-index: var(--bx-mkb-pointer-lock-msg-z-index);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -282,6 +282,9 @@ function main() {
|
|||||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') {
|
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all') {
|
||||||
TouchController.setup();
|
TouchController.setup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start PointerProviderServer
|
||||||
|
(getPref(PrefKey.MKB_ENABLED)) && AppInterface && AppInterface.startPointerServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
@ -82,6 +82,18 @@ export class GameBar {
|
|||||||
document.documentElement.appendChild($gameBar);
|
document.documentElement.appendChild($gameBar);
|
||||||
this.$gameBar = $gameBar;
|
this.$gameBar = $gameBar;
|
||||||
this.$container = $container;
|
this.$container = $container;
|
||||||
|
|
||||||
|
// Enable/disable Game Bar when playing/pausing
|
||||||
|
getPref(PrefKey.GAME_BAR_POSITION) !== 'off' && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => {
|
||||||
|
if (!STATES.isPlaying) {
|
||||||
|
this.disable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle Game bar
|
||||||
|
const mode = (e as any).mode;
|
||||||
|
mode !== 'None' ? this.disable() : this.enable();
|
||||||
|
}).bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private beginHideTimeout() {
|
private beginHideTimeout() {
|
||||||
|
@ -99,7 +99,4 @@ export enum MkbPresetKey {
|
|||||||
MOUSE_SENSITIVITY_Y = 'sensitivity_y',
|
MOUSE_SENSITIVITY_Y = 'sensitivity_y',
|
||||||
|
|
||||||
MOUSE_DEADZONE_COUNTERWEIGHT = 'deadzone_counterweight',
|
MOUSE_DEADZONE_COUNTERWEIGHT = 'deadzone_counterweight',
|
||||||
|
|
||||||
MOUSE_STICK_DECAY_STRENGTH = 'stick_decay_strength',
|
|
||||||
MOUSE_STICK_DECAY_MIN = 'stick_decay_min',
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export class KeyHelper {
|
|||||||
let name;
|
let name;
|
||||||
|
|
||||||
if (e instanceof KeyboardEvent) {
|
if (e instanceof KeyboardEvent) {
|
||||||
code = e.code;
|
code = e.code || e.key;
|
||||||
} else if (e instanceof WheelEvent) {
|
} else if (e instanceof WheelEvent) {
|
||||||
if (e.deltaY < 0) {
|
if (e.deltaY < 0) {
|
||||||
code = WheelCode.SCROLL_UP;
|
code = WheelCode.SCROLL_UP;
|
||||||
@ -28,7 +28,7 @@ export class KeyHelper {
|
|||||||
code = WheelCode.SCROLL_DOWN;
|
code = WheelCode.SCROLL_DOWN;
|
||||||
} else if (e.deltaX < 0) {
|
} else if (e.deltaX < 0) {
|
||||||
code = WheelCode.SCROLL_LEFT;
|
code = WheelCode.SCROLL_LEFT;
|
||||||
} else {
|
} else if (e.deltaX > 0) {
|
||||||
code = WheelCode.SCROLL_RIGHT;
|
code = WheelCode.SCROLL_RIGHT;
|
||||||
}
|
}
|
||||||
} else if (e instanceof MouseEvent) {
|
} else if (e instanceof MouseEvent) {
|
||||||
|
@ -9,13 +9,152 @@ import { LocalDb } from "@utils/local-db";
|
|||||||
import { KeyHelper } from "./key-helper";
|
import { KeyHelper } from "./key-helper";
|
||||||
import type { MkbStoredPreset } from "@/types/mkb";
|
import type { MkbStoredPreset } from "@/types/mkb";
|
||||||
import { showStreamSettings } from "@modules/stream/stream-ui";
|
import { showStreamSettings } from "@modules/stream/stream-ui";
|
||||||
import { STATES } from "@utils/global";
|
import { AppInterface, STATES } from "@utils/global";
|
||||||
import { UserAgent } from "@utils/user-agent";
|
import { UserAgent } from "@utils/user-agent";
|
||||||
import { BxLogger } from "@utils/bx-logger";
|
import { BxLogger } from "@utils/bx-logger";
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
|
import { PointerClient } from "./pointer-client";
|
||||||
|
|
||||||
const LOG_TAG = 'MkbHandler';
|
const LOG_TAG = 'MkbHandler';
|
||||||
|
|
||||||
|
|
||||||
|
abstract class MouseDataProvider {
|
||||||
|
protected mkbHandler: MkbHandler;
|
||||||
|
constructor(handler: MkbHandler) {
|
||||||
|
this.mkbHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract init(): void;
|
||||||
|
abstract start(): void;
|
||||||
|
abstract stop(): void;
|
||||||
|
abstract destroy(): void;
|
||||||
|
abstract toggle(enabled: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketMouseDataProvider extends MouseDataProvider {
|
||||||
|
#pointerClient: PointerClient | undefined
|
||||||
|
#connected = false
|
||||||
|
|
||||||
|
init(): void {
|
||||||
|
this.#pointerClient = PointerClient.getInstance();
|
||||||
|
this.#connected = false;
|
||||||
|
try {
|
||||||
|
this.#pointerClient.start(this.mkbHandler);
|
||||||
|
this.#connected = true;
|
||||||
|
} catch (e) {
|
||||||
|
Toast.show('Cannot enable Mouse & Keyboard feature');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): void {
|
||||||
|
this.#connected && AppInterface.requestPointerCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
this.#connected && AppInterface.releasePointerCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
this.#connected && this.#pointerClient?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(enabled: boolean): void {
|
||||||
|
if (!this.#connected) {
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enabled ? this.mkbHandler.start() : this.mkbHandler.stop();
|
||||||
|
this.mkbHandler.waitForMouseData(!enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PointerLockMouseDataProvider extends MouseDataProvider {
|
||||||
|
init(): void {
|
||||||
|
document.addEventListener('pointerlockchange', this.#onPointerLockChange);
|
||||||
|
document.addEventListener('pointerlockerror', this.#onPointerLockError);
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): void {
|
||||||
|
if (!document.pointerLockElement) {
|
||||||
|
document.body.requestPointerLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', this.#onMouseMoveEvent);
|
||||||
|
window.addEventListener('mousedown', this.#onMouseEvent);
|
||||||
|
window.addEventListener('mouseup', this.#onMouseEvent);
|
||||||
|
window.addEventListener('wheel', this.#onWheelEvent);
|
||||||
|
window.addEventListener('contextmenu', this.#disableContextMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
stop(): void {
|
||||||
|
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(): void {
|
||||||
|
document.removeEventListener('pointerlockchange', this.#onPointerLockChange);
|
||||||
|
document.removeEventListener('pointerlockerror', this.#onPointerLockError);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle(enabled: boolean): void {
|
||||||
|
enabled ? document.pointerLockElement && this.mkbHandler.start() : this.mkbHandler.stop();
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
!document.pointerLockElement && this.mkbHandler.waitForMouseData(true);
|
||||||
|
} else {
|
||||||
|
this.mkbHandler.waitForMouseData(false);
|
||||||
|
document.pointerLockElement && document.exitPointerLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#onPointerLockChange = () => {
|
||||||
|
if (this.mkbHandler.isEnabled() && !document.pointerLockElement) {
|
||||||
|
this.mkbHandler.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#onPointerLockError = (e: Event) => {
|
||||||
|
console.log(e);
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
#onMouseMoveEvent = (e: MouseEvent) => {
|
||||||
|
this.mkbHandler.handleMouseMove({
|
||||||
|
movementX: e.movementX,
|
||||||
|
movementY: e.movementY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#onMouseEvent = (e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const isMouseDown = e.type === 'mousedown';
|
||||||
|
const key = KeyHelper.getKeyFromEvent(e);
|
||||||
|
const data: MkbMouseClick = {
|
||||||
|
key: key,
|
||||||
|
pressed: isMouseDown
|
||||||
|
};
|
||||||
|
|
||||||
|
this.mkbHandler.handleMouseClick(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#onWheelEvent = (e: WheelEvent) => {
|
||||||
|
const key = KeyHelper.getKeyFromEvent(e);
|
||||||
|
if (!key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mkbHandler.handleMouseWheel({key})) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#disableContextMenu = (e: Event) => e.preventDefault();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This class uses some code from Yuzu emulator to handle mouse's movements
|
This class uses some code from Yuzu emulator to handle mouse's movements
|
||||||
Source: https://github.com/yuzu-emu/yuzu-mainline/blob/master/src/input_common/drivers/mouse.cpp
|
Source: https://github.com/yuzu-emu/yuzu-mainline/blob/master/src/input_common/drivers/mouse.cpp
|
||||||
@ -33,7 +172,6 @@ export class MkbHandler {
|
|||||||
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
#CURRENT_PRESET_DATA = MkbPreset.convert(MkbPreset.DEFAULT_PRESET);
|
||||||
|
|
||||||
static readonly DEFAULT_PANNING_SENSITIVITY = 0.0010;
|
static readonly DEFAULT_PANNING_SENSITIVITY = 0.0010;
|
||||||
static readonly DEFAULT_STICK_SENSITIVITY = 0.0006;
|
|
||||||
static readonly DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
|
static readonly DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01;
|
||||||
static readonly MAXIMUM_STICK_RANGE = 1.1;
|
static readonly MAXIMUM_STICK_RANGE = 1.1;
|
||||||
|
|
||||||
@ -55,13 +193,13 @@ export class MkbHandler {
|
|||||||
#nativeGetGamepads = window.navigator.getGamepads.bind(window.navigator);
|
#nativeGetGamepads = window.navigator.getGamepads.bind(window.navigator);
|
||||||
|
|
||||||
#enabled = false;
|
#enabled = false;
|
||||||
|
#mouseDataProvider: MouseDataProvider | undefined;
|
||||||
#isPolling = false;
|
#isPolling = false;
|
||||||
|
|
||||||
#prevWheelCode = null;
|
#prevWheelCode = null;
|
||||||
#wheelStoppedTimeout?: number | null;
|
#wheelStoppedTimeout?: number | null;
|
||||||
|
|
||||||
#detectMouseStoppedTimeout?: number | null;
|
#detectMouseStoppedTimeout?: number | null;
|
||||||
#allowStickDecaying = false;
|
|
||||||
|
|
||||||
#$message?: HTMLElement;
|
#$message?: HTMLElement;
|
||||||
|
|
||||||
@ -85,6 +223,8 @@ export class MkbHandler {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isEnabled = () => this.#enabled;
|
||||||
|
|
||||||
#patchedGetGamepads = () => {
|
#patchedGetGamepads = () => {
|
||||||
const gamepads = this.#nativeGetGamepads() || [];
|
const gamepads = this.#nativeGetGamepads() || [];
|
||||||
(gamepads as any)[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD;
|
(gamepads as any)[this.#VIRTUAL_GAMEPAD.index] = this.#VIRTUAL_GAMEPAD;
|
||||||
@ -102,6 +242,7 @@ export class MkbHandler {
|
|||||||
virtualGamepad.timestamp = performance.now();
|
virtualGamepad.timestamp = performance.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
#getStickAxes(stick: GamepadStick) {
|
#getStickAxes(stick: GamepadStick) {
|
||||||
const virtualGamepad = this.#getVirtualGamepad();
|
const virtualGamepad = this.#getVirtualGamepad();
|
||||||
return {
|
return {
|
||||||
@ -109,11 +250,10 @@ export class MkbHandler {
|
|||||||
y: virtualGamepad.axes[stick * 2 + 1],
|
y: virtualGamepad.axes[stick * 2 + 1],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#vectorLength = (x: number, y: number): number => Math.sqrt(x ** 2 + y ** 2);
|
#vectorLength = (x: number, y: number): number => Math.sqrt(x ** 2 + y ** 2);
|
||||||
|
|
||||||
#disableContextMenu = (e: Event) => e.preventDefault();
|
|
||||||
|
|
||||||
#resetGamepad = () => {
|
#resetGamepad = () => {
|
||||||
const gamepad = this.#getVirtualGamepad();
|
const gamepad = this.#getVirtualGamepad();
|
||||||
|
|
||||||
@ -172,6 +312,10 @@ export class MkbHandler {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.toggle();
|
this.toggle();
|
||||||
return;
|
return;
|
||||||
|
} else if (e.code === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.#enabled && this.stop();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.#isPolling) {
|
if (!this.#isPolling) {
|
||||||
@ -179,7 +323,7 @@ export class MkbHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code]!;
|
const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[e.code || e.key]!;
|
||||||
if (typeof buttonIndex === 'undefined') {
|
if (typeof buttonIndex === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -193,89 +337,29 @@ export class MkbHandler {
|
|||||||
this.#pressButton(buttonIndex, isKeyDown);
|
this.#pressButton(buttonIndex, isKeyDown);
|
||||||
}
|
}
|
||||||
|
|
||||||
#onMouseEvent = (e: MouseEvent) => {
|
#onMouseStopped = () => {
|
||||||
const isMouseDown = e.type === 'mousedown';
|
// Reset stick position
|
||||||
const key = KeyHelper.getKeyFromEvent(e);
|
this.#detectMouseStoppedTimeout = null;
|
||||||
if (!key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]!;
|
|
||||||
if (typeof buttonIndex === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
this.#pressButton(buttonIndex, isMouseDown);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onWheelEvent = (e: WheelEvent) => {
|
|
||||||
const key = KeyHelper.getKeyFromEvent(e);
|
|
||||||
if (!key) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[key.code]!;
|
|
||||||
if (typeof buttonIndex === 'undefined') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (this.#prevWheelCode === null || this.#prevWheelCode === key.code) {
|
|
||||||
this.#wheelStoppedTimeout && clearTimeout(this.#wheelStoppedTimeout);
|
|
||||||
this.#pressButton(buttonIndex, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#wheelStoppedTimeout = window.setTimeout(() => {
|
|
||||||
this.#prevWheelCode = null;
|
|
||||||
this.#pressButton(buttonIndex, false);
|
|
||||||
}, 20);
|
|
||||||
}
|
|
||||||
|
|
||||||
#decayStick = () => {
|
|
||||||
if (!this.#allowStickDecaying) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_MAP_TO];
|
const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_MAP_TO];
|
||||||
if (mouseMapTo === MouseMapTo.OFF) {
|
const analog = mouseMapTo === MouseMapTo.LS ? GamepadStick.LEFT : GamepadStick.RIGHT;
|
||||||
|
this.#updateStick(analog, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseClick = (data: MkbMouseClick) => {
|
||||||
|
if (!data || !data.key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const analog = mouseMapTo === MouseMapTo.LS ? GamepadStick.LEFT : GamepadStick.RIGHT;
|
const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[data.key.code]!;
|
||||||
|
if (typeof buttonIndex === 'undefined') {
|
||||||
let { x, y } = this.#getStickAxes(analog);
|
return;
|
||||||
const length = this.#vectorLength(x, y);
|
|
||||||
|
|
||||||
const clampedLength = Math.min(1.0, length);
|
|
||||||
const decayStrength = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_STICK_DECAY_STRENGTH];
|
|
||||||
const decay = 1 - clampedLength * clampedLength * decayStrength;
|
|
||||||
const minDecay = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_STICK_DECAY_MIN];
|
|
||||||
const clampedDecay = Math.min(1 - minDecay, decay);
|
|
||||||
|
|
||||||
x *= clampedDecay;
|
|
||||||
y *= clampedDecay;
|
|
||||||
|
|
||||||
const deadzoneCounterweight = 20 * MkbHandler.DEFAULT_DEADZONE_COUNTERWEIGHT;
|
|
||||||
if (Math.abs(x) <= deadzoneCounterweight && Math.abs(y) <= deadzoneCounterweight) {
|
|
||||||
x = 0;
|
|
||||||
y = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.#allowStickDecaying) {
|
this.#pressButton(buttonIndex, data.pressed);
|
||||||
this.#updateStick(analog, x, y);
|
|
||||||
|
|
||||||
(x !== 0 || y !== 0) && requestAnimationFrame(this.#decayStick);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#onMouseStopped = () => {
|
handleMouseMove = (data: MkbMouseMove) => {
|
||||||
this.#allowStickDecaying = true;
|
|
||||||
requestAnimationFrame(this.#decayStick);
|
|
||||||
}
|
|
||||||
|
|
||||||
#onMouseMoveEvent = (e: MouseEvent) => {
|
|
||||||
// TODO: optimize this
|
// TODO: optimize this
|
||||||
const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_MAP_TO];
|
const mouseMapTo = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_MAP_TO];
|
||||||
if (mouseMapTo === MouseMapTo.OFF) {
|
if (mouseMapTo === MouseMapTo.OFF) {
|
||||||
@ -283,17 +367,13 @@ export class MkbHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#allowStickDecaying = false;
|
|
||||||
this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout);
|
this.#detectMouseStoppedTimeout && clearTimeout(this.#detectMouseStoppedTimeout);
|
||||||
this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 10);
|
this.#detectMouseStoppedTimeout = window.setTimeout(this.#onMouseStopped.bind(this), 50);
|
||||||
|
|
||||||
const deltaX = e.movementX;
|
|
||||||
const deltaY = e.movementY;
|
|
||||||
|
|
||||||
const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT];
|
const deadzoneCounterweight = this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT];
|
||||||
|
|
||||||
let x = deltaX * this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_SENSITIVITY_X];
|
let x = data.movementX * this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_SENSITIVITY_X];
|
||||||
let y = deltaY * this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_SENSITIVITY_Y];
|
let y = data.movementY * this.#CURRENT_PRESET_DATA.mouse[MkbPresetKey.MOUSE_SENSITIVITY_Y];
|
||||||
|
|
||||||
let length = this.#vectorLength(x, y);
|
let length = this.#vectorLength(x, y);
|
||||||
if (length !== 0 && length < deadzoneCounterweight) {
|
if (length !== 0 && length < deadzoneCounterweight) {
|
||||||
@ -308,18 +388,33 @@ export class MkbHandler {
|
|||||||
this.#updateStick(analog, x, y);
|
this.#updateStick(analog, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseWheel = (data: MkbMouseWheel): boolean => {
|
||||||
|
if (!data || !data.key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonIndex = this.#CURRENT_PRESET_DATA.mapping[data.key.code]!;
|
||||||
|
if (typeof buttonIndex === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.#prevWheelCode === null || this.#prevWheelCode === data.key.code) {
|
||||||
|
this.#wheelStoppedTimeout && clearTimeout(this.#wheelStoppedTimeout);
|
||||||
|
this.#pressButton(buttonIndex, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#wheelStoppedTimeout = window.setTimeout(() => {
|
||||||
|
this.#prevWheelCode = null;
|
||||||
|
this.#pressButton(buttonIndex, false);
|
||||||
|
}, 20);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
toggle = () => {
|
toggle = () => {
|
||||||
this.#enabled = !this.#enabled;
|
this.#enabled = !this.#enabled;
|
||||||
this.#enabled ? document.pointerLockElement && this.start() : this.stop();
|
|
||||||
|
|
||||||
Toast.show(t('mouse-and-keyboard'), t(this.#enabled ? 'enabled' : 'disabled'), {instant: true});
|
Toast.show(t('mouse-and-keyboard'), t(this.#enabled ? 'enabled' : 'disabled'), {instant: true});
|
||||||
|
this.#mouseDataProvider?.toggle(this.#enabled);
|
||||||
if (this.#enabled) {
|
|
||||||
!document.pointerLockElement && this.#waitForPointerLock(true);
|
|
||||||
} else {
|
|
||||||
this.#waitForPointerLock(false);
|
|
||||||
document.pointerLockElement && document.exitPointerLock();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#getCurrentPreset = (): Promise<MkbStoredPreset> => {
|
#getCurrentPreset = (): Promise<MkbStoredPreset> => {
|
||||||
@ -338,47 +433,35 @@ export class MkbHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#onPointerLockChange = () => {
|
waitForMouseData = (wait: boolean) => {
|
||||||
if (this.#enabled && !document.pointerLockElement) {
|
|
||||||
this.stop();
|
|
||||||
this.#waitForPointerLock(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#onPointerLockError = (e: Event) => {
|
|
||||||
console.log(e);
|
|
||||||
this.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
#onActivatePointerLock = () => {
|
|
||||||
if (!document.pointerLockElement) {
|
|
||||||
document.body.requestPointerLock();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#waitForPointerLock(false);
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
#waitForPointerLock = (wait: boolean) => {
|
|
||||||
this.#$message && this.#$message.classList.toggle('bx-gone', !wait);
|
this.#$message && this.#$message.classList.toggle('bx-gone', !wait);
|
||||||
}
|
}
|
||||||
|
|
||||||
#onStreamMenuShown = () => {
|
#onPollingModeChanged = (e: Event) => {
|
||||||
this.#enabled && this.#waitForPointerLock(false);
|
if (!this.#$message) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#onStreamMenuHidden = () => {
|
const mode = (e as any).mode;
|
||||||
this.#enabled && this.#waitForPointerLock(true);
|
if (mode === 'None') {
|
||||||
|
this.#$message.classList.remove('bx-offscreen');
|
||||||
|
} else {
|
||||||
|
this.#$message.classList.add('bx-offscreen');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init = () => {
|
init = () => {
|
||||||
this.refreshPresetData();
|
this.refreshPresetData();
|
||||||
this.#enabled = true;
|
this.#enabled = true;
|
||||||
|
|
||||||
window.addEventListener('keydown', this.#onKeyboardEvent);
|
if (AppInterface) {
|
||||||
|
this.#mouseDataProvider = new WebSocketMouseDataProvider(this);
|
||||||
|
} else {
|
||||||
|
this.#mouseDataProvider = new PointerLockMouseDataProvider(this);
|
||||||
|
}
|
||||||
|
this.#mouseDataProvider.init();
|
||||||
|
|
||||||
document.addEventListener('pointerlockchange', this.#onPointerLockChange);
|
window.addEventListener('keydown', this.#onKeyboardEvent);
|
||||||
document.addEventListener('pointerlockerror', this.#onPointerLockError);
|
|
||||||
|
|
||||||
this.#$message = CE('div', {'class': 'bx-mkb-pointer-lock-msg bx-gone'},
|
this.#$message = CE('div', {'class': 'bx-mkb-pointer-lock-msg bx-gone'},
|
||||||
createButton({
|
createButton({
|
||||||
@ -397,13 +480,12 @@ export class MkbHandler {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.#$message.addEventListener('click', this.#onActivatePointerLock);
|
this.#$message.addEventListener('click', this.start.bind(this));
|
||||||
document.documentElement.appendChild(this.#$message);
|
document.documentElement.appendChild(this.#$message);
|
||||||
|
|
||||||
window.addEventListener(BxEvent.STREAM_MENU_SHOWN, this.#onStreamMenuShown);
|
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged);
|
||||||
window.addEventListener(BxEvent.STREAM_MENU_HIDDEN, this.#onStreamMenuHidden);
|
|
||||||
|
|
||||||
this.#waitForPointerLock(true);
|
this.waitForMouseData(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy = () => {
|
destroy = () => {
|
||||||
@ -411,31 +493,31 @@ export class MkbHandler {
|
|||||||
this.#enabled = false;
|
this.#enabled = false;
|
||||||
this.stop();
|
this.stop();
|
||||||
|
|
||||||
this.#waitForPointerLock(false);
|
this.waitForMouseData(false);
|
||||||
document.pointerLockElement && document.exitPointerLock();
|
document.pointerLockElement && document.exitPointerLock();
|
||||||
|
|
||||||
window.removeEventListener('keydown', this.#onKeyboardEvent);
|
window.removeEventListener('keydown', this.#onKeyboardEvent);
|
||||||
|
|
||||||
document.removeEventListener('pointerlockchange', this.#onPointerLockChange);
|
this.#mouseDataProvider?.destroy();
|
||||||
document.removeEventListener('pointerlockerror', this.#onPointerLockError);
|
|
||||||
|
|
||||||
window.removeEventListener(BxEvent.STREAM_MENU_SHOWN, this.#onStreamMenuShown);
|
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.#onPollingModeChanged);
|
||||||
window.removeEventListener(BxEvent.STREAM_MENU_HIDDEN, this.#onStreamMenuHidden);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start = () => {
|
start = () => {
|
||||||
|
if (!this.#enabled) {
|
||||||
|
this.#enabled = true;
|
||||||
|
Toast.show(t('mouse-and-keyboard'), t('enabled'), {instant: true});
|
||||||
|
}
|
||||||
|
|
||||||
this.#isPolling = true;
|
this.#isPolling = true;
|
||||||
window.navigator.getGamepads = this.#patchedGetGamepads;
|
|
||||||
|
|
||||||
this.#resetGamepad();
|
this.#resetGamepad();
|
||||||
|
window.navigator.getGamepads = this.#patchedGetGamepads;
|
||||||
|
|
||||||
|
this.waitForMouseData(false);
|
||||||
|
|
||||||
window.addEventListener('keyup', this.#onKeyboardEvent);
|
window.addEventListener('keyup', this.#onKeyboardEvent);
|
||||||
|
this.#mouseDataProvider?.start();
|
||||||
window.addEventListener('mousemove', this.#onMouseMoveEvent);
|
|
||||||
window.addEventListener('mousedown', this.#onMouseEvent);
|
|
||||||
window.addEventListener('mouseup', this.#onMouseEvent);
|
|
||||||
window.addEventListener('wheel', this.#onWheelEvent);
|
|
||||||
window.addEventListener('contextmenu', this.#disableContextMenu);
|
|
||||||
|
|
||||||
// Dispatch "gamepadconnected" event
|
// Dispatch "gamepadconnected" event
|
||||||
const virtualGamepad = this.#getVirtualGamepad();
|
const virtualGamepad = this.#getVirtualGamepad();
|
||||||
@ -451,6 +533,8 @@ export class MkbHandler {
|
|||||||
this.#isPolling = false;
|
this.#isPolling = false;
|
||||||
|
|
||||||
// Dispatch "gamepaddisconnected" event
|
// Dispatch "gamepaddisconnected" event
|
||||||
|
this.#resetGamepad();
|
||||||
|
|
||||||
const virtualGamepad = this.#getVirtualGamepad();
|
const virtualGamepad = this.#getVirtualGamepad();
|
||||||
virtualGamepad.connected = false;
|
virtualGamepad.connected = false;
|
||||||
virtualGamepad.timestamp = performance.now();
|
virtualGamepad.timestamp = performance.now();
|
||||||
@ -461,19 +545,14 @@ export class MkbHandler {
|
|||||||
|
|
||||||
window.navigator.getGamepads = this.#nativeGetGamepads;
|
window.navigator.getGamepads = this.#nativeGetGamepads;
|
||||||
|
|
||||||
this.#resetGamepad();
|
|
||||||
|
|
||||||
window.removeEventListener('keyup', this.#onKeyboardEvent);
|
window.removeEventListener('keyup', this.#onKeyboardEvent);
|
||||||
|
|
||||||
window.removeEventListener('mousemove', this.#onMouseMoveEvent);
|
this.waitForMouseData(true);
|
||||||
window.removeEventListener('mousedown', this.#onMouseEvent);
|
this.#mouseDataProvider?.stop();
|
||||||
window.removeEventListener('mouseup', this.#onMouseEvent);
|
|
||||||
window.removeEventListener('wheel', this.#onWheelEvent);
|
|
||||||
window.removeEventListener('contextmenu', this.#disableContextMenu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static setupEvents() {
|
static setupEvents() {
|
||||||
getPref(PrefKey.MKB_ENABLED) && !UserAgent.isMobile() && window.addEventListener(BxEvent.STREAM_PLAYING, () => {
|
getPref(PrefKey.MKB_ENABLED) && (AppInterface || !UserAgent.isMobile()) && window.addEventListener(BxEvent.STREAM_PLAYING, () => {
|
||||||
// Enable MKB
|
// Enable MKB
|
||||||
if (!STATES.currentStream.titleInfo?.details.hasMkbSupport) {
|
if (!STATES.currentStream.titleInfo?.details.hasMkbSupport) {
|
||||||
BxLogger.info(LOG_TAG, 'Emulate MKB');
|
BxLogger.info(LOG_TAG, 'Emulate MKB');
|
||||||
|
@ -24,11 +24,11 @@ export class MkbPreset {
|
|||||||
type: SettingElementType.NUMBER_STEPPER,
|
type: SettingElementType.NUMBER_STEPPER,
|
||||||
default: 50,
|
default: 50,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 200,
|
max: 300,
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
exactTicks: 20,
|
exactTicks: 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -37,11 +37,11 @@ export class MkbPreset {
|
|||||||
type: SettingElementType.NUMBER_STEPPER,
|
type: SettingElementType.NUMBER_STEPPER,
|
||||||
default: 50,
|
default: 50,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 200,
|
max: 300,
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
exactTicks: 20,
|
exactTicks: 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -50,38 +50,13 @@ export class MkbPreset {
|
|||||||
type: SettingElementType.NUMBER_STEPPER,
|
type: SettingElementType.NUMBER_STEPPER,
|
||||||
default: 20,
|
default: 20,
|
||||||
min: 1,
|
min: 1,
|
||||||
max: 100,
|
max: 50,
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
suffix: '%',
|
suffix: '%',
|
||||||
exactTicks: 10,
|
exactTicks: 10,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[MkbPresetKey.MOUSE_STICK_DECAY_STRENGTH]: {
|
|
||||||
label: t('stick-decay-strength'),
|
|
||||||
type: SettingElementType.NUMBER_STEPPER,
|
|
||||||
default: 100,
|
|
||||||
min: 10,
|
|
||||||
max: 100,
|
|
||||||
|
|
||||||
params: {
|
|
||||||
suffix: '%',
|
|
||||||
exactTicks: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
[MkbPresetKey.MOUSE_STICK_DECAY_MIN]: {
|
|
||||||
label: t('stick-decay-minimum'),
|
|
||||||
type: SettingElementType.NUMBER_STEPPER,
|
|
||||||
default: 10,
|
|
||||||
min: 1,
|
|
||||||
max: 10,
|
|
||||||
|
|
||||||
params: {
|
|
||||||
suffix: '%',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static DEFAULT_PRESET: MkbPresetData = {
|
static DEFAULT_PRESET: MkbPresetData = {
|
||||||
@ -124,11 +99,9 @@ export class MkbPreset {
|
|||||||
|
|
||||||
'mouse': {
|
'mouse': {
|
||||||
[MkbPresetKey.MOUSE_MAP_TO]: MouseMapTo[MouseMapTo.RS],
|
[MkbPresetKey.MOUSE_MAP_TO]: MouseMapTo[MouseMapTo.RS],
|
||||||
[MkbPresetKey.MOUSE_SENSITIVITY_X]: 50,
|
[MkbPresetKey.MOUSE_SENSITIVITY_X]: 100,
|
||||||
[MkbPresetKey.MOUSE_SENSITIVITY_Y]: 50,
|
[MkbPresetKey.MOUSE_SENSITIVITY_Y]: 100,
|
||||||
[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT]: 20,
|
[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT]: 20,
|
||||||
[MkbPresetKey.MOUSE_STICK_DECAY_STRENGTH]: 100,
|
|
||||||
[MkbPresetKey.MOUSE_STICK_DECAY_MIN]: 10,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -149,8 +122,6 @@ export class MkbPreset {
|
|||||||
mouse[MkbPresetKey.MOUSE_SENSITIVITY_X] *= MkbHandler.DEFAULT_PANNING_SENSITIVITY;
|
mouse[MkbPresetKey.MOUSE_SENSITIVITY_X] *= MkbHandler.DEFAULT_PANNING_SENSITIVITY;
|
||||||
mouse[MkbPresetKey.MOUSE_SENSITIVITY_Y] *= MkbHandler.DEFAULT_PANNING_SENSITIVITY;
|
mouse[MkbPresetKey.MOUSE_SENSITIVITY_Y] *= MkbHandler.DEFAULT_PANNING_SENSITIVITY;
|
||||||
mouse[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT] *= MkbHandler.DEFAULT_DEADZONE_COUNTERWEIGHT;
|
mouse[MkbPresetKey.MOUSE_DEADZONE_COUNTERWEIGHT] *= MkbHandler.DEFAULT_DEADZONE_COUNTERWEIGHT;
|
||||||
mouse[MkbPresetKey.MOUSE_STICK_DECAY_STRENGTH] *= 0.01;
|
|
||||||
mouse[MkbPresetKey.MOUSE_STICK_DECAY_MIN] *= 0.01;
|
|
||||||
|
|
||||||
const mouseMapTo = MouseMapTo[mouse[MkbPresetKey.MOUSE_MAP_TO]!];
|
const mouseMapTo = MouseMapTo[mouse[MkbPresetKey.MOUSE_MAP_TO]!];
|
||||||
if (typeof mouseMapTo !== 'undefined') {
|
if (typeof mouseMapTo !== 'undefined') {
|
||||||
|
152
src/modules/mkb/pointer-client.ts
Normal file
152
src/modules/mkb/pointer-client.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
import { BxLogger } from "@/utils/bx-logger";
|
||||||
|
import type { MkbHandler } from "./mkb-handler";
|
||||||
|
import { KeyHelper } from "./key-helper";
|
||||||
|
import { WheelCode } from "./definitions";
|
||||||
|
import { Toast } from "@/utils/toast";
|
||||||
|
|
||||||
|
const LOG_TAG = 'PointerClient';
|
||||||
|
|
||||||
|
enum PointerAction {
|
||||||
|
MOVE = 1,
|
||||||
|
BUTTON_PRESS = 2,
|
||||||
|
BUTTON_RELEASE = 3,
|
||||||
|
SCROLL = 4,
|
||||||
|
POINTER_CAPTURE_CHANGED = 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
const FixedMouseIndex = {
|
||||||
|
1: 0,
|
||||||
|
2: 2,
|
||||||
|
4: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PointerClient {
|
||||||
|
static #PORT = 9269;
|
||||||
|
|
||||||
|
private static instance: PointerClient;
|
||||||
|
public static getInstance(): PointerClient {
|
||||||
|
if (!PointerClient.instance) {
|
||||||
|
PointerClient.instance = new PointerClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
return PointerClient.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
#socket: WebSocket | undefined | null;
|
||||||
|
#mkbHandler: MkbHandler | undefined;
|
||||||
|
|
||||||
|
start(mkbHandler: MkbHandler) {
|
||||||
|
this.#mkbHandler = mkbHandler;
|
||||||
|
|
||||||
|
// Create WebSocket connection.
|
||||||
|
this.#socket = new WebSocket(`ws://localhost:${PointerClient.#PORT}`);
|
||||||
|
this.#socket.binaryType = 'arraybuffer';
|
||||||
|
|
||||||
|
// Connection opened
|
||||||
|
this.#socket.addEventListener('open', (event) => {
|
||||||
|
BxLogger.info(LOG_TAG, 'connected')
|
||||||
|
});
|
||||||
|
|
||||||
|
// Error
|
||||||
|
this.#socket.addEventListener('error', (event) => {
|
||||||
|
BxLogger.error(LOG_TAG, event);
|
||||||
|
Toast.show('Cannot setup mouse');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#socket.addEventListener('close', (event) => {
|
||||||
|
this.#socket = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for messages
|
||||||
|
this.#socket.addEventListener('message', (event) => {
|
||||||
|
const dataView = new DataView(event.data);
|
||||||
|
|
||||||
|
let messageType = dataView.getInt8(0);
|
||||||
|
let offset = Int8Array.BYTES_PER_ELEMENT;
|
||||||
|
switch (messageType) {
|
||||||
|
case PointerAction.MOVE:
|
||||||
|
this.onMove(dataView, offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PointerAction.BUTTON_PRESS:
|
||||||
|
case PointerAction.BUTTON_RELEASE:
|
||||||
|
this.onPress(messageType, dataView, offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PointerAction.SCROLL:
|
||||||
|
this.onScroll(dataView, offset);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PointerAction.POINTER_CAPTURE_CHANGED:
|
||||||
|
this.onPointerCaptureChanged(dataView, offset);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMove(dataView: DataView, offset: number) {
|
||||||
|
// [X, Y]
|
||||||
|
const x = dataView.getInt16(offset);
|
||||||
|
offset += Int16Array.BYTES_PER_ELEMENT;
|
||||||
|
const y = dataView.getInt16(offset);
|
||||||
|
|
||||||
|
this.#mkbHandler?.handleMouseMove({
|
||||||
|
movementX: x,
|
||||||
|
movementY: y,
|
||||||
|
});
|
||||||
|
// BxLogger.info(LOG_TAG, 'move', x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPress(messageType: PointerAction, dataView: DataView, offset: number) {
|
||||||
|
const buttonIndex = dataView.getInt8(offset);
|
||||||
|
const fixedIndex = FixedMouseIndex[buttonIndex as keyof typeof FixedMouseIndex];
|
||||||
|
const keyCode = 'Mouse' + fixedIndex;
|
||||||
|
|
||||||
|
this.#mkbHandler?.handleMouseClick({
|
||||||
|
key: {
|
||||||
|
code: keyCode,
|
||||||
|
name: KeyHelper.codeToKeyName(keyCode),
|
||||||
|
},
|
||||||
|
pressed: messageType === PointerAction.BUTTON_PRESS,
|
||||||
|
});
|
||||||
|
|
||||||
|
// BxLogger.info(LOG_TAG, 'press', buttonIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
onScroll(dataView: DataView, offset: number) {
|
||||||
|
// [V_SCROLL, H_SCROLL]
|
||||||
|
const vScroll = dataView.getInt8(offset);
|
||||||
|
offset += Int8Array.BYTES_PER_ELEMENT;
|
||||||
|
const hScroll = dataView.getInt8(offset);
|
||||||
|
|
||||||
|
let code = '';
|
||||||
|
if (vScroll < 0) {
|
||||||
|
code = WheelCode.SCROLL_UP;
|
||||||
|
} else if (vScroll > 0) {
|
||||||
|
code = WheelCode.SCROLL_DOWN;
|
||||||
|
} else if (hScroll < 0) {
|
||||||
|
code = WheelCode.SCROLL_LEFT;
|
||||||
|
} else if (hScroll > 0) {
|
||||||
|
code = WheelCode.SCROLL_RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
code && this.#mkbHandler?.handleMouseWheel({
|
||||||
|
key: {
|
||||||
|
code: code,
|
||||||
|
name: KeyHelper.codeToKeyName(code),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// BxLogger.info(LOG_TAG, 'scroll', vScroll, hScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPointerCaptureChanged(dataView: DataView, offset: number) {
|
||||||
|
const hasCapture = dataView.getInt8(offset) === 1;
|
||||||
|
!hasCapture && this.#mkbHandler?.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
try {
|
||||||
|
this.#socket?.close();
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
}
|
@ -407,7 +407,7 @@ e.guideUI = null;
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newCode = `
|
const newCode = `
|
||||||
window.BX_EXPOSED.onPollingModeChanged && window.BX_EXPOSED.onPollingModeChanged(e);
|
BxEvent.dispatch(window, BxEvent.XCLOUD_POLLING_MODE_CHANGED, {mode: e});
|
||||||
`;
|
`;
|
||||||
str = str.replace(text, text + newCode);
|
str = str.replace(text, text + newCode);
|
||||||
return str;
|
return str;
|
||||||
|
@ -105,12 +105,6 @@ export function injectStreamMenuButtons() {
|
|||||||
if (!($node as HTMLElement).className || !($node as HTMLElement).className.startsWith) {
|
if (!($node as HTMLElement).className || !($node as HTMLElement).className.startsWith) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($node as HTMLElement).className.startsWith('StreamMenu')) {
|
|
||||||
if (!document.querySelector('div[class^=PureInStreamConfirmationModal]')) {
|
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_MENU_HIDDEN);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
item.addedNodes.forEach(async $node => {
|
item.addedNodes.forEach(async $node => {
|
||||||
@ -139,8 +133,6 @@ export function injectStreamMenuButtons() {
|
|||||||
|
|
||||||
// Render badges
|
// Render badges
|
||||||
if ($elm.className?.startsWith('StreamMenu-module__container')) {
|
if ($elm.className?.startsWith('StreamMenu-module__container')) {
|
||||||
BxEvent.dispatch(window, BxEvent.STREAM_MENU_SHOWN);
|
|
||||||
|
|
||||||
const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
|
const $btnCloseHud = document.querySelector('button[class*=StreamMenu-module__backButton]');
|
||||||
if (!$btnCloseHud) {
|
if (!$btnCloseHud) {
|
||||||
return;
|
return;
|
||||||
|
20
src/types/index.d.ts
vendored
20
src/types/index.d.ts
vendored
@ -74,3 +74,23 @@ type XcloudTitleInfo = {
|
|||||||
declare module '*.js';
|
declare module '*.js';
|
||||||
declare module '*.svg';
|
declare module '*.svg';
|
||||||
declare module '*.styl';
|
declare module '*.styl';
|
||||||
|
|
||||||
|
type MkbMouseMove = {
|
||||||
|
movementX: number;
|
||||||
|
movementY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkbMouseClick = {
|
||||||
|
key: {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
} | null;
|
||||||
|
pressed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type MkbMouseWheel = {
|
||||||
|
key: {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
2
src/types/preferences.d.ts
vendored
2
src/types/preferences.d.ts
vendored
@ -6,7 +6,7 @@ export type PreferenceSetting = {
|
|||||||
note?: string | HTMLElement;
|
note?: string | HTMLElement;
|
||||||
type?: SettingElementType;
|
type?: SettingElementType;
|
||||||
ready?: (setting: PreferenceSetting) => void;
|
ready?: (setting: PreferenceSetting) => void;
|
||||||
migrate?: (savedPrefs: any, value: any) => {};
|
migrate?: (this: Preferences, savedPrefs: any, value: any) => void;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
steps?: number;
|
steps?: number;
|
||||||
|
@ -13,9 +13,6 @@ export enum BxEvent {
|
|||||||
STREAM_STOPPED = 'bx-stream-stopped',
|
STREAM_STOPPED = 'bx-stream-stopped',
|
||||||
STREAM_ERROR_PAGE = 'bx-stream-error-page',
|
STREAM_ERROR_PAGE = 'bx-stream-error-page',
|
||||||
|
|
||||||
STREAM_MENU_SHOWN = 'bx-stream-menu-shown',
|
|
||||||
STREAM_MENU_HIDDEN = 'bx-stream-menu-hidden',
|
|
||||||
|
|
||||||
STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected',
|
STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected',
|
||||||
STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected',
|
STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected',
|
||||||
|
|
||||||
@ -41,6 +38,8 @@ export enum BxEvent {
|
|||||||
// xCloud Dialog events
|
// xCloud Dialog events
|
||||||
XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown',
|
XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown',
|
||||||
XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed',
|
XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed',
|
||||||
|
|
||||||
|
XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum XcloudEvent {
|
export enum XcloudEvent {
|
||||||
|
@ -16,23 +16,6 @@ export enum InputType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const BxExposed = {
|
export const BxExposed = {
|
||||||
// Enable/disable Game Bar when playing/pausing
|
|
||||||
onPollingModeChanged: (mode: 'All' | 'None') => {
|
|
||||||
if (getPref(PrefKey.GAME_BAR_POSITION) === 'off') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gameBar = GameBar.getInstance();
|
|
||||||
|
|
||||||
if (!STATES.isPlaying) {
|
|
||||||
gameBar.disable();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Toggle Game bar
|
|
||||||
mode !== 'None' ? gameBar.disable() : gameBar.enable();
|
|
||||||
},
|
|
||||||
|
|
||||||
getTitleInfo: () => STATES.currentStream.titleInfo,
|
getTitleInfo: () => STATES.currentStream.titleInfo,
|
||||||
|
|
||||||
modifyTitleInfo: (titleInfo: XcloudTitleInfo): XcloudTitleInfo => {
|
modifyTitleInfo: (titleInfo: XcloudTitleInfo): XcloudTitleInfo => {
|
||||||
|
@ -103,7 +103,7 @@ export function patchRtcPeerConnection() {
|
|||||||
try {
|
try {
|
||||||
const maxVideoBitrate = getPref(PrefKey.BITRATE_VIDEO_MAX);
|
const maxVideoBitrate = getPref(PrefKey.BITRATE_VIDEO_MAX);
|
||||||
if (maxVideoBitrate > 0) {
|
if (maxVideoBitrate > 0) {
|
||||||
arguments[0].sdp = patchSdpBitrate(arguments[0].sdp, maxVideoBitrate * 1000);
|
arguments[0].sdp = patchSdpBitrate(arguments[0].sdp, Math.round(maxVideoBitrate / 1000));
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
BxLogger.error('setLocalDescription', e);
|
BxLogger.error('setLocalDescription', e);
|
||||||
|
@ -4,7 +4,7 @@ import { SettingElement, SettingElementType } from "@utils/settings";
|
|||||||
import { UserAgentProfile } from "@utils/user-agent";
|
import { UserAgentProfile } from "@utils/user-agent";
|
||||||
import { StreamStat } from "@modules/stream/stream-stats";
|
import { StreamStat } from "@modules/stream/stream-stats";
|
||||||
import type { PreferenceSetting, PreferenceSettings } from "@/types/preferences";
|
import type { PreferenceSetting, PreferenceSettings } from "@/types/preferences";
|
||||||
import { STATES } from "@utils/global";
|
import { AppInterface, STATES } from "@utils/global";
|
||||||
|
|
||||||
export enum PrefKey {
|
export enum PrefKey {
|
||||||
LAST_UPDATE_CHECK = 'version_last_check',
|
LAST_UPDATE_CHECK = 'version_last_check',
|
||||||
@ -325,21 +325,33 @@ export class Preferences {
|
|||||||
note: '⚠️ ' + t('unexpected-behavior'),
|
note: '⚠️ ' + t('unexpected-behavior'),
|
||||||
default: 0,
|
default: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 14,
|
max: 14 * 1024 * 1000,
|
||||||
steps: 1,
|
steps: 100 * 1024,
|
||||||
params: {
|
params: {
|
||||||
suffix: ' Mb/s',
|
exactTicks: 5 * 1024 * 1000,
|
||||||
exactTicks: 5,
|
|
||||||
customTextValue: (value: any) => {
|
customTextValue: (value: any) => {
|
||||||
value = parseInt(value);
|
value = parseInt(value);
|
||||||
|
|
||||||
if (value === 0) {
|
if (value === 0) {
|
||||||
return t('unlimited');
|
return t('unlimited');
|
||||||
|
} else {
|
||||||
|
return (value / (1024 * 1000)).toFixed(1) + ' Mb/s';
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
migrate: function(savedPrefs: any, value: any) {
|
||||||
|
try {
|
||||||
|
value = parseInt(value);
|
||||||
|
if (value < 100) {
|
||||||
|
value *= 1024 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set(PrefKey.BITRATE_VIDEO_MAX, value);
|
||||||
|
savedPrefs[PrefKey.BITRATE_VIDEO_MAX] = value;
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.GAME_BAR_POSITION]: {
|
[PrefKey.GAME_BAR_POSITION]: {
|
||||||
@ -405,7 +417,7 @@ export class Preferences {
|
|||||||
default: false,
|
default: false,
|
||||||
unsupported: ((): string | boolean => {
|
unsupported: ((): string | boolean => {
|
||||||
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||||
return userAgent.match(/(android|iphone|ipad)/) ? t('browser-unsupported-feature') : false;
|
return !AppInterface && userAgent.match(/(android|iphone|ipad)/) ? t('browser-unsupported-feature') : false;
|
||||||
})(),
|
})(),
|
||||||
ready: (setting: PreferenceSetting) => {
|
ready: (setting: PreferenceSetting) => {
|
||||||
let note;
|
let note;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user