From b2e932cc4c438827b920fdcfb60ae026e714490f Mon Sep 17 00:00:00 2001
From: redphx <96280+redphx@users.noreply.github.com>
Date: Fri, 10 May 2024 18:35:40 +0700
Subject: [PATCH] Game bar (#392)
* Fix games with custom touch control sometimes not showing touch icon
* Create game-bar with screenshot button
* Disable Game bar when opening the Guide
* Remove SCREENSHOT_BUTTON_POSITION pref
* Make the touch control action functional
* Show game bar when the game starts
* Fix 720p/High not working (#387)
* Update icons
* Update game bar's animations
* Reset states of Game bar actions before playing
* Don't show Touch control action on non-touch-supported devices
* Clean up
* Update translations
* Update actions' texts
* Clean up
---
src/assets/css/game-bar.styl | 91 ++++++++++
src/assets/css/root.styl | 3 +-
src/assets/css/stream-actions.styl | 46 -----
src/assets/css/styles.styl | 2 +-
src/assets/svg/camera.svg | 6 +
src/assets/svg/caret-right.svg | 5 +
src/assets/svg/touch-control-disable.svg | 9 +
src/assets/svg/touch-control-enable.svg | 6 +
src/index.ts | 41 +----
src/modules/game-bar/action-base.ts | 6 +
src/modules/game-bar/action-screenshot.ts | 78 +++++++++
src/modules/game-bar/action-touch-control.ts | 51 ++++++
src/modules/game-bar/game-bar.ts | 116 +++++++++++++
src/modules/screenshot.ts | 100 -----------
src/modules/touch-controller.ts | 59 ++-----
src/modules/ui/global-settings.ts | 1 -
src/modules/ui/ui.ts | 7 +-
src/utils/bx-event.ts | 2 +
src/utils/bx-exposed.ts | 22 +--
src/utils/bx-icon.ts | 9 +
src/utils/network.ts | 7 +-
src/utils/preferences.ts | 10 --
src/utils/translation.ts | 171 ++++++++++++-------
23 files changed, 533 insertions(+), 315 deletions(-)
create mode 100644 src/assets/css/game-bar.styl
delete mode 100644 src/assets/css/stream-actions.styl
create mode 100644 src/assets/svg/camera.svg
create mode 100644 src/assets/svg/caret-right.svg
create mode 100644 src/assets/svg/touch-control-disable.svg
create mode 100644 src/assets/svg/touch-control-enable.svg
create mode 100644 src/modules/game-bar/action-base.ts
create mode 100644 src/modules/game-bar/action-screenshot.ts
create mode 100644 src/modules/game-bar/action-touch-control.ts
create mode 100644 src/modules/game-bar/game-bar.ts
delete mode 100644 src/modules/screenshot.ts
diff --git a/src/assets/css/game-bar.styl b/src/assets/css/game-bar.styl
new file mode 100644
index 0000000..84cd586
--- /dev/null
+++ b/src/assets/css/game-bar.styl
@@ -0,0 +1,91 @@
+#bx-game-bar {
+ z-index: var(--bx-game-bar-z-index);
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 40px;
+ height: 90px;
+ overflow: visible;
+ cursor: pointer;
+
+ > svg {
+ display: none;
+ pointer-events: none;
+ position: absolute;
+ height: 28px;
+ margin-top: 16px;
+ }
+
+ @media (hover: hover) {
+ &:hover {
+ > svg {
+ display: block;
+ }
+ }
+ }
+
+ .bx-game-bar-container {
+ opacity: 0;
+ position absolute;
+ display: flex;
+ overflow: hidden;
+ background: #1a1b1ee8;
+ border-radius: 0 10px 10px 0;
+ box-shadow: 0px 0px 6px #1c1c1c;
+ transition: opacity 0.1s ease-in;
+
+ &.bx-show {
+ opacity: 1;
+
+ + svg {
+ display: none !important;
+ }
+ }
+
+ &.bx-hide {
+ opacity: 0;
+ }
+
+ button {
+ width: 60px;
+ height: 60px;
+
+ svg {
+ width: 28px;
+ height: 28px;
+ transition: transform 0.08s ease 0s;
+ }
+
+ &:hover {
+ border-radius: 0;
+ }
+
+ &:active {
+ svg {
+ transform: scale(0.75);
+ }
+ }
+ }
+
+ /* Touch controller buttons */
+ div[data-enabled] {
+ button {
+ display: none;
+ }
+ }
+
+ /* Show disable button */
+ div[data-enabled='true'] {
+ button:last-of-type {
+ display: block;
+ }
+ }
+
+ /* Show enable button */
+ div[data-enabled='false'] {
+ button:first-of-type {
+ display: block;
+ }
+ }
+ }
+}
diff --git a/src/assets/css/root.styl b/src/assets/css/root.styl
index 4ac4a7e..56f5e13 100644
--- a/src/assets/css/root.styl
+++ b/src/assets/css/root.styl
@@ -27,8 +27,7 @@
--bx-stats-bar-z-index: 9001;
--bx-stream-settings-z-index: 9000;
--bx-mkb-pointer-lock-msg-z-index: 8999;
- --bx-screenshot-z-index: 8888;
- --bx-touch-controller-bar-z-index: 5555;
+ --bx-game-bar-z-index: 8888;
--bx-wait-time-box-z-index: 100;
}
diff --git a/src/assets/css/stream-actions.styl b/src/assets/css/stream-actions.styl
deleted file mode 100644
index db6d1de..0000000
--- a/src/assets/css/stream-actions.styl
+++ /dev/null
@@ -1,46 +0,0 @@
-.bx-screenshot-button {
- display: none;
- opacity: 0;
- position: fixed;
- bottom: 0;
- box-sizing: border-box;
- width: 60px;
- height: 90px;
- padding: 16px 16px 46px 16px;
- background-size: cover;
- background-repeat: no-repeat;
- background-origin: content-box;
- filter: drop-shadow(0 0 2px #000000B0);
- transition: opacity 0.1s ease-in-out 0s, padding 0.1s ease-in 0s;
- z-index: var(--bx-screenshot-z-index);
-
- /* Credit: https://phosphoricons.com */
- background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDMyIDMyIiBmaWxsPSIjZmZmIj48cGF0aCBkPSJNMjguMzA4IDUuMDM4aC00LjI2NWwtMi4wOTctMy4xNDVhMS4yMyAxLjIzIDAgMCAwLTEuMDIzLS41NDhoLTkuODQ2YTEuMjMgMS4yMyAwIDAgMC0xLjAyMy41NDhMNy45NTYgNS4wMzhIMy42OTJBMy43MSAzLjcxIDAgMCAwIDAgOC43MzF2MTcuMjMxYTMuNzEgMy43MSAwIDAgMCAzLjY5MiAzLjY5MmgyNC42MTVBMy43MSAzLjcxIDAgMCAwIDMyIDI1Ljk2MlY4LjczMWEzLjcxIDMuNzEgMCAwIDAtMy42OTItMy42OTJ6bS02Ljc2OSAxMS42OTJjMCAzLjAzOS0yLjUgNS41MzgtNS41MzggNS41MzhzLTUuNTM4LTIuNS01LjUzOC01LjUzOCAyLjUtNS41MzggNS41MzgtNS41MzggNS41MzggMi41IDUuNTM4IDUuNTM4eiIvPjwvc3ZnPgo=');
-
- &[data-showing=true] {
- opacity: 0.9;
- }
-
- &[data-capturing=true] {
- padding: 8px 8px 38px 8px;
- }
-}
-
-.bx-screenshot-canvas {
- display: none;
-}
-
-#bx-touch-controller-bar {
- display: none;
- opacity: 0;
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: 6vh;
- z-index: var(--bx-touch-controller-bar-z-index);
-
- &[data-showing=true] {
- display: block;
- }
-}
diff --git a/src/assets/css/styles.styl b/src/assets/css/styles.styl
index 475bea2..d9c6780 100644
--- a/src/assets/css/styles.styl
+++ b/src/assets/css/styles.styl
@@ -10,7 +10,7 @@
@import 'stream.styl';
@import 'number-stepper.styl';
-@import 'stream-actions.styl';
+@import 'game-bar.styl';
@import 'stream-stats.styl';
@import 'stream-settings.styl';
@import 'mkb.styl';
diff --git a/src/assets/svg/camera.svg b/src/assets/svg/camera.svg
new file mode 100644
index 0000000..5be4147
--- /dev/null
+++ b/src/assets/svg/camera.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/svg/caret-right.svg b/src/assets/svg/caret-right.svg
new file mode 100644
index 0000000..804de29
--- /dev/null
+++ b/src/assets/svg/caret-right.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/svg/touch-control-disable.svg b/src/assets/svg/touch-control-disable.svg
new file mode 100644
index 0000000..a3a29d7
--- /dev/null
+++ b/src/assets/svg/touch-control-disable.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/assets/svg/touch-control-enable.svg b/src/assets/svg/touch-control-enable.svg
new file mode 100644
index 0000000..2764d91
--- /dev/null
+++ b/src/assets/svg/touch-control-enable.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/index.ts b/src/index.ts
index 899008f..be9ad9f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -11,7 +11,7 @@ import { StreamBadges } from "@modules/stream/stream-badges";
import { StreamStats } from "@modules/stream/stream-stats";
import { addCss } from "@utils/css";
import { Toast } from "@utils/toast";
-import { setupBxUi, updateVideoPlayerCss } from "@modules/ui/ui";
+import { setupStreamUi, updateVideoPlayerCss } from "@modules/ui/ui";
import { PrefKey, getPref } from "@utils/preferences";
import { LoadingScreen } from "@modules/loading-screen";
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
@@ -27,6 +27,7 @@ import { patchAudioContext, patchCanvasContext, patchMeControl, patchRtcCodecs,
import { STATES } from "@utils/global";
import { injectStreamMenuButtons } from "@modules/stream/stream-ui";
import { BxLogger } from "@utils/bx-logger";
+import { GameBar } from "./modules/game-bar/game-bar";
// Handle login page
if (window.location.pathname.includes('/auth/msa')) {
@@ -123,9 +124,7 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => {
}
// Setup UI
- setupBxUi();
-
-
+ setupStreamUi();
});
// Setup loading screen
@@ -148,32 +147,14 @@ window.addEventListener(BxEvent.STREAM_PLAYING, e => {
STATES.isPlaying = true;
injectStreamMenuButtons();
- /*
- if (getPref(Preferences.CONTROLLER_ENABLE_SHORTCUTS)) {
- GamepadHandler.startPolling();
- }
- */
- const PREF_SCREENSHOT_BUTTON_POSITION = getPref(PrefKey.SCREENSHOT_BUTTON_POSITION);
+ GameBar.reset();
+ GameBar.enable();
+ GameBar.showBar();
+
STATES.currentStream.$screenshotCanvas!.width = $video.videoWidth;
STATES.currentStream.$screenshotCanvas!.height = $video.videoHeight;
updateVideoPlayerCss();
-
- // Setup screenshot button
- if (PREF_SCREENSHOT_BUTTON_POSITION !== 'none') {
- const $btn = document.querySelector('.bx-screenshot-button')! as HTMLElement;
- $btn.classList.remove('bx-gone');
- $btn.style.display = 'block';
-
- if (PREF_SCREENSHOT_BUTTON_POSITION === 'bottom-right') {
- $btn.style.right = '0';
- } else {
- $btn.style.left = '0';
- }
- }
-
- const $touchControllerBar = document.getElementById('bx-touch-controller-bar');
- $touchControllerBar && $touchControllerBar.classList.remove('bx-gone');
});
window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
@@ -199,13 +180,9 @@ window.addEventListener(BxEvent.STREAM_STOPPED, e => {
STATES.currentStream.$video = null;
StreamStats.onStoppedPlaying();
- const $screenshotBtn = document.querySelector('.bx-screenshot-button');
- if ($screenshotBtn) {
- $screenshotBtn.removeAttribute('style');
- }
-
MouseCursorHider.stop();
TouchController.reset();
+ GameBar.disable();
});
@@ -231,7 +208,7 @@ function main() {
// Setup UI
addCss();
Toast.setup();
- BX_FLAGS.PreloadUi && setupBxUi();
+ BX_FLAGS.PreloadUi && setupStreamUi();
StreamBadges.setupEvents();
StreamStats.setupEvents();
diff --git a/src/modules/game-bar/action-base.ts b/src/modules/game-bar/action-base.ts
new file mode 100644
index 0000000..ecd678e
--- /dev/null
+++ b/src/modules/game-bar/action-base.ts
@@ -0,0 +1,6 @@
+export abstract class BaseGameBarAction {
+ constructor() {}
+ reset() {}
+
+ abstract render(): HTMLElement;
+}
diff --git a/src/modules/game-bar/action-screenshot.ts b/src/modules/game-bar/action-screenshot.ts
new file mode 100644
index 0000000..3d6121a
--- /dev/null
+++ b/src/modules/game-bar/action-screenshot.ts
@@ -0,0 +1,78 @@
+import { BxEvent } from "@utils/bx-event";
+import { AppInterface, STATES } from "@utils/global";
+import { BxIcon } from "@utils/bx-icon";
+import { createButton, ButtonStyle, CE } from "@utils/html";
+import { BaseGameBarAction } from "./action-base";
+import { t } from "@utils/translation";
+
+export class ScreenshotAction extends BaseGameBarAction {
+ $content: HTMLElement;
+
+ constructor() {
+ super();
+
+ const currentStream = STATES.currentStream;
+ currentStream.$screenshotCanvas = CE('canvas', {'class': 'bx-gone'});
+ document.documentElement.appendChild(currentStream.$screenshotCanvas!);
+
+ const onClick = (e: Event) => {
+ BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
+ this.takeScreenshot();
+ };
+
+ this.$content = createButton({
+ style: ButtonStyle.GHOST,
+ icon: BxIcon.SCREENSHOT,
+ title: t('take-screenshot'),
+ onClick: onClick,
+ });
+ }
+
+ render(): HTMLElement {
+ return this.$content;
+ }
+
+ takeScreenshot(callback?: any) {
+ const currentStream = STATES.currentStream;
+ const $video = currentStream.$video;
+ const $canvas = currentStream.$screenshotCanvas;
+ if (!$video || !$canvas) {
+ return;
+ }
+
+ const $canvasContext = $canvas.getContext('2d', {
+ alpha: false,
+ willReadFrequently: false,
+ })!;
+
+ $canvasContext.drawImage($video, 0, 0, $canvas.width, $canvas.height);
+
+ // Get data URL and pass to parent app
+ if (AppInterface) {
+ const data = $canvas.toDataURL('image/png').split(';base64,')[1];
+ AppInterface.saveScreenshot(currentStream.titleId, data);
+
+ // Free screenshot from memory
+ $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
+
+ callback && callback();
+ return;
+ }
+
+ $canvas && $canvas.toBlob(blob => {
+ // Download screenshot
+ const now = +new Date;
+ const $anchor = CE('a', {
+ 'download': `${currentStream.titleId}-${now}.png`,
+ 'href': URL.createObjectURL(blob!),
+ });
+ $anchor.click();
+
+ // Free screenshot from memory
+ URL.revokeObjectURL($anchor.href);
+ $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
+
+ callback && callback();
+ }, 'image/png');
+ }
+}
diff --git a/src/modules/game-bar/action-touch-control.ts b/src/modules/game-bar/action-touch-control.ts
new file mode 100644
index 0000000..03bb13c
--- /dev/null
+++ b/src/modules/game-bar/action-touch-control.ts
@@ -0,0 +1,51 @@
+import { BxEvent } from "@utils/bx-event";
+import { BxIcon } from "@utils/bx-icon";
+import { createButton, ButtonStyle, CE } from "@utils/html";
+import { TouchController } from "@modules/touch-controller";
+import { BaseGameBarAction } from "./action-base";
+import { t } from "@utils/translation";
+
+export class TouchControlAction extends BaseGameBarAction {
+ $content: HTMLElement;
+
+ constructor() {
+ super();
+
+ const onClick = (e: Event) => {
+ BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
+
+ const $parent = (e as any).target.closest('div[data-enabled]');
+ let enabled = $parent.getAttribute('data-enabled', 'true') === 'true';
+ $parent.setAttribute('data-enabled', (!enabled).toString());
+
+ TouchController.toggleVisibility(enabled);
+ };
+
+ const $btnEnable = createButton({
+ style: ButtonStyle.GHOST,
+ icon: BxIcon.TOUCH_CONTROL_ENABLE,
+ title: t('show-touch-controller'),
+ onClick: onClick,
+ });
+
+ const $btnDisable = createButton({
+ style: ButtonStyle.GHOST,
+ icon: BxIcon.TOUCH_CONTROL_DISABLE,
+ title: t('hide-touch-controller'),
+ onClick: onClick,
+ });
+
+ this.$content = CE('div', {'data-enabled': 'true'},
+ $btnEnable,
+ $btnDisable,
+ );
+ }
+
+ render(): HTMLElement {
+ return this.$content;
+ }
+
+ reset(): void {
+ this.$content.setAttribute('data-enabled', 'true');
+ }
+}
diff --git a/src/modules/game-bar/game-bar.ts b/src/modules/game-bar/game-bar.ts
new file mode 100644
index 0000000..11e7ef4
--- /dev/null
+++ b/src/modules/game-bar/game-bar.ts
@@ -0,0 +1,116 @@
+import { CE, createSvgIcon } from "@utils/html";
+import { ScreenshotAction } from "./action-screenshot";
+import { TouchControlAction } from "./action-touch-control";
+import { BxEvent } from "@utils/bx-event";
+import { BxIcon } from "@utils/bx-icon";
+import type { BaseGameBarAction } from "./action-base";
+import { STATES } from "@utils/global";
+import { PrefKey, getPref } from "@utils/preferences";
+
+
+export class GameBar {
+ static readonly #VISIBLE_DURATION = 2000;
+ static #timeout: number | null;
+
+ static #$gameBar: HTMLElement;
+ static #$container: HTMLElement;
+
+ static #$actions: BaseGameBarAction[] = [];
+
+ static #beginHideTimeout() {
+ GameBar.#clearHideTimeout();
+
+ GameBar.#timeout = window.setTimeout(() => {
+ GameBar.#timeout = null;
+ GameBar.hideBar();
+ }, GameBar.#VISIBLE_DURATION);
+ }
+
+ static #clearHideTimeout() {
+ GameBar.#timeout && clearTimeout(GameBar.#timeout);
+ GameBar.#timeout = null;
+ }
+
+ static enable() {
+ GameBar.#$gameBar && GameBar.#$gameBar.classList.remove('bx-gone');
+ }
+
+ static disable() {
+ GameBar.#$gameBar && GameBar.#$gameBar.classList.add('bx-gone');
+ GameBar.hideBar();
+ }
+
+ static showBar() {
+ if (!GameBar.#$container) {
+ return;
+ }
+
+ GameBar.#$container.classList.remove('bx-offscreen', 'bx-hide');
+ GameBar.#$container.classList.add('bx-show');
+
+ GameBar.#beginHideTimeout();
+ }
+
+ static hideBar() {
+ if (!GameBar.#$container) {
+ return;
+ }
+
+ GameBar.#$container.classList.remove('bx-show');
+ GameBar.#$container.classList.add('bx-hide');
+ }
+
+ // Reset all states
+ static reset() {
+ for (const action of GameBar.#$actions) {
+ action.reset();
+ }
+ }
+
+ static setup() {
+ let $container;
+ const $gameBar = CE('div', {id: 'bx-game-bar', class: 'bx-gone'},
+ $container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
+ createSvgIcon(BxIcon.CARET_RIGHT),
+ );
+
+ GameBar.#$actions = [
+ new ScreenshotAction(),
+ ...(STATES.hasTouchSupport && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== 'off') ? [new TouchControlAction()] : []),
+ ];
+
+ for (const action of GameBar.#$actions) {
+ $container.appendChild(action.render());
+ }
+
+ // Toggle game bar when clicking on the game bar box
+ $gameBar.addEventListener('click', e => {
+ if (e.target === $gameBar) {
+ if ($container.classList.contains('bx-show')) {
+ GameBar.hideBar();
+ } else {
+ GameBar.showBar();
+ }
+ }
+ });
+
+ // Hide game bar after clicking on an action
+ window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, GameBar.hideBar);
+
+ $container.addEventListener('pointerover', GameBar.#clearHideTimeout);
+ $container.addEventListener('pointerout', GameBar.#beginHideTimeout);
+
+ // Add animation when hiding game bar
+ $container.addEventListener('transitionend', e => {
+ const classList = $container.classList;
+ if (classList.contains('bx-hide')) {
+ classList.remove('bx-offscreen', 'bx-hide');
+ classList.add('bx-offscreen');
+ }
+ });
+
+ document.documentElement.appendChild($gameBar);
+ GameBar.#$gameBar = $gameBar;
+ GameBar.#$container = $container;
+ }
+}
diff --git a/src/modules/screenshot.ts b/src/modules/screenshot.ts
deleted file mode 100644
index a02cb37..0000000
--- a/src/modules/screenshot.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import { STATES, AppInterface } from "@utils/global";
-import { CE } from "@utils/html";
-
-export function takeScreenshot(callback: any) {
- const currentStream = STATES.currentStream!;
- const $video = currentStream.$video;
- const $canvas = currentStream.$screenshotCanvas;
- if (!$video || !$canvas) {
- return;
- }
-
- const $canvasContext = $canvas.getContext('2d', {
- alpha: false,
- willReadFrequently: false,
- })!;
-
- $canvasContext.drawImage($video, 0, 0, $canvas.width, $canvas.height);
-
- // Get data URL and pass to parent app
- if (AppInterface) {
- const data = $canvas.toDataURL('image/png').split(';base64,')[1];
- AppInterface.saveScreenshot(currentStream.titleId, data);
-
- // Free screenshot from memory
- $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
-
- callback && callback();
- return;
- }
-
- $canvas && $canvas.toBlob(blob => {
- // Download screenshot
- const now = +new Date;
- const $anchor = CE('a', {
- 'download': `${currentStream.titleId}-${now}.png`,
- 'href': URL.createObjectURL(blob!),
- });
- $anchor.click();
-
- // Free screenshot from memory
- URL.revokeObjectURL($anchor.href);
- $canvasContext.clearRect(0, 0, $canvas.width, $canvas.height);
-
- callback && callback();
- }, 'image/png');
-}
-
-
-export function setupScreenshotButton() {
- const currentStream = STATES.currentStream!
- currentStream.$screenshotCanvas = CE('canvas', {'class': 'bx-screenshot-canvas'});
- document.documentElement.appendChild(currentStream.$screenshotCanvas!);
-
- const delay = 2000;
- const $btn = CE('div', {'class': 'bx-screenshot-button', 'data-showing': false});
-
- let timeout: number | null;
- const detectDbClick = (e: MouseEvent) => {
- if (!currentStream.$video) {
- timeout = null;
- $btn.style.display = 'none';
- return;
- }
-
- if (timeout) {
- clearTimeout(timeout);
- timeout = null;
- $btn.setAttribute('data-capturing', 'true');
-
- takeScreenshot(() => {
- // Hide button
- $btn.setAttribute('data-showing', 'false');
- window.setTimeout(() => {
- if (!timeout) {
- $btn.setAttribute('data-capturing', 'false');
- }
- }, 100);
- });
-
- return;
- }
-
- const isShowing = $btn.getAttribute('data-showing') === 'true';
- if (!isShowing) {
- // Show button
- $btn.setAttribute('data-showing', 'true');
- $btn.setAttribute('data-capturing', 'false');
-
- timeout && clearTimeout(timeout);
- timeout = window.setTimeout(() => {
- timeout = null;
- $btn.setAttribute('data-showing', 'false');
- $btn.setAttribute('data-capturing', 'false');
- }, delay);
- }
- }
-
- $btn.addEventListener('mousedown', detectDbClick);
- document.documentElement.appendChild($btn);
-}
diff --git a/src/modules/touch-controller.ts b/src/modules/touch-controller.ts
index 466de00..b9ec819 100644
--- a/src/modules/touch-controller.ts
+++ b/src/modules/touch-controller.ts
@@ -1,5 +1,5 @@
import { STATES } from "@utils/global";
-import { CE, escapeHtml } from "@utils/html";
+import { escapeHtml } from "@utils/html";
import { Toast } from "@utils/toast";
import { BxEvent } from "@utils/bx-event";
import { BX_FLAGS } from "@utils/bx-flags";
@@ -12,7 +12,11 @@ const LOG_TAG = 'TouchController';
export class TouchController {
static readonly #EVENT_SHOW_DEFAULT_CONTROLLER = new MessageEvent('message', {
- data: '{"content":"{\\"layoutId\\":\\"\\"}","target":"/streaming/touchcontrols/showlayoutv2","type":"Message"}',
+ data: JSON.stringify({
+ content: '{"layoutId":""}',
+ target: '/streaming/touchcontrols/showlayoutv2',
+ type: 'Message',
+ }),
origin: 'better-xcloud',
});
@@ -23,17 +27,17 @@ export class TouchController {
});
*/
- static #$bar: HTMLElement;
static #$style: HTMLStyleElement;
static #enable = false;
- static #showing = false;
static #dataChannel: RTCDataChannel | null;
static #customLayouts: {[index: string]: any} = {};
static #baseCustomLayouts: {[index: string]: any} = {};
static #currentLayoutId: string;
+ static #customList: string[];
+
static enable() {
TouchController.#enable = true;
}
@@ -48,37 +52,28 @@ export class TouchController {
static #showDefault() {
TouchController.#dispatchMessage(TouchController.#EVENT_SHOW_DEFAULT_CONTROLLER);
- TouchController.#showing = true;
}
static #show() {
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.remove('bx-offscreen');
- TouchController.#showing = true;
}
static #hide() {
document.querySelector('#BabylonCanvasContainer-main')?.parentElement?.classList.add('bx-offscreen');
- TouchController.#showing = false;
}
- static #toggleVisibility() {
+ static toggleVisibility(status: boolean) {
if (!TouchController.#dataChannel) {
return;
}
- TouchController.#showing ? TouchController.#hide() : TouchController.#show();
- }
-
- static #toggleBar(value: boolean) {
- TouchController.#$bar && TouchController.#$bar.setAttribute('data-showing', value.toString());
+ status ? TouchController.#hide() : TouchController.#show();
}
static reset() {
TouchController.#enable = false;
- TouchController.#showing = false;
TouchController.#dataChannel = null;
- TouchController.#$bar && TouchController.#$bar.removeAttribute('data-showing');
TouchController.#$style && (TouchController.#$style.textContent = '');
}
@@ -195,15 +190,19 @@ export class TouchController {
}
static updateCustomList() {
+ const key = 'better_xcloud_custom_touch_layouts';
+ TouchController.#customList = JSON.parse(window.localStorage.getItem(key) || '[]');
+
NATIVE_FETCH('https://raw.githubusercontent.com/redphx/better-xcloud/gh-pages/touch-layouts/ids.json')
.then(response => response.json())
.then(json => {
- window.localStorage.setItem('better_xcloud_custom_touch_layouts', JSON.stringify(json));
+ TouchController.#customList = json;
+ window.localStorage.setItem(key, JSON.stringify(json));
});
}
static getCustomList(): string[] {
- return JSON.parse(window.localStorage.getItem('better_xcloud_custom_touch_layouts') || '[]');
+ return TouchController.#customList;
}
static setup() {
@@ -223,32 +222,9 @@ export class TouchController {
});
};
- const $fragment = document.createDocumentFragment();
const $style = document.createElement('style');
- $fragment.appendChild($style);
+ document.documentElement.appendChild($style);
- const $bar = CE('div', {'id': 'bx-touch-controller-bar'});
- $fragment.appendChild($bar);
-
- document.documentElement.appendChild($fragment);
-
- // Setup double-tap event
- let clickTimeout: number | null;
- $bar.addEventListener('mousedown', (e: MouseEvent) => {
- clickTimeout && clearTimeout(clickTimeout);
- if (clickTimeout) {
- // Double-clicked
- clickTimeout = null;
- TouchController.#toggleVisibility();
- return;
- }
-
- clickTimeout = window.setTimeout(() => {
- clickTimeout = null;
- }, 400);
- });
-
- TouchController.#$bar = $bar;
TouchController.#$style = $style;
const PREF_STYLE_STANDARD = getPref(PrefKey.STREAM_TOUCH_CONTROLLER_STYLE_STANDARD);
@@ -307,7 +283,6 @@ export class TouchController {
try {
if (msg.data.includes('/titleinfo')) {
const json = JSON.parse(JSON.parse(msg.data).content);
- TouchController.#toggleBar(json.focused);
focused = json.focused;
if (!json.focused) {
diff --git a/src/modules/ui/global-settings.ts b/src/modules/ui/global-settings.ts
index d3eec15..9fcf8f9 100644
--- a/src/modules/ui/global-settings.ts
+++ b/src/modules/ui/global-settings.ts
@@ -31,7 +31,6 @@ const SETTINGS_UI = {
PrefKey.AUDIO_MIC_ON_PLAYING,
PrefKey.STREAM_DISABLE_FEEDBACK_DIALOG,
- PrefKey.SCREENSHOT_BUTTON_POSITION,
PrefKey.SCREENSHOT_APPLY_FILTERS,
PrefKey.AUDIO_ENABLE_VOLUME_CONTROL,
diff --git a/src/modules/ui/ui.ts b/src/modules/ui/ui.ts
index dfd527a..49218a7 100644
--- a/src/modules/ui/ui.ts
+++ b/src/modules/ui/ui.ts
@@ -5,11 +5,11 @@ import { UserAgent } from "@utils/user-agent";
import { BxEvent } from "@utils/bx-event";
import { MkbRemapper } from "@modules/mkb/mkb-remapper";
import { getPref, PrefKey, toPrefElement } from "@utils/preferences";
-import { setupScreenshotButton } from "@modules/screenshot";
import { StreamStats } from "@modules/stream/stream-stats";
import { TouchController } from "@modules/touch-controller";
import { t } from "@utils/translation";
import { VibrationManager } from "@modules/vibration-manager";
+import { GameBar } from "../game-bar/game-bar";
export function localRedirect(path: string) {
@@ -468,13 +468,14 @@ div[data-testid="media-container"] {
$elm.textContent = css;
}
-export function setupBxUi() {
+export function setupStreamUi() {
// Prevent initializing multiple times
if (!document.querySelector('.bx-quick-settings-bar')) {
window.addEventListener('resize', updateVideoPlayerCss);
setupQuickSettingsBar();
- setupScreenshotButton();
StreamStats.render();
+
+ GameBar.setup();
}
updateVideoPlayerCss();
diff --git a/src/utils/bx-event.ts b/src/utils/bx-event.ts
index 6264dbe..023c34f 100644
--- a/src/utils/bx-event.ts
+++ b/src/utils/bx-event.ts
@@ -27,6 +27,8 @@ export enum BxEvent {
XCLOUD_SERVERS_READY = 'bx-servers-ready',
DATA_CHANNEL_CREATED = 'bx-data-channel-created',
+
+ GAME_BAR_ACTION_ACTIVATED = 'bx-game-bar-action-activated',
}
export namespace BxEvent {
diff --git a/src/utils/bx-exposed.ts b/src/utils/bx-exposed.ts
index e63c35f..29a2f04 100644
--- a/src/utils/bx-exposed.ts
+++ b/src/utils/bx-exposed.ts
@@ -1,3 +1,4 @@
+import { GameBar } from "@modules/game-bar/game-bar";
import { BxEvent } from "@utils/bx-event";
import { STATES } from "@utils/global";
import { getPref, PrefKey } from "@utils/preferences";
@@ -15,25 +16,12 @@ enum InputType {
export const BxExposed = {
onPollingModeChanged: (mode: 'All' | 'None') => {
if (!STATES.isPlaying) {
- return false;
+ GameBar.disable();
+ return;
}
- const $screenshotBtn = document.querySelector('.bx-screenshot-button');
- const $touchControllerBar = document.getElementById('bx-touch-controller-bar');
-
- if (mode !== 'None') {
- // Hide screenshot button
- $screenshotBtn && $screenshotBtn.classList.add('bx-gone');
-
- // Hide touch controller bar
- $touchControllerBar && $touchControllerBar.classList.add('bx-gone');
- } else {
- // Show screenshot button
- $screenshotBtn && $screenshotBtn.classList.remove('bx-gone');
-
- // Show touch controller bar
- $touchControllerBar && $touchControllerBar.classList.remove('bx-gone');
- }
+ // Toggle Game bar
+ mode !== 'None' ? GameBar.disable() : GameBar.enable();
},
getTitleInfo: () => STATES.currentStream.titleInfo,
diff --git a/src/utils/bx-icon.ts b/src/utils/bx-icon.ts
index 862dfab..2e16174 100644
--- a/src/utils/bx-icon.ts
+++ b/src/utils/bx-icon.ts
@@ -1,3 +1,5 @@
+import iconCadetRight from "@assets/svg/caret-right.svg" with { type: "text" };
+import iconCamera from "@assets/svg/camera.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 iconCursorText from "@assets/svg/cursor-text.svg" with { type: "text" };
@@ -11,6 +13,8 @@ import iconRemotePlay from "@assets/svg/remote-play.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 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" };
export const BxIcon = {
STREAM_SETTINGS: iconStreamSettings,
@@ -28,5 +32,10 @@ export const BxIcon = {
REMOTE_PLAY: iconRemotePlay,
+ CARET_RIGHT: iconCadetRight,
+ SCREENSHOT: iconCamera,
+ TOUCH_CONTROL_ENABLE: iconTouchControlEnable,
+ TOUCH_CONTROL_DISABLE: iconTouchControlDisable,
+
// HAND_TAP = '',
} as const;
diff --git a/src/utils/network.ts b/src/utils/network.ts
index 1c7f23e..01f92ca 100644
--- a/src/utils/network.ts
+++ b/src/utils/network.ts
@@ -438,6 +438,9 @@ class XcloudInterceptor {
overrides.inputConfiguration = overrides.inputConfiguration || {};
overrides.inputConfiguration.enableVibration = true;
+ overrides.videoConfiguration = overrides.videoConfiguration || {};
+ overrides.videoConfiguration.setCodecPreferences = true;
+
// Enable touch controller
if (TouchController.isEnabled()) {
overrides.inputConfiguration.enableTouchInput = true;
@@ -570,7 +573,9 @@ export function interceptHttpRequests() {
const newCustomList = customList.map(item => ({ id: item }));
obj.push(...newCustomList);
- } catch (e) {}
+ } catch (e) {
+ console.log(e);
+ }
}
response.json = () => Promise.resolve(obj);
diff --git a/src/utils/preferences.ts b/src/utils/preferences.ts
index d86c1da..58717d3 100644
--- a/src/utils/preferences.ts
+++ b/src/utils/preferences.ts
@@ -46,7 +46,6 @@ export enum PrefKey {
MKB_ABSOLUTE_MOUSE = 'mkb_absolute_mouse',
MKB_DEFAULT_PRESET_ID = 'mkb_default_preset_id',
- SCREENSHOT_BUTTON_POSITION = 'screenshot_button_position',
SCREENSHOT_APPLY_FILTERS = 'screenshot_apply_filters',
BLOCK_TRACKING = 'block_tracking',
@@ -227,15 +226,6 @@ export class Preferences {
default: false,
},
- [PrefKey.SCREENSHOT_BUTTON_POSITION]: {
- label: t('screenshot-button-position'),
- default: 'bottom-left',
- options: {
- 'bottom-left': t('bottom-left'),
- 'bottom-right': t('bottom-right'),
- 'none': t('disable'),
- },
- },
[PrefKey.SCREENSHOT_APPLY_FILTERS]: {
label: t('screenshot-apply-filters'),
default: false,
diff --git a/src/utils/translation.ts b/src/utils/translation.ts
index 2ed7624..f43bad5 100644
--- a/src/utils/translation.ts
+++ b/src/utils/translation.ts
@@ -23,7 +23,7 @@ const Texts = {
"Activate",
"Activo",
,
- ,
+ "Abilita",
"設定する",
"활성화",
"Aktywuj",
@@ -40,7 +40,7 @@ const Texts = {
"Activated",
"Activado",
,
- ,
+ "In uso",
"設定中",
"활성화 됨",
"Aktywowane",
@@ -57,7 +57,7 @@ const Texts = {
"Active",
"Activo",
,
- ,
+ "Attivo",
"有効",
"활성화",
"Aktywny",
@@ -91,7 +91,7 @@ const Texts = {
"Apply",
"Aplicar",
,
- ,
+ "Applica",
"適用",
,
"Zastosuj",
@@ -303,7 +303,7 @@ const Texts = {
"Яркость",
"Aydınlık",
"Яскравість",
- "Độ sáng",
+ "Độ sáng",
"亮度",
],
"browser-unsupported-feature": [
@@ -414,7 +414,7 @@ const Texts = {
"Clear",
"Borrar",
,
- ,
+ "Pulisci",
"消去",
"비우기",
"Wyczyść",
@@ -448,7 +448,7 @@ const Texts = {
"Combine audio & video streams",
"Combinar flujos de audio y vídeo",
,
- ,
+ "Combinare i flussi audio e video",
"音声を映像ストリーミングと統合",
,
"Połącz strumienie audio i wideo",
@@ -465,7 +465,7 @@ const Texts = {
"May fix the laggy audio problem",
"Puede arreglar el problema de audio con retraso",
,
- ,
+ "Potrebbe risolvere il problema dell'audio irregolare",
"音声の遅延を改善できる可能性があります",
,
"Może rozwiązać problem z zacinającym dźwiękiem",
@@ -499,7 +499,7 @@ const Texts = {
"Do you want to delete this preset?",
"¿Desea eliminar este preajuste?",
"Voulez-vous supprimer ce préréglage?",
- ,
+ "Vuoi eliminare questo profilo?",
"このプリセットを削除しますか?",
"이 프리셋을 삭제하시겠습니까?",
"Czy na pewno chcesz usunąć ten szablon?",
@@ -533,7 +533,7 @@ const Texts = {
"Connected",
"Conectado",
,
- ,
+ "Connesso",
"接続済み",
,
"Połączony",
@@ -618,7 +618,7 @@ const Texts = {
"Controller vibration",
"Vibración del mando",
,
- ,
+ "Vibrazione del controller",
"コントローラーの振動",
"컨트롤러 진동",
"Wibracje kontrolera",
@@ -635,7 +635,7 @@ const Texts = {
"Copy",
"Copiar",
,
- ,
+ "Duplica",
"コピー",
"복사",
"Kopiuj",
@@ -669,7 +669,7 @@ const Texts = {
"Deadzone counterweight",
"Contrapeso de la zona muerta",
,
- ,
+ "Compensazione della zona morta",
"デッドゾーンのカウンターウエイト",
,
"Przeciwwaga martwej strefy",
@@ -737,7 +737,7 @@ const Texts = {
"Device vibration",
"Vibración del dispositivo",
,
- ,
+ "Vibrazione del dispositivo",
"デバイスの振動",
"기기 진동",
"Wibracje urządzenia",
@@ -754,7 +754,7 @@ const Texts = {
"On when not using gamepad",
"Activado cuando no se utiliza el mando",
,
- ,
+ "Abilita quando non si usa un gamepad",
"ゲームパッド未使用時にオン",
"게임패드를 사용하지 않을 때",
"Włączone, gdy nie używasz kontrolera",
@@ -839,7 +839,7 @@ const Texts = {
"Disabled",
"Desactivado",
,
- ,
+ "Disattivato",
"無効",
"비활성화됨",
"Wyłączony",
@@ -856,7 +856,7 @@ const Texts = {
"Disconnected",
"Desconectado",
,
- ,
+ "Disconnesso",
"切断",
,
"Rozłączony",
@@ -907,7 +907,7 @@ const Texts = {
"Enable local co-op support",
"Habilitar soporte co-op local",
,
- ,
+ "Abilita supporto cooperativo locale",
"ローカルマルチプレイのサポートを有効化",
,
"Włącz lokalny co-op",
@@ -924,7 +924,7 @@ const Texts = {
"Only works if the game doesn't require a different profile",
"Solo funciona si el juego no requiere un perfil diferente",
,
- ,
+ "Funziona quando il gioco non richiede un profilo differente",
"別アカウントでのサインインを必要としないゲームのみ動作します",
,
"Działa tylko wtedy, gdy gra nie wymaga innego profilu",
@@ -1026,7 +1026,7 @@ const Texts = {
"Enabled",
"Activado",
,
- ,
+ "Attivato",
"有効",
"활성화됨",
"Włączony",
@@ -1043,7 +1043,7 @@ const Texts = {
"Experimental",
"Experimental",
,
- ,
+ "Sperimentale",
"実験的機能",
,
"Eksperymentalne",
@@ -1060,7 +1060,7 @@ const Texts = {
"Export",
"Exportar",
,
- ,
+ "Esporta",
"エクスポート(書出し)",
"내보내기",
"Eksportuj",
@@ -1094,7 +1094,7 @@ const Texts = {
"Allows playing STW mode on mobile",
"Permitir jugar al modo STW en el móvil",
,
- ,
+ "Consente di riprodurre la modalità Salva il Mondo sul cellulare",
"モバイル版で「世界を救え」をプレイできるようになります",
,
"Zezwól na granie w tryb STW na urządzeniu mobilnym",
@@ -1111,7 +1111,7 @@ const Texts = {
"Fortnite: force console version",
"Fortnite: forzar versión de consola",
,
- "Fortnite: Foza la versione console",
+ "Fortnite: forza la versione console",
"Fortnite: 強制的にコンソール版を起動する",
,
"Fortnite: wymuś wersję konsolową",
@@ -1145,7 +1145,7 @@ const Texts = {
"Help",
"Ayuda",
,
- ,
+ "Guida",
"ヘルプ",
,
"Pomoc",
@@ -1179,7 +1179,7 @@ const Texts = {
"Hide web page's scrollbar",
"Oculta la barra de desplazamiento de la página",
,
- ,
+ "Nascondi la barra di scorrimento della pagina web",
"Webページのスクロールバーを隠す",
,
"Ukryj pasek przewijania strony",
@@ -1207,13 +1207,30 @@ const Texts = {
"Ẩn biểu tượng của menu Hệ thống",
"隐藏系统菜单图标",
],
+ "hide-touch-controller": [
+ "Touch-Controller ausblenden",
+ ,
+ "Hide touch controller",
+ "Ocultar controles táctiles",
+ ,
+ ,
+ "タッチコントローラーを隠す",
+ ,
+ ,
+ ,
+ "Скрыть сенсорный контроллер",
+ ,
+ "Приховати сенсорний контролер",
+ "Ẩn bộ điều khiển cảm ứng",
+ ,
+ ],
"horizontal-sensitivity": [
"Horizontale Empfindlichkeit",
"Sensitifitas horizontal",
"Horizontal sensitivity",
"Sensibilidad horizontal",
,
- ,
+ "Sensibilità orizzontale",
"左右方向の感度",
,
"Czułość pozioma",
@@ -1230,7 +1247,7 @@ const Texts = {
"Import",
"Importar",
,
- ,
+ "Importa",
"インポート(読込み)",
"가져오기",
"Importuj",
@@ -1247,7 +1264,7 @@ const Texts = {
"Install Better xCloud app for Android",
"Instale la aplicación Better xCloud para Android",
,
- ,
+ "Installa l'applicazione Better xCloud per Android",
"Android用のBetter xCloudをインストール",
,
"Zainstaluj aplikację Better xCloud na Androida",
@@ -1264,7 +1281,7 @@ const Texts = {
"Keyboard shortcuts",
"Atajos del teclado",
,
- ,
+ "Scorciatoie da tastiera",
"キーボードショートカット",
,
"Skróty klawiszowe",
@@ -1332,7 +1349,7 @@ const Texts = {
"Left stick",
"Joystick izquierdo",
,
- ,
+ "Levetta sinistra",
"左スティック",
"왼쪽 스틱",
"Lewy drążek analogowy",
@@ -1366,7 +1383,7 @@ const Texts = {
"Local co-op",
"Co-op local",
,
- ,
+ "Cooperativa locale",
"ローカルマルチプレイ",
,
"Lokalna kooperacja",
@@ -1383,7 +1400,7 @@ const Texts = {
"Map mouse to",
"Mapear ratón a",
,
- ,
+ "Usa il mouse come",
"マウスの割り当て",
,
"Przypisz myszkę do",
@@ -1553,7 +1570,7 @@ const Texts = {
"Name",
"Nombre",
,
- ,
+ "Nome",
"名前",
"이름",
"Nazwa",
@@ -1570,7 +1587,7 @@ const Texts = {
"New",
"Nuevo",
,
- ,
+ "Nuovo",
"新しい",
"새로 만들기",
"Nowy",
@@ -1808,7 +1825,7 @@ const Texts = {
"Preset",
"Preajuste",
,
- ,
+ "Profilo",
"プリセット",
"프리셋",
"Szablon",
@@ -1825,7 +1842,7 @@ const Texts = {
"Press Esc to cancel",
"Presione Esc para cancelar",
,
- ,
+ "Premi Esc per annullare",
"Escを押してキャンセル",
"ESC를 눌러 취소",
"Naciśnij Esc, aby anulować",
@@ -1842,7 +1859,7 @@ const Texts = {
(e: any) => `Press ${e.key} to toggle the Mouse and Keyboard feature`,
(e: any) => `Pulsa ${e.key} para activar la función de ratón y teclado`,
,
- ,
+ (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`,
@@ -1859,7 +1876,7 @@ const Texts = {
"Press a key or do a mouse click to bind...",
"Presione una tecla o haga un clic del ratón para enlazar...",
,
- ,
+ "Premi un tasto o fai un clic del mouse per associare...",
"キーを押すかマウスをクリックして割り当て...",
"정지하려면 아무키나 마우스를 클릭해주세요...",
"Naciśnij klawisz lub kliknij myszą, aby przypisać...",
@@ -1876,7 +1893,7 @@ const Texts = {
"Preset's name:",
"Nombre del preajuste:",
,
- ,
+ "Nome del profilo:",
"プリセット名:",
"프리셋 이름:",
"Nazwa szablonu:",
@@ -1961,7 +1978,7 @@ const Texts = {
"Rename",
"Renombrar",
,
- ,
+ "Rinomina",
"名前変更",
"이름 바꾸기",
"Zmień nazwę",
@@ -1978,7 +1995,7 @@ const Texts = {
"Right-click on a key to unbind it",
"Clic derecho en una tecla para desvincularla",
,
- ,
+ "Clic col tasto destro su una assegnazione per dissociarla",
"右クリックで割り当て解除",
"할당 해제하려면 키를 오른쪽 클릭하세요",
"Kliknij prawym przyciskiem myszy na klawisz, aby anulować przypisanie",
@@ -1995,7 +2012,7 @@ const Texts = {
"Right stick",
"Joystick derecho",
,
- ,
+ "Levetta destra",
"右スティック",
"오른쪽 스틱",
"Prawy drążek analogowy",
@@ -2114,7 +2131,7 @@ const Texts = {
"Save",
"Guardar",
,
- ,
+ "Conferma",
"保存",
"저장",
"Zapisz",
@@ -2131,7 +2148,7 @@ const Texts = {
"Applies video filters to screenshots",
"Aplica filtros de vídeo a las capturas de pantalla",
,
- ,
+ "Applica filtri video agli screenshot",
"スクリーンショットにビデオフィルターを適用",
,
"Stosuje filtry wideo do zrzutów ekranu",
@@ -2165,7 +2182,7 @@ const Texts = {
"Separate Touch controller & Controller #1",
"Separar controlador táctil y controlador #1",
,
- ,
+ "Controller su schermo e Controller #1 separati",
"タッチコントローラーとコントローラー#1を分ける",
,
"Oddziel Kontroler dotykowy i Kontroler #1",
@@ -2182,7 +2199,7 @@ const Texts = {
"Touch controller is Player 1, Controller #1 is Player 2",
"El controlador táctil es Jugador 1, Controlador #1 es Jugador 2",
,
- ,
+ "Il Giocatore 1 userà il Controller su schermo, il Giocatore 2 userà il Controller #1",
"タッチコントローラーがプレイヤー1、コントローラー#1がプレイヤー2に割り当てられます",
,
"Kontroler dotykowy to Gracz 1, Kontroler #1 to Gracz 2",
@@ -2250,7 +2267,7 @@ const Texts = {
"Shortcut keys",
"Teclas de atajo",
,
- ,
+ "Tasti di scelta rapida",
"ショートカットキー",
,
"Skróty klawiszowe",
@@ -2295,6 +2312,23 @@ const Texts = {
"Hiển thị thông số khi vào game",
"开始游戏时显示统计信息",
],
+ "show-touch-controller": [
+ "Touch-Controller anzeigen",
+ ,
+ "Show touch controller",
+ "Mostrar controles táctiles",
+ ,
+ ,
+ "タッチコントローラーを表示",
+ ,
+ ,
+ ,
+ "Показать сенсорный контроллер",
+ ,
+ "Показати сенсорний контролер",
+ "Hiện bộ điều khiển cảm ứng",
+ ,
+ ],
"show-wait-time": [
"Geschätzte Wartezeit anzeigen",
"Tampilkan waktu antrian",
@@ -2556,7 +2590,7 @@ const Texts = {
"Stick decay minimum",
"Disminuir mínimamente el analógico",
,
- ,
+ "Tempo minimo di rilascio dello stick",
"スティックの減衰の最小値",
,
"Minimalne opóźnienie drążka",
@@ -2573,7 +2607,7 @@ const Texts = {
"Stick decay strength",
"Intensidad de decaimiento del analógico",
,
- ,
+ "Velocità di rilascio dello stick",
"スティックの減衰の強さ",
,
"Siła opóźnienia drążka",
@@ -2624,7 +2658,7 @@ const Texts = {
"Support Better xCloud",
"Apoyar a Better xCloud",
,
- ,
+ "Sostieni Better xCloud",
"Better xCloudをサポート",
,
"Wesprzyj Better xCloud",
@@ -2652,6 +2686,23 @@ const Texts = {
"Hoán đổi nút",
"交换按钮",
],
+ "take-screenshot": [
+ "Screenshot aufnehmen",
+ ,
+ "Take screenshot",
+ "Capturar pantalla",
+ ,
+ ,
+ "スクリーンショットを撮影",
+ ,
+ ,
+ ,
+ "Сделать снимок экрана",
+ ,
+ "Зробити знімок екрану",
+ "Lưu ảnh màn hình",
+ ,
+ ],
"target-resolution": [
"Festgelegte Auflösung",
"Resolusi",
@@ -2709,7 +2760,7 @@ const Texts = {
"Off when controller found",
"Desactivar cuando se encuentra el controlador",
,
- ,
+ "Disabilitata quando un controllor viene rilevato",
"コントローラー接続時に無効化",
,
"Wyłącz, gdy kontroler zostanie znaleziony",
@@ -2756,11 +2807,11 @@ const Texts = {
],
"tc-default-opacity": [
"Standard Deckkraft",
- ,
+ "Opasitas bawaan",
"Default opacity",
"Opacidad por defecto",
,
- ,
+ "Opacità predefinita",
"既定の透過度",
,
"Domyślna przezroczystość",
@@ -2894,14 +2945,14 @@ const Texts = {
(e: any) => `Touch-Steuerungslayout von ${e.name}`,
,
(e: any) => `Touch control layout by ${e.name}`,
+ (e: any) => `Disposición del control táctil por ${e.nombre}`,
,
- ,
- ,
+ (e: any) => `Configurazione dei comandi su schermo creata da ${e.name}`,
(e: any) => `タッチ操作レイアウト作成者: ${e.name}`,
,
(e: any) => `Układ sterowania dotykowego stworzony przez ${e.name}`,
,
- ,
+ (e: any) => `Сенсорная раскладка по ${e.name}`,
(e: any) => `${e.name} kişisinin dokunmatik kontrolcü tuş şeması`,
(e: any) => `Розташування сенсорного керування від ${e.name}`,
(e: any) => `Bố cục điều khiển cảm ứng tạo bởi ${e.name}`,
@@ -3015,7 +3066,7 @@ const Texts = {
"Use mouse's absolute position",
"Usar la posición absoluta del ratón",
,
- ,
+ "Usa la posizione assoluta del mouse",
"マウスの絶対座標を使用",
"마우스 절대위치 사용",
"Użyj pozycji bezwzględnej myszy",
@@ -3066,7 +3117,7 @@ const Texts = {
"Vibration intensity",
"Intensidad de la vibración",
,
- ,
+ "Intensità della vibrazione",
"振動の強さ",
"진동 세기",
"Siła wibracji",
@@ -3083,7 +3134,7 @@ const Texts = {
"Vibration",
"Vibración",
,
- ,
+ "Vibrazione",
"振動",
,
"Wibracje",