mirror of
https://github.com/redphx/better-xcloud.git
synced 2025-07-05 22:01:43 +02:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
0bf4c289db | |||
c8865bd8a0 | |||
a2f062d9d5 | |||
b6d4c51ca9 | |||
785df72972 | |||
48da8bc527 | |||
f9cf02b2da | |||
77e0f2d8ba | |||
d05a68c470 | |||
153873e034 | |||
8d7fbf2804 | |||
488b0dfef2 | |||
b3697df8dc | |||
de21549e0d | |||
097164b92e | |||
3fe6d97133 | |||
328fdf46ea | |||
e4dbdea9a5 | |||
f13ce94cf2 | |||
a6c19fec15 | |||
6448a00271 | |||
68b29ecb50 | |||
90f89a0244 | |||
9862f794cf | |||
e109cdec6a | |||
40d1878fb2 | |||
95f842d9f6 | |||
d691ea0cf6 | |||
3c05fdcb6d | |||
0cff0b3d3f | |||
6ea47aed48 | |||
c8142e5079 | |||
ef85175a91 | |||
116640eb32 | |||
54e28ce350 | |||
0cd2c02ed6 | |||
e585264e8c | |||
6a133186b8 | |||
91b5434952 |
14
.github/ISSUE_TEMPLATE/02-feature-request.yml
vendored
14
.github/ISSUE_TEMPLATE/02-feature-request.yml
vendored
@ -13,7 +13,7 @@ body:
|
|||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: device_type
|
id: device_type
|
||||||
attributes:
|
attributes:
|
||||||
label: Device
|
label: Device type
|
||||||
description: "Which device type is this feature for?"
|
description: "Which device type is this feature for?"
|
||||||
options:
|
options:
|
||||||
- All devices
|
- All devices
|
||||||
@ -23,10 +23,20 @@ body:
|
|||||||
multiple: false
|
multiple: false
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: device_name
|
||||||
|
attributes:
|
||||||
|
label: "Device"
|
||||||
|
description: "Name of the device"
|
||||||
|
placeholder: "e.g., Google Pixel 8"
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: suggestion
|
id: suggestion
|
||||||
attributes:
|
attributes:
|
||||||
label: "Suggestion"
|
label: "Suggestion"
|
||||||
description: "What do you want to suggest?"
|
description: "What do you want to suggest? Include (mockup) screenshot if possible."
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,9 +1,4 @@
|
|||||||
src/modules/patcher/patches/controller-customization.js
|
src/modules/patcher/patches/*.js
|
||||||
src/modules/patcher/patches/expose-stream-session.js
|
|
||||||
src/modules/patcher/patches/local-co-op-enable.js
|
|
||||||
src/modules/patcher/patches/poll-gamepad.js
|
|
||||||
src/modules/patcher/patches/remote-play-keep-alive.js
|
|
||||||
src/modules/patcher/patches/vibration-adjust.js
|
|
||||||
|
|
||||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@ -3,6 +3,7 @@
|
|||||||
"dist/**/*": true,
|
"dist/**/*": true,
|
||||||
"src/modules/patcher/patches/controller-customization.js": true,
|
"src/modules/patcher/patches/controller-customization.js": true,
|
||||||
"src/modules/patcher/patches/expose-stream-session.js": true,
|
"src/modules/patcher/patches/expose-stream-session.js": true,
|
||||||
|
"src/modules/patcher/patches/game-card-icons.js": true,
|
||||||
"src/modules/patcher/patches/local-co-op-enable.js": true,
|
"src/modules/patcher/patches/local-co-op-enable.js": true,
|
||||||
"src/modules/patcher/patches/poll-gamepad.js": true,
|
"src/modules/patcher/patches/poll-gamepad.js": true,
|
||||||
"src/modules/patcher/patches/remote-play-keep-alive.js": true,
|
"src/modules/patcher/patches/remote-play-keep-alive.js": true,
|
||||||
|
21
README.md
21
README.md
@ -1,8 +1,15 @@
|
|||||||
# Better xCloud
|
<div align="center">
|
||||||
Improve Xbox Cloud Gaming (xCloud) experience on [xbox.com/play](https://www.xbox.com/play). It also allows you to use Remote Play on the xCloud website.
|
<img src="https://raw.githubusercontent.com/redphx/better-xcloud/refs/heads/typescript/resources/logos/better-xcloud.png" width="256"/>
|
||||||
|
<h1>Better xCloud</h1>
|
||||||
|
<!-- Latest Version Badge -->
|
||||||
|
<a href="https://github.com/redphx/better-xcloud/releases"><img src="https://img.shields.io/github/v/release/redphx/better-xcloud?label=latest" alt="Latest version" /></a>
|
||||||
|
<!-- Total Downloads Badge -->
|
||||||
|
<a href="https://github.com/redphx/better-xcloud/releases"><img src="https://img.shields.io/github/downloads/redphx/better-xcloud/total?color=%23e15f2c" alt="Total downloads" /></a>
|
||||||
|
<!-- Total Stars Badge -->
|
||||||
|
<a href="https://github.com/redphx/better-xcloud/stargazers"><img src="https://img.shields.io/github/stars/redphx/better-xcloud?color=%23cca400" alt="Total stars" /></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
> [!TIP]
|
### Improve Xbox Cloud Gaming (xCloud) experience on [xbox.com/play](https://www.xbox.com/play). It also allows you to use Remote Play on the xCloud website.
|
||||||
> The Android app is in development at [redphx/better-xcloud-android](https://github.com/redphx/better-xcloud-android)
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> I only accept pull requests for:
|
> I only accept pull requests for:
|
||||||
@ -13,16 +20,12 @@ Improve Xbox Cloud Gaming (xCloud) experience on [xbox.com/play](https://www.xbo
|
|||||||
- Windows
|
- Windows
|
||||||
- macOS
|
- macOS
|
||||||
- Linux, SteamOS (including Steam Deck)
|
- Linux, SteamOS (including Steam Deck)
|
||||||
- Android, Android TV (including Meta Quest VR Headsets)
|
- Android, Android TV (including Meta Quest VR Headsets): [redphx/better-xcloud-android](https://github.com/redphx/better-xcloud-android)
|
||||||
- iOS, iPadOS
|
- iOS, iPadOS
|
||||||
|
|
||||||
This script makes me spend more time with xCloud, and I hope the same thing happens to you.
|
This script makes me spend more time with xCloud, and I hope the same thing happens to you.
|
||||||
If you like this project please give it a 🌟. Thank you 🙏.
|
If you like this project please give it a 🌟. Thank you 🙏.
|
||||||
|
|
||||||
[](https://github.com/redphx/better-xcloud/releases)
|
|
||||||
[](https://github.com/redphx/better-xcloud/releases)
|
|
||||||
[](https://github.com/redphx/better-xcloud/stargazers)
|
|
||||||
|
|
||||||
## How to install
|
## How to install
|
||||||
Visit the [home page](https://better-xcloud.github.io) to know how to install Better xCloud on your device.
|
Visit the [home page](https://better-xcloud.github.io) to know how to install Better xCloud on your device.
|
||||||
|
|
||||||
|
623
dist/better-xcloud.lite.user.js
vendored
623
dist/better-xcloud.lite.user.js
vendored
File diff suppressed because one or more lines are too long
2
dist/better-xcloud.meta.js
vendored
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Better xCloud
|
// @name Better xCloud
|
||||||
// @namespace https://github.com/redphx
|
// @namespace https://github.com/redphx
|
||||||
// @version 6.1.0
|
// @version 6.2.1
|
||||||
// ==/UserScript==
|
// ==/UserScript==
|
||||||
|
458
dist/better-xcloud.user.js
vendored
458
dist/better-xcloud.user.js
vendored
File diff suppressed because one or more lines are too long
BIN
resources/logos/better-xcloud-transparent.png
Normal file
BIN
resources/logos/better-xcloud-transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
BIN
resources/logos/better-xcloud.png
Normal file
BIN
resources/logos/better-xcloud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
@ -106,7 +106,6 @@
|
|||||||
&.bx-frosted {
|
&.bx-frosted {
|
||||||
--button-alpha: 0.2;
|
--button-alpha: 0.2;
|
||||||
background-color: unquote('rgba(var(--button-rgb), var(--button-alpha))');
|
background-color: unquote('rgba(var(--button-rgb), var(--button-alpha))');
|
||||||
backdrop-filter: blur(4px) brightness(1.5);
|
|
||||||
|
|
||||||
&:not([disabled]):not(:active) {
|
&:not([disabled]):not(:active) {
|
||||||
&:hover, &.bx-focusable:focus {
|
&:hover, &.bx-focusable:focus {
|
||||||
@ -145,15 +144,16 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
|
// Text with icon
|
||||||
&:not(:only-child) {
|
&:not(:only-child) {
|
||||||
margin-left: 10px;
|
margin-inline-start: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bx-button-multi-lines {
|
&.bx-button-multi-lines {
|
||||||
height: auto;
|
height: auto;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 10px 0;
|
padding: 10px;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
line-height: unset;
|
line-height: unset;
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
.bx-product-details-icons {
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.bx-product-details-buttons {
|
.bx-product-details-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
font-size: 1.2rem;
|
font-size: 1.3rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,23 +1,3 @@
|
|||||||
.bx-remote-play-container {
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
|
|
||||||
color: white;
|
|
||||||
background: #1a1b1e;
|
|
||||||
border-radius: 10px;
|
|
||||||
width: 420px;
|
|
||||||
max-width: calc(100vw - 20px);
|
|
||||||
margin: 0 0 0 auto;
|
|
||||||
padding: 16px;
|
|
||||||
|
|
||||||
> .bx-button {
|
|
||||||
display: table;
|
|
||||||
margin: 0 0 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bx-remote-play-settings {
|
.bx-remote-play-settings {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
@ -29,6 +9,7 @@
|
|||||||
|
|
||||||
label {
|
label {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
@ -63,23 +44,24 @@
|
|||||||
|
|
||||||
.bx-remote-play-device-info {
|
.bx-remote-play-device-info {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
align-self: center;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bx-remote-play-device-name {
|
.bx-remote-play-device-name {
|
||||||
font-size: 20px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bx-remote-play-console-type {
|
.bx-remote-play-console-type {
|
||||||
font-size: 12px;
|
font-size: 8px;
|
||||||
background: #004c87;
|
background: #004c87;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-radius: 14px;
|
border-radius: 8px;
|
||||||
padding: 2px 10px;
|
padding: 2px 6px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,10 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
|||||||
font-family: var(--bx-normal-font) !important;
|
font-family: var(--bx-normal-font) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bx-frosted {
|
||||||
|
backdrop-filter: blur(4px) brightness(1.5);
|
||||||
|
}
|
||||||
|
|
||||||
select[multiple], select[multiple]:focus {
|
select[multiple], select[multiple]:focus {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: none;
|
border: none;
|
||||||
@ -190,14 +194,6 @@ div[class*=NotFocusedDialog] {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide Controller icon in Game tiles */
|
|
||||||
div[class*=SupportedInputsBadge] {
|
|
||||||
&:not(:has(:nth-child(2))), svg:first-of-type {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.bx-game-tile-wait-time {
|
.bx-game-tile-wait-time {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -190,6 +190,12 @@
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-inline-end: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
+ * {
|
+ * {
|
||||||
margin: 0 0 0 auto;
|
margin: 0 0 0 auto;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='#fff' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='2' stroke-width='2' viewBox='0 0 32 32'>
|
<svg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='none' fill-rule='evenodd' viewBox='0 0 32 32'>
|
||||||
<path d='M16.001 7.236h-2.328c-.443 0-1.941-.851-2.357-.905-.824-.106-1.684 0-2.489.176a13.04 13.04 0 0 0-3.137 1.14c-.392.275-.677.668-.866 1.104v.03l-3.302 8.963-.015.015c-.288.867-.553 3.75-.5 4.279a4.89 4.89 0 0 0 1.022 2.55c.654.823 3.71 1.364 4.057 1.016l4.462-4.475c.185-.186 1.547-.706 2.01-.706h6.884c.463 0 1.825.52 2.01.706l4.462 4.475c.347.348 3.403-.193 4.057-1.016a4.89 4.89 0 0 0 1.022-2.55c.053-.529-.212-3.412-.5-4.279l-.015-.015-3.302-8.963v-.03c-.189-.436-.474-.829-.866-1.104a13.04 13.04 0 0 0-3.137-1.14c-.805-.176-1.665-.282-2.489-.176-.416.054-1.914.905-2.357.905h-2.328' fill='none' stroke='#fff'/>
|
<clipPath id='svg-bx-logo'>
|
||||||
<path d='M8.172 12.914H6.519c-.235 0-.315.267-.335.452l-.052.578c0 .193.033.384.054.576.023.202.091.511.355.511h1.631l-.001 1.652c0 .234.266.315.452.335l.578.052c.193 0 .384-.033.576-.054.203-.023.511-.091.511-.355V15.03l1.652.001c.234 0 .315-.266.335-.452l.052-.578c-.001-.193-.033-.385-.055-.577-.022-.202-.09-.51-.354-.51h-1.632v-1.652c0-.234-.266-.315-.453-.335l-.577-.052c-.193 0-.385.033-.577.054-.202.023-.51.091-.51.355v1.631m16.546 2.994h-3.487c-.206 0-.413-.043-.604-.121-.177-.072-.339-.183-.476-.316-.149-.144-.259-.315-.341-.504-.156-.361-.172-.788-.032-1.157a1.57 1.57 0 0 1 .459-.641c.106-.089.223-.164.349-.222a1.52 1.52 0 0 1 .423-.123c.167-.024.338-.02.504.012a1.83 1.83 0 0 1 .455-.482 1.62 1.62 0 0 1 .522-.252c.307-.089.651-.09.959-.003a1.75 1.75 0 0 1 1.009.764 1.83 1.83 0 0 1 .251.721c.156 0 .312.031.456.09a1.24 1.24 0 0 1 .372.248c.091.087.165.19.221.302a1.19 1.19 0 0 1-.173 1.299c-.119.132-.276.239-.441.305a1.17 1.17 0 0 1-.426.08z' fill='#fff' stroke='none'/>
|
<path d='M0 0h32v32H0z'/>
|
||||||
|
</clipPath>
|
||||||
|
<g clip-path='url(#svg-bx-logo)'>
|
||||||
|
<path d='M19.959 18.286l3.959 2.285-3.959 2.286V32L16 29.714v-9.143l3.959-2.285zM16 16V6.857l3.959-2.286 3.959 2.286-3.959 2.286v9.143L16 16zm-3.959-2.286L16 16l-3.959 2.286v9.143l-3.959-2.286V16l3.959-2.286zM8.082 2.286L12.041 0 16 2.286l-3.959 2.285v9.143l-3.959-2.285V2.286zm8.846 19.535c-.171-.098-.309-.018-.309.179s.138.437.309.536.309.018.309-.179-.138-.437-.309-.536zm0-13.714c-.171-.098-.309-.018-.309.179s.138.437.309.535.309.019.309-.178-.138-.437-.309-.536zM9.01 17.25c-.171-.099-.309-.019-.309.179s.138.437.309.535.309.019.309-.178-.138-.437-.309-.536zm0-13.714c-.171-.099-.309-.019-.309.178s.138.437.309.536.309.019.309-.179-.138-.437-.309-.535z' fill='#fff'/>
|
||||||
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 926 B |
7
src/assets/svg/local-co-op.svg
Normal file
7
src/assets/svg/local-co-op.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 32 32' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round'>
|
||||||
|
<g>
|
||||||
|
<path d='M24.272 11.165h-3.294l-3.14 3.564c-.391.391-.922.611-1.476.611a2.1 2.1 0 0 1-2.087-2.088 2.09 2.09 0 0 1 .031-.362l1.22-6.274a3.89 3.89 0 0 1 3.81-3.206h6.57c1.834 0 3.439 1.573 3.833 3.295l1.205 6.185a2.09 2.09 0 0 1 .031.362 2.1 2.1 0 0 1-2.087 2.088c-.554 0-1.085-.22-1.476-.611l-3.14-3.564' fill='none' stroke='#fff' stroke-width='2'/>
|
||||||
|
<circle cx='22.625' cy='5.874' r='.879'/><path d='M11.022 24.415H7.728l-3.14 3.564c-.391.391-.922.611-1.476.611a2.1 2.1 0 0 1-2.087-2.088 2.09 2.09 0 0 1 .031-.362l1.22-6.274a3.89 3.89 0 0 1 3.81-3.206h6.57c1.834 0 3.439 1.573 3.833 3.295l1.205 6.185a2.09 2.09 0 0 1 .031.362 2.1 2.1 0 0 1-2.087 2.088c-.554 0-1.085-.22-1.476-.611l-3.14-3.564' fill='none' stroke='#fff' stroke-width='2'/>
|
||||||
|
<circle cx='9.375' cy='19.124' r='.879'/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 981 B |
@ -12,6 +12,7 @@ export const enum StorageKey {
|
|||||||
GH_PAGES_COMMIT_HASH = 'BetterXcloud.GhPages.CommitHash',
|
GH_PAGES_COMMIT_HASH = 'BetterXcloud.GhPages.CommitHash',
|
||||||
LIST_CUSTOM_TOUCH_LAYOUTS = 'BetterXcloud.GhPages.CustomTouchLayouts',
|
LIST_CUSTOM_TOUCH_LAYOUTS = 'BetterXcloud.GhPages.CustomTouchLayouts',
|
||||||
LIST_FORCE_NATIVE_MKB = 'BetterXcloud.GhPages.ForceNativeMkb',
|
LIST_FORCE_NATIVE_MKB = 'BetterXcloud.GhPages.ForceNativeMkb',
|
||||||
|
LIST_LOCAL_CO_OP = 'BetterXcloud.GhPages.LocalCoOp',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -85,6 +86,7 @@ export const enum PrefKey {
|
|||||||
UI_SKIP_SPLASH_VIDEO = 'ui.splashVideo.skip',
|
UI_SKIP_SPLASH_VIDEO = 'ui.splashVideo.skip',
|
||||||
UI_HIDE_SYSTEM_MENU_ICON = 'ui.systemMenu.hideHandle',
|
UI_HIDE_SYSTEM_MENU_ICON = 'ui.systemMenu.hideHandle',
|
||||||
UI_REDUCE_ANIMATIONS = 'ui.reduceAnimations',
|
UI_REDUCE_ANIMATIONS = 'ui.reduceAnimations',
|
||||||
|
UI_IMAGE_QUALITY = 'ui.imageQuality',
|
||||||
|
|
||||||
VIDEO_PLAYER_TYPE = 'video.player.type',
|
VIDEO_PLAYER_TYPE = 'video.player.type',
|
||||||
VIDEO_POWER_PREFERENCE = 'video.player.powerPreference',
|
VIDEO_POWER_PREFERENCE = 'video.player.powerPreference',
|
||||||
@ -171,6 +173,7 @@ export type PrefTypeMap = {
|
|||||||
[PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: boolean,
|
[PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME]: boolean,
|
||||||
[PrefKey.UI_HIDE_SECTIONS]: UiSection[],
|
[PrefKey.UI_HIDE_SECTIONS]: UiSection[],
|
||||||
[PrefKey.UI_HIDE_SYSTEM_MENU_ICON]: boolean,
|
[PrefKey.UI_HIDE_SYSTEM_MENU_ICON]: boolean,
|
||||||
|
[PrefKey.UI_IMAGE_QUALITY]: number,
|
||||||
[PrefKey.UI_LAYOUT]: UiLayout,
|
[PrefKey.UI_LAYOUT]: UiLayout,
|
||||||
[PrefKey.UI_REDUCE_ANIMATIONS]: boolean,
|
[PrefKey.UI_REDUCE_ANIMATIONS]: boolean,
|
||||||
[PrefKey.UI_SCROLLBAR_HIDE]: boolean,
|
[PrefKey.UI_SCROLLBAR_HIDE]: boolean,
|
||||||
|
18
src/index.ts
18
src/index.ts
@ -164,7 +164,9 @@ document.addEventListener('readystatechange', e => {
|
|||||||
|
|
||||||
if (STATES.isSignedIn) {
|
if (STATES.isSignedIn) {
|
||||||
// Preload Remote Play
|
// Preload Remote Play
|
||||||
RemotePlayManager.getInstance()?.initialize();
|
if (isFullVersion()) {
|
||||||
|
RemotePlayManager.getInstance()?.initialize();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show Settings button in the header when not signed in
|
// Show Settings button in the header when not signed in
|
||||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||||
@ -234,8 +236,10 @@ BxEventBus.Stream.on('state.starting', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
BxEventBus.Stream.on('state.playing', payload => {
|
BxEventBus.Stream.on('state.playing', payload => {
|
||||||
window.BX_STREAM_SETTINGS = StreamSettings.settings;
|
if (isFullVersion()) {
|
||||||
StreamSettings.refreshAllSettings();
|
window.BX_STREAM_SETTINGS = StreamSettings.settings;
|
||||||
|
StreamSettings.refreshAllSettings();
|
||||||
|
}
|
||||||
|
|
||||||
STATES.isPlaying = true;
|
STATES.isPlaying = true;
|
||||||
StreamUiHandler.observe();
|
StreamUiHandler.observe();
|
||||||
@ -357,9 +361,11 @@ isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
|||||||
function main() {
|
function main() {
|
||||||
GhPagesUtils.fetchLatestCommit();
|
GhPagesUtils.fetchLatestCommit();
|
||||||
|
|
||||||
if (getPref(PrefKey.NATIVE_MKB_MODE) !== NativeMkbMode.OFF) {
|
if (isFullVersion()) {
|
||||||
const customList = getPref(PrefKey.NATIVE_MKB_FORCED_GAMES);
|
if (getPref(PrefKey.NATIVE_MKB_MODE) !== NativeMkbMode.OFF) {
|
||||||
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
const customList = getPref(PrefKey.NATIVE_MKB_FORCED_GAMES);
|
||||||
|
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSettings.setup();
|
StreamSettings.setup();
|
||||||
|
@ -63,6 +63,11 @@ export class LoadingScreen {
|
|||||||
// Limit max width to reduce image size
|
// Limit max width to reduce image size
|
||||||
imageUrl = imageUrl + '?w=1920';
|
imageUrl = imageUrl + '?w=1920';
|
||||||
|
|
||||||
|
const imageQuality = getPref(PrefKey.UI_IMAGE_QUALITY);
|
||||||
|
if (imageQuality !== 90) {
|
||||||
|
imageUrl += '&q=' + imageQuality;
|
||||||
|
}
|
||||||
|
|
||||||
$bgStyle.textContent! += compressCss(`
|
$bgStyle.textContent! += compressCss(`
|
||||||
#game-stream {
|
#game-stream {
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
|
@ -19,6 +19,7 @@ import type { MkbConvertedPresetData } from "@/types/presets";
|
|||||||
import { StreamSettings } from "@/utils/stream-settings";
|
import { StreamSettings } from "@/utils/stream-settings";
|
||||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
import { generateVirtualControllerMapping, toXcloudGamepadKey } from "@/utils/gamepad";
|
||||||
|
|
||||||
const PointerToMouseButton = {
|
const PointerToMouseButton = {
|
||||||
1: 0,
|
1: 0,
|
||||||
@ -152,6 +153,8 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
};
|
};
|
||||||
private nativeGetGamepads: Navigator['getGamepads'];
|
private nativeGetGamepads: Navigator['getGamepads'];
|
||||||
|
|
||||||
|
private xCloudGamepad: XcloudGamepad = generateVirtualControllerMapping(0);
|
||||||
|
|
||||||
private initialized = false;
|
private initialized = false;
|
||||||
private enabled = false;
|
private enabled = false;
|
||||||
private mouseDataProvider: MouseDataProvider | undefined;
|
private mouseDataProvider: MouseDataProvider | undefined;
|
||||||
@ -171,16 +174,16 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
private popup: MkbPopup;
|
private popup: MkbPopup;
|
||||||
|
|
||||||
private STICK_MAP: { [key in GamepadKey]?: [GamepadKey[], number, number] } = {
|
private STICK_MAP: { [key in GamepadKey]?: [GamepadKey[], number] } = {
|
||||||
[GamepadKey.LS_LEFT]: [this.LEFT_STICK_X, 0, -1],
|
[GamepadKey.LS_LEFT]: [this.LEFT_STICK_X, -1],
|
||||||
[GamepadKey.LS_RIGHT]: [this.LEFT_STICK_X, 0, 1],
|
[GamepadKey.LS_RIGHT]: [this.LEFT_STICK_X, 1],
|
||||||
[GamepadKey.LS_UP]: [this.LEFT_STICK_Y, 1, -1],
|
[GamepadKey.LS_UP]: [this.LEFT_STICK_Y, 1],
|
||||||
[GamepadKey.LS_DOWN]: [this.LEFT_STICK_Y, 1, 1],
|
[GamepadKey.LS_DOWN]: [this.LEFT_STICK_Y, -1],
|
||||||
|
|
||||||
[GamepadKey.RS_LEFT]: [this.RIGHT_STICK_X, 2, -1],
|
[GamepadKey.RS_LEFT]: [this.RIGHT_STICK_X, -1],
|
||||||
[GamepadKey.RS_RIGHT]: [this.RIGHT_STICK_X, 2, 1],
|
[GamepadKey.RS_RIGHT]: [this.RIGHT_STICK_X, 1],
|
||||||
[GamepadKey.RS_UP]: [this.RIGHT_STICK_Y, 3, -1],
|
[GamepadKey.RS_UP]: [this.RIGHT_STICK_Y, 1],
|
||||||
[GamepadKey.RS_DOWN]: [this.RIGHT_STICK_Y, 3, 1],
|
[GamepadKey.RS_DOWN]: [this.RIGHT_STICK_Y, -1],
|
||||||
};
|
};
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
@ -205,11 +208,16 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
private getVirtualGamepad = () => this.VIRTUAL_GAMEPAD;
|
private getVirtualGamepad = () => this.VIRTUAL_GAMEPAD;
|
||||||
|
|
||||||
private updateStick(stick: GamepadStick, x: number, y: number) {
|
private updateStick(stick: GamepadStick, x: number, y: number) {
|
||||||
const virtualGamepad = this.getVirtualGamepad();
|
const gamepad = this.xCloudGamepad;
|
||||||
virtualGamepad.axes[stick * 2] = x;
|
if (stick === GamepadStick.LEFT) {
|
||||||
virtualGamepad.axes[stick * 2 + 1] = y;
|
gamepad.LeftThumbXAxis = x;
|
||||||
|
gamepad.LeftThumbYAxis = -y;
|
||||||
|
} else {
|
||||||
|
gamepad.RightThumbXAxis = x;
|
||||||
|
gamepad.RightThumbYAxis = -y;
|
||||||
|
}
|
||||||
|
|
||||||
virtualGamepad.timestamp = performance.now();
|
window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -224,29 +232,20 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
private vectorLength = (x: number, y: number): number => Math.sqrt(x ** 2 + y ** 2);
|
private vectorLength = (x: number, y: number): number => Math.sqrt(x ** 2 + y ** 2);
|
||||||
|
|
||||||
private resetGamepad() {
|
resetXcloudGamepads() {
|
||||||
const gamepad = this.getVirtualGamepad();
|
const index = getPref(PrefKey.MKB_P1_SLOT) - 1;
|
||||||
|
|
||||||
// Reset axes
|
this.xCloudGamepad = generateVirtualControllerMapping(0, {
|
||||||
gamepad.axes = [0, 0, 0, 0];
|
GamepadIndex: getPref(PrefKey.LOCAL_CO_OP_ENABLED) ? index : 0,
|
||||||
|
Dirty: true,
|
||||||
// Reset buttons
|
});
|
||||||
for (const button of gamepad.buttons) {
|
this.VIRTUAL_GAMEPAD.index = index;
|
||||||
button.pressed = false;
|
|
||||||
button.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
gamepad.timestamp = performance.now();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private pressButton(buttonIndex: GamepadKey, pressed: boolean) {
|
private pressButton(buttonIndex: GamepadKey, pressed: boolean) {
|
||||||
const virtualGamepad = this.getVirtualGamepad();
|
const xCloudKey = toXcloudGamepadKey(buttonIndex)!;
|
||||||
|
|
||||||
if (buttonIndex >= 100) {
|
if (buttonIndex >= 100) {
|
||||||
let [valueArr, axisIndex] = this.STICK_MAP[buttonIndex]!;
|
let [valueArr]: [GamepadKey[], number] = this.STICK_MAP[buttonIndex]!;
|
||||||
valueArr = valueArr as number[];
|
|
||||||
axisIndex = axisIndex as number;
|
|
||||||
|
|
||||||
// Remove old index of the array
|
// Remove old index of the array
|
||||||
for (let i = valueArr.length - 1; i >= 0; i--) {
|
for (let i = valueArr.length - 1; i >= 0; i--) {
|
||||||
if (valueArr[i] === buttonIndex) {
|
if (valueArr[i] === buttonIndex) {
|
||||||
@ -259,18 +258,19 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
let value;
|
let value;
|
||||||
if (valueArr.length) {
|
if (valueArr.length) {
|
||||||
// Get value of the last key of the axis
|
// Get value of the last key of the axis
|
||||||
value = this.STICK_MAP[valueArr[valueArr.length - 1]]![2] as number;
|
value = this.STICK_MAP[valueArr[valueArr.length - 1]]![1] as number;
|
||||||
} else {
|
} else {
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtualGamepad.axes[axisIndex] = value;
|
// @ts-ignore
|
||||||
|
this.xCloudGamepad[xCloudKey] = value;
|
||||||
} else {
|
} else {
|
||||||
virtualGamepad.buttons[buttonIndex].pressed = pressed;
|
// @ts-ignore
|
||||||
virtualGamepad.buttons[buttonIndex].value = pressed ? 1 : 0;
|
this.xCloudGamepad[xCloudKey] = pressed ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtualGamepad.timestamp = performance.now();
|
window.BX_EXPOSED.inputChannel?.sendGamepadInput(performance.now(), [this.xCloudGamepad]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onKeyboardEvent = (e: KeyboardEvent) => {
|
private onKeyboardEvent = (e: KeyboardEvent) => {
|
||||||
@ -428,7 +428,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle(force?: boolean) {
|
async toggle(force?: boolean) {
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -440,9 +440,12 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
document.body.requestPointerLock({
|
try {
|
||||||
unadjustedMovement: true,
|
await document.body.requestPointerLock({ unadjustedMovement: true });
|
||||||
});
|
} catch (e) {
|
||||||
|
document.body.requestPointerLock();
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
document.pointerLockElement && document.exitPointerLock();
|
document.pointerLockElement && document.exitPointerLock();
|
||||||
}
|
}
|
||||||
@ -450,7 +453,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
refreshPresetData() {
|
refreshPresetData() {
|
||||||
this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset;
|
this.PRESET = window.BX_STREAM_SETTINGS.mkbPreset;
|
||||||
this.resetGamepad();
|
this.resetXcloudGamepads();
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForMouseData(showPopup: boolean) {
|
waitForMouseData(showPopup: boolean) {
|
||||||
@ -557,7 +560,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
this.stop();
|
this.stop();
|
||||||
|
|
||||||
this.waitForMouseData(false);
|
this.waitForMouseData(false);
|
||||||
document.pointerLockElement && document.exitPointerLock();
|
document.exitPointerLock();
|
||||||
|
|
||||||
window.removeEventListener('keydown', this.onKeyboardEvent);
|
window.removeEventListener('keydown', this.onKeyboardEvent);
|
||||||
window.removeEventListener('keyup', this.onKeyboardEvent);
|
window.removeEventListener('keyup', this.onKeyboardEvent);
|
||||||
@ -578,11 +581,6 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
window.removeEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, this.onPollingModeChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGamepadSlots() {
|
|
||||||
// Set gamepad slot
|
|
||||||
this.VIRTUAL_GAMEPAD.index = getPref(PrefKey.MKB_P1_SLOT) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
if (!this.enabled) {
|
if (!this.enabled) {
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
@ -592,8 +590,8 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
this.isPolling = true;
|
this.isPolling = true;
|
||||||
this.escKeyDownTime = -1;
|
this.escKeyDownTime = -1;
|
||||||
|
|
||||||
this.resetGamepad();
|
window.BX_EXPOSED.toggleLocalCoOp(getPref(PrefKey.LOCAL_CO_OP_ENABLED));
|
||||||
this.updateGamepadSlots();
|
this.resetXcloudGamepads();
|
||||||
window.navigator.getGamepads = this.patchedGetGamepads;
|
window.navigator.getGamepads = this.patchedGetGamepads;
|
||||||
|
|
||||||
this.waitForMouseData(false);
|
this.waitForMouseData(false);
|
||||||
@ -622,7 +620,7 @@ export class EmulatedMkbHandler extends MkbHandler {
|
|||||||
const virtualGamepad = this.getVirtualGamepad();
|
const virtualGamepad = this.getVirtualGamepad();
|
||||||
if (virtualGamepad.connected) {
|
if (virtualGamepad.connected) {
|
||||||
// Dispatch "gamepaddisconnected" event
|
// Dispatch "gamepaddisconnected" event
|
||||||
this.resetGamepad();
|
this.resetXcloudGamepads();
|
||||||
|
|
||||||
virtualGamepad.connected = false;
|
virtualGamepad.connected = false;
|
||||||
virtualGamepad.timestamp = performance.now();
|
virtualGamepad.timestamp = performance.now();
|
||||||
|
@ -13,19 +13,7 @@ import { StreamSettings } from "@/utils/stream-settings";
|
|||||||
import { ShortcutAction } from "@/enums/shortcut-actions";
|
import { ShortcutAction } from "@/enums/shortcut-actions";
|
||||||
import { NativeMkbMode } from "@/enums/pref-values";
|
import { NativeMkbMode } from "@/enums/pref-values";
|
||||||
import { BxEventBus } from "@/utils/bx-event-bus";
|
import { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
|
import type { NativeMouseData, XcloudInputChannel } from "@/utils/gamepad";
|
||||||
type NativeMouseData = {
|
|
||||||
X: number,
|
|
||||||
Y: number,
|
|
||||||
Buttons: number,
|
|
||||||
WheelX: number,
|
|
||||||
WheelY: number,
|
|
||||||
Type?: 0, // 0: Relative, 1: Absolute
|
|
||||||
}
|
|
||||||
|
|
||||||
type XcloudInputSink = {
|
|
||||||
onMouseInput: (data: NativeMouseData) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class NativeMkbHandler extends MkbHandler {
|
export class NativeMkbHandler extends MkbHandler {
|
||||||
private static instance: NativeMkbHandler | null | undefined;
|
private static instance: NativeMkbHandler | null | undefined;
|
||||||
@ -50,13 +38,11 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
private enabled = false;
|
private enabled = false;
|
||||||
|
|
||||||
private mouseButtonsPressed = 0;
|
private mouseButtonsPressed = 0;
|
||||||
private mouseWheelX = 0;
|
|
||||||
private mouseWheelY = 0;
|
|
||||||
|
|
||||||
private mouseVerticalMultiply = 0;
|
private mouseVerticalMultiply = 0;
|
||||||
private mouseHorizontalMultiply = 0;
|
private mouseHorizontalMultiply = 0;
|
||||||
|
|
||||||
private inputSink: XcloudInputSink | undefined;
|
private inputChannel: XcloudInputChannel | undefined;
|
||||||
|
|
||||||
private popup!: MkbPopup;
|
private popup!: MkbPopup;
|
||||||
|
|
||||||
@ -116,7 +102,7 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.pointerClient = PointerClient.getInstance();
|
this.pointerClient = PointerClient.getInstance();
|
||||||
this.inputSink = window.BX_EXPOSED.inputSink;
|
this.inputChannel = window.BX_EXPOSED.inputChannel;
|
||||||
|
|
||||||
// Stop keyboard input at startup
|
// Stop keyboard input at startup
|
||||||
this.updateInputConfigurationAsync(false);
|
this.updateInputConfigurationAsync(false);
|
||||||
@ -202,7 +188,7 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
BxEventBus.Script.off('dialog.shown', this.onDialogShown);
|
BxEventBus.Script.off('dialog.shown', this.onDialogShown);
|
||||||
|
|
||||||
this.waitForMouseData(false);
|
this.waitForMouseData(false);
|
||||||
document.pointerLockElement && document.exitPointerLock();
|
document.exitPointerLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseMove(data: MkbMouseMove): void {
|
handleMouseMove(data: MkbMouseMove): void {
|
||||||
@ -210,8 +196,8 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
X: data.movementX,
|
X: data.movementX,
|
||||||
Y: data.movementY,
|
Y: data.movementY,
|
||||||
Buttons: this.mouseButtonsPressed,
|
Buttons: this.mouseButtonsPressed,
|
||||||
WheelX: this.mouseWheelX,
|
WheelX: 0,
|
||||||
WheelY: this.mouseWheelY,
|
WheelY: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,30 +215,30 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
X: 0,
|
X: 0,
|
||||||
Y: 0,
|
Y: 0,
|
||||||
Buttons: this.mouseButtonsPressed,
|
Buttons: this.mouseButtonsPressed,
|
||||||
WheelX: this.mouseWheelX,
|
WheelX: 0,
|
||||||
WheelY: this.mouseWheelY,
|
WheelY: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseWheel(data: MkbMouseWheel): boolean {
|
handleMouseWheel(data: MkbMouseWheel): boolean {
|
||||||
const { vertical, horizontal } = data;
|
const { vertical, horizontal } = data;
|
||||||
|
|
||||||
this.mouseWheelX = horizontal;
|
let mouseWheelX = horizontal;
|
||||||
if (this.mouseHorizontalMultiply && this.mouseHorizontalMultiply !== 1) {
|
if (this.mouseHorizontalMultiply && this.mouseHorizontalMultiply !== 1) {
|
||||||
this.mouseWheelX *= this.mouseHorizontalMultiply;
|
mouseWheelX *= this.mouseHorizontalMultiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.mouseWheelY = vertical;
|
let mouseWheelY = vertical;
|
||||||
if (this.mouseVerticalMultiply && this.mouseVerticalMultiply !== 1) {
|
if (this.mouseVerticalMultiply && this.mouseVerticalMultiply !== 1) {
|
||||||
this.mouseWheelY *= this.mouseVerticalMultiply;
|
mouseWheelY *= this.mouseVerticalMultiply;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendMouseInput({
|
this.sendMouseInput({
|
||||||
X: 0,
|
X: 0,
|
||||||
Y: 0,
|
Y: 0,
|
||||||
Buttons: this.mouseButtonsPressed,
|
Buttons: this.mouseButtonsPressed,
|
||||||
WheelX: this.mouseWheelX,
|
WheelX: mouseWheelX,
|
||||||
WheelY: this.mouseWheelY,
|
WheelY: mouseWheelY,
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -276,13 +262,11 @@ export class NativeMkbHandler extends MkbHandler {
|
|||||||
|
|
||||||
private sendMouseInput(data: NativeMouseData) {
|
private sendMouseInput(data: NativeMouseData) {
|
||||||
data.Type = 0; // Relative
|
data.Type = 0; // Relative
|
||||||
this.inputSink?.onMouseInput(data);
|
this.inputChannel?.queueMouseInput(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private resetMouseInput() {
|
private resetMouseInput() {
|
||||||
this.mouseButtonsPressed = 0;
|
this.mouseButtonsPressed = 0;
|
||||||
this.mouseWheelX = 0;
|
|
||||||
this.mouseWheelY = 0;
|
|
||||||
|
|
||||||
this.sendMouseInput({
|
this.sendMouseInput({
|
||||||
X: 0,
|
X: 0,
|
||||||
|
@ -8,6 +8,7 @@ enum PointerAction {
|
|||||||
BUTTON_RELEASE = 3,
|
BUTTON_RELEASE = 3,
|
||||||
SCROLL = 4,
|
SCROLL = 4,
|
||||||
POINTER_CAPTURE_CHANGED = 5,
|
POINTER_CAPTURE_CHANGED = 5,
|
||||||
|
PROTOCOL_VERSION = 127,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ export class PointerClient {
|
|||||||
private static instance: PointerClient;
|
private static instance: PointerClient;
|
||||||
public static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient());
|
public static getInstance = () => PointerClient.instance ?? (PointerClient.instance = new PointerClient());
|
||||||
private readonly LOG_TAG = 'PointerClient';
|
private readonly LOG_TAG = 'PointerClient';
|
||||||
|
private readonly REQUIRED_PROTOCOL_VERSION = 2;
|
||||||
|
|
||||||
private socket: WebSocket | undefined | null;
|
private socket: WebSocket | undefined | null;
|
||||||
private mkbHandler: MkbHandler | undefined;
|
private mkbHandler: MkbHandler | undefined;
|
||||||
@ -56,6 +58,15 @@ export class PointerClient {
|
|||||||
let messageType = dataView.getInt8(0);
|
let messageType = dataView.getInt8(0);
|
||||||
let offset = Int8Array.BYTES_PER_ELEMENT;
|
let offset = Int8Array.BYTES_PER_ELEMENT;
|
||||||
switch (messageType) {
|
switch (messageType) {
|
||||||
|
case PointerAction.PROTOCOL_VERSION:
|
||||||
|
const protocolVersion = this.onProtocolVersion(dataView, offset);
|
||||||
|
BxLogger.info(this.LOG_TAG, 'Protocol version', protocolVersion);
|
||||||
|
|
||||||
|
if (protocolVersion !== this.REQUIRED_PROTOCOL_VERSION) {
|
||||||
|
alert('Required MKB protocol: ' + protocolVersion);
|
||||||
|
this.stop();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case PointerAction.MOVE:
|
case PointerAction.MOVE:
|
||||||
this.onMove(dataView, offset);
|
this.onMove(dataView, offset);
|
||||||
break;
|
break;
|
||||||
@ -75,6 +86,10 @@ export class PointerClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private onProtocolVersion(dataView: DataView, offset: number) {
|
||||||
|
return dataView.getUint16(offset);
|
||||||
|
}
|
||||||
|
|
||||||
onMove(dataView: DataView, offset: number) {
|
onMove(dataView: DataView, offset: number) {
|
||||||
// [X, Y]
|
// [X, Y]
|
||||||
const x = dataView.getInt16(offset);
|
const x = dataView.getInt16(offset);
|
||||||
|
@ -50,4 +50,50 @@ export class PatcherUtils {
|
|||||||
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static isVarCharacter(char: string) {
|
||||||
|
const code = char.charCodeAt(0);
|
||||||
|
|
||||||
|
// Check for uppercase letters (A-Z)
|
||||||
|
const isUppercase = code >= 65 && code <= 90;
|
||||||
|
|
||||||
|
// Check for lowercase letters (a-z)
|
||||||
|
const isLowercase = code >= 97 && code <= 122;
|
||||||
|
|
||||||
|
// Check for digits (0-9)
|
||||||
|
const isDigit = code >= 48 && code <= 57;
|
||||||
|
|
||||||
|
// Check for special characters '_' and '$'
|
||||||
|
const isSpecial = char === '_' || char === '$';
|
||||||
|
|
||||||
|
return isUppercase || isLowercase || isDigit || isSpecial;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getVariableNameBefore(str: string, index: number) {
|
||||||
|
if (index < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const end = index;
|
||||||
|
let start = end - 1;
|
||||||
|
while (PatcherUtils.isVarCharacter(str[start])) {
|
||||||
|
start -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.substring(start + 1, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getVariableNameAfter(str: string, index: number) {
|
||||||
|
if (index < 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = index;
|
||||||
|
let end = start + 1;
|
||||||
|
while (PatcherUtils.isVarCharacter(str[end])) {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.substring(start, end);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import { BxEvent } from "@/utils/bx-event";
|
|||||||
import codeControllerCustomization from "./patches/controller-customization.js" with { type: "text" };
|
import codeControllerCustomization from "./patches/controller-customization.js" with { type: "text" };
|
||||||
import codePollGamepad from "./patches/poll-gamepad.js" with { type: "text" };
|
import codePollGamepad from "./patches/poll-gamepad.js" with { type: "text" };
|
||||||
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
|
import codeExposeStreamSession from "./patches/expose-stream-session.js" with { type: "text" };
|
||||||
|
import codeGameCardIcons from "./patches/game-card-icons.js" with { type: "text" };
|
||||||
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
import codeLocalCoOpEnable from "./patches/local-co-op-enable.js" with { type: "text" };
|
||||||
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
import codeRemotePlayKeepAlive from "./patches/remote-play-keep-alive.js" with { type: "text" };
|
||||||
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
import codeVibrationAdjust from "./patches/vibration-adjust.js" with { type: "text" };
|
||||||
@ -642,15 +643,14 @@ true` + text;
|
|||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
exposeInputSink(str: string) {
|
exposeInputChannel(str: string) {
|
||||||
let text = 'this.controlChannel=null,this.inputChannel=null';
|
let index = str.indexOf('this.flushData=');
|
||||||
if (!str.includes(text)) {
|
if (index < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newCode = 'window.BX_EXPOSED.inputSink = this;';
|
const newCode = 'window.BX_EXPOSED.inputChannel = this,';
|
||||||
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
str = str.replace(text, newCode + text);
|
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1003,15 +1003,133 @@ ${subsVar} = subs;
|
|||||||
str = PatcherUtils.insertAt(str, index, newCode);
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
return str;
|
return str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exposeReactCreateComponent(str: string) {
|
||||||
|
let index = str.indexOf('.prototype.isReactComponent={}');
|
||||||
|
|
||||||
|
index > -1 && (index = PatcherUtils.indexOf(str, '.createElement=', index));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = 'window.BX_EXPOSED.reactCreateElement=';
|
||||||
|
str = PatcherUtils.insertAt(str, index - 1, newCode);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 27.0.6-hotfix.1, 73704.js
|
||||||
|
gameCardCustomIcons(str: string) {
|
||||||
|
let initialIndex = str.indexOf('const{supportedInputIcons:');
|
||||||
|
if (initialIndex < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnIndex = PatcherUtils.lastIndexOf(str, 'return ', str.indexOf('SupportedInputsBadge'));
|
||||||
|
if (returnIndex < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find function's parameter
|
||||||
|
const arrowIndex = PatcherUtils.lastIndexOf(str, '=>{', initialIndex, 300);
|
||||||
|
if (arrowIndex < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramVar = PatcherUtils.getVariableNameBefore(str, arrowIndex);
|
||||||
|
|
||||||
|
// Find supportedInputIcons and title var names
|
||||||
|
const supportedInputIconsVar = PatcherUtils.getVariableNameAfter(str, PatcherUtils.indexOf(str, 'supportedInputIcons:', initialIndex, 100, true));
|
||||||
|
|
||||||
|
if (!paramVar || !supportedInputIconsVar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = renderString(codeGameCardIcons, {
|
||||||
|
param: paramVar,
|
||||||
|
supportedInputIcons: supportedInputIconsVar,
|
||||||
|
});
|
||||||
|
|
||||||
|
str = PatcherUtils.insertAt(str, returnIndex, newCode);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
// 27.0.6-hotfix.1, 28444.js
|
||||||
|
gameCardPassTitle(str: string) {
|
||||||
|
// Pass gameTitle info to gameCardCustomIcons()
|
||||||
|
let index = str.indexOf('=["productId","showInputBadges","ownershipBadgeType"');
|
||||||
|
index > -1 && (index = PatcherUtils.indexOf(str, ',gameTitle:', index, 500, true));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gameTitleVar = PatcherUtils.getVariableNameAfter(str, index);
|
||||||
|
if (!gameTitleVar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = PatcherUtils.indexOf(str, 'return', index);
|
||||||
|
index = PatcherUtils.indexOf(str, 'productId:', index);
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newCode = `gameTitle: ${gameTitleVar},`;
|
||||||
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 27.0.6-hotfix.1, 78831.js
|
||||||
|
setImageQuality(str: string) {
|
||||||
|
let index = str.indexOf('const{size:{width:');
|
||||||
|
index > -1 && (index = PatcherUtils.indexOf(str, '=new URLSearchParams', index, 500));
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramVar = PatcherUtils.getVariableNameBefore(str, index);
|
||||||
|
if (!paramVar) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find "return" keyword
|
||||||
|
index = PatcherUtils.indexOf(str, 'return', index, 200);
|
||||||
|
|
||||||
|
const newCode = `${paramVar}.set('q', ${getPref(PrefKey.UI_IMAGE_QUALITY)});`;
|
||||||
|
str = PatcherUtils.insertAt(str, index, newCode);
|
||||||
|
|
||||||
|
return str;
|
||||||
|
},
|
||||||
|
|
||||||
|
setBackgroundImageQuality(str: string) {
|
||||||
|
let index = str.indexOf('}?w=${');
|
||||||
|
index > -1 && (index = PatcherUtils.indexOf(str, '}', index + 1, 10, true));
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
str = PatcherUtils.insertAt(str, index, `&q=${getPref(PrefKey.UI_IMAGE_QUALITY)}`);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
let PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
...(AppInterface && getPref(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON ? [
|
||||||
'enableNativeMkb',
|
'enableNativeMkb',
|
||||||
'exposeInputSink',
|
|
||||||
'disableAbsoluteMouse',
|
'disableAbsoluteMouse',
|
||||||
] : []),
|
] : []),
|
||||||
|
|
||||||
|
'exposeReactCreateComponent',
|
||||||
|
'gameCardCustomIcons',
|
||||||
|
// 'gameCardPassTitle',
|
||||||
|
|
||||||
|
...(getPref(PrefKey.UI_IMAGE_QUALITY) < 90 ? [
|
||||||
|
'setImageQuality',
|
||||||
|
] : []),
|
||||||
|
|
||||||
'modifyPreloadedState',
|
'modifyPreloadedState',
|
||||||
|
|
||||||
'optimizeGameSlugGenerator',
|
'optimizeGameSlugGenerator',
|
||||||
@ -1068,10 +1186,6 @@ let PATCH_ORDERS = PatcherUtils.filterPatches([
|
|||||||
'enableConsoleLogging',
|
'enableConsoleLogging',
|
||||||
'enableXcloudLogger',
|
'enableXcloudLogger',
|
||||||
] : []),
|
] : []),
|
||||||
|
|
||||||
...(blockSomeNotifications() ? [
|
|
||||||
'changeNotificationsSubscription',
|
|
||||||
] : []),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const hideSections = getPref(PrefKey.UI_HIDE_SECTIONS);
|
const hideSections = getPref(PrefKey.UI_HIDE_SECTIONS);
|
||||||
@ -1081,12 +1195,22 @@ let HOME_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
|||||||
hideSections.includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
hideSections.includes(UiSection.ALL_GAMES) && 'ignoreAllGamesSection',
|
||||||
STATES.browser.capabilities.touch && hideSections.includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
STATES.browser.capabilities.touch && hideSections.includes(UiSection.TOUCH) && 'ignorePlayWithTouchSection',
|
||||||
hideSections.some(value => [UiSection.NATIVE_MKB, UiSection.MOST_POPULAR].includes(value)) && 'ignoreSiglSections',
|
hideSections.some(value => [UiSection.NATIVE_MKB, UiSection.MOST_POPULAR].includes(value)) && 'ignoreSiglSections',
|
||||||
|
|
||||||
|
...(getPref(PrefKey.UI_IMAGE_QUALITY) < 90 ? [
|
||||||
|
'setBackgroundImageQuality',
|
||||||
|
] : []),
|
||||||
|
|
||||||
|
...(blockSomeNotifications() ? [
|
||||||
|
'changeNotificationsSubscription',
|
||||||
|
] : []),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Only when playing
|
// Only when playing
|
||||||
// TODO: check this
|
// TODO: check this
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
|
'exposeInputChannel',
|
||||||
|
|
||||||
'patchXcloudTitleInfo',
|
'patchXcloudTitleInfo',
|
||||||
'disableGamepadDisconnectedScreen',
|
'disableGamepadDisconnectedScreen',
|
||||||
'patchStreamHud',
|
'patchStreamHud',
|
||||||
@ -1131,7 +1255,7 @@ let STREAM_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
let PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
let PRODUCT_DETAIL_PAGE_PATCH_ORDERS = PatcherUtils.filterPatches([
|
||||||
AppInterface && 'detectProductDetailPage',
|
'detectProductDetailPage',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const ALL_PATCHES = [...PATCH_ORDERS, ...HOME_PAGE_PATCH_ORDERS, ...STREAM_PAGE_PATCH_ORDERS, ...PRODUCT_DETAIL_PAGE_PATCH_ORDERS];
|
const ALL_PATCHES = [...PATCH_ORDERS, ...HOME_PAGE_PATCH_ORDERS, ...STREAM_PAGE_PATCH_ORDERS, ...PRODUCT_DETAIL_PAGE_PATCH_ORDERS];
|
||||||
@ -1256,6 +1380,7 @@ export class Patcher {
|
|||||||
|
|
||||||
// Apply patched functions
|
// Apply patched functions
|
||||||
if (modified) {
|
if (modified) {
|
||||||
|
BX_FLAGS.Debug && console.time(LOG_TAG);
|
||||||
try {
|
try {
|
||||||
chunkData[chunkId] = eval(patchedFuncStr);
|
chunkData[chunkId] = eval(patchedFuncStr);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
@ -1263,6 +1388,7 @@ export class Patcher {
|
|||||||
BxLogger.error(LOG_TAG, 'Error', appliedPatches, e.message, patchedFuncStr);
|
BxLogger.error(LOG_TAG, 'Error', appliedPatches, e.message, patchedFuncStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BX_FLAGS.Debug && console.timeEnd(LOG_TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to cache
|
// Save to cache
|
||||||
|
12
src/modules/patcher/patches/src/game-card-icons.ts
Normal file
12
src/modules/patcher/patches/src/game-card-icons.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
declare const $supportedInputIcons$: Array<any>;
|
||||||
|
declare const $param$: { productId: string };
|
||||||
|
|
||||||
|
const supportedInputIcons = $supportedInputIcons$;
|
||||||
|
const { productId } = $param$;
|
||||||
|
|
||||||
|
// Remove controller icon
|
||||||
|
supportedInputIcons.shift();
|
||||||
|
|
||||||
|
if (window.BX_EXPOSED.localCoOpManager!.isSupported(productId)) {
|
||||||
|
supportedInputIcons.push(window.BX_EXPOSED.createReactLocalCoOpIcon);
|
||||||
|
}
|
@ -7,8 +7,8 @@ export class VirtualControllerShortcut {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const released = generateVirtualControllerMapping();
|
const released = generateVirtualControllerMapping(0);
|
||||||
const pressed = generateVirtualControllerMapping({
|
const pressed = generateVirtualControllerMapping(0, {
|
||||||
Nexus: 1,
|
Nexus: 1,
|
||||||
VirtualPhysicality: 1024, // Home
|
VirtualPhysicality: 1024, // Home
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { GamepadKey } from "@/enums/gamepad";
|
import { GamepadKey } from "@/enums/gamepad";
|
||||||
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
||||||
import { BxEvent } from "@/utils/bx-event";
|
import { BxEvent } from "@/utils/bx-event";
|
||||||
@ -408,9 +410,6 @@ export class NavigationDialogManager {
|
|||||||
|
|
||||||
BxEventBus.Script.emit('dialog.shown', {});
|
BxEventBus.Script.emit('dialog.shown', {});
|
||||||
|
|
||||||
// Stop xCloud's navigation polling
|
|
||||||
window.BX_EXPOSED.disableGamepadPolling = true;
|
|
||||||
|
|
||||||
// Lock scroll bar
|
// Lock scroll bar
|
||||||
document.body.classList.add('bx-no-scroll');
|
document.body.classList.add('bx-no-scroll');
|
||||||
|
|
||||||
@ -437,11 +436,18 @@ export class NavigationDialogManager {
|
|||||||
this.$container.addEventListener('keydown', this);
|
this.$container.addEventListener('keydown', this);
|
||||||
|
|
||||||
// Start gamepad polling
|
// Start gamepad polling
|
||||||
this.startGamepadPolling();
|
if (isFullVersion()) {
|
||||||
|
this.startGamepadPolling();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
this.clearGamepadHoldingInterval();
|
// Stop gamepad polling
|
||||||
|
if (isFullVersion()) {
|
||||||
|
this.clearGamepadHoldingInterval();
|
||||||
|
this.stopGamepadPolling();
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isShowing()) {
|
if (!this.isShowing()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -459,9 +465,6 @@ export class NavigationDialogManager {
|
|||||||
// Remove event listeners
|
// Remove event listeners
|
||||||
this.$container.removeEventListener('keydown', this);
|
this.$container.removeEventListener('keydown', this);
|
||||||
|
|
||||||
// Stop gamepad polling
|
|
||||||
this.stopGamepadPolling();
|
|
||||||
|
|
||||||
// Remove current dialog and everything after it from dialogs stack
|
// Remove current dialog and everything after it from dialogs stack
|
||||||
if (this.dialog) {
|
if (this.dialog) {
|
||||||
const dialogIndex = this.dialogsStack.indexOf(this.dialog);
|
const dialogIndex = this.dialogsStack.indexOf(this.dialog);
|
||||||
@ -473,9 +476,6 @@ export class NavigationDialogManager {
|
|||||||
// Unmount dialog
|
// Unmount dialog
|
||||||
this.unmountCurrentDialog();
|
this.unmountCurrentDialog();
|
||||||
|
|
||||||
// Enable xCloud's navigation polling
|
|
||||||
window.BX_EXPOSED.disableGamepadPolling = false;
|
|
||||||
|
|
||||||
// Show the last dialog in dialogs stack
|
// Show the last dialog in dialogs stack
|
||||||
if (this.dialogsStack.length) {
|
if (this.dialogsStack.length) {
|
||||||
this.dialogsStack[this.dialogsStack.length - 1].show();
|
this.dialogsStack[this.dialogsStack.length - 1].show();
|
||||||
@ -639,14 +639,18 @@ export class NavigationDialogManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private startGamepadPolling() {
|
private startGamepadPolling() {
|
||||||
this.stopGamepadPolling();
|
// Stop xCloud's navigation polling
|
||||||
|
window.BX_EXPOSED.disableGamepadPolling = true;
|
||||||
|
|
||||||
|
this.stopGamepadPolling();
|
||||||
this.gamepadPollingIntervalId = window.setInterval(this.pollGamepad, NavigationDialogManager.GAMEPAD_POLLING_INTERVAL);
|
this.gamepadPollingIntervalId = window.setInterval(this.pollGamepad, NavigationDialogManager.GAMEPAD_POLLING_INTERVAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private stopGamepadPolling() {
|
private stopGamepadPolling() {
|
||||||
this.gamepadLastStates = [];
|
// Enable xCloud's navigation polling
|
||||||
|
window.BX_EXPOSED.disableGamepadPolling = false;
|
||||||
|
|
||||||
|
this.gamepadLastStates = [];
|
||||||
this.gamepadPollingIntervalId && window.clearInterval(this.gamepadPollingIntervalId);
|
this.gamepadPollingIntervalId && window.clearInterval(this.gamepadPollingIntervalId);
|
||||||
this.gamepadPollingIntervalId = null;
|
this.gamepadPollingIntervalId = null;
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,11 @@ export class RemotePlayDialog extends NavigationDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setupDialog() {
|
private setupDialog() {
|
||||||
const $fragment = CE('div', { class: 'bx-remote-play-container' });
|
const $fragment = CE('div', { class: 'bx-centered-dialog' },
|
||||||
|
CE('div', { class: 'bx-dialog-title' },
|
||||||
|
CE('p', false, t('remote-play')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const $settingNote = CE('p', {});
|
const $settingNote = CE('p', {});
|
||||||
|
|
||||||
|
@ -274,6 +274,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
label: t('ui'),
|
label: t('ui'),
|
||||||
items: [
|
items: [
|
||||||
PrefKey.UI_LAYOUT,
|
PrefKey.UI_LAYOUT,
|
||||||
|
PrefKey.UI_IMAGE_QUALITY,
|
||||||
PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME,
|
PrefKey.UI_GAME_CARD_SHOW_WAIT_TIME,
|
||||||
PrefKey.UI_CONTROLLER_SHOW_STATUS,
|
PrefKey.UI_CONTROLLER_SHOW_STATUS,
|
||||||
PrefKey.UI_SIMPLIFY_STREAM_MENU,
|
PrefKey.UI_SIMPLIFY_STREAM_MENU,
|
||||||
@ -312,7 +313,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
items: [
|
items: [
|
||||||
PrefKey.BLOCK_TRACKING,
|
PrefKey.BLOCK_TRACKING,
|
||||||
],
|
],
|
||||||
}, {
|
}, isFullVersion() && {
|
||||||
group: 'advanced',
|
group: 'advanced',
|
||||||
label: t('advanced'),
|
label: t('advanced'),
|
||||||
items: [
|
items: [
|
||||||
@ -494,24 +495,23 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
}],
|
}],
|
||||||
}];
|
}];
|
||||||
|
|
||||||
private readonly TAB_CONTROLLER_ITEMS: Array<SettingTabSection | HTMLElement | false> = [{
|
private readonly TAB_CONTROLLER_ITEMS: Array<SettingTabSection | HTMLElement | false> = isFullVersion() ? [{
|
||||||
group: 'controller',
|
group: 'controller',
|
||||||
label: t('controller'),
|
label: t('controller'),
|
||||||
helpUrl: 'https://better-xcloud.github.io/ingame-features/#controller',
|
helpUrl: 'https://better-xcloud.github.io/ingame-features/#controller',
|
||||||
items: [
|
items: [
|
||||||
isFullVersion() && {
|
{
|
||||||
pref: PrefKey.LOCAL_CO_OP_ENABLED,
|
pref: PrefKey.LOCAL_CO_OP_ENABLED,
|
||||||
onChange: () => { BxExposed.toggleLocalCoOp(getPref(PrefKey.LOCAL_CO_OP_ENABLED)); },
|
onChange: () => { BxExposed.toggleLocalCoOp(getPref(PrefKey.LOCAL_CO_OP_ENABLED)); },
|
||||||
},
|
}, {
|
||||||
isFullVersion() && {
|
|
||||||
pref: PrefKey.CONTROLLER_POLLING_RATE,
|
pref: PrefKey.CONTROLLER_POLLING_RATE,
|
||||||
onChange: () => StreamSettings.refreshControllerSettings(),
|
onChange: () => StreamSettings.refreshControllerSettings(),
|
||||||
}, isFullVersion() && ($parent => {
|
}, ($parent => {
|
||||||
$parent.appendChild(ControllerExtraSettings.renderSettings.apply(this));
|
$parent.appendChild(ControllerExtraSettings.renderSettings.apply(this));
|
||||||
})],
|
})],
|
||||||
},
|
},
|
||||||
|
|
||||||
isFullVersion() && STATES.userAgent.capabilities.touch && {
|
STATES.userAgent.capabilities.touch && {
|
||||||
group: 'touch-control',
|
group: 'touch-control',
|
||||||
label: t('touch-controller'),
|
label: t('touch-controller'),
|
||||||
items: [{
|
items: [{
|
||||||
@ -563,7 +563,9 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
}, isFullVersion() && STATES.browser.capabilities.deviceVibration && {
|
},
|
||||||
|
|
||||||
|
STATES.browser.capabilities.deviceVibration && {
|
||||||
group: 'device',
|
group: 'device',
|
||||||
label: t('device'),
|
label: t('device'),
|
||||||
items: [{
|
items: [{
|
||||||
@ -576,22 +578,22 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
unsupported: !STATES.browser.capabilities.deviceVibration,
|
unsupported: !STATES.browser.capabilities.deviceVibration,
|
||||||
onChange: () => StreamSettings.refreshControllerSettings(),
|
onChange: () => StreamSettings.refreshControllerSettings(),
|
||||||
}],
|
}],
|
||||||
}];
|
}] : [];
|
||||||
|
|
||||||
private readonly TAB_MKB_ITEMS: (() => Array<SettingTabSection | false>) = () => [
|
private readonly TAB_MKB_ITEMS: (() => Array<SettingTabSection | false>) = isFullVersion() ? () => [
|
||||||
isFullVersion() && {
|
{
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
group: 'mkb',
|
group: 'mkb',
|
||||||
label: t('mouse-and-keyboard'),
|
label: t('mouse-and-keyboard'),
|
||||||
helpUrl: 'https://better-xcloud.github.io/mouse-and-keyboard/',
|
helpUrl: 'https://better-xcloud.github.io/mouse-and-keyboard/',
|
||||||
items: [
|
items: [
|
||||||
isFullVersion() && (($parent: HTMLElement) => {
|
($parent: HTMLElement) => {
|
||||||
$parent.appendChild(MkbExtraSettings.renderSettings.apply(this));
|
$parent.appendChild(MkbExtraSettings.renderSettings.apply(this));
|
||||||
})
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
isFullVersion() && NativeMkbHandler.isAllowed() && {
|
NativeMkbHandler.isAllowed() && {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
group: 'native-mkb',
|
group: 'native-mkb',
|
||||||
label: t('native-mkb'),
|
label: t('native-mkb'),
|
||||||
@ -606,7 +608,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
NativeMkbHandler.getInstance()?.setHorizontalScrollMultiplier(value / 100);
|
NativeMkbHandler.getInstance()?.setHorizontalScrollMultiplier(value / 100);
|
||||||
},
|
},
|
||||||
}] : [],
|
}] : [],
|
||||||
}];
|
}] : () => [];
|
||||||
|
|
||||||
private readonly TAB_STATS_ITEMS: Array<SettingTabSection | false> = [{
|
private readonly TAB_STATS_ITEMS: Array<SettingTabSection | false> = [{
|
||||||
group: 'stats',
|
group: 'stats',
|
||||||
@ -1007,6 +1009,7 @@ export class SettingsDialog extends NavigationDialog {
|
|||||||
const $row = createSettingRow(label, !prefDefinition?.unsupported && $control, {
|
const $row = createSettingRow(label, !prefDefinition?.unsupported && $control, {
|
||||||
$note,
|
$note,
|
||||||
multiLines: setting.multiLines,
|
multiLines: setting.multiLines,
|
||||||
|
icon: prefDefinition?.labelIcon,
|
||||||
});
|
});
|
||||||
if (pref) {
|
if (pref) {
|
||||||
$row.htmlFor = `bx_setting_${escapeCssSelector(pref)}`;
|
$row.htmlFor = `bx_setting_${escapeCssSelector(pref)}`;
|
||||||
|
@ -69,7 +69,7 @@ export class MkbExtraSettings extends HTMLElement {
|
|||||||
createSettingRow(
|
createSettingRow(
|
||||||
t('virtual-controller-slot'),
|
t('virtual-controller-slot'),
|
||||||
SettingElement.fromPref(PrefKey.MKB_P1_SLOT, STORAGE.Global, () => {
|
SettingElement.fromPref(PrefKey.MKB_P1_SLOT, STORAGE.Global, () => {
|
||||||
EmulatedMkbHandler.getInstance()?.updateGamepadSlots();
|
EmulatedMkbHandler.getInstance()?.resetXcloudGamepads();
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
] : []),
|
] : []),
|
||||||
|
@ -41,6 +41,7 @@ export class GuideMenu {
|
|||||||
const buttons = {
|
const buttons = {
|
||||||
scriptSettings: createButton({
|
scriptSettings: createButton({
|
||||||
label: t('better-xcloud'),
|
label: t('better-xcloud'),
|
||||||
|
icon: BxIcon.BETTER_XCLOUD,
|
||||||
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
style: ButtonStyle.FULL_WIDTH | ButtonStyle.FOCUSABLE | ButtonStyle.PRIMARY,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
// Wait until the Guide dialog is closed
|
// Wait until the Guide dialog is closed
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { SCRIPT_VERSION } from "@utils/global";
|
import { SCRIPT_VERSION } from "@utils/global";
|
||||||
import { createButton, ButtonStyle, CE, isElementVisible } from "@utils/html";
|
import { createButton, ButtonStyle, CE, isElementVisible } from "@utils/html";
|
||||||
import { BxIcon } from "@utils/bx-icon";
|
import { BxIcon } from "@utils/bx-icon";
|
||||||
@ -14,7 +16,7 @@ export class HeaderSection {
|
|||||||
public static getInstance = () => HeaderSection.instance ?? (HeaderSection.instance = new HeaderSection());
|
public static getInstance = () => HeaderSection.instance ?? (HeaderSection.instance = new HeaderSection());
|
||||||
private readonly LOG_TAG = 'HeaderSection';
|
private readonly LOG_TAG = 'HeaderSection';
|
||||||
|
|
||||||
private $btnRemotePlay: HTMLElement;
|
private $btnRemotePlay: HTMLElement | null;
|
||||||
private $btnSettings: HTMLElement;
|
private $btnSettings: HTMLElement;
|
||||||
private $buttonsWrapper: HTMLElement;
|
private $buttonsWrapper: HTMLElement;
|
||||||
|
|
||||||
@ -24,13 +26,17 @@ export class HeaderSection {
|
|||||||
constructor() {
|
constructor() {
|
||||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||||
|
|
||||||
this.$btnRemotePlay = createButton({
|
if (isFullVersion()) {
|
||||||
classes: ['bx-header-remote-play-button', 'bx-gone'],
|
this.$btnRemotePlay = createButton({
|
||||||
icon: BxIcon.REMOTE_PLAY,
|
classes: ['bx-header-remote-play-button', 'bx-gone'],
|
||||||
title: t('remote-play'),
|
icon: BxIcon.REMOTE_PLAY,
|
||||||
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.CIRCULAR,
|
title: t('remote-play'),
|
||||||
onClick: e => RemotePlayManager.getInstance()?.togglePopup(),
|
style: ButtonStyle.GHOST | ButtonStyle.FOCUSABLE | ButtonStyle.CIRCULAR,
|
||||||
});
|
onClick: e => RemotePlayManager.getInstance()?.togglePopup(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$btnRemotePlay = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.$btnSettings = createButton({
|
this.$btnSettings = createButton({
|
||||||
classes: ['bx-header-settings-button'],
|
classes: ['bx-header-settings-button'],
|
||||||
@ -98,7 +104,7 @@ export class HeaderSection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showRemotePlayButton() {
|
showRemotePlayButton() {
|
||||||
this.$btnRemotePlay.classList.remove('bx-gone');
|
this.$btnRemotePlay?.classList.remove('bx-gone');
|
||||||
}
|
}
|
||||||
|
|
||||||
static watchHeader() {
|
static watchHeader() {
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { BX_FLAGS } from "@/utils/bx-flags";
|
import { BX_FLAGS } from "@/utils/bx-flags";
|
||||||
import { BxIcon } from "@/utils/bx-icon";
|
import { BxIcon } from "@/utils/bx-icon";
|
||||||
import { AppInterface } from "@/utils/global";
|
import { AppInterface } from "@/utils/global";
|
||||||
import { ButtonStyle, CE, createButton } from "@/utils/html";
|
import { ButtonStyle, CE, createButton, createSvgIcon } from "@/utils/html";
|
||||||
|
import { LocalCoOpManager } from "@/utils/local-co-op-manager";
|
||||||
import { t } from "@/utils/translation";
|
import { t } from "@/utils/translation";
|
||||||
import { parseDetailsPath } from "@/utils/utils";
|
import { parseDetailsPath } from "@/utils/utils";
|
||||||
|
|
||||||
@ -28,21 +29,33 @@ export class ProductDetailsPage {
|
|||||||
private static injectTimeoutId: number | null = null;
|
private static injectTimeoutId: number | null = null;
|
||||||
|
|
||||||
static injectButtons() {
|
static injectButtons() {
|
||||||
if (!AppInterface) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProductDetailsPage.injectTimeoutId && clearTimeout(ProductDetailsPage.injectTimeoutId);
|
ProductDetailsPage.injectTimeoutId && clearTimeout(ProductDetailsPage.injectTimeoutId);
|
||||||
ProductDetailsPage.injectTimeoutId = window.setTimeout(() => {
|
ProductDetailsPage.injectTimeoutId = window.setTimeout(() => {
|
||||||
// Find action buttons container
|
// Inputs
|
||||||
const $container = document.querySelector('div[class*=ActionButtons-module__container]');
|
const $inputsContainer = document.querySelector<HTMLElement>('div[class*="Header-module__gamePassAndInputsContainer"]');
|
||||||
if ($container && $container.parentElement) {
|
if ($inputsContainer && !$inputsContainer.dataset.bxInjected) {
|
||||||
$container.parentElement.appendChild(CE('div', {
|
$inputsContainer.dataset.bxInjected = 'true';
|
||||||
class: 'bx-product-details-buttons',
|
|
||||||
},
|
const { productId } = parseDetailsPath(window.location.pathname);
|
||||||
['android-handheld', 'android'].includes(BX_FLAGS.DeviceInfo.deviceType) && ProductDetailsPage.$btnShortcut,
|
if (LocalCoOpManager.getInstance().isSupported(productId || '')) {
|
||||||
ProductDetailsPage.$btnWallpaper,
|
$inputsContainer.insertAdjacentElement('afterend', CE('div', {
|
||||||
));
|
class: 'bx-product-details-icons bx-frosted',
|
||||||
|
}, createSvgIcon(BxIcon.LOCAL_CO_OP), t('local-co-op')));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject buttons for Android app
|
||||||
|
if (AppInterface) {
|
||||||
|
// Find action buttons container
|
||||||
|
const $container = document.querySelector('div[class*=ActionButtons-module__container]');
|
||||||
|
if ($container && $container.parentElement) {
|
||||||
|
$container.parentElement.appendChild(CE('div', {
|
||||||
|
class: 'bx-product-details-buttons',
|
||||||
|
},
|
||||||
|
['android-handheld', 'android'].includes(BX_FLAGS.DeviceInfo.deviceType) && ProductDetailsPage.$btnShortcut,
|
||||||
|
ProductDetailsPage.$btnWallpaper,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
3
src/types/global.d.ts
vendored
3
src/types/global.d.ts
vendored
@ -5,6 +5,7 @@ import type { StreamSettings, type StreamSettingsData } from "@/utils/stream-set
|
|||||||
import type { BxEvent } from "@/utils/bx-event";
|
import type { BxEvent } from "@/utils/bx-event";
|
||||||
import type { BxEventBus } from "@/utils/bx-event-bus";
|
import type { BxEventBus } from "@/utils/bx-event-bus";
|
||||||
import type { BxLogger } from "@/utils/bx-logger";
|
import type { BxLogger } from "@/utils/bx-logger";
|
||||||
|
import type { XcloudInputChannel } from "@/utils/gamepad";
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ declare global {
|
|||||||
closeAll: () => void;
|
closeAll: () => void;
|
||||||
};
|
};
|
||||||
showStreamMenu: () => void;
|
showStreamMenu: () => void;
|
||||||
inputSink: any;
|
inputChannel: XcloudInputChannel | undefined;
|
||||||
streamSession: any;
|
streamSession: any;
|
||||||
touchLayoutManager: any;
|
touchLayoutManager: any;
|
||||||
}>;
|
}>;
|
||||||
|
1
src/types/setting-definition.d.ts
vendored
1
src/types/setting-definition.d.ts
vendored
@ -20,6 +20,7 @@ interface BaseSettingDefinition {
|
|||||||
default: any;
|
default: any;
|
||||||
|
|
||||||
label?: string;
|
label?: string;
|
||||||
|
labelIcon?: BxIconRaw,
|
||||||
note?: string | (() => HTMLElement) | HTMLElement;
|
note?: string | (() => HTMLElement) | HTMLElement;
|
||||||
experimental?: boolean;
|
experimental?: boolean;
|
||||||
unsupported?: boolean;
|
unsupported?: boolean;
|
||||||
|
@ -31,6 +31,10 @@ type ScriptEvents = {
|
|||||||
data: any;
|
data: any;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
'list.localCoOp.updated': {
|
||||||
|
ids: Set<string>,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type StreamEvents = {
|
type StreamEvents = {
|
||||||
|
@ -13,6 +13,7 @@ import { NativeMkbMode, TouchControllerMode } from "@/enums/pref-values";
|
|||||||
import { Patcher, type PatchPage } from "@/modules/patcher/patcher";
|
import { Patcher, type PatchPage } from "@/modules/patcher/patcher";
|
||||||
import { BxEventBus } from "./bx-event-bus";
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
import { FeatureGates } from "./feature-gates";
|
import { FeatureGates } from "./feature-gates";
|
||||||
|
import { LocalCoOpManager } from "./local-co-op-manager";
|
||||||
|
|
||||||
export enum SupportedInputType {
|
export enum SupportedInputType {
|
||||||
CONTROLLER = 'Controller',
|
CONTROLLER = 'Controller',
|
||||||
@ -230,4 +231,25 @@ export const BxExposed = {
|
|||||||
BxLogger.info('beforePageLoad', page);
|
BxLogger.info('beforePageLoad', page);
|
||||||
Patcher.patchPage(page);
|
Patcher.patchPage(page);
|
||||||
} : () => {},
|
} : () => {},
|
||||||
|
|
||||||
|
localCoOpManager: isFullVersion() ? LocalCoOpManager.getInstance() : null,
|
||||||
|
reactCreateElement: function(...args: any[]) {},
|
||||||
|
|
||||||
|
createReactLocalCoOpIcon: isFullVersion() ? (attrs: any): any => {
|
||||||
|
const reactCE = window.BX_EXPOSED.reactCreateElement;
|
||||||
|
|
||||||
|
// local-co-op.svg
|
||||||
|
return reactCE(
|
||||||
|
'svg',
|
||||||
|
{ xmlns: 'http://www.w3.org/2000/svg', width: '1em', height: '1em', viewBox: '0 0 32 32', 'fill-rule': 'evenodd', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', ...attrs },
|
||||||
|
reactCE(
|
||||||
|
'g',
|
||||||
|
null,
|
||||||
|
reactCE('path', { d: 'M24.272 11.165h-3.294l-3.14 3.564c-.391.391-.922.611-1.476.611a2.1 2.1 0 0 1-2.087-2.088 2.09 2.09 0 0 1 .031-.362l1.22-6.274a3.89 3.89 0 0 1 3.81-3.206h6.57c1.834 0 3.439 1.573 3.833 3.295l1.205 6.185a2.09 2.09 0 0 1 .031.362 2.1 2.1 0 0 1-2.087 2.088c-.554 0-1.085-.22-1.476-.611l-3.14-3.564', fill: 'none', stroke: '#fff', 'stroke-width': '2' }),
|
||||||
|
reactCE('circle', { cx: '22.625', cy: '5.874', r: '.879' }),
|
||||||
|
reactCE('path', { d: 'M11.022 24.415H7.728l-3.14 3.564c-.391.391-.922.611-1.476.611a2.1 2.1 0 0 1-2.087-2.088 2.09 2.09 0 0 1 .031-.362l1.22-6.274a3.89 3.89 0 0 1 3.81-3.206h6.57c1.834 0 3.439 1.573 3.833 3.295l1.205 6.185a2.09 2.09 0 0 1 .031.362 2.1 2.1 0 0 1-2.087 2.088c-.554 0-1.085-.22-1.476-.611l-3.14-3.564', fill: 'none', stroke: '#fff', 'stroke-width': '2' }),
|
||||||
|
reactCE('circle', { cx: '9.375', cy: '19.124', r: '.879' })
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} : () => {},
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,7 @@ import iconDisplay from "@assets/svg/display.svg" with { type: "text" };
|
|||||||
import iconEye from "@assets/svg/eye.svg" with { type: "text" };
|
import iconEye from "@assets/svg/eye.svg" with { type: "text" };
|
||||||
import iconEyeSlash from "@assets/svg/eye-slash.svg" with { type: "text" };
|
import iconEyeSlash from "@assets/svg/eye-slash.svg" with { type: "text" };
|
||||||
import iconHome from "@assets/svg/home.svg" with { type: "text" };
|
import iconHome from "@assets/svg/home.svg" with { type: "text" };
|
||||||
|
import iconLocalCoOp from "@assets/svg/local-co-op.svg" with { type: "text" };
|
||||||
import iconNativeMkb from "@assets/svg/native-mkb.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 iconNew from "@assets/svg/new.svg" with { type: "text" };
|
||||||
import iconPencil from "@assets/svg/pencil-simple-line.svg" with { type: "text" };
|
import iconPencil from "@assets/svg/pencil-simple-line.svg" with { type: "text" };
|
||||||
@ -52,6 +53,7 @@ export const BxIcon = {
|
|||||||
EYE: iconEye,
|
EYE: iconEye,
|
||||||
EYE_SLASH: iconEyeSlash,
|
EYE_SLASH: iconEyeSlash,
|
||||||
HOME: iconHome,
|
HOME: iconHome,
|
||||||
|
LOCAL_CO_OP: iconLocalCoOp,
|
||||||
NATIVE_MKB: iconNativeMkb,
|
NATIVE_MKB: iconNativeMkb,
|
||||||
NEW: iconNew,
|
NEW: iconNew,
|
||||||
MANAGE: iconPencil,
|
MANAGE: iconPencil,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CE } from "@utils/html";
|
import { CE } from "@utils/html";
|
||||||
import { compressCss, renderStylus } from "@macros/build" with { type: "macro" };
|
import { compressCss, isLiteVersion, renderStylus } from "@macros/build" with { type: "macro" };
|
||||||
import { BlockFeature, UiSection } from "@/enums/pref-values";
|
import { BlockFeature, UiSection } from "@/enums/pref-values";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
@ -12,6 +12,12 @@ export function addCss() {
|
|||||||
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
|
const PREF_HIDE_SECTIONS = getPref(PrefKey.UI_HIDE_SECTIONS);
|
||||||
const selectorToHide = [];
|
const selectorToHide = [];
|
||||||
|
|
||||||
|
if (isLiteVersion()) {
|
||||||
|
// Hide Controller icon in Game tiles
|
||||||
|
selectorToHide.push('div[class*=SupportedInputsBadge] svg:first-of-type');
|
||||||
|
selectorToHide.push('div[class*=SupportedInputsBadge]:not(:has(:nth-child(2)))');
|
||||||
|
}
|
||||||
|
|
||||||
// Hide "News" section
|
// Hide "News" section
|
||||||
if (PREF_HIDE_SECTIONS.includes(UiSection.NEWS)) {
|
if (PREF_HIDE_SECTIONS.includes(UiSection.NEWS)) {
|
||||||
selectorToHide.push('#BodyContent > div[class*=CarouselRow-module]');
|
selectorToHide.push('#BodyContent > div[class*=CarouselRow-module]');
|
||||||
|
@ -4,7 +4,21 @@ import { Toast } from "@utils/toast";
|
|||||||
import { BxLogger } from "@utils/bx-logger";
|
import { BxLogger } from "@utils/bx-logger";
|
||||||
import { PrefKey } from "@/enums/pref-keys";
|
import { PrefKey } from "@/enums/pref-keys";
|
||||||
import { getPref } from "./settings-storages/global-settings-storage";
|
import { getPref } from "./settings-storages/global-settings-storage";
|
||||||
import { GamepadKeyName, type GamepadKey } from "@/enums/gamepad";
|
import { GamepadKey, GamepadKeyName } from "@/enums/gamepad";
|
||||||
|
|
||||||
|
export type NativeMouseData = {
|
||||||
|
X: number,
|
||||||
|
Y: number,
|
||||||
|
Buttons: number,
|
||||||
|
WheelX: number,
|
||||||
|
WheelY: number,
|
||||||
|
Type?: 0, // 0: Relative, 1: Absolute
|
||||||
|
}
|
||||||
|
|
||||||
|
export type XcloudInputChannel = {
|
||||||
|
sendGamepadInput: (timestamp: number, gamepads: XcloudGamepad[]) => void;
|
||||||
|
queueMouseInput: (data: NativeMouseData) => void;
|
||||||
|
}
|
||||||
|
|
||||||
// Show a toast when connecting/disconecting controller
|
// Show a toast when connecting/disconecting controller
|
||||||
export function showGamepadToast(gamepad: Gamepad) {
|
export function showGamepadToast(gamepad: Gamepad) {
|
||||||
@ -59,9 +73,9 @@ export function hasGamepad() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateVirtualControllerMapping(override: {}={}) {
|
export function generateVirtualControllerMapping(index: number, override: Partial<XcloudGamepad>={}) {
|
||||||
const mapping = {
|
const mapping = {
|
||||||
GamepadIndex: 0,
|
GamepadIndex: index,
|
||||||
A: 0,
|
A: 0,
|
||||||
B: 0,
|
B: 0,
|
||||||
X: 0,
|
X: 0,
|
||||||
@ -95,3 +109,44 @@ export function generateVirtualControllerMapping(override: {}={}) {
|
|||||||
export function getGamepadPrompt(gamepadKey: GamepadKey): string {
|
export function getGamepadPrompt(gamepadKey: GamepadKey): string {
|
||||||
return GamepadKeyName[gamepadKey][1];
|
return GamepadKeyName[gamepadKey][1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const XCLOUD_GAMEPAD_KEY_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.HOME]: 'Nexus',
|
||||||
|
[GamepadKey.SHARE]: 'Share',
|
||||||
|
|
||||||
|
[GamepadKey.LS_LEFT]: 'LeftThumbXAxis',
|
||||||
|
[GamepadKey.LS_RIGHT]: 'LeftThumbXAxis',
|
||||||
|
[GamepadKey.LS_UP]: 'LeftThumbYAxis',
|
||||||
|
[GamepadKey.LS_DOWN]: 'LeftThumbYAxis',
|
||||||
|
|
||||||
|
[GamepadKey.RS_LEFT]: 'RightThumbXAxis',
|
||||||
|
[GamepadKey.RS_RIGHT]: 'RightThumbXAxis',
|
||||||
|
[GamepadKey.RS_UP]: 'RightThumbYAxis',
|
||||||
|
[GamepadKey.RS_DOWN]: 'RightThumbYAxis',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toXcloudGamepadKey(gamepadKey: GamepadKey) {
|
||||||
|
return XCLOUD_GAMEPAD_KEY_MAPPING[gamepadKey];
|
||||||
|
}
|
||||||
|
@ -56,6 +56,8 @@ export class GhPagesUtils {
|
|||||||
BxEventBus.Script.emit('list.forcedNativeMkb.updated', {
|
BxEventBus.Script.emit('list.forcedNativeMkb.updated', {
|
||||||
data: json,
|
data: json,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -70,6 +72,7 @@ export class GhPagesUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static getTouchControlCustomList() {
|
static getTouchControlCustomList() {
|
||||||
|
// TODO: use Set()
|
||||||
const key = StorageKey.LIST_CUSTOM_TOUCH_LAYOUTS;
|
const key = StorageKey.LIST_CUSTOM_TOUCH_LAYOUTS;
|
||||||
|
|
||||||
NATIVE_FETCH(GhPagesUtils.getUrl('touch-layouts/ids.json'))
|
NATIVE_FETCH(GhPagesUtils.getUrl('touch-layouts/ids.json'))
|
||||||
@ -83,4 +86,31 @@ export class GhPagesUtils {
|
|||||||
const customList = JSON.parse(window.localStorage.getItem(key) || '[]');
|
const customList = JSON.parse(window.localStorage.getItem(key) || '[]');
|
||||||
return customList;
|
return customList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getLocalCoOpList(): Set<string> {
|
||||||
|
const supportedSchema = 1;
|
||||||
|
const key = StorageKey.LIST_LOCAL_CO_OP;
|
||||||
|
|
||||||
|
NATIVE_FETCH(GhPagesUtils.getUrl('local-co-op/ids.json'))
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(json => {
|
||||||
|
if (json.$schemaVersion === supportedSchema) {
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(json));
|
||||||
|
const ids = new Set(Object.keys(json.data));
|
||||||
|
BxEventBus.Script.emit('list.localCoOp.updated', { ids });
|
||||||
|
} else {
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
BxEventBus.Script.emit('list.localCoOp.updated', { ids: new Set() });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const info = JSON.parse(window.localStorage.getItem(key) || '{}');
|
||||||
|
if (info.$schemaVersion !== supportedSchema) {
|
||||||
|
// Delete storage;
|
||||||
|
window.localStorage.removeItem(key);
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Set(Object.keys(info.data || {}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { isFullVersion } from "@macros/build" with { type: "macro" };
|
||||||
|
|
||||||
import { BxEvent } from "@utils/bx-event";
|
import { BxEvent } from "@utils/bx-event";
|
||||||
import { LoadingScreen } from "@modules/loading-screen";
|
import { LoadingScreen } from "@modules/loading-screen";
|
||||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||||
@ -25,7 +27,9 @@ export function onHistoryChanged(e: PopStateEvent) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.setTimeout(RemotePlayManager.detect, 10);
|
if (isFullVersion()) {
|
||||||
|
window.setTimeout(RemotePlayManager.detect, 10);
|
||||||
|
}
|
||||||
|
|
||||||
// Hide Navigation dialog
|
// Hide Navigation dialog
|
||||||
NavigationDialogManager.getInstance().hide();
|
NavigationDialogManager.getInstance().hide();
|
||||||
|
@ -54,6 +54,7 @@ export type BxButtonOptions = Partial<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type SettingsRowOptions = Partial<{
|
export type SettingsRowOptions = Partial<{
|
||||||
|
icon: BxIconRaw,
|
||||||
multiLines: boolean;
|
multiLines: boolean;
|
||||||
$note: HTMLElement;
|
$note: HTMLElement;
|
||||||
}>;
|
}>;
|
||||||
@ -210,6 +211,7 @@ export function createSettingRow(label: string, $control: HTMLElement | false |
|
|||||||
|
|
||||||
const $row = CE('label', { class: 'bx-settings-row' },
|
const $row = CE('label', { class: 'bx-settings-row' },
|
||||||
$label = CE('span', { class: 'bx-settings-label' },
|
$label = CE('span', { class: 'bx-settings-label' },
|
||||||
|
options.icon && createSvgIcon(options.icon),
|
||||||
label,
|
label,
|
||||||
options.$note,
|
options.$note,
|
||||||
),
|
),
|
||||||
|
21
src/utils/local-co-op-manager.ts
Normal file
21
src/utils/local-co-op-manager.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { BxEventBus } from "./bx-event-bus";
|
||||||
|
import { GhPagesUtils } from "./gh-pages";
|
||||||
|
|
||||||
|
export class LocalCoOpManager {
|
||||||
|
private static instance: LocalCoOpManager;
|
||||||
|
public static getInstance = () => LocalCoOpManager.instance ?? (LocalCoOpManager.instance = new LocalCoOpManager());
|
||||||
|
|
||||||
|
private supportedIds: Set<string>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
BxEventBus.Script.once('list.localCoOp.updated', e => {
|
||||||
|
this.supportedIds = e.ids;
|
||||||
|
});
|
||||||
|
this.supportedIds = GhPagesUtils.getLocalCoOpList();
|
||||||
|
console.log('this.supportedIds', this.supportedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported(productId: string) {
|
||||||
|
return this.supportedIds.has(productId);
|
||||||
|
}
|
||||||
|
}
|
@ -46,7 +46,7 @@ export class MkbMappingPresetsTable extends BasePresetsTable<MkbPresetRecord> {
|
|||||||
[GamepadKey.A]: ['Space', 'KeyE'],
|
[GamepadKey.A]: ['Space', 'KeyE'],
|
||||||
[GamepadKey.X]: ['KeyR'],
|
[GamepadKey.X]: ['KeyR'],
|
||||||
[GamepadKey.B]: ['KeyC', 'Backspace'],
|
[GamepadKey.B]: ['KeyC', 'Backspace'],
|
||||||
[GamepadKey.Y]: ['KeyE'],
|
[GamepadKey.Y]: ['KeyV'],
|
||||||
|
|
||||||
[GamepadKey.START]: ['Enter'],
|
[GamepadKey.START]: ['Enter'],
|
||||||
[GamepadKey.SELECT]: ['Tab'],
|
[GamepadKey.SELECT]: ['Tab'],
|
||||||
|
@ -221,8 +221,12 @@ export function interceptHttpRequests() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ignore domains
|
// Ignore domains
|
||||||
const domain = (new URL(url)).hostname;
|
try {
|
||||||
if (IGNORED_DOMAINS.includes(domain)) {
|
const domain = (new URL(url)).hostname;
|
||||||
|
if (IGNORED_DOMAINS.includes(domain)) {
|
||||||
|
return NATIVE_FETCH(request, init);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
return NATIVE_FETCH(request, init);
|
return NATIVE_FETCH(request, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ import { MkbMappingDefaultPresetId } from "../local-db/mkb-mapping-presets-table
|
|||||||
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
import { KeyboardShortcutDefaultId } from "../local-db/keyboard-shortcuts-table";
|
||||||
import { GhPagesUtils } from "../gh-pages";
|
import { GhPagesUtils } from "../gh-pages";
|
||||||
import { BxEventBus } from "../bx-event-bus";
|
import { BxEventBus } from "../bx-event-bus";
|
||||||
|
import { BxIcon } from "../bx-icon";
|
||||||
|
|
||||||
|
|
||||||
function getSupportedCodecProfiles() {
|
function getSupportedCodecProfiles() {
|
||||||
@ -192,6 +193,25 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
label: t('hide-system-menu-icon'),
|
label: t('hide-system-menu-icon'),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
[PrefKey.UI_IMAGE_QUALITY]: {
|
||||||
|
requiredVariants: 'full',
|
||||||
|
label: t('image-quality'),
|
||||||
|
default: 90,
|
||||||
|
min: 10,
|
||||||
|
max: 90,
|
||||||
|
params: {
|
||||||
|
steps: 10,
|
||||||
|
exactTicks: 20,
|
||||||
|
hideSlider: true,
|
||||||
|
customTextValue(value, min, max) {
|
||||||
|
if (value === 90) {
|
||||||
|
return t('default');
|
||||||
|
}
|
||||||
|
|
||||||
|
return value + '%';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
[PrefKey.STREAM_COMBINE_SOURCES]: {
|
[PrefKey.STREAM_COMBINE_SOURCES]: {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
@ -320,6 +340,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
[PrefKey.LOCAL_CO_OP_ENABLED]: {
|
[PrefKey.LOCAL_CO_OP_ENABLED]: {
|
||||||
requiredVariants: 'full',
|
requiredVariants: 'full',
|
||||||
label: t('enable-local-co-op-support'),
|
label: t('enable-local-co-op-support'),
|
||||||
|
labelIcon: BxIcon.LOCAL_CO_OP,
|
||||||
default: false,
|
default: false,
|
||||||
note: () => CE('div', false,
|
note: () => CE('div', false,
|
||||||
CE('a', {
|
CE('a', {
|
||||||
@ -436,7 +457,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
if (!setting.unsupported) {
|
if (!setting.unsupported) {
|
||||||
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(true);
|
(setting as any).multipleOptions = GhPagesUtils.getNativeMkbCustomList(true);
|
||||||
|
|
||||||
BxEventBus.Script.on('list.forcedNativeMkb.updated', payload => {
|
BxEventBus.Script.once('list.forcedNativeMkb.updated', payload => {
|
||||||
(setting as any).multipleOptions = payload.data.data;
|
(setting as any).multipleOptions = payload.data.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -597,6 +618,7 @@ export class GlobalSettingsStorage extends BaseSettingsStorage {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
[PrefKey.BLOCK_FEATURES]: {
|
[PrefKey.BLOCK_FEATURES]: {
|
||||||
|
requiredVariants: 'full',
|
||||||
label: t('disable-features'),
|
label: t('disable-features'),
|
||||||
default: [],
|
default: [],
|
||||||
multipleOptions: {
|
multipleOptions: {
|
||||||
|
@ -6,7 +6,7 @@ import type { ControllerCustomizationConvertedPresetData, ControllerCustomizatio
|
|||||||
import { STATES } from "./global";
|
import { STATES } from "./global";
|
||||||
import { DeviceVibrationMode } from "@/enums/pref-values";
|
import { DeviceVibrationMode } from "@/enums/pref-values";
|
||||||
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
import { VIRTUAL_GAMEPAD_ID } from "@/modules/mkb/mkb-handler";
|
||||||
import { hasGamepad } from "./gamepad";
|
import { hasGamepad, toXcloudGamepadKey } from "./gamepad";
|
||||||
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
|
import { MkbMappingPresetsTable } from "./local-db/mkb-mapping-presets-table";
|
||||||
import { GamepadKey } from "@/enums/gamepad";
|
import { GamepadKey } from "@/enums/gamepad";
|
||||||
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
|
import { MkbPresetKey, MouseConstant } from "@/enums/mkb";
|
||||||
@ -51,32 +51,6 @@ export class StreamSettings {
|
|||||||
keyboardShortcuts: {},
|
keyboardShortcuts: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
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) {
|
static getPref<T extends keyof PrefTypeMap>(key: T) {
|
||||||
return getPref<T>(key);
|
return getPref<T>(key);
|
||||||
}
|
}
|
||||||
@ -146,14 +120,14 @@ export class StreamSettings {
|
|||||||
// Swap GamepadKey.A with "A"
|
// Swap GamepadKey.A with "A"
|
||||||
let gamepadKey: unknown;
|
let gamepadKey: unknown;
|
||||||
for (gamepadKey in customization.mapping) {
|
for (gamepadKey in customization.mapping) {
|
||||||
const gamepadStr = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[gamepadKey as GamepadKey];
|
const gamepadStr = toXcloudGamepadKey(gamepadKey as GamepadKey);
|
||||||
if (!gamepadStr) {
|
if (!gamepadStr) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mappedKey = customization.mapping[gamepadKey as GamepadKey];
|
const mappedKey = customization.mapping[gamepadKey as GamepadKey];
|
||||||
if (typeof mappedKey === 'number') {
|
if (typeof mappedKey === 'number') {
|
||||||
converted.mapping[gamepadStr] = StreamSettings.CONTROLLER_CUSTOMIZATION_MAPPING[mappedKey as GamepadKey];
|
converted.mapping[gamepadStr] = toXcloudGamepadKey(mappedKey as GamepadKey);
|
||||||
} else {
|
} else {
|
||||||
converted.mapping[gamepadStr] = false;
|
converted.mapping[gamepadStr] = false;
|
||||||
}
|
}
|
||||||
|
@ -151,6 +151,7 @@ const Texts = {
|
|||||||
"how-to-fix": "How to fix",
|
"how-to-fix": "How to fix",
|
||||||
"how-to-improve-app-performance": "How to improve app's performance",
|
"how-to-improve-app-performance": "How to improve app's performance",
|
||||||
"ignore": "Ignore",
|
"ignore": "Ignore",
|
||||||
|
"image-quality": "Website's image quality",
|
||||||
"import": "Import",
|
"import": "Import",
|
||||||
"in-game-controller-customization": "In-game controller customization",
|
"in-game-controller-customization": "In-game controller customization",
|
||||||
"in-game-controller-shortcuts": "In-game controller shortcuts",
|
"in-game-controller-shortcuts": "In-game controller shortcuts",
|
||||||
|
@ -123,7 +123,7 @@ export function productTitleToSlug(title: string): string {
|
|||||||
export function parseDetailsPath(path: string) {
|
export function parseDetailsPath(path: string) {
|
||||||
const matches = /\/games\/(?<titleSlug>[^\/]+)\/(?<productId>\w+)/.exec(path);
|
const matches = /\/games\/(?<titleSlug>[^\/]+)\/(?<productId>\w+)/.exec(path);
|
||||||
if (!matches?.groups) {
|
if (!matches?.groups) {
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const titleSlug = matches.groups.titleSlug!.replaceAll('\%' + '7C', '-');
|
const titleSlug = matches.groups.titleSlug!.replaceAll('\%' + '7C', '-');
|
||||||
|
@ -59,7 +59,9 @@ export class XcloudInterceptor {
|
|||||||
const obj = await response.clone().json();
|
const obj = await response.clone().json();
|
||||||
|
|
||||||
// Store xCloud token
|
// Store xCloud token
|
||||||
RemotePlayManager.getInstance()?.setXcloudToken(obj.gsToken);
|
if (isFullVersion()) {
|
||||||
|
RemotePlayManager.getInstance()?.setXcloudToken(obj.gsToken);
|
||||||
|
}
|
||||||
|
|
||||||
// Get server list
|
// Get server list
|
||||||
const serverRegex = /\/\/(\w+)\./;
|
const serverRegex = /\/\/(\w+)\./;
|
||||||
|
Reference in New Issue
Block a user