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); } }