mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-05 05:41:43 +02:00
Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
ca64b592c5 | |||
d0a8b894b9 | |||
3230b99a05 | |||
f0e4d4b8d0 | |||
d0b84d4591 | |||
d292bef5e7 | |||
5381575048 | |||
7206c9e8bc | |||
5fb0dec9f2 | |||
4ffc034076 | |||
b11d465804 | |||
e1ba2344b7 | |||
8c446ceec3 | |||
7438375356 | |||
741bc9a4e5 | |||
de7bf3edc7 | |||
79ebb1a817 | |||
160044c958 | |||
78c70b5d90 | |||
9044a07c0b |
209
dist/better-xcloud.lite.user.js
vendored
209
dist/better-xcloud.lite.user.js
vendored
File diff suppressed because one or more lines are too long
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 6.0.2
|
||||
// @version 6.0.3
|
||||
// ==/UserScript==
|
||||
|
371
dist/better-xcloud.user.js
vendored
371
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -16,7 +16,7 @@
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
min-width: 420px;
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
z-index: var(--bx-key-binding-dialog-z-index);
|
||||
background: #1a1b1e;
|
||||
|
@ -45,7 +45,7 @@
|
||||
width: 450px;
|
||||
max-width: calc(100vw - 20px);
|
||||
margin: 0 0 0 auto;
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
|
||||
max-height: 95vh;
|
||||
flex-direction: column;
|
||||
@ -90,6 +90,13 @@
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-default-preset-note {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-centered-dialog,
|
||||
@ -214,5 +221,6 @@
|
||||
|
||||
.bx-settings-row {
|
||||
background: none;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
width: 420px;
|
||||
max-width: calc(100vw - 20px);
|
||||
margin: 0 0 0 auto;
|
||||
padding: 20px;
|
||||
padding: 16px;
|
||||
|
||||
> .bx-button {
|
||||
display: table;
|
||||
|
@ -62,6 +62,7 @@ div.bx-select {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
min-height: 15px;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
@ -70,6 +71,8 @@ div.bx-select {
|
||||
text-align: left;
|
||||
line-height: initial;
|
||||
white-space: pre;
|
||||
min-height: 15px;
|
||||
align-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
export const enum ShortcutAction {
|
||||
BETTER_XCLOUD_SETTINGS_SHOW = 'bx.settings.show',
|
||||
|
||||
CONTROLLER_XBOX_BUTTON_PRESS = 'controller.xbox.press',
|
||||
|
||||
STREAM_VIDEO_TOGGLE = 'stream.video.toggle',
|
||||
STREAM_SCREENSHOT_CAPTURE = 'stream.screenshot.capture',
|
||||
|
||||
|
62
src/index.ts
62
src/index.ts
@ -44,6 +44,7 @@ import { StreamSettings } from "./utils/stream-settings";
|
||||
import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler";
|
||||
import { GhPagesUtils } from "./utils/gh-pages";
|
||||
import { DeviceVibrationManager } from "./modules/device-vibration-manager";
|
||||
import { BxEventBus } from "./utils/bx-event-bus";
|
||||
|
||||
// Handle login page
|
||||
if (window.location.pathname.includes('/auth/msa')) {
|
||||
@ -159,7 +160,7 @@ document.addEventListener('readystatechange', e => {
|
||||
return;
|
||||
}
|
||||
|
||||
STATES.isSignedIn = !!((window as any).xbcUser?.isSignedIn);
|
||||
STATES.isSignedIn = !!window.xbcUser?.isSignedIn;
|
||||
|
||||
if (STATES.isSignedIn) {
|
||||
// Preload Remote Play
|
||||
@ -190,7 +191,7 @@ window.addEventListener('popstate', onHistoryChanged);
|
||||
window.history.pushState = patchHistoryMethod('pushState');
|
||||
window.history.replaceState = patchHistoryMethod('replaceState');
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => {
|
||||
BxEventBus.Script.once('xcloudServerUnavailable', () => {
|
||||
STATES.supportedRegion = false;
|
||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||
|
||||
@ -199,14 +200,14 @@ window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => {
|
||||
if ($unsupportedPage) {
|
||||
SettingsDialog.getInstance().show();
|
||||
}
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_SERVERS_READY, e => {
|
||||
BxEventBus.Script.on('xcloud.server.ready', () => {
|
||||
STATES.isSignedIn = true;
|
||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_LOADING, e => {
|
||||
BxEventBus.Stream.on('state.loading', () => {
|
||||
// Get title ID for screenshot's name
|
||||
if (window.location.pathname.includes('/launch/') && STATES.currentStream.titleInfo) {
|
||||
STATES.currentStream.titleSlug = productTitleToSlug(STATES.currentStream.titleInfo.product.title);
|
||||
@ -216,9 +217,9 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => {
|
||||
});
|
||||
|
||||
// Setup loading screen
|
||||
getPref(PrefKey.LOADING_SCREEN_GAME_ART) && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup);
|
||||
getPref(PrefKey.LOADING_SCREEN_GAME_ART) && BxEventBus.Script.on('titleInfo.ready', LoadingScreen.setup);
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_STARTING, e => {
|
||||
BxEventBus.Stream.on('state.starting', () => {
|
||||
// Hide loading screen
|
||||
LoadingScreen.hide();
|
||||
|
||||
@ -232,7 +233,7 @@ window.addEventListener(BxEvent.STREAM_STARTING, e => {
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
||||
BxEventBus.Stream.on('state.playing', payload => {
|
||||
window.BX_STREAM_SETTINGS = StreamSettings.settings;
|
||||
StreamSettings.refreshAllSettings();
|
||||
|
||||
@ -251,7 +252,7 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
||||
KeyboardShortcutHandler.getInstance().start();
|
||||
|
||||
// Setup screenshot
|
||||
const $video = (e as any).$video as HTMLVideoElement;
|
||||
const $video = payload.$video as HTMLVideoElement;
|
||||
ScreenshotManager.getInstance().updateCanvasSize($video.videoWidth, $video.videoHeight);
|
||||
|
||||
// Setup local co-op
|
||||
@ -262,8 +263,8 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
||||
updateVideoPlayer();
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
||||
BxEventBus.Stream.on('state.error', () => {
|
||||
BxEventBus.Stream.emit('state.stopped', {});
|
||||
});
|
||||
|
||||
isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
||||
@ -274,9 +275,9 @@ isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e
|
||||
});
|
||||
|
||||
// Detect game change
|
||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
||||
const dataChannel = (e as any).dataChannel;
|
||||
if (!dataChannel || dataChannel.label !== 'message') {
|
||||
BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||
const { dataChannel } = payload;
|
||||
if (dataChannel?.label !== 'message') {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -285,26 +286,29 @@ window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get xboxTitleId from message
|
||||
if (msg.data.includes('/titleinfo')) {
|
||||
const json = JSON.parse(JSON.parse(msg.data).content);
|
||||
const xboxTitleId = parseInt(json.titleid, 16);
|
||||
STATES.currentStream.xboxTitleId = xboxTitleId;
|
||||
if (!msg.data.includes('/titleinfo')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get titleSlug for Remote Play
|
||||
if (STATES.remotePlay.isPlaying) {
|
||||
STATES.currentStream.titleSlug = 'remote-play';
|
||||
if (json.focused) {
|
||||
const productTitle = await XboxApi.getProductTitle(xboxTitleId);
|
||||
if (productTitle) {
|
||||
STATES.currentStream.titleSlug = productTitleToSlug(productTitle);
|
||||
}
|
||||
// Get xboxTitleId from message
|
||||
const json = JSON.parse(JSON.parse(msg.data).content);
|
||||
const xboxTitleId = parseInt(json.titleid, 16);
|
||||
STATES.currentStream.xboxTitleId = xboxTitleId;
|
||||
|
||||
// Get titleSlug for Remote Play
|
||||
if (STATES.remotePlay.isPlaying) {
|
||||
STATES.currentStream.titleSlug = 'remote-play';
|
||||
if (json.focused) {
|
||||
const productTitle = await XboxApi.getProductTitle(xboxTitleId);
|
||||
if (productTitle) {
|
||||
STATES.currentStream.titleSlug = productTitleToSlug(productTitle);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function unload() {
|
||||
if (!STATES.isPlaying) {
|
||||
return;
|
||||
@ -340,9 +344,9 @@ function unload() {
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_STOPPED, unload);
|
||||
BxEventBus.Stream.on('state.stopped', unload);
|
||||
window.addEventListener('pagehide', e => {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
||||
BxEventBus.Stream.emit('state.stopped', {});
|
||||
});
|
||||
|
||||
isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { AppInterface, STATES } from "@utils/global";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { StreamSettings } from "@/utils/stream-settings";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
const VIBRATION_DATA_MAP = {
|
||||
gamepadIndex: 8,
|
||||
@ -37,8 +37,8 @@ export class DeviceVibrationManager {
|
||||
constructor() {
|
||||
this.boundOnMessage = this.onMessage.bind(this);
|
||||
|
||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
||||
const dataChannel = (e as any).dataChannel as RTCDataChannel;
|
||||
BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||
const { dataChannel } = payload;
|
||||
if (dataChannel?.label === 'input') {
|
||||
this.reset();
|
||||
|
||||
@ -47,9 +47,7 @@ export class DeviceVibrationManager {
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.DEVICE_VIBRATION_CHANGED, e => {
|
||||
this.setupDataChannel();
|
||||
});
|
||||
BxEventBus.Script.on('deviceVibration.updated', () => this.setupDataChannel());
|
||||
}
|
||||
|
||||
private setupDataChannel() {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
export abstract class BaseGameBarAction {
|
||||
abstract $content: HTMLElement;
|
||||
@ -7,7 +7,7 @@ export abstract class BaseGameBarAction {
|
||||
reset() {}
|
||||
|
||||
onClick(e: Event) {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
BxEventBus.Stream.emit('gameBar.activated', {});
|
||||
};
|
||||
|
||||
render(): HTMLElement {
|
||||
|
@ -13,6 +13,7 @@ import { SpeakerAction } from "./speaker-action";
|
||||
import { RendererAction } from "./renderer-action";
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { GameBarPosition, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
|
||||
export class GameBar {
|
||||
@ -81,7 +82,7 @@ export class GameBar {
|
||||
});
|
||||
|
||||
// Hide game bar after clicking on an action
|
||||
window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, this.hideBar);
|
||||
BxEventBus.Stream.on('gameBar.activated', this.hideBar);
|
||||
|
||||
$container.addEventListener('pointerover', this.clearHideTimeout);
|
||||
$container.addEventListener('pointerout', this.beginHideTimeout);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/microphone-shortcut";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
|
||||
export class MicrophoneAction extends BaseGameBarAction {
|
||||
@ -26,9 +26,8 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
|
||||
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||
|
||||
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
||||
const microphoneState = (e as any).microphoneState;
|
||||
const enabled = microphoneState === MicrophoneState.ENABLED;
|
||||
BxEventBus.Stream.on('microphone.state.changed', payload => {
|
||||
const enabled = payload.state === MicrophoneState.ENABLED;
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
|
||||
// Show the button in Game Bar if the mic is enabled
|
||||
|
@ -2,7 +2,7 @@ import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { RendererShortcut } from "../shortcuts/renderer-shortcut";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
|
||||
export class RendererAction extends BaseGameBarAction {
|
||||
@ -26,9 +26,8 @@ export class RendererAction extends BaseGameBarAction {
|
||||
|
||||
this.$content = CE('div', {}, $btnDefault, $btnActivated);
|
||||
|
||||
window.addEventListener(BxEvent.VIDEO_VISIBILITY_CHANGED, e => {
|
||||
const isShowing = (e as any).isShowing;
|
||||
this.$content.dataset.activated = (!isShowing).toString();
|
||||
BxEventBus.Stream.on('video.visibility.changed', payload => {
|
||||
this.$content.dataset.activated = (!payload.isVisible).toString();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { SoundShortcut, SpeakerState } from "../shortcuts/sound-shortcut";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
|
||||
export class SpeakerAction extends BaseGameBarAction {
|
||||
@ -26,10 +26,8 @@ export class SpeakerAction extends BaseGameBarAction {
|
||||
|
||||
this.$content = CE('div', {}, $btnEnable, $btnMuted);
|
||||
|
||||
window.addEventListener(BxEvent.SPEAKER_STATE_CHANGED, e => {
|
||||
const speakerState = (e as any).speakerState;
|
||||
const enabled = speakerState === SpeakerState.ENABLED;
|
||||
|
||||
BxEventBus.Stream.on('speaker.state.changed', payload => {
|
||||
const enabled = payload.state === SpeakerState.ENABLED;
|
||||
this.$content.dataset.activated = (!enabled).toString();
|
||||
});
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import { MkbPopup } from "./mkb-popup";
|
||||
import type { MkbConvertedPresetData } from "@/types/presets";
|
||||
import { StreamSettings } from "@/utils/stream-settings";
|
||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
const PointerToMouseButton = {
|
||||
1: 0,
|
||||
@ -517,7 +518,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
||||
window.addEventListener('keyup', this.onKeyboardEvent);
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
||||
window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.onDialogShown);
|
||||
BxEventBus.Script.on('dialog.shown', this.onDialogShown);
|
||||
|
||||
if (AppInterface) {
|
||||
// Android app doesn't support PointerLock API so we need to use a different method
|
||||
@ -568,7 +569,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
||||
}
|
||||
|
||||
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
||||
window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this.onDialogShown);
|
||||
BxEventBus.Script.off('dialog.shown', this.onDialogShown);
|
||||
|
||||
this.mouseDataProvider?.destroy();
|
||||
|
||||
@ -639,7 +640,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
||||
|
||||
static setupEvents() {
|
||||
if (isFullVersion()) {
|
||||
window.addEventListener(BxEvent.STREAM_PLAYING, () => {
|
||||
BxEventBus.Stream.on('state.playing', () => {
|
||||
if (STATES.currentStream.titleInfo?.details.hasMkbSupport) {
|
||||
// Enable native MKB in Android app
|
||||
NativeMkbHandler.getInstance()?.init();
|
||||
@ -649,7 +650,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
||||
});
|
||||
|
||||
if (EmulatedMkbHandler.isAllowed()) {
|
||||
window.addEventListener(BxEvent.MKB_UPDATED, () => {
|
||||
BxEventBus.Script.on('mkb.setting.updated', () => {
|
||||
EmulatedMkbHandler.getInstance()?.refreshPresetData();
|
||||
});
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { CE, createButton, ButtonStyle, type BxButtonOptions } from "@/utils/html";
|
||||
import { t } from "@/utils/translation";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||
import { SettingsDialog } from "../ui/dialog/settings-dialog";
|
||||
import type { MkbHandler } from "./base-mkb-handler";
|
||||
import { NativeMkbHandler } from "./native-mkb-handler";
|
||||
import { StreamSettings } from "@/utils/stream-settings";
|
||||
import { KeyHelper } from "./key-helper";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
type MkbPopupType = 'virtual' | 'native';
|
||||
|
||||
@ -24,7 +24,7 @@ export class MkbPopup {
|
||||
constructor() {
|
||||
this.render();
|
||||
|
||||
window.addEventListener(BxEvent.KEYBOARD_SHORTCUTS_UPDATED, e => {
|
||||
BxEventBus.Script.on('keyboardShortcuts.updated', () => {
|
||||
const $newButton = this.createActivateButton();
|
||||
this.$btnActivate.replaceWith($newButton);
|
||||
this.$btnActivate = $newButton;
|
||||
|
@ -12,6 +12,7 @@ import { KeyHelper } from "./key-helper";
|
||||
import { StreamSettings } from "@/utils/stream-settings";
|
||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||
import { NativeMkbMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
type NativeMouseData = {
|
||||
X: number,
|
||||
@ -100,10 +101,6 @@ export class NativeMkbHandler extends MkbHandler {
|
||||
this.onKeyboardEvent(event as KeyboardEvent);
|
||||
break;
|
||||
|
||||
case BxEvent.XCLOUD_DIALOG_SHOWN:
|
||||
this.onDialogShown();
|
||||
break;
|
||||
|
||||
case BxEvent.POINTER_LOCK_REQUESTED:
|
||||
this.onPointerLockRequested(event);
|
||||
break;
|
||||
@ -135,10 +132,10 @@ export class NativeMkbHandler extends MkbHandler {
|
||||
|
||||
window.addEventListener('keyup', this);
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this);
|
||||
window.addEventListener(BxEvent.POINTER_LOCK_REQUESTED, this);
|
||||
window.addEventListener(BxEvent.POINTER_LOCK_EXITED, this);
|
||||
window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this);
|
||||
BxEventBus.Script.on('dialog.shown', this.onDialogShown);
|
||||
|
||||
const shortcutKey = StreamSettings.findKeyboardShortcut(ShortcutAction.MKB_TOGGLE);
|
||||
if (shortcutKey) {
|
||||
@ -199,10 +196,10 @@ export class NativeMkbHandler extends MkbHandler {
|
||||
|
||||
window.removeEventListener('keyup', this);
|
||||
|
||||
window.removeEventListener(BxEvent.XCLOUD_DIALOG_SHOWN, this);
|
||||
window.removeEventListener(BxEvent.POINTER_LOCK_REQUESTED, this);
|
||||
window.removeEventListener(BxEvent.POINTER_LOCK_EXITED, this);
|
||||
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this);
|
||||
BxEventBus.Script.off('dialog.shown', this.onDialogShown);
|
||||
|
||||
this.waitForMouseData(false);
|
||||
document.pointerLockElement && document.exitPointerLock();
|
||||
|
@ -7,7 +7,6 @@ import { BxEvent } from "@/utils/bx-event";
|
||||
import codeControllerShortcuts from "./patches/controller-shortcuts.js" with { type: "text" };
|
||||
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
|
||||
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
||||
import codeSetCurrentlyFocusedInteractable from "./patches/set-currently-focused-interactable.js" with { type: "text" };
|
||||
import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type: "text" };
|
||||
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
||||
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
||||
@ -258,9 +257,8 @@ logFunc(logTag, '//', logMessage);
|
||||
let newSettings = JSON.stringify(FeatureGates);
|
||||
newSettings = newSettings.substring(1, newSettings.length - 1);
|
||||
|
||||
const newCode = newSettings;
|
||||
|
||||
str = str.substring(0, endIndex) + ',' + newCode + str.substring(endIndex);
|
||||
const newCode = ',' + newSettings;
|
||||
str = PatcherUtils.insertAt(str, endIndex, newCode);
|
||||
return str;
|
||||
},
|
||||
|
||||
@ -852,7 +850,7 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
|
||||
}
|
||||
|
||||
index = str.indexOf('{', index) + 1;
|
||||
str = str.substring(0, index) + codeSetCurrentlyFocusedInteractable + str.substring(index);
|
||||
str = PatcherUtils.insertAt(str, index, 'e && BxEvent.dispatch(window, BxEvent.NAVIGATION_FOCUS_CHANGED, { element: e });');
|
||||
return str;
|
||||
},
|
||||
|
||||
|
@ -3,11 +3,7 @@ window.BX_EXPOSED.streamSession = this;
|
||||
const orgSetMicrophoneState = this.setMicrophoneState.bind(this);
|
||||
this.setMicrophoneState = state => {
|
||||
orgSetMicrophoneState(state);
|
||||
|
||||
const evt = new Event(BxEvent.MICROPHONE_STATE_CHANGED);
|
||||
evt.microphoneState = state;
|
||||
|
||||
window.dispatchEvent(evt);
|
||||
window.BxEventBus.Stream.emit('microphone.state.changed', { state });
|
||||
};
|
||||
|
||||
window.dispatchEvent(new Event(BxEvent.STREAM_SESSION_READY));
|
||||
|
@ -1 +0,0 @@
|
||||
e && BxEvent.dispatch(window, BxEvent.NAVIGATION_FOCUS_CHANGED, { element: e });
|
@ -1,21 +1,21 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { limitVideoPlayerFps } from "../stream/stream-settings-utils";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
export class RendererShortcut {
|
||||
static toggleVisibility() {
|
||||
const $mediaContainer = document.querySelector('#game-stream div[data-testid="media-container"]');
|
||||
if (!$mediaContainer) {
|
||||
BxEvent.dispatch(window, BxEvent.VIDEO_VISIBILITY_CHANGED, { isShowing: true });
|
||||
BxEventBus.Stream.emit('video.visibility.changed', { isVisible: true });
|
||||
return;
|
||||
}
|
||||
|
||||
$mediaContainer.classList.toggle('bx-gone');
|
||||
const isShowing = !$mediaContainer.classList.contains('bx-gone');
|
||||
const isVisible = !$mediaContainer.classList.contains('bx-gone');
|
||||
|
||||
// Switch FPS
|
||||
limitVideoPlayerFps(isShowing ? getPref(PrefKey.VIDEO_MAX_FPS) : 0);
|
||||
BxEvent.dispatch(window, BxEvent.VIDEO_VISIBILITY_CHANGED, { isShowing });
|
||||
limitVideoPlayerFps(isVisible ? getPref(PrefKey.VIDEO_MAX_FPS) : 0);
|
||||
BxEventBus.Stream.emit('video.visibility.changed', { isVisible });
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,17 @@ export const SHORTCUT_ACTIONS: ShortcutActions = {
|
||||
[ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW]: [t('settings'), t('show')],
|
||||
},
|
||||
|
||||
// MKB
|
||||
...(STATES.browser.capabilities.mkb ? {
|
||||
[t('mouse-and-keyboard')]: {
|
||||
[ShortcutAction.MKB_TOGGLE]: [t('toggle')],
|
||||
},
|
||||
} : {}),
|
||||
|
||||
[t('controller')]: {
|
||||
[ShortcutAction.CONTROLLER_XBOX_BUTTON_PRESS]: [t('button-xbox'), t('press')],
|
||||
},
|
||||
|
||||
// Device
|
||||
...(!!AppInterface ? {
|
||||
[t('device')]: {
|
||||
@ -45,13 +56,6 @@ export const SHORTCUT_ACTIONS: ShortcutActions = {
|
||||
[ShortcutAction.STREAM_MICROPHONE_TOGGLE]: [t('microphone'), t('toggle')],
|
||||
},
|
||||
|
||||
// MKB
|
||||
...(STATES.browser.capabilities.mkb ? {
|
||||
[t('mouse-and-keyboard')]: {
|
||||
[ShortcutAction.MKB_TOGGLE]: [t('toggle')],
|
||||
},
|
||||
} : {}),
|
||||
|
||||
// Other
|
||||
[t('other')]: {
|
||||
[ShortcutAction.TRUE_ACHIEVEMENTS_OPEN]: [t('true-achievements'), t('show')],
|
||||
|
@ -4,7 +4,7 @@ import { Toast } from "@utils/toast";
|
||||
import { ceilToNearest, floorToNearest } from "@/utils/utils";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, setPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
export enum SpeakerState {
|
||||
ENABLED,
|
||||
@ -71,8 +71,8 @@ export class SoundShortcut {
|
||||
SoundShortcut.setGainNodeVolume(targetValue);
|
||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, { instant: true });
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
|
||||
speakerState: targetValue === 0 ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||
BxEventBus.Stream.emit('speaker.state.changed', {
|
||||
state: targetValue === 0 ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@ -84,9 +84,9 @@ export class SoundShortcut {
|
||||
const status = $media.muted ? t('muted') : t('unmuted');
|
||||
Toast.show(`${t('stream')} ❯ ${t('volume')}`, status, { instant: true });
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.SPEAKER_STATE_CHANGED, {
|
||||
speakerState: $media.muted ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||
})
|
||||
BxEventBus.Stream.emit('speaker.state.changed', {
|
||||
state: $media.muted ? SpeakerState.MUTED : SpeakerState.ENABLED,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
21
src/modules/shortcuts/virtual-controller-shortcut.ts
Normal file
21
src/modules/shortcuts/virtual-controller-shortcut.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { generateVirtualControllerMapping } from "@/utils/gamepad";
|
||||
|
||||
export class VirtualControllerShortcut {
|
||||
static pressXboxButton(): void {
|
||||
const streamSession = window.BX_EXPOSED.streamSession;
|
||||
if (!streamSession) {
|
||||
return;
|
||||
}
|
||||
|
||||
const released = generateVirtualControllerMapping();
|
||||
const pressed = generateVirtualControllerMapping({
|
||||
Nexus: 1,
|
||||
VirtualPhysicality: 1024, // Home
|
||||
});
|
||||
|
||||
streamSession.onVirtualGamepadInput('systemMenu', performance.now(), [pressed]);
|
||||
setTimeout(() => {
|
||||
streamSession.onVirtualGamepadInput('systemMenu', performance.now(), [released]);
|
||||
}, 100);
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import { BxEvent } from "@utils/bx-event"
|
||||
import { CE } from "@utils/html"
|
||||
import { t } from "@utils/translation"
|
||||
import { STATES } from "@utils/global"
|
||||
@ -7,6 +6,7 @@ import { getPref } from "@/utils/settings-storages/global-settings-storage"
|
||||
import { StreamStatsCollector, type StreamStatGrade } from "@/utils/stream-stats-collector"
|
||||
import { BxLogger } from "@/utils/bx-logger"
|
||||
import { StreamStat } from "@/enums/pref-values"
|
||||
import { BxEventBus } from "@/utils/bx-event-bus"
|
||||
|
||||
|
||||
export class StreamStats {
|
||||
@ -230,7 +230,7 @@ export class StreamStats {
|
||||
}
|
||||
|
||||
static setupEvents() {
|
||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
||||
BxEventBus.Stream.on('state.playing', () => {
|
||||
const PREF_STATS_QUICK_GLANCE = getPref(PrefKey.STATS_QUICK_GLANCE_ENABLED);
|
||||
const PREF_STATS_SHOW_WHEN_PLAYING = getPref(PrefKey.STATS_SHOW_WHEN_PLAYING);
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { STATES } from "@utils/global.ts";
|
||||
import { createSvgIcon } from "@utils/html.ts";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { BxEvent } from "@utils/bx-event.ts";
|
||||
import { t } from "@utils/translation.ts";
|
||||
import { StreamBadges } from "./stream-badges.ts";
|
||||
import { StreamStats } from "./stream-stats.ts";
|
||||
import { SettingsDialog } from "../ui/dialog/settings-dialog.ts";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus.ts";
|
||||
|
||||
|
||||
export class StreamUiHandler {
|
||||
@ -243,7 +243,7 @@ export class StreamUiHandler {
|
||||
|
||||
// Error Page: .PureErrorPage.ErrorScreen
|
||||
if (className.includes('PureErrorPage')) {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_ERROR_PAGE);
|
||||
BxEventBus.Stream.emit('state.error', {});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { TouchControllerStyleCustom, TouchControllerStyleStandard } from "@/enums/pref-values";
|
||||
import { GhPagesUtils } from "@/utils/gh-pages";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
const LOG_TAG = 'TouchController';
|
||||
|
||||
@ -268,7 +269,7 @@ export class TouchController {
|
||||
|
||||
static setup() {
|
||||
// Function for testing touch control
|
||||
(window as any).testTouchLayout = (layout: any) => {
|
||||
window.testTouchLayout = (layout: any) => {
|
||||
const { touchLayoutManager } = window.BX_EXPOSED;
|
||||
|
||||
touchLayoutManager && touchLayoutManager.changeLayoutForScope({
|
||||
@ -291,9 +292,9 @@ export class TouchController {
|
||||
const PREF_STYLE_STANDARD = getPref<TouchControllerStyleStandard>(PrefKey.TOUCH_CONTROLLER_STYLE_STANDARD);
|
||||
const PREF_STYLE_CUSTOM = getPref<TouchControllerStyleCustom>(PrefKey.TOUCH_CONTROLLER_STYLE_CUSTOM);
|
||||
|
||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
||||
const dataChannel = (e as any).dataChannel;
|
||||
if (!dataChannel || dataChannel.label !== 'message') {
|
||||
BxEventBus.Stream.on('dataChannelCreated', payload => {
|
||||
const { dataChannel } = payload;
|
||||
if (dataChannel?.label !== 'message') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { GamepadKey } from "@/enums/gamepad";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { CE, isElementVisible } from "@/utils/html";
|
||||
import { setNearby } from "@/utils/navigation-utils";
|
||||
@ -439,10 +440,10 @@ export class NavigationDialogManager {
|
||||
show(dialog: NavigationDialog, configs={}, clearStack=false) {
|
||||
this.clearGamepadHoldingInterval();
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
||||
BxEventBus.Script.emit('dialog.shown', {});
|
||||
|
||||
// Stop xCloud's navigation polling
|
||||
(window as any).BX_EXPOSED.disableGamepadPolling = true;
|
||||
window.BX_EXPOSED.disableGamepadPolling = true;
|
||||
|
||||
// Lock scroll bar
|
||||
document.body.classList.add('bx-no-scroll');
|
||||
@ -475,11 +476,14 @@ export class NavigationDialogManager {
|
||||
|
||||
hide() {
|
||||
this.clearGamepadHoldingInterval();
|
||||
if (!this.isShowing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlock scroll bar
|
||||
document.body.classList.remove('bx-no-scroll');
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||
BxEventBus.Script.emit('dialog.dismissed', {});
|
||||
|
||||
// Hide content
|
||||
this.$overlay.classList.add('bx-gone');
|
||||
@ -504,7 +508,7 @@ export class NavigationDialogManager {
|
||||
this.unmountCurrentDialog();
|
||||
|
||||
// Enable xCloud's navigation polling
|
||||
(window as any).BX_EXPOSED.disableGamepadPolling = false;
|
||||
window.BX_EXPOSED.disableGamepadPolling = false;
|
||||
|
||||
// Show the last dialog in dialogs stack
|
||||
if (this.dialogsStack.length) {
|
||||
|
@ -16,6 +16,7 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
||||
|
||||
private $presets!: HTMLSelectElement;
|
||||
private $header!: HTMLElement;
|
||||
private $defaultNote!: HTMLElement;
|
||||
protected $content!: HTMLElement;
|
||||
|
||||
private $btnRename!: HTMLButtonElement;
|
||||
@ -36,15 +37,12 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
||||
const isDefaultPreset = this.currentPresetId <= 0;
|
||||
this.$btnRename.disabled = isDefaultPreset;
|
||||
this.$btnDelete.disabled = isDefaultPreset;
|
||||
this.$defaultNote.classList.toggle('bx-gone', !isDefaultPreset);
|
||||
}
|
||||
|
||||
private async renderPresetsList() {
|
||||
this.allPresets = await this.presetsDb.getPresets();
|
||||
if (!this.currentPresetId) {
|
||||
this.currentPresetId = this.allPresets.default[0];
|
||||
}
|
||||
|
||||
renderPresetsList<T>(this.$presets, this.allPresets, this.currentPresetId);
|
||||
renderPresetsList<T>(this.$presets, this.allPresets, this.currentPresetId, { selectedIndicator: true });
|
||||
}
|
||||
|
||||
private promptNewName(action: string,value='') {
|
||||
@ -121,7 +119,7 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
||||
createButton({
|
||||
icon: BxIcon.NEW,
|
||||
title: t('new'),
|
||||
style: ButtonStyle.FOCUSABLE,
|
||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||
onClick: async (e) => {
|
||||
const newName = this.promptNewName(t('new'));
|
||||
if (!newName) {
|
||||
@ -140,7 +138,7 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
||||
createButton({
|
||||
icon: BxIcon.COPY,
|
||||
title: t('copy'),
|
||||
style: ButtonStyle.FOCUSABLE,
|
||||
style: ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||
onClick: async (e) => {
|
||||
const preset = this.allPresets.data[this.currentPresetId];
|
||||
|
||||
@ -168,8 +166,11 @@ export abstract class BaseProfileManagerDialog<T extends PresetRecord> extends N
|
||||
onClick: e => this.hide(),
|
||||
}),
|
||||
),
|
||||
$header,
|
||||
CE('div', { class: 'bx-dialog-content bx-hide-scroll-bar' }, this.$content),
|
||||
CE('div', {},
|
||||
$header,
|
||||
this.$defaultNote = CE('div', { class: 'bx-default-preset-note bx-gone' }, t('default-preset-note')),
|
||||
),
|
||||
CE('div', { class: 'bx-dialog-content' }, this.$content),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ export class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog<Key
|
||||
// private readonly LOG_TAG = 'KeyboardShortcutsManagerDialog';
|
||||
|
||||
protected $content: HTMLElement;
|
||||
private $unbindNote: HTMLElement;
|
||||
private readonly allKeyElements: BxKeyBindingButton[] = [];
|
||||
|
||||
protected readonly BLANK_PRESET_DATA: KeyboardShortcutPresetData = {
|
||||
@ -65,7 +66,10 @@ export class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog<Key
|
||||
}
|
||||
}
|
||||
|
||||
this.$content = CE('div', {}, $rows);
|
||||
this.$content = CE('div', {},
|
||||
this.$unbindNote = CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
|
||||
$rows,
|
||||
);
|
||||
}
|
||||
|
||||
private onKeyChanged = (e: Event) => {
|
||||
@ -109,6 +113,9 @@ export class KeyboardShortcutsManagerDialog extends BaseProfileManagerDialog<Key
|
||||
const isDefaultPreset = id <= 0;
|
||||
this.updateButtonStates();
|
||||
|
||||
// Toggle unbind note
|
||||
this.$unbindNote.classList.toggle('bx-gone', isDefaultPreset);
|
||||
|
||||
// Update buttons
|
||||
for (const $elm of this.allKeyElements) {
|
||||
const { action } = this.parseDataset($elm);
|
||||
|
@ -48,6 +48,7 @@ export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetR
|
||||
private $mouseSensitivityX!: BxNumberStepper;
|
||||
private $mouseSensitivityY!: BxNumberStepper;
|
||||
private $mouseDeadzone!: BxNumberStepper;
|
||||
private $unbindNote!: HTMLElement;
|
||||
|
||||
constructor(title: string) {
|
||||
super(title, MkbMappingPresetsTable.getInstance());
|
||||
@ -93,7 +94,7 @@ export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetR
|
||||
|
||||
private render() {
|
||||
const $rows = CE('div', {},
|
||||
CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
|
||||
this.$unbindNote = CE('i', { class: 'bx-mkb-note' }, t('right-click-to-unbind')),
|
||||
);
|
||||
|
||||
for (const buttonIndex of this.BUTTONS_ORDER) {
|
||||
@ -185,6 +186,9 @@ export class MkbMappingManagerDialog extends BaseProfileManagerDialog<MkbPresetR
|
||||
const isDefaultPreset = id <= 0;
|
||||
this.updateButtonStates();
|
||||
|
||||
// Toggle unbind note
|
||||
this.$unbindNote.classList.toggle('bx-gone', isDefaultPreset);
|
||||
|
||||
// Update buttons
|
||||
for (const $elm of this.allKeyElements) {
|
||||
const { buttonIndex, keySlot } = this.parseDataset($elm);
|
||||
|
@ -30,6 +30,7 @@ import { SuggestionsSetting } from "./settings/suggestions";
|
||||
import { StreamSettings } from "@/utils/stream-settings";
|
||||
import { MkbExtraSettings } from "./settings/mkb-extra";
|
||||
import { BxExposed } from "@/utils/bx-exposed";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
|
||||
type SettingTabSectionItem = Partial<{
|
||||
@ -317,7 +318,7 @@ export class SettingsDialog extends NavigationDialog {
|
||||
pref: PrefKey.USER_AGENT_PROFILE,
|
||||
multiLines: true,
|
||||
onCreated: (setting, $control) => {
|
||||
const defaultUserAgent = (window.navigator as any).orgUserAgent || window.navigator.userAgent;
|
||||
const defaultUserAgent = window.navigator.orgUserAgent || window.navigator.userAgent;
|
||||
|
||||
const $inpCustomUserAgent = CE<HTMLInputElement>('input', {
|
||||
type: 'text',
|
||||
@ -434,16 +435,13 @@ export class SettingsDialog extends NavigationDialog {
|
||||
},
|
||||
onCreated: (setting: SettingTabSectionItem, $elm: HTMLElement) => {
|
||||
const $range = $elm.querySelector<HTMLInputElement>('input[type=range')!;
|
||||
window.addEventListener(BxEvent.SETTINGS_CHANGED, e => {
|
||||
const { storageKey, settingKey, settingValue } = e as any;
|
||||
if (storageKey !== StorageKey.GLOBAL || settingKey !== PrefKey.AUDIO_VOLUME) {
|
||||
return;
|
||||
}
|
||||
|
||||
$range.value = settingValue;
|
||||
BxEvent.dispatch($range, 'input', {
|
||||
ignoreOnChange: true,
|
||||
});
|
||||
BxEventBus.Script.on('setting.changed', payload => {
|
||||
const { storageKey, settingKey, settingValue } = payload;
|
||||
if (storageKey === StorageKey.GLOBAL && settingKey === PrefKey.AUDIO_VOLUME) {
|
||||
$range.value = settingValue;
|
||||
BxEvent.dispatch($range, 'input', { ignoreOnChange: true });
|
||||
}
|
||||
});
|
||||
},
|
||||
}],
|
||||
|
@ -136,7 +136,7 @@ export class ControllerExtraSettings extends HTMLElement {
|
||||
|
||||
// Render shortcut presets
|
||||
const allShortcutPresets = await ControllerShortcutsTable.getInstance().getPresets();
|
||||
renderPresetsList(this.$selectShortcuts, allShortcutPresets, null, true);
|
||||
renderPresetsList(this.$selectShortcuts, allShortcutPresets, null, { addOffValue: true });
|
||||
|
||||
for (const name of this.controllerIds) {
|
||||
const $option = CE<HTMLOptionElement>('option', { value: name }, name);
|
||||
|
@ -108,11 +108,11 @@ export class MkbExtraSettings extends HTMLElement {
|
||||
private static async updateLayout(this: MkbExtraSettings) {
|
||||
// Render shortcut presets
|
||||
const mappingPresets = await MkbMappingPresetsTable.getInstance().getPresets();
|
||||
renderPresetsList(this.$mappingPresets, mappingPresets, getPref<MkbPresetId>(PrefKey.MKB_P1_MAPPING_PRESET_ID), false);
|
||||
renderPresetsList(this.$mappingPresets, mappingPresets, getPref<MkbPresetId>(PrefKey.MKB_P1_MAPPING_PRESET_ID));
|
||||
|
||||
// Render shortcut presets
|
||||
const shortcutsPresets = await KeyboardShortcutsTable.getInstance().getPresets();
|
||||
renderPresetsList(this.$shortcutsPresets, shortcutsPresets, getPref<MkbPresetId>(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID), true);
|
||||
renderPresetsList(this.$shortcutsPresets, shortcutsPresets, getPref<MkbPresetId>(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID), { addOffValue: true });
|
||||
}
|
||||
|
||||
private static async saveMkbSettings(this: MkbExtraSettings) {
|
||||
|
@ -7,6 +7,7 @@ import { t } from "@/utils/translation";
|
||||
import { SettingsDialog } from "./dialog/settings-dialog";
|
||||
import { TrueAchievements } from "@/utils/true-achievements";
|
||||
import { BxIcon } from "@/utils/bx-icon";
|
||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||
|
||||
export enum GuideMenuTab {
|
||||
HOME = 'home',
|
||||
@ -40,9 +41,9 @@ export class GuideMenu {
|
||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||
onClick: () => {
|
||||
// Wait until the Guide dialog is closed
|
||||
window.addEventListener(BxEvent.XCLOUD_DIALOG_DISMISSED, e => {
|
||||
BxEventBus.Script.once('xcloudDialogDismissed', () => {
|
||||
setTimeout(() => SettingsDialog.getInstance().show(), 50);
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
// Close all xCloud's dialogs
|
||||
this.closeGuideMenu();
|
||||
|
@ -24,4 +24,4 @@ export function localRedirect(path: string) {
|
||||
$anchor.click();
|
||||
}
|
||||
|
||||
(window as any).localRedirect = localRedirect;
|
||||
window.localRedirect = localRedirect;
|
||||
|
25
src/types/global.d.ts
vendored
25
src/types/global.d.ts
vendored
@ -2,6 +2,8 @@ import type { BxExposed } from "@/utils/bx-exposed";
|
||||
import type { AllPresets, ControllerShortcutPresetRecord } from "./presets";
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { StreamSettings, type StreamSettingsData } from "@/utils/stream-settings";
|
||||
import type { BxEvent } from "@/utils/bx-event";
|
||||
import type { BxLogger } from "@/utils/bx-logger";
|
||||
|
||||
export {};
|
||||
|
||||
@ -24,5 +26,28 @@ declare global {
|
||||
|
||||
BX_REMOTE_PLAY_CONFIG: BxStates.remotePlay.config;
|
||||
BX_STREAM_SETTINGS: StreamSettingsData;
|
||||
|
||||
BX_FETCH: typeof window['fetch'];
|
||||
|
||||
BxEvent: typeof BxEvent;
|
||||
BxEventBus: typeof BxEventBus;
|
||||
BxLogger: typeof BxLogger;
|
||||
localRedirect: (path: stringn) => void;
|
||||
testTouchLayout: (layout: any) => void;
|
||||
|
||||
chrome?: any;
|
||||
|
||||
// xCloud properties
|
||||
xbcUser?: {
|
||||
isSignedIn: boolean;
|
||||
};
|
||||
MSA: any;
|
||||
MeControl: any;
|
||||
adobe: any;
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
orgUserAgent?: string;
|
||||
orgUserAgentData?: any;
|
||||
}
|
||||
}
|
||||
|
139
src/utils/bx-event-bus.ts
Normal file
139
src/utils/bx-event-bus.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import type { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { AppInterface } from "./global";
|
||||
import type { MicrophoneState } from "@/modules/shortcuts/microphone-shortcut";
|
||||
import type { SpeakerState } from "@/modules/shortcuts/sound-shortcut";
|
||||
|
||||
type EventCallback<T = any> = (payload: T) => void;
|
||||
|
||||
type ScriptEvents = {
|
||||
'xcloud.server.ready': {};
|
||||
'xcloud.server.unavailable': {};
|
||||
|
||||
'dialog.shown': {},
|
||||
'dialog.dismissed': {},
|
||||
|
||||
'titleInfo.ready': {};
|
||||
'setting.changed': {
|
||||
storageKey: StorageKey;
|
||||
settingKey: PrefKey;
|
||||
settingValue: any;
|
||||
};
|
||||
|
||||
'mkb.setting.updated': {};
|
||||
'keyboardShortcuts.updated': {};
|
||||
'deviceVibration.updated': {};
|
||||
|
||||
// GH pages
|
||||
'list.forcedNativeMkb.updated': {
|
||||
data: {
|
||||
data: any;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type StreamEvents = {
|
||||
'state.loading': {};
|
||||
'state.starting': {};
|
||||
'state.playing': { $video?: HTMLVideoElement };
|
||||
'state.stopped': {};
|
||||
'state.error': {};
|
||||
|
||||
'gameBar.activated': {},
|
||||
'speaker.state.changed': { state: SpeakerState },
|
||||
'video.visibility.changed': { isVisible: boolean },
|
||||
// Inside patch
|
||||
'microphone.state.changed': { state: MicrophoneState },
|
||||
|
||||
dataChannelCreated: { dataChannel: RTCDataChannel };
|
||||
};
|
||||
|
||||
export class BxEventBus<TEvents extends Record<string, any>> {
|
||||
private listeners: Map<keyof TEvents, Set<EventCallback<any>>> = new Map();
|
||||
private group: string;
|
||||
private appJsInterfaces: { [key in keyof TEvents]?: string };
|
||||
|
||||
static readonly Script = new BxEventBus<ScriptEvents>('script', {
|
||||
'dialog.shown': 'onDialogShown',
|
||||
'dialog.dismissed': 'onDialogDismissed',
|
||||
});
|
||||
static readonly Stream = new BxEventBus<StreamEvents>('stream', {
|
||||
'state.loading': 'onStreamPlaying',
|
||||
'state.playing': 'onStreamPlaying',
|
||||
'state.stopped': 'onStreamStopped',
|
||||
});
|
||||
|
||||
constructor(group: string, appJsInterfaces: { [key in keyof TEvents]?: string }) {
|
||||
this.group = group;
|
||||
this.appJsInterfaces = appJsInterfaces;
|
||||
}
|
||||
|
||||
on<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]>): void {
|
||||
if (!this.listeners.has(event)) {
|
||||
this.listeners.set(event, new Set());
|
||||
}
|
||||
this.listeners.get(event)!.add(callback);
|
||||
|
||||
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'on', event, callback);
|
||||
}
|
||||
|
||||
once<K extends keyof TEvents>(event: string, callback: EventCallback<TEvents[K]>): void {
|
||||
const wrapper = (...args: any[]) => {
|
||||
// @ts-ignore
|
||||
callback(...args);
|
||||
this.off(event, wrapper);
|
||||
};
|
||||
|
||||
this.on(event, wrapper);
|
||||
}
|
||||
|
||||
off<K extends keyof TEvents>(event: K, callback: EventCallback<TEvents[K]> | null): void {
|
||||
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'off', event, callback);
|
||||
|
||||
if (!callback) {
|
||||
// Remove all listener callbacks
|
||||
this.listeners.delete(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const callbacks = this.listeners.get(event);
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks.delete(callback);
|
||||
if (callbacks.size === 0) {
|
||||
this.listeners.delete(event);
|
||||
}
|
||||
}
|
||||
|
||||
offAll(): void {
|
||||
this.listeners.clear();
|
||||
}
|
||||
|
||||
emit<K extends keyof TEvents>(event: K, payload: TEvents[K]): void {
|
||||
const callbacks = this.listeners.get(event) || [];
|
||||
for (const callback of callbacks) {
|
||||
callback(payload);
|
||||
}
|
||||
|
||||
// Call method inside Android app
|
||||
if (AppInterface) {
|
||||
try {
|
||||
if (event in this.appJsInterfaces) {
|
||||
const method = this.appJsInterfaces[event];
|
||||
AppInterface[method] && AppInterface[method]();
|
||||
} else {
|
||||
AppInterface.onEventBus(this.group + '.' + (event as string));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
BX_FLAGS.Debug && BxLogger.warning('EventBus', 'emit', event, payload);
|
||||
}
|
||||
}
|
||||
|
||||
window.BxEventBus = BxEventBus;
|
@ -4,59 +4,28 @@ import { BX_FLAGS } from "./bx-flags";
|
||||
|
||||
|
||||
export namespace BxEvent {
|
||||
export const JUMP_BACK_IN_READY = 'bx-jump-back-in-ready';
|
||||
export const POPSTATE = 'bx-popstate';
|
||||
|
||||
export const TITLE_INFO_READY = 'bx-title-info-ready';
|
||||
|
||||
export const SETTINGS_CHANGED = 'bx-settings-changed';
|
||||
|
||||
export const STREAM_LOADING = 'bx-stream-loading';
|
||||
export const STREAM_STARTING = 'bx-stream-starting';
|
||||
export const STREAM_STARTED = 'bx-stream-started';
|
||||
export const STREAM_PLAYING = 'bx-stream-playing';
|
||||
export const STREAM_STOPPED = 'bx-stream-stopped';
|
||||
export const STREAM_ERROR_PAGE = 'bx-stream-error-page';
|
||||
|
||||
export const STREAM_WEBRTC_CONNECTED = 'bx-stream-webrtc-connected';
|
||||
export const STREAM_WEBRTC_DISCONNECTED = 'bx-stream-webrtc-disconnected';
|
||||
|
||||
export const MKB_UPDATED = 'bx-mkb-updated';
|
||||
export const KEYBOARD_SHORTCUTS_UPDATED = 'bx-keyboard-shortcuts-updated';
|
||||
|
||||
// export const STREAM_EVENT_TARGET_READY = 'bx-stream-event-target-ready';
|
||||
// Inside patch
|
||||
export const STREAM_SESSION_READY = 'bx-stream-session-ready';
|
||||
|
||||
export const CUSTOM_TOUCH_LAYOUTS_LOADED = 'bx-custom-touch-layouts-loaded';
|
||||
export const TOUCH_LAYOUT_MANAGER_READY = 'bx-touch-layout-manager-ready';
|
||||
|
||||
// Inside app
|
||||
export const REMOTE_PLAY_READY = 'bx-remote-play-ready';
|
||||
export const REMOTE_PLAY_FAILED = 'bx-remote-play-failed';
|
||||
|
||||
export const XCLOUD_SERVERS_READY = 'bx-servers-ready';
|
||||
export const XCLOUD_SERVERS_UNAVAILABLE = 'bx-servers-unavailable';
|
||||
|
||||
export const DATA_CHANNEL_CREATED = 'bx-data-channel-created';
|
||||
export const DEVICE_VIBRATION_CHANGED = 'bx-device-vibration-changed';
|
||||
|
||||
export const GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated';
|
||||
export const MICROPHONE_STATE_CHANGED = 'bx-microphone-state-changed';
|
||||
export const SPEAKER_STATE_CHANGED = 'bx-speaker-state-changed';
|
||||
export const VIDEO_VISIBILITY_CHANGED = 'bx-video-visibility-changed';
|
||||
|
||||
// Inside patch
|
||||
export const CAPTURE_SCREENSHOT = 'bx-capture-screenshot';
|
||||
|
||||
export const POINTER_LOCK_REQUESTED = 'bx-pointer-lock-requested';
|
||||
export const POINTER_LOCK_EXITED = 'bx-pointer-lock-exited';
|
||||
|
||||
// Inside patch
|
||||
export const NAVIGATION_FOCUS_CHANGED = 'bx-nav-focus-changed';
|
||||
|
||||
export const GH_PAGES_FORCE_NATIVE_MKB_UPDATED = 'bx-gh-pages-force-native-mkb-updated';
|
||||
|
||||
// xCloud Dialog events
|
||||
export const XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown';
|
||||
export const XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed';
|
||||
|
||||
export const XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown';
|
||||
|
||||
export const XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed';
|
||||
@ -76,7 +45,6 @@ export namespace BxEvent {
|
||||
}
|
||||
|
||||
const event = new Event(eventName);
|
||||
|
||||
if (data) {
|
||||
for (const key in data) {
|
||||
(event as any)[key] = data[key];
|
||||
@ -86,8 +54,8 @@ export namespace BxEvent {
|
||||
target.dispatchEvent(event);
|
||||
AppInterface && AppInterface.onEvent(eventName);
|
||||
|
||||
BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data)
|
||||
BX_FLAGS.Debug && BxLogger.warning('BxEvent', 'dispatch', eventName, data);
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BxEvent = BxEvent;
|
||||
window.BxEvent = BxEvent;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||
|
||||
import { ControllerShortcut } from "@/modules/controller-shortcut";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { deepClone, STATES } from "@utils/global";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
@ -12,6 +11,7 @@ import { GamePassCloudGallery } from "@/enums/game-pass-gallery";
|
||||
import { TouchController } from "@/modules/touch-controller";
|
||||
import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { Patcher, type PatchPage } from "@/modules/patcher/patcher";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export enum SupportedInputType {
|
||||
CONTROLLER = 'Controller',
|
||||
@ -139,7 +139,7 @@ export const BxExposed = {
|
||||
|
||||
// Save this info in STATES
|
||||
STATES.currentStream.titleInfo = titleInfo;
|
||||
BxEvent.dispatch(window, BxEvent.TITLE_INFO_READY);
|
||||
BxEventBus.Script.emit('titleInfo.ready', {});
|
||||
|
||||
return titleInfo;
|
||||
},
|
||||
|
@ -16,4 +16,4 @@ export class BxLogger {
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).BxLogger = BxLogger;
|
||||
window.BxLogger = BxLogger;
|
||||
|
@ -57,3 +57,36 @@ export function hasGamepad() {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function generateVirtualControllerMapping(override: {}={}) {
|
||||
const mapping = {
|
||||
GamepadIndex: 0,
|
||||
A: 0,
|
||||
B: 0,
|
||||
X: 0,
|
||||
Y: 0,
|
||||
LeftShoulder: 0,
|
||||
RightShoulder: 0,
|
||||
LeftTrigger: 0,
|
||||
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: false,
|
||||
Virtual: false,
|
||||
};
|
||||
|
||||
return Object.assign({}, mapping, override);
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { StorageKey } from "@/enums/pref-keys";
|
||||
import { NATIVE_FETCH } from "./bx-flags";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
|
||||
export type ForceNativeMkbResponse = {
|
||||
@ -53,7 +53,9 @@ export class GhPagesUtils {
|
||||
if (json.$schemaVersion === supportedSchema) {
|
||||
// Save to storage
|
||||
window.localStorage.setItem(key, JSON.stringify(json));
|
||||
BxEvent.dispatch(window, BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED);
|
||||
BxEventBus.Script.emit('list.forcedNativeMkb.updated', {
|
||||
data: json,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { BxEvent } from "@utils/bx-event";
|
||||
import { LoadingScreen } from "@modules/loading-screen";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { HeaderSection } from "@/modules/ui/header";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
import { NavigationDialogManager } from "@/modules/ui/dialog/navigation-dialog";
|
||||
|
||||
export function patchHistoryMethod(type: 'pushState' | 'replaceState') {
|
||||
@ -9,8 +10,8 @@ export function patchHistoryMethod(type: 'pushState' | 'replaceState') {
|
||||
|
||||
return function(...args: any[]) {
|
||||
BxEvent.dispatch(window, BxEvent.POPSTATE, {
|
||||
arguments: args,
|
||||
});
|
||||
arguments: args,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
return orig.apply(this, arguments);
|
||||
@ -26,17 +27,11 @@ export function onHistoryChanged(e: PopStateEvent) {
|
||||
|
||||
window.setTimeout(RemotePlayManager.detect, 10);
|
||||
|
||||
// Hide Global settings
|
||||
const $settings = document.querySelector('.bx-settings-container');
|
||||
if ($settings) {
|
||||
$settings.classList.add('bx-gone');
|
||||
}
|
||||
|
||||
// Hide Navigation dialog
|
||||
NavigationDialogManager.getInstance().hide();
|
||||
|
||||
LoadingScreen.reset();
|
||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
||||
BxEventBus.Stream.emit('state.stopped', {});
|
||||
}
|
||||
|
@ -263,10 +263,10 @@ export function clearDataSet($elm: HTMLElement) {
|
||||
});
|
||||
}
|
||||
|
||||
export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectElement, allPresets: AllPresets<T>, selectedValue: number | null, addOffValue=false) {
|
||||
export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectElement, allPresets: AllPresets<T>, selectedValue: number | null, options: { addOffValue?: boolean, selectedIndicator?: boolean }={}) {
|
||||
removeChildElements($select);
|
||||
|
||||
if (addOffValue) {
|
||||
if (options.addOffValue) {
|
||||
const $option = CE<HTMLOptionElement>('option', { value: 0 }, t('off'));
|
||||
$option.selected = selectedValue === 0;
|
||||
|
||||
@ -275,7 +275,7 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
||||
|
||||
// Render options
|
||||
const groups = {
|
||||
default: t('default'),
|
||||
default: t('default') + ' 🔒',
|
||||
custom: t('custom'),
|
||||
};
|
||||
|
||||
@ -284,8 +284,13 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
||||
const $optGroup = CE('optgroup', { label: groups[key] });
|
||||
for (const id of allPresets[key]) {
|
||||
const record = allPresets.data[id];
|
||||
const $option = CE<HTMLOptionElement>('option', { value: record.id }, record.name);
|
||||
$option.selected = selectedValue === record.id;
|
||||
const selected = selectedValue === record.id;
|
||||
const name = options.selectedIndicator && selected ? '✅ ' + record.name : record.name;
|
||||
|
||||
const $option = CE<HTMLOptionElement>('option', { value: record.id }, name);
|
||||
if (selected) {
|
||||
$option.selected = true;
|
||||
}
|
||||
|
||||
$optGroup.appendChild($option);
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, getPrefDefinition } from "./settings-storages/global-settings-storage";
|
||||
import { CodecProfile } from "@/enums/pref-values";
|
||||
import type { SettingDefinition } from "@/types/setting-definition";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export function patchVideoApi() {
|
||||
const PREF_SKIP_SPLASH_VIDEO = getPref(PrefKey.UI_SKIP_SPLASH_VIDEO);
|
||||
@ -27,9 +28,9 @@ export function patchVideoApi() {
|
||||
} satisfies StreamPlayerOptions;
|
||||
STATES.currentStream.streamPlayer = new StreamPlayer(this, getPref(PrefKey.VIDEO_PLAYER_TYPE), playerOptions);
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_PLAYING, {
|
||||
$video: this,
|
||||
});
|
||||
BxEventBus.Stream.emit('state.playing', {
|
||||
$video: this,
|
||||
})
|
||||
}
|
||||
|
||||
const nativePlay = HTMLMediaElement.prototype.play;
|
||||
@ -75,10 +76,7 @@ export function patchRtcPeerConnection() {
|
||||
// @ts-ignore
|
||||
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.DATA_CHANNEL_CREATED, {
|
||||
dataChannel: dataChannel,
|
||||
});
|
||||
|
||||
BxEventBus.Stream.emit('dataChannelCreated', { dataChannel });
|
||||
return dataChannel;
|
||||
}
|
||||
|
||||
@ -200,8 +198,8 @@ export function patchMeControl() {
|
||||
},
|
||||
};
|
||||
|
||||
(window as any).MSA = new Proxy(MSA, MsaHandler);
|
||||
(window as any).MeControl = new Proxy(MeControl, MeControlHandler);
|
||||
window.MSA = new Proxy(MSA, MsaHandler);
|
||||
window.MeControl = new Proxy(MeControl, MeControlHandler);
|
||||
}
|
||||
|
||||
|
||||
@ -209,7 +207,9 @@ export function patchMeControl() {
|
||||
* Disable Adobe Audience Manager (AAM)
|
||||
*/
|
||||
export function disableAdobeAudienceManager() {
|
||||
(window as any).adobe = Object.freeze({});
|
||||
Object.defineProperty(window, 'adobe', {
|
||||
get() { return Object.freeze({}); }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,6 +177,7 @@ export function interceptHttpRequests() {
|
||||
'chat.xboxlive.com',
|
||||
'notificationinbox.xboxlive.com',
|
||||
'peoplehub.xboxlive.com',
|
||||
'peoplehub-public.xboxlive.com',
|
||||
'rta.xboxlive.com',
|
||||
'userpresence.xboxlive.com',
|
||||
'xblmessaging.xboxlive.com',
|
||||
@ -188,11 +189,11 @@ export function interceptHttpRequests() {
|
||||
'2c06dea3f26c40c69b8456d319791fd0@o427368.ingest.sentry.io',
|
||||
];
|
||||
|
||||
(window as any).BX_FETCH = window.fetch = async (request: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
window.BX_FETCH = window.fetch = async (request: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
||||
let url = (typeof request === 'string') ? request : (request as Request).url;
|
||||
|
||||
// Check blocked URLs
|
||||
for (let blocked of BLOCKED_URLS) {
|
||||
for (const blocked of BLOCKED_URLS) {
|
||||
if (url.startsWith(blocked)) {
|
||||
return new Response('{"acc":1,"webResult":{}}', {
|
||||
status: 200,
|
||||
@ -201,7 +202,7 @@ export function interceptHttpRequests() {
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore URLs
|
||||
// Ignore domains
|
||||
const domain = (new URL(url)).hostname;
|
||||
if (IGNORED_DOMAINS.includes(domain)) {
|
||||
return NATIVE_FETCH(request, init);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { GuideMenu } from "@/modules/ui/guide-menu";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { BX_FLAGS } from "./bx-flags";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { BxIcon } from "./bx-icon";
|
||||
@ -7,6 +6,7 @@ import { AppInterface } from "./global";
|
||||
import { createButton, ButtonStyle } from "./html";
|
||||
import { t } from "./translation";
|
||||
import { parseDetailsPath } from "./utils";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
|
||||
export class RootDialogObserver {
|
||||
@ -85,7 +85,7 @@ export class RootDialogObserver {
|
||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
||||
if (shown !== beingShown) {
|
||||
beingShown = shown;
|
||||
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||
BxEventBus.Script.emit(shown ? 'dialog.shown' : 'dialog.dismissed', {});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1,16 +1,16 @@
|
||||
import type { PrefKey } from "@/enums/pref-keys";
|
||||
import type { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||
import type { NumberStepperParams, SettingAction, SettingDefinitions } from "@/types/setting-definition";
|
||||
import { BxEvent } from "../bx-event";
|
||||
import { t } from "../translation";
|
||||
import { SCRIPT_VARIANT } from "../global";
|
||||
import { BxEventBus } from "../bx-event-bus";
|
||||
|
||||
export class BaseSettingsStore {
|
||||
private storage: Storage;
|
||||
private storageKey: string;
|
||||
private storageKey: StorageKey;
|
||||
private _settings: object | null;
|
||||
private definitions: SettingDefinitions;
|
||||
|
||||
constructor(storageKey: string, definitions: SettingDefinitions) {
|
||||
constructor(storageKey: StorageKey, definitions: SettingDefinitions) {
|
||||
this.storage = window.localStorage;
|
||||
this.storageKey = storageKey;
|
||||
|
||||
@ -93,7 +93,7 @@ export class BaseSettingsStore {
|
||||
this.settings[key] = this.validateValue('get', key, value);
|
||||
this.saveSettings();
|
||||
|
||||
emitEvent && BxEvent.dispatch(window, BxEvent.SETTINGS_CHANGED, {
|
||||
emitEvent && BxEventBus.Script.emit('setting.changed', {
|
||||
storageKey: this.storageKey,
|
||||
settingKey: key,
|
||||
settingValue: value,
|
||||
|
@ -12,7 +12,7 @@ import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerSty
|
||||
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
|
||||
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
||||
import { GhPagesUtils } from "../gh-pages";
|
||||
import { BxEvent } from "../bx-event";
|
||||
import { BxEventBus } from "../bx-event-bus";
|
||||
|
||||
|
||||
function getSupportedCodecProfiles() {
|
||||
@ -432,8 +432,8 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
if (!setting.unsupported) {
|
||||
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(true);
|
||||
|
||||
window.addEventListener(BxEvent.GH_PAGES_FORCE_NATIVE_MKB_UPDATED, e => {
|
||||
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList();
|
||||
BxEventBus.Script.on('list.forcedNativeMkb.updated', payload => {
|
||||
(setting as any).multipleOptions = payload.data.data;
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -652,7 +652,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
},
|
||||
},
|
||||
[PrefKey.VIDEO_MAX_FPS]: {
|
||||
label: t('max-fps'),
|
||||
label: t('limit-fps'),
|
||||
default: 60,
|
||||
min: 10,
|
||||
max: 60,
|
||||
|
@ -10,6 +10,7 @@ import { EmulatedMkbHandler } from "@/modules/mkb/mkb-handler";
|
||||
import { RendererShortcut } from "@/modules/shortcuts/renderer-shortcut";
|
||||
import { TrueAchievements } from "./true-achievements";
|
||||
import { NativeMkbHandler } from "@/modules/mkb/native-mkb-handler";
|
||||
import { VirtualControllerShortcut } from "@/modules/shortcuts/virtual-controller-shortcut";
|
||||
|
||||
export class ShortcutHandler {
|
||||
static runAction(action: ShortcutAction) {
|
||||
@ -69,6 +70,10 @@ export class ShortcutHandler {
|
||||
case ShortcutAction.TRUE_ACHIEVEMENTS_OPEN:
|
||||
TrueAchievements.getInstance().open(false);
|
||||
break;
|
||||
|
||||
case ShortcutAction.CONTROLLER_XBOX_BUTTON_PRESS:
|
||||
VirtualControllerShortcut.pressXboxButton();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,10 @@ import { hasGamepad } from "./gamepad";
|
||||
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
|
||||
import type { GamepadKey } from "@/enums/gamepad";
|
||||
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { KeyboardShortcutDefaultId, KeyboardShortcutsTable } from "./local-db/keyboard-shortcuts-table";
|
||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||
import { KeyHelper } from "@/modules/mkb/key-helper";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
|
||||
export type StreamSettingsData = {
|
||||
@ -110,7 +110,7 @@ export class StreamSettings {
|
||||
}
|
||||
|
||||
StreamSettings.settings.deviceVibrationIntensity = intensity;
|
||||
BxEvent.dispatch(window, BxEvent.DEVICE_VIBRATION_CHANGED);
|
||||
BxEventBus.Script.emit('deviceVibration.updated', {});
|
||||
}
|
||||
|
||||
static async refreshMkbSettings() {
|
||||
@ -148,7 +148,7 @@ export class StreamSettings {
|
||||
settings.mkbPreset = converted;
|
||||
|
||||
setPref(PrefKey.MKB_P1_MAPPING_PRESET_ID, orgPreset.id);
|
||||
BxEvent.dispatch(window, BxEvent.MKB_UPDATED);
|
||||
BxEventBus.Script.emit('mkb.setting.updated', {});
|
||||
}
|
||||
|
||||
static async refreshKeyboardShortcuts() {
|
||||
@ -159,7 +159,7 @@ export class StreamSettings {
|
||||
settings.keyboardShortcuts = null;
|
||||
|
||||
setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, presetId);
|
||||
BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED);
|
||||
BxEventBus.Script.emit('keyboardShortcuts.updated', {});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@ export class StreamSettings {
|
||||
settings.keyboardShortcuts = converted;
|
||||
|
||||
setPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID, orgPreset.id);
|
||||
BxEvent.dispatch(window, BxEvent.KEYBOARD_SHORTCUTS_UPDATED);
|
||||
BxEventBus.Script.emit('keyboardShortcuts.updated', {});
|
||||
}
|
||||
|
||||
static async refreshAllSettings() {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { STATES } from "./global";
|
||||
import { humanFileSize, secondsToHm } from "./html";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { BxLogger } from "./bx-logger";
|
||||
import { StreamStat } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export type StreamStatGrade = '' | 'bad' | 'ok' | 'good';
|
||||
|
||||
@ -310,9 +310,8 @@ export class StreamStatsCollector {
|
||||
}
|
||||
|
||||
static setupEvents() {
|
||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
||||
const statsCollector = StreamStatsCollector.getInstance();
|
||||
statsCollector.reset();
|
||||
BxEventBus.Stream.on('state.playing', () => {
|
||||
StreamStatsCollector.getInstance().reset();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ const Texts = {
|
||||
"brazil": "Brazil",
|
||||
"brightness": "Brightness",
|
||||
"browser-unsupported-feature": "Your browser doesn't support this feature",
|
||||
"button-xbox": "Xbox button",
|
||||
"bypass-region-restriction": "Bypass region restriction",
|
||||
"can-stream-xbox-360-games": "Can stream Xbox 360 games",
|
||||
"cancel": "Cancel",
|
||||
@ -94,6 +95,7 @@ const Texts = {
|
||||
"deadzone-counterweight": "Deadzone counterweight",
|
||||
"decrease": "Decrease",
|
||||
"default": "Default",
|
||||
"default-preset-note": "You can't modify default presets. Create a new one to customize it.",
|
||||
"delete": "Delete",
|
||||
"device": "Device",
|
||||
"device-unsupported-touch": "Your device doesn't have touch support",
|
||||
@ -156,13 +158,13 @@ const Texts = {
|
||||
"large": "Large",
|
||||
"layout": "Layout",
|
||||
"left-stick": "Left stick",
|
||||
"limit-fps": "Limit FPS",
|
||||
"load-failed-message": "Failed to run Better xCloud",
|
||||
"loading-screen": "Loading screen",
|
||||
"local-co-op": "Local co-op",
|
||||
"lowest-quality": "Lowest quality",
|
||||
"manage": "Manage",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"max-fps": "Max FPS",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
"microphone": "Microphone",
|
||||
@ -217,6 +219,7 @@ const Texts = {
|
||||
"prefer-ipv6-server": "Prefer IPv6 server",
|
||||
"preferred-game-language": "Preferred game's language",
|
||||
"preset": "Preset",
|
||||
"press": "Press",
|
||||
"press-esc-to-cancel": "Press Esc to cancel",
|
||||
"press-key-to-toggle-mkb": [
|
||||
(e: any) => `Press ${e.key} to toggle this feature`,
|
||||
|
@ -10,7 +10,7 @@ type UserAgentConfig = {
|
||||
const SMART_TV_UNIQUE_ID = 'FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA';
|
||||
|
||||
let CHROMIUM_VERSION = '125.0.0.0';
|
||||
if (!!(window as any).chrome || window.navigator.userAgent.includes('Chrome')) {
|
||||
if (!!window.chrome || window.navigator.userAgent.includes('Chrome')) {
|
||||
// Get Chromium version in the original User-Agent value
|
||||
const match = window.navigator.userAgent.match(/\s(?:Chrome|Edg)\/([\d\.]+)/);
|
||||
if (match) {
|
||||
@ -59,7 +59,7 @@ export class UserAgent {
|
||||
}
|
||||
|
||||
static getDefault(): string {
|
||||
return (window.navigator as any).orgUserAgent || window.navigator.userAgent;
|
||||
return window.navigator.orgUserAgent || window.navigator.userAgent;
|
||||
}
|
||||
|
||||
static get(profile: UserAgentProfile): string {
|
||||
@ -123,12 +123,12 @@ export class UserAgent {
|
||||
|
||||
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
|
||||
if ('userAgentData' in window.navigator) {
|
||||
(window.navigator as any).orgUserAgentData = (window.navigator as any).userAgentData;
|
||||
window.navigator.orgUserAgentData = window.navigator.userAgentData;
|
||||
Object.defineProperty(window.navigator, 'userAgentData', {});
|
||||
}
|
||||
|
||||
// Override navigator.userAgent
|
||||
(window.navigator as any).orgUserAgent = window.navigator.userAgent;
|
||||
window.navigator.orgUserAgent = window.navigator.userAgent;
|
||||
Object.defineProperty(window.navigator, 'userAgent', {
|
||||
value: newUserAgent,
|
||||
});
|
||||
|
@ -44,7 +44,7 @@ export function checkForUpdate() {
|
||||
* Disable PWA requirement on Safari
|
||||
*/
|
||||
export function disablePwa() {
|
||||
const userAgent = ((window.navigator as any).orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||
const userAgent = (window.navigator.orgUserAgent || window.navigator.userAgent || '').toLowerCase();
|
||||
if (!userAgent) {
|
||||
return;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import { LoadingScreen } from "@modules/loading-screen";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { StreamBadges } from "@modules/stream/stream-badges";
|
||||
import { TouchController } from "@modules/touch-controller";
|
||||
import { BxEvent } from "./bx-event";
|
||||
import { NATIVE_FETCH, BX_FLAGS } from "./bx-flags";
|
||||
import { STATES } from "./global";
|
||||
import { generateMsDeviceInfo, getOsNameFromResolution, patchIceCandidates } from "./network";
|
||||
@ -13,6 +12,7 @@ import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { NativeMkbMode, StreamResolution, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export class XcloudInterceptor {
|
||||
private static readonly SERVER_EXTRA_INFO: Record<string, [string, ServerContinent]> = {
|
||||
@ -52,7 +52,7 @@ export class XcloudInterceptor {
|
||||
const response = await NATIVE_FETCH(request, init);
|
||||
if (response.status !== 200) {
|
||||
// Unsupported region
|
||||
BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_UNAVAILABLE);
|
||||
BxEventBus.Script.emit('xcloud.server.unavailable', {});
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -89,7 +89,7 @@ export class XcloudInterceptor {
|
||||
STATES.serverRegions[region.name] = Object.assign({}, region);
|
||||
}
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.XCLOUD_SERVERS_READY);
|
||||
BxEventBus.Script.emit('xcloud.server.ready', {});
|
||||
|
||||
const preferredRegion = getPreferredServerRegion();
|
||||
if (preferredRegion && preferredRegion in STATES.serverRegions) {
|
||||
@ -107,7 +107,7 @@ export class XcloudInterceptor {
|
||||
}
|
||||
|
||||
private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_LOADING);
|
||||
BxEventBus.Stream.emit('state.loading', {});
|
||||
|
||||
const PREF_STREAM_TARGET_RESOLUTION = getPref<StreamResolution>(PrefKey.STREAM_RESOLUTION);
|
||||
const PREF_STREAM_PREFERRED_LOCALE = getPref<StreamPreferredLocale>(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||
@ -189,7 +189,7 @@ export class XcloudInterceptor {
|
||||
return response;
|
||||
}
|
||||
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STARTING);
|
||||
BxEventBus.Stream.emit('state.starting', {});
|
||||
|
||||
const obj = JSON.parse(text);
|
||||
let overrides = JSON.parse(obj.clientStreamingConfigOverrides || '{}') || {};
|
||||
|
@ -9,6 +9,7 @@ import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { StreamResolution, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
private static consoleAddrs: RemotePlayConsoleAddresses = {};
|
||||
@ -35,7 +36,7 @@ export class XhomeInterceptor {
|
||||
}
|
||||
|
||||
private static async handleConfiguration(request: Request | URL) {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STARTING);
|
||||
BxEventBus.Stream.emit('state.starting', {});
|
||||
|
||||
const response = await NATIVE_FETCH(request);
|
||||
const obj = await response.clone().json();
|
||||
@ -124,7 +125,7 @@ export class XhomeInterceptor {
|
||||
}
|
||||
|
||||
private static async handlePlay(request: RequestInfo | URL) {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_LOADING);
|
||||
BxEventBus.Stream.emit('state.loading', {});
|
||||
|
||||
const clone = (request as Request).clone();
|
||||
const body = await clone.json();
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type { NumberStepperParams } from "@/types/setting-definition";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { CE, escapeCssSelector } from "@/utils/html";
|
||||
import { setNearby } from "@/utils/navigation-utils";
|
||||
import type { BxHtmlSettingElement } from "@/utils/setting-element";
|
||||
@ -26,7 +25,6 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl
|
||||
private $btnDec!: HTMLButtonElement;
|
||||
private $range!: HTMLInputElement | null;
|
||||
|
||||
onInput!: typeof BxNumberStepper['onInput'];
|
||||
onRangeInput!: typeof BxNumberStepper['onRangeInput'];
|
||||
onClick!: typeof BxNumberStepper['onClick'];
|
||||
onPointerUp!: typeof BxNumberStepper['onPointerUp'];
|
||||
@ -77,7 +75,6 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl
|
||||
self.$btnDec = $btnDec;
|
||||
self.onChange = onChange;
|
||||
|
||||
self.onInput = BxNumberStepper.onInput.bind(self);
|
||||
self.onRangeInput = BxNumberStepper.onRangeInput.bind(self);
|
||||
self.onClick = BxNumberStepper.onClick.bind(self);
|
||||
self.onPointerUp = BxNumberStepper.onPointerUp.bind(self);
|
||||
@ -117,8 +114,7 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl
|
||||
self.$range = $range;
|
||||
options.hideSlider && $range.classList.add('bx-gone');
|
||||
|
||||
$range.addEventListener('input', self.onRangeInput);
|
||||
self.addEventListener('input', self.onInput);
|
||||
self.addEventListener('input', self.onRangeInput);
|
||||
self.appendChild($range);
|
||||
|
||||
if (options.ticks || options.exactTicks) {
|
||||
@ -183,10 +179,6 @@ export class BxNumberStepper extends HTMLInputElement implements BxHtmlSettingEl
|
||||
return value;
|
||||
}
|
||||
|
||||
private static onInput(this: BxNumberStepper, e: Event) {
|
||||
BxEvent.dispatch(this.$range, 'input');
|
||||
}
|
||||
|
||||
private static onRangeInput(this: BxNumberStepper, e: Event) {
|
||||
let value = parseInt((e.target as HTMLInputElement).value);
|
||||
if (this.options.reverse) {
|
||||
|
Reference in New Issue
Block a user