mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-01 11:51:43 +02:00
Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
0f360d4be1 | |||
900ab38153 | |||
c03c63f3c3 | |||
d4f4084991 | |||
975549b4e7 | |||
345d0f78dc | |||
938dfa6aaa | |||
d7ed9e1603 | |||
224e98829d | |||
56a3f1d8c8 | |||
d82a38c0f1 | |||
77729789e3 | |||
5763701355 | |||
cafeed1a3c | |||
691f116ea0 | |||
481b365e6e | |||
2b63edb7eb | |||
b6746598a3 | |||
45bda4bb24 | |||
c93db035f3 | |||
e75fa397ee | |||
98a9f4fc37 | |||
dee8c9dbd0 | |||
d31a06be89 | |||
277c777121 | |||
385fd71e86 | |||
986d9fe088 | |||
6de235ce2f |
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 5.1.0
|
// @version 5.1.2
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
342
dist/better-xcloud.user.js
vendored
342
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -105,6 +105,10 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
|||||||
font-family: var(--bx-promptfont-font);
|
font-family: var(--bx-promptfont-font);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select[multiple] {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide UI elements */
|
/* Hide UI elements */
|
||||||
#headerArea, #uhfSkipToMain, .uhf-footer {
|
#headerArea, #uhfSkipToMain, .uhf-footer {
|
||||||
display: none;
|
display: none;
|
||||||
|
6
src/enums/ui-sections.ts
Normal file
6
src/enums/ui-sections.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum UiSection {
|
||||||
|
NEWS = 'news',
|
||||||
|
FRIENDS = 'friends',
|
||||||
|
MOST_POPULAR = 'most-popular',
|
||||||
|
ALL_GAMES = 'all-games',
|
||||||
|
}
|
34
src/index.ts
34
src/index.ts
@ -33,17 +33,24 @@ import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
|||||||
import { GuideMenu, GuideMenuTab } from "./modules/ui/guide-menu";
|
import { GuideMenu, GuideMenuTab } from "./modules/ui/guide-menu";
|
||||||
import { StreamSettings } from "./modules/stream/stream-settings";
|
import { StreamSettings } from "./modules/stream/stream-settings";
|
||||||
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
||||||
|
import { UiSection } from "./enums/ui-sections";
|
||||||
|
|
||||||
|
|
||||||
// Handle login page
|
// Handle login page
|
||||||
if (window.location.pathname.includes('/auth/msa')) {
|
if (window.location.pathname.includes('/auth/msa')) {
|
||||||
window.addEventListener('load', e => {
|
const nativePushState = window.history['pushState'];
|
||||||
window.location.search.includes('loggedIn') && window.setTimeout(() => {
|
window.history['pushState'] = function(...args: any[]) {
|
||||||
const location = window.location;
|
const url = args[2];
|
||||||
|
if (url && (url.startsWith('/play') || url.substring(6).startsWith('/play'))) {
|
||||||
|
console.log('Redirecting to xbox.com/play');
|
||||||
|
window.stop();
|
||||||
|
window.location.href = 'https://www.xbox.com' + url;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
location.pathname.includes('/play') && location.reload(true);
|
return nativePushState.apply(this, arguments);
|
||||||
}, 2000);
|
}
|
||||||
});
|
|
||||||
// Stop processing the script
|
// Stop processing the script
|
||||||
throw new Error('[Better xCloud] Refreshing the page after logging in');
|
throw new Error('[Better xCloud] Refreshing the page after logging in');
|
||||||
}
|
}
|
||||||
@ -94,8 +101,19 @@ window.addEventListener('load', e => {
|
|||||||
window.location.reload(true);
|
window.location.reload(true);
|
||||||
}
|
}
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Hide "Play with Friends" skeleton section
|
||||||
|
if (getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS)) {
|
||||||
|
document.addEventListener('readystatechange', e => {
|
||||||
|
if (document.readyState === 'interactive') {
|
||||||
|
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest('div[class*=HomePage-module]') as HTMLElement;
|
||||||
|
$parent && ($parent.style.display = 'none');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
window.BX_EXPOSED = BxExposed;
|
window.BX_EXPOSED = BxExposed;
|
||||||
|
|
||||||
// Hide Settings UI when navigate to another page
|
// Hide Settings UI when navigate to another page
|
||||||
@ -277,7 +295,7 @@ function main() {
|
|||||||
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext();
|
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext();
|
||||||
getPref(PrefKey.BLOCK_TRACKING) && patchMeControl();
|
getPref(PrefKey.BLOCK_TRACKING) && patchMeControl();
|
||||||
|
|
||||||
STATES.userAgentHasTouchSupport && TouchController.updateCustomList();
|
STATES.userAgent.capabilities.touch && TouchController.updateCustomList();
|
||||||
overridePreloadState();
|
overridePreloadState();
|
||||||
|
|
||||||
VibrationManager.initialSetup();
|
VibrationManager.initialSetup();
|
||||||
@ -303,8 +321,10 @@ function main() {
|
|||||||
disablePwa();
|
disablePwa();
|
||||||
|
|
||||||
// Show a toast when connecting/disconecting controller
|
// Show a toast when connecting/disconecting controller
|
||||||
|
if (getPref(PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS)) {
|
||||||
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
|
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
|
||||||
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
|
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
|
||||||
|
}
|
||||||
|
|
||||||
// Preload Remote Play
|
// Preload Remote Play
|
||||||
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {
|
if (getPref(PrefKey.REMOTE_PLAY_ENABLED)) {
|
||||||
|
@ -41,7 +41,7 @@ export class GameBar {
|
|||||||
|
|
||||||
this.actions = [
|
this.actions = [
|
||||||
new ScreenshotAction(),
|
new ScreenshotAction(),
|
||||||
...(STATES.userAgentHasTouchSupport && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'off') ? [new TouchControlAction()] : []),
|
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'off') ? [new TouchControlAction()] : []),
|
||||||
new MicrophoneAction(),
|
new MicrophoneAction(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import codeRemotePlayEnable from "./patches/remote-play-enable.js" with { type:
|
|||||||
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.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" };
|
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
||||||
import { FeatureGates } from "@/utils/feature-gates.js";
|
import { FeatureGates } from "@/utils/feature-gates.js";
|
||||||
|
import { UiSection } from "@/enums/ui-sections.js";
|
||||||
|
|
||||||
type PatchArray = (keyof typeof PATCHES)[];
|
type PatchArray = (keyof typeof PATCHES)[];
|
||||||
|
|
||||||
@ -189,12 +190,18 @@ if (!!window.BX_REMOTE_PLAY_CONFIG) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
enableXcloudLogger(str: string) {
|
enableXcloudLogger(str: string) {
|
||||||
const text = 'this.telemetryProvider=e}log(e,t,i){';
|
const text = 'this.telemetryProvider=e}log(e,t,r){';
|
||||||
if (!str.includes(text)) {
|
if (!str.includes(text)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = str.replaceAll(text, text + 'console.log(Array.from(arguments));');
|
const newCode = `
|
||||||
|
const [logTag, logLevel, logMessage] = Array.from(arguments);
|
||||||
|
const logFunc = [console.debug, console.log, console.warn, console.error][logLevel];
|
||||||
|
logFunc(logTag, '//', logMessage);
|
||||||
|
`;
|
||||||
|
|
||||||
|
str = str.replaceAll(text, text + newCode);
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -635,6 +642,102 @@ true` + text;
|
|||||||
str = str.replace(text, 'if (!e) e = "https://www.xbox.com";');
|
str = str.replace(text, 'if (!e) e = "https://www.xbox.com";');
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exposeDialogRoutes(str: string) {
|
||||||
|
const text = 'return{goBack:function(){';
|
||||||
|
if (!str.includes(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.replace(text, 'return window.BX_EXPOSED.dialogRoutes = {goBack:function(){');
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
(x.AW, {
|
||||||
|
path: V.LoginDeviceCode.path,
|
||||||
|
exact: !0,
|
||||||
|
render: () => (0, n.jsx)(qe, {
|
||||||
|
children: (0, n.jsx)(Et.R, {})
|
||||||
|
})
|
||||||
|
}, V.LoginDeviceCode.name),
|
||||||
|
|
||||||
|
const qe = e => {
|
||||||
|
let {
|
||||||
|
children: t
|
||||||
|
} = e;
|
||||||
|
const {
|
||||||
|
isTV: a,
|
||||||
|
isSupportedTVBrowser: r
|
||||||
|
} = (0, T.d)();
|
||||||
|
return a && r ? (0, n.jsx)(n.Fragment, {
|
||||||
|
children: t
|
||||||
|
}) : (0, n.jsx)(x.l_, {
|
||||||
|
to: V.Home.getLink()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
*/
|
||||||
|
enableTvRoutes(str: string) {
|
||||||
|
let index = str.indexOf('.LoginDeviceCode.path,');
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find *qe* name
|
||||||
|
const match = /render:.*?jsx\)\(([^,]+),/.exec(str.substring(index, index + 100));
|
||||||
|
if (!match) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const funcName = match[1];
|
||||||
|
|
||||||
|
// Replace *qe*'s return value
|
||||||
|
// `return a && r ?` => `return a && r || true ?`
|
||||||
|
index = str.indexOf(`const ${funcName}=e=>{`);
|
||||||
|
index > -1 && (index = str.indexOf('return ', index));
|
||||||
|
index > -1 && (index = str.indexOf('?', index));
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substring(0, index) + '|| true' + str.substring(index);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Don't render "Play With Friends" sections
|
||||||
|
ignorePlayWithFriendsSection(str: string) {
|
||||||
|
let index = str.indexOf('location:"PlayWithFriendsRow",');
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = str.indexOf('return', index - 50);
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substring(0, index) + 'return null;' + str.substring(index + 6);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Don't render "All Games" sections
|
||||||
|
ignoreAllGamesSection(str: string) {
|
||||||
|
let index = str.indexOf('className:"AllGamesRow-module__allGamesRowContainer');
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = str.indexOf('grid:!0,', index);
|
||||||
|
index > -1 && (index = str.indexOf('(0,', index - 70));
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = str.substring(0, index) + 'true ? null :' + str.substring(index);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let PATCH_ORDERS: PatchArray = [
|
let PATCH_ORDERS: PatchArray = [
|
||||||
@ -652,11 +755,17 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
'broadcastPollingMode',
|
'broadcastPollingMode',
|
||||||
|
|
||||||
'exposeStreamSession',
|
'exposeStreamSession',
|
||||||
|
'exposeDialogRoutes',
|
||||||
|
|
||||||
|
'enableTvRoutes',
|
||||||
|
|
||||||
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
|
getPref(PrefKey.UI_LAYOUT) !== 'default' && 'websiteLayout',
|
||||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && 'supportLocalCoOp',
|
||||||
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
getPref(PrefKey.GAME_FORTNITE_FORCE_CONSOLE) && 'forceFortniteConsole',
|
||||||
|
|
||||||
|
getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS) && 'ignorePlayWithFriendsSection',
|
||||||
|
getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
||||||
|
|
||||||
...(getPref(PrefKey.BLOCK_TRACKING) ? [
|
...(getPref(PrefKey.BLOCK_TRACKING) ? [
|
||||||
'disableAiTrack',
|
'disableAiTrack',
|
||||||
'disableTelemetry',
|
'disableTelemetry',
|
||||||
@ -672,7 +781,7 @@ let PATCH_ORDERS: PatchArray = [
|
|||||||
'remotePlayKeepAlive',
|
'remotePlayKeepAlive',
|
||||||
'remotePlayDirectConnectUrl',
|
'remotePlayDirectConnectUrl',
|
||||||
'remotePlayDisableAchievementToast',
|
'remotePlayDisableAchievementToast',
|
||||||
STATES.userAgentHasTouchSupport && 'patchUpdateInputConfigurationAsync',
|
STATES.userAgent.capabilities.touch && 'patchUpdateInputConfigurationAsync',
|
||||||
] : []),
|
] : []),
|
||||||
|
|
||||||
...(BX_FLAGS.EnableXcloudLogging ? [
|
...(BX_FLAGS.EnableXcloudLogging ? [
|
||||||
@ -698,7 +807,7 @@ let PLAYING_PATCH_ORDERS: PatchArray = [
|
|||||||
// Skip feedback dialog
|
// Skip feedback dialog
|
||||||
getPref(PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG) && 'skipFeedbackDialog',
|
getPref(PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG) && 'skipFeedbackDialog',
|
||||||
|
|
||||||
...(STATES.userAgentHasTouchSupport ? [
|
...(STATES.userAgent.capabilities.touch ? [
|
||||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'patchShowSensorControls',
|
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'patchShowSensorControls',
|
||||||
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'exposeTouchLayoutManager',
|
getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'all' && 'exposeTouchLayoutManager',
|
||||||
(getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
(getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === 'off' || getPref(PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF)) && 'disableTakRenderer',
|
||||||
|
@ -91,7 +91,7 @@ export class StreamBadges {
|
|||||||
let batteryLevel = '100%';
|
let batteryLevel = '100%';
|
||||||
let batteryLevelInt = 100;
|
let batteryLevelInt = 100;
|
||||||
let isCharging = false;
|
let isCharging = false;
|
||||||
if ('getBattery' in navigator) {
|
if (STATES.browser.capabilities.batteryApi) {
|
||||||
try {
|
try {
|
||||||
const bm = await (navigator as NavigatorBattery).getBattery();
|
const bm = await (navigator as NavigatorBattery).getBattery();
|
||||||
isCharging = bm.charging;
|
isCharging = bm.charging;
|
||||||
@ -224,7 +224,7 @@ export class StreamBadges {
|
|||||||
|
|
||||||
// Battery
|
// Battery
|
||||||
let batteryLevel = '';
|
let batteryLevel = '';
|
||||||
if ('getBattery' in navigator) {
|
if (STATES.browser.capabilities.batteryApi) {
|
||||||
batteryLevel = '100%';
|
batteryLevel = '100%';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -338,7 +338,7 @@ export class StreamBadges {
|
|||||||
|
|
||||||
// Get battery level
|
// Get battery level
|
||||||
try {
|
try {
|
||||||
'getBattery' in navigator && (navigator as NavigatorBattery).getBattery().then(bm => {
|
STATES.browser.capabilities.batteryApi && (navigator as NavigatorBattery).getBattery().then(bm => {
|
||||||
streamBadges.startBatteryLevel = Math.round(bm.level * 100);
|
streamBadges.startBatteryLevel = Math.round(bm.level * 100);
|
||||||
});
|
});
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
@ -103,7 +103,7 @@ export class StreamSettings {
|
|||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
|
|
||||||
STATES.userAgentHasTouchSupport && {
|
STATES.userAgent.capabilities.touch && {
|
||||||
group: 'touch-controller',
|
group: 'touch-controller',
|
||||||
label: t('touch-controller'),
|
label: t('touch-controller'),
|
||||||
items: [{
|
items: [{
|
||||||
@ -255,6 +255,8 @@ export class StreamSettings {
|
|||||||
|
|
||||||
$container.classList.remove('bx-gone');
|
$container.classList.remove('bx-gone');
|
||||||
document.body.classList.add('bx-no-scroll');
|
document.body.classList.add('bx-no-scroll');
|
||||||
|
|
||||||
|
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
@ -262,6 +264,8 @@ export class StreamSettings {
|
|||||||
this.$container!.classList.add('bx-gone');
|
this.$container!.classList.add('bx-gone');
|
||||||
|
|
||||||
document.body.classList.remove('bx-no-scroll');
|
document.body.classList.remove('bx-no-scroll');
|
||||||
|
|
||||||
|
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
#setupDialog() {
|
#setupDialog() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { PrefKey } from "@utils/preferences"
|
import { PrefKey, getPref } from "@utils/preferences"
|
||||||
import { BxEvent } from "@utils/bx-event"
|
import { BxEvent } from "@utils/bx-event"
|
||||||
import { getPref } from "@utils/preferences"
|
|
||||||
import { CE } from "@utils/html"
|
import { CE } from "@utils/html"
|
||||||
import { t } from "@utils/translation"
|
import { t } from "@utils/translation"
|
||||||
import { STATES } from "@utils/global"
|
import { STATES } from "@utils/global"
|
||||||
|
@ -35,7 +35,7 @@ function cloneStreamHudButton($orgButton: HTMLElement, label: string, svgIcon: t
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (STATES.browserHasTouchSupport) {
|
if (STATES.browser.capabilities.touch) {
|
||||||
$container.addEventListener('transitionstart', onTransitionStart);
|
$container.addEventListener('transitionstart', onTransitionStart);
|
||||||
$container.addEventListener('transitionend', onTransitionEnd);
|
$container.addEventListener('transitionend', onTransitionEnd);
|
||||||
}
|
}
|
||||||
|
@ -66,8 +66,8 @@ const SETTINGS_UI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
[t('touch-controller')]: {
|
[t('touch-controller')]: {
|
||||||
note: !STATES.userAgentHasTouchSupport ? '⚠️ ' + t('device-unsupported-touch') : null,
|
note: !STATES.userAgent.capabilities.touch ? '⚠️ ' + t('device-unsupported-touch') : null,
|
||||||
unsupported: !STATES.userAgentHasTouchSupport,
|
unsupported: !STATES.userAgent.capabilities.touch,
|
||||||
items: [
|
items: [
|
||||||
PrefKey.STREAM_TOUCH_CONTROLLER,
|
PrefKey.STREAM_TOUCH_CONTROLLER,
|
||||||
PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF,
|
PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF,
|
||||||
@ -89,17 +89,19 @@ const SETTINGS_UI = {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.UI_LAYOUT,
|
PrefKey.UI_LAYOUT,
|
||||||
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
PrefKey.UI_HOME_CONTEXT_MENU_DISABLED,
|
||||||
|
PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS,
|
||||||
PrefKey.STREAM_SIMPLIFY_MENU,
|
PrefKey.STREAM_SIMPLIFY_MENU,
|
||||||
PrefKey.SKIP_SPLASH_VIDEO,
|
PrefKey.SKIP_SPLASH_VIDEO,
|
||||||
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
!AppInterface && PrefKey.UI_SCROLLBAR_HIDE,
|
||||||
PrefKey.HIDE_DOTS_ICON,
|
PrefKey.HIDE_DOTS_ICON,
|
||||||
PrefKey.REDUCE_ANIMATIONS,
|
PrefKey.REDUCE_ANIMATIONS,
|
||||||
|
PrefKey.BLOCK_SOCIAL_FEATURES,
|
||||||
|
PrefKey.UI_HIDE_SECTIONS,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
[t('other')]: {
|
[t('other')]: {
|
||||||
items: [
|
items: [
|
||||||
PrefKey.BLOCK_SOCIAL_FEATURES,
|
|
||||||
PrefKey.BLOCK_TRACKING,
|
PrefKey.BLOCK_TRACKING,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -1,33 +1,97 @@
|
|||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
import { AppInterface, STATES } from "@/utils/global";
|
import { AppInterface, STATES } from "@/utils/global";
|
||||||
import { createButton, ButtonStyle } from "@/utils/html";
|
import { createButton, ButtonStyle, CE } from "@/utils/html";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
|
import { StreamSettings } from "../stream/stream-settings";
|
||||||
|
|
||||||
export enum GuideMenuTab {
|
export enum GuideMenuTab {
|
||||||
HOME,
|
HOME,
|
||||||
}
|
}
|
||||||
|
|
||||||
export class GuideMenu {
|
export class GuideMenu {
|
||||||
|
static #BUTTONS = {
|
||||||
|
streamSetting: createButton({
|
||||||
|
label: t('stream-settings'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
// Wait until the Guide dialog is closed
|
||||||
|
window.addEventListener(BxEvent.XCLOUD_DIALOG_DISMISSED, e => {
|
||||||
|
setTimeout(() => StreamSettings.getInstance().show(), 50);
|
||||||
|
}, {once: true});
|
||||||
|
|
||||||
|
// Close all xCloud's dialogs
|
||||||
|
window.BX_EXPOSED.dialogRoutes.closeAll();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
appSettings: createButton({
|
||||||
|
label: t('android-app-settings'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
// Close all xCloud's dialogs
|
||||||
|
window.BX_EXPOSED.dialogRoutes.closeAll();
|
||||||
|
|
||||||
|
AppInterface.openAppSettings && AppInterface.openAppSettings();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
closeApp: createButton({
|
||||||
|
label: t('close-app'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
|
||||||
|
onClick: e => {
|
||||||
|
AppInterface.closeApp();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
reloadStream: createButton({
|
||||||
|
label: t('reload-stream'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
confirm(t('confirm-reload-stream')) && window.location.reload();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
backToHome: createButton({
|
||||||
|
label: t('back-to-home'),
|
||||||
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
||||||
|
onClick: e => {
|
||||||
|
confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
static #renderButtons(buttons: HTMLElement[]) {
|
||||||
|
const $div = CE('div', {});
|
||||||
|
|
||||||
|
for (const $button of buttons) {
|
||||||
|
$div.appendChild($button);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $div;
|
||||||
|
}
|
||||||
|
|
||||||
static #injectHome($root: HTMLElement) {
|
static #injectHome($root: HTMLElement) {
|
||||||
// Find the last divider
|
// Find the last divider
|
||||||
const $dividers = $root.querySelectorAll('div[class*=Divider-module__divider]');
|
const $dividers = $root.querySelectorAll('div[class*=Divider-module__divider]');
|
||||||
if (!$dividers) {
|
if (!$dividers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const $lastDivider = $dividers[$dividers.length - 1];
|
|
||||||
|
|
||||||
// Add "Close app" button
|
const buttons: HTMLElement[] = [];
|
||||||
|
|
||||||
|
// "Stream settings" button
|
||||||
|
buttons.push(GuideMenu.#BUTTONS.streamSetting);
|
||||||
|
|
||||||
|
// "App settings" & "Close app" buttons
|
||||||
if (AppInterface) {
|
if (AppInterface) {
|
||||||
const $btnQuit = createButton({
|
buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||||
label: t('close-app'),
|
buttons.push(GuideMenu.#BUTTONS.closeApp);
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.DANGER,
|
|
||||||
onClick: e => {
|
|
||||||
AppInterface.closeApp();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$lastDivider.insertAdjacentElement('afterend', $btnQuit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||||
|
|
||||||
|
const $lastDivider = $dividers[$dividers.length - 1];
|
||||||
|
$lastDivider.insertAdjacentElement('afterend', $buttons);
|
||||||
}
|
}
|
||||||
|
|
||||||
static #injectHomePlaying($root: HTMLElement) {
|
static #injectHomePlaying($root: HTMLElement) {
|
||||||
@ -36,25 +100,19 @@ export class GuideMenu {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add buttons
|
const buttons: HTMLElement[] = [];
|
||||||
const $btnReload = createButton({
|
|
||||||
label: t('reload-stream'),
|
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
|
||||||
onClick: e => {
|
|
||||||
confirm(t('confirm-reload-stream')) && window.location.reload();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const $btnHome = createButton({
|
buttons.push(GuideMenu.#BUTTONS.streamSetting);
|
||||||
label: t('back-to-home'),
|
AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
|
|
||||||
onClick: e => {
|
|
||||||
confirm(t('back-to-home-confirm')) && (window.location.href = window.location.href.substring(0, 31));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
$btnQuit.insertAdjacentElement('afterend', $btnReload);
|
// Reload stream
|
||||||
$btnReload.insertAdjacentElement('afterend', $btnHome);
|
buttons.push(GuideMenu.#BUTTONS.reloadStream);
|
||||||
|
|
||||||
|
// Back to home
|
||||||
|
buttons.push(GuideMenu.#BUTTONS.backToHome);
|
||||||
|
|
||||||
|
const $buttons = GuideMenu.#renderButtons(buttons);
|
||||||
|
$btnQuit.insertAdjacentElement('afterend', $buttons);
|
||||||
|
|
||||||
// Hide xCloud's Home button
|
// Hide xCloud's Home button
|
||||||
const $btnXcloudHome = $root.querySelector('div[class^=HomeButtonWithDivider]') as HTMLElement;
|
const $btnXcloudHome = $root.querySelector('div[class^=HomeButtonWithDivider]') as HTMLElement;
|
||||||
@ -65,7 +123,8 @@ export class GuideMenu {
|
|||||||
const where = (e as any).where as GuideMenuTab;
|
const where = (e as any).where as GuideMenuTab;
|
||||||
|
|
||||||
if (where === GuideMenuTab.HOME) {
|
if (where === GuideMenuTab.HOME) {
|
||||||
const $root = document.querySelector('#gamepass-dialog-root div[role=dialog]') as HTMLElement;
|
const $root = document.querySelector('#gamepass-dialog-root div[role=dialog] div[role=tabpanel] div[class*=HomeLandingPage]') as HTMLElement;
|
||||||
|
if ($root) {
|
||||||
if (STATES.isPlaying) {
|
if (STATES.isPlaying) {
|
||||||
GuideMenu.#injectHomePlaying($root);
|
GuideMenu.#injectHomePlaying($root);
|
||||||
} else {
|
} else {
|
||||||
@ -73,6 +132,7 @@ export class GuideMenu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static observe() {
|
static observe() {
|
||||||
window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, GuideMenu.#onShown);
|
window.addEventListener(BxEvent.XCLOUD_GUIDE_MENU_SHOWN, GuideMenu.#onShown);
|
||||||
|
@ -26,8 +26,10 @@ export function localRedirect(path: string) {
|
|||||||
$anchor.click();
|
$anchor.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function setupStreamUi() {
|
export function setupStreamUi() {
|
||||||
StreamSettings.getInstance();
|
StreamSettings.getInstance();
|
||||||
onChangeVideoPlayerType();
|
onChangeVideoPlayerType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(window as any).localRedirect = localRedirect;
|
||||||
|
14
src/types/index.d.ts
vendored
14
src/types/index.d.ts
vendored
@ -28,8 +28,18 @@ type BxStates = {
|
|||||||
appContext: any | null;
|
appContext: any | null;
|
||||||
serverRegions: any;
|
serverRegions: any;
|
||||||
|
|
||||||
userAgentHasTouchSupport: boolean;
|
browser: {
|
||||||
browserHasTouchSupport: boolean;
|
capabilities: {
|
||||||
|
touch: boolean;
|
||||||
|
batteryApi: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
userAgent: {
|
||||||
|
capabilities: {
|
||||||
|
touch: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
currentStream: Partial<{
|
currentStream: Partial<{
|
||||||
titleId: string;
|
titleId: string;
|
||||||
|
@ -34,7 +34,7 @@ export const BxExposed = {
|
|||||||
|
|
||||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
|
||||||
|
|
||||||
if (STATES.userAgentHasTouchSupport) {
|
if (STATES.userAgent.capabilities.touch) {
|
||||||
let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
|
let touchControllerAvailability = getPref(PrefKey.STREAM_TOUCH_CONTROLLER);
|
||||||
|
|
||||||
// Disable touch control when gamepad found
|
// Disable touch control when gamepad found
|
||||||
|
@ -11,6 +11,8 @@ type BxFlags = Partial<{
|
|||||||
FeatureGates: {[key: string]: boolean} | null,
|
FeatureGates: {[key: string]: boolean} | null,
|
||||||
|
|
||||||
ScriptUi: 'default' | 'tv',
|
ScriptUi: 'default' | 'tv',
|
||||||
|
|
||||||
|
IsSupportedTvBrowser: boolean,
|
||||||
}>
|
}>
|
||||||
|
|
||||||
// Setup flags
|
// Setup flags
|
||||||
|
@ -1,23 +1,33 @@
|
|||||||
import { CE } from "@utils/html";
|
import { CE } from "@utils/html";
|
||||||
import { PrefKey, getPref } from "@utils/preferences";
|
import { PrefKey, getPref } from "@utils/preferences";
|
||||||
import { renderStylus } from "@macros/build" with {type: "macro"};
|
import { renderStylus } from "@macros/build" with {type: "macro"};
|
||||||
|
import { UiSection } from "@/enums/ui-sections";
|
||||||
|
|
||||||
|
|
||||||
export function addCss() {
|
export function addCss() {
|
||||||
const STYLUS_CSS = renderStylus();
|
const STYLUS_CSS = renderStylus();
|
||||||
let css = STYLUS_CSS;
|
let css = STYLUS_CSS;
|
||||||
|
|
||||||
|
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
|
||||||
|
const selectorToHide = [];
|
||||||
|
|
||||||
|
// Hide "News" section
|
||||||
|
if (PREF_HIDE_SECTIONS.includes(UiSection.NEWS)) {
|
||||||
|
selectorToHide.push('#BodyContent > div[class*=CarouselRow-module]');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide "All games" section
|
||||||
|
if (PREF_HIDE_SECTIONS.includes(UiSection.ALL_GAMES)) {
|
||||||
|
selectorToHide.push('#BodyContent div[class*=AllGamesRow-module__gridContainer]');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide "Start a party" button in the Guide menu
|
||||||
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
if (getPref(PrefKey.BLOCK_SOCIAL_FEATURES)) {
|
||||||
css += `
|
selectorToHide.push('#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]');
|
||||||
/* Hide "Play with friends" section */
|
}
|
||||||
div[class^=HomePage-module__bottomSpacing]:has(button[class*=SocialEmptyCard]),
|
|
||||||
button[class*=SocialEmptyCard],
|
if (selectorToHide) {
|
||||||
/* Hide "Start a party" button in the Guide menu */
|
css += selectorToHide.join(',') + '{ display: none; }';
|
||||||
#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reduce animations
|
// Reduce animations
|
||||||
|
@ -16,8 +16,19 @@ export const STATES: BxStates = {
|
|||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
appContext: {},
|
appContext: {},
|
||||||
serverRegions: {},
|
serverRegions: {},
|
||||||
userAgentHasTouchSupport: userAgentHasTouchSupport,
|
|
||||||
browserHasTouchSupport: browserHasTouchSupport,
|
browser: {
|
||||||
|
capabilities: {
|
||||||
|
touch: browserHasTouchSupport,
|
||||||
|
batteryApi: 'getBattery' in window.navigator,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
userAgent: {
|
||||||
|
capabilities: {
|
||||||
|
touch: userAgentHasTouchSupport,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
currentStream: {},
|
currentStream: {},
|
||||||
remotePlay: {},
|
remotePlay: {},
|
||||||
@ -31,7 +42,7 @@ export function deepClone(obj: any): any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!obj) {
|
if (!obj) {
|
||||||
return obj;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
@ -11,8 +11,6 @@ export function patchVideoApi() {
|
|||||||
// Show video player when it's ready
|
// Show video player when it's ready
|
||||||
const showFunc = function(this: HTMLVideoElement) {
|
const showFunc = function(this: HTMLVideoElement) {
|
||||||
this.style.visibility = 'visible';
|
this.style.visibility = 'visible';
|
||||||
this.removeEventListener('playing', showFunc);
|
|
||||||
|
|
||||||
if (!this.videoWidth) {
|
if (!this.videoWidth) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -49,7 +47,7 @@ export function patchVideoApi() {
|
|||||||
const $parent = this.parentElement!!;
|
const $parent = this.parentElement!!;
|
||||||
// Video tag is stream player
|
// Video tag is stream player
|
||||||
if (!this.src && $parent.dataset.testid === 'media-container') {
|
if (!this.src && $parent.dataset.testid === 'media-container') {
|
||||||
this.addEventListener('playing', showFunc);
|
this.addEventListener('loadedmetadata', showFunc, {once: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
return nativePlay.apply(this);
|
return nativePlay.apply(this);
|
||||||
@ -163,6 +161,8 @@ export function patchMeControl() {
|
|||||||
API: {
|
API: {
|
||||||
setDisplayMode: () => {},
|
setDisplayMode: () => {},
|
||||||
setMobileState: () => {},
|
setMobileState: () => {},
|
||||||
|
addEventListener: () => {},
|
||||||
|
removeEventListener: () => {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ import { getPreferredServerRegion } from "@utils/region";
|
|||||||
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
|
import { GamePassCloudGallery } from "../enums/game-pass-gallery";
|
||||||
import { InputType } from "./bx-exposed";
|
import { InputType } from "./bx-exposed";
|
||||||
import { FeatureGates } from "./feature-gates";
|
import { FeatureGates } from "./feature-gates";
|
||||||
|
import { BxLogger } from "./bx-logger";
|
||||||
|
|
||||||
enum RequestType {
|
enum RequestType {
|
||||||
XCLOUD = 'xcloud',
|
XCLOUD = 'xcloud',
|
||||||
@ -95,7 +96,7 @@ function updateIceCandidates(candidates: any, options: any) {
|
|||||||
|
|
||||||
newCandidates.push(newCandidate('a=end-of-candidates'));
|
newCandidates.push(newCandidate('a=end-of-candidates'));
|
||||||
|
|
||||||
console.log(newCandidates);
|
BxLogger.info('ICE Candidates', newCandidates);
|
||||||
return newCandidates;
|
return newCandidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,6 +158,10 @@ class XhomeInterceptor {
|
|||||||
console.log(obj);
|
console.log(obj);
|
||||||
|
|
||||||
const serverDetails = obj.serverDetails;
|
const serverDetails = obj.serverDetails;
|
||||||
|
if (serverDetails.ipAddress) {
|
||||||
|
XhomeInterceptor.#consoleAddrs[serverDetails.ipAddress] = serverDetails.port;
|
||||||
|
}
|
||||||
|
|
||||||
if (serverDetails.ipV4Address) {
|
if (serverDetails.ipV4Address) {
|
||||||
XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port;
|
XhomeInterceptor.#consoleAddrs[serverDetails.ipV4Address] = serverDetails.ipV4Port;
|
||||||
}
|
}
|
||||||
@ -441,7 +446,7 @@ class XcloudInterceptor {
|
|||||||
|
|
||||||
let overrideMkb: boolean | null = null;
|
let overrideMkb: boolean | null = null;
|
||||||
|
|
||||||
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo!.details.productId)) {
|
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || (STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId))) {
|
||||||
overrideMkb = true;
|
overrideMkb = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,7 +581,7 @@ export function interceptHttpRequests() {
|
|||||||
const response = await NATIVE_FETCH(request, init);
|
const response = await NATIVE_FETCH(request, init);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
|
|
||||||
if (json && json.exp && json.treatments) {
|
if (json && json.exp && json.exp.treatments) {
|
||||||
for (const key in FeatureGates) {
|
for (const key in FeatureGates) {
|
||||||
json.exp.treatments[key] = FeatureGates[key]
|
json.exp.treatments[key] = FeatureGates[key]
|
||||||
}
|
}
|
||||||
@ -590,7 +595,7 @@ export function interceptHttpRequests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add list of games with custom layouts to the official list
|
// Add list of games with custom layouts to the official list
|
||||||
if (STATES.userAgentHasTouchSupport && url.includes('catalog.gamepass.com/sigls/')) {
|
if (STATES.userAgent.capabilities.touch && url.includes('catalog.gamepass.com/sigls/')) {
|
||||||
const response = await NATIVE_FETCH(request, init);
|
const response = await NATIVE_FETCH(request, init);
|
||||||
const obj = await response.clone().json();
|
const obj = await response.clone().json();
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import type { PreferenceSetting, PreferenceSettings } from "@/types/preferences"
|
|||||||
import { AppInterface, STATES } from "@utils/global";
|
import { AppInterface, STATES } from "@utils/global";
|
||||||
import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
|
import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
|
||||||
import { UserAgentProfile } from "@/enums/user-agent";
|
import { UserAgentProfile } from "@/enums/user-agent";
|
||||||
|
import { UiSection } from "@/enums/ui-sections";
|
||||||
|
|
||||||
export enum PrefKey {
|
export enum PrefKey {
|
||||||
LAST_UPDATE_CHECK = 'version_last_check',
|
LAST_UPDATE_CHECK = 'version_last_check',
|
||||||
@ -45,6 +46,7 @@ export enum PrefKey {
|
|||||||
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
|
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
|
||||||
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
|
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
|
||||||
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
|
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
|
||||||
|
CONTROLLER_SHOW_CONNECTION_STATUS = 'controller_show_connection_status',
|
||||||
|
|
||||||
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
|
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
|
||||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
|
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
|
||||||
@ -69,6 +71,7 @@ export enum PrefKey {
|
|||||||
|
|
||||||
UI_LAYOUT = 'ui_layout',
|
UI_LAYOUT = 'ui_layout',
|
||||||
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
|
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
|
||||||
|
UI_HIDE_SECTIONS = 'ui_hide_sections',
|
||||||
|
|
||||||
UI_HOME_CONTEXT_MENU_DISABLED = 'ui_home_context_menu_disabled',
|
UI_HOME_CONTEXT_MENU_DISABLED = 'ui_home_context_menu_disabled',
|
||||||
|
|
||||||
@ -267,7 +270,7 @@ export class Preferences {
|
|||||||
all: t('tc-all-games'),
|
all: t('tc-all-games'),
|
||||||
off: t('off'),
|
off: t('off'),
|
||||||
},
|
},
|
||||||
unsupported: !STATES.userAgentHasTouchSupport,
|
unsupported: !STATES.userAgent.capabilities.touch,
|
||||||
ready: (setting: PreferenceSetting) => {
|
ready: (setting: PreferenceSetting) => {
|
||||||
if (setting.unsupported) {
|
if (setting.unsupported) {
|
||||||
setting.default = 'default';
|
setting.default = 'default';
|
||||||
@ -277,7 +280,7 @@ export class Preferences {
|
|||||||
[PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: {
|
[PrefKey.STREAM_TOUCH_CONTROLLER_AUTO_OFF]: {
|
||||||
label: t('tc-auto-off'),
|
label: t('tc-auto-off'),
|
||||||
default: false,
|
default: false,
|
||||||
unsupported: !STATES.userAgentHasTouchSupport,
|
unsupported: !STATES.userAgent.capabilities.touch,
|
||||||
},
|
},
|
||||||
[PrefKey.STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY]: {
|
[PrefKey.STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY]: {
|
||||||
type: SettingElementType.NUMBER_STEPPER,
|
type: SettingElementType.NUMBER_STEPPER,
|
||||||
@ -291,7 +294,7 @@ export class Preferences {
|
|||||||
ticks: 10,
|
ticks: 10,
|
||||||
hideSlider: true,
|
hideSlider: true,
|
||||||
},
|
},
|
||||||
unsupported: !STATES.userAgentHasTouchSupport,
|
unsupported: !STATES.userAgent.capabilities.touch,
|
||||||
},
|
},
|
||||||
[PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: {
|
[PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: {
|
||||||
label: t('tc-standard-layout-style'),
|
label: t('tc-standard-layout-style'),
|
||||||
@ -301,7 +304,7 @@ export class Preferences {
|
|||||||
white: t('tc-all-white'),
|
white: t('tc-all-white'),
|
||||||
muted: t('tc-muted-colors'),
|
muted: t('tc-muted-colors'),
|
||||||
},
|
},
|
||||||
unsupported: !STATES.userAgentHasTouchSupport,
|
unsupported: !STATES.userAgent.capabilities.touch,
|
||||||
},
|
},
|
||||||
[PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: {
|
[PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: {
|
||||||
label: t('tc-custom-layout-style'),
|
label: t('tc-custom-layout-style'),
|
||||||
@ -310,7 +313,7 @@ export class Preferences {
|
|||||||
default: t('default'),
|
default: t('default'),
|
||||||
muted: t('tc-muted-colors'),
|
muted: t('tc-muted-colors'),
|
||||||
},
|
},
|
||||||
unsupported: !STATES.userAgentHasTouchSupport,
|
unsupported: !STATES.userAgent.capabilities.touch,
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.STREAM_SIMPLIFY_MENU]: {
|
[PrefKey.STREAM_SIMPLIFY_MENU]: {
|
||||||
@ -384,6 +387,11 @@ export class Preferences {
|
|||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
[PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS]: {
|
||||||
|
label: t('show-controller-connection-status'),
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
|
||||||
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
|
[PrefKey.CONTROLLER_ENABLE_SHORTCUTS]: {
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
@ -548,7 +556,21 @@ export class Preferences {
|
|||||||
|
|
||||||
[PrefKey.UI_HOME_CONTEXT_MENU_DISABLED]: {
|
[PrefKey.UI_HOME_CONTEXT_MENU_DISABLED]: {
|
||||||
label: t('disable-home-context-menu'),
|
label: t('disable-home-context-menu'),
|
||||||
default: STATES.browserHasTouchSupport,
|
default: STATES.browser.capabilities.touch,
|
||||||
|
},
|
||||||
|
|
||||||
|
[PrefKey.UI_HIDE_SECTIONS]: {
|
||||||
|
label: t('hide-sections'),
|
||||||
|
default: [],
|
||||||
|
multipleOptions: {
|
||||||
|
[UiSection.NEWS]: t('section-news'),
|
||||||
|
[UiSection.FRIENDS]: t('section-play-with-friends'),
|
||||||
|
// [UiSection.MOST_POPULAR]: t('section-most-popular'),
|
||||||
|
[UiSection.ALL_GAMES]: t('section-all-games'),
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
size: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
[PrefKey.BLOCK_SOCIAL_FEATURES]: {
|
[PrefKey.BLOCK_SOCIAL_FEATURES]: {
|
||||||
|
@ -24,7 +24,7 @@ export function overridePreloadState() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add list of games with custom layouts to the official list
|
// Add list of games with custom layouts to the official list
|
||||||
if (STATES.userAgentHasTouchSupport) {
|
if (STATES.userAgent.capabilities.touch) {
|
||||||
try {
|
try {
|
||||||
const sigls = state.xcloud.sigls;
|
const sigls = state.xcloud.sigls;
|
||||||
if (GamePassCloudGallery.TOUCH in sigls) {
|
if (GamePassCloudGallery.TOUCH in sigls) {
|
||||||
|
@ -57,7 +57,7 @@ export class Screenshot {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$player.parentElement!.addEventListener('animationend', this.#onAnimationEnd);
|
$player.parentElement!.addEventListener('animationend', this.#onAnimationEnd, { once: true });
|
||||||
$player.parentElement!.classList.add('bx-taking-screenshot');
|
$player.parentElement!.classList.add('bx-taking-screenshot');
|
||||||
|
|
||||||
const canvasContext = Screenshot.#canvasContext;
|
const canvasContext = Screenshot.#canvasContext;
|
||||||
|
@ -27,7 +27,7 @@ export enum SettingElementType {
|
|||||||
export class SettingElement {
|
export class SettingElement {
|
||||||
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
static #renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any) {
|
||||||
const $control = CE<HTMLSelectElement>('select', {
|
const $control = CE<HTMLSelectElement>('select', {
|
||||||
title: setting.label,
|
// title: setting.label,
|
||||||
tabindex: 0,
|
tabindex: 0,
|
||||||
}) as HTMLSelectElement;
|
}) as HTMLSelectElement;
|
||||||
for (let value in setting.options) {
|
for (let value in setting.options) {
|
||||||
@ -54,7 +54,7 @@ export class SettingElement {
|
|||||||
|
|
||||||
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}) {
|
static #renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}) {
|
||||||
const $control = CE<HTMLSelectElement>('select', {
|
const $control = CE<HTMLSelectElement>('select', {
|
||||||
title: setting.label,
|
// title: setting.label,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
tabindex: 0,
|
tabindex: 0,
|
||||||
});
|
});
|
||||||
|
@ -110,6 +110,7 @@ const Texts = {
|
|||||||
"hide": "Hide",
|
"hide": "Hide",
|
||||||
"hide-idle-cursor": "Hide mouse cursor on idle",
|
"hide-idle-cursor": "Hide mouse cursor on idle",
|
||||||
"hide-scrollbar": "Hide web page's scrollbar",
|
"hide-scrollbar": "Hide web page's scrollbar",
|
||||||
|
"hide-sections": "Hide sections",
|
||||||
"hide-system-menu-icon": "Hide System menu's icon",
|
"hide-system-menu-icon": "Hide System menu's icon",
|
||||||
"hide-touch-controller": "Hide touch controller",
|
"hide-touch-controller": "Hide touch controller",
|
||||||
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
||||||
@ -164,13 +165,13 @@ const Texts = {
|
|||||||
(e: any) => `${e.key} でこの機能を切替`,
|
(e: any) => `${e.key} でこの機能を切替`,
|
||||||
(e: any) => `${e.key} 키를 눌러 이 기능을 켜고 끄세요`,
|
(e: any) => `${e.key} 키를 눌러 이 기능을 켜고 끄세요`,
|
||||||
(e: any) => `Naciśnij ${e.key} aby przełączyć tę funkcję`,
|
(e: any) => `Naciśnij ${e.key} aby przełączyć tę funkcję`,
|
||||||
,
|
(e: any) => `Pressione ${e.key} para alternar este recurso`,
|
||||||
(e: any) => `Нажмите ${e.key} для переключения этой функции`,
|
(e: any) => `Нажмите ${e.key} для переключения этой функции`,
|
||||||
,
|
,
|
||||||
(e: any) => `Etkinleştirmek için ${e.key} tuşuna basın`,
|
(e: any) => `Etkinleştirmek için ${e.key} tuşuna basın`,
|
||||||
(e: any) => `Натисніть ${e.key} щоб перемкнути цю функцію`,
|
(e: any) => `Натисніть ${e.key} щоб перемкнути цю функцію`,
|
||||||
(e: any) => `Nhấn ${e.key} để bật/tắt tính năng này`,
|
(e: any) => `Nhấn ${e.key} để bật/tắt tính năng này`,
|
||||||
,
|
(e: any) => `按下 ${e.key} 来切换此功能`,
|
||||||
],
|
],
|
||||||
"press-to-bind": "Press a key or do a mouse click to bind...",
|
"press-to-bind": "Press a key or do a mouse click to bind...",
|
||||||
"prompt-preset-name": "Preset's name:",
|
"prompt-preset-name": "Preset's name:",
|
||||||
@ -191,6 +192,10 @@ const Texts = {
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"screen": "Screen",
|
"screen": "Screen",
|
||||||
"screenshot-apply-filters": "Applies video filters to screenshots",
|
"screenshot-apply-filters": "Applies video filters to screenshots",
|
||||||
|
"section-all-games": "All games",
|
||||||
|
"section-most-popular": "Most popular",
|
||||||
|
"section-news": "News",
|
||||||
|
"section-play-with-friends": "Play with friends",
|
||||||
"separate-touch-controller": "Separate Touch controller & Controller #1",
|
"separate-touch-controller": "Separate Touch controller & Controller #1",
|
||||||
"separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2",
|
"separate-touch-controller-note": "Touch controller is Player 1, Controller #1 is Player 2",
|
||||||
"server": "Server",
|
"server": "Server",
|
||||||
@ -200,6 +205,7 @@ const Texts = {
|
|||||||
"sharpness": "Sharpness",
|
"sharpness": "Sharpness",
|
||||||
"shortcut-keys": "Shortcut keys",
|
"shortcut-keys": "Shortcut keys",
|
||||||
"show": "Show",
|
"show": "Show",
|
||||||
|
"show-controller-connection-status": "Show controller connection status",
|
||||||
"show-game-art": "Show game art",
|
"show-game-art": "Show game art",
|
||||||
"show-hide": "Show/hide",
|
"show-hide": "Show/hide",
|
||||||
"show-stats-on-startup": "Show stats when starting the game",
|
"show-stats-on-startup": "Show stats when starting the game",
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { UserAgentProfile } from "@enums/user-agent";
|
import { UserAgentProfile } from "@enums/user-agent";
|
||||||
import { deepClone } from "./global";
|
import { deepClone } from "./global";
|
||||||
|
import { BX_FLAGS } from "./bx-flags";
|
||||||
|
|
||||||
type UserAgentConfig = {
|
type UserAgentConfig = {
|
||||||
profile: UserAgentProfile,
|
profile: UserAgentProfile,
|
||||||
custom?: string,
|
custom?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SMART_TV_UNIQUE_ID = 'FC4A1DA2-711C-4E9C-BC7F-047AF8A672EA';
|
||||||
|
|
||||||
let CHROMIUM_VERSION = '123.0.0.0';
|
let CHROMIUM_VERSION = '123.0.0.0';
|
||||||
if (!!(window as any).chrome || window.navigator.userAgent.includes('Chrome')) {
|
if (!!(window as any).chrome || window.navigator.userAgent.includes('Chrome')) {
|
||||||
// Get Chromium version in the original User-Agent value
|
// Get Chromium version in the original User-Agent value
|
||||||
@ -26,8 +29,8 @@ export class UserAgent {
|
|||||||
static #USER_AGENTS: PartialRecord<UserAgentProfile, string> = {
|
static #USER_AGENTS: PartialRecord<UserAgentProfile, string> = {
|
||||||
[UserAgentProfile.WINDOWS_EDGE]: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Safari/537.36 Edg/${CHROMIUM_VERSION}`,
|
[UserAgentProfile.WINDOWS_EDGE]: `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${CHROMIUM_VERSION} Safari/537.36 Edg/${CHROMIUM_VERSION}`,
|
||||||
[UserAgentProfile.MACOS_SAFARI]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
|
[UserAgentProfile.MACOS_SAFARI]: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.5.2 Safari/605.1.1',
|
||||||
[UserAgentProfile.SMARTTV_GENERIC]: window.navigator.userAgent + ' SmartTV',
|
[UserAgentProfile.SMARTTV_GENERIC]: `${window.navigator.userAgent} SmartTV ${SMART_TV_UNIQUE_ID}`,
|
||||||
[UserAgentProfile.SMARTTV_TIZEN]: `Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) ${CHROMIUM_VERSION}/7.0 TV Safari/537.36`,
|
[UserAgentProfile.SMARTTV_TIZEN]: `Mozilla/5.0 (SMART-TV; LINUX; Tizen 7.0) AppleWebKit/537.36 (KHTML, like Gecko) ${CHROMIUM_VERSION}/7.0 TV Safari/537.36 ${SMART_TV_UNIQUE_ID}`,
|
||||||
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + ' OculusBrowser VR',
|
[UserAgentProfile.VR_OCULUS]: window.navigator.userAgent + ' OculusBrowser VR',
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +119,12 @@ export class UserAgent {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newUserAgent = UserAgent.get(profile);
|
let newUserAgent = UserAgent.get(profile);
|
||||||
|
|
||||||
|
// Pretend to be Tizen TV
|
||||||
|
if (BX_FLAGS.IsSupportedTvBrowser) {
|
||||||
|
newUserAgent += ` SmartTV ${SMART_TV_UNIQUE_ID}`;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
|
// Clear data of navigator.userAgentData, force xCloud to detect browser based on navigator.userAgent
|
||||||
(window.navigator as any).orgUserAgentData = (window.navigator as any).userAgentData;
|
(window.navigator as any).orgUserAgentData = (window.navigator as any).userAgentData;
|
||||||
|
Reference in New Issue
Block a user