diff --git a/better-xcloud.user.js b/better-xcloud.user.js
index a478845..028655a 100644
--- a/better-xcloud.user.js
+++ b/better-xcloud.user.js
@@ -47,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
@@ -2796,6 +2798,8 @@ const Icon = {
REMOTE_PLAY: '',
+ HAND_TAP: '',
+
SCREENSHOT_B64: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMjguMzA4IDUuMDM4aC00LjI2NWwtMi4wOTctMy4xNDVhMS4yMyAxLjIzIDAgMCAwLTEuMDIzLS41NDhoLTkuODQ2YTEuMjMgMS4yMyAwIDAgMC0xLjAyMy41NDhMNy45NTYgNS4wMzhIMy42OTJBMy43MSAzLjcxIDAgMCAwIDAgOC43MzF2MTcuMjMxYTMuNzEgMy43MSAwIDAgMCAzLjY5MiAzLjY5MmgyNC42MTVBMy43MSAzLjcxIDAgMCAwIDMyIDI1Ljk2MlY4LjczMWEzLjcxIDMuNzEgMCAwIDAtMy42OTItMy42OTJ6bS02Ljc2OSAxMS42OTJjMCAzLjAzOS0yLjUgNS41MzgtNS41MzggNS41MzhzLTUuNTM4LTIuNS01LjUzOC01LjUzOCAyLjUtNS41MzggNS41MzgtNS41MzggNS41MzggMi41IDUuNTM4IDUuNTM4eiIvPjwvc3ZnPgo=',
};
@@ -3362,6 +3366,7 @@ class TouchController {
static #dataChannel;
static #customLayouts = {};
+ static #currentLayoutId;
static enable() {
TouchController.#enable = true;
@@ -3381,7 +3386,7 @@ class TouchController {
}
static #show() {
- TouchController.loadCustomLayout(GAME_XBOX_TITLE_ID, 0);
+ TouchController.loadCustomLayout(GAME_XBOX_TITLE_ID, TouchController.#currentLayoutId, 0);
TouchController.#showing = true;
}
@@ -3417,10 +3422,16 @@ class TouchController {
}, 10);
}
- static #getCustomLayout(xboxTitleId, callback) {
+ 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) {
- callback(TouchController.#customLayouts[xboxTitleId]);
+ dispatchLayouts(TouchController.#customLayouts[xboxTitleId]);
return;
}
@@ -3430,43 +3441,65 @@ class TouchController {
} else {
url += `${xboxTitleId}.json`;
}
- window.BX_EXPOSED.touch_layout_manager && NATIVE_FETCH(url)
+ NATIVE_FETCH(url)
.then(resp => resp.json(), () => {
TouchController.#customLayouts[xboxTitleId] = null;
- callback(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;
- callback(json);
+
+ // Wait for BX_EXPOSED.touch_layout_manager
+ setTimeout(() => dispatchLayouts(json), 1000);
});
}
- static loadCustomLayout(xboxTitleId, delay) {
+ static loadCustomLayout(xboxTitleId, layoutId, delay) {
if (!window.BX_EXPOSED.touch_layout_manager) {
return;
}
+ TouchController.#currentLayoutId = layoutId;
xboxTitleId = '' + xboxTitleId;
- TouchController.#getCustomLayout(xboxTitleId, json => {
- if (!json) {
- return;
- }
- setTimeout(() => {
- window.BX_EXPOSED.touch_layout_manager.changeLayoutForScope({
- type: 'showLayout',
- scope: xboxTitleId,
- subscope: 'base',
- layout: {
- id: 'System.Standard',
- displayName: 'System',
- layoutFile: {
- content: json.layout,
- },
- }
- });
- }, delay);
+ 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() {
@@ -3501,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;
}
@@ -3533,27 +3566,20 @@ class TouchController {
return;
}
+ // Dispatch a message to display generic touch controller
+ if (msg.data.includes('touchcontrols/showtitledefault')) {
+ 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);
- if (json.focused) {
- const xboxTitleId = parseInt(json.titleid, 16);
- GAME_XBOX_TITLE_ID = xboxTitleId;
- TouchController.loadCustomLayout(xboxTitleId, 1000);
- } else {
- GAME_XBOX_TITLE_ID = null;
- }
-
- return;
+ const xboxTitleId = parseInt(json.titleid, 16);
+ GAME_XBOX_TITLE_ID = xboxTitleId;
}
} catch (e) { console.log(e) }
-
-
- // Dispatch a message to display generic touch controller
- if (msg.data.includes('touchcontrols/showtitledefault')) {
- TouchController.#show();
- }
});
return dataChannel;
@@ -8764,7 +8790,7 @@ function interceptHttpRequests() {
}
if (IS_REMOTE_PLAYING && (url.includes('/sessions/home') || url.includes('inputconfigs'))) {
- TouchController.enable();
+ TouchController.disable();
const clone = request.clone();
@@ -8818,6 +8844,31 @@ function interceptHttpRequests() {
response.json = () => Promise.resolve(obj);
response.text = () => Promise.resolve(JSON.stringify(obj));
+ 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);
+ }
+
+ response.json = () => Promise.resolve(obj);
+ response.text = () => Promise.resolve(JSON.stringify(obj));
+
return response;
});
});
@@ -9041,7 +9092,7 @@ function interceptHttpRequests() {
});
}
- if (PREF_STREAM_TOUCH_CONTROLLER === 'all' && (url.endsWith('/titles') || url.endsWith('/mru'))) {
+ 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 => {
@@ -9837,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));
@@ -9847,7 +9899,7 @@ function setupQuickSettingsBar() {
disabled: !getPref(Preferences.AUDIO_ENABLE_VOLUME_CONTROL),
},
},
- },
+ ],
},
{
@@ -9855,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,
},
- },
+ ],
},
],
},
@@ -9894,29 +9951,91 @@ 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'));
+ });
+ },
+ },
+ ],
+ }
+ ],
+ },
+
{
icon: Icon.STREAM_STATS,
group: 'stats',
@@ -9925,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,
},
- },
+ ],
},
],
},
@@ -10034,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);
}
@@ -10054,6 +10188,8 @@ function setupQuickSettingsBar() {
);
$group.appendChild($content);
+
+ setting.onMounted && setting.onMounted($control);
}
}