mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-08-06 13:18:27 +02:00
Add native MKB support for Android app
This commit is contained in:
@@ -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) {
|
||||
|
@@ -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) {
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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 += `
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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));
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
});
|
||||
}
|
||||
|
@@ -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]: {
|
||||
|
@@ -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",
|
||||
|
Reference in New Issue
Block a user