mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-08 07:11:43 +02:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
24f0cf18d9 | |||
2df8274233 | |||
a095370ab8 | |||
339447d29c | |||
efe0caf02f | |||
6daabea288 | |||
772a642283 | |||
675fc8431c | |||
9a97053662 |
6
build.ts
6
build.ts
@ -85,8 +85,10 @@ const build = async (target: BuildTarget, version: string, config: any={}) => {
|
|||||||
// Save to script
|
// Save to script
|
||||||
await Bun.write(path, scriptHeader + result);
|
await Bun.write(path, scriptHeader + result);
|
||||||
|
|
||||||
// Create meta file
|
// Create meta file (don't build if it's beta version)
|
||||||
await Bun.write(outDir + '/' + outputMetaName, txtMetaHeader.replace('[[VERSION]]', version));
|
if (!version.includes('beta')) {
|
||||||
|
await Bun.write(outDir + '/' + outputMetaName, txtMetaHeader.replace('[[VERSION]]', version));
|
||||||
|
}
|
||||||
|
|
||||||
// Check with ESLint
|
// Check with ESLint
|
||||||
const eslint = new ESLint();
|
const eslint = new ESLint();
|
||||||
|
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 5.6.0
|
// @version 5.6.1
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
106
dist/better-xcloud.user.js
vendored
106
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
@ -9,10 +9,10 @@
|
|||||||
"build": "build.ts"
|
"build": "build.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "^1.1.6",
|
"@types/bun": "^1.1.8",
|
||||||
"@types/node": "^20.16.1",
|
"@types/node": "^20.16.2",
|
||||||
"@types/stylus": "^0.48.42",
|
"@types/stylus": "^0.48.42",
|
||||||
"eslint": "^9.9.0",
|
"eslint": "^9.9.1",
|
||||||
"eslint-plugin-compat": "^6.0.0",
|
"eslint-plugin-compat": "^6.0.0",
|
||||||
"stylus": "^0.63.0"
|
"stylus": "^0.63.0"
|
||||||
},
|
},
|
||||||
|
@ -168,16 +168,11 @@ div[class*=SupportedInputsBadge] {
|
|||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background: #0000008c;
|
background: #0000008c;
|
||||||
display: none;
|
display: flex;
|
||||||
border-radius: 0 0 4px 0;
|
border-radius: 4px 0 4px 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
|
|
||||||
a[class^=BaseItem-module__container]:focus &,
|
|
||||||
button[class^=BaseItem-module__container]:focus & {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@ -190,6 +185,7 @@ div[class*=SupportedInputsBadge] {
|
|||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
margin-left: 2px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,11 +60,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #5dc21e;
|
color: #1c9d1c;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover, &:focus {
|
&:hover, &:focus {
|
||||||
color: #128112;
|
color: #5dc21e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -470,9 +470,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bx-suggest-link {
|
.bx-suggest-link {
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-top: 10px;
|
margin-top: 4px;
|
||||||
|
padding: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bx-suggest-row {
|
.bx-suggest-row {
|
||||||
|
50
src/index.ts
50
src/index.ts
@ -38,6 +38,7 @@ import { getPref, StreamTouchController } from "./utils/settings-storages/global
|
|||||||
import { compressCss } from "@macros/build" with {type: "macro"};
|
import { compressCss } from "@macros/build" with {type: "macro"};
|
||||||
import { SettingsNavigationDialog } from "./modules/ui/dialog/settings-dialog";
|
import { SettingsNavigationDialog } from "./modules/ui/dialog/settings-dialog";
|
||||||
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
||||||
|
import { UserAgent } from "./utils/user-agent";
|
||||||
|
|
||||||
|
|
||||||
// Handle login page
|
// Handle login page
|
||||||
@ -70,26 +71,65 @@ if (BX_FLAGS.SafariWorkaround && document.readyState !== 'loading') {
|
|||||||
.bx-reload-overlay {
|
.bx-reload-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
background: #000000cc;
|
background: #000000cc;
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
width: 100%;
|
|
||||||
line-height: 100vh;
|
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-family: "Segoe UI", Arial, Helvetica, sans-serif;
|
font-family: "Segoe UI", Arial, Helvetica, sans-serif;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bx-reload-overlay *:focus {
|
||||||
|
outline: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bx-reload-overlay > div {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bx-reload-overlay a {
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
background: #107c10;
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const isSafari = UserAgent.isSafari();
|
||||||
|
let $secondaryAction: HTMLElement;
|
||||||
|
if (isSafari) {
|
||||||
|
$secondaryAction = CE('p', {}, t('settings-reloading'));
|
||||||
|
} else {
|
||||||
|
$secondaryAction = CE('a', {
|
||||||
|
href: 'https://better-xcloud.github.io/troubleshooting',
|
||||||
|
target: '_blank',
|
||||||
|
}, '🤓 ' + t('how-to-fix'));
|
||||||
|
}
|
||||||
|
|
||||||
const $fragment = document.createDocumentFragment();
|
const $fragment = document.createDocumentFragment();
|
||||||
$fragment.appendChild(CE('style', {}, css));
|
$fragment.appendChild(CE('style', {}, css));
|
||||||
$fragment.appendChild(CE('div', {'class': 'bx-reload-overlay'}, t('safari-failed-message')));
|
$fragment.appendChild(CE('div',{
|
||||||
|
class: 'bx-reload-overlay',
|
||||||
|
},
|
||||||
|
CE('div', {},
|
||||||
|
CE('p', {}, t('load-failed-message')),
|
||||||
|
$secondaryAction,
|
||||||
|
),
|
||||||
|
));
|
||||||
|
|
||||||
document.documentElement.appendChild($fragment);
|
document.documentElement.appendChild($fragment);
|
||||||
|
|
||||||
// Reload the page
|
// Reload the page if using Safari
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.location.reload(true);
|
isSafari && window.location.reload(true);
|
||||||
|
|
||||||
// Stop processing the script
|
// Stop processing the script
|
||||||
throw new Error('[Better xCloud] Executing workaround for Safari');
|
throw new Error('[Better xCloud] Executing workaround for Safari');
|
||||||
|
@ -913,7 +913,7 @@ export class SettingsNavigationDialog extends NavigationDialog {
|
|||||||
href: 'https://better-xcloud.github.io/guide/android-webview-tweaks/',
|
href: 'https://better-xcloud.github.io/guide/android-webview-tweaks/',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
tabindex: 0,
|
tabindex: 0,
|
||||||
}, t('how-to-improve-app-performance')),
|
}, '🤓 ' + t('how-to-improve-app-performance')),
|
||||||
|
|
||||||
BX_FLAGS.DeviceInfo.deviceType.includes('android') && !hasRecommendedSettings && CE('a', {
|
BX_FLAGS.DeviceInfo.deviceType.includes('android') && !hasRecommendedSettings && CE('a', {
|
||||||
class: 'bx-suggest-link bx-focusable',
|
class: 'bx-suggest-link bx-focusable',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
import { BxIcon } from "@/utils/bx-icon";
|
import { BxIcon } from "@/utils/bx-icon";
|
||||||
import { CE, createSvgIcon, getReactProps } from "@/utils/html";
|
import { CE, createSvgIcon, getReactProps, isElementVisible } from "@/utils/html";
|
||||||
import { XcloudApi } from "@/utils/xcloud-api";
|
import { XcloudApi } from "@/utils/xcloud-api";
|
||||||
|
|
||||||
export class GameTile {
|
export class GameTile {
|
||||||
@ -23,6 +23,11 @@ export class GameTile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async #showWaitTime($elm: HTMLElement, productId: string) {
|
static async #showWaitTime($elm: HTMLElement, productId: string) {
|
||||||
|
if (($elm as any).hasWaitTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
($elm as any).hasWaitTime = true;
|
||||||
|
|
||||||
let totalWaitTime;
|
let totalWaitTime;
|
||||||
|
|
||||||
const api = XcloudApi.getInstance();
|
const api = XcloudApi.getInstance();
|
||||||
@ -34,7 +39,7 @@ export class GameTile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof totalWaitTime === 'number' && $elm.isConnected) {
|
if (typeof totalWaitTime === 'number' && isElementVisible($elm)) {
|
||||||
const $div = CE('div', {'class': 'bx-game-tile-wait-time'},
|
const $div = CE('div', {'class': 'bx-game-tile-wait-time'},
|
||||||
createSvgIcon(BxIcon.PLAYTIME),
|
createSvgIcon(BxIcon.PLAYTIME),
|
||||||
CE('span', {}, GameTile.#secondsToHms(totalWaitTime)),
|
CE('span', {}, GameTile.#secondsToHms(totalWaitTime)),
|
||||||
@ -43,45 +48,61 @@ export class GameTile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static requestWaitTime($elm: HTMLElement, productId: string) {
|
static #requestWaitTime($elm: HTMLElement, productId: string) {
|
||||||
GameTile.#timeout && clearTimeout(GameTile.#timeout);
|
GameTile.#timeout && clearTimeout(GameTile.#timeout);
|
||||||
GameTile.#timeout = window.setTimeout(async () => {
|
GameTile.#timeout = window.setTimeout(async () => {
|
||||||
if (!($elm as any).hasWaitTime) {
|
GameTile.#showWaitTime($elm, productId);
|
||||||
($elm as any).hasWaitTime = true;
|
}, 500);
|
||||||
GameTile.#showWaitTime($elm, productId);
|
}
|
||||||
|
|
||||||
|
static #findProductId($elm: HTMLElement): string | null {
|
||||||
|
let productId = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (($elm.tagName === 'BUTTON' && $elm.className.includes('MruGameCard')) || (($elm.tagName === 'A' && $elm.className.includes('GameCard')))) {
|
||||||
|
let props = getReactProps($elm.parentElement!);
|
||||||
|
|
||||||
|
// When context menu is enabled
|
||||||
|
if (Array.isArray(props.children)) {
|
||||||
|
productId = props.children[0].props.productId;
|
||||||
|
} else {
|
||||||
|
productId = props.children.props.productId;
|
||||||
|
}
|
||||||
|
} else if ($elm.tagName === 'A' && $elm.className.includes('GameItem')) {
|
||||||
|
let props = getReactProps($elm.parentElement!);
|
||||||
|
props = props.children.props;
|
||||||
|
if (props.location !== 'NonStreamableGameItem') {
|
||||||
|
if ('productId' in props) {
|
||||||
|
productId = props.productId;
|
||||||
|
} else {
|
||||||
|
// Search page
|
||||||
|
productId = props.children.props.productId;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 1000);
|
} catch (e) {}
|
||||||
|
|
||||||
|
return productId;
|
||||||
}
|
}
|
||||||
|
|
||||||
static setup() {
|
static setup() {
|
||||||
window.addEventListener(BxEvent.NAVIGATION_FOCUS_CHANGED, e => {
|
window.addEventListener(BxEvent.NAVIGATION_FOCUS_CHANGED, e => {
|
||||||
let productId;
|
|
||||||
const $elm = (e as any).element;
|
const $elm = (e as any).element;
|
||||||
try {
|
const className = $elm.className || '';
|
||||||
if (($elm.tagName === 'BUTTON' && $elm.className.includes('MruGameCard')) || (($elm.tagName === 'A' && $elm.className.includes('GameCard')))) {
|
if (className.includes('MruGameCard')) {
|
||||||
let props = getReactProps($elm.parentElement);
|
// Show the wait time of every games in the "Jump back in" section all at once
|
||||||
|
const $ol = $elm.closest('ol');
|
||||||
// When context menu is enabled
|
if ($ol && !($ol as any).hasWaitTime) {
|
||||||
if (Array.isArray(props.children)) {
|
($ol as any).hasWaitTime = true;
|
||||||
productId = props.children[0].props.productId;
|
$ol.querySelectorAll('button[class*=MruGameCard]').forEach(($elm: HTMLElement) => {
|
||||||
} else {
|
const productId = GameTile.#findProductId($elm);
|
||||||
productId = props.children.props.productId;
|
productId && GameTile.#showWaitTime($elm, productId);
|
||||||
}
|
});
|
||||||
} else if ($elm.tagName === 'A' && $elm.className.includes('GameItem')) {
|
|
||||||
let props = getReactProps($elm.parentElement);
|
|
||||||
props = props.children.props;
|
|
||||||
if (props.location !== 'NonStreamableGameItem') {
|
|
||||||
if ('productId' in props) {
|
|
||||||
productId = props.productId;
|
|
||||||
} else {
|
|
||||||
// Search page
|
|
||||||
productId = props.children.props.productId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} else {
|
||||||
|
const productId = GameTile.#findProductId($elm);
|
||||||
productId && GameTile.requestWaitTime($elm, productId);
|
productId && GameTile.#requestWaitTime($elm, productId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -51,7 +51,7 @@ export namespace BxEvent {
|
|||||||
|
|
||||||
export const XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed';
|
export const XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed';
|
||||||
|
|
||||||
export const XCLOUD_RENDERING_COMPONENT = 'bx-xcloud-rendering-page';
|
export const XCLOUD_RENDERING_COMPONENT = 'bx-xcloud-rendering-component';
|
||||||
|
|
||||||
export const XCLOUD_ROUTER_HISTORY_READY = 'bx-xcloud-router-history-ready';
|
export const XCLOUD_ROUTER_HISTORY_READY = 'bx-xcloud-router-history-ready';
|
||||||
|
|
||||||
|
@ -113,6 +113,7 @@ const Texts = {
|
|||||||
"fortnite-force-console-version": "Fortnite: force console version",
|
"fortnite-force-console-version": "Fortnite: force console version",
|
||||||
"game-bar": "Game Bar",
|
"game-bar": "Game Bar",
|
||||||
"getting-consoles-list": "Getting the list of consoles...",
|
"getting-consoles-list": "Getting the list of consoles...",
|
||||||
|
"guide": "Guide",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"hide": "Hide",
|
"hide": "Hide",
|
||||||
"hide-idle-cursor": "Hide mouse cursor on idle",
|
"hide-idle-cursor": "Hide mouse cursor on idle",
|
||||||
@ -125,6 +126,7 @@ const Texts = {
|
|||||||
"highest-quality-note": "Your device may not be powerful enough to use these settings",
|
"highest-quality-note": "Your device may not be powerful enough to use these settings",
|
||||||
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
|
||||||
"horizontal-sensitivity": "Horizontal sensitivity",
|
"horizontal-sensitivity": "Horizontal sensitivity",
|
||||||
|
"how-to-fix": "How to fix",
|
||||||
"how-to-improve-app-performance": "How to improve app's performance",
|
"how-to-improve-app-performance": "How to improve app's performance",
|
||||||
"ignore": "Ignore",
|
"ignore": "Ignore",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
@ -137,6 +139,7 @@ const Texts = {
|
|||||||
"large": "Large",
|
"large": "Large",
|
||||||
"layout": "Layout",
|
"layout": "Layout",
|
||||||
"left-stick": "Left stick",
|
"left-stick": "Left stick",
|
||||||
|
"load-failed-message": "Failed to run Better xCloud",
|
||||||
"loading-screen": "Loading screen",
|
"loading-screen": "Loading screen",
|
||||||
"local-co-op": "Local co-op",
|
"local-co-op": "Local co-op",
|
||||||
"low-power": "Low power",
|
"low-power": "Low power",
|
||||||
@ -198,22 +201,22 @@ const Texts = {
|
|||||||
(e: any) => `Recommended settings for ${e.device}`,
|
(e: any) => `Recommended settings for ${e.device}`,
|
||||||
,
|
,
|
||||||
,
|
,
|
||||||
,
|
(e: any) => `Empfohlene Einstellungen für ${e.device}`,
|
||||||
,
|
,
|
||||||
(e: any) => `Ajustes recomendados para ${e.device}`,
|
(e: any) => `Ajustes recomendados para ${e.device}`,
|
||||||
,
|
(e: any) => `Paramètres recommandés pour ${e.device}`,
|
||||||
(e: any) => `Configurazioni consigliate per ${e.device}`,
|
(e: any) => `Configurazioni consigliate per ${e.device}`,
|
||||||
,
|
(e: any) => `${e.device} の推奨設定`,
|
||||||
(e: any) => `다음 기기에서 권장되는 설정: ${e.device}`,
|
(e: any) => `다음 기기에서 권장되는 설정: ${e.device}`,
|
||||||
|
(e: any) => `Zalecane ustawienia dla ${e.device}`,
|
||||||
,
|
,
|
||||||
|
(e: any) => `Рекомендуемые настройки для ${e.device}`,
|
||||||
,
|
,
|
||||||
,
|
(e: any) => `${e.device} için önerilen ayarlar`,
|
||||||
,
|
|
||||||
,
|
|
||||||
(e: any) => `Рекомендовані налаштування для ${e.device}`,
|
(e: any) => `Рекомендовані налаштування для ${e.device}`,
|
||||||
(e: any) => `Cấu hình được đề xuất cho ${e.device}`,
|
(e: any) => `Cấu hình được đề xuất cho ${e.device}`,
|
||||||
,
|
(e: any) => `${e.device} 的推荐设置`,
|
||||||
,
|
(e: any) => `${e.device} 推薦的設定`,
|
||||||
],
|
],
|
||||||
"reduce-animations": "Reduce UI animations",
|
"reduce-animations": "Reduce UI animations",
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
@ -228,7 +231,6 @@ const Texts = {
|
|||||||
"rocket-always-show": "Always show",
|
"rocket-always-show": "Always show",
|
||||||
"rocket-animation": "Rocket animation",
|
"rocket-animation": "Rocket animation",
|
||||||
"rocket-hide-queue": "Hide when queuing",
|
"rocket-hide-queue": "Hide when queuing",
|
||||||
"safari-failed-message": "Failed to run Better xCloud. Retrying, please wait...",
|
|
||||||
"saturation": "Saturation",
|
"saturation": "Saturation",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"screen": "Screen",
|
"screen": "Screen",
|
||||||
|
Reference in New Issue
Block a user