Compare commits

...

10 Commits

Author SHA1 Message Date
6f517d85b3 Bump version to 3.1.1 2024-01-31 07:28:44 +07:00
bf06ef6aa6 Custom layout selector (#243)
* Read layout from "layouts" property

* Support schema version 2

* Support switching custom layouts

* Fix custom touch layouts not working in Remote Play

* Fix game's layout not working when switching between games

* Fix loading custom layout before touch_layout_manager is ready

* Fix game's custom layout not working sometimes

* Set USE_DEV_TOUCH_LAYOUT to false
2024-01-31 07:26:53 +07:00
a90868ef2f Fix not showing custom touch controller layout sometimes 2024-01-30 09:40:07 +07:00
a132a1b1e0 Update README.md 2024-01-29 17:09:46 +07:00
a6c1e5f487 Bump version to 3.1 2024-01-29 16:04:18 +07:00
c3432ea125 Custom touch layout (#238)
* Expose Layout Manager

* Test layout

* Update better-xcloud.user.js

* Fix custom layout not being applied after switching off "Basic controls" setting

* Support custom layout in Remote Play

* Fix bug when showing/hiding touch controller

* Fix Remote Play

* Wait for 1s before displaying custom layout

* Support dev layouts

* Update translations

* Show notes in Settings

* Fix not being able to show touch controller
2024-01-29 15:48:21 +07:00
886a23e5ac Bump version to 3.0.5 2024-01-21 12:27:57 +07:00
309feca3f9 Only set "pointerEvents" property on touch-supported devices 2024-01-21 11:55:17 +07:00
82a9a91534 Fix Clarity Boost warning not working 2024-01-21 11:32:52 +07:00
7248dac3f6 Fix patches 2024-01-21 11:15:09 +07:00
3 changed files with 339 additions and 83 deletions

View File

@ -16,7 +16,8 @@ If you like this project please give it a 🌟. Thank you 🙏.
[![Total stars](https://img.shields.io/github/stars/redphx/better-xcloud?color=%23cca400)](https://github.com/redphx/better-xcloud/stargazers)
## Full documentations
For the full details please visit: https://better-xcloud.github.io
- For the full details please visit: https://better-xcloud.github.io
- [Demo video](https://youtu.be/hyp69Jrb2sQ)
⚠️ Please DO NOT report **Better xCloud**'s bugs on [/r/xcloud subreddit](https://reddit.com/r/xcloud/). Report bugs in [Issues](https://github.com/redphx/better-xcloud/issues) or [Telegram channel](https://t.me/betterxcloud) instead.

View File

@ -1,5 +1,5 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.0.4
// @version 3.1.1
// ==/UserScript==

View File

@ -1,7 +1,7 @@
// ==UserScript==
// @name Better xCloud
// @namespace https://github.com/redphx
// @version 3.0.4
// @version 3.1.1
// @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx
// @license MIT
@ -13,11 +13,12 @@
// ==/UserScript==
'use strict';
const SCRIPT_VERSION = '3.0.4';
const SCRIPT_VERSION = '3.1.1';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const ENABLE_XCLOUD_LOGGER = false;
const ENABLE_PRELOAD_BX_UI = false;
const USE_DEV_TOUCH_LAYOUT = false;
const ENABLE_NATIVE_MKB_BETA = false;
window.NATIVE_MKB_TITLES = [
@ -46,6 +47,8 @@ const BxEvent = {
STREAM_STARTING: 'bx-stream-starting',
STREAM_STARTED: 'bx-stream-started',
STREAM_STOPPED: 'bx-stream-stopped',
CUSTOM_TOUCH_LAYOUTS_LOADED: 'bx-custom-touch-layouts-loaded',
};
// Quickly create a tree of elements without having to use innerHTML
@ -706,6 +709,7 @@ const Translations = {
"en-US": "Deadzone counterweight",
"es-ES": "Contrapeso de la zona muerta",
"ja-JP": "デッドゾーンのカウンターウエイト",
"pl-PL": "Przeciwwaga martwej strefy",
"pt-BR": "Contador da Zona Morta",
"ru-RU": "Противодействие мертвой зоне игры",
"tr-TR": "Ölü alan denge ağırlığı",
@ -1280,6 +1284,17 @@ const Translations = {
"vi-VN": "Nhấn vào để kích hoạt",
"zh-CN": "单击以启用",
},
"mkb-disclaimer": {
"de-DE": "Das Nutzen dieser Funktion beim Online-Spielen könnte als Betrug angesehen werden",
"en-US": "Using this feature when playing online could be viewed as cheating",
"es-ES": "Usar esta función al jugar en línea podría ser visto como trampas",
"ja-JP": "オンラインプレイでこの機能を使用すると不正行為と判定される可能性があります",
"pl-PL": "Używanie tej funkcji podczas grania online może być postrzegane jako oszukiwanie",
"pt-BR": "Usar esta função em jogos online pode ser considerado como uma forma de trapaça",
"ru-RU": "Использование этой функции при игре онлайн может рассматриваться как читерство",
"uk-UA": "Використання цієї функції під час гри онлайн може розглядатися як шахрайство",
"vi-VN": "Sử dụng chức năng này khi chơi trực tuyến có thể bị xem là gian lận",
},
"mouse-and-keyboard": {
"de-DE": "Maus & Tastatur",
"en-US": "Mouse & Keyboard",
@ -2161,6 +2176,7 @@ const Translations = {
"en-US": "Stick decay minimum",
"es-ES": "Disminuir mínimamente el analógico",
"ja-JP": "スティックの減衰の最小値",
"pl-PL": "Minimalne opóźnienie drążka",
"pt-BR": "Mínimo decaimento do analógico",
"ru-RU": "Минимальная перезарядка стика",
"tr-TR": "Çubuğun ortalanma süresi minimumu",
@ -2173,6 +2189,7 @@ const Translations = {
"en-US": "Stick decay strength",
"es-ES": "Intensidad de decaimiento del analógico",
"ja-JP": "スティックの減衰の強さ",
"pl-PL": "Siła opóźnienia drążka",
"pt-BR": "Força de decaimento do analógico",
"ru-RU": "Скорость перезарядки стика",
"tr-TR": "Çubuğun ortalanma gücü",
@ -2217,6 +2234,7 @@ const Translations = {
"en-US": "Support Better xCloud",
"es-ES": "Apoyar a Better xCloud",
"ja-JP": "Better xCloudをサポート",
"pl-PL": "Wesprzyj Better xCloud",
"pt-BR": "Suporte ao Melhor xCloud",
"ru-RU": "Поддержать Better xCloud",
"tr-TR": "Better xCloud'a destek ver",
@ -2744,6 +2762,7 @@ window.addEventListener('load', e => {
});
const NATIVE_FETCH = window.fetch;
const SERVER_REGIONS = {};
var IS_PLAYING = false;
var STREAM_WEBRTC;
@ -2752,9 +2771,12 @@ var STREAM_AUDIO_GAIN_NODE;
var $STREAM_VIDEO;
var $SCREENSHOT_CANVAS;
var GAME_TITLE_ID;
var GAME_XBOX_TITLE_ID;
var GAME_PRODUCT_ID;
var APP_CONTEXT;
window.BX_EXPOSED = {};
let IS_REMOTE_PLAYING;
let REMOTE_PLAY_CONFIG;
@ -2776,6 +2798,8 @@ const Icon = {
REMOTE_PLAY: '<g transform="matrix(.492308 0 0 .581818 -14.7692 -11.6364)"><clipPath id="A"><path d="M30 20h65v55H30z"/></clipPath><g clip-path="url(#A)"><g transform="matrix(.395211 0 0 .334409 11.913 7.01124)"><g transform="matrix(.555556 0 0 .555556 57.8889 -20.2417)" fill="none" stroke="#fff" stroke-width="13.88"><path d="M200 140.564c-42.045-33.285-101.955-33.285-144 0M168 165c-23.783-17.3-56.217-17.3-80 0"/></g><g transform="matrix(-.555556 0 0 -.555556 200.111 262.393)"><g transform="matrix(1 0 0 1 0 11.5642)"><path d="M200 129c-17.342-13.728-37.723-21.795-58.636-24.198C111.574 101.378 80.703 109.444 56 129" fill="none" stroke="#fff" stroke-width="13.88"/></g><path d="M168 165c-23.783-17.3-56.217-17.3-80 0" fill="none" stroke="#fff" stroke-width="13.88"/></g><g transform="matrix(.75 0 0 .75 32 32)"><path d="M24 72h208v93.881H24z" fill="none" stroke="#fff" stroke-linejoin="miter" stroke-width="9.485"/><circle cx="188" cy="128" r="12" stroke-width="10" transform="matrix(.708333 0 0 .708333 71.8333 12.8333)"/><path d="M24.358 103.5h110" fill="none" stroke="#fff" stroke-linecap="butt" stroke-width="10.282"/></g></g></g></g>',
HAND_TAP: '<path d="M6.537 8.906c0-4.216 3.469-7.685 7.685-7.685s7.685 3.469 7.685 7.685M7.719 30.778l-4.333-7.389C3.133 22.944 3 22.44 3 21.928a2.97 2.97 0 0 1 2.956-2.956 2.96 2.96 0 0 1 2.55 1.461l2.761 4.433V8.906a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v8.276a2.97 2.97 0 0 1 2.956-2.956 2.97 2.97 0 0 1 2.956 2.956v2.365a2.97 2.97 0 0 1 2.956-2.956A2.97 2.97 0 0 1 29 19.547v5.32c0 3.547-1.182 5.911-1.182 5.911"/>',
SCREENSHOT_B64: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMjguMzA4IDUuMDM4aC00LjI2NWwtMi4wOTctMy4xNDVhMS4yMyAxLjIzIDAgMCAwLTEuMDIzLS41NDhoLTkuODQ2YTEuMjMgMS4yMyAwIDAgMC0xLjAyMy41NDhMNy45NTYgNS4wMzhIMy42OTJBMy43MSAzLjcxIDAgMCAwIDAgOC43MzF2MTcuMjMxYTMuNzEgMy43MSAwIDAgMCAzLjY5MiAzLjY5MmgyNC42MTVBMy43MSAzLjcxIDAgMCAwIDMyIDI1Ljk2MlY4LjczMWEzLjcxIDMuNzEgMCAwIDAtMy42OTItMy42OTJ6bS02Ljc2OSAxMS42OTJjMCAzLjAzOS0yLjUgNS41MzgtNS41MzggNS41MzhzLTUuNTM4LTIuNS01LjUzOC01LjUzOCAyLjUtNS41MzggNS41MzgtNS41MzggNS41MzggMi41IDUuNTM4IDUuNTM4eiIvPjwvc3ZnPgo=',
};
@ -3103,6 +3127,7 @@ class TitlesInfo {
const details = titleInfo.details;
TitlesInfo.update(details.productId, {
titleId: titleInfo.titleId,
xboxTitleId: details.xboxTitleId,
// Has more than one input type -> must have touch support
hasTouchSupport: (details.supportedInputTypes.length > 1),
});
@ -3319,7 +3344,7 @@ class LoadingScreen {
class TouchController {
static get #EVENT_SHOW_CONTROLLER() {
static get #EVENT_SHOW_DEFAULT_CONTROLLER() {
return new MessageEvent('message', {
data: '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message"}',
origin: 'better-xcloud',
@ -3340,6 +3365,9 @@ class TouchController {
static #showing = false;
static #dataChannel;
static #customLayouts = {};
static #currentLayoutId;
static enable() {
TouchController.#enable = true;
}
@ -3352,8 +3380,13 @@ class TouchController {
return TouchController.#enable;
}
static #showDefault() {
TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_DEFAULT_CONTROLLER);
TouchController.#showing = true;
}
static #show() {
TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_CONTROLLER);
TouchController.loadCustomLayout(GAME_XBOX_TITLE_ID, TouchController.#currentLayoutId, 0);
TouchController.#showing = true;
}
@ -3389,6 +3422,86 @@ class TouchController {
}, 10);
}
static getCustomLayouts(xboxTitleId) {
const dispatchLayouts = data => {
const event = new Event(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED);
event.data = data;
window.dispatchEvent(event);
};
xboxTitleId = '' + xboxTitleId;
if (xboxTitleId in TouchController.#customLayouts) {
dispatchLayouts(TouchController.#customLayouts[xboxTitleId]);
return;
}
let url = 'https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts/';
if (USE_DEV_TOUCH_LAYOUT) {
url += `dev/${xboxTitleId}.json`;
} else {
url += `${xboxTitleId}.json`;
}
NATIVE_FETCH(url)
.then(resp => resp.json(), () => {
TouchController.#customLayouts[xboxTitleId] = null;
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout(() => dispatchLayouts(null), 1000);
})
.then(json => {
// Normalize data
const schema_version = json.schema_version || 1;
let layout;
try {
if (schema_version === 1) {
json.layouts = {
default: {
name: 'Default',
content: json.layout,
},
};
json.default_layout = 'default';
delete json.layout;
}
} catch (e) {}
TouchController.#customLayouts[xboxTitleId] = json;
// Wait for BX_EXPOSED.touch_layout_manager
setTimeout(() => dispatchLayouts(json), 1000);
});
}
static loadCustomLayout(xboxTitleId, layoutId, delay) {
if (!window.BX_EXPOSED.touch_layout_manager) {
return;
}
TouchController.#currentLayoutId = layoutId;
xboxTitleId = '' + xboxTitleId;
const layoutData = TouchController.#customLayouts[xboxTitleId];
if (!xboxTitleId || !layoutId || !layoutData) {
TouchController.#enable && TouchController.#showDefault();
return;
}
const layout = (layoutData.layouts[layoutId] || layoutData.layouts[layoutData.default_layout]);
layout && setTimeout(() => {
window.BX_EXPOSED.touch_layout_manager.changeLayoutForScope({
type: 'showLayout',
scope: xboxTitleId,
subscope: 'base',
layout: {
id: 'System.Standard',
displayName: 'System',
layoutFile: {
content: layout.content,
},
}
});
}, delay);
}
static setup() {
const $style = document.createElement('style');
document.documentElement.appendChild($style);
@ -3421,7 +3534,7 @@ class TouchController {
const nativeCreateDataChannel = RTCPeerConnection.prototype.createDataChannel;
RTCPeerConnection.prototype.createDataChannel = function() {
const dataChannel = nativeCreateDataChannel.apply(this, arguments);
if (!TouchController.#enable || dataChannel.label !== 'message') {
if (dataChannel.label !== 'message') {
return dataChannel;
}
@ -3455,8 +3568,18 @@ class TouchController {
// Dispatch a message to display generic touch controller
if (msg.data.includes('touchcontrols/showtitledefault')) {
TouchController.#show();
TouchController.#enable && TouchController.getCustomLayouts(GAME_XBOX_TITLE_ID);
return;
}
// Load custom touch layout
try {
if (msg.data.includes('/titleinfo')) {
const json = JSON.parse(JSON.parse(msg.data).content);
const xboxTitleId = parseInt(json.titleid, 16);
GAME_XBOX_TITLE_ID = xboxTitleId;
}
} catch (e) { console.log(e) }
});
return dataChannel;
@ -6209,7 +6332,8 @@ class Preferences {
'ready': () => {
const options = Preferences.SETTINGS[Preferences.STREAM_CODEC_PROFILE].options;
if (Object.keys(options).length <= 1) {
Preferences.SETTINGS[Preferences.STREAM_CODEC_PROFILE].unsupported = __('browser-unsupported-feature');
Preferences.SETTINGS[Preferences.STREAM_CODEC_PROFILE].unsupported = true;
Preferences.SETTINGS[Preferences.STREAM_CODEC_PROFILE].note = '⚠️ ' + __('browser-unsupported-feature');
}
},
},
@ -6237,7 +6361,7 @@ class Preferences {
'all': __('tc-all-games'),
'off': __('off'),
},
'unsupported': !HAS_TOUCH_SUPPORT ? __('device-unsupported-touch') : false,
'unsupported': !HAS_TOUCH_SUPPORT,
},
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: {
'default': 'default',
@ -6246,7 +6370,7 @@ class Preferences {
'white': __('tc-all-white'),
'muted': __('tc-muted-colors'),
},
'unsupported': !HAS_TOUCH_SUPPORT ? __('device-unsupported-touch') : false,
'unsupported': !HAS_TOUCH_SUPPORT,
},
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: {
'default': 'default',
@ -6254,7 +6378,7 @@ class Preferences {
'default': __('default'),
'muted': __('tc-muted-colors'),
},
'unsupported': !HAS_TOUCH_SUPPORT ? __('device-unsupported-touch') : false,
'unsupported': !HAS_TOUCH_SUPPORT,
},
[Preferences.STREAM_SIMPLIFY_MENU]: {
'default': false,
@ -6301,6 +6425,11 @@ class Preferences {
const userAgent = (window.navigator.orgUserAgent || window.navigator.userAgent || '').toLowerCase();
return userAgent.match(/(android|iphone|ipad)/) ? __('browser-unsupported-feature') : false;
})(),
'ready': () => {
const pref = Preferences.SETTINGS[Preferences.MKB_ENABLED];
const note = __(pref.unsupported ? 'browser-unsupported-feature' : 'mkb-disclaimer');
Preferences.SETTINGS[Preferences.MKB_ENABLED].note = '⚠️ ' + note;
},
},
[Preferences.MKB_DEFAULT_PRESET_ID]: {
@ -6707,7 +6836,7 @@ class Patcher {
funcStr = funcStr.replace('onServerDisconnectMessage(e){', `onServerDisconnectMessage(e) {
const msg = JSON.parse(e);
if (msg.reason === 'WarningForBeingIdle') {
if (msg.reason === 'WarningForBeingIdle' && !window.location.pathname.includes('/launch/')) {
try {
this.sendKeepAlive();
return;
@ -6808,7 +6937,7 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
];
// Enable native Mouse and Keyboard support
if (getPref(Preferences.MKB_ENABLED)) {
if (ENABLE_NATIVE_MKB_BETA) {
newSettings.push('EnableMouseAndKeyboard: true');
newSettings.push('ShowMouseKeyboardSetting: true');
@ -6888,6 +7017,16 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
funcStr = funcStr.substring(0, bracketIndex) + 'return 0;' + funcStr.substring(bracketIndex);
return funcStr;
},
exposeTouchLayoutManager: function(funcStr) {
const text = 'this._perScopeLayoutsStream=new';
if (!funcStr.includes(text)) {
return false;
}
funcStr = funcStr.replace(text, 'window.BX_EXPOSED["touch_layout_manager"] = this,' + text);
return funcStr;
},
};
static #PATCH_ORDERS = [
@ -6900,17 +7039,25 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
getPref(Preferences.UI_LAYOUT) === 'tv' && ['tvLayout'],
ENABLE_XCLOUD_LOGGER && ['enableXcloudLogger'],
ENABLE_XCLOUD_LOGGER && [
'enableXcloudLogger',
'enableConsoleLogging',
],
getPref(Preferences.BLOCK_TRACKING) && [
'disableTrackEvent',
'blockWebRtcStatsCollector',
],
getPref(Preferences.REMOTE_PLAY_ENABLED) && [
'remotePlayDirectConnectUrl',
'remotePlayKeepAlive',
],
[
'overrideSettings',
getPref(Preferences.REMOTE_PLAY_ENABLED) && 'remotePlayDirectConnectUrl',
getPref(Preferences.BLOCK_TRACKING) && 'disableTrackEvent',
ENABLE_NATIVE_MKB_BETA && 'mkbIsMouseAndKeyboardTitle',
HAS_TOUCH_SUPPORT && 'patchUpdateInputConfigurationAsync',
ENABLE_NATIVE_MKB_BETA && getPref(Preferences.MKB_ENABLED) && 'mkbIsMouseAndKeyboardTitle',
ENABLE_XCLOUD_LOGGER && 'enableConsoleLogging',
getPref(Preferences.REMOTE_PLAY_ENABLED) && 'remotePlayKeepAlive',
getPref(Preferences.BLOCK_TRACKING) && 'blockWebRtcStatsCollector',
],
];
@ -6919,12 +7066,13 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
getPref(Preferences.REMOTE_PLAY_ENABLED) && ['remotePlayConnectMode'],
['playVibration'],
getPref(Preferences.STREAM_TOUCH_CONTROLLER) === 'all' && ['exposeTouchLayoutManager'],
ENABLE_XCLOUD_LOGGER && ['enableConsoleLogging'],
[
'disableGamepadDisconnectedScreen',
ENABLE_NATIVE_MKB_BETA && getPref(Preferences.MKB_ENABLED) && 'mkbMouseAndKeyboardEnabled',
ENABLE_NATIVE_MKB_BETA && 'mkbMouseAndKeyboardEnabled',
],
];
@ -7027,6 +7175,10 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
static #cleanupPatches() {
for (let groupIndex = Patcher.#PATCH_ORDERS.length - 1; groupIndex >= 0; groupIndex--) {
const group = Patcher.#PATCH_ORDERS[groupIndex];
if (group === false) {
Patcher.#PATCH_ORDERS.splice(groupIndex, 1);
continue;
}
for (let patchIndex = group.length - 1; patchIndex >= 0; patchIndex--) {
const patchName = group[patchIndex];
@ -7830,11 +7982,11 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
overflow: hidden;
}
.bx-quick-settings-tab-contents div:not([data-clarity-boost="true"]) .bx-clarity-boost-warning {
.bx-quick-settings-bar:not([data-clarity-boost="true"]) .bx-clarity-boost-warning {
display: none;
}
.bx-quick-settings-tab-contents div[data-clarity-boost="true"] .bx-clarity-boost-warning {
.bx-quick-settings-bar[data-clarity-boost="true"] .bx-clarity-boost-warning {
display: block;
margin: 0px 8px;
padding: 12px;
@ -7844,7 +7996,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
border-radius: 4px;
}
.bx-quick-settings-tab-contents div[data-clarity-boost="true"] > div[data-type="video"] {
.bx-quick-settings-bar[data-clarity-boost="true"] div[data-type="video"] {
display: none;
}
@ -8591,8 +8743,6 @@ function interceptHttpRequests() {
const PREF_STREAM_TOUCH_CONTROLLER = getPref(Preferences.STREAM_TOUCH_CONTROLLER);
const PREF_AUDIO_MIC_ON_PLAYING = getPref(Preferences.AUDIO_MIC_ON_PLAYING);
const orgFetch = window.fetch;
const consoleAddrs = {};
const patchIceCandidates = function(...arg) {
@ -8601,7 +8751,7 @@ function interceptHttpRequests() {
const url = (typeof request === 'string') ? request : request.url;
if (url && url.endsWith('/ice') && url.includes('/sessions/') && request.method === 'GET') {
const promise = orgFetch(...arg);
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().text().then(text => {
@ -8640,7 +8790,7 @@ function interceptHttpRequests() {
}
if (IS_REMOTE_PLAYING && (url.includes('/sessions/home') || url.includes('inputconfigs'))) {
TouchController.enable();
TouchController.disable();
const clone = request.clone();
@ -8676,7 +8826,7 @@ function interceptHttpRequests() {
// Get console IP
if (url.includes('/configuration')) {
const promise = orgFetch(...arg);
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(obj => {
@ -8697,9 +8847,34 @@ function interceptHttpRequests() {
return response;
});
});
} else if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && url.includes('inputconfigs')) {
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(obj => {
if (obj[0].supportedTabs.length > 0) {
TouchController.disable();
const event = new Event(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED);
event.data = null;
window.dispatchEvent(event);
} else {
TouchController.enable();
const xboxTitleId = JSON.parse(opts.body).titleIds[0];
GAME_XBOX_TITLE_ID = xboxTitleId;
TouchController.getCustomLayouts(xboxTitleId);
}
return patchIceCandidates(...arg) || orgFetch(...arg);
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
return response;
});
});
}
return patchIceCandidates(...arg) || NATIVE_FETCH(...arg);
}
if (IS_REMOTE_PLAYING && url.includes('/login/user')) {
@ -8723,7 +8898,7 @@ function interceptHttpRequests() {
console.log(e);
}
return orgFetch(...arg);
return NATIVE_FETCH(...arg);
}
if (IS_REMOTE_PLAYING && url.includes('/titles')) {
@ -8743,7 +8918,7 @@ function interceptHttpRequests() {
});
arg[0] = request;
return orgFetch(...arg);
return NATIVE_FETCH(...arg);
}
// ICE server candidates
@ -8754,7 +8929,7 @@ function interceptHttpRequests() {
// Server list
if (!url.includes('xhome.') && url.endsWith('/v2/login/user')) {
const promise = orgFetch(...arg);
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(obj => {
@ -8829,12 +9004,12 @@ function interceptHttpRequests() {
});
arg[0] = newRequest;
return orgFetch(...arg);
return NATIVE_FETCH(...arg);
}
// Get wait time
if (PREF_UI_LOADING_SCREEN_WAIT_TIME && url.includes('xboxlive.com') && url.includes('/waittime/')) {
const promise = orgFetch(...arg);
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(json => {
if (json.estimatedAllocationTimeInSeconds > 0) {
@ -8850,7 +9025,7 @@ function interceptHttpRequests() {
if (url.endsWith('/configuration') && url.includes('/sessions/cloud/') && request.method === 'GET') {
PREF_UI_LOADING_SCREEN_GAME_ART && LoadingScreen.hide();
const promise = orgFetch(...arg);
const promise = NATIVE_FETCH(...arg);
// Touch controller for all games
if (PREF_STREAM_TOUCH_CONTROLLER === 'all') {
@ -8878,7 +9053,7 @@ function interceptHttpRequests() {
overrides.inputConfiguration = overrides.inputConfiguration || {};
overrides.inputConfiguration.enableVibration = true;
if (ENABLE_NATIVE_MKB_BETA) {
overrides.inputConfiguration.enableMouseAndKeyboard = getPref(Preferences.MKB_ENABLED);
overrides.inputConfiguration.enableMouseAndKeyboard = true;
}
// Enable touch controller
@ -8905,7 +9080,7 @@ function interceptHttpRequests() {
// catalog.gamepass
if (url.startsWith('https://catalog.gamepass.com') && url.includes('/products')) {
const promise = orgFetch(...arg);
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(json => {
for (let productId in json.Products) {
@ -8917,8 +9092,8 @@ function interceptHttpRequests() {
});
}
if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.endsWith('/titles') || url.endsWith('/mru'))) {
const promise = orgFetch(...arg);
if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.includes('/titles') || url.includes('/mru'))) {
const promise = NATIVE_FETCH(...arg);
return promise.then(response => {
return response.clone().json().then(json => {
for (let game of json.results) {
@ -8941,7 +9116,7 @@ function interceptHttpRequests() {
});
}
return orgFetch(...arg);
return NATIVE_FETCH(...arg);
}
}
@ -9057,6 +9232,7 @@ function injectSettingsButton($parent) {
},
*/
[__('touch-controller')]: {
_note: !HAS_TOUCH_SUPPORT ? '⚠️ ' + __('device-unsupported-touch') : null,
[Preferences.STREAM_TOUCH_CONTROLLER]: __('tc-availability'),
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD]: __('tc-standard-layout-style'),
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: __('tc-custom-layout-style'),
@ -9102,14 +9278,8 @@ function injectSettingsButton($parent) {
const setting = Preferences.SETTINGS[settingId];
let settingLabel;
let settingNote;
if (Array.isArray(SETTINGS_UI[groupLabel][settingId])) {
[settingLabel, settingNote] = SETTINGS_UI[groupLabel][settingId];
} else {
settingLabel = SETTINGS_UI[groupLabel][settingId];
}
const settingLabel = SETTINGS_UI[groupLabel][settingId];
const settingNote = setting.note;
let $control, $inpCustomUserAgent;
let labelAttrs = {};
@ -9180,11 +9350,8 @@ function injectSettingsButton($parent) {
// Disable unsupported settings
if (setting.unsupported) {
$control.disabled = true;
$control.title = setting.unsupported;
}
$control.disabled && ($control.style.cursor = 'help');
const $label = CE('label', labelAttrs, settingLabel);
if (settingNote) {
$label.appendChild(CE('b', {}, settingNote));
@ -9384,8 +9551,10 @@ function cloneStreamHudButton($orgButton, label, svg_icon) {
}
};
if (HAS_TOUCH_SUPPORT) {
$container.addEventListener('transitionstart', onTransitionStart);
$container.addEventListener('transitionend', onTransitionEnd);
}
const $button = $container.querySelector('button');
$button.setAttribute('title', label);
@ -9719,8 +9888,9 @@ function setupQuickSettingsBar() {
group: 'audio',
label: __('audio'),
help_url: 'https://better-xcloud.github.io/ingame-features/#audio',
items: {
[Preferences.AUDIO_VOLUME]: {
items: [
{
pref: Preferences.AUDIO_VOLUME,
label: __('volume'),
onChange: (e, value) => {
STREAM_AUDIO_GAIN_NODE && (STREAM_AUDIO_GAIN_NODE.gain.value = (value / 100).toFixed(2));
@ -9729,7 +9899,7 @@ function setupQuickSettingsBar() {
disabled: !getPref(Preferences.AUDIO_ENABLE_VOLUME_CONTROL),
},
},
},
],
},
{
@ -9737,33 +9907,38 @@ function setupQuickSettingsBar() {
label: __('video'),
help_url: 'https://better-xcloud.github.io/ingame-features/#video',
note: CE('div', {'class': 'bx-quick-settings-bar-note bx-clarity-boost-warning'}, `⚠️ ${__('clarity-boost-warning')}`),
items: {
[Preferences.VIDEO_RATIO]: {
items: [
{
pref: Preferences.VIDEO_RATIO,
label: __('ratio'),
onChange: updateVideoPlayerCss,
},
[Preferences.VIDEO_CLARITY]: {
{
pref: Preferences.VIDEO_CLARITY,
label: __('clarity'),
onChange: updateVideoPlayerCss,
unsupported: isSafari,
},
[Preferences.VIDEO_SATURATION]: {
{
pref: Preferences.VIDEO_SATURATION,
label: __('saturation'),
onChange: updateVideoPlayerCss,
},
[Preferences.VIDEO_CONTRAST]: {
{
pref: Preferences.VIDEO_CONTRAST,
label: __('contrast'),
onChange: updateVideoPlayerCss,
},
[Preferences.VIDEO_BRIGHTNESS]: {
{
pref: Preferences.VIDEO_BRIGHTNESS,
label: __('brightness'),
onChange: updateVideoPlayerCss,
},
},
],
},
],
},
@ -9776,27 +9951,89 @@ function setupQuickSettingsBar() {
group: 'controller',
label: __('controller'),
help_url: 'https://better-xcloud.github.io/ingame-features/#controller',
items: {
[Preferences.CONTROLLER_ENABLE_VIBRATION]: {
items: [
{
pref: Preferences.CONTROLLER_ENABLE_VIBRATION,
label: __('controller-vibration'),
unsupported: !VibrationManager.supportControllerVibration(),
onChange: VibrationManager.updateGlobalVars,
},
[Preferences.CONTROLLER_DEVICE_VIBRATION]: {
{
pref: Preferences.CONTROLLER_DEVICE_VIBRATION,
label: __('device-vibration'),
unsupported: !VibrationManager.supportDeviceVibration(),
onChange: VibrationManager.updateGlobalVars,
},
[Preferences.CONTROLLER_VIBRATION_INTENSITY]: (VibrationManager.supportControllerVibration() || VibrationManager.supportDeviceVibration()) && {
(VibrationManager.supportControllerVibration() || VibrationManager.supportDeviceVibration()) && {
pref: Preferences.CONTROLLER_VIBRATION_INTENSITY,
label: __('vibration-intensity'),
unsupported: !VibrationManager.supportDeviceVibration(),
onChange: VibrationManager.updateGlobalVars,
},
],
},
],
},
HAS_TOUCH_SUPPORT && {
icon: Icon.HAND_TAP,
group: 'touch-controller',
items: [
{
group: 'touch-controller',
label: __('touch-controller'),
items: [
{
label: __('layout'),
content: CE('select', {disabled: true}, CE('option', {}, __('default'))),
onMounted: $elm => {
$elm.addEventListener('change', e => {
TouchController.loadCustomLayout(GAME_XBOX_TITLE_ID, $elm.value, 1000);
});
window.addEventListener(BxEvent.CUSTOM_TOUCH_LAYOUTS_LOADED, e => {
const data = e.data;
if (GAME_XBOX_TITLE_ID && $elm.xboxTitleId === GAME_XBOX_TITLE_ID) {
$elm.dispatchEvent(new Event('change'));
return;
}
$elm.xboxTitleId = GAME_XBOX_TITLE_ID;
// Clear options
while ($elm.firstChild) {
$elm.removeChild($elm.firstChild);
}
$elm.disabled = !data;
if (!data) {
$elm.appendChild(CE('option', {value: ''}, __('default')));
$elm.value = '';
$elm.dispatchEvent(new Event('change'));
return;
}
// Add options
const $fragment = document.createDocumentFragment();
for (const key in data.layouts) {
const layout = data.layouts[key];
const $option = CE('option', {value: key}, layout.name);
$fragment.appendChild($option);
}
$elm.appendChild($fragment);
$elm.value = data.default_layout;
$elm.dispatchEvent(new Event('change'));
});
},
},
],
}
],
},
{
@ -9807,41 +10044,49 @@ function setupQuickSettingsBar() {
group: 'stats',
label: __('menu-stream-stats'),
help_url: 'https://better-xcloud.github.io/stream-stats/',
items: {
[Preferences.STATS_SHOW_WHEN_PLAYING]: {
items: [
{
pref: Preferences.STATS_SHOW_WHEN_PLAYING,
label: __('show-stats-on-startup'),
},
[Preferences.STATS_QUICK_GLANCE]: {
{
pref: Preferences.STATS_QUICK_GLANCE,
label: __('enable-quick-glance-mode'),
onChange: e => {
e.target.checked ? StreamStats.quickGlanceSetup() : StreamStats.quickGlanceStop();
},
},
[Preferences.STATS_ITEMS]: {
{
pref: Preferences.STATS_ITEMS,
label: __('stats'),
onChange: StreamStats.refreshStyles,
},
[Preferences.STATS_POSITION]: {
{
pref: Preferences.STATS_POSITION,
label: __('position'),
onChange: StreamStats.refreshStyles,
},
[Preferences.STATS_TEXT_SIZE]: {
{
pref: Preferences.STATS_TEXT_SIZE,
label: __('text-size'),
onChange: StreamStats.refreshStyles,
},
[Preferences.STATS_OPACITY]: {
{
pref: Preferences.STATS_OPACITY,
label: __('opacity'),
onChange: StreamStats.refreshStyles,
},
[Preferences.STATS_TRANSPARENT]: {
{
pref: Preferences.STATS_TRANSPARENT,
label: __('transparent-background'),
onChange: StreamStats.refreshStyles,
},
[Preferences.STATS_CONDITIONAL_FORMATTING]: {
{
pref: Preferences.STATS_CONDITIONAL_FORMATTING,
label: __('conditional-formatting'),
onChange: StreamStats.refreshStyles,
},
},
],
},
],
},
@ -9916,14 +10161,21 @@ function setupQuickSettingsBar() {
continue;
}
for (const pref in settingGroup.items) {
const setting = settingGroup.items[pref];
if (!settingGroup.items) {
settingGroup.items = [];
}
for (const setting of settingGroup.items) {
if (!setting) {
continue;
}
const pref = setting.pref;
let $control;
if (!setting.unsupported) {
if (setting.content) {
$control = setting.content;
} else if (!setting.unsupported) {
$control = PREFS.toElement(pref, setting.onChange, setting.params);
}
@ -9936,6 +10188,8 @@ function setupQuickSettingsBar() {
);
$group.appendChild($content);
setting.onMounted && setting.onMounted($control);
}
}
@ -10088,6 +10342,7 @@ function onStreamStarted($video) {
GAME_PRODUCT_ID = matches.groups.product_id;
} else {
GAME_TITLE_ID = 'remote-play';
GAME_PRODUCT_ID = null;
}
// Enable MKB