Compare commits

..

16 Commits

25 changed files with 413 additions and 79 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 5.4.0 // @version 5.4.1
// ==/UserScript== // ==/UserScript==

File diff suppressed because one or more lines are too long

View File

@ -69,7 +69,7 @@
height: var(--bx-button-height); height: var(--bx-button-height);
&:not(:only-child) { &:not(:only-child) {
margin-right: 4px; margin-right: 10px;
} }
} }
@ -117,3 +117,7 @@ button.bx-inactive {
opacity: 0.2; opacity: 0.2;
background: transparent !important; background: transparent !important;
} }
.bx-button-shortcut {
margin-top: 10px;
}

View File

@ -203,3 +203,23 @@
display: block; display: block;
width: 100%; width: 100%;
} }
.bx-debug-info {
button {
margin-top: 10px;
}
pre {
margin-top: 10px;
cursor: copy;
color: white;
padding: 8px;
border: 1px solid #2d2d2d;
background: #212121;
white-space: break-spaces;
&:hover {
background: #272727;
}
}
}

View File

@ -5,7 +5,7 @@
--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-button-height: 40px;
--bx-default-button-color: #2d3036; --bx-default-button-color: #2d3036;
--bx-default-button-hover-color: #515863; --bx-default-button-hover-color: #515863;

View File

@ -1,6 +1,9 @@
.bx-stream-settings-dialog { .bx-stream-settings-dialog {
display: flex; display: flex;
position: fixed; position: fixed;
top: 0;
right: 0;
bottom: 0;
z-index: var(--bx-stream-settings-z-index); z-index: var(--bx-stream-settings-z-index);
opacity: 0.98; opacity: 0.98;
user-select: none; user-select: none;
@ -22,10 +25,8 @@
} }
.bx-stream-settings-tabs { .bx-stream-settings-tabs {
position: fixed;
top: 0;
right: 420px;
display: flex; display: flex;
position: fixed;
flex-direction: column; flex-direction: column;
border-radius: 0 0 0 8px; border-radius: 0 0 0 8px;
box-shadow: 0px 0px 6px #000; box-shadow: 0px 0px 6px #000;
@ -60,10 +61,6 @@
.bx-stream-settings-tab-contents { .bx-stream-settings-tab-contents {
flex-direction: column; flex-direction: column;
position: fixed;
right: 0;
top: 0;
bottom: 0;
padding: 14px 14px 0; padding: 14px 14px 0;
width: 420px; width: 420px;
background: #1a1b1e; background: #1a1b1e;
@ -74,6 +71,8 @@
text-align: center; text-align: center;
box-shadow: 0px 0px 6px #000; box-shadow: 0px 0px 6px #000;
overflow: overlay; overflow: overlay;
margin-left: 56px;
z-index: 1;
> div[data-tab-group=mkb] { > div[data-tab-group=mkb] {
display: flex; display: flex;
@ -108,6 +107,11 @@
} }
} }
@media screen and (max-width: 500px) {
.bx-stream-settings-tab-contents {
width: calc(100vw - 56px);
}
}
.bx-stream-settings-row { .bx-stream-settings-row {
display: flex; display: flex;

View File

@ -0,0 +1,4 @@
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
<path d="M13.253 3.639c0-.758-.615-1.373-1.373-1.373H3.639c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V3.639zm0 16.481c0-.758-.615-1.373-1.373-1.373H3.639c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V20.12zm16.481 0c0-.758-.615-1.373-1.373-1.373H20.12c-.758 0-1.373.615-1.373 1.373v8.241c0 .758.615 1.373 1.373 1.373h8.241c.758 0 1.373-.615 1.373-1.373V20.12zM19.262 7.76h9.957"/>
<path d="M24.24 2.781v9.957"/>
</svg>

After

Width:  |  Height:  |  Size: 711 B

View File

@ -35,6 +35,7 @@ import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
import { UiSection } from "./enums/ui-sections"; import { UiSection } from "./enums/ui-sections";
import { HeaderSection } from "./modules/ui/header"; import { HeaderSection } from "./modules/ui/header";
import { GameTile } from "./modules/ui/game-tile"; import { GameTile } from "./modules/ui/game-tile";
import { ProductDetailsPage } from "./modules/ui/product-details";
// Handle login page // Handle login page
@ -198,6 +199,13 @@ window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED); BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
}); });
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
const component = (e as any).component;
if (component === 'product-details') {
ProductDetailsPage.injectShortcutButton();
}
});
function unload() { function unload() {
if (!STATES.isPlaying) { if (!STATES.isPlaying) {
return; return;

View File

@ -183,14 +183,14 @@ export class ControllerShortcut {
$fragment.appendChild($option); $fragment.appendChild($option);
} }
$container.dataset.hasGamepad = hasGamepad.toString();
if (hasGamepad) { if (hasGamepad) {
$select.appendChild($fragment); $select.appendChild($fragment);
$select.selectedIndex = 0; $select.selectedIndex = 0;
$select.dispatchEvent(new Event('change')); $select.dispatchEvent(new Event('input'));
} }
$container.dataset.hasGamepad = hasGamepad.toString();
} }
static #switchProfile(profile: string) { static #switchProfile(profile: string) {
@ -205,9 +205,9 @@ export class ControllerShortcut {
const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!; const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!;
$select.value = actions[button] || ''; $select.value = actions[button] || '';
BxEvent.dispatch($select, 'change', { BxEvent.dispatch($select, 'input', {
ignoreOnChange: true, ignoreOnChange: true,
}); });
} }
} }

View File

@ -1,4 +1,4 @@
import { SCRIPT_VERSION, STATES } from "@utils/global"; import { AppInterface, SCRIPT_VERSION, STATES } from "@utils/global";
import { BX_FLAGS } from "@utils/bx-flags"; import { BX_FLAGS } from "@utils/bx-flags";
import { getPref, PrefKey } from "@utils/preferences"; import { getPref, PrefKey } from "@utils/preferences";
import { VibrationManager } from "@modules/vibration-manager"; import { VibrationManager } from "@modules/vibration-manager";
@ -799,6 +799,22 @@ if (this.baseStorageKey in window.BX_EXPOSED.overrideSettings) {
str = str.substring(0, index) + codeSetCurrentlyFocusedInteractable + str.substring(index); str = str.substring(0, index) + codeSetCurrentlyFocusedInteractable + str.substring(index);
return str; return str;
}, },
// product-details-page.js#2388, 24.17.20
detectProductDetailsPage(str: string) {
let index = str.indexOf('{location:"ProductDetailPage",');
if (index === -1) {
return false;
}
index = str.indexOf('return', index - 40);
if (index === -1) {
return false;
}
str = str.substring(0, index) + 'BxEvent.dispatch(window, BxEvent.XCLOUD_RENDERING_COMPONENT, {component: "product-details"});' + str.substring(index);
return str;
},
}; };
let PATCH_ORDERS: PatchArray = [ let PATCH_ORDERS: PatchArray = [
@ -820,6 +836,7 @@ let PATCH_ORDERS: PatchArray = [
'exposeDialogRoutes', 'exposeDialogRoutes',
'enableTvRoutes', 'enableTvRoutes',
AppInterface && 'detectProductDetailsPage',
'overrideStorageGetSettings', 'overrideStorageGetSettings',
getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentlyFocusedInteractable', getPref(PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME) && 'patchSetCurrentlyFocusedInteractable',

View File

@ -1,6 +1,7 @@
import vertClarityBoost from "./shaders/clarity_boost.vert" with { type: "text" }; import vertClarityBoost from "./shaders/clarity_boost.vert" with { type: "text" };
import fsClarityBoost from "./shaders/clarity_boost.fs" with { type: "text" }; import fsClarityBoost from "./shaders/clarity_boost.fs" with { type: "text" };
import { BxLogger } from "@/utils/bx-logger"; import { BxLogger } from "@/utils/bx-logger";
import { getPref, PrefKey } from "@/utils/preferences";
const LOG_TAG = 'WebGL2Player'; const LOG_TAG = 'WebGL2Player';
@ -120,11 +121,13 @@ export class WebGL2Player {
} }
#setupShaders() { #setupShaders() {
BxLogger.info(LOG_TAG, 'Setting up', getPref(PrefKey.VIDEO_POWER_PREFERENCE));
const gl = this.#$canvas.getContext('webgl2', { const gl = this.#$canvas.getContext('webgl2', {
isBx: true, isBx: true,
antialias: true, antialias: true,
alpha: false, alpha: false,
powerPreference: 'high-performance', powerPreference: getPref(PrefKey.VIDEO_POWER_PREFERENCE),
}) as WebGL2RenderingContext; }) as WebGL2RenderingContext;
this.#gl = gl; this.#gl = gl;

View File

@ -260,9 +260,20 @@ export class StreamPlayer {
this.#resizePlayer(); this.#resizePlayer();
} }
reloadPlayer() {
this.#cleanUpWebGL2Player();
this.#playerType = StreamPlayerType.VIDEO;
this.setPlayerType(StreamPlayerType.WEBGL2, false);
}
#cleanUpWebGL2Player() {
// Clean up WebGL2 Player
this.#webGL2Player?.destroy();
this.#webGL2Player = null;
}
destroy() { destroy() {
// Cleanup WebGL2 Player this.#cleanUpWebGL2Player();
this.#webGL2Player?.destroy();
this.#webGL2Player = null;
} }
} }

View File

@ -8,6 +8,7 @@ export function onChangeVideoPlayerType() {
const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE); const playerType = getPref(PrefKey.VIDEO_PLAYER_TYPE);
const $videoProcessing = document.getElementById('bx_setting_video_processing') as HTMLSelectElement; const $videoProcessing = document.getElementById('bx_setting_video_processing') as HTMLSelectElement;
const $videoSharpness = document.getElementById('bx_setting_video_sharpness') as HTMLElement; const $videoSharpness = document.getElementById('bx_setting_video_sharpness') as HTMLElement;
const $videoPowerPreference = document.getElementById('bx_setting_video_power_preference') as HTMLElement;
let isDisabled = false; let isDisabled = false;
@ -28,6 +29,9 @@ export function onChangeVideoPlayerType() {
$videoProcessing.disabled = isDisabled; $videoProcessing.disabled = isDisabled;
$videoSharpness.dataset.disabled = isDisabled.toString(); $videoSharpness.dataset.disabled = isDisabled.toString();
// Hide Power Preference setting if renderer isn't WebGL2
$videoPowerPreference.closest('.bx-stream-settings-row')!.classList.toggle('bx-gone', playerType !== StreamPlayerType.WEBGL2);
updateVideoPlayer(); updateVideoPlayer();
} }

View File

@ -112,6 +112,17 @@ export class StreamSettings {
}, { }, {
pref: PrefKey.VIDEO_PROCESSING, pref: PrefKey.VIDEO_PROCESSING,
onChange: updateVideoPlayer, onChange: updateVideoPlayer,
}, {
pref: PrefKey.VIDEO_POWER_PREFERENCE,
onChange: () => {
const streamPlayer = STATES.currentStream.streamPlayer;
if (!streamPlayer) {
return;
}
streamPlayer.reloadPlayer();
updateVideoPlayer();
},
}, { }, {
pref: PrefKey.VIDEO_SHARPNESS, pref: PrefKey.VIDEO_SHARPNESS,
onChange: updateVideoPlayer, onChange: updateVideoPlayer,
@ -324,6 +335,9 @@ export class StreamSettings {
(window as any).BX_EXPOSED.disableGamepadPolling = true; (window as any).BX_EXPOSED.disableGamepadPolling = true;
BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN); BxEvent.dispatch(window, BxEvent.XCLOUD_DIALOG_SHOWN);
// Update video's settings
onChangeVideoPlayerType();
} }
hide() { hide() {
@ -476,6 +490,11 @@ export class StreamSettings {
$sibling && $sibling.focus(); $sibling && $sibling.focus();
return; return;
} }
// If it's the first/last item -> loop around
const pseudo = direction === NavigationDirection.UP ? 'last-of-type' : 'first-of-type';
const $target = this.$tabs!.querySelector(`svg:not(.bx-gone):${pseudo}`);
$target && ($target as HTMLElement).focus();
} else if (direction === NavigationDirection.RIGHT) { } else if (direction === NavigationDirection.RIGHT) {
this.#focusFirstVisibleSetting(); this.#focusFirstVisibleSetting();
} }
@ -513,6 +532,12 @@ export class StreamSettings {
return; return;
} }
} }
// If it's the first/last item -> loop around
// TODO: bugged if pseudo is "first-of-type" and the first setting is disabled
const pseudo = direction === NavigationDirection.UP ? ':last-of-type' : '';
const $target = this.$settings!.querySelector(`div[data-tab-group]:not(.bx-gone) div[data-focus-container]${pseudo} [tabindex="0"]:not(:disabled):last-of-type`);
$target && ($target as HTMLElement).focus();
} else if (direction === NavigationDirection.LEFT || direction === NavigationDirection.RIGHT) { } else if (direction === NavigationDirection.LEFT || direction === NavigationDirection.RIGHT) {
// Find all child elements with tabindex // Find all child elements with tabindex
const children = Array.from($parent.querySelectorAll('[tabindex="0"]')); const children = Array.from($parent.querySelectorAll('[tabindex="0"]'));
@ -643,6 +668,15 @@ export class StreamSettings {
this.hide(); this.hide();
}); });
// Close dialog when not clicking on any child elements in the dialog
$container.addEventListener('click', e => {
if (e.target === $container) {
e.preventDefault();
e.stopPropagation();
this.hide();
}
});
for (const settingTab of this.SETTINGS_UI) { for (const settingTab of this.SETTINGS_UI) {
if (!settingTab) { if (!settingTab) {
continue; continue;
@ -753,8 +787,5 @@ export class StreamSettings {
document.documentElement.appendChild($overlay); document.documentElement.appendChild($overlay);
document.documentElement.appendChild($container); document.documentElement.appendChild($container);
// Update video's settings
onChangeVideoPlayerType();
} }
} }

View File

@ -1,4 +1,4 @@
import { STATES, AppInterface, SCRIPT_VERSION } from "@utils/global"; import { STATES, AppInterface, SCRIPT_VERSION, deepClone } from "@utils/global";
import { CE, createButton, ButtonStyle } from "@utils/html"; import { CE, createButton, ButtonStyle } from "@utils/html";
import { BxIcon } from "@utils/bx-icon"; import { BxIcon } from "@utils/bx-icon";
import { getPreferredServerRegion } from "@utils/region"; import { getPreferredServerRegion } from "@utils/region";
@ -9,6 +9,8 @@ import { PatcherCache } from "../patcher";
import { UserAgentProfile } from "@enums/user-agent"; import { UserAgentProfile } from "@enums/user-agent";
import { BxSelectElement } from "@/web-components/bx-select"; import { BxSelectElement } from "@/web-components/bx-select";
import { StreamSettings } from "../stream/stream-settings"; import { StreamSettings } from "../stream/stream-settings";
import { BX_FLAGS } from "@/utils/bx-flags";
import { Toast } from "@/utils/toast";
const SETTINGS_UI = { const SETTINGS_UI = {
'Better xCloud': { 'Better xCloud': {
@ -455,6 +457,47 @@ export function setupSettingsUi() {
$wrapper.appendChild(CE('div', {'class': 'bx-settings-app-version'}, `xCloud website version ${appVersion} (${appDate})`)); $wrapper.appendChild(CE('div', {'class': 'bx-settings-app-version'}, `xCloud website version ${appVersion} (${appDate})`));
} catch (e) {} } catch (e) {}
// Show Debug info
const debugInfo = deepClone(BX_FLAGS.DeviceInfo);
const debugSettings = [
PrefKey.STREAM_TARGET_RESOLUTION,
PrefKey.STREAM_CODEC_PROFILE,
PrefKey.VIDEO_PLAYER_TYPE,
PrefKey.VIDEO_PROCESSING,
PrefKey.VIDEO_SHARPNESS,
];
debugInfo['settings'] = {};
for (const key of debugSettings) {
debugInfo['settings'][key] = getPref(key);
}
const $debugInfo = CE('div', {class: 'bx-debug-info'},
createButton({
label: 'Debug info',
style: ButtonStyle.GHOST | ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE,
onClick: e => {
console.log(e);
(e.target as HTMLElement).closest('button')?.nextElementSibling?.classList.toggle('bx-gone');
},
}),
CE('pre', {
class: 'bx-gone',
on: {
click: async (e: Event) => {
try {
await navigator.clipboard.writeText((e.target as HTMLElement).innerText);
Toast.show('Copied to clipboard', '', {instant: true});
} catch (err) {
console.error('Failed to copy: ', err);
}
},
},
}, '```\n' + JSON.stringify(debugInfo, null, ' ') + '\n```'),
);
$wrapper.appendChild($debugInfo);
$container.appendChild($wrapper); $container.appendChild($wrapper);
// Add Settings UI to the web page // Add Settings UI to the web page

View File

@ -89,15 +89,15 @@ export class GuideMenu {
// "Stream settings" button // "Stream settings" button
buttons.push(GuideMenu.#BUTTONS.streamSetting); buttons.push(GuideMenu.#BUTTONS.streamSetting);
// "App settings" & "Close app" buttons // "App settings" button
if (AppInterface) { AppInterface && buttons.push(GuideMenu.#BUTTONS.appSettings);
buttons.push(GuideMenu.#BUTTONS.appSettings);
buttons.push(GuideMenu.#BUTTONS.closeApp);
}
// Reload page // "Reload page" button
buttons.push(GuideMenu.#BUTTONS.reloadPage); buttons.push(GuideMenu.#BUTTONS.reloadPage);
// "Close app" buttons
AppInterface && buttons.push(GuideMenu.#BUTTONS.closeApp);
const $buttons = GuideMenu.#renderButtons(buttons); const $buttons = GuideMenu.#renderButtons(buttons);
const $lastDivider = $dividers[$dividers.length - 1]; const $lastDivider = $dividers[$dividers.length - 1];

View File

@ -0,0 +1,36 @@
import { BX_FLAGS } from "@/utils/bx-flags";
import { BxIcon } from "@/utils/bx-icon";
import { AppInterface } from "@/utils/global";
import { ButtonStyle, createButton } from "@/utils/html";
import { t } from "@/utils/translation";
export class ProductDetailsPage {
private static $btnShortcut = createButton({
classes: ['bx-button-shortcut'],
icon: BxIcon.CREATE_SHORTCUT,
label: t('create-shortcut'),
style: ButtonStyle.FOCUSABLE,
tabIndex: 0,
onClick: e => {
AppInterface && AppInterface.createShortcut(window.location.pathname.substring(6));
},
});
private static shortcutTimeoutId: number | null = null;
static injectShortcutButton() {
if (!AppInterface || BX_FLAGS.DeviceInfo?.deviceType !== 'android') {
return;
}
ProductDetailsPage.shortcutTimeoutId && clearTimeout(ProductDetailsPage.shortcutTimeoutId);
ProductDetailsPage.shortcutTimeoutId = window.setTimeout(() => {
// Find action buttons container
const $container = document.querySelector('div[class*=ActionButtons-module__container]');
if ($container) {
this.$btnShortcut.style.width = $container.getBoundingClientRect().width + 'px';
$container.parentElement?.appendChild(ProductDetailsPage.$btnShortcut);
}
}, 500);
}
}

View File

@ -48,6 +48,8 @@ export enum BxEvent {
XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown', XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown',
XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed', XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed',
XCLOUD_RENDERING_COMPONENT = 'bx-xcloud-rendering-page',
} }
export enum XcloudEvent { export enum XcloudEvent {

View File

@ -11,6 +11,11 @@ type BxFlags = Partial<{
FeatureGates: {[key: string]: boolean} | null, FeatureGates: {[key: string]: boolean} | null,
IsSupportedTvBrowser: boolean, IsSupportedTvBrowser: boolean,
DeviceInfo: Partial<{
deviceType: 'android' | 'android-tv' | 'webos' | 'unknown',
userAgent?: string,
}>,
}> }>
// Setup flags // Setup flags
@ -25,6 +30,10 @@ const DEFAULT_FLAGS: BxFlags = {
ForceNativeMkbTitles: [], ForceNativeMkbTitles: [],
FeatureGates: null, FeatureGates: null,
DeviceInfo: {
deviceType: 'unknown',
},
} }
export const BX_FLAGS: BxFlags = Object.assign(DEFAULT_FLAGS, window.BX_FLAGS || {}); export const BX_FLAGS: BxFlags = Object.assign(DEFAULT_FLAGS, window.BX_FLAGS || {});
@ -32,4 +41,8 @@ try {
delete window.BX_FLAGS; delete window.BX_FLAGS;
} catch (e) {} } catch (e) {}
if (!BX_FLAGS.DeviceInfo!.userAgent) {
BX_FLAGS.DeviceInfo!.userAgent = window.navigator.userAgent;
}
export const NATIVE_FETCH = window.fetch; export const NATIVE_FETCH = window.fetch;

View File

@ -1,6 +1,7 @@
import iconCommand from "@assets/svg/command.svg" with { type: "text" }; import iconCommand from "@assets/svg/command.svg" with { type: "text" };
import iconController from "@assets/svg/controller.svg" with { type: "text" }; import iconController from "@assets/svg/controller.svg" with { type: "text" };
import iconCopy from "@assets/svg/copy.svg" with { type: "text" }; import iconCopy from "@assets/svg/copy.svg" with { type: "text" };
import iconCreateShortcut from "@assets/svg/create-shortcut.svg" with { type: "text" };
import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" }; import iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
import iconDisplay from "@assets/svg/display.svg" with { type: "text" }; import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
import iconHome from "@assets/svg/home.svg" with { type: "text" }; import iconHome from "@assets/svg/home.svg" with { type: "text" };
@ -11,9 +12,9 @@ import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" }; import iconRemotePlay from "@assets/svg/remote-play.svg" with { type: "text" };
import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" }; import iconStreamSettings from "@assets/svg/stream-settings.svg" with { type: "text" };
import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" }; import iconStreamStats from "@assets/svg/stream-stats.svg" with { type: "text" };
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
import iconTouchControlDisable from "@assets/svg/touch-control-disable.svg" with { type: "text" }; import iconTouchControlDisable from "@assets/svg/touch-control-disable.svg" with { type: "text" };
import iconTouchControlEnable from "@assets/svg/touch-control-enable.svg" with { type: "text" };
import iconTrash from "@assets/svg/trash.svg" with { type: "text" };
import iconVirtualController from "@assets/svg/virtual-controller.svg" with { type: "text" }; import iconVirtualController from "@assets/svg/virtual-controller.svg" with { type: "text" };
// Game Bar // Game Bar
@ -37,6 +38,7 @@ export const BxIcon = {
STREAM_STATS: iconStreamStats, STREAM_STATS: iconStreamStats,
COMMAND: iconCommand, COMMAND: iconCommand,
CONTROLLER: iconController, CONTROLLER: iconController,
CREATE_SHORTCUT: iconCreateShortcut,
DISPLAY: iconDisplay, DISPLAY: iconDisplay,
HOME: iconHome, HOME: iconHome,
NATIVE_MKB: iconNativeMkb, NATIVE_MKB: iconNativeMkb,

View File

@ -4,6 +4,8 @@ import { getPref, PrefKey } from "./preferences";
export let FeatureGates: {[key: string]: boolean} = { export let FeatureGates: {[key: string]: boolean} = {
'PwaPrompt': false, 'PwaPrompt': false,
'EnableWifiWarnings': false, 'EnableWifiWarnings': false,
'EnableUpdateRequiredPage': false,
'ShowForcedUpdateScreen': false,
}; };
// Disable context menu in Home page // Disable context menu in Home page

View File

@ -35,18 +35,23 @@ function createElement<T=HTMLElement>(elmName: string, props: {[index: string]:
if (hasNs) { if (hasNs) {
$elm.setAttributeNS(null, key, props[key]); $elm.setAttributeNS(null, key, props[key]);
} else { } else {
$elm.setAttribute(key, props[key]); if (key === 'on') {
for (const eventName in props[key]) {
$elm.addEventListener(eventName, props[key][eventName]);
}
} else {
$elm.setAttribute(key, props[key]);
}
} }
} }
for (let i = 2, size = arguments.length; i < size; i++) { for (let i = 2, size = arguments.length; i < size; i++) {
const arg = arguments[i]; const arg = arguments[i];
const argType = typeof arg;
if (argType === 'string' || argType === 'number') { if (arg instanceof Node) {
$elm.appendChild(document.createTextNode(arg));
} else if (arg) {
$elm.appendChild(arg); $elm.appendChild(arg);
} else if (arg !== null && typeof arg !== 'undefined') {
$elm.appendChild(document.createTextNode(arg));
} }
} }

View File

@ -9,6 +9,7 @@ import { StreamPlayerType, StreamVideoProcessing } from "@enums/stream-player";
import { UserAgentProfile } from "@/enums/user-agent"; import { UserAgentProfile } from "@/enums/user-agent";
import { UiSection } from "@/enums/ui-sections"; import { UiSection } from "@/enums/ui-sections";
import { BypassServers } from "@/enums/bypass-servers"; import { BypassServers } from "@/enums/bypass-servers";
import { BX_FLAGS } from "./bx-flags";
export enum PrefKey { export enum PrefKey {
LAST_UPDATE_CHECK = 'version_last_check', LAST_UPDATE_CHECK = 'version_last_check',
@ -82,6 +83,7 @@ export enum PrefKey {
VIDEO_PLAYER_TYPE = 'video_player_type', VIDEO_PLAYER_TYPE = 'video_player_type',
VIDEO_PROCESSING = 'video_processing', VIDEO_PROCESSING = 'video_processing',
VIDEO_POWER_PREFERENCE = 'video_power_preference',
VIDEO_SHARPNESS = 'video_sharpness', VIDEO_SHARPNESS = 'video_sharpness',
VIDEO_RATIO = 'video_ratio', VIDEO_RATIO = 'video_ratio',
VIDEO_BRIGHTNESS = 'video_brightness', VIDEO_BRIGHTNESS = 'video_brightness',
@ -557,7 +559,7 @@ export class Preferences {
[PrefKey.UI_CONTROLLER_FRIENDLY]: { [PrefKey.UI_CONTROLLER_FRIENDLY]: {
label: t('controller-friendly-ui'), label: t('controller-friendly-ui'),
default: false, default: !STATES.browser.capabilities.touch || BX_FLAGS.DeviceInfo?.deviceType === "android-tv",
}, },
[PrefKey.UI_LAYOUT]: { [PrefKey.UI_LAYOUT]: {
@ -610,7 +612,7 @@ export class Preferences {
[PrefKey.USER_AGENT_PROFILE]: { [PrefKey.USER_AGENT_PROFILE]: {
label: t('user-agent-profile'), label: t('user-agent-profile'),
note: '⚠️ ' + t('unexpected-behavior'), note: '⚠️ ' + t('unexpected-behavior'),
default: 'default', default: BX_FLAGS.DeviceInfo?.deviceType === 'android-tv' ? UserAgentProfile.VR_OCULUS : 'default',
options: { options: {
[UserAgentProfile.DEFAULT]: t('default'), [UserAgentProfile.DEFAULT]: t('default'),
[UserAgentProfile.WINDOWS_EDGE]: 'Edge + Windows', [UserAgentProfile.WINDOWS_EDGE]: 'Edge + Windows',
@ -637,6 +639,15 @@ export class Preferences {
[StreamVideoProcessing.CAS]: t('amd-fidelity-cas'), [StreamVideoProcessing.CAS]: t('amd-fidelity-cas'),
}, },
}, },
[PrefKey.VIDEO_POWER_PREFERENCE]: {
label: t('gpu-configuration'),
default: 'default',
options: {
'default': t('default'),
'high-performance': t('high-performance'),
'low-power': t('low-power'),
},
},
[PrefKey.VIDEO_SHARPNESS]: { [PrefKey.VIDEO_SHARPNESS]: {
label: t('sharpness'), label: t('sharpness'),
type: SettingElementType.NUMBER_STEPPER, type: SettingElementType.NUMBER_STEPPER,

View File

@ -76,6 +76,7 @@ const Texts = {
"controller-shortcuts-xbox-note": "Button to open the Guide menu", "controller-shortcuts-xbox-note": "Button to open the Guide menu",
"controller-vibration": "Controller vibration", "controller-vibration": "Controller vibration",
"copy": "Copy", "copy": "Copy",
"create-shortcut": "Create shortcut",
"custom": "Custom", "custom": "Custom",
"deadzone-counterweight": "Deadzone counterweight", "deadzone-counterweight": "Deadzone counterweight",
"decrease": "Decrease", "decrease": "Decrease",
@ -109,6 +110,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...",
"gpu-configuration": "GPU configuration",
"help": "Help", "help": "Help",
"hide": "Hide", "hide": "Hide",
"hide-idle-cursor": "Hide mouse cursor on idle", "hide-idle-cursor": "Hide mouse cursor on idle",
@ -116,6 +118,7 @@ const Texts = {
"hide-sections": "Hide sections", "hide-sections": "Hide sections",
"hide-system-menu-icon": "Hide System menu's icon", "hide-system-menu-icon": "Hide System menu's icon",
"hide-touch-controller": "Hide touch controller", "hide-touch-controller": "Hide touch controller",
"high-performance": "High performance",
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity", "horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
"horizontal-sensitivity": "Horizontal sensitivity", "horizontal-sensitivity": "Horizontal sensitivity",
"ignore": "Ignore", "ignore": "Ignore",
@ -129,6 +132,7 @@ const Texts = {
"left-stick": "Left stick", "left-stick": "Left stick",
"loading-screen": "Loading screen", "loading-screen": "Loading screen",
"local-co-op": "Local co-op", "local-co-op": "Local co-op",
"low-power": "Low power",
"map-mouse-to": "Map mouse to", "map-mouse-to": "Map mouse to",
"may-not-work-properly": "May not work properly!", "may-not-work-properly": "May not work properly!",
"menu": "Menu", "menu": "Menu",

View File

@ -22,7 +22,6 @@ export class BxSelectElement {
}); });
const isMultiple = $select.multiple; const isMultiple = $select.multiple;
let visibleIndex = $select.selectedIndex;
let $checkBox: HTMLInputElement; let $checkBox: HTMLInputElement;
let $label: HTMLElement; let $label: HTMLElement;
@ -42,7 +41,7 @@ export class BxSelectElement {
}); });
$checkBox.addEventListener('input', e => { $checkBox.addEventListener('input', e => {
const $option = getOptionAtIndex(visibleIndex); const $option = getOptionAtIndex($select.selectedIndex);
$option && ($option.selected = (e.target as HTMLInputElement).checked); $option && ($option.selected = (e.target as HTMLInputElement).checked);
$select.dispatchEvent(new Event('input')); $select.dispatchEvent(new Event('input'));
@ -61,7 +60,7 @@ export class BxSelectElement {
const render = () => { const render = () => {
// console.log('options', this.options, 'selectedIndices', this.selectedIndices, 'selectedOptions', this.selectedOptions); // console.log('options', this.options, 'selectedIndices', this.selectedIndices, 'selectedOptions', this.selectedOptions);
visibleIndex = normalizeIndex(visibleIndex); const visibleIndex = normalizeIndex($select.selectedIndex);
const $option = getOptionAtIndex(visibleIndex); const $option = getOptionAtIndex(visibleIndex);
let content = ''; let content = '';
@ -108,11 +107,10 @@ export class BxSelectElement {
const onPrevNext = (e: Event) => { const onPrevNext = (e: Event) => {
const goNext = e.target === $btnNext; const goNext = e.target === $btnNext;
const currentIndex = visibleIndex; const currentIndex = $select.selectedIndex;
let newIndex = goNext ? currentIndex + 1 : currentIndex - 1; let newIndex = goNext ? currentIndex + 1 : currentIndex - 1;
newIndex = normalizeIndex(newIndex); newIndex = normalizeIndex(newIndex);
visibleIndex = newIndex;
if (!isMultiple && newIndex !== currentIndex) { if (!isMultiple && newIndex !== currentIndex) {
$select.selectedIndex = newIndex; $select.selectedIndex = newIndex;
} }