mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-02 03:16:42 +02:00
Controller customization feature
This commit is contained in:
@@ -106,14 +106,14 @@ export const BxExposed = {
|
||||
}
|
||||
|
||||
// Remove native MKB support on mobile browsers or by user's choice
|
||||
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.OFF) {
|
||||
if (getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.OFF) {
|
||||
supportedInputTypes = supportedInputTypes.filter(i => i !== SupportedInputType.MKB);
|
||||
}
|
||||
|
||||
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(SupportedInputType.MKB);
|
||||
|
||||
if (STATES.userAgent.capabilities.touch) {
|
||||
let touchControllerAvailability = getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE);
|
||||
let touchControllerAvailability = getPref(PrefKey.TOUCH_CONTROLLER_MODE);
|
||||
|
||||
// Disable touch control when gamepad found
|
||||
if (touchControllerAvailability !== TouchControllerMode.OFF && getPref(PrefKey.TOUCH_CONTROLLER_AUTO_OFF)) {
|
||||
@@ -185,8 +185,8 @@ export const BxExposed = {
|
||||
}
|
||||
},
|
||||
|
||||
handleControllerShortcut: isFullVersion() && ControllerShortcut.handle,
|
||||
resetControllerShortcut: isFullVersion() && ControllerShortcut.reset,
|
||||
handleControllerShortcut: isFullVersion() ? ControllerShortcut.handle : () => {},
|
||||
resetControllerShortcut: isFullVersion() ? ControllerShortcut.reset : () => {},
|
||||
|
||||
overrideSettings: {
|
||||
Tv_settings: {
|
||||
|
@@ -12,6 +12,7 @@ import iconEyeSlash from "@assets/svg/eye-slash.svg" with { type: "text" };
|
||||
import iconHome from "@assets/svg/home.svg" with { type: "text" };
|
||||
import iconNativeMkb from "@assets/svg/native-mkb.svg" with { type: "text" };
|
||||
import iconNew from "@assets/svg/new.svg" with { type: "text" };
|
||||
import iconPencil from "@assets/svg/pencil-simple-line.svg" with { type: "text" };
|
||||
import iconPower from "@assets/svg/power.svg" with { type: "text" };
|
||||
import iconQuestion from "@assets/svg/question.svg" with { type: "text" };
|
||||
import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
|
||||
@@ -53,6 +54,7 @@ export const BxIcon = {
|
||||
HOME: iconHome,
|
||||
NATIVE_MKB: iconNativeMkb,
|
||||
NEW: iconNew,
|
||||
MANAGE: iconPencil,
|
||||
COPY: iconCopy,
|
||||
TRASH: iconTrash,
|
||||
CURSOR_TEXT: iconCursorText,
|
||||
|
@@ -9,7 +9,7 @@ export function addCss() {
|
||||
const STYLUS_CSS = renderStylus() as unknown as string;
|
||||
let css = STYLUS_CSS;
|
||||
|
||||
const PREF_HIDE_SECTIONS = getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS);
|
||||
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
|
||||
const selectorToHide = [];
|
||||
|
||||
// Hide "News" section
|
||||
@@ -18,7 +18,7 @@ export function addCss() {
|
||||
}
|
||||
|
||||
// Hide BYOG section
|
||||
if (getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.BYOG)) {
|
||||
if (getPref(PrefKey.BLOCK_FEATURES).includes(BlockFeature.BYOG)) {
|
||||
selectorToHide.push('#BodyContent > div[class*=ByogRow-module__container___]');
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export function addCss() {
|
||||
}
|
||||
|
||||
// Hide "Start a party" button in the Guide menu
|
||||
if (getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
|
||||
if (getPref(PrefKey.BLOCK_FEATURES).includes(BlockFeature.FRIENDS)) {
|
||||
selectorToHide.push('#gamepass-dialog-root div[class^=AchievementsPreview-module__container] + button[class*=HomeLandingPage-module__button]');
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ body::-webkit-scrollbar {
|
||||
|
||||
|
||||
export function preloadFonts() {
|
||||
const $link = CE<HTMLLinkElement>('link', {
|
||||
const $link = CE('link', {
|
||||
rel: 'preload',
|
||||
href: 'https://redphx.github.io/better-xcloud/fonts/promptfont.otf',
|
||||
as: 'font',
|
||||
|
@@ -12,13 +12,13 @@ export let FeatureGates: { [key: string]: boolean } = {
|
||||
};
|
||||
|
||||
// Enable Native Mouse & Keyboard
|
||||
const nativeMkbMode = getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE);
|
||||
const nativeMkbMode = getPref(PrefKey.NATIVE_MKB_MODE);
|
||||
if (nativeMkbMode !== NativeMkbMode.DEFAULT) {
|
||||
FeatureGates.EnableMouseAndKeyboard = nativeMkbMode === NativeMkbMode.ON;
|
||||
}
|
||||
|
||||
// Disable chat feature
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
|
||||
if (blockFeatures.includes(BlockFeature.CHAT)) {
|
||||
FeatureGates.EnableGuideChatTab = false;
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ import { setNearby } from "./navigation-utils";
|
||||
import type { NavigationNearbyElements } from "@/modules/ui/dialog/navigation-dialog";
|
||||
import type { PresetRecord, AllPresets } from "@/types/presets";
|
||||
import { t } from "./translation";
|
||||
import type { BxSelectElement } from "@/web-components/bx-select";
|
||||
|
||||
export enum ButtonStyle {
|
||||
PRIMARY = 1,
|
||||
@@ -14,10 +15,11 @@ export enum ButtonStyle {
|
||||
FOCUSABLE = 1 << 6,
|
||||
FULL_WIDTH = 1 << 7,
|
||||
FULL_HEIGHT = 1 << 8,
|
||||
TALL = 1 << 9,
|
||||
CIRCULAR = 1 << 10,
|
||||
NORMAL_CASE = 1 << 11,
|
||||
NORMAL_LINK = 1 << 12,
|
||||
AUTO_HEIGHT = 1 << 9,
|
||||
TALL = 1 << 10,
|
||||
CIRCULAR = 1 << 11,
|
||||
NORMAL_CASE = 1 << 12,
|
||||
NORMAL_LINK = 1 << 13,
|
||||
}
|
||||
|
||||
const ButtonStyleClass = {
|
||||
@@ -30,6 +32,7 @@ const ButtonStyleClass = {
|
||||
[ButtonStyle.FOCUSABLE]: 'bx-focusable',
|
||||
[ButtonStyle.FULL_WIDTH]: 'bx-full-width',
|
||||
[ButtonStyle.FULL_HEIGHT]: 'bx-full-height',
|
||||
[ButtonStyle.AUTO_HEIGHT]: 'bx-auto-height',
|
||||
[ButtonStyle.TALL]: 'bx-tall',
|
||||
[ButtonStyle.CIRCULAR]: 'bx-circular',
|
||||
[ButtonStyle.NORMAL_CASE]: 'bx-normal-case',
|
||||
@@ -62,23 +65,41 @@ type CreateElementOptions = {
|
||||
[ key: string ]: (e: Event) => void;
|
||||
};
|
||||
_dataset?: {
|
||||
[ key: string ]: string | number;
|
||||
[ key: string ]: string | number | boolean;
|
||||
};
|
||||
_nearby?: NavigationNearbyElements;
|
||||
};
|
||||
|
||||
type HTMLElementTagNameMap = {
|
||||
a: HTMLAnchorElement;
|
||||
button: HTMLButtonElement;
|
||||
canvas: HTMLCanvasElement;
|
||||
datalist: HTMLDataListElement,
|
||||
div: HTMLDivElement;
|
||||
fieldset: HTMLFieldSetElement;
|
||||
input: HTMLInputElement;
|
||||
label: HTMLLabelElement;
|
||||
link: HTMLLinkElement;
|
||||
optgroup: HTMLOptGroupElement;
|
||||
option: HTMLOptionElement;
|
||||
p: HTMLParagraphElement;
|
||||
select: HTMLSelectElement;
|
||||
span: HTMLSpanElement;
|
||||
style: HTMLStyleElement;
|
||||
[key: string] : HTMLElement;
|
||||
};
|
||||
|
||||
function createElement<T=HTMLElement>(elmName: string, props: CreateElementOptions={}, ..._: any): T {
|
||||
function createElement<T extends keyof HTMLElementTagNameMap>(elmName: T, props: CreateElementOptions={}, ..._: any): HTMLElementTagNameMap[T] {
|
||||
let $elm;
|
||||
const hasNs = 'xmlns' in props;
|
||||
|
||||
// console.trace('createElement', elmName, props);
|
||||
|
||||
if (hasNs) {
|
||||
$elm = document.createElementNS(props.xmlns, elmName);
|
||||
$elm = document.createElementNS(props.xmlns, elmName as string);
|
||||
delete props.xmlns;
|
||||
} else {
|
||||
$elm = document.createElement(elmName);
|
||||
$elm = document.createElement(elmName as string);
|
||||
}
|
||||
|
||||
if (props._nearby) {
|
||||
@@ -121,7 +142,7 @@ function createElement<T=HTMLElement>(elmName: string, props: CreateElementOptio
|
||||
}
|
||||
}
|
||||
|
||||
return $elm as T;
|
||||
return $elm as HTMLElementTagNameMap[T];
|
||||
}
|
||||
|
||||
|
||||
@@ -137,13 +158,13 @@ export function createButton<T=HTMLButtonElement>(options: BxButtonOptions): T {
|
||||
|
||||
// Create base button element
|
||||
if (options.url) {
|
||||
$btn = CE<HTMLAnchorElement>('a', {
|
||||
$btn = CE('a', {
|
||||
class: 'bx-button',
|
||||
href: options.url,
|
||||
target: '_blank',
|
||||
});
|
||||
} else {
|
||||
$btn = CE<HTMLButtonElement>('button', {
|
||||
$btn = CE('button', {
|
||||
class: 'bx-button',
|
||||
type: 'button',
|
||||
});
|
||||
@@ -185,7 +206,7 @@ export function createButton<T=HTMLButtonElement>(options: BxButtonOptions): T {
|
||||
export function createSettingRow(label: string, $control: HTMLElement | false | undefined, options: SettingsRowOptions={}) {
|
||||
let $label: HTMLElement;
|
||||
|
||||
const $row = CE<HTMLLabelElement>('label', { class: 'bx-settings-row' },
|
||||
const $row = CE('label', { class: 'bx-settings-row' },
|
||||
$label = CE('span', { class: 'bx-settings-label' },
|
||||
label,
|
||||
options.$note,
|
||||
@@ -267,7 +288,7 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
||||
removeChildElements($select);
|
||||
|
||||
if (options.addOffValue) {
|
||||
const $option = CE<HTMLOptionElement>('option', { value: 0 }, t('off'));
|
||||
const $option = CE('option', { value: 0 }, t('off'));
|
||||
$option.selected = selectedValue === 0;
|
||||
|
||||
$select.appendChild($option);
|
||||
@@ -287,7 +308,7 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
||||
const selected = selectedValue === record.id;
|
||||
const name = options.selectedIndicator && selected ? '✅ ' + record.name : record.name;
|
||||
|
||||
const $option = CE<HTMLOptionElement>('option', { value: record.id }, name);
|
||||
const $option = CE('option', { value: record.id }, name);
|
||||
if (selected) {
|
||||
$option.selected = true;
|
||||
}
|
||||
@@ -301,6 +322,48 @@ export function renderPresetsList<T extends PresetRecord>($select: HTMLSelectEle
|
||||
}
|
||||
}
|
||||
|
||||
export function calculateSelectBoxes($root: HTMLElement) {
|
||||
const selects = Array.from<HTMLSelectElement>($root.querySelectorAll('div.bx-select:not([data-calculated]) select'));
|
||||
|
||||
for (const $select of selects) {
|
||||
const $parent = $select.parentElement! as BxSelectElement;
|
||||
|
||||
// Don't apply to select.bx-full-width elements
|
||||
if ($parent.classList.contains('bx-full-width')) {
|
||||
$parent.dataset.calculated = 'true';
|
||||
continue;
|
||||
}
|
||||
|
||||
const rect = $select.getBoundingClientRect();
|
||||
|
||||
let $label: HTMLElement;
|
||||
let width = Math.ceil(rect.width);
|
||||
if (!width) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = $parent.querySelector<HTMLElement>($select.multiple ? '.bx-select-value' : 'div')!;
|
||||
if ($parent.isControllerFriendly) {
|
||||
// Adjust width for controller-friendly UI
|
||||
if ($select.multiple) {
|
||||
width += 20; // Add checkbox's width
|
||||
}
|
||||
|
||||
// Reduce width if it has <optgroup>
|
||||
if ($select.querySelector('optgroup')) {
|
||||
width -= 15;
|
||||
}
|
||||
} else {
|
||||
width += 10;
|
||||
}
|
||||
|
||||
// Set min-width
|
||||
$select.style.left = '0';
|
||||
$label.style.minWidth = width + 'px';
|
||||
$parent.dataset.calculated = 'true';
|
||||
};
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/20732091
|
||||
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
export function humanFileSize(size: number) {
|
||||
|
45
src/utils/local-db/controller-customizations-table.ts
Normal file
45
src/utils/local-db/controller-customizations-table.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { ControllerCustomizationPresetRecord, PresetRecords } from "@/types/presets";
|
||||
import { LocalDb } from "./local-db";
|
||||
import { BasePresetsTable } from "./base-presets-table";
|
||||
import { GamepadKey } from "@/enums/gamepad";
|
||||
|
||||
export const enum ControllerCustomizationDefaultPresetId {
|
||||
OFF = 0,
|
||||
BAYX = -1,
|
||||
|
||||
DEFAULT = OFF,
|
||||
};
|
||||
|
||||
export class ControllerCustomizationsTable extends BasePresetsTable<ControllerCustomizationPresetRecord> {
|
||||
private static instance: ControllerCustomizationsTable;
|
||||
public static getInstance = () => ControllerCustomizationsTable.instance ?? (ControllerCustomizationsTable.instance = new ControllerCustomizationsTable(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS));
|
||||
|
||||
protected readonly TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS;
|
||||
protected DEFAULT_PRESETS: PresetRecords<ControllerCustomizationPresetRecord> = {
|
||||
[ControllerCustomizationDefaultPresetId.BAYX]: {
|
||||
id: ControllerCustomizationDefaultPresetId.BAYX,
|
||||
name: 'ABXY ⇄ BAYX',
|
||||
data: {
|
||||
mapping: {
|
||||
[GamepadKey.A]: GamepadKey.B,
|
||||
[GamepadKey.B]: GamepadKey.A,
|
||||
[GamepadKey.X]: GamepadKey.Y,
|
||||
[GamepadKey.Y]: GamepadKey.X,
|
||||
},
|
||||
|
||||
settings: {
|
||||
leftStickDeadzone: [0, 100],
|
||||
rightStickDeadzone: [0, 100],
|
||||
|
||||
leftTriggerRange: [0, 100],
|
||||
rightTriggerRange: [0, 100],
|
||||
|
||||
vibrationIntensity: 100,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
protected DEFAULT_PRESET_ID = ControllerCustomizationDefaultPresetId.DEFAULT;
|
||||
|
||||
}
|
@@ -2,6 +2,7 @@ import { BaseLocalTable } from "./base-table";
|
||||
import { LocalDb } from "./local-db";
|
||||
import { ControllerShortcutDefaultId } from "./controller-shortcuts-table";
|
||||
import { deepClone } from "../global";
|
||||
import { ControllerCustomizationDefaultPresetId } from "./controller-customizations-table";
|
||||
|
||||
export class ControllerSettingsTable extends BaseLocalTable<ControllerSettingsRecord> {
|
||||
private static instance: ControllerSettingsTable;
|
||||
@@ -9,7 +10,7 @@ export class ControllerSettingsTable extends BaseLocalTable<ControllerSettingsRe
|
||||
|
||||
static readonly DEFAULT_DATA: ControllerSettingsRecord['data'] = {
|
||||
shortcutPresetId: ControllerShortcutDefaultId.DEFAULT,
|
||||
vibrationIntensity: 50,
|
||||
customizationPresetId: ControllerCustomizationDefaultPresetId.DEFAULT,
|
||||
};
|
||||
|
||||
async getControllerData(id: string): Promise<ControllerSettingsRecord['data']> {
|
||||
@@ -30,10 +31,7 @@ export class ControllerSettingsTable extends BaseLocalTable<ControllerSettingsRe
|
||||
continue;
|
||||
}
|
||||
|
||||
const settings = all[key].data;
|
||||
// Pre-calculate virabtionIntensity
|
||||
settings.vibrationIntensity /= 100;
|
||||
|
||||
const settings = Object.assign(all[key].data, ControllerSettingsTable.DEFAULT_DATA);
|
||||
results[key] = settings;
|
||||
}
|
||||
|
||||
|
@@ -4,10 +4,11 @@ export class LocalDb {
|
||||
// private readonly LOG_TAG = 'LocalDb';
|
||||
|
||||
static readonly DB_NAME = 'BetterXcloud';
|
||||
static readonly DB_VERSION = 3;
|
||||
static readonly DB_VERSION = 4;
|
||||
|
||||
static readonly TABLE_VIRTUAL_CONTROLLERS = 'virtual_controllers';
|
||||
static readonly TABLE_CONTROLLER_SHORTCUTS = 'controller_shortcuts';
|
||||
static readonly TABLE_CONTROLLER_CUSTOMIZATIONS = 'controller_customizations';
|
||||
static readonly TABLE_CONTROLLER_SETTINGS = 'controller_settings';
|
||||
static readonly TABLE_KEYBOARD_SHORTCUTS = 'keyboard_shortcuts';
|
||||
|
||||
@@ -52,6 +53,14 @@ export class LocalDb {
|
||||
});
|
||||
}
|
||||
|
||||
// Controller mappings
|
||||
if (!db.objectStoreNames.contains(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS)) {
|
||||
db.createObjectStore(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS, {
|
||||
keyPath: 'id',
|
||||
autoIncrement: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
if (!db.objectStoreNames.contains(LocalDb.TABLE_KEYBOARD_SHORTCUTS)) {
|
||||
db.createObjectStore(LocalDb.TABLE_KEYBOARD_SHORTCUTS, {
|
||||
|
@@ -60,7 +60,7 @@ export function patchVideoApi() {
|
||||
|
||||
|
||||
export function patchRtcCodecs() {
|
||||
const codecProfile = getPref<CodecProfile>(PrefKey.STREAM_CODEC_PROFILE);
|
||||
const codecProfile = getPref(PrefKey.STREAM_CODEC_PROFILE);
|
||||
if (codecProfile === 'default') {
|
||||
return;
|
||||
}
|
||||
@@ -81,8 +81,8 @@ export function patchRtcPeerConnection() {
|
||||
}
|
||||
|
||||
const maxVideoBitrateDef = getPrefDefinition(PrefKey.STREAM_MAX_VIDEO_BITRATE) as Extract<SettingDefinition, { min: number }>;
|
||||
const maxVideoBitrate = getPref<VideoMaxBitrate>(PrefKey.STREAM_MAX_VIDEO_BITRATE);
|
||||
const codec = getPref<CodecProfile>(PrefKey.STREAM_CODEC_PROFILE);
|
||||
const maxVideoBitrate = getPref(PrefKey.STREAM_MAX_VIDEO_BITRATE);
|
||||
const codec = getPref(PrefKey.STREAM_CODEC_PROFILE);
|
||||
|
||||
if (codec !== CodecProfile.DEFAULT || maxVideoBitrate < maxVideoBitrateDef.max) {
|
||||
const nativeSetLocalDescription = RTCPeerConnection.prototype.setLocalDescription;
|
||||
@@ -134,7 +134,7 @@ export function patchAudioContext() {
|
||||
|
||||
ctx.createGain = function() {
|
||||
const gainNode = nativeCreateGain.apply(this);
|
||||
gainNode.gain.value = getPref<AudioVolume>(PrefKey.AUDIO_VOLUME) / 100;
|
||||
gainNode.gain.value = getPref(PrefKey.AUDIO_VOLUME) / 100;
|
||||
|
||||
STATES.currentStream.audioGainNode = gainNode;
|
||||
return gainNode;
|
||||
|
@@ -141,7 +141,7 @@ export function interceptHttpRequests() {
|
||||
|
||||
// 'https://notificationinbox.xboxlive.com',
|
||||
// 'https://accounts.xboxlive.com/family/memberXuid',
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
|
||||
if (blockFeatures.includes(BlockFeature.CHAT)) {
|
||||
BLOCKED_URLS.push(
|
||||
'https://xblmessaging.xboxlive.com/network/xbox/users/me/inbox',
|
||||
|
@@ -4,7 +4,7 @@ import { getPref } from "./settings-storages/global-settings-storage";
|
||||
|
||||
|
||||
export function getPreferredServerRegion(shortName = false): string | null {
|
||||
let preferredRegion = getPref<ServerRegionName>(PrefKey.SERVER_REGION);
|
||||
let preferredRegion = getPref(PrefKey.SERVER_REGION);
|
||||
const serverRegions = STATES.serverRegions;
|
||||
|
||||
// Return preferred region
|
||||
|
@@ -18,9 +18,9 @@ export class ScreenshotManager {
|
||||
private constructor() {
|
||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||
|
||||
this.$download = CE<HTMLAnchorElement>('a');
|
||||
this.$download = CE('a');
|
||||
|
||||
this.$canvas = CE<HTMLCanvasElement>('canvas', { class: 'bx-gone' });
|
||||
this.$canvas = CE('canvas', { class: 'bx-gone' });
|
||||
this.canvasContext = this.$canvas.getContext('2d', {
|
||||
alpha: false,
|
||||
willReadFrequently: false,
|
||||
|
@@ -23,10 +23,10 @@ export interface BxSelectSettingElement extends HTMLSelectElement, BxBaseSetting
|
||||
|
||||
export class SettingElement {
|
||||
private static renderOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any): BxSelectSettingElement {
|
||||
const $control = CE<BxSelectSettingElement>('select', {
|
||||
const $control = CE('select', {
|
||||
// title: setting.label,
|
||||
tabindex: 0,
|
||||
});
|
||||
}) as BxSelectSettingElement;
|
||||
|
||||
let $parent: HTMLElement;
|
||||
if (setting.optionsGroup) {
|
||||
@@ -41,7 +41,7 @@ export class SettingElement {
|
||||
for (let value in setting.options) {
|
||||
const label = setting.options[value];
|
||||
|
||||
const $option = CE<HTMLOptionElement>('option', { value }, label);
|
||||
const $option = CE('option', { value }, label);
|
||||
$parent.appendChild($option);
|
||||
}
|
||||
|
||||
@@ -62,11 +62,11 @@ export class SettingElement {
|
||||
}
|
||||
|
||||
private static renderMultipleOptions(key: string, setting: PreferenceSetting, currentValue: any, onChange: any, params: MultipleOptionsParams={}): BxSelectSettingElement {
|
||||
const $control = CE<BxSelectSettingElement>('select', {
|
||||
const $control = CE('select', {
|
||||
// title: setting.label,
|
||||
multiple: true,
|
||||
tabindex: 0,
|
||||
});
|
||||
}) as BxSelectSettingElement;
|
||||
|
||||
const totalOptions = Object.keys(setting.multipleOptions!).length;
|
||||
const size = params.size ? Math.min(params.size, totalOptions) : totalOptions;
|
||||
@@ -75,7 +75,7 @@ export class SettingElement {
|
||||
for (const value in setting.multipleOptions) {
|
||||
const label = setting.multipleOptions[value];
|
||||
|
||||
const $option = CE<HTMLOptionElement>('option', { value }, label) as HTMLOptionElement;
|
||||
const $option = CE('option', { value }, label) as HTMLOptionElement;
|
||||
$option.selected = currentValue.indexOf(value) > -1;
|
||||
|
||||
$option.addEventListener('mousedown', function(e) {
|
||||
@@ -156,6 +156,7 @@ export class SettingElement {
|
||||
|
||||
static fromPref(key: PrefKey, storage: BaseSettingsStore, onChange: any, overrideParams={}) {
|
||||
const definition = storage.getDefinition(key);
|
||||
// @ts-ignore
|
||||
let currentValue = storage.getSetting(key);
|
||||
|
||||
let type;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { PrefKey, StorageKey } from "@/enums/pref-keys";
|
||||
import type { PrefKey, PrefTypeMap, StorageKey } from "@/enums/pref-keys";
|
||||
import type { NumberStepperParams, SettingAction, SettingDefinitions } from "@/types/setting-definition";
|
||||
import { t } from "../translation";
|
||||
import { SCRIPT_VARIANT } from "../global";
|
||||
@@ -63,20 +63,20 @@ export class BaseSettingsStore {
|
||||
return this.definitions[key];
|
||||
}
|
||||
|
||||
getSetting<T=boolean>(key: PrefKey, checkUnsupported = true): T {
|
||||
getSetting<T extends keyof PrefTypeMap>(key: T, checkUnsupported = true): PrefTypeMap[T] {
|
||||
const definition = this.definitions[key];
|
||||
|
||||
// Return default value if build variant is different
|
||||
if (definition.requiredVariants && !definition.requiredVariants.includes(SCRIPT_VARIANT)) {
|
||||
return definition.default as T;
|
||||
return definition.default as PrefTypeMap[T];
|
||||
}
|
||||
|
||||
// Return default value if the feature is not supported
|
||||
if (checkUnsupported && definition.unsupported) {
|
||||
if ('unsupportedValue' in definition) {
|
||||
return definition.unsupportedValue as T;
|
||||
return definition.unsupportedValue as PrefTypeMap[T];
|
||||
} else {
|
||||
return definition.default as T;
|
||||
return definition.default as PrefTypeMap[T];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ export class BaseSettingsStore {
|
||||
this.settings[key] = this.validateValue('get', key, null);
|
||||
}
|
||||
|
||||
return this.settings[key] as T;
|
||||
return this.settings[key] as PrefTypeMap[T];
|
||||
}
|
||||
|
||||
setSetting<T=any>(key: PrefKey, value: T, emitEvent = false) {
|
||||
|
@@ -8,7 +8,7 @@ import { CE } from "../html";
|
||||
import { t, SUPPORTED_LANGUAGES } from "../translation";
|
||||
import { UserAgent } from "../user-agent";
|
||||
import { BaseSettingsStore as BaseSettingsStorage } from "./base-settings-storage";
|
||||
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, DeviceVibrationMode, NativeMkbMode, UiLayout, UiSection, StreamPlayerType, StreamVideoProcessing, VideoRatio, StreamStat, VideoPosition, BlockFeature } from "@/enums/pref-values";
|
||||
import { CodecProfile, StreamResolution, TouchControllerMode, TouchControllerStyleStandard, TouchControllerStyleCustom, GameBarPosition, DeviceVibrationMode, NativeMkbMode, UiLayout, UiSection, StreamPlayerType, StreamVideoProcessing, VideoRatio, StreamStat, VideoPosition, BlockFeature, StreamStatPosition, VideoPowerPreference } from "@/enums/pref-values";
|
||||
import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table";
|
||||
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
||||
import { GhPagesUtils } from "../gh-pages";
|
||||
@@ -322,7 +322,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
label: t('enable-local-co-op-support'),
|
||||
default: false,
|
||||
note: () => CE('div', {},
|
||||
CE<HTMLAnchorElement>('a', {
|
||||
CE('a', {
|
||||
href: 'https://github.com/redphx/better-xcloud/discussions/275',
|
||||
target: '_blank',
|
||||
}, t('enable-local-co-op-support-note')),
|
||||
@@ -399,7 +399,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
url = 'https://better-xcloud.github.io/mouse-and-keyboard/#disclaimer';
|
||||
}
|
||||
|
||||
setting.unsupportedNote = () => CE<HTMLAnchorElement>('a', {
|
||||
setting.unsupportedNote = () => CE('a', {
|
||||
href: url,
|
||||
target: '_blank',
|
||||
}, '⚠️ ' + note);
|
||||
@@ -649,11 +649,11 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
},
|
||||
[PrefKey.VIDEO_POWER_PREFERENCE]: {
|
||||
label: t('renderer-configuration'),
|
||||
default: 'default',
|
||||
default: VideoPowerPreference.DEFAULT,
|
||||
options: {
|
||||
default: t('default'),
|
||||
'low-power': t('battery-saving'),
|
||||
'high-performance': t('high-performance'),
|
||||
[VideoPowerPreference.DEFAULT]: t('default'),
|
||||
[VideoPowerPreference.LOW_POWER]: t('battery-saving'),
|
||||
[VideoPowerPreference.HIGH_PERFORMANCE]: t('high-performance'),
|
||||
},
|
||||
suggest: {
|
||||
highest: 'low-power',
|
||||
@@ -813,11 +813,11 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
||||
},
|
||||
[PrefKey.STATS_POSITION]: {
|
||||
label: t('position'),
|
||||
default: 'top-right',
|
||||
default: StreamStatPosition.TOP_RIGHT,
|
||||
options: {
|
||||
'top-left': t('top-left'),
|
||||
'top-center': t('top-center'),
|
||||
'top-right': t('top-right'),
|
||||
[StreamStatPosition.TOP_LEFT]: t('top-left'),
|
||||
[StreamStatPosition.TOP_CENTER]: t('top-center'),
|
||||
[StreamStatPosition.TOP_RIGHT]: t('top-right'),
|
||||
},
|
||||
},
|
||||
[PrefKey.STATS_TEXT_SIZE]: {
|
||||
|
@@ -1,32 +1,33 @@
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { PrefKey, type PrefTypeMap } from "@/enums/pref-keys";
|
||||
import { ControllerSettingsTable } from "./local-db/controller-settings-table";
|
||||
import { ControllerShortcutsTable } from "./local-db/controller-shortcuts-table";
|
||||
import { getPref, setPref } from "./settings-storages/global-settings-storage";
|
||||
import type { ControllerShortcutPresetRecord, KeyboardShortcutConvertedPresetData, MkbConvertedPresetData } from "@/types/presets";
|
||||
import type { ControllerCustomizationConvertedPresetData, ControllerCustomizationPresetData, ControllerShortcutPresetRecord, KeyboardShortcutConvertedPresetData, MkbConvertedPresetData } from "@/types/presets";
|
||||
import { STATES } from "./global";
|
||||
import { DeviceVibrationMode } from "@/enums/pref-values";
|
||||
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
||||
import { hasGamepad } from "./gamepad";
|
||||
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
|
||||
import type { GamepadKey } from "@/enums/gamepad";
|
||||
import { GamepadKey } from "@/enums/gamepad";
|
||||
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
|
||||
import { KeyboardShortcutDefaultId, KeyboardShortcutsTable } from "./local-db/keyboard-shortcuts-table";
|
||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||
import { KeyHelper } from "@/modules/mkb/key-helper";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
import { ControllerCustomizationsTable } from "./local-db/controller-customizations-table";
|
||||
|
||||
|
||||
export type StreamSettingsData = {
|
||||
settings: Partial<Record<PrefKey, any>>;
|
||||
xCloudPollingMode: 'none' | 'callbacks' | 'navigation' | 'all';
|
||||
|
||||
deviceVibrationIntensity: DeviceVibrationIntensity;
|
||||
deviceVibrationIntensity: number;
|
||||
|
||||
controllerPollingRate: ControllerPollingRate;
|
||||
controllerPollingRate: number;
|
||||
controllers: {
|
||||
[gamepadId: string]: {
|
||||
vibrationIntensity: number;
|
||||
shortcuts: ControllerShortcutPresetRecord['data']['mapping'] | null;
|
||||
customization: ControllerCustomizationConvertedPresetData | null;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -50,7 +51,33 @@ export class StreamSettings {
|
||||
keyboardShortcuts: {},
|
||||
};
|
||||
|
||||
static getPref<T=boolean>(key: PrefKey) {
|
||||
private static CONTROLLER_CUSTOMIZATION_MAPPING: { [key in GamepadKey]?: keyof XcloudGamepad } = {
|
||||
[GamepadKey.A]: 'A',
|
||||
[GamepadKey.B]: 'B',
|
||||
[GamepadKey.X]: 'X',
|
||||
[GamepadKey.Y]: 'Y',
|
||||
|
||||
[GamepadKey.UP]: 'DPadUp',
|
||||
[GamepadKey.RIGHT]: 'DPadRight',
|
||||
[GamepadKey.DOWN]: 'DPadDown',
|
||||
[GamepadKey.LEFT]: 'DPadLeft',
|
||||
|
||||
[GamepadKey.LB]: 'LeftShoulder',
|
||||
[GamepadKey.RB]: 'RightShoulder',
|
||||
[GamepadKey.LT]: 'LeftTrigger',
|
||||
[GamepadKey.RT]: 'RightTrigger',
|
||||
|
||||
[GamepadKey.L3]: 'LeftThumb',
|
||||
[GamepadKey.R3]: 'RightThumb',
|
||||
[GamepadKey.LS]: 'LeftStickAxes',
|
||||
[GamepadKey.RS]: 'RightStickAxes',
|
||||
|
||||
[GamepadKey.SELECT]: 'View',
|
||||
[GamepadKey.START]: 'Menu',
|
||||
[GamepadKey.SHARE]: 'Share',
|
||||
};
|
||||
|
||||
static getPref<T extends keyof PrefTypeMap>(key: T) {
|
||||
return getPref<T>(key);
|
||||
}
|
||||
|
||||
@@ -60,6 +87,7 @@ export class StreamSettings {
|
||||
|
||||
const settingsTable = ControllerSettingsTable.getInstance();
|
||||
const shortcutsTable = ControllerShortcutsTable.getInstance();
|
||||
const mappingTable = ControllerCustomizationsTable.getInstance();
|
||||
|
||||
const gamepads = window.navigator.getGamepads();
|
||||
for (const gamepad of gamepads) {
|
||||
@@ -74,17 +102,17 @@ export class StreamSettings {
|
||||
|
||||
const settingsData = await settingsTable.getControllerData(gamepad.id);
|
||||
|
||||
let shortcutsMapping;
|
||||
const preset = await shortcutsTable.getPreset(settingsData.shortcutPresetId);
|
||||
if (!preset) {
|
||||
shortcutsMapping = null;
|
||||
} else {
|
||||
shortcutsMapping = preset.data.mapping;
|
||||
}
|
||||
// Shortcuts
|
||||
const shortcutsPreset = await shortcutsTable.getPreset(settingsData.shortcutPresetId);
|
||||
const shortcutsMapping = !shortcutsPreset ? null : shortcutsPreset.data.mapping;
|
||||
|
||||
// Mapping
|
||||
const customizationPreset = await mappingTable.getPreset(settingsData.customizationPresetId);
|
||||
const customizationData = StreamSettings.convertControllerCustomization(customizationPreset?.data);
|
||||
|
||||
controllers[gamepad.id] = {
|
||||
vibrationIntensity: settingsData.vibrationIntensity,
|
||||
shortcuts: shortcutsMapping,
|
||||
customization: customizationData,
|
||||
}
|
||||
}
|
||||
settings.controllers = controllers;
|
||||
@@ -95,18 +123,66 @@ export class StreamSettings {
|
||||
await StreamSettings.refreshDeviceVibration();
|
||||
}
|
||||
|
||||
private static preCalculateControllerRange(obj: Record<string, [number, number]>, target: keyof XcloudGamepad, values: [number, number] | undefined) {
|
||||
if (values && Array.isArray(values)) {
|
||||
const [from, to] = values;
|
||||
if (from > 1 || to < 100) {
|
||||
obj[target] = [from / 100, to / 100];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static convertControllerCustomization(customization: ControllerCustomizationPresetData | null | undefined) {
|
||||
if (!customization) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const converted = {
|
||||
mapping: {},
|
||||
ranges: {},
|
||||
vibrationIntensity: 1,
|
||||
} as ControllerCustomizationConvertedPresetData;
|
||||
|
||||
// Swap GamepadKey.A with "A"
|
||||
let gamepadKey: unknown;
|
||||
for (gamepadKey in customization.mapping) {
|
||||
const gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey as GamepadKey];
|
||||
if (!gamepadStr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mappedKey = customization.mapping[gamepadKey as GamepadKey];
|
||||
if (typeof mappedKey === 'number') {
|
||||
converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey as GamepadKey];
|
||||
} else {
|
||||
converted.mapping[gamepadStr] = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-calculate ranges & deadzone
|
||||
StreamSettings.preCalculateControllerRange(converted.ranges, 'LeftTrigger', customization.settings.leftTriggerRange);
|
||||
StreamSettings.preCalculateControllerRange(converted.ranges, 'RightTrigger', customization.settings.rightTriggerRange);
|
||||
StreamSettings.preCalculateControllerRange(converted.ranges, 'LeftThumb', customization.settings.leftStickDeadzone);
|
||||
StreamSettings.preCalculateControllerRange(converted.ranges, 'RightThumb', customization.settings.rightStickDeadzone);
|
||||
|
||||
// Pre-calculate virabtionIntensity
|
||||
converted.vibrationIntensity = customization.settings.vibrationIntensity / 100;
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
private static async refreshDeviceVibration() {
|
||||
if (!STATES.browser.capabilities.deviceVibration) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mode = StreamSettings.getPref<DeviceVibrationMode>(PrefKey.DEVICE_VIBRATION_MODE);
|
||||
const mode = StreamSettings.getPref(PrefKey.DEVICE_VIBRATION_MODE);
|
||||
let intensity = 0; // Disable
|
||||
|
||||
// Enable when no controllers are detected in Auto mode
|
||||
if (mode === DeviceVibrationMode.ON || (mode === DeviceVibrationMode.AUTO && !hasGamepad())) {
|
||||
// Set intensity
|
||||
intensity = StreamSettings.getPref<DeviceVibrationIntensity>(PrefKey.DEVICE_VIBRATION_INTENSITY) / 100;
|
||||
intensity = StreamSettings.getPref(PrefKey.DEVICE_VIBRATION_INTENSITY) / 100;
|
||||
}
|
||||
|
||||
StreamSettings.settings.deviceVibrationIntensity = intensity;
|
||||
@@ -116,7 +192,7 @@ export class StreamSettings {
|
||||
static async refreshMkbSettings() {
|
||||
const settings = StreamSettings.settings;
|
||||
|
||||
let presetId = StreamSettings.getPref<MkbPresetId>(PrefKey.MKB_P1_MAPPING_PRESET_ID);
|
||||
let presetId = StreamSettings.getPref(PrefKey.MKB_P1_MAPPING_PRESET_ID);
|
||||
const orgPreset = (await MkbMappingPresetsTable.getInstance().getPreset(presetId))!;
|
||||
const orgPresetData = orgPreset.data;
|
||||
|
||||
@@ -154,7 +230,7 @@ export class StreamSettings {
|
||||
static async refreshKeyboardShortcuts() {
|
||||
const settings = StreamSettings.settings;
|
||||
|
||||
let presetId = StreamSettings.getPref<KeyboardShortcutsPresetId>(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID);
|
||||
let presetId = StreamSettings.getPref(PrefKey.KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID);
|
||||
if (presetId === KeyboardShortcutDefaultId.OFF) {
|
||||
settings.keyboardShortcuts = null;
|
||||
|
||||
|
@@ -96,7 +96,7 @@ export class StreamStatsCollector {
|
||||
current: -1,
|
||||
grades: [40, 75, 100],
|
||||
toString() {
|
||||
return this.current === -1 ? '???' : this.current.toString().padStart(3, ' ');
|
||||
return this.current === -1 ? '???' : this.current.toString().padStart(3);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -104,22 +104,22 @@ export class StreamStatsCollector {
|
||||
current: 0,
|
||||
grades: [30, 40, 60],
|
||||
toString() {
|
||||
return `${this.current.toFixed(1)}ms`.padStart(6, ' ');
|
||||
return `${this.current.toFixed(1)}ms`.padStart(6);
|
||||
},
|
||||
},
|
||||
|
||||
[StreamStat.FPS]: {
|
||||
current: 0,
|
||||
toString() {
|
||||
const maxFps = getPref<VideoMaxFps>(PrefKey.VIDEO_MAX_FPS);
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5, ' ') : this.current.toString();
|
||||
const maxFps = getPref(PrefKey.VIDEO_MAX_FPS);
|
||||
return maxFps < 60 ? `${maxFps}/${this.current}`.padStart(5) : this.current.toString();
|
||||
},
|
||||
},
|
||||
|
||||
[StreamStat.BITRATE]: {
|
||||
current: 0,
|
||||
toString() {
|
||||
return `${this.current.toFixed(1)} Mbps`.padStart(9, ' ');
|
||||
return `${this.current.toFixed(1)} Mbps`.padStart(9);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -146,14 +146,14 @@ export class StreamStatsCollector {
|
||||
total: 0,
|
||||
grades: [6, 9, 12],
|
||||
toString() {
|
||||
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(1)}ms`.padStart(6, ' ');
|
||||
return isNaN(this.current) ? '??ms' : `${this.current.toFixed(1)}ms`.padStart(6);
|
||||
},
|
||||
},
|
||||
|
||||
[StreamStat.DOWNLOAD]: {
|
||||
total: 0,
|
||||
toString() {
|
||||
return humanFileSize(this.total).padStart(8, ' ');
|
||||
return humanFileSize(this.total).padStart(8);
|
||||
},
|
||||
},
|
||||
|
||||
|
@@ -27,6 +27,7 @@ export const SUPPORTED_LANGUAGES = {
|
||||
};
|
||||
|
||||
const Texts = {
|
||||
"slightly-increase-input-latency": "Slightly increase input latency",
|
||||
"achievements": "Achievements",
|
||||
"activate": "Activate",
|
||||
"activated": "Activated",
|
||||
@@ -86,12 +87,10 @@ const Texts = {
|
||||
"continent-south-america": "South America",
|
||||
"contrast": "Contrast",
|
||||
"controller": "Controller",
|
||||
"controller-customization": "Controller customization",
|
||||
"controller-friendly-ui": "Controller-friendly UI",
|
||||
"controller-mapping": "Controller mapping",
|
||||
"controller-mapping-in-game": "In-game controller mapping",
|
||||
"controller-shortcuts": "Controller shortcuts",
|
||||
"controller-shortcuts-connect-note": "Connect a controller to use this feature",
|
||||
"controller-shortcuts-in-game": "In-game controller shortcuts",
|
||||
"controller-shortcuts-xbox-note": "Button to open the Guide menu",
|
||||
"controller-vibration": "Controller vibration",
|
||||
"copy": "Copy",
|
||||
@@ -102,12 +101,12 @@ const Texts = {
|
||||
"default": "Default",
|
||||
"default-preset-note": "You can't modify default presets. Create a new one to customize it.",
|
||||
"delete": "Delete",
|
||||
"detect-controller-button": "Detect controller button",
|
||||
"device": "Device",
|
||||
"device-unsupported-touch": "Your device doesn't have touch support",
|
||||
"device-vibration": "Device vibration",
|
||||
"device-vibration-not-using-gamepad": "On when not using gamepad",
|
||||
"disable": "Disable",
|
||||
"disable-byog-feature": "Disable \"Stream your own game\" feature",
|
||||
"disable-features": "Disable features",
|
||||
"disable-home-context-menu": "Disable context menu in Home page",
|
||||
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
|
||||
@@ -153,6 +152,10 @@ const Texts = {
|
||||
"how-to-improve-app-performance": "How to improve app's performance",
|
||||
"ignore": "Ignore",
|
||||
"import": "Import",
|
||||
"in-game-controller-customization": "In-game controller customization",
|
||||
"in-game-controller-shortcuts": "In-game controller shortcuts",
|
||||
"in-game-keyboard-shortcuts": "In-game keyboard shortcuts",
|
||||
"in-game-shortcuts": "In-game shortcuts",
|
||||
"increase": "Increase",
|
||||
"install-android": "Better xCloud app for Android",
|
||||
"invites": "Invites",
|
||||
@@ -160,12 +163,13 @@ const Texts = {
|
||||
"jitter": "Jitter",
|
||||
"keyboard-key": "Keyboard key",
|
||||
"keyboard-shortcuts": "Keyboard shortcuts",
|
||||
"keyboard-shortcuts-in-game": "In-game keyboard shortcuts",
|
||||
"korea": "Korea",
|
||||
"language": "Language",
|
||||
"large": "Large",
|
||||
"layout": "Layout",
|
||||
"left-stick": "Left stick",
|
||||
"left-stick-deadzone": "Left stick deadzone",
|
||||
"left-trigger-range": "Left trigger range",
|
||||
"limit-fps": "Limit FPS",
|
||||
"load-failed-message": "Failed to run Better xCloud",
|
||||
"loading-screen": "Loading screen",
|
||||
@@ -173,7 +177,6 @@ const Texts = {
|
||||
"lowest-quality": "Lowest quality",
|
||||
"manage": "Manage",
|
||||
"map-mouse-to": "Map mouse to",
|
||||
"mapping": "Mapping",
|
||||
"may-not-work-properly": "May not work properly!",
|
||||
"menu": "Menu",
|
||||
"microphone": "Microphone",
|
||||
@@ -230,6 +233,7 @@ const Texts = {
|
||||
"preferred-game-language": "Preferred game's language",
|
||||
"preset": "Preset",
|
||||
"press": "Press",
|
||||
"press-any-button": "Press any button...",
|
||||
"press-esc-to-cancel": "Press Esc to cancel",
|
||||
"press-key-to-toggle-mkb": [
|
||||
(e: any) => `Press ${e.key} to toggle this feature`,
|
||||
@@ -285,6 +289,8 @@ const Texts = {
|
||||
"renderer-configuration": "Renderer configuration",
|
||||
"right-click-to-unbind": "Right-click on a key to unbind it",
|
||||
"right-stick": "Right stick",
|
||||
"right-stick-deadzone": "Right stick deadzone",
|
||||
"right-trigger-range": "Right trigger range",
|
||||
"rocket-always-hide": "Always hide",
|
||||
"rocket-always-show": "Always show",
|
||||
"rocket-animation": "Rocket animation",
|
||||
@@ -294,7 +300,6 @@ const Texts = {
|
||||
"screen": "Screen",
|
||||
"screenshot-apply-filters": "Apply video filters to screenshots",
|
||||
"section-all-games": "All games",
|
||||
"section-byog": "Stream your own game",
|
||||
"section-most-popular": "Most popular",
|
||||
"section-native-mkb": "Play with mouse & keyboard",
|
||||
"section-news": "News",
|
||||
@@ -384,7 +389,6 @@ const Texts = {
|
||||
(e: any) => `觸控遊玩佈局由 ${e.name} 提供`,
|
||||
],
|
||||
"touch-controller": "Touch controller",
|
||||
"transparent-background": "Transparent background",
|
||||
"true-achievements": "TrueAchievements",
|
||||
"ui": "UI",
|
||||
"unexpected-behavior": "May cause unexpected behavior",
|
||||
|
@@ -32,7 +32,7 @@ export class TrueAchievements {
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
this.$hiddenLink = CE<HTMLAnchorElement>('a', {
|
||||
this.$hiddenLink = CE('a', {
|
||||
target: '_blank',
|
||||
});
|
||||
}
|
||||
|
@@ -18,8 +18,8 @@ export function checkForUpdate() {
|
||||
|
||||
const CHECK_INTERVAL_SECONDS = 2 * 3600; // check every 2 hours
|
||||
|
||||
const currentVersion = getPref<VersionCurrent>(PrefKey.VERSION_CURRENT);
|
||||
const lastCheck = getPref<VersionLastCheck>(PrefKey.VERSION_LAST_CHECK);
|
||||
const currentVersion = getPref(PrefKey.VERSION_CURRENT);
|
||||
const lastCheck = getPref(PrefKey.VERSION_LAST_CHECK);
|
||||
const now = Math.round((+new Date) / 1000);
|
||||
|
||||
if (currentVersion === SCRIPT_VERSION && now - lastCheck < CHECK_INTERVAL_SECONDS) {
|
||||
@@ -77,13 +77,10 @@ export function hashCode(str: string): number {
|
||||
|
||||
|
||||
export function renderString(str: string, obj: any){
|
||||
return str.replace(/\$\{.+?\}/g, match => {
|
||||
const key = match.substring(2, match.length - 1);
|
||||
if (key in obj) {
|
||||
return obj[key];
|
||||
}
|
||||
|
||||
return match;
|
||||
// Accept ${var} and $var$
|
||||
return str.replace(/\$\{([A-Za-z0-9_$]+)\}|\$([A-Za-z0-9_$]+)\$/g, (match, p1, p2) => {
|
||||
const name = p1 || p2;
|
||||
return name in obj ? obj[name] : match;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -158,13 +155,13 @@ export function clearAllData() {
|
||||
}
|
||||
|
||||
export function blockAllNotifications() {
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
|
||||
const blockAll = [BlockFeature.FRIENDS, BlockFeature.NOTIFICATIONS_ACHIEVEMENTS, BlockFeature.NOTIFICATIONS_INVITES].every(value => blockFeatures.includes(value));
|
||||
return blockAll;
|
||||
}
|
||||
|
||||
export function blockSomeNotifications() {
|
||||
const blockFeatures = getPref<BlockFeature[]>(PrefKey.BLOCK_FEATURES);
|
||||
const blockFeatures = getPref(PrefKey.BLOCK_FEATURES);
|
||||
if (blockAllNotifications()) {
|
||||
return false;
|
||||
}
|
||||
|
@@ -11,7 +11,7 @@ import { getPreferredServerRegion } from "./region";
|
||||
import { BypassServerIps } from "@/enums/bypass-servers";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import { NativeMkbMode, StreamResolution, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export class XcloudInterceptor {
|
||||
@@ -43,7 +43,7 @@ export class XcloudInterceptor {
|
||||
};
|
||||
|
||||
private static async handleLogin(request: RequestInfo | URL, init?: RequestInit) {
|
||||
const bypassServer = getPref<string>(PrefKey.SERVER_BYPASS_RESTRICTION);
|
||||
const bypassServer = getPref(PrefKey.SERVER_BYPASS_RESTRICTION);
|
||||
if (bypassServer !== 'off') {
|
||||
const ip = BypassServerIps[bypassServer as keyof typeof BypassServerIps];
|
||||
ip && (request as Request).headers.set('X-Forwarded-For', ip);
|
||||
@@ -110,8 +110,8 @@ export class XcloudInterceptor {
|
||||
private static async handlePlay(request: RequestInfo | URL, init?: RequestInit) {
|
||||
BxEventBus.Stream.emit('state.loading', {});
|
||||
|
||||
const PREF_STREAM_TARGET_RESOLUTION = getPref<StreamResolution>(PrefKey.STREAM_RESOLUTION);
|
||||
const PREF_STREAM_PREFERRED_LOCALE = getPref<StreamPreferredLocale>(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||
const PREF_STREAM_TARGET_RESOLUTION = getPref(PrefKey.STREAM_RESOLUTION);
|
||||
const PREF_STREAM_PREFERRED_LOCALE = getPref(PrefKey.STREAM_PREFERRED_LOCALE);
|
||||
|
||||
const url = (typeof request === 'string') ? request : (request as Request).url;
|
||||
const parsedUrl = new URL(url);
|
||||
@@ -174,7 +174,7 @@ export class XcloudInterceptor {
|
||||
}
|
||||
|
||||
// Touch controller for all games
|
||||
if (isFullVersion() && getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
|
||||
if (isFullVersion() && getPref(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
|
||||
const titleInfo = STATES.currentStream.titleInfo;
|
||||
if (titleInfo?.details.hasTouchSupport) {
|
||||
TouchController.disable();
|
||||
@@ -200,11 +200,11 @@ export class XcloudInterceptor {
|
||||
|
||||
let overrideMkb: boolean | null = null;
|
||||
|
||||
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON || (STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId))) {
|
||||
if (getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON || (STATES.currentStream.titleInfo && BX_FLAGS.ForceNativeMkbTitles?.includes(STATES.currentStream.titleInfo.details.productId))) {
|
||||
overrideMkb = true;
|
||||
}
|
||||
|
||||
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.OFF) {
|
||||
if (getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.OFF) {
|
||||
overrideMkb = false;
|
||||
}
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "./settings-storages/global-settings-storage";
|
||||
import type { RemotePlayConsoleAddresses } from "@/types/network";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { StreamResolution, TouchControllerMode } from "@/enums/pref-values";
|
||||
import { TouchControllerMode } from "@/enums/pref-values";
|
||||
import { BxEventBus } from "./bx-event-bus";
|
||||
|
||||
export class XhomeInterceptor {
|
||||
@@ -71,7 +71,7 @@ export class XhomeInterceptor {
|
||||
private static async handleInputConfigs(request: Request | URL, opts: { [index: string]: any }) {
|
||||
const response = await NATIVE_FETCH(request);
|
||||
|
||||
if (getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.ALL) {
|
||||
if (getPref(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.ALL) {
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ export class XhomeInterceptor {
|
||||
headers.authorization = `Bearer ${RemotePlayManager.getInstance()!.getXhomeToken()}`;
|
||||
|
||||
// Patch resolution
|
||||
const osName = getOsNameFromResolution(getPref<StreamResolution>(PrefKey.REMOTE_PLAY_STREAM_RESOLUTION));
|
||||
const osName = getOsNameFromResolution(getPref(PrefKey.REMOTE_PLAY_STREAM_RESOLUTION));
|
||||
headers['x-ms-device-info'] = JSON.stringify(generateMsDeviceInfo(osName));
|
||||
|
||||
const opts: Record<string, any> = {
|
||||
|
Reference in New Issue
Block a user