Add native MKB support for Android app

This commit is contained in:
redphx
2024-06-08 17:04:49 +07:00
parent a41d0cda0c
commit eb8490a798
30 changed files with 1054 additions and 347 deletions

View File

@@ -35,11 +35,14 @@ export enum BxEvent {
CAPTURE_SCREENSHOT = 'bx-capture-screenshot',
GAINNODE_VOLUME_CHANGED = 'bx-gainnode-volume-changed',
POINTER_LOCK_REQUESTED = 'bx-pointer-lock-requested',
POINTER_LOCK_EXITED = 'bx-pointer-lock-exited',
// xCloud Dialog events
XCLOUD_DIALOG_SHOWN = 'bx-xcloud-dialog-shown',
XCLOUD_DIALOG_DISMISSED = 'bx-xcloud-dialog-dismissed',
XCLOUD_GUIDE_SHOWN = 'bx-xcloud-guide-shown',
XCLOUD_GUIDE_MENU_SHOWN = 'bx-xcloud-guide-menu-shown',
XCLOUD_POLLING_MODE_CHANGED = 'bx-xcloud-polling-mode-changed',
}
@@ -48,10 +51,6 @@ export enum XcloudEvent {
MICROPHONE_STATE_CHANGED = 'microphoneStateChanged',
}
export enum XcloudGuideWhere {
HOME,
}
export namespace BxEvent {
export function dispatch(target: HTMLElement | Window, eventName: string, data?: any) {
if (!eventName) {

View File

@@ -2,7 +2,6 @@ import { ControllerShortcut } from "@/modules/controller-shortcut";
import { BxEvent } from "@utils/bx-event";
import { STATES } from "@utils/global";
import { getPref, PrefKey } from "@utils/preferences";
import { UserAgent } from "@utils/user-agent";
import { BxLogger } from "./bx-logger";
import { BX_FLAGS } from "./bx-flags";
@@ -24,13 +23,15 @@ export const BxExposed = {
let supportedInputTypes = titleInfo.details.supportedInputTypes;
// Remove native MKB support on mobile browsers or by user's choice
if (getPref(PrefKey.NATIVE_MKB_DISABLED) || UserAgent.isMobile()) {
supportedInputTypes = supportedInputTypes.filter(i => i !== InputType.MKB);
} else if (BX_FLAGS.ForceNativeMkbTitles.includes(titleInfo.details.productId)) {
if (BX_FLAGS.ForceNativeMkbTitles.includes(titleInfo.details.productId)) {
supportedInputTypes.push(InputType.MKB);
}
// Remove native MKB support on mobile browsers or by user's choice
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'off') {
supportedInputTypes = supportedInputTypes.filter(i => i !== InputType.MKB);
}
titleInfo.details.hasMkbSupport = supportedInputTypes.includes(InputType.MKB);
if (STATES.hasTouchSupport) {

View File

@@ -4,8 +4,7 @@ import iconCopy from "@assets/svg/copy.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 iconHome from "@assets/svg/home.svg" with { type: "text" };
import iconMouseSettings from "@assets/svg/mouse-settings.svg" with { type: "text" };
import iconMouse from "@assets/svg/mouse.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 iconQuestion from "@assets/svg/question.svg" with { type: "text" };
import iconRefresh from "@assets/svg/refresh.svg" with { type: "text" };
@@ -15,6 +14,7 @@ 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 iconVirtualController from "@assets/svg/virtual-controller.svg" with { type: "text" };
// Game Bar
import iconCaretLeft from "@assets/svg/caret-left.svg" with { type: "text" };
@@ -39,14 +39,14 @@ export const BxIcon = {
CONTROLLER: iconController,
DISPLAY: iconDisplay,
HOME: iconHome,
MOUSE: iconMouse,
MOUSE_SETTINGS: iconMouseSettings,
NATIVE_MKB: iconNativeMkb,
NEW: iconNew,
COPY: iconCopy,
TRASH: iconTrash,
CURSOR_TEXT: iconCursorText,
QUESTION: iconQuestion,
REFRESH: iconRefresh,
VIRTUAL_CONTROLLER: iconVirtualController,
REMOTE_PLAY: iconRemotePlay,

View File

@@ -19,15 +19,6 @@ button[class*=SocialEmptyCard],
`;
}
if (getPref(PrefKey.BLOCK_TRACKING)) {
css += `
/* Remove Feedback button in the Guide menu */
#gamepass-dialog-root #Home-panel button[class*=FeedbackButton] {
display: none;
}
`;
}
// Reduce animations
if (getPref(PrefKey.REDUCE_ANIMATIONS)) {
css += `

View File

@@ -1,4 +1,4 @@
import { MkbHandler } from "@modules/mkb/mkb-handler";
import { EmulatedMkbHandler } from "@modules/mkb/mkb-handler";
import { PrefKey, getPref } from "@utils/preferences";
import { t } from "@utils/translation";
import { Toast } from "@utils/toast";
@@ -7,7 +7,7 @@ import { BxLogger } from "@utils/bx-logger";
// Show a toast when connecting/disconecting controller
export function showGamepadToast(gamepad: Gamepad) {
// Don't show Toast for virtual controller
if (gamepad.id === MkbHandler.VIRTUAL_GAMEPAD_ID) {
if (gamepad.id === EmulatedMkbHandler.VIRTUAL_GAMEPAD_ID) {
return;
}

View File

@@ -1,7 +1,7 @@
import type { BxIcon } from "@utils/bx-icon";
type BxButton = {
style?: number | string;
style?: number | string | ButtonStyle;
url?: string;
classes?: string[];
icon?: typeof BxIcon;
@@ -67,6 +67,7 @@ ButtonStyle[ButtonStyle.GHOST = 4] = 'bx-ghost';
ButtonStyle[ButtonStyle.FOCUSABLE = 8] = 'bx-focusable';
ButtonStyle[ButtonStyle.FULL_WIDTH = 16] = 'bx-full-width';
ButtonStyle[ButtonStyle.FULL_HEIGHT = 32] = 'bx-full-height';
ButtonStyle[ButtonStyle.TALL = 64] = 'bx-tall';
const ButtonStyleIndices = Object.keys(ButtonStyle).splice(0, Object.keys(ButtonStyle).length / 2).map(i => parseInt(i));

View File

@@ -219,3 +219,44 @@ export function patchCanvasContext() {
return nativeGetContext.apply(this, [contextType, contextAttributes]);
}
}
export function patchPointerLockApi() {
Object.defineProperty(document, 'fullscreenElement', {
configurable: true,
get() {
return document.documentElement;
},
});
HTMLElement.prototype.requestFullscreen = function(options?: FullscreenOptions): Promise<void> {
return Promise.resolve();
}
let pointerLockElement: unknown = null;
Object.defineProperty(document, 'pointerLockElement', {
configurable: true,
get() {
return pointerLockElement;
},
});
// const nativeRequestPointerLock = HTMLElement.prototype.requestPointerLock;
HTMLElement.prototype.requestPointerLock = function() {
pointerLockElement = document.documentElement;
window.dispatchEvent(new Event(BxEvent.POINTER_LOCK_REQUESTED));
// document.dispatchEvent(new Event('pointerlockchange'));
// @ts-ignore
// nativeRequestPointerLock.apply(this, arguments);
}
// const nativeExitPointerLock = Document.prototype.exitPointerLock;
Document.prototype.exitPointerLock = function() {
pointerLockElement = null;
window.dispatchEvent(new Event(BxEvent.POINTER_LOCK_EXITED));
// document.dispatchEvent(new Event('pointerlockchange'));
// nativeExitPointerLock.apply(this);
}
}

View File

@@ -9,7 +9,6 @@ import { STATES } from "@utils/global";
import { getPreferredServerRegion } from "@utils/region";
import { GamePassCloudGallery } from "./gamepass-gallery";
import { InputType } from "./bx-exposed";
import { UserAgent } from "./user-agent";
enum RequestType {
XCLOUD = 'xcloud',
@@ -441,16 +440,17 @@ class XcloudInterceptor {
let overrideMkb: boolean | null = null;
if (getPref(PrefKey.NATIVE_MKB_DISABLED) || UserAgent.isMobile()) {
overrideMkb = false;
} else if (BX_FLAGS.ForceNativeMkbTitles.includes(STATES.currentStream.titleInfo!.details.productId)) {
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'on' || BX_FLAGS.ForceNativeMkbTitles.includes(STATES.currentStream.titleInfo!.details.productId)) {
overrideMkb = true;
}
if (getPref(PrefKey.NATIVE_MKB_ENABLED) === 'off') {
overrideMkb = false;
}
if (overrideMkb !== null) {
overrides.inputConfiguration = Object.assign(overrides.inputConfiguration, {
enableMouseInput: overrideMkb,
enableAbsoluteMouse: overrideMkb,
enableKeyboardInput: overrideMkb,
});
}

View File

@@ -1,7 +1,7 @@
import { CE } from "@utils/html";
import { SUPPORTED_LANGUAGES, t } from "@utils/translation";
import { SettingElement, SettingElementType } from "@utils/settings";
import { UserAgentProfile } from "@utils/user-agent";
import { UserAgent, UserAgentProfile } from "@utils/user-agent";
import { StreamStat } from "@modules/stream/stream-stats";
import type { PreferenceSetting, PreferenceSettings } from "@/types/preferences";
import { AppInterface, STATES } from "@utils/global";
@@ -44,7 +44,10 @@ export enum PrefKey {
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
NATIVE_MKB_DISABLED = 'native_mkb_disabled',
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'native_mkb_scroll_y_sensitivity',
MKB_ENABLED = 'mkb_enabled',
MKB_HIDE_IDLE_CURSOR = 'mkb_hide_idle_cursor',
MKB_ABSOLUTE_MOUSE = 'mkb_absolute_mouse',
@@ -337,8 +340,6 @@ export class Preferences {
} else {
return (value / (1024 * 1000)).toFixed(1) + ' Mb/s';
}
return null;
},
},
migrate: function(savedPrefs: any, value: any) {
@@ -436,9 +437,64 @@ export class Preferences {
},
},
[PrefKey.NATIVE_MKB_DISABLED]: {
label: t('disable-native-mkb'),
default: false,
[PrefKey.NATIVE_MKB_ENABLED]: {
label: t('native-mkb'),
default: 'default',
options: {
default: t('default'),
on: t('on'),
off: t('off'),
},
ready: (setting: PreferenceSetting) => {
if (AppInterface) {
} else if (UserAgent.isMobile()) {
setting.unsupported = true;
setting.default = 'off';
delete setting.options!['default'];
delete setting.options!['on'];
} else {
delete setting.options!['on'];
}
},
},
[PrefKey.NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY]: {
label: t('horizontal-scroll-sensitivity'),
type: SettingElementType.NUMBER_STEPPER,
default: 0,
min: 0,
max: 100 * 100,
steps: 10,
params: {
exactTicks: 20 * 100,
customTextValue: (value: any) => {
if (!value) {
return t('default');
}
return (value / 100).toFixed(1) + 'x';
},
},
},
[PrefKey.NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY]: {
label: t('vertical-scroll-sensitivity'),
type: SettingElementType.NUMBER_STEPPER,
default: 0,
min: 0,
max: 100 * 100,
steps: 10,
params: {
exactTicks: 20 * 100,
customTextValue: (value: any) => {
if (!value) {
return t('default');
}
return (value / 100).toFixed(1) + 'x';
},
},
},
[PrefKey.MKB_DEFAULT_PRESET_ID]: {

View File

@@ -4,8 +4,8 @@ export const SUPPORTED_LANGUAGES = {
'en-US': 'English (United States)',
'ca-CA': 'Català',
'en-ID': 'Bahasa Indonesia',
'de-DE': 'Deutsch',
'en-ID': 'Bahasa Indonesia',
'es-ES': 'español (España)',
'fr-FR': 'français',
'it-IT': 'italiano',
@@ -52,6 +52,7 @@ const Texts = {
"clarity-boost-warning": "These settings don't work when the Clarity Boost mode is ON",
"clear": "Clear",
"close": "Close",
"close-app": "Close app",
"combine-audio-video-streams": "Combine audio & video streams",
"combine-audio-video-streams-summary": "May fix the laggy audio problem",
"conditional-formatting": "Conditional formatting text color",
@@ -77,7 +78,6 @@ const Texts = {
"device-vibration-not-using-gamepad": "On when not using gamepad",
"disable": "Disable",
"disable-home-context-menu": "Disable context menu in Home page",
"disable-native-mkb": "Disable native Mouse & Keyboard feature",
"disable-post-stream-feedback-dialog": "Disable post-stream feedback dialog",
"disable-social-features": "Disable social features",
"disable-xcloud-analytics": "Disable xCloud analytics",
@@ -106,7 +106,9 @@ const Texts = {
"hide-scrollbar": "Hide web page's scrollbar",
"hide-system-menu-icon": "Hide System menu's icon",
"hide-touch-controller": "Hide touch controller",
"horizontal-scroll-sensitivity": "Horizontal scroll sensitivity",
"horizontal-sensitivity": "Horizontal sensitivity",
"ignore": "Ignore",
"import": "Import",
"increase": "Increase",
"install-android": "Install Better xCloud app for Android",
@@ -125,8 +127,10 @@ const Texts = {
"mkb-click-to-activate": "Click to activate",
"mkb-disclaimer": "Using this feature when playing online could be viewed as cheating",
"mouse-and-keyboard": "Mouse & Keyboard",
"mouse-wheel": "Mouse wheel",
"muted": "Muted",
"name": "Name",
"native-mkb": "Native Mouse & Keyboard",
"new": "New",
"no-consoles-found": "No consoles found",
"normal": "Normal",
@@ -144,23 +148,23 @@ const Texts = {
"preset": "Preset",
"press-esc-to-cancel": "Press Esc to cancel",
"press-key-to-toggle-mkb": [
(e: any) => `Press ${e.key} to toggle the Mouse and Keyboard feature`,
(e: any) => `Premeu ${e.key} per alternar la funció de teclat i ratolí`,
(e: any) => `${e.key}: Maus- und Tastaturunterstützung an-/ausschalten`,
(e: any) => `Tekan ${e.key} untuk mengaktifkan fitur Mouse dan Keyboard`,
(e: any) => `Pulsa ${e.key} para activar la función de ratón y teclado`,
(e: any) => `Appuyez sur ${e.key} pour activer/désactiver la fonction Souris et Clavier`,
(e: any) => `Premi ${e.key} per attivare o disattivare la funzione Mouse e Tastiera`,
(e: any) => `${e.key} キーでマウスとキーボードの機能を切り替える`,
(e: any) => `${e.key} 키를 눌러 마우스와 키보드 기능을 활성화 하십시오`,
(e: any) => `Naciśnij ${e.key}, aby przełączyć funkcję myszy i klawiatury`,
(e: any) => `Pressione ${e.key} para ativar/desativar a função de Mouse e Teclado`,
(e: any) => `Нажмите ${e.key} для переключения функции мыши и клавиатуры`,
(e: any) => `Press ${e.key} to toggle this feature`,
,
(e: any) => `${e.key}: Funktion an-/ausschalten`,
,
,
(e: any) => `Appuyez sur ${e.key} pour activer cette fonctionnalité`,
(e: any) => `Premi ${e.key} per attivare questa funzionalità`,
(e: any) => `${e.key} でこの機能を切`,
,
,
,
,
,
(e: any) => `Etkinleştirmek için ${e.key} tuşuna basın`,
(e: any) => `Натисніть ${e.key} щоб перемкнути цю функцію`,
(e: any) => `Nhấn ${e.key} để bật/tắt tính năng này`,
,
(e: any) => `Klavye ve fare özelliğini açmak için ${e.key} tuşuna basın`,
(e: any) => `Натисніть ${e.key}, щоб увімкнути або вимкнути функцію миші та клавіатури`,
(e: any) => `Nhấn ${e.key} để bật/tắt tính năng Chuột và Bàn phím`,
(e: any) => `按下 ${e.key} 切换键鼠模式`,
],
"press-to-bind": "Press a key or do a mouse click to bind...",
"prompt-preset-name": "Preset's name:",
@@ -261,10 +265,12 @@ const Texts = {
"unmuted": "Unmuted",
"use-mouse-absolute-position": "Use mouse's absolute position",
"user-agent-profile": "User-Agent profile",
"vertical-scroll-sensitivity": "Vertical scroll sensitivity",
"vertical-sensitivity": "Vertical sensitivity",
"vibration-intensity": "Vibration intensity",
"vibration-status": "Vibration",
"video": "Video",
"virtual-controller": "Virtual controller",
"visual-quality": "Visual quality",
"visual-quality-high": "High",
"visual-quality-low": "Low",