Compare commits

..

20 Commits

Author SHA1 Message Date
83a8e1f847 Bump version to 3.0.3 2024-01-13 17:14:15 +07:00
5ccd04478d Reduce the chance that other Webpack extensions can cause the Patcher class not to work 2024-01-13 16:38:04 +07:00
da2a3c87bc Update Remote Play's CSS 2024-01-13 15:49:03 +07:00
a8996fe3a5 Fix minor CSS 2024-01-13 15:26:32 +07:00
89a5bbdd2e Fix strafing doesn't work (#216) 2024-01-13 15:15:50 +07:00
58f8f6e762 Update translations 2024-01-13 14:45:42 +07:00
401f8def06 Cut off button's label when it's too long 2024-01-12 18:30:49 +07:00
f560c225de Show stat's full name when hovering on it 2024-01-12 18:12:59 +07:00
d6b254b134 Don't show "0.00%" on stats bar 2024-01-12 17:57:33 +07:00
36a6259817 Put MKB settings before Touch Controller settings 2024-01-12 17:49:16 +07:00
76947a39de Update styling of the Connect button in the Remote Play dialog 2024-01-12 17:43:04 +07:00
151b87fb69 Change button's height from 32px to 36px 2024-01-12 17:14:35 +07:00
0a6bd5b763 Fix layout on small screen devices 2024-01-12 17:02:50 +07:00
477989d542 Update styling of buttons in header 2024-01-12 16:46:19 +07:00
15eaf76042 Add ButtonStyle enum 2024-01-12 16:03:45 +07:00
a495d3147b Update styling of Stream Settings 2024-01-12 15:28:27 +07:00
2fb61c1c44 Truncate long headers in Stream Settings dialog (#215) 2024-01-09 17:31:30 +07:00
0c45a7705d Bump version to 3.0.2.1 2024-01-08 20:40:46 +07:00
5655c5f3b6 Update styling of dialog 2024-01-08 20:36:55 +07:00
2a6713a038 Move the "Remote Play" button to header 2024-01-08 20:30:23 +07:00
2 changed files with 237 additions and 213 deletions

View File

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

View File

@ -1,7 +1,7 @@
// ==UserScript== // ==UserScript==
// @name Better xCloud // @name Better xCloud
// @namespace https://github.com/redphx // @namespace https://github.com/redphx
// @version 3.0.2 // @version 3.0.3
// @description Improve Xbox Cloud Gaming (xCloud) experience // @description Improve Xbox Cloud Gaming (xCloud) experience
// @author redphx // @author redphx
// @license MIT // @license MIT
@ -13,7 +13,7 @@
// ==/UserScript== // ==/UserScript==
'use strict'; 'use strict';
const SCRIPT_VERSION = '3.0.2'; const SCRIPT_VERSION = '3.0.3';
const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud'; const SCRIPT_HOME = 'https://github.com/redphx/better-xcloud';
const ENABLE_XCLOUD_LOGGER = false; const ENABLE_XCLOUD_LOGGER = false;
@ -110,13 +110,27 @@ const createSvgIcon = (icon, strokeWidth=2) => {
return $svg; return $svg;
}; };
const ButtonStyle = {};
ButtonStyle[ButtonStyle.PRIMARY = 1] = 'bx-primary';
ButtonStyle[ButtonStyle.DANGER = 2] = 'bx-danger';
ButtonStyle[ButtonStyle.GHOST = 4] = 'bx-ghost';
ButtonStyle[ButtonStyle.FOCUSABLE = 8] = 'bx-focusable';
ButtonStyle[ButtonStyle.FULL_WIDTH = 16] = 'bx-full-width';
ButtonStyle[ButtonStyle.FULL_HEIGHT = 32] = 'bx-full-height';
const ButtonStyleIndices = Object.keys(ButtonStyle).splice(0, Object.keys(ButtonStyle).length / 2).map(i => parseInt(i));
const createButton = options => { const createButton = options => {
const $btn = CE(options.url ? 'a' : 'button', {'class': 'bx-button'}); const $btn = CE(options.url ? 'a' : 'button', {'class': 'bx-button'});
options.isPrimary && $btn.classList.add('bx-primary'); const style = options.style || 0;
options.isDanger && $btn.classList.add('bx-danger'); style && ButtonStyleIndices.forEach(index => {
options.isGhost && $btn.classList.add('bx-ghost'); (style & index) && $btn.classList.add(ButtonStyle[index]);
});
options.classes && $btn.classList.add(...options.classes);
options.icon && $btn.appendChild(createSvgIcon(options.icon, 4)); options.icon && $btn.appendChild(createSvgIcon(options.icon, 4));
options.label && $btn.appendChild(CE('span', {}, options.label)); options.label && $btn.appendChild(CE('span', {}, options.label));
options.title && $btn.setAttribute('title', options.title); options.title && $btn.setAttribute('title', options.title);
@ -181,6 +195,7 @@ const Translations = {
"tr-TR": "Etkinleştir", "tr-TR": "Etkinleştir",
"uk-UA": "Активувати", "uk-UA": "Активувати",
"vi-VN": "Kích hoạt", "vi-VN": "Kích hoạt",
"zh-CN": "启用",
}, },
"activated": { "activated": {
"de-DE": "Aktiviert", "de-DE": "Aktiviert",
@ -194,6 +209,7 @@ const Translations = {
"tr-TR": "Etkin", "tr-TR": "Etkin",
"uk-UA": "Активований", "uk-UA": "Активований",
"vi-VN": "Đã kích hoạt", "vi-VN": "Đã kích hoạt",
"zh-CN": "已启用",
}, },
"active": { "active": {
"de-DE": "Aktiv", "de-DE": "Aktiv",
@ -235,6 +251,7 @@ const Translations = {
"tr-TR": "Uygula", "tr-TR": "Uygula",
"uk-UA": "Застосувати", "uk-UA": "Застосувати",
"vi-VN": "Áp dụng", "vi-VN": "Áp dụng",
"zh-CN": "应用",
}, },
"audio": { "audio": {
"de-DE": "Audio", "de-DE": "Audio",
@ -425,7 +442,7 @@ const Translations = {
"ru-RU": "Яркость", "ru-RU": "Яркость",
"tr-TR": "Aydınlık", "tr-TR": "Aydınlık",
"uk-UA": "Яскравість", "uk-UA": "Яскравість",
"vi-VN": ộ sáng", "vi-VN": ộ sáng",
"zh-CN": "亮度", "zh-CN": "亮度",
}, },
"browser-unsupported-feature": { "browser-unsupported-feature": {
@ -531,7 +548,7 @@ const Translations = {
"ru-RU": "Очистить", "ru-RU": "Очистить",
"tr-TR": "Temizle", "tr-TR": "Temizle",
"uk-UA": "Очистити", "uk-UA": "Очистити",
"vi-VN": "Xóa", "vi-VN": "Xa",
"zh-CN": "清空", "zh-CN": "清空",
}, },
"close": { "close": {
@ -668,6 +685,7 @@ const Translations = {
"tr-TR": "Kopyala", "tr-TR": "Kopyala",
"uk-UA": "Копіювати", "uk-UA": "Копіювати",
"vi-VN": "Sao chép", "vi-VN": "Sao chép",
"zh-CN": "复制",
}, },
"custom": { "custom": {
"de-DE": "Benutzerdefiniert", "de-DE": "Benutzerdefiniert",
@ -936,7 +954,7 @@ const Translations = {
"ru-RU": "Включить функцию «Удаленная игра»", "ru-RU": "Включить функцию «Удаленная игра»",
"tr-TR": "\"Uzaktan Oynama\" özelliğini aktive et", "tr-TR": "\"Uzaktan Oynama\" özelliğini aktive et",
"uk-UA": "Увімкнути функцію \"Remote Play\"", "uk-UA": "Увімкнути функцію \"Remote Play\"",
"vi-VN": "Bật tính năng \"Chơi txa\"", "vi-VN": "Bật tính năng \"Chơi TXa\"",
"zh-CN": "启用\"远程播放\"功能", "zh-CN": "启用\"远程播放\"功能",
}, },
"enable-volume-control": { "enable-volume-control": {
@ -1016,10 +1034,13 @@ const Translations = {
"help": { "help": {
"de-DE": "Hilfe", "de-DE": "Hilfe",
"en-US": "Help", "en-US": "Help",
"es-ES": "Ayuda",
"ja-JP": "ヘルプ", "ja-JP": "ヘルプ",
"pt-BR": "Ajuda", "pt-BR": "Ajuda",
"ru-RU": "Справка", "ru-RU": "Справка",
"uk-UA": "Довідка",
"vi-VN": "Trợ giúp", "vi-VN": "Trợ giúp",
"zh-CN": "帮助",
}, },
"hide-idle-cursor": { "hide-idle-cursor": {
"de-DE": "Mauszeiger bei Inaktivität ausblenden", "de-DE": "Mauszeiger bei Inaktivität ausblenden",
@ -1064,6 +1085,7 @@ const Translations = {
"tr-TR": "Yatay hassasiyet", "tr-TR": "Yatay hassasiyet",
"uk-UA": "Горизонтальна чутливість", "uk-UA": "Горизонтальна чутливість",
"vi-VN": "Độ nhạy ngang", "vi-VN": "Độ nhạy ngang",
"zh-CN": "水平灵敏度",
}, },
"import": { "import": {
"de-DE": "Importieren", "de-DE": "Importieren",
@ -1138,6 +1160,7 @@ const Translations = {
"tr-TR": "Sol analog çubuk", "tr-TR": "Sol analog çubuk",
"uk-UA": "Лівий стік", "uk-UA": "Лівий стік",
"vi-VN": "Analog trái", "vi-VN": "Analog trái",
"zh-CN": "左摇杆",
}, },
"loading-screen": { "loading-screen": {
"de-DE": "Ladebildschirm", "de-DE": "Ladebildschirm",
@ -1208,7 +1231,7 @@ const Translations = {
"ko-KR": "통계", "ko-KR": "통계",
"pl-PL": "Statystyki strumienia", "pl-PL": "Statystyki strumienia",
"pt-BR": "Estatísticas da transmissão", "pt-BR": "Estatísticas da transmissão",
"ru-RU": "Статистика потоковой передачи", "ru-RU": "Статистика стрима",
"tr-TR": "Yayın durumu", "tr-TR": "Yayın durumu",
"uk-UA": "Статистика трансляції", "uk-UA": "Статистика трансляції",
"vi-VN": "Thông số stream", "vi-VN": "Thông số stream",
@ -1250,6 +1273,7 @@ const Translations = {
"tr-TR": "Etkinleştirmek için tıklayın", "tr-TR": "Etkinleştirmek için tıklayın",
"uk-UA": "Натисніть, щоб активувати", "uk-UA": "Натисніть, щоб активувати",
"vi-VN": "Nhấn vào để kích hoạt", "vi-VN": "Nhấn vào để kích hoạt",
"zh-CN": "单击以启用",
}, },
"mouse-and-keyboard": { "mouse-and-keyboard": {
"de-DE": "Maus & Tastatur", "de-DE": "Maus & Tastatur",
@ -1307,6 +1331,7 @@ const Translations = {
"tr-TR": "Yeni", "tr-TR": "Yeni",
"uk-UA": "Новий", "uk-UA": "Новий",
"vi-VN": "Tạo mới", "vi-VN": "Tạo mới",
"zh-CN": "新建",
}, },
"no-consoles-found": { "no-consoles-found": {
"de-DE": "Keine Konsolen gefunden", "de-DE": "Keine Konsolen gefunden",
@ -1638,7 +1663,7 @@ const Translations = {
"ru-RU": "Удаленная игра", "ru-RU": "Удаленная игра",
"tr-TR": "Uzaktan Bağlanma", "tr-TR": "Uzaktan Bağlanma",
"uk-UA": "Віддалена гра", "uk-UA": "Віддалена гра",
"vi-VN": "Chơi txa", "vi-VN": "Chơi TXa",
"zh-CN": "远程游玩", "zh-CN": "远程游玩",
}, },
"rename": { "rename": {
@ -1653,6 +1678,7 @@ const Translations = {
"tr-TR": "Ad değiştir", "tr-TR": "Ad değiştir",
"uk-UA": "Перейменувати", "uk-UA": "Перейменувати",
"vi-VN": "Sửa tên", "vi-VN": "Sửa tên",
"zh-CN": "重命名",
}, },
"right-click-to-unbind": { "right-click-to-unbind": {
"de-DE": "Rechtsklick auf Taste: Zuordnung aufheben", "de-DE": "Rechtsklick auf Taste: Zuordnung aufheben",
@ -1678,6 +1704,7 @@ const Translations = {
"tr-TR": "Sağ analog çubuk", "tr-TR": "Sağ analog çubuk",
"uk-UA": "Правий стік", "uk-UA": "Правий стік",
"vi-VN": "Analog phải", "vi-VN": "Analog phải",
"zh-CN": "右摇杆",
}, },
"rocket-always-hide": { "rocket-always-hide": {
"de-DE": "Immer ausblenden", "de-DE": "Immer ausblenden",
@ -2128,6 +2155,7 @@ const Translations = {
"pt-BR": "Mínimo decaimento do analógico", "pt-BR": "Mínimo decaimento do analógico",
"ru-RU": "Минимальная перезарядка стика", "ru-RU": "Минимальная перезарядка стика",
"tr-TR": "Çubuğun ortalanma süresi minimumu", "tr-TR": "Çubuğun ortalanma süresi minimumu",
"uk-UA": "Мінімальне згасання стіка",
"vi-VN": "Độ suy giảm tối thiểu của cần điều khiển", "vi-VN": "Độ suy giảm tối thiểu của cần điều khiển",
}, },
"stick-decay-strength": { "stick-decay-strength": {
@ -2137,6 +2165,7 @@ const Translations = {
"pt-BR": "Força de decaimento do analógico", "pt-BR": "Força de decaimento do analógico",
"ru-RU": "Скорость перезарядки стика", "ru-RU": "Скорость перезарядки стика",
"tr-TR": "Çubuğun ortalanma gücü", "tr-TR": "Çubuğun ortalanma gücü",
"uk-UA": "Сила згасання стіка",
"vi-VN": "Sức mạnh độ suy giảm của cần điều khiển", "vi-VN": "Sức mạnh độ suy giảm của cần điều khiển",
}, },
"stream": { "stream": {
@ -2180,7 +2209,8 @@ const Translations = {
"ru-RU": "Поддержать Better xCloud", "ru-RU": "Поддержать Better xCloud",
"tr-TR": "Better xCloud'a destek ver", "tr-TR": "Better xCloud'a destek ver",
"uk-UA": "Підтримати Better xCloud", "uk-UA": "Підтримати Better xCloud",
"vi-VN": "Hỗ trợ Better xCloud", "vi-VN": "Ủng hộ Better xCloud",
"zh-CN": "赞助本插件",
}, },
"swap-buttons": { "swap-buttons": {
"de-DE": "Tasten tauschen", "de-DE": "Tasten tauschen",
@ -2506,6 +2536,7 @@ const Translations = {
"tr-TR": "Dikey hassasiyet", "tr-TR": "Dikey hassasiyet",
"uk-UA": "Вертикальна чутливість", "uk-UA": "Вертикальна чутливість",
"vi-VN": "Độ ngạy dọc", "vi-VN": "Độ ngạy dọc",
"zh-CN": "垂直灵敏度",
}, },
"vibration-intensity": { "vibration-intensity": {
"de-DE": "Vibrationsstärke", "de-DE": "Vibrationsstärke",
@ -2760,7 +2791,12 @@ class Dialog {
this.onClose = onClose; this.onClose = onClose;
this.$dialog = CE('div', {'class': `bx-dialog ${className || ''} bx-gone`}, this.$dialog = CE('div', {'class': `bx-dialog ${className || ''} bx-gone`},
this.$title = CE('h2', {}, CE('b', {}, title), this.$title = CE('h2', {}, CE('b', {}, title),
helpUrl && createButton({icon: Icon.QUESTION, isGhost: true, title: __('help'), url: helpUrl}), helpUrl && createButton({
icon: Icon.QUESTION,
style: ButtonStyle.GHOST,
title: __('help'),
url: helpUrl,
}),
), ),
this.$content = CE('div', {'class': 'bx-dialog-content'}, content), this.$content = CE('div', {'class': 'bx-dialog-content'}, content),
!hideCloseButton && ($close = CE('button', {}, __('close'))), !hideCloseButton && ($close = CE('button', {}, __('close'))),
@ -2780,6 +2816,9 @@ class Dialog {
} }
show(newOptions) { show(newOptions) {
// Clear focus
document.activeElement && document.activeElement.blur();
if (newOptions && newOptions.title) { if (newOptions && newOptions.title) {
this.$title.querySelector('b').textContent = newOptions.title; this.$title.querySelector('b').textContent = newOptions.title;
this.$title.classList.remove('bx-gone'); this.$title.classList.remove('bx-gone');
@ -2929,34 +2968,40 @@ class RemotePlay {
CE('div', {'class': 'bx-remote-play-device-info'}, CE('div', {'class': 'bx-remote-play-device-info'},
CE('div', {}, CE('div', {},
CE('span', {'class': 'bx-remote-play-device-name'}, con.deviceName), CE('span', {'class': 'bx-remote-play-device-name'}, con.deviceName),
CE('span', {'class': 'bx-remote-play-console-type'}, con.consoleType) CE('span', {'class': 'bx-remote-play-console-type'}, con.consoleType.replace('Xbox', ''))
), ),
CE('div', {'class': 'bx-remote-play-power-state'}, RemotePlay.#STATE_LABELS[con.powerState]), CE('div', {'class': 'bx-remote-play-power-state'}, RemotePlay.#STATE_LABELS[con.powerState]),
), ),
$connectButton = CE('button', {'class': 'bx-primary-button bx-no-margin'}, __('console-connect')),
// Connect button
createButton({
classes: ['bx-remote-play-connect-button'],
label: __('console-connect'),
style: ButtonStyle.PRIMARY,
onClick: e => {
REMOTE_PLAY_CONFIG = {
serverId: con.serverId,
};
window.BX_REMOTE_PLAY_CONFIG = REMOTE_PLAY_CONFIG;
const url = window.location.href.substring(0, 31) + '/launch/fortnite/BT5P2X999VH2#remote-play';
const $pageContent = document.getElementById('PageContent');
const $anchor = CE('a', { href: url, class: 'bx-hidden bx-offscreen' }, '');
$anchor.addEventListener('click', e => {
setTimeout(() => {
$pageContent.removeChild($anchor);
}, 1000);
});
$pageContent.appendChild($anchor);
$anchor.click();
RemotePlay.#dialog.hide();
},
}),
); );
$connectButton.addEventListener('click', e => {
REMOTE_PLAY_CONFIG = {
serverId: con.serverId,
};
window.BX_REMOTE_PLAY_CONFIG = REMOTE_PLAY_CONFIG;
const url = window.location.href.substring(0, 31) + '/launch/fortnite/BT5P2X999VH2#remote-play';
const $pageContent = document.getElementById('PageContent');
const $anchor = CE('a', {href: url, class: 'bx-hidden', style: 'position:absolute;top:-9990px;left:-9999px'}, '');
$anchor.addEventListener('click', e => {
setTimeout(() => {
$pageContent.removeChild($anchor);
}, 1000);
});
$pageContent.appendChild($anchor);
$anchor.click();
RemotePlay.#dialog.hide();
});
$fragment.appendChild($child); $fragment.appendChild($child);
} }
@ -4276,7 +4321,7 @@ class MkbHandler {
value = (buttonIndex === GamepadKey.RS_LEFT || buttonIndex === GamepadKey.RS_UP) ? -1 : 1; value = (buttonIndex === GamepadKey.RS_LEFT || buttonIndex === GamepadKey.RS_UP) ? -1 : 1;
} }
virtualGamepad.axes[axisIndex] = pressed ? value : 0; virtualGamepad.axes[axisIndex] += pressed ? value : - value;
} else { } else {
virtualGamepad.buttons[buttonIndex].pressed = pressed; virtualGamepad.buttons[buttonIndex].pressed = pressed;
virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0; virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0;
@ -4300,6 +4345,11 @@ class MkbHandler {
return; return;
} }
// Ignore repeating keys
if (e.repeat) {
return;
}
e.preventDefault(); e.preventDefault();
this.#pressButton(buttonIndex, isKeyDown); this.#pressButton(buttonIndex, isKeyDown);
} }
@ -4943,7 +4993,7 @@ class MkbRemapper {
// Delete button // Delete button
createButton({ createButton({
icon: Icon.TRASH, icon: Icon.TRASH,
isDanger: true, style: ButtonStyle.DANGER,
title: __('delete'), title: __('delete'),
onClick: e => { onClick: e => {
if (!confirm(__('confirm-delete-preset'))) { if (!confirm(__('confirm-delete-preset'))) {
@ -5029,7 +5079,7 @@ class MkbRemapper {
// Activate button // Activate button
this.#$.activateButton = createButton({ this.#$.activateButton = createButton({
label: __('activate'), label: __('activate'),
isPrimary: true, style: ButtonStyle.PRIMARY,
onClick: e => { onClick: e => {
PREFS.set(Preferences.MKB_DEFAULT_PRESET_ID, this.#STATE.currentPresetId); PREFS.set(Preferences.MKB_DEFAULT_PRESET_ID, this.#STATE.currentPresetId);
MkbHandler.INSTANCE.refreshPresetData(); MkbHandler.INSTANCE.refreshPresetData();
@ -5043,7 +5093,7 @@ class MkbRemapper {
// Cancel button // Cancel button
createButton({ createButton({
label: __('cancel'), label: __('cancel'),
isGhost: true, style: ButtonStyle.GHOST,
onClick: e => { onClick: e => {
// Restore preset // Restore preset
this.#switchPreset(this.#STATE.currentPresetId); this.#switchPreset(this.#STATE.currentPresetId);
@ -5054,7 +5104,7 @@ class MkbRemapper {
// Save button // Save button
createButton({ createButton({
label: __('save'), label: __('save'),
isPrimary: true, style: ButtonStyle.PRIMARY,
onClick: e => { onClick: e => {
const updatedPreset = structuredClone(this.#getCurrentPreset()); const updatedPreset = structuredClone(this.#getCurrentPreset());
updatedPreset.data = this.#STATE.editingPresetData; updatedPreset.data = this.#STATE.editingPresetData;
@ -5724,13 +5774,13 @@ class StreamStats {
const packetsLost = stat.packetsLost; const packetsLost = stat.packetsLost;
const packetsReceived = stat.packetsReceived; const packetsReceived = stat.packetsReceived;
const packetsLostPercentage = (packetsLost * 100 / ((packetsLost + packetsReceived) || 1)).toFixed(2); const packetsLostPercentage = (packetsLost * 100 / ((packetsLost + packetsReceived) || 1)).toFixed(2);
StreamStats.#$pl.textContent = `${packetsLost} (${packetsLostPercentage}%)`; StreamStats.#$pl.textContent = packetsLostPercentage === '0.00' ? packetsLost : `${packetsLost} (${packetsLostPercentage}%)`;
// Frames Dropped // Frames Dropped
const framesDropped = stat.framesDropped; const framesDropped = stat.framesDropped;
const framesReceived = stat.framesReceived; const framesReceived = stat.framesReceived;
const framesDroppedPercentage = (framesDropped * 100 / ((framesDropped + framesReceived) || 1)).toFixed(2); const framesDroppedPercentage = (framesDropped * 100 / ((framesDropped + framesReceived) || 1)).toFixed(2);
StreamStats.#$fl.textContent = `${framesDropped} (${framesDroppedPercentage}%)`; StreamStats.#$fl.textContent = framesDroppedPercentage === '0.00' ? framesDropped : `${framesDropped} (${framesDroppedPercentage}%)`;
if (StreamStats.#lastStat) { if (StreamStats.#lastStat) {
const lastStat = StreamStats.#lastStat; const lastStat = StreamStats.#lastStat;
@ -5793,17 +5843,17 @@ class StreamStats {
} }
const STATS = { const STATS = {
[StreamStats.PING]: (StreamStats.#$ping = CE('span', {}, '0')), [StreamStats.PING]: [__('stat-ping'), StreamStats.#$ping = CE('span', {}, '0')],
[StreamStats.FPS]: (StreamStats.#$fps = CE('span', {}, '0')), [StreamStats.FPS]: [__('stat-fps'), StreamStats.#$fps = CE('span', {}, '0')],
[StreamStats.BITRATE]: (StreamStats.#$br = CE('span', {}, '0 Mbps')), [StreamStats.BITRATE]: [__('stat-bitrate'), StreamStats.#$br = CE('span', {}, '0 Mbps')],
[StreamStats.DECODE_TIME]: (StreamStats.#$dt = CE('span', {}, '0ms')), [StreamStats.DECODE_TIME]: [__('stat-decode-time'), StreamStats.#$dt = CE('span', {}, '0ms')],
[StreamStats.PACKETS_LOST]: (StreamStats.#$pl = CE('span', {}, '0 (0.00%)')), [StreamStats.PACKETS_LOST]: [__('stat-packets-lost'), StreamStats.#$pl = CE('span', {}, '0')],
[StreamStats.FRAMES_LOST]: (StreamStats.#$fl = CE('span', {}, '0 (0.00%)')), [StreamStats.FRAMES_LOST]: [__('stat-frames-lost'), StreamStats.#$fl = CE('span', {}, '0')],
}; };
const $barFragment = document.createDocumentFragment(); const $barFragment = document.createDocumentFragment();
for (let statKey in STATS) { for (let statKey in STATS) {
const $div = CE('div', {'class': `bx-stat-${statKey}`}, CE('label', {}, statKey.toUpperCase()), STATS[statKey]); const $div = CE('div', {'class': `bx-stat-${statKey}`, title: STATS[statKey][0]}, CE('label', {}, statKey.toUpperCase()), STATS[statKey][1]);
$barFragment.appendChild($div); $barFragment.appendChild($div);
} }
@ -6626,57 +6676,6 @@ class Patcher {
return funcStr.replace(text, `connectMode:window.BX_REMOTE_PLAY_CONFIG?"xhome-connect":"cloud-connect",remotePlayServerId:(window.BX_REMOTE_PLAY_CONFIG&&window.BX_REMOTE_PLAY_CONFIG.serverId)||''`); return funcStr.replace(text, `connectMode:window.BX_REMOTE_PLAY_CONFIG?"xhome-connect":"cloud-connect",remotePlayServerId:(window.BX_REMOTE_PLAY_CONFIG&&window.BX_REMOTE_PLAY_CONFIG.serverId)||''`);
}, },
// Add a "Remote Play" button to the "Jump back in" list
remotePlayPatchHomeJumpBackIn: PREFS.get(Preferences.REMOTE_PLAY_ENABLED) && function(funcStr) {
const index = funcStr.indexOf('MruTitlesGamesRow-module__childContainer');
if (index === -1) {
return false;
}
const startIndex = funcStr.indexOf('return(', index);
const newCode = `
setTimeout(() => { window.dispatchEvent(new Event('${BxEvent.JUMP_BACK_IN_READY}')); }, 1000);
`;
// Add event listener
window.addEventListener(BxEvent.JUMP_BACK_IN_READY, e => {
const $list = document.querySelector('ol[class^=ItemRow-module__list]');
if (!$list) {
return;
}
if ($list.querySelector('.bx-jump-in-li')) {
return;
}
const $li = $list.firstElementChild.cloneNode(true);
$li.classList.add('bx-jump-in-li');
$li.addEventListener('click', e => {
RemotePlay.showDialog();
});
const $button = $li.querySelector('button');
$button.removeAttribute('id');
$button.removeAttribute('aria-labelledby');
$button.setAttribute('aria-label', __('remote-play'));
// Remove card's info
const $cardInfo = $button.querySelector('div[class^=BaseItem]');
$cardInfo.parentElement.removeChild($cardInfo);
const $images = $button.querySelector('div[class^=WrappedResponsiveImage]');
$images.classList.add('bx-remote-play-icon-wrapper');
$images.innerHTML = '';
$images.appendChild(createSvgIcon(Icon.REMOTE_PLAY), 2);
$list.insertBefore($li, $list.firstElementChild);
});
funcStr = funcStr.substring(0, startIndex) + newCode + funcStr.substring(startIndex);
return funcStr;
},
// Disable trackEvent() function // Disable trackEvent() function
disableTrackEvent: PREFS.get(Preferences.BLOCK_TRACKING) && function(funcStr) { disableTrackEvent: PREFS.get(Preferences.BLOCK_TRACKING) && function(funcStr) {
const text = 'this.trackEvent='; const text = 'this.trackEvent=';
@ -6847,8 +6846,6 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
['disableStreamGate'], ['disableStreamGate'],
['remotePlayPatchHomeJumpBackIn'],
['tvLayout'], ['tvLayout'],
['enableXcloudLogger'], ['enableXcloudLogger'],
@ -6880,7 +6877,7 @@ if (window.BX_VIBRATION_INTENSITY && window.BX_VIBRATION_INTENSITY < 1) {
const nativeBind = Function.prototype.bind; const nativeBind = Function.prototype.bind;
Function.prototype.bind = function() { Function.prototype.bind = function() {
let valid = false; let valid = false;
if (arguments.length === 2 && arguments[0] === null) { if (this.name.length <= 2 && arguments.length === 2 && arguments[0] === null) {
if (arguments[1] === 0 || (typeof arguments[1] === 'function')) { if (arguments[1] === 0 || (typeof arguments[1] === 'function')) {
valid = true; valid = true;
} }
@ -7093,6 +7090,8 @@ function addCss() {
--bx-monospaced-font: Consolas, "Courier New", Courier, monospace; --bx-monospaced-font: Consolas, "Courier New", Courier, monospace;
--bx-promptfont-font: promptfont; --bx-promptfont-font: promptfont;
--bx-button-height: 36px;
--bx-default-button-color: #2d3036; --bx-default-button-color: #2d3036;
--bx-default-button-hover-color: #515863; --bx-default-button-hover-color: #515863;
--bx-default-button-disabled-color: #8e8e8e; --bx-default-button-disabled-color: #8e8e8e;
@ -7130,35 +7129,6 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
left: -9999px; left: -9999px;
} }
.bx-jump-in-li {
display: inline-block;
}
.bx-jump-in-li button {
width: 60px;
box-shadow: none !important;
}
.bx-jump-in-li button:not(:focus) .bx-remote-play-icon-wrapper {
background: transparent;
}
.bx-remote-play-icon-wrapper, .bx-jump-in-li button:hover .bx-remote-play-icon-wrapper {
background: #7b7b7b1a;
}
.bx-remote-play-icon-wrapper svg {
padding: 10px;
height: 100%;
filter: drop-shadow(0px 1px 1px rgba(0, 0, 0, 0.5));
opacity: 0.7;
}
.bx-jump-in-li button:hover .bx-remote-play-icon-wrapper svg, .bx-jump-in-li button:focus .bx-remote-play-icon-wrapper svg {
opacity: 1;
}
a.bx-button { a.bx-button {
display: inline-block; display: inline-block;
} }
@ -7172,11 +7142,12 @@ a.bx-button {
font-size: 14px; font-size: 14px;
border: none; border: none;
font-weight: 400; font-weight: 400;
height: 32px; height: var(--bx-button-height);
border-radius: 4px; border-radius: 4px;
padding: 0 8px; padding: 0 8px;
text-transform: uppercase; text-transform: uppercase;
cursor: pointer; cursor: pointer;
overflow: hidden;
} }
.bx-button:hover, .bx-button.bx-focusable:focus { .bx-button:hover, .bx-button.bx-focusable:focus {
@ -7223,7 +7194,7 @@ a.bx-button {
.bx-button svg { .bx-button svg {
display: inline-block; display: inline-block;
width: 16px; width: 16px;
height: 32px; height: var(--bx-button-height);
} }
.bx-button svg:not(:only-child) { .bx-button svg:not(:only-child) {
@ -7232,30 +7203,53 @@ a.bx-button {
.bx-button span { .bx-button span {
display: inline-block; display: inline-block;
height: 30px; height: calc(var(--bx-button-height) - 2px);
line-height: 32px; line-height: var(--bx-button-height);
vertical-align: middle; vertical-align: middle;
color: #fff; color: #fff;
overflow: hidden;
white-space: nowrap;
}
.bx-remote-play-button {
height: auto;
margin-right: 8px !important;
}
.bx-remote-play-button svg {
width: 28px;
height: 46px;
} }
.bx-settings-button { .bx-settings-button {
background-color: transparent;
border: none;
color: white;
font-weight: bold;
line-height: 30px; line-height: 30px;
border-radius: 4px; font-size: 14px;
padding: 8px; text-transform: none;
}
.bx-settings-button:hover, .bx-settings-button:focus {
background-color: #515863;
} }
.bx-settings-button[data-update-available]::after { .bx-settings-button[data-update-available]::after {
content: ' 🌟'; content: ' 🌟';
} }
.bx-remote-play-button, .bx-settings-button {
position: relative;
}
.bx-remote-play-button::after, .bx-settings-button::after {
border: 2px solid transparent;
border-radius: 4px;
}
.bx-remote-play-button:focus::after, .bx-settings-button:focus::after {
content: '';
border-color: white;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.better_xcloud_settings { .better_xcloud_settings {
background-color: #151515; background-color: #151515;
user-select: none; user-select: none;
@ -7268,6 +7262,10 @@ a.bx-button {
width: 100% !important; width: 100% !important;
} }
.bx-full-height {
height: 100% !important;
}
.bx-no-scroll { .bx-no-scroll {
overflow: hidden !important; overflow: hidden !important;
} }
@ -7301,6 +7299,12 @@ a.bx-button {
padding: 12px 6px; padding: 12px 6px;
} }
@media screen and (max-width: 450px) {
.bx-settings-wrapper {
width: 100%;
}
}
.bx-settings-wrapper *:focus { .bx-settings-wrapper *:focus {
outline: none !important; outline: none !important;
} }
@ -7391,39 +7395,8 @@ a.bx-button {
align-self: center; align-self: center;
} }
.bx-primary-button { .bx-settings-wrapper .bx-button.bx-primary {
padding: 8px 32px; margin-top: 8px;
margin: 10px auto 0;
border: none;
border-radius: 4px;
display: block;
background-color: #044e2a;
text-align: center;
color: white;
text-transform: uppercase;
font-family: var(--bx-title-font);
font-weight: 400;
font-size: 14px;
line-height: 24px;
}
@media (hover: hover) {
.bx-primary-button:hover {
background-color: #00753c;
}
}
.bx-primary-button:focus {
background-color: #00753c;
}
.bx-primary-button:active {
background-color: #00753c;
}
.bx-primary-button[disabled] {
background: #393939;
color: #a2a2a2;
} }
.bx-settings-app-version { .bx-settings-app-version {
@ -7551,6 +7524,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
margin-right: 8px; margin-right: 8px;
border-right: 1px solid #fff; border-right: 1px solid #fff;
padding-right: 8px; padding-right: 8px;
cursor: help;
} }
.bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps, .bx-stats-bar[data-stats*="[fps]"] > .bx-stat-fps,
@ -7604,6 +7578,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
font-size: inherit; font-size: inherit;
font-weight: bold; font-weight: bold;
vertical-align: middle; vertical-align: middle;
cursor: help;
} }
.bx-stats-bar span { .bx-stats-bar span {
@ -7664,6 +7639,12 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
outline: none !important; outline: none !important;
} }
@media screen and (max-width: 450px) {
.bx-dialog {
min-width: 100%;
}
}
.bx-dialog h2 { .bx-dialog h2 {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 12px;
@ -7676,7 +7657,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
font-family: var(--bx-title-font); font-family: var(--bx-title-font);
font-size: 26px; font-size: 26px;
font-weight: 400; font-weight: 400;
line-height: 32px; line-height: var(--bx-button-height);
} }
.bx-dialog.bx-binding-dialog h2 b { .bx-dialog.bx-binding-dialog h2 b {
@ -7690,7 +7671,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.bx-dialog > button { .bx-dialog > button {
padding: 8px 32px; padding: 8px 32px;
margin: 20px auto 0; margin: 10px auto 0;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
display: block; display: block;
@ -7837,16 +7818,16 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.bx-quick-settings-tab-contents h2 span { .bx-quick-settings-tab-contents h2 span {
display: inline-block; display: inline-block;
font-size: 28px; font-size: 24px;
font-weight: bold; font-weight: bold;
text-transform: uppercase; text-transform: uppercase;
text-align: left; text-align: left;
flex: 1; flex: 1;
height: 32px; height: var(--bx-button-height);
line-height: 32px; line-height: calc(var(--bx-button-height) + 4px);
} text-overflow: ellipsis;
overflow: hidden;
.bx-quick-settings-tab-contents h2 a { white-space: nowrap;
} }
.bx-quick-settings-tab-contents input[type="range"] { .bx-quick-settings-tab-contents input[type="range"] {
@ -7953,7 +7934,8 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
overflow: scroll; padding-bottom: 10px;
overflow: hidden;
} }
.bx-mkb-settings select:disabled { .bx-mkb-settings select:disabled {
@ -8044,6 +8026,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
font-family: var(--bx-promptfont-font); font-family: var(--bx-promptfont-font);
font-size: 26px; font-size: 26px;
text-align: center; text-align: center;
width: 26px;
height: 32px; height: 32px;
line-height: 32px; line-height: 32px;
} }
@ -8204,11 +8187,11 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.bx-remote-play-device-wrapper { .bx-remote-play-device-wrapper {
display: flex; display: flex;
margin-bottom: 8px; margin-bottom: 12px;
} }
.bx-remote-play-device-wrapper:not(:last-child) { .bx-remote-play-device-wrapper:last-child {
margin-bottom: 14px; margin-bottom: 2px;
} }
.bx-remote-play-device-info { .bx-remote-play-device-info {
@ -8225,7 +8208,7 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
.bx-remote-play-console-type { .bx-remote-play-console-type {
font-size: 12px; font-size: 12px;
background: #888; background: #004c87;
color: #fff; color: #fff;
display: inline-block; display: inline-block;
border-radius: 14px; border-radius: 14px;
@ -8239,6 +8222,11 @@ div[class*=StreamMenu-module__menuContainer] > div[class*=Menu-module] {
font-size: 14px; font-size: 14px;
} }
.bx-remote-play-connect-button {
min-height: 100%;
margin: 4px 0;
}
/* ----------- */ /* ----------- */
/* Hide UI elements */ /* Hide UI elements */
@ -8901,21 +8889,45 @@ function injectSettingsButton($parent) {
const PREF_PREFERRED_REGION = getPreferredServerRegion(); const PREF_PREFERRED_REGION = getPreferredServerRegion();
const PREF_LATEST_VERSION = PREFS.get(Preferences.LATEST_VERSION); const PREF_LATEST_VERSION = PREFS.get(Preferences.LATEST_VERSION);
const $headerFragment = document.createDocumentFragment();
// Remote Play button
if (PREFS.get(Preferences.REMOTE_PLAY_ENABLED)) {
const $remotePlayBtn = createButton({
classes: ['bx-remote-play-button'],
icon: Icon.REMOTE_PLAY,
title: __('remote-play'),
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE,
onClick: e => {
RemotePlay.showDialog();
},
});
$headerFragment.appendChild($remotePlayBtn);
}
// Setup Settings button // Setup Settings button
const $button = CE('button', {'class': 'bx-settings-button'}, PREF_PREFERRED_REGION); const $settingsBtn = createButton({
$button.addEventListener('click', e => { classes: ['bx-settings-button'],
const $settings = document.querySelector('.better_xcloud_settings'); label: PREF_PREFERRED_REGION,
$settings.classList.toggle('bx-gone'); style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_HEIGHT,
$settings.scrollIntoView(); onClick: e => {
const $settings = document.querySelector('.better_xcloud_settings');
$settings.classList.toggle('bx-gone');
$settings.scrollIntoView();
document.activeElement && document.activeElement.blur();
},
}); });
// Show new update status // Show new update status
if (PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) { if (PREF_LATEST_VERSION && PREF_LATEST_VERSION !== SCRIPT_VERSION) {
$button.setAttribute('data-update-available', true); $settingsBtn.setAttribute('data-update-available', true);
} }
// Add Settings button to the web page // Add the Settings button to the web page
$parent.appendChild($button); $headerFragment.appendChild($settingsBtn);
$parent.appendChild($headerFragment);
// Setup Settings UI // Setup Settings UI
const $container = CE('div', { const $container = CE('div', {
@ -8966,6 +8978,14 @@ function injectSettingsButton($parent) {
[Preferences.AUDIO_MIC_ON_PLAYING]: __('enable-mic-on-startup'), [Preferences.AUDIO_MIC_ON_PLAYING]: __('enable-mic-on-startup'),
[Preferences.STREAM_DISABLE_FEEDBACK_DIALOG]: __('disable-post-stream-feedback-dialog'), [Preferences.STREAM_DISABLE_FEEDBACK_DIALOG]: __('disable-post-stream-feedback-dialog'),
}, },
[__('mouse-and-keyboard')]: {
// '_note': '⚠️ ' + __('may-not-work-properly'),
// [Preferences.MKB_ENABLED]: [__('enable-mkb'), __('only-supports-some-games')],
[Preferences.MKB_ENABLED]: __('enable-mkb'),
[Preferences.MKB_HIDE_IDLE_CURSOR]: __('hide-idle-cursor'),
},
/* /*
[__('controller')]: { [__('controller')]: {
[Preferences.CONTROLLER_ENABLE_SHORTCUTS]: __('enable-controller-shortcuts'), [Preferences.CONTROLLER_ENABLE_SHORTCUTS]: __('enable-controller-shortcuts'),
@ -8977,13 +8997,6 @@ function injectSettingsButton($parent) {
[Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: __('tc-custom-layout-style'), [Preferences.STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM]: __('tc-custom-layout-style'),
}, },
[__('mouse-and-keyboard')]: {
// '_note': '⚠️ ' + __('may-not-work-properly'),
// [Preferences.MKB_ENABLED]: [__('enable-mkb'), __('only-supports-some-games')],
[Preferences.MKB_ENABLED]: __('enable-mkb'),
[Preferences.MKB_HIDE_IDLE_CURSOR]: __('hide-idle-cursor'),
},
[__('loading-screen')]: { [__('loading-screen')]: {
[Preferences.UI_LOADING_SCREEN_GAME_ART]: __('show-game-art'), [Preferences.UI_LOADING_SCREEN_GAME_ART]: __('show-game-art'),
[Preferences.UI_LOADING_SCREEN_WAIT_TIME]: __('show-wait-time'), [Preferences.UI_LOADING_SCREEN_WAIT_TIME]: __('show-wait-time'),
@ -9128,11 +9141,17 @@ function injectSettingsButton($parent) {
} }
// Setup Reload button // Setup Reload button
const $reloadBtn = CE('button', {'class': 'bx-primary-button bx-full-width', 'tabindex': 0}, __('settings-reload')); const $reloadBtn = createButton({
$reloadBtn.addEventListener('click', e => { classes: ['bx-settings-reload-button'],
window.location.reload(); label: __('settings-reload'),
$reloadBtn.textContent = __('settings-reloading'); style: ButtonStyle.PRIMARY | ButtonStyle.FOCUSABLE | ButtonStyle.FULL_WIDTH,
onClick: e => {
window.location.reload();
$reloadBtn.disabled = true;
$reloadBtn.textContent = __('settings-reloading');
},
}); });
$reloadBtn.setAttribute('tabindex', 0);
$wrapper.appendChild($reloadBtn); $wrapper.appendChild($reloadBtn);
// Donation link // Donation link
@ -9785,7 +9804,12 @@ function setupQuickSettingsBar() {
for (const settingGroup of settingTab.items) { for (const settingGroup of settingTab.items) {
$group.appendChild(CE('h2', {}, $group.appendChild(CE('h2', {},
CE('span', {}, settingGroup.label), CE('span', {}, settingGroup.label),
settingGroup.help_url && createButton({icon: Icon.QUESTION, isGhost: true, url: settingGroup.help_url, title: __('help')}), settingGroup.help_url && createButton({
icon: Icon.QUESTION,
style: ButtonStyle.GHOST,
url: settingGroup.help_url,
title: __('help'),
}),
)); ));
if (settingGroup.note) { if (settingGroup.note) {
if (typeof settingGroup.note === 'string') { if (typeof settingGroup.note === 'string') {