Compare commits
135 Commits
Author | SHA1 | Date | |
---|---|---|---|
f7266d6361 | |||
4bd96de89e | |||
4011eb402a | |||
557a38214d | |||
4648126f03 | |||
07b2e47757 | |||
cf4609d87b | |||
1ca2b771e7 | |||
fe98a1165f | |||
4777f90a53 | |||
1ea1afe4d4 | |||
fe696043f8 | |||
5b67c344de | |||
e1f8fcef41 | |||
f9e5ef1b35 | |||
9199351af1 | |||
c836e33f7b | |||
0f4195246b | |||
f3c61191fb | |||
361e494e11 | |||
8ab63e6e44 | |||
a1d6cf97e8 | |||
fcd6f041e6 | |||
2f280cf6e9 | |||
24c3588f1a | |||
330b7362ed | |||
5d177bd76c | |||
f18c5c14ed | |||
d0ceed00f8 | |||
fce8af4b3b | |||
57686f9d8e | |||
f0e7272a82 | |||
b0ecc7171b | |||
17c08792e1 | |||
e8376b52fe | |||
f6581abe34 | |||
b090d325ae | |||
ec3daa09fd | |||
b2a2e4d27e | |||
4f3430c43c | |||
15c6d3c74b | |||
b170b95145 | |||
4217b89194 | |||
38211168e9 | |||
392dc2cf86 | |||
67de264aa9 | |||
3e2c1bb2a4 | |||
5653914d19 | |||
4a8f66f2a1 | |||
70f43ba8f2 | |||
4d49639622 | |||
22f1ebdd08 | |||
bae51eff3d | |||
adc9897210 | |||
53442557e1 | |||
5b67b4c37d | |||
5a06933143 | |||
6440c91cdf | |||
b06dc6e219 | |||
540a50fb3a | |||
e5178830cb | |||
75549bc477 | |||
8a3d48d4a3 | |||
33c3b2810a | |||
95881dd241 | |||
c89ebb78a4 | |||
222ad1c34e | |||
6cfff0274d | |||
01502363ab | |||
9ab63c4a53 | |||
89a968d688 | |||
5e98c756d4 | |||
831fd98d02 | |||
de76364a46 | |||
075b15aa48 | |||
9388d7fbf4 | |||
2d8361ba73 | |||
79c7af10d4 | |||
6bd658e8a6 | |||
7e6b89b357 | |||
4271583a5a | |||
1b2cf70248 | |||
87447df7fd | |||
8664c1a60f | |||
602c31dc7f | |||
bbaea5f629 | |||
03efa528c8 | |||
63aaca7d61 | |||
15ae88e9e6 | |||
7578671cc3 | |||
82cfb11a6d | |||
15700e736d | |||
b27cfc8215 | |||
1e644504ec | |||
7206d11825 | |||
fa19a5a68e | |||
3f834f74b6 | |||
749d5d720e | |||
b969d52a3c | |||
e5bd7e64a7 | |||
82ee00b4ae | |||
8e88af5f8c | |||
927eae3f2f | |||
9f440e9cf4 | |||
1acb30e3af | |||
34159fad22 | |||
741538ebcf | |||
6d2e04aff1 | |||
f2bc98229f | |||
49fb8e2818 | |||
d012d96675 | |||
c129feaf2d | |||
4f7b23912d | |||
e4d73f9e36 | |||
2eea9ce8f5 | |||
27abab8473 | |||
0c34173815 | |||
0164423e45 | |||
71dcaf4b07 | |||
8f49c48e74 | |||
6fa1f73702 | |||
728abced45 | |||
411e43ceb0 | |||
baa22dbefc | |||
97fb7a114f | |||
39b2f814b6 | |||
3d34bb3edf | |||
ab1c93eb3a | |||
739adfce41 | |||
2e77f19006 | |||
8a40d361d9 | |||
98fa273b48 | |||
1e6527413c | |||
b9134bc141 | |||
336a965653 |
0
.github/FUNDING.yml
vendored
Normal file → Executable file
0
.github/ISSUE_TEMPLATE/01-bug-report.yml
vendored
Normal file → Executable file
0
.github/ISSUE_TEMPLATE/02-feature-request.yml
vendored
Normal file → Executable file
0
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file → Executable file
0
.gitignore
vendored
Normal file → Executable file
0
.vscode/settings.json
vendored
Normal file → Executable file
0
.vscode/tasks.json
vendored
Normal file → Executable file
88
build.ts
Normal file → Executable file
@ -20,6 +20,42 @@ enum BuildTarget {
|
||||
|
||||
type BuildVariant = 'full' | 'lite';
|
||||
|
||||
const MINIFY_SYNTAX = true;
|
||||
|
||||
function minifySvgImports(str: string): string {
|
||||
// Minify SVG imports
|
||||
const svgMap = {};
|
||||
str = str.replaceAll(/var ([\w_]+) = `(<svg.*?\n)`;\n\n/gsm, (match, p1, p2) => {
|
||||
// Remove new lines in SVG
|
||||
p2 = p2.replaceAll(/\n\s*/g, '');
|
||||
svgMap[p1] = '"' + p2.trim() + '"';
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
for (const name in svgMap) {
|
||||
str = str.replace(name + ',', svgMap[name] + ',');
|
||||
str = str.replace(name + '\n', svgMap[name] + '\n');
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function minifyCodeImports(str: string): string {
|
||||
str = str.replaceAll(/var ([\w_]+_default\d?) = `(.*?)`;/gsm, (match, p1, p2) => {
|
||||
// Remove new lines in SVG
|
||||
p2 = p2.replaceAll(/\n\s*/g, '\n');
|
||||
p2 = p2.replaceAll(/\n\/\/.*/g, '\n');
|
||||
p2 = p2.replaceAll(/^\/\/.*/g, '\n');
|
||||
p2 = p2.replaceAll(/\n+/g, '\n');
|
||||
p2 = p2.trim();
|
||||
|
||||
return `var ${p1} = \`${p2}\`;`;
|
||||
});
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const postProcess = (str: string): string => {
|
||||
// Unescape unicode charaters
|
||||
str = unescape((str.replace(/\\u/g, '%u')));
|
||||
@ -30,7 +66,7 @@ const postProcess = (str: string): string => {
|
||||
str = str.replaceAll('globalThis.', 'var ');
|
||||
|
||||
// Remove enum's inlining comments
|
||||
str = str.replaceAll(/ \/\* [A-Z0-9_]+ \*\//g, '');
|
||||
str = str.replaceAll(/ \/\* [A-Z0-9_:]+ \*\//g, '');
|
||||
str = str.replaceAll('/* @__PURE__ */ ', '');
|
||||
|
||||
// Remove comments from import
|
||||
@ -53,29 +89,43 @@ const postProcess = (str: string): string => {
|
||||
return p1 + ': ';
|
||||
});
|
||||
|
||||
// Minify SVG import code
|
||||
const svgMap = {}
|
||||
str = str.replaceAll(/var ([\w_]+) = ("<svg.*?");\n\n/g, function(match, p1, p2) {
|
||||
// Remove new lines in SVG
|
||||
p2 = p2.replaceAll(/\\n*\s*/g, '');
|
||||
|
||||
svgMap[p1] = p2;
|
||||
return '';
|
||||
});
|
||||
|
||||
for (const name in svgMap) {
|
||||
str = str.replace(`: ${name}`, `: ${svgMap[name]}`);
|
||||
}
|
||||
str = minifySvgImports(str);
|
||||
str = minifyCodeImports(str);
|
||||
|
||||
// Collapse empty brackets
|
||||
str = str.replaceAll(/\{[\s\n]+\}/g, '{}');
|
||||
|
||||
// Collapse if/else blocks without curly braces
|
||||
str = str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
|
||||
|
||||
// Remove blank lines
|
||||
str = str.replaceAll(/\n([\s]*)\n/g, "\n");
|
||||
|
||||
// Minify WebGL shaders & JS strings
|
||||
// Replace "\n " with "\n"
|
||||
str = str.replaceAll(/\\n+\s*/g, '\\n');
|
||||
// Remove comment line
|
||||
str = str.replaceAll(/\\n\/\/.*?(?=\\n)/g, '');
|
||||
|
||||
// Replace ${"time".toUpperCase()} with "TIME"
|
||||
str = str.replaceAll(/\$\{"([^"]+)"\.toUpperCase\(\)\}/g, (match, p1) => {
|
||||
return p1.toUpperCase();
|
||||
});
|
||||
|
||||
// Replace " (e) =>" to " e =>"
|
||||
// str = str.replaceAll(/ \(([^\s,.$()]+)\) =>/g, ' $1 =>');
|
||||
|
||||
// Set indent to 1 space
|
||||
if (MINIFY_SYNTAX) {
|
||||
// Collapse if/else blocks without curly braces
|
||||
str = str.replaceAll(/((if \(.*?\)|else)\n\s+)/g, '$2 ');
|
||||
|
||||
str = str.replaceAll(/\n(\s+)/g, (match, p1) => {
|
||||
const len = p1.length / 2;
|
||||
return '\n' + ' '.repeat(len);
|
||||
});
|
||||
}
|
||||
|
||||
// Fix unicode regex in Patcher.optimizeGameSlugGenerator
|
||||
str = str.replaceAll('^\\™', '^\\\\u2122');
|
||||
|
||||
assert(str.includes('/* ADDITIONAL CODE */'));
|
||||
assert(str.includes('window.BX_EXPOSED = BxExposed'));
|
||||
assert(str.includes('window.BxEvent = BxEvent'));
|
||||
@ -108,7 +158,7 @@ const build = async (target: BuildTarget, version: string, variant: BuildVariant
|
||||
outdir: outDir,
|
||||
naming: outputScriptName,
|
||||
minify: {
|
||||
syntax: true,
|
||||
syntax: MINIFY_SYNTAX,
|
||||
},
|
||||
define: {
|
||||
'Bun.env.BUILD_TARGET': JSON.stringify(target),
|
||||
@ -139,7 +189,7 @@ const build = async (target: BuildTarget, version: string, variant: BuildVariant
|
||||
await Bun.write(path, scriptHeader + result);
|
||||
|
||||
// Create meta file (don't build if it's beta version)
|
||||
if (!version.includes('beta')) {
|
||||
if (!version.includes('beta') && variant === 'full') {
|
||||
await Bun.write(outDir + '/' + outputMetaName, txtMetaHeader.replace('[[VERSION]]', version));
|
||||
}
|
||||
|
||||
|
5
dist/better-xcloud.lite.meta.js
vendored
@ -1,5 +0,0 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.8.0
|
||||
// ==/UserScript==
|
11502
dist/better-xcloud.lite.user.js
vendored
Normal file → Executable file
2
dist/better-xcloud.meta.js
vendored
@ -1,5 +1,5 @@
|
||||
// ==UserScript==
|
||||
// @name Better xCloud
|
||||
// @namespace https://github.com/redphx
|
||||
// @version 5.8.0
|
||||
// @version 6.0.1
|
||||
// ==/UserScript==
|
||||
|
16522
dist/better-xcloud.user.js
vendored
Normal file → Executable file
0
eslint.config.mjs
Normal file → Executable file
10
package.json
Normal file → Executable file
@ -10,14 +10,14 @@
|
||||
"build": "build.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.1.10",
|
||||
"@types/node": "^22.7.4",
|
||||
"@types/bun": "^1.1.14",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/stylus": "^0.48.43",
|
||||
"eslint": "^9.12.0",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-plugin-compat": "^6.0.1",
|
||||
"stylus": "^0.63.0"
|
||||
"stylus": "^0.64.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.6.2"
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
|
0
scripts/custom-flags.user.js
Normal file → Executable file
38
src/assets/css/button.styl
Normal file → Executable file
@ -67,6 +67,24 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.bx-warning {
|
||||
--button-rgb: var(--bx-warning-button-rgb);
|
||||
|
||||
&:not([disabled]):active {
|
||||
--button-active-rgb: var(--bx-warning-button-active-rgb);
|
||||
}
|
||||
|
||||
&:not([disabled]):not(:active) {
|
||||
&:hover, &.bx-focusable:focus {
|
||||
--button-hover-rgb: var(--bx-warning-button-hover-rgb);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
--button-disabled-rgb: var(--bx-warning-button-disabled-rgb);
|
||||
}
|
||||
}
|
||||
|
||||
&.bx-danger {
|
||||
--button-rgb: var(--bx-danger-button-rgb);
|
||||
|
||||
@ -107,6 +125,7 @@
|
||||
|
||||
&.bx-circular {
|
||||
border-radius: var(--bx-button-height);
|
||||
width: var(--bx-button-height);
|
||||
height: var(--bx-button-height);
|
||||
}
|
||||
|
||||
@ -130,6 +149,25 @@
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&.bx-button-multi-lines {
|
||||
height: auto;
|
||||
text-align: left;
|
||||
padding: 10px 0;
|
||||
|
||||
span {
|
||||
line-height: unset;
|
||||
display: block;
|
||||
|
||||
&:last-of-type {
|
||||
text-transform: none;
|
||||
font-weight: normal;
|
||||
font-family: "Segoe Sans Variable Text";
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-focusable {
|
||||
|
10
src/assets/css/game-bar.styl
Normal file → Executable file
@ -76,21 +76,21 @@
|
||||
}
|
||||
|
||||
/* Touch controller buttons */
|
||||
div[data-enabled] {
|
||||
div[data-activated] {
|
||||
button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show enabled button */
|
||||
div[data-enabled='true'] {
|
||||
/* Show default button */
|
||||
div[data-activated='false'] {
|
||||
button:first-of-type {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show enable button */
|
||||
div[data-enabled='false'] {
|
||||
/* Show activated button */
|
||||
div[data-activated='true'] {
|
||||
button:last-of-type {
|
||||
display: block;
|
||||
}
|
||||
|
0
src/assets/css/guide-menu.styl
Normal file → Executable file
0
src/assets/css/header.styl
Normal file → Executable file
50
src/assets/css/dialog.styl → src/assets/css/key-binding-dialog.styl
Normal file → Executable file
@ -1,12 +1,12 @@
|
||||
.bx-dialog-overlay {
|
||||
.bx-key-binding-dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: var(--bx-dialog-overlay-z-index);
|
||||
z-index: var(--bx-key-binding-dialog-overlay-z-index);
|
||||
background: black;
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.bx-dialog {
|
||||
.bx-key-binding-dialog {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 90vh;
|
||||
@ -18,7 +18,7 @@
|
||||
min-width: 420px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
z-index: var(--bx-dialog-z-index);
|
||||
z-index: var(--bx-key-binding-dialog-z-index);
|
||||
background: #1a1b1e;
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
@ -33,26 +33,13 @@
|
||||
}
|
||||
|
||||
h2 {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
|
||||
b {
|
||||
flex: 1;
|
||||
color: #fff;
|
||||
display: block;
|
||||
font-family: var(--bx-title-font);
|
||||
font-size: 26px;
|
||||
font-weight: 400;
|
||||
line-height: var(--bx-button-height);
|
||||
}
|
||||
}
|
||||
|
||||
&.bx-binding-dialog {
|
||||
h2 {
|
||||
b {
|
||||
font-family: var(--bx-promptfont-font) !important;
|
||||
}
|
||||
}
|
||||
color: #fff;
|
||||
display: block;
|
||||
font-family: var(--bx-title-font);
|
||||
font-size: 32px;
|
||||
font-weight: 400;
|
||||
line-height: var(--bx-button-height);
|
||||
}
|
||||
|
||||
> div {
|
||||
@ -85,11 +72,26 @@
|
||||
background-color: #515863;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
li {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[data-flags*="[1]"] > li[data-flag="1"],
|
||||
&[data-flags*="[2]"] > li[data-flag="2"],
|
||||
&[data-flags*="[4]"] > li[data-flag="4"],
|
||||
&[data-flags*="[8]"] > li[data-flag="8"] {
|
||||
display: list-item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 450px) {
|
||||
.bx-dialog {
|
||||
.bx-key-binding-dialog {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
0
src/assets/css/loading-screen.styl
Normal file → Executable file
0
src/assets/css/misc.styl
Normal file → Executable file
174
src/assets/css/mkb.styl
Normal file → Executable file
@ -4,15 +4,6 @@
|
||||
flex: 1;
|
||||
padding-bottom: 10px;
|
||||
overflow: hidden;
|
||||
|
||||
select:disabled {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
text-align-last: right;
|
||||
text-align: right;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-mkb-pointer-lock-msg {
|
||||
@ -20,13 +11,12 @@
|
||||
-webkit-user-select: none;
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
bottom: 40px;
|
||||
transform: translateX(-50%);
|
||||
margin: auto;
|
||||
background: #151515;
|
||||
z-index: var(--bx-mkb-pointer-lock-msg-z-index);
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-family: "Segoe UI", Arial, Helvetica, sans-serif;
|
||||
font-size: 1.3rem;
|
||||
@ -34,117 +24,55 @@
|
||||
border-radius: 8px;
|
||||
align-items: center;
|
||||
box-shadow: 0 0 6px #000;
|
||||
min-width: 220px;
|
||||
min-width: 300px;
|
||||
opacity: 0.9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> div:first-of-type {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> p {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 22px;
|
||||
margin-bottom: 4px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
> div {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&:first-child {
|
||||
font-size: 22px;
|
||||
margin-bottom: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
> div:last-of-type {
|
||||
margin-top: 10px;
|
||||
|
||||
&[data-type='native'] {
|
||||
button {
|
||||
&:first-of-type {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
gap: 10px;
|
||||
button {
|
||||
&:first-of-type {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-type='virtual'] {
|
||||
div {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
margin-top: 8px;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
|
||||
&:first-of-type {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
&:last-of-type {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-mkb-preset-tools {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.bx-mkb-settings-rows {
|
||||
flex: 1;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
.bx-mkb-key-row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
font-family: var(--bx-promptfont-font);
|
||||
font-size: 26px;
|
||||
font-size: 32px;
|
||||
text-align: center;
|
||||
width: 26px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
margin: 0 0 0 10px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: white;
|
||||
border-radius: 0;
|
||||
border-left: 1px solid #373737;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -181,10 +109,58 @@
|
||||
|
||||
.bx-mkb-note {
|
||||
display: block;
|
||||
margin: 16px 0 10px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&:first-of-type {
|
||||
margin-top: 0;
|
||||
button.bx-binding-button {
|
||||
flex: 1;
|
||||
min-height: 38px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
padding: 0 6px;
|
||||
|
||||
&:disabled {
|
||||
background: #131416;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
&:not(:disabled) {
|
||||
border: 2px solid transparent;
|
||||
border-top: none;
|
||||
border-bottom: 4px solid #252525;
|
||||
background: #3b3b3b;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &.bx-focusable:focus {
|
||||
background: #20b217;
|
||||
border-bottom-color: #186c13;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background: #16900f;
|
||||
border-bottom: 3px solid #0c4e08;
|
||||
border-left-width: 2px;
|
||||
border-right-width: 2px;
|
||||
}
|
||||
|
||||
&.bx-focusable:focus {
|
||||
&::after {
|
||||
top: -6px;
|
||||
left: -8px;
|
||||
right: -8px;
|
||||
bottom: -10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-settings-row .bx-binding-button-wrapper & {
|
||||
min-width: 60px;
|
||||
}
|
||||
}
|
||||
|
195
src/assets/css/navigation-dialog.styl
Normal file → Executable file
@ -6,6 +6,17 @@
|
||||
*:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
-webkit-appearance: none;
|
||||
text-align-last: right;
|
||||
text-align: right;
|
||||
color: #fff;
|
||||
background: #131416;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-navigation-dialog-overlay {
|
||||
@ -21,3 +32,187 @@
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-centered-dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
color: white;
|
||||
background: #1a1b1e;
|
||||
border-radius: 10px;
|
||||
width: 450px;
|
||||
max-width: calc(100vw - 20px);
|
||||
margin: 0 0 0 auto;
|
||||
padding: 20px;
|
||||
|
||||
max-height: 95vh;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.bx-dialog-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
p {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
flex: 1;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-dialog-content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
|
||||
> div {
|
||||
}
|
||||
}
|
||||
|
||||
.bx-dialog-preset-tools {
|
||||
display: flex;
|
||||
margin-bottom: 12px;
|
||||
gap: 6px;
|
||||
|
||||
select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-centered-dialog,
|
||||
.bx-settings-dialog {
|
||||
input {
|
||||
accent-color: var(--bx-primary-button-color);
|
||||
|
||||
&:focus {
|
||||
accent-color: var(--bx-danger-button-color);
|
||||
}
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
text-align-last: right;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
select option:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type=checkbox],
|
||||
select {
|
||||
&:focus {
|
||||
filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1c9d1c;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #5dc21e;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-controller-shortcuts-manager-container {
|
||||
.bx-shortcut-note {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bx-shortcut-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
align-items: center;
|
||||
|
||||
label.bx-prompt {
|
||||
flex-shrink: 0;
|
||||
font-size: 32px;
|
||||
margin: 0;
|
||||
|
||||
&::first-letter {
|
||||
letter-spacing: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-shortcut-actions {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 38px;
|
||||
display: block;
|
||||
|
||||
&:first-of-type {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&:last-of-type {
|
||||
opacity: 0;
|
||||
z-index: calc(var(--bx-settings-z-index) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
text-align: left;
|
||||
text-align-last: left;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-keyboard-shortcuts-manager-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
fieldset {
|
||||
background: #2a2a2a;
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
legend {
|
||||
width: auto;
|
||||
padding: 4px 8px;
|
||||
margin: 0 4px 4px;
|
||||
background: #004f87;
|
||||
box-shadow: 0px 2px 0px #071e3d;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.bx-settings-row {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
70
src/assets/css/number-stepper.styl
Normal file → Executable file
@ -1,46 +1,54 @@
|
||||
.bx-number-stepper {
|
||||
text-align: center;
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
min-width: 40px;
|
||||
font-family: var(--bx-monospaced-font);
|
||||
font-size: 12px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
line-height: 24px;
|
||||
background-color: var(--bx-default-button-color);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
font-family: var(--bx-monospaced-font);
|
||||
span {
|
||||
flex: 1;
|
||||
display: inline-block;
|
||||
min-width: 40px;
|
||||
font-family: var(--bx-monospaced-font);
|
||||
font-size: 13px;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
button {
|
||||
flex-shrink: 0;
|
||||
border: none;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
line-height: 24px;
|
||||
background-color: var(--bx-default-button-color);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
font-family: var(--bx-monospaced-font);
|
||||
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--bx-default-button-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--bx-default-button-hover-color);
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--bx-default-button-hover-color);
|
||||
}
|
||||
|
||||
&:disabled + span {
|
||||
font-family: var(--bx-title-font);
|
||||
&:disabled + span {
|
||||
font-family: var(--bx-title-font);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
display: block;
|
||||
margin: 12px auto 2px;
|
||||
width: 180px;
|
||||
margin: 8px 0 2px auto;
|
||||
min-width: 180px;
|
||||
width: 100%;
|
||||
color: #959595 !important;
|
||||
}
|
||||
|
||||
@ -48,7 +56,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
&[data-disabled=true] {
|
||||
&[data-disabled=true], &[disabled=true] {
|
||||
input[type=range], button {
|
||||
display: none;
|
||||
}
|
||||
|
0
src/assets/css/remote-play.styl
Normal file → Executable file
36
src/assets/css/root.styl
Normal file → Executable file
@ -24,22 +24,24 @@ button_color(name, normal, hover, active, disabled)
|
||||
|
||||
button_color('default', #2d3036, #515863, #222428, #8e8e8e);
|
||||
button_color('primary', #008746, #04b358, #044e2a, #448262);
|
||||
button_color('warning', #c16e04, #fa9005, #965603, #a2816c);
|
||||
button_color('danger', #c10404, #e61d1d, #a26c6c, #df5656);
|
||||
|
||||
--bx-fullscreen-text-z-index: 99999;
|
||||
--bx-toast-z-index: 60000;
|
||||
--bx-dialog-z-index: 50000;
|
||||
--bx-fullscreen-text-z-index: 9999;
|
||||
--bx-toast-z-index: 6000;
|
||||
--bx-key-binding-dialog-z-index: 5010;
|
||||
--bx-key-binding-dialog-overlay-z-index: 5000;
|
||||
|
||||
--bx-dialog-overlay-z-index: 40200;
|
||||
--bx-stats-bar-z-index: 40100;
|
||||
--bx-mkb-pointer-lock-msg-z-index: 40000;
|
||||
--bx-stats-bar-z-index: 4010;
|
||||
|
||||
--bx-navigation-dialog-z-index: 30100;
|
||||
--bx-navigation-dialog-overlay-z-index: 30000;
|
||||
--bx-navigation-dialog-z-index: 3010;
|
||||
--bx-navigation-dialog-overlay-z-index: 3000;
|
||||
|
||||
--bx-game-bar-z-index: 10000;
|
||||
--bx-screenshot-animation-z-index: 9000;
|
||||
--bx-wait-time-box-z-index: 1000;
|
||||
--bx-mkb-pointer-lock-msg-z-index: 2000;
|
||||
|
||||
--bx-game-bar-z-index: 1000;
|
||||
--bx-screenshot-animation-z-index: 200;
|
||||
--bx-wait-time-box-z-index: 100;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
@ -120,7 +122,7 @@ div[class^=HUDButton-module__hiddenContainer] ~ div:not([class^=HUDButton-module
|
||||
}
|
||||
|
||||
.bx-prompt {
|
||||
font-family: var(--bx-promptfont-font);
|
||||
font-family: var(--bx-promptfont-font) !important;
|
||||
}
|
||||
|
||||
.bx-line-through {
|
||||
@ -226,3 +228,13 @@ div[class*=SupportedInputsBadge] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-blink-me {
|
||||
animation: bx-blinker 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes bx-blinker {
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
193
src/assets/css/settings-dialog.styl
Normal file → Executable file
@ -31,42 +31,6 @@
|
||||
font-weight: normal;
|
||||
height: var(--bx-button-height);
|
||||
}
|
||||
|
||||
input {
|
||||
accent-color: var(--bx-primary-button-color);
|
||||
|
||||
&:focus {
|
||||
accent-color: var(--bx-danger-button-color);
|
||||
}
|
||||
}
|
||||
|
||||
select:disabled {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
text-align-last: right;
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
select option:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input[type=checkbox],
|
||||
select {
|
||||
&:focus {
|
||||
filter: drop-shadow(1px 0 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 1px 0 #fff) drop-shadow(0 -1px 0 #fff);
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1c9d1c;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #5dc21e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-settings-tabs-container {
|
||||
@ -170,69 +134,6 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> div[data-tab-group=shortcuts] {
|
||||
> div {
|
||||
&[data-has-gamepad=true] {
|
||||
> div:first-of-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> div:last-of-type {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-has-gamepad=false] {
|
||||
> div:first-of-type {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> div:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-shortcut-profile {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.bx-shortcut-note {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.bx-shortcut-row {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
label.bx-prompt {
|
||||
flex: 1;
|
||||
font-size: 26px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bx-shortcut-actions {
|
||||
flex: 2;
|
||||
position: relative;
|
||||
|
||||
select {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
|
||||
&:last-of-type {
|
||||
opacity: 0;
|
||||
z-index: calc(var(--bx-settings-z-index) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-top-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -262,6 +163,8 @@
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
min-height: var(--bx-button-height);
|
||||
align-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,6 +209,18 @@
|
||||
margin: 0 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-multi-lines="true"] {
|
||||
flex-direction: column;
|
||||
|
||||
> span.bx-settings-label {
|
||||
align-self: start;
|
||||
|
||||
+ * {
|
||||
margin: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-settings-dialog-note {
|
||||
@ -339,6 +254,7 @@
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.bx-debug-info {
|
||||
@ -378,24 +294,26 @@
|
||||
}
|
||||
|
||||
.bx-settings-tab-contents {
|
||||
border-radius-size = 6px;
|
||||
|
||||
> div {
|
||||
// Label at the beginning
|
||||
*:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:has(+ .bx-settings-row) {
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
border-top-left-radius: border-radius-size;
|
||||
border-top-right-radius: border-radius-size;
|
||||
}
|
||||
|
||||
// Label at the end
|
||||
.bx-settings-row:not(:has(+ .bx-settings-row)) {
|
||||
border: none;
|
||||
border-bottom-left-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-bottom-left-radius: border-radius-size;
|
||||
border-bottom-right-radius: border-radius-size;
|
||||
}
|
||||
|
||||
// Single label
|
||||
*:not(.bx-settings-row):has(+ .bx-settings-row) + .bx-settings-row:not(:has(+ .bx-settings-row)) {
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
border-radius: border-radius-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,7 +327,6 @@
|
||||
|
||||
label {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
padding: 10px;
|
||||
background: #004f87;
|
||||
}
|
||||
@ -444,10 +361,6 @@
|
||||
|
||||
.bx-suggest-box {
|
||||
display: none;
|
||||
background: #161616;
|
||||
padding: 10px;
|
||||
box-shadow: 0px 0px 12px #0f0f0f inset;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.bx-suggest-wrapper {
|
||||
@ -563,3 +476,65 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-sub-content-box {
|
||||
background: #161616;
|
||||
padding: 10px;
|
||||
box-shadow: 0px 0px 12px #0f0f0f inset;
|
||||
border-radius: 10px;
|
||||
|
||||
.bx-settings-row & {
|
||||
background: #202020;
|
||||
padding: 12px;
|
||||
box-shadow: 0 0 4px #000000 inset;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-controller-extra-settings {
|
||||
&[data-has-gamepad=true] {
|
||||
> :first-child {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-has-gamepad=false] {
|
||||
> :first-child {
|
||||
display: block;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bx-controller-extra-wrapper {
|
||||
flex: 1;
|
||||
min-width: 1px;
|
||||
}
|
||||
|
||||
.bx-sub-content-box {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 10px;
|
||||
|
||||
> label {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-preset-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
|
||||
.bx-select {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
2
src/assets/css/stream-stats.styl
Normal file → Executable file
@ -91,6 +91,7 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
||||
&[data-stats*="[batt]"] > .bx-stat-batt,
|
||||
&[data-stats*="[fps]"] > .bx-stat-fps,
|
||||
&[data-stats*="[ping]"] > .bx-stat-ping,
|
||||
&[data-stats*="[jit]"] > .bx-stat-jit,
|
||||
&[data-stats*="[btr]"] > .bx-stat-btr,
|
||||
&[data-stats*="[dt]"] > .bx-stat-dt,
|
||||
&[data-stats*="[pl]"] > .bx-stat-pl,
|
||||
@ -106,6 +107,7 @@ div[class^=StreamMenu-module__container] .bx-badges {
|
||||
&[data-stats$="[batt]"] > .bx-stat-batt,
|
||||
&[data-stats$="[fps]"] > .bx-stat-fps,
|
||||
&[data-stats$="[ping]"] > .bx-stat-ping,
|
||||
&[data-stats$="[jit]"] > .bx-stat-jit,
|
||||
&[data-stats$="[btr]"] > .bx-stat-btr,
|
||||
&[data-stats$="[dt]"] > .bx-stat-dt,
|
||||
&[data-stats$="[pl]"] > .bx-stat-pl,
|
||||
|
51
src/assets/css/stream.styl
Normal file → Executable file
@ -47,7 +47,41 @@ body[data-media-type=tv] .bx-stream-home-button {
|
||||
}
|
||||
|
||||
div[data-testid=media-container] {
|
||||
display: flex;
|
||||
&[data-position=center] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&[data-position=top] {
|
||||
video, canvas {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-position=bottom] {
|
||||
video, canvas {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#game-stream {
|
||||
video {
|
||||
margin: auto;
|
||||
align-self: center;
|
||||
background: #000;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
canvas {
|
||||
align-self: center;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.bx-taking-screenshot:before {
|
||||
animation: bx-anim-taking-screenshot 0.5s ease;
|
||||
@ -59,21 +93,6 @@ div[data-testid=media-container] {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#game-stream video {
|
||||
margin: auto;
|
||||
align-self: center;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
#game-stream canvas {
|
||||
position: absolute;
|
||||
align-self: center;
|
||||
margin: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#gamepass-dialog-root div[class^=Guide-module__guide] {
|
||||
.bx-button {
|
||||
overflow: visible;
|
||||
|
2
src/assets/css/styles.styl
Normal file → Executable file
@ -2,7 +2,7 @@
|
||||
|
||||
@import 'button.styl';
|
||||
@import 'header.styl';
|
||||
@import 'dialog.styl';
|
||||
@import 'key-binding-dialog.styl';
|
||||
@import 'navigation-dialog.styl';
|
||||
@import 'settings-dialog.styl';
|
||||
@import 'toast.styl';
|
||||
|
0
src/assets/css/toast.styl
Normal file → Executable file
66
src/assets/css/web-components.styl
Normal file → Executable file
@ -1,7 +1,12 @@
|
||||
.bx-select {
|
||||
select.bx-select {
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
div.bx-select {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 1 auto;
|
||||
gap: 8px;
|
||||
|
||||
select {
|
||||
// Render offscreen instead of "display: none" so we could get its size
|
||||
@ -9,23 +14,41 @@
|
||||
top: -9999px !important;
|
||||
left: -9999px !important;
|
||||
visibility: hidden !important;
|
||||
|
||||
&:disabled {
|
||||
& ~ button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
& ~ div {
|
||||
background: #131416;
|
||||
color: white;
|
||||
pointer-events: none;
|
||||
|
||||
.bx-select-indicators {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> div, button.bx-select-value {
|
||||
min-width: 120px;
|
||||
text-align: left;
|
||||
margin: 0 8px;
|
||||
line-height: 24px;
|
||||
vertical-align: middle;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
min-height: 24px;
|
||||
box-sizing: content-box;
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
@ -36,6 +59,9 @@
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
@ -43,18 +69,23 @@
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
line-height: initial;
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.bx-select-value {
|
||||
border: none;
|
||||
display: inline-flex;
|
||||
cursor: pointer;
|
||||
min-height: 30px;
|
||||
font-size: 0.9rem;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
@ -97,3 +128,30 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bx-select-indicators {
|
||||
display: flex;
|
||||
height: 4px;
|
||||
gap: 2px;
|
||||
margin-bottom: 2px;
|
||||
|
||||
span {
|
||||
content: ' ';
|
||||
display: inline-block;
|
||||
flex: 1;
|
||||
background: #cfcfcf;
|
||||
border-radius: 4px;
|
||||
|
||||
&[data-highlighted] {
|
||||
background: #9c9c9c;
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
background: #aacfe7;
|
||||
}
|
||||
|
||||
&[data-highlighted][data-selected] {
|
||||
background: #5fa3d0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
0
src/assets/header_meta.txt
Normal file → Executable file
0
src/assets/header_script.lite.txt
Normal file → Executable file
0
src/assets/header_script.txt
Normal file → Executable file
0
src/assets/svg/battery-full.svg
Normal file → Executable file
Before Width: | Height: | Size: 821 B After Width: | Height: | Size: 821 B |
0
src/assets/svg/better-xcloud.svg
Normal file → Executable file
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
0
src/assets/svg/camera.svg
Normal file → Executable file
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
0
src/assets/svg/caret-left.svg
Normal file → Executable file
Before Width: | Height: | Size: 386 B After Width: | Height: | Size: 386 B |
0
src/assets/svg/caret-right.svg
Normal file → Executable file
Before Width: | Height: | Size: 385 B After Width: | Height: | Size: 385 B |
0
src/assets/svg/clock.svg
Normal file → Executable file
Before Width: | Height: | Size: 374 B After Width: | Height: | Size: 374 B |
0
src/assets/svg/close.svg
Normal file → Executable file
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 264 B |
0
src/assets/svg/cloud.svg
Normal file → Executable file
Before Width: | Height: | Size: 427 B After Width: | Height: | Size: 427 B |
0
src/assets/svg/command.svg
Normal file → Executable file
Before Width: | Height: | Size: 667 B After Width: | Height: | Size: 667 B |
0
src/assets/svg/controller.svg
Normal file → Executable file
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 646 B |
0
src/assets/svg/copy.svg
Normal file → Executable file
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 250 B |
0
src/assets/svg/create-shortcut.svg
Normal file → Executable file
Before Width: | Height: | Size: 711 B After Width: | Height: | Size: 711 B |
0
src/assets/svg/cursor-text.svg
Normal file → Executable file
Before Width: | Height: | Size: 370 B After Width: | Height: | Size: 370 B |
0
src/assets/svg/display.svg
Normal file → Executable file
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 382 B |
0
src/assets/svg/download.svg
Normal file → Executable file
Before Width: | Height: | Size: 355 B After Width: | Height: | Size: 355 B |
8
src/assets/svg/eye-slash.svg
Executable file
@ -0,0 +1,8 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' fill='#fff' stroke='none ' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||
<clipPath id='A'>
|
||||
<path d='M0 0h32v32H0z'/>
|
||||
</clipPath>
|
||||
<g clip-path='url(#A)'>
|
||||
<path d='M6.123 3.549a1.07 1.07 0 0 0-.798-.359c-.585 0-1.067.482-1.067 1.067 0 .27.102.53.286.727l2.565 2.823C2.267 10.779.184 15.36.092 15.568c-.123.276-.123.591 0 .867.047.105 1.176 2.609 3.687 5.12 3.345 3.344 7.57 5.112 12.221 5.112a16.97 16.97 0 0 0 6.943-1.444l2.933 3.228c.202.228.493.359.798.359.585 0 1.067-.482 1.067-1.067a1.07 1.07 0 0 0-.286-.727L6.123 3.549zm6.31 10.112l5.556 6.114c-.612.322-1.294.49-1.986.49a4.29 4.29 0 0 1-4.267-4.266c0-.831.242-1.643.697-2.338zM16 24.533c-4.104 0-7.689-1.492-10.657-4.433A17.73 17.73 0 0 1 2.267 16c.625-1.172 2.621-4.452 6.313-6.584l2.4 2.633c-.878 1.125-1.356 2.512-1.356 3.939 0 3.511 2.89 6.4 6.4 6.4 1.221 0 2.416-.349 3.444-1.005l1.964 2.16a14.92 14.92 0 0 1-5.432.99zm.8-12.724a1.07 1.07 0 0 1-.867-1.048c0-.585.482-1.067 1.067-1.067a1.12 1.12 0 0 1 .2.019c2.784.54 4.896 2.863 5.169 5.686a1.07 1.07 0 0 1-.962 1.161c-.034.002-.067.002-.1 0a1.07 1.07 0 0 1-1.067-.968 4.29 4.29 0 0 0-3.44-3.783zm15.104 4.626c-.056.125-1.407 3.116-4.448 5.84a1.07 1.07 0 0 1-.724.283c-.585 0-1.067-.482-1.067-1.067a1.07 1.07 0 0 1 .368-.806A17.7 17.7 0 0 0 29.74 16a17.73 17.73 0 0 0-3.083-4.103C23.689 8.959 20.104 7.467 16 7.467a15.82 15.82 0 0 0-2.581.209 1.06 1.06 0 0 1-.186.016 1.07 1.07 0 0 1-1.067-1.066 1.07 1.07 0 0 1 .901-1.054A17.89 17.89 0 0 1 16 5.333c4.651 0 8.876 1.768 12.221 5.114 2.511 2.51 3.64 5.016 3.687 5.121.123.276.123.591 0 .867h-.004z' fill-rule='nonzero'/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
8
src/assets/svg/eye.svg
Executable file
@ -0,0 +1,8 @@
|
||||
<svg xmlns='http://www.w3.org/2000/svg' fill='#fff' stroke='none ' fill-rule='evenodd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 32 32'>
|
||||
<clipPath id='A'>
|
||||
<path d='M0 0h32v32H0z'/>
|
||||
</clipPath>
|
||||
<g clip-path='url(#A)'>
|
||||
<path d='M31.908 15.568c-.047-.105-1.176-2.611-3.687-5.121C24.876 7.101 20.651 5.333 16 5.333S7.124 7.101 3.779 10.447c-2.511 2.51-3.646 5.02-3.687 5.121-.123.276-.123.591 0 .867.047.105 1.176 2.609 3.687 5.12 3.345 3.344 7.57 5.112 12.221 5.112s8.876-1.768 12.221-5.112c2.511-2.511 3.64-5.015 3.687-5.12.123-.276.123-.591 0-.867zM16 24.533c-4.104 0-7.689-1.492-10.657-4.433-1.218-1.211-2.254-2.592-3.076-4.1.822-1.508 1.858-2.889 3.076-4.1C8.311 8.959 11.896 7.467 16 7.467s7.689 1.492 10.657 4.433c1.221 1.211 2.259 2.592 3.083 4.1-.961 1.795-5.149 8.533-13.74 8.533zM16 9.6c-3.511 0-6.4 2.889-6.4 6.4s2.889 6.4 6.4 6.4 6.4-2.889 6.4-6.4A6.44 6.44 0 0 0 16 9.6zm0 10.667A4.29 4.29 0 0 1 11.733 16 4.29 4.29 0 0 1 16 11.733 4.29 4.29 0 0 1 20.267 16 4.29 4.29 0 0 1 16 20.267z' fill-rule='nonzero'/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
0
src/assets/svg/home.svg
Normal file → Executable file
Before Width: | Height: | Size: 358 B After Width: | Height: | Size: 358 B |
0
src/assets/svg/microphone-slash.svg
Normal file → Executable file
Before Width: | Height: | Size: 551 B After Width: | Height: | Size: 551 B |
0
src/assets/svg/microphone.svg
Normal file → Executable file
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 411 B |
0
src/assets/svg/mouse-settings.svg
Normal file → Executable file
Before Width: | Height: | Size: 981 B After Width: | Height: | Size: 981 B |
0
src/assets/svg/mouse.svg
Normal file → Executable file
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
0
src/assets/svg/native-mkb.svg
Normal file → Executable file
Before Width: | Height: | Size: 619 B After Width: | Height: | Size: 619 B |
0
src/assets/svg/new.svg
Normal file → Executable file
Before Width: | Height: | Size: 411 B After Width: | Height: | Size: 411 B |
0
src/assets/svg/power.svg
Normal file → Executable file
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
0
src/assets/svg/question.svg
Normal file → Executable file
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
0
src/assets/svg/refresh.svg
Normal file → Executable file
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
0
src/assets/svg/remote-play.svg
Normal file → Executable file
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
0
src/assets/svg/speaker-high.svg
Normal file → Executable file
Before Width: | Height: | Size: 410 B After Width: | Height: | Size: 410 B |
0
src/assets/svg/speaker-slash.svg
Normal file → Executable file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
0
src/assets/svg/stream-settings.svg
Normal file → Executable file
Before Width: | Height: | Size: 768 B After Width: | Height: | Size: 768 B |
0
src/assets/svg/stream-stats.svg
Normal file → Executable file
Before Width: | Height: | Size: 430 B After Width: | Height: | Size: 430 B |
0
src/assets/svg/touch-control-disable.svg
Normal file → Executable file
Before Width: | Height: | Size: 915 B After Width: | Height: | Size: 915 B |
0
src/assets/svg/touch-control-enable.svg
Normal file → Executable file
Before Width: | Height: | Size: 796 B After Width: | Height: | Size: 796 B |
0
src/assets/svg/trash.svg
Normal file → Executable file
Before Width: | Height: | Size: 433 B After Width: | Height: | Size: 433 B |
0
src/assets/svg/true-achievements.svg
Normal file → Executable file
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
0
src/assets/svg/upload.svg
Normal file → Executable file
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
0
src/assets/svg/virtual-controller.svg
Normal file → Executable file
Before Width: | Height: | Size: 680 B After Width: | Height: | Size: 680 B |
0
src/build-config.ts
Normal file → Executable file
24
src/enums/bypass-servers.ts
Normal file → Executable file
@ -1,17 +1,17 @@
|
||||
import { t } from "@/utils/translation"
|
||||
|
||||
export const BypassServers = {
|
||||
'br': t('brazil'),
|
||||
'jp': t('japan'),
|
||||
'kr': t('korea'),
|
||||
'pl': t('poland'),
|
||||
'us': t('united-states'),
|
||||
}
|
||||
br: t('brazil'),
|
||||
jp: t('japan'),
|
||||
kr: t('korea'),
|
||||
pl: t('poland'),
|
||||
us: t('united-states'),
|
||||
} as const;
|
||||
|
||||
export const BypassServerIps: Record<keyof typeof BypassServers, string> = {
|
||||
'br': '169.150.198.66',
|
||||
'kr': '121.125.60.151',
|
||||
'jp': '138.199.21.239',
|
||||
'pl': '45.134.212.66',
|
||||
'us': '143.244.47.65',
|
||||
}
|
||||
br: '169.150.198.66',
|
||||
kr: '121.125.60.151',
|
||||
jp: '138.199.21.239',
|
||||
pl: '45.134.212.66',
|
||||
us: '143.244.47.65',
|
||||
} as const;
|
||||
|
2
src/enums/game-pass-gallery.ts
Normal file → Executable file
@ -1,5 +1,5 @@
|
||||
export enum GamePassCloudGallery {
|
||||
ALL = '29a81209-df6f-41fd-a528-2ae6b91f719c',
|
||||
ALL = 'ce573635-7c18-4d0c-9d68-90b932393470',
|
||||
MOST_POPULAR = 'e7590b22-e299-44db-ae22-25c61405454c',
|
||||
NATIVE_MKB = '8fa264dd-124f-4af3-97e8-596fcdf4b486',
|
||||
TOUCH = '9c86f07a-f3e8-45ad-82a0-a1f759597059',
|
||||
|
71
src/enums/gamepad.ts
Executable file
@ -0,0 +1,71 @@
|
||||
import { PrompFont } from "./prompt-font";
|
||||
|
||||
export enum GamepadKey {
|
||||
A = 0,
|
||||
B = 1,
|
||||
X = 2,
|
||||
Y = 3,
|
||||
LB = 4,
|
||||
RB = 5,
|
||||
LT = 6,
|
||||
RT = 7,
|
||||
SELECT = 8,
|
||||
START = 9,
|
||||
L3 = 10,
|
||||
R3 = 11,
|
||||
UP = 12,
|
||||
DOWN = 13,
|
||||
LEFT = 14,
|
||||
RIGHT = 15,
|
||||
HOME = 16,
|
||||
SHARE = 17,
|
||||
|
||||
LS_UP = 100,
|
||||
LS_DOWN = 101,
|
||||
LS_LEFT = 102,
|
||||
LS_RIGHT = 103,
|
||||
|
||||
RS_UP = 200,
|
||||
RS_DOWN = 201,
|
||||
RS_LEFT = 202,
|
||||
RS_RIGHT = 203,
|
||||
};
|
||||
|
||||
export const GamepadKeyName: Record<number, [string, PrompFont]> = {
|
||||
[GamepadKey.A]: ['A', PrompFont.A],
|
||||
[GamepadKey.B]: ['B', PrompFont.B],
|
||||
[GamepadKey.X]: ['X', PrompFont.X],
|
||||
[GamepadKey.Y]: ['Y', PrompFont.Y],
|
||||
|
||||
[GamepadKey.LB]: ['LB', PrompFont.LB],
|
||||
[GamepadKey.RB]: ['RB', PrompFont.RB],
|
||||
[GamepadKey.LT]: ['LT', PrompFont.LT],
|
||||
[GamepadKey.RT]: ['RT', PrompFont.RT],
|
||||
|
||||
[GamepadKey.SELECT]: ['Select', PrompFont.SELECT],
|
||||
[GamepadKey.START]: ['Start', PrompFont.START],
|
||||
[GamepadKey.HOME]: ['Home', PrompFont.HOME],
|
||||
|
||||
[GamepadKey.UP]: ['D-Pad Up', PrompFont.UP],
|
||||
[GamepadKey.DOWN]: ['D-Pad Down', PrompFont.DOWN],
|
||||
[GamepadKey.LEFT]: ['D-Pad Left', PrompFont.LEFT],
|
||||
[GamepadKey.RIGHT]: ['D-Pad Right', PrompFont.RIGHT],
|
||||
|
||||
[GamepadKey.L3]: ['L3', PrompFont.L3],
|
||||
[GamepadKey.LS_UP]: ['Left Stick Up', PrompFont.LS_UP],
|
||||
[GamepadKey.LS_DOWN]: ['Left Stick Down', PrompFont.LS_DOWN],
|
||||
[GamepadKey.LS_LEFT]: ['Left Stick Left', PrompFont.LS_LEFT],
|
||||
[GamepadKey.LS_RIGHT]: ['Left Stick Right', PrompFont.LS_RIGHT],
|
||||
|
||||
[GamepadKey.R3]: ['R3', PrompFont.R3],
|
||||
[GamepadKey.RS_UP]: ['Right Stick Up', PrompFont.RS_UP],
|
||||
[GamepadKey.RS_DOWN]: ['Right Stick Down', PrompFont.RS_DOWN],
|
||||
[GamepadKey.RS_LEFT]: ['Right Stick Left', PrompFont.RS_LEFT],
|
||||
[GamepadKey.RS_RIGHT]: ['Right Stick Right', PrompFont.RS_RIGHT],
|
||||
};
|
||||
|
||||
|
||||
export enum GamepadStick {
|
||||
LEFT = 0,
|
||||
RIGHT = 1,
|
||||
};
|
227
src/enums/mkb.ts
Normal file → Executable file
@ -1,102 +1,169 @@
|
||||
import type { GamepadKeyNameType } from "@/types/mkb";
|
||||
import { PrompFont } from "@enums/prompt-font";
|
||||
|
||||
export enum GamepadKey {
|
||||
A = 0,
|
||||
B = 1,
|
||||
X = 2,
|
||||
Y = 3,
|
||||
LB = 4,
|
||||
RB = 5,
|
||||
LT = 6,
|
||||
RT = 7,
|
||||
SELECT = 8,
|
||||
START = 9,
|
||||
L3 = 10,
|
||||
R3 = 11,
|
||||
UP = 12,
|
||||
DOWN = 13,
|
||||
LEFT = 14,
|
||||
RIGHT = 15,
|
||||
HOME = 16,
|
||||
SHARE = 17,
|
||||
|
||||
LS_UP = 100,
|
||||
LS_DOWN = 101,
|
||||
LS_LEFT = 102,
|
||||
LS_RIGHT = 103,
|
||||
|
||||
RS_UP = 200,
|
||||
RS_DOWN = 201,
|
||||
RS_LEFT = 202,
|
||||
RS_RIGHT = 203,
|
||||
};
|
||||
export const enum MouseConstant {
|
||||
DEFAULT_PANNING_SENSITIVITY = 0.0010,
|
||||
DEFAULT_DEADZONE_COUNTERWEIGHT = 0.01,
|
||||
MAXIMUM_STICK_RANGE = 1.1,
|
||||
}
|
||||
|
||||
|
||||
export const GamepadKeyName: GamepadKeyNameType = {
|
||||
[GamepadKey.A]: ['A', PrompFont.A],
|
||||
[GamepadKey.B]: ['B', PrompFont.B],
|
||||
[GamepadKey.X]: ['X', PrompFont.X],
|
||||
[GamepadKey.Y]: ['Y', PrompFont.Y],
|
||||
|
||||
[GamepadKey.LB]: ['LB', PrompFont.LB],
|
||||
[GamepadKey.RB]: ['RB', PrompFont.RB],
|
||||
[GamepadKey.LT]: ['LT', PrompFont.LT],
|
||||
[GamepadKey.RT]: ['RT', PrompFont.RT],
|
||||
|
||||
[GamepadKey.SELECT]: ['Select', PrompFont.SELECT],
|
||||
[GamepadKey.START]: ['Start', PrompFont.START],
|
||||
[GamepadKey.HOME]: ['Home', PrompFont.HOME],
|
||||
|
||||
[GamepadKey.UP]: ['D-Pad Up', PrompFont.UP],
|
||||
[GamepadKey.DOWN]: ['D-Pad Down', PrompFont.DOWN],
|
||||
[GamepadKey.LEFT]: ['D-Pad Left', PrompFont.LEFT],
|
||||
[GamepadKey.RIGHT]: ['D-Pad Right', PrompFont.RIGHT],
|
||||
|
||||
[GamepadKey.L3]: ['L3', PrompFont.L3],
|
||||
[GamepadKey.LS_UP]: ['Left Stick Up', PrompFont.LS_UP],
|
||||
[GamepadKey.LS_DOWN]: ['Left Stick Down', PrompFont.LS_DOWN],
|
||||
[GamepadKey.LS_LEFT]: ['Left Stick Left', PrompFont.LS_LEFT],
|
||||
[GamepadKey.LS_RIGHT]: ['Left Stick Right', PrompFont.LS_RIGHT],
|
||||
|
||||
[GamepadKey.R3]: ['R3', PrompFont.R3],
|
||||
[GamepadKey.RS_UP]: ['Right Stick Up', PrompFont.RS_UP],
|
||||
[GamepadKey.RS_DOWN]: ['Right Stick Down', PrompFont.RS_DOWN],
|
||||
[GamepadKey.RS_LEFT]: ['Right Stick Left', PrompFont.RS_LEFT],
|
||||
[GamepadKey.RS_RIGHT]: ['Right Stick Right', PrompFont.RS_RIGHT],
|
||||
};
|
||||
|
||||
|
||||
export enum GamepadStick {
|
||||
LEFT = 0,
|
||||
RIGHT = 1,
|
||||
};
|
||||
|
||||
export enum MouseButtonCode {
|
||||
export const enum MouseButtonCode {
|
||||
LEFT_CLICK = 'Mouse0',
|
||||
RIGHT_CLICK = 'Mouse2',
|
||||
MIDDLE_CLICK = 'Mouse1',
|
||||
};
|
||||
|
||||
export enum MouseMapTo {
|
||||
|
||||
export const enum MouseMapTo {
|
||||
OFF = 0,
|
||||
LS = 1,
|
||||
RS = 2,
|
||||
}
|
||||
|
||||
|
||||
export enum WheelCode {
|
||||
export const enum WheelCode {
|
||||
SCROLL_UP = 'ScrollUp',
|
||||
SCROLL_DOWN = 'ScrollDown',
|
||||
SCROLL_LEFT = 'ScrollLeft',
|
||||
SCROLL_RIGHT = 'ScrollRight',
|
||||
};
|
||||
|
||||
export enum MkbPresetKey {
|
||||
MOUSE_MAP_TO = 'map_to',
|
||||
|
||||
MOUSE_SENSITIVITY_X = 'sensitivity_x',
|
||||
MOUSE_SENSITIVITY_Y = 'sensitivity_y',
|
||||
export const enum MkbPresetKey {
|
||||
MOUSE_MAP_TO = 'mapTo',
|
||||
|
||||
MOUSE_DEADZONE_COUNTERWEIGHT = 'deadzone_counterweight',
|
||||
MOUSE_SENSITIVITY_X = 'sensitivityX',
|
||||
MOUSE_SENSITIVITY_Y = 'sensitivityY',
|
||||
|
||||
MOUSE_DEADZONE_COUNTERWEIGHT = 'deadzoneCounterweight',
|
||||
}
|
||||
|
||||
|
||||
export type KeyCode =
|
||||
| 'Backspace'
|
||||
| 'Tab'
|
||||
| 'Enter'
|
||||
| 'ShiftLeft'
|
||||
| 'ShiftRight'
|
||||
| 'ControlLeft'
|
||||
| 'ControlRight'
|
||||
| 'AltLeft'
|
||||
| 'AltRight'
|
||||
| 'Pause'
|
||||
| 'CapsLock'
|
||||
| 'Escape'
|
||||
| 'Space'
|
||||
| 'PageUp'
|
||||
| 'PageDown'
|
||||
| 'End'
|
||||
| 'Home'
|
||||
| 'ArrowLeft'
|
||||
| 'ArrowUp'
|
||||
| 'ArrowRight'
|
||||
| 'ArrowDown'
|
||||
| 'PrintScreen'
|
||||
| 'Insert'
|
||||
| 'Delete'
|
||||
| 'Digit0'
|
||||
| 'Digit1'
|
||||
| 'Digit2'
|
||||
| 'Digit3'
|
||||
| 'Digit4'
|
||||
| 'Digit5'
|
||||
| 'Digit6'
|
||||
| 'Digit7'
|
||||
| 'Digit8'
|
||||
| 'Digit9'
|
||||
| 'KeyA'
|
||||
| 'KeyB'
|
||||
| 'KeyC'
|
||||
| 'KeyD'
|
||||
| 'KeyE'
|
||||
| 'KeyF'
|
||||
| 'KeyG'
|
||||
| 'KeyH'
|
||||
| 'KeyI'
|
||||
| 'KeyJ'
|
||||
| 'KeyK'
|
||||
| 'KeyL'
|
||||
| 'KeyM'
|
||||
| 'KeyN'
|
||||
| 'KeyO'
|
||||
| 'KeyP'
|
||||
| 'KeyQ'
|
||||
| 'KeyR'
|
||||
| 'KeyS'
|
||||
| 'KeyT'
|
||||
| 'KeyU'
|
||||
| 'KeyV'
|
||||
| 'KeyW'
|
||||
| 'KeyX'
|
||||
| 'KeyY'
|
||||
| 'KeyZ'
|
||||
| 'MetaLeft'
|
||||
| 'MetaRight'
|
||||
| 'ContextMenu'
|
||||
| 'F1'
|
||||
| 'F2'
|
||||
| 'F3'
|
||||
| 'F4'
|
||||
| 'F5'
|
||||
| 'F6'
|
||||
| 'F7'
|
||||
| 'F8'
|
||||
| 'F9'
|
||||
| 'F10'
|
||||
| 'F11'
|
||||
| 'F12'
|
||||
| 'NumLock'
|
||||
| 'ScrollLock'
|
||||
| 'AudioVolumeMute'
|
||||
| 'AudioVolumeDown'
|
||||
| 'AudioVolumeUp'
|
||||
| 'MediaTrackNext'
|
||||
| 'MediaTrackPrevious'
|
||||
| 'MediaStop'
|
||||
| 'MediaPlayPause'
|
||||
| 'LaunchMail'
|
||||
| 'LaunchMediaPlayer'
|
||||
| 'LaunchApplication1'
|
||||
| 'LaunchApplication2'
|
||||
| 'Semicolon'
|
||||
| 'Equal'
|
||||
| 'Comma'
|
||||
| 'Minus'
|
||||
| 'Period'
|
||||
| 'Slash'
|
||||
| 'Backquote'
|
||||
| 'BracketLeft'
|
||||
| 'Backslash'
|
||||
| 'BracketRight'
|
||||
| 'Quote'
|
||||
| 'Numpad0'
|
||||
| 'Numpad1'
|
||||
| 'Numpad2'
|
||||
| 'Numpad3'
|
||||
| 'Numpad4'
|
||||
| 'Numpad5'
|
||||
| 'Numpad6'
|
||||
| 'Numpad7'
|
||||
| 'Numpad8'
|
||||
| 'Numpad9'
|
||||
| 'NumpadMultiply'
|
||||
| 'NumpadAdd'
|
||||
| 'NumpadSubtract'
|
||||
| 'NumpadDecimal'
|
||||
| 'NumpadDivide';
|
||||
|
||||
export type KeyCodeExcludeModifiers = Exclude<KeyCode,
|
||||
'ShiftLeft'
|
||||
| 'ShiftRight'
|
||||
| 'ControlLeft'
|
||||
| 'ControlRight'
|
||||
| 'AltLeft'
|
||||
| 'AltRight'
|
||||
>
|
||||
|
||||
export const enum KeyModifier {
|
||||
CTRL = 1,
|
||||
ALT = 2,
|
||||
SHIFT = 4,
|
||||
}
|
||||
|
164
src/enums/pref-keys.ts
Normal file → Executable file
@ -1,101 +1,115 @@
|
||||
export enum StorageKey {
|
||||
GLOBAL = 'better_xcloud',
|
||||
export const enum StorageKey {
|
||||
GLOBAL = 'BetterXcloud',
|
||||
|
||||
LOCALE = 'BetterXcloud.Locale',
|
||||
LOCALE_TRANSLATIONS = 'BetterXcloud.Locale.Translations',
|
||||
PATCHES_CACHE = 'BetterXcloud.Patches.Cache',
|
||||
PATCHES_SIGNATURE = 'BetterXcloud.Patches.Cache.Signature',
|
||||
USER_AGENT = 'BetterXcloud.UserAgent',
|
||||
|
||||
GH_PAGES_COMMIT_HASH = 'BetterXcloud.GhPages.CommitHash',
|
||||
LIST_CUSTOM_TOUCH_LAYOUTS = 'BetterXcloud.GhPages.CustomTouchLayouts',
|
||||
LIST_FORCE_NATIVE_MKB = 'BetterXcloud.GhPages.ForceNativeMkb',
|
||||
}
|
||||
|
||||
export enum PrefKey {
|
||||
LAST_UPDATE_CHECK = 'version_last_check',
|
||||
LATEST_VERSION = 'version_latest',
|
||||
CURRENT_VERSION = 'version_current',
|
||||
export const enum PrefKey {
|
||||
VERSION_LAST_CHECK = 'version.lastCheck',
|
||||
VERSION_LATEST = 'version.latest',
|
||||
VERSION_CURRENT = 'version.current',
|
||||
|
||||
BETTER_XCLOUD_LOCALE = 'bx_locale',
|
||||
SCRIPT_LOCALE = 'bx.locale',
|
||||
|
||||
SERVER_REGION = 'server_region',
|
||||
SERVER_BYPASS_RESTRICTION = 'server_bypass_restriction',
|
||||
SERVER_REGION = 'server.region',
|
||||
SERVER_BYPASS_RESTRICTION = 'server.bypassRestriction',
|
||||
SERVER_PREFER_IPV6 = 'server.ipv6.prefer',
|
||||
|
||||
PREFER_IPV6_SERVER = 'prefer_ipv6_server',
|
||||
STREAM_TARGET_RESOLUTION = 'stream_target_resolution',
|
||||
STREAM_PREFERRED_LOCALE = 'stream_preferred_locale',
|
||||
STREAM_CODEC_PROFILE = 'stream_codec_profile',
|
||||
STREAM_PREFERRED_LOCALE = 'stream.locale',
|
||||
STREAM_RESOLUTION = 'stream.video.resolution',
|
||||
STREAM_CODEC_PROFILE = 'stream.video.codecProfile',
|
||||
STREAM_MAX_VIDEO_BITRATE = 'stream.video.maxBitrate',
|
||||
STREAM_COMBINE_SOURCES = 'stream.video.combineAudio',
|
||||
|
||||
USER_AGENT_PROFILE = 'user_agent_profile',
|
||||
STREAM_SIMPLIFY_MENU = 'stream_simplify_menu',
|
||||
USER_AGENT_PROFILE = 'userAgent.profile',
|
||||
|
||||
STREAM_COMBINE_SOURCES = 'stream_combine_sources',
|
||||
TOUCH_CONTROLLER_MODE = 'touchController.mode',
|
||||
TOUCH_CONTROLLER_AUTO_OFF = 'touchController.autoOff',
|
||||
TOUCH_CONTROLLER_DEFAULT_OPACITY = 'touchController.opacity.default',
|
||||
TOUCH_CONTROLLER_STYLE_STANDARD = 'touchController.style.standard',
|
||||
TOUCH_CONTROLLER_STYLE_CUSTOM = 'touchController.style.custom',
|
||||
|
||||
STREAM_TOUCH_CONTROLLER = 'stream_touch_controller',
|
||||
STREAM_TOUCH_CONTROLLER_AUTO_OFF = 'stream_touch_controller_auto_off',
|
||||
STREAM_TOUCH_CONTROLLER_DEFAULT_OPACITY = 'stream_touch_controller_default_opacity',
|
||||
STREAM_TOUCH_CONTROLLER_STYLE_STANDARD = 'stream_touch_controller_style_standard',
|
||||
STREAM_TOUCH_CONTROLLER_STYLE_CUSTOM = 'stream_touch_controller_style_custom',
|
||||
GAME_BAR_POSITION = 'gameBar.position',
|
||||
|
||||
STREAM_DISABLE_FEEDBACK_DIALOG = 'stream_disable_feedback_dialog',
|
||||
LOCAL_CO_OP_ENABLED = 'localCoOp.enabled',
|
||||
|
||||
BITRATE_VIDEO_MAX = 'bitrate_video_max',
|
||||
DEVICE_VIBRATION_MODE = 'deviceVibration.mode',
|
||||
DEVICE_VIBRATION_INTENSITY = 'deviceVibration.intensity',
|
||||
|
||||
GAME_BAR_POSITION = 'game_bar_position',
|
||||
CONTROLLER_POLLING_RATE = 'controller.pollingRate',
|
||||
|
||||
LOCAL_CO_OP_ENABLED = 'local_co_op_enabled',
|
||||
// LOCAL_CO_OP_SEPARATE_TOUCH_CONTROLLER = 'local_co_op_separate_touch_controller',
|
||||
NATIVE_MKB_MODE = 'nativeMkb.mode',
|
||||
NATIVE_MKB_FORCED_GAMES = 'nativeMkb.forcedGames',
|
||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityX',
|
||||
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityY',
|
||||
|
||||
CONTROLLER_ENABLE_SHORTCUTS = 'controller_enable_shortcuts',
|
||||
CONTROLLER_ENABLE_VIBRATION = 'controller_enable_vibration',
|
||||
CONTROLLER_DEVICE_VIBRATION = 'controller_device_vibration',
|
||||
CONTROLLER_VIBRATION_INTENSITY = 'controller_vibration_intensity',
|
||||
CONTROLLER_SHOW_CONNECTION_STATUS = 'controller_show_connection_status',
|
||||
MKB_ENABLED = 'mkb.enabled',
|
||||
MKB_HIDE_IDLE_CURSOR = 'mkb.cursor.hideIdle',
|
||||
MKB_P1_MAPPING_PRESET_ID = 'mkb.p1.preset.mappingId',
|
||||
MKB_P1_SLOT = 'mkb.p1.slot',
|
||||
MKB_P2_MAPPING_PRESET_ID = 'mkb.p2.preset.mappingId',
|
||||
MKB_P2_SLOT = 'mkb.p2.slot',
|
||||
|
||||
NATIVE_MKB_ENABLED = 'native_mkb_enabled',
|
||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'native_mkb_scroll_x_sensitivity',
|
||||
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'native_mkb_scroll_y_sensitivity',
|
||||
KEYBOARD_SHORTCUTS_IN_GAME_PRESET_ID = 'keyboardShortcuts.preset.inGameId',
|
||||
|
||||
MKB_ENABLED = 'mkb_enabled',
|
||||
MKB_HIDE_IDLE_CURSOR = 'mkb_hide_idle_cursor',
|
||||
MKB_ABSOLUTE_MOUSE = 'mkb_absolute_mouse',
|
||||
MKB_DEFAULT_PRESET_ID = 'mkb_default_preset_id',
|
||||
SCREENSHOT_APPLY_FILTERS = 'screenshot.applyFilters',
|
||||
|
||||
SCREENSHOT_APPLY_FILTERS = 'screenshot_apply_filters',
|
||||
BLOCK_TRACKING = 'block.tracking',
|
||||
BLOCK_SOCIAL_FEATURES = 'block.social',
|
||||
|
||||
BLOCK_TRACKING = 'block_tracking',
|
||||
BLOCK_SOCIAL_FEATURES = 'block_social_features',
|
||||
SKIP_SPLASH_VIDEO = 'skip_splash_video',
|
||||
HIDE_DOTS_ICON = 'hide_dots_icon',
|
||||
REDUCE_ANIMATIONS = 'reduce_animations',
|
||||
LOADING_SCREEN_GAME_ART = 'loadingScreen.gameArt.show',
|
||||
LOADING_SCREEN_SHOW_WAIT_TIME = 'loadingScreen.waitTime.show',
|
||||
LOADING_SCREEN_ROCKET = 'loadingScreen.rocket',
|
||||
|
||||
UI_LOADING_SCREEN_GAME_ART = 'ui_loading_screen_game_art',
|
||||
UI_LOADING_SCREEN_WAIT_TIME = 'ui_loading_screen_wait_time',
|
||||
UI_LOADING_SCREEN_ROCKET = 'ui_loading_screen_rocket',
|
||||
UI_CONTROLLER_FRIENDLY = 'ui.controllerFriendly',
|
||||
UI_LAYOUT = 'ui.layout',
|
||||
UI_SCROLLBAR_HIDE = 'ui.hideScrollbar',
|
||||
UI_HIDE_SECTIONS = 'ui.hideSections',
|
||||
BYOG_DISABLED = 'feature.byog.disabled',
|
||||
|
||||
UI_CONTROLLER_FRIENDLY = 'ui_controller_friendly',
|
||||
UI_LAYOUT = 'ui_layout',
|
||||
UI_SCROLLBAR_HIDE = 'ui_scrollbar_hide',
|
||||
UI_HIDE_SECTIONS = 'ui_hide_sections',
|
||||
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui.gameCard.waitTime.show',
|
||||
UI_SIMPLIFY_STREAM_MENU = 'ui.streamMenu.simplify',
|
||||
UI_DISABLE_FEEDBACK_DIALOG = 'ui.feedbackDialog.disabled',
|
||||
UI_CONTROLLER_SHOW_STATUS = 'ui.controllerStatus.show',
|
||||
|
||||
UI_HOME_CONTEXT_MENU_DISABLED = 'ui_home_context_menu_disabled',
|
||||
UI_GAME_CARD_SHOW_WAIT_TIME = 'ui_game_card_show_wait_time',
|
||||
UI_SKIP_SPLASH_VIDEO = 'ui.splashVideo.skip',
|
||||
UI_HIDE_SYSTEM_MENU_ICON = 'ui.systemMenu.hideHandle',
|
||||
UI_REDUCE_ANIMATIONS = 'ui.reduceAnimations',
|
||||
|
||||
VIDEO_PLAYER_TYPE = 'video_player_type',
|
||||
VIDEO_PROCESSING = 'video_processing',
|
||||
VIDEO_POWER_PREFERENCE = 'video_power_preference',
|
||||
VIDEO_SHARPNESS = 'video_sharpness',
|
||||
VIDEO_RATIO = 'video_ratio',
|
||||
VIDEO_BRIGHTNESS = 'video_brightness',
|
||||
VIDEO_CONTRAST = 'video_contrast',
|
||||
VIDEO_SATURATION = 'video_saturation',
|
||||
VIDEO_PLAYER_TYPE = 'video.player.type',
|
||||
VIDEO_POWER_PREFERENCE = 'video.player.powerPreference',
|
||||
VIDEO_PROCESSING = 'video.processing',
|
||||
VIDEO_SHARPNESS = 'video.processing.sharpness',
|
||||
VIDEO_MAX_FPS = 'video.maxFps',
|
||||
VIDEO_RATIO = 'video.ratio',
|
||||
VIDEO_BRIGHTNESS = 'video.brightness',
|
||||
VIDEO_CONTRAST = 'video.contrast',
|
||||
VIDEO_SATURATION = 'video.saturation',
|
||||
VIDEO_POSITION = 'video.position',
|
||||
|
||||
AUDIO_MIC_ON_PLAYING = 'audio_mic_on_playing',
|
||||
AUDIO_ENABLE_VOLUME_CONTROL = 'audio_enable_volume_control',
|
||||
AUDIO_VOLUME = 'audio_volume',
|
||||
AUDIO_MIC_ON_PLAYING = 'audio.mic.onPlaying',
|
||||
AUDIO_VOLUME_CONTROL_ENABLED = 'audio.volume.booster.enabled',
|
||||
AUDIO_VOLUME = 'audio.volume',
|
||||
|
||||
STATS_ITEMS = 'stats_items',
|
||||
STATS_SHOW_WHEN_PLAYING = 'stats_show_when_playing',
|
||||
STATS_QUICK_GLANCE = 'stats_quick_glance',
|
||||
STATS_POSITION = 'stats_position',
|
||||
STATS_TEXT_SIZE = 'stats_text_size',
|
||||
STATS_TRANSPARENT = 'stats_transparent',
|
||||
STATS_OPACITY = 'stats_opacity',
|
||||
STATS_CONDITIONAL_FORMATTING = 'stats_conditional_formatting',
|
||||
STATS_ITEMS = 'stats.items',
|
||||
STATS_SHOW_WHEN_PLAYING = 'stats.showWhenPlaying',
|
||||
STATS_QUICK_GLANCE_ENABLED = 'stats.quickGlance.enabled',
|
||||
STATS_POSITION = 'stats.position',
|
||||
STATS_TEXT_SIZE = 'stats.textSize',
|
||||
STATS_TRANSPARENT = 'stats.transparent',
|
||||
STATS_OPACITY = 'stats.opacity',
|
||||
STATS_CONDITIONAL_FORMATTING = 'stats.colors',
|
||||
|
||||
REMOTE_PLAY_ENABLED = 'xhome_enabled',
|
||||
REMOTE_PLAY_RESOLUTION = 'xhome_resolution',
|
||||
REMOTE_PLAY_ENABLED = 'xhome.enabled',
|
||||
REMOTE_PLAY_STREAM_RESOLUTION = 'xhome.video.resolution',
|
||||
|
||||
GAME_FORTNITE_FORCE_CONSOLE = 'game_fortnite_force_console',
|
||||
GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole',
|
||||
}
|
||||
|
112
src/enums/pref-values.ts
Executable file
@ -0,0 +1,112 @@
|
||||
export const enum UiSection {
|
||||
ALL_GAMES = 'all-games',
|
||||
FRIENDS = 'friends',
|
||||
MOST_POPULAR = 'most-popular',
|
||||
NATIVE_MKB = 'native-mkb',
|
||||
NEWS = 'news',
|
||||
TOUCH = 'touch',
|
||||
BOYG = 'byog',
|
||||
}
|
||||
|
||||
export const enum GameBarPosition {
|
||||
BOTTOM_LEFT = 'bottom-left',
|
||||
BOTTOM_RIGHT = 'bottom-right',
|
||||
OFF = 'off',
|
||||
};
|
||||
|
||||
export const enum UiLayout {
|
||||
TV = 'tv',
|
||||
NORMAL = 'normal',
|
||||
DEFAULT = 'default',
|
||||
}
|
||||
|
||||
export const enum LoadingScreenRocket {
|
||||
SHOW = 'show',
|
||||
HIDE = 'hide',
|
||||
HIDE_QUEUE = 'hide-queue',
|
||||
}
|
||||
|
||||
export const enum StreamResolution {
|
||||
DIM_720P = '720p',
|
||||
DIM_1080P = '1080p',
|
||||
DIM_1080P_HQ = '1080p-hq',
|
||||
AUTO = 'auto',
|
||||
}
|
||||
|
||||
export const enum CodecProfile {
|
||||
DEFAULT = 'default',
|
||||
LOW = 'low',
|
||||
NORMAL = 'normal',
|
||||
HIGH = 'high',
|
||||
};
|
||||
|
||||
export const enum TouchControllerMode {
|
||||
DEFAULT = 'default',
|
||||
ALL = 'all',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
export const enum TouchControllerStyleStandard {
|
||||
DEFAULT = 'default',
|
||||
WHITE = 'white',
|
||||
MUTED = 'muted',
|
||||
}
|
||||
|
||||
export const enum TouchControllerStyleCustom {
|
||||
DEFAULT = 'default',
|
||||
MUTED = 'muted',
|
||||
}
|
||||
|
||||
export const enum DeviceVibrationMode {
|
||||
ON = 'on',
|
||||
AUTO = 'auto',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
export const enum NativeMkbMode {
|
||||
DEFAULT = 'default',
|
||||
ON = 'on',
|
||||
OFF = 'off',
|
||||
}
|
||||
|
||||
export const enum StreamStat {
|
||||
PING = 'ping',
|
||||
JITTER = 'jit',
|
||||
FPS = 'fps',
|
||||
BITRATE = 'btr',
|
||||
DECODE_TIME = 'dt',
|
||||
PACKETS_LOST = 'pl',
|
||||
FRAMES_LOST = 'fl',
|
||||
DOWNLOAD = 'dl',
|
||||
UPLOAD = 'ul',
|
||||
PLAYTIME = 'play',
|
||||
BATTERY = 'batt',
|
||||
CLOCK = 'time',
|
||||
};
|
||||
|
||||
export const enum VideoRatio {
|
||||
'16:9' = '16:9',
|
||||
'18:9' = '18:9',
|
||||
'21:9' = '21:9',
|
||||
'16:10' = '16:10',
|
||||
'4:3' = '4:3',
|
||||
FILL = 'fill',
|
||||
}
|
||||
|
||||
export const enum VideoPosition {
|
||||
CENTER = 'center',
|
||||
TOP = 'top',
|
||||
TOP_HALF = 'top-half',
|
||||
BOTTOM = 'bottom',
|
||||
BOTTOM_HALF = 'bottom-half',
|
||||
}
|
||||
|
||||
export const enum StreamPlayerType {
|
||||
VIDEO = 'default',
|
||||
WEBGL2 = 'webgl2',
|
||||
}
|
||||
|
||||
export const enum StreamVideoProcessing {
|
||||
USM = 'usm',
|
||||
CAS = 'cas',
|
||||
}
|
0
src/enums/prompt-font.ts
Normal file → Executable file
25
src/enums/shortcut-actions.ts
Executable file
@ -0,0 +1,25 @@
|
||||
export const enum ShortcutAction {
|
||||
BETTER_XCLOUD_SETTINGS_SHOW = 'bx.settings.show',
|
||||
|
||||
STREAM_VIDEO_TOGGLE = 'stream.video.toggle',
|
||||
STREAM_SCREENSHOT_CAPTURE = 'stream.screenshot.capture',
|
||||
|
||||
STREAM_MENU_SHOW = 'stream.menu.show',
|
||||
STREAM_STATS_TOGGLE = 'stream.stats.toggle',
|
||||
STREAM_SOUND_TOGGLE = 'stream.sound.toggle',
|
||||
STREAM_MICROPHONE_TOGGLE = 'stream.microphone.toggle',
|
||||
|
||||
STREAM_VOLUME_INC = 'stream.volume.inc',
|
||||
STREAM_VOLUME_DEC = 'stream.volume.dec',
|
||||
|
||||
DEVICE_SOUND_TOGGLE = 'device.sound.toggle',
|
||||
DEVICE_VOLUME_INC = 'device.volume.inc',
|
||||
DEVICE_VOLUME_DEC = 'device.volume.dec',
|
||||
|
||||
DEVICE_BRIGHTNESS_INC = 'device.brightness.inc',
|
||||
DEVICE_BRIGHTNESS_DEC = 'device.brightness.dec',
|
||||
|
||||
MKB_TOGGLE = 'mkb.toggle',
|
||||
|
||||
TRUE_ACHIEVEMENTS_OPEN = 'ta.open',
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
export enum StreamPlayerType {
|
||||
VIDEO = 'default',
|
||||
WEBGL2 = 'webgl2',
|
||||
}
|
||||
|
||||
export enum StreamVideoProcessing {
|
||||
USM = 'usm',
|
||||
CAS = 'cas',
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
export enum UiSection {
|
||||
ALL_GAMES = 'all-games',
|
||||
FRIENDS = 'friends',
|
||||
MOST_POPULAR = 'most-popular',
|
||||
NATIVE_MKB = 'native-mkb',
|
||||
NEWS = 'news',
|
||||
TOUCH = 'touch',
|
||||
}
|
0
src/enums/user-agent.ts
Normal file → Executable file
171
src/index.ts
Normal file → Executable file
@ -1,4 +1,4 @@
|
||||
import { compressCss, isFullVersion } from "@macros/build" with {type: "macro"};
|
||||
import { compressCss, isFullVersion } from "@macros/build" with { type: "macro" };
|
||||
|
||||
import "@utils/global";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
@ -12,36 +12,38 @@ import { EmulatedMkbHandler } from "@modules/mkb/mkb-handler";
|
||||
import { StreamBadges } from "@modules/stream/stream-badges";
|
||||
import { StreamStats } from "@modules/stream/stream-stats";
|
||||
import { addCss, preloadFonts } from "@utils/css";
|
||||
import { Toast } from "@utils/toast";
|
||||
import { LoadingScreen } from "@modules/loading-screen";
|
||||
import { MouseCursorHider } from "@modules/mkb/mouse-cursor-hider";
|
||||
import { TouchController } from "@modules/touch-controller";
|
||||
import { checkForUpdate, disablePwa, productTitleToSlug } from "@utils/utils";
|
||||
import { Patcher } from "@modules/patcher";
|
||||
import { Patcher } from "@/modules/patcher/patcher";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { onHistoryChanged, patchHistoryMethod } from "@utils/history";
|
||||
import { VibrationManager } from "@modules/vibration-manager";
|
||||
import { overridePreloadState } from "@utils/preload-state";
|
||||
import { disableAdobeAudienceManager, patchAudioContext, patchCanvasContext, patchMeControl, patchPointerLockApi, patchRtcCodecs, patchRtcPeerConnection, patchVideoApi } from "@utils/monkey-patches";
|
||||
import { AppInterface, STATES } from "@utils/global";
|
||||
import { BxLogger } from "@utils/bx-logger";
|
||||
import { GameBar } from "./modules/game-bar/game-bar";
|
||||
import { Screenshot } from "./utils/screenshot";
|
||||
import { ScreenshotManager } from "./utils/screenshot-manager";
|
||||
import { NativeMkbHandler } from "./modules/mkb/native-mkb-handler";
|
||||
import { GuideMenu } from "./modules/ui/guide-menu";
|
||||
import { updateVideoPlayer } from "./modules/stream/stream-settings-utils";
|
||||
import { UiSection } from "./enums/ui-sections";
|
||||
import { NativeMkbMode, TouchControllerMode, UiSection } from "./enums/pref-values";
|
||||
import { HeaderSection } from "./modules/ui/header";
|
||||
import { GameTile } from "./modules/ui/game-tile";
|
||||
import { ProductDetailsPage } from "./modules/ui/product-details";
|
||||
import { NavigationDialogManager } from "./modules/ui/dialog/navigation-dialog";
|
||||
import { PrefKey } from "./enums/pref-keys";
|
||||
import { getPref, StreamTouchController } from "./utils/settings-storages/global-settings-storage";
|
||||
import { SettingsNavigationDialog } from "./modules/ui/dialog/settings-dialog";
|
||||
import { getPref } from "./utils/settings-storages/global-settings-storage";
|
||||
import { SettingsDialog } from "./modules/ui/dialog/settings-dialog";
|
||||
import { StreamUiHandler } from "./modules/stream/stream-ui";
|
||||
import { UserAgent } from "./utils/user-agent";
|
||||
import { XboxApi } from "./utils/xbox-api";
|
||||
import { StreamStatsCollector } from "./utils/stream-stats-collector";
|
||||
import { RootDialogObserver } from "./utils/root-dialog-observer";
|
||||
import { StreamSettings } from "./utils/stream-settings";
|
||||
import { KeyboardShortcutHandler } from "./modules/mkb/keyboard-shortcut-handler";
|
||||
import { GhPagesUtils } from "./utils/gh-pages";
|
||||
import { DeviceVibrationManager } from "./modules/device-vibration-manager";
|
||||
|
||||
// Handle login page
|
||||
if (window.location.pathname.includes('/auth/msa')) {
|
||||
@ -161,15 +163,15 @@ document.addEventListener('readystatechange', e => {
|
||||
|
||||
if (STATES.isSignedIn) {
|
||||
// Preload Remote Play
|
||||
getPref(PrefKey.REMOTE_PLAY_ENABLED) && RemotePlayManager.getInstance().initialize();
|
||||
RemotePlayManager.getInstance()?.initialize();
|
||||
} else {
|
||||
// Show Settings button in the header when not signed in
|
||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||
}
|
||||
|
||||
// Hide "Play with Friends" skeleton section
|
||||
if (getPref(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS)) {
|
||||
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest('div[class*=HomePage-module]') as HTMLElement;
|
||||
if (getPref<UiSection[]>(PrefKey.UI_HIDE_SECTIONS).includes(UiSection.FRIENDS)) {
|
||||
const $parent = document.querySelector('div[class*=PlayWithFriendsSkeleton]')?.closest<HTMLElement>('div[class*=HomePage-module]');
|
||||
$parent && ($parent.style.display = 'none');
|
||||
}
|
||||
|
||||
@ -193,11 +195,11 @@ window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => {
|
||||
window.setTimeout(HeaderSection.watchHeader, 2000);
|
||||
|
||||
// Open Settings dialog on Unsupported page
|
||||
const $unsupportedPage = document.querySelector('div[class^=UnsupportedMarketPage-module__container]') as HTMLElement;
|
||||
const $unsupportedPage = document.querySelector<HTMLElement>('div[class^=UnsupportedMarketPage-module__container]');
|
||||
if ($unsupportedPage) {
|
||||
SettingsNavigationDialog.getInstance().show();
|
||||
SettingsDialog.getInstance().show();
|
||||
}
|
||||
}, {once: true});
|
||||
}, { once: true });
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_SERVERS_READY, e => {
|
||||
STATES.isSignedIn = true;
|
||||
@ -214,34 +216,48 @@ window.addEventListener(BxEvent.STREAM_LOADING, e => {
|
||||
});
|
||||
|
||||
// Setup loading screen
|
||||
getPref(PrefKey.UI_LOADING_SCREEN_GAME_ART) && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup);
|
||||
getPref(PrefKey.LOADING_SCREEN_GAME_ART) && window.addEventListener(BxEvent.TITLE_INFO_READY, LoadingScreen.setup);
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_STARTING, e => {
|
||||
// Hide loading screen
|
||||
LoadingScreen.hide();
|
||||
|
||||
// Start hiding cursor
|
||||
if (!getPref(PrefKey.MKB_ENABLED) && getPref(PrefKey.MKB_HIDE_IDLE_CURSOR)) {
|
||||
MouseCursorHider.start();
|
||||
MouseCursorHider.hide();
|
||||
if (isFullVersion()) {
|
||||
// Start hiding cursor
|
||||
const cursorHider = MouseCursorHider.getInstance();
|
||||
if (cursorHider) {
|
||||
cursorHider.start();
|
||||
cursorHider.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.STREAM_PLAYING, e => {
|
||||
window.BX_STREAM_SETTINGS = StreamSettings.settings;
|
||||
StreamSettings.refreshAllSettings();
|
||||
|
||||
STATES.isPlaying = true;
|
||||
StreamUiHandler.observe();
|
||||
|
||||
if (isFullVersion() && getPref(PrefKey.GAME_BAR_POSITION) !== 'off') {
|
||||
if (isFullVersion()) {
|
||||
const gameBar = GameBar.getInstance();
|
||||
gameBar.reset();
|
||||
gameBar.enable();
|
||||
gameBar.showBar();
|
||||
if (gameBar) {
|
||||
gameBar.reset();
|
||||
gameBar.enable();
|
||||
gameBar.showBar();
|
||||
}
|
||||
|
||||
// Setup Keyboard shortcuts
|
||||
KeyboardShortcutHandler.getInstance().start();
|
||||
|
||||
// Setup screenshot
|
||||
const $video = (e as any).$video as HTMLVideoElement;
|
||||
ScreenshotManager.getInstance().updateCanvasSize($video.videoWidth, $video.videoHeight);
|
||||
|
||||
// Setup local co-op
|
||||
getPref(PrefKey.LOCAL_CO_OP_ENABLED) && BxExposed.toggleLocalCoOp(getPref(PrefKey.LOCAL_CO_OP_ENABLED));
|
||||
}
|
||||
|
||||
if (isFullVersion()) {
|
||||
const $video = (e as any).$video as HTMLVideoElement;
|
||||
Screenshot.updateCanvasSize($video.videoWidth, $video.videoHeight);
|
||||
}
|
||||
|
||||
updateVideoPlayer();
|
||||
});
|
||||
@ -250,9 +266,9 @@ window.addEventListener(BxEvent.STREAM_ERROR_PAGE, e => {
|
||||
BxEvent.dispatch(window, BxEvent.STREAM_STOPPED);
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
||||
isFullVersion() && window.addEventListener(BxEvent.XCLOUD_RENDERING_COMPONENT, e => {
|
||||
const component = (e as any).component;
|
||||
if (component === 'product-details') {
|
||||
if (component === 'product-detail') {
|
||||
ProductDetailsPage.injectButtons();
|
||||
}
|
||||
});
|
||||
@ -295,9 +311,13 @@ function unload() {
|
||||
}
|
||||
|
||||
if (isFullVersion()) {
|
||||
KeyboardShortcutHandler.getInstance().stop();
|
||||
|
||||
// Stop MKB listeners
|
||||
EmulatedMkbHandler.getInstance().destroy();
|
||||
NativeMkbHandler.getInstance().destroy();
|
||||
EmulatedMkbHandler.getInstance()?.destroy();
|
||||
NativeMkbHandler.getInstance()?.destroy();
|
||||
|
||||
DeviceVibrationManager.getInstance()?.reset();
|
||||
}
|
||||
|
||||
// Destroy StreamPlayer
|
||||
@ -309,12 +329,14 @@ function unload() {
|
||||
window.BX_EXPOSED.stopTakRendering = false;
|
||||
|
||||
NavigationDialogManager.getInstance().hide();
|
||||
StreamStats.getInstance().onStoppedPlaying();
|
||||
StreamStats.getInstance().destroy();
|
||||
StreamBadges.getInstance().destroy();
|
||||
|
||||
if (isFullVersion()) {
|
||||
MouseCursorHider.stop();
|
||||
MouseCursorHider.getInstance()?.stop();
|
||||
TouchController.reset();
|
||||
GameBar.getInstance().disable();
|
||||
|
||||
GameBar.getInstance()?.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@ -324,60 +346,20 @@ window.addEventListener('pagehide', e => {
|
||||
});
|
||||
|
||||
isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
||||
Screenshot.takeScreenshot();
|
||||
ScreenshotManager.getInstance().takeScreenshot();
|
||||
});
|
||||
|
||||
|
||||
function observeRootDialog($root: HTMLElement) {
|
||||
let beingShown = false;
|
||||
|
||||
const observer = new MutationObserver(mutationList => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type !== 'childList') {
|
||||
continue;
|
||||
}
|
||||
|
||||
BX_FLAGS.Debug && BxLogger.warning('RootDialog', 'added', mutation.addedNodes);
|
||||
if (mutation.addedNodes.length === 1) {
|
||||
const $addedElm = mutation.addedNodes[0];
|
||||
if ($addedElm instanceof HTMLElement && $addedElm.className) {
|
||||
// Make sure it's Guide dialog
|
||||
if ($root.querySelector('div[class*=GuideDialog]')) {
|
||||
GuideMenu.observe($addedElm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const shown = !!($root.firstElementChild && $root.firstElementChild.childElementCount > 0);
|
||||
if (shown !== beingShown) {
|
||||
beingShown = shown;
|
||||
BxEvent.dispatch(window, shown ? BxEvent.XCLOUD_DIALOG_SHOWN : BxEvent.XCLOUD_DIALOG_DISMISSED);
|
||||
}
|
||||
}
|
||||
});
|
||||
observer.observe($root, {subtree: true, childList: true});
|
||||
}
|
||||
|
||||
function waitForRootDialog() {
|
||||
const observer = new MutationObserver(mutationList => {
|
||||
for (const mutation of mutationList) {
|
||||
if (mutation.type !== 'childList') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $target = mutation.target as HTMLElement;
|
||||
if ($target.id && $target.id === 'gamepass-dialog-root') {
|
||||
observer.disconnect();
|
||||
observeRootDialog($target);
|
||||
break;
|
||||
}
|
||||
};
|
||||
});
|
||||
observer.observe(document.documentElement, {subtree: true, childList: true});
|
||||
}
|
||||
|
||||
|
||||
function main() {
|
||||
GhPagesUtils.fetchLatestCommit();
|
||||
|
||||
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) !== NativeMkbMode.OFF) {
|
||||
const customList = getPref<string[]>(PrefKey.NATIVE_MKB_FORCED_GAMES);
|
||||
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
||||
}
|
||||
|
||||
StreamSettings.setup();
|
||||
|
||||
// Monkey patches
|
||||
patchRtcPeerConnection();
|
||||
patchRtcCodecs();
|
||||
@ -386,32 +368,27 @@ function main() {
|
||||
patchCanvasContext();
|
||||
isFullVersion() && AppInterface && patchPointerLockApi();
|
||||
|
||||
getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && patchAudioContext();
|
||||
getPref(PrefKey.AUDIO_VOLUME_CONTROL_ENABLED) && patchAudioContext();
|
||||
|
||||
if (getPref(PrefKey.BLOCK_TRACKING)) {
|
||||
patchMeControl();
|
||||
disableAdobeAudienceManager();
|
||||
}
|
||||
|
||||
waitForRootDialog();
|
||||
RootDialogObserver.waitForRootDialog();
|
||||
|
||||
// Setup UI
|
||||
addCss();
|
||||
Toast.setup();
|
||||
|
||||
GuideMenu.addEventListeners();
|
||||
GuideMenu.getInstance().addEventListeners();
|
||||
StreamStatsCollector.setupEvents();
|
||||
StreamBadges.setupEvents();
|
||||
StreamStats.setupEvents();
|
||||
|
||||
if (isFullVersion()) {
|
||||
(getPref(PrefKey.GAME_BAR_POSITION) !== 'off') && GameBar.getInstance();
|
||||
Screenshot.setup();
|
||||
|
||||
STATES.userAgent.capabilities.touch && TouchController.updateCustomList();
|
||||
overridePreloadState();
|
||||
|
||||
VibrationManager.initialSetup();
|
||||
DeviceVibrationManager.getInstance();
|
||||
|
||||
// Check for Update
|
||||
BX_FLAGS.CheckForUpdate && checkForUpdate();
|
||||
@ -424,12 +401,12 @@ function main() {
|
||||
RemotePlayManager.detect();
|
||||
}
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) {
|
||||
if (getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
|
||||
TouchController.setup();
|
||||
}
|
||||
|
||||
// Start PointerProviderServer
|
||||
if (getPref(PrefKey.MKB_ENABLED) && AppInterface) {
|
||||
if (AppInterface && (getPref(PrefKey.MKB_ENABLED) || getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON)) {
|
||||
STATES.pointerServerPort = AppInterface.startPointerServer() || 9269;
|
||||
BxLogger.info('startPointerServer', 'Port', STATES.pointerServerPort.toString());
|
||||
}
|
||||
@ -441,7 +418,7 @@ function main() {
|
||||
}
|
||||
|
||||
// Show a toast when connecting/disconecting controller
|
||||
if (getPref(PrefKey.CONTROLLER_SHOW_CONNECTION_STATUS)) {
|
||||
if (getPref(PrefKey.UI_CONTROLLER_SHOW_STATUS)) {
|
||||
window.addEventListener('gamepadconnected', e => showGamepadToast(e.gamepad));
|
||||
window.addEventListener('gamepaddisconnected', e => showGamepadToast(e.gamepad));
|
||||
}
|
||||
|
0
src/macros/build.ts
Normal file → Executable file
417
src/modules/controller-shortcut.ts
Normal file → Executable file
@ -1,426 +1,55 @@
|
||||
import { Screenshot } from "@utils/screenshot";
|
||||
import { GamepadKey } from "@enums/mkb";
|
||||
import { PrompFont } from "@enums/prompt-font";
|
||||
import { CE, removeChildElements } from "@utils/html";
|
||||
import { t } from "@utils/translation";
|
||||
import { StreamStats } from "./stream/stream-stats";
|
||||
import { MicrophoneShortcut } from "./shortcuts/shortcut-microphone";
|
||||
import { StreamUiShortcut } from "./shortcuts/shortcut-stream-ui";
|
||||
import { SoundShortcut } from "./shortcuts/shortcut-sound";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
import { AppInterface } from "@/utils/global";
|
||||
import { BxSelectElement } from "@/web-components/bx-select";
|
||||
import { setNearby } from "@/utils/navigation-utils";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { SettingsNavigationDialog } from "./ui/dialog/settings-dialog";
|
||||
import { VIRTUAL_GAMEPAD_ID } from "./mkb/mkb-handler";
|
||||
import { GamepadKey } from "@enums/gamepad";
|
||||
import { ShortcutHandler } from "@/utils/shortcut-handler";
|
||||
|
||||
const enum ShortcutAction {
|
||||
BETTER_XCLOUD_SETTINGS_SHOW = 'bx-settings-show',
|
||||
|
||||
STREAM_SCREENSHOT_CAPTURE = 'stream-screenshot-capture',
|
||||
|
||||
STREAM_MENU_SHOW = 'stream-menu-show',
|
||||
STREAM_STATS_TOGGLE = 'stream-stats-toggle',
|
||||
STREAM_SOUND_TOGGLE = 'stream-sound-toggle',
|
||||
STREAM_MICROPHONE_TOGGLE = 'stream-microphone-toggle',
|
||||
|
||||
STREAM_VOLUME_INC = 'stream-volume-inc',
|
||||
STREAM_VOLUME_DEC = 'stream-volume-dec',
|
||||
|
||||
DEVICE_SOUND_TOGGLE = 'device-sound-toggle',
|
||||
DEVICE_VOLUME_INC = 'device-volume-inc',
|
||||
DEVICE_VOLUME_DEC = 'device-volume-dec',
|
||||
|
||||
DEVICE_BRIGHTNESS_INC = 'device-brightness-inc',
|
||||
DEVICE_BRIGHTNESS_DEC = 'device-brightness-dec',
|
||||
}
|
||||
|
||||
export class ControllerShortcut {
|
||||
static readonly #STORAGE_KEY = 'better_xcloud_controller_shortcuts';
|
||||
|
||||
static #buttonsCache: {[key: string]: boolean[]} = {};
|
||||
static #buttonsStatus: {[key: string]: boolean[]} = {};
|
||||
|
||||
static #$selectProfile: HTMLSelectElement;
|
||||
static #$selectActions: Partial<{[key in GamepadKey]: HTMLSelectElement}> = {};
|
||||
static #$container: HTMLElement;
|
||||
|
||||
static #ACTIONS: {[key: string]: (ShortcutAction | null)[]} | null = null;
|
||||
private static buttonsCache: { [key: string]: boolean[] } = {};
|
||||
private static buttonsStatus: { [key: string]: boolean[] } = {};
|
||||
|
||||
static reset(index: number) {
|
||||
ControllerShortcut.#buttonsCache[index] = [];
|
||||
ControllerShortcut.#buttonsStatus[index] = [];
|
||||
ControllerShortcut.buttonsCache[index] = [];
|
||||
ControllerShortcut.buttonsStatus[index] = [];
|
||||
}
|
||||
|
||||
static handle(gamepad: Gamepad): boolean {
|
||||
if (!ControllerShortcut.#ACTIONS) {
|
||||
ControllerShortcut.#ACTIONS = ControllerShortcut.#getActionsFromStorage();
|
||||
const controllerSettings = window.BX_STREAM_SETTINGS.controllers[gamepad.id];
|
||||
if (!controllerSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gamepadIndex = gamepad.index;
|
||||
const actions = ControllerShortcut.#ACTIONS![gamepad.id];
|
||||
const actions = controllerSettings.shortcuts;
|
||||
if (!actions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const gamepadIndex = gamepad.index;
|
||||
|
||||
// Move the buttons status from the previous frame to the cache
|
||||
ControllerShortcut.#buttonsCache[gamepadIndex] = ControllerShortcut.#buttonsStatus[gamepadIndex].slice(0);
|
||||
ControllerShortcut.buttonsCache[gamepadIndex] = ControllerShortcut.buttonsStatus[gamepadIndex].slice(0);
|
||||
// Clear the buttons status
|
||||
ControllerShortcut.#buttonsStatus[gamepadIndex] = [];
|
||||
ControllerShortcut.buttonsStatus[gamepadIndex] = [];
|
||||
|
||||
const pressed: boolean[] = [];
|
||||
let otherButtonPressed = false;
|
||||
|
||||
gamepad.buttons.forEach((button, index) => {
|
||||
const entries = gamepad.buttons.entries();
|
||||
let index: GamepadKey;
|
||||
let button: GamepadButton;
|
||||
for ([index, button] of entries) {
|
||||
// Only add the newly pressed button to the array (holding doesn't count)
|
||||
if (button.pressed && index !== GamepadKey.HOME) {
|
||||
otherButtonPressed = true;
|
||||
pressed[index] = true;
|
||||
|
||||
// If this is newly pressed button -> run action
|
||||
if (actions[index] && !ControllerShortcut.#buttonsCache[gamepadIndex][index]) {
|
||||
setTimeout(() => ControllerShortcut.#runAction(actions[index]!), 0);
|
||||
if (actions[index] && !ControllerShortcut.buttonsCache[gamepadIndex][index]) {
|
||||
const idx = index;
|
||||
setTimeout(() => ShortcutHandler.runAction(actions[idx]!), 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
ControllerShortcut.#buttonsStatus[gamepadIndex] = pressed;
|
||||
ControllerShortcut.buttonsStatus[gamepadIndex] = pressed;
|
||||
return otherButtonPressed;
|
||||
}
|
||||
|
||||
static #runAction(action: ShortcutAction) {
|
||||
switch (action) {
|
||||
case ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW:
|
||||
SettingsNavigationDialog.getInstance().show();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_SCREENSHOT_CAPTURE:
|
||||
Screenshot.takeScreenshot();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_STATS_TOGGLE:
|
||||
StreamStats.getInstance().toggle();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_MICROPHONE_TOGGLE:
|
||||
MicrophoneShortcut.toggle();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_MENU_SHOW:
|
||||
StreamUiShortcut.showHideStreamMenu();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_SOUND_TOGGLE:
|
||||
SoundShortcut.muteUnmute();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_VOLUME_INC:
|
||||
SoundShortcut.adjustGainNodeVolume(10);
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_VOLUME_DEC:
|
||||
SoundShortcut.adjustGainNodeVolume(-10);
|
||||
break;
|
||||
|
||||
case ShortcutAction.DEVICE_BRIGHTNESS_INC:
|
||||
case ShortcutAction.DEVICE_BRIGHTNESS_DEC:
|
||||
case ShortcutAction.DEVICE_SOUND_TOGGLE:
|
||||
case ShortcutAction.DEVICE_VOLUME_INC:
|
||||
case ShortcutAction.DEVICE_VOLUME_DEC:
|
||||
AppInterface && AppInterface.runShortcut && AppInterface.runShortcut(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static #updateAction(profile: string, button: GamepadKey, action: ShortcutAction | null) {
|
||||
const actions = ControllerShortcut.#ACTIONS!;
|
||||
if (!(profile in actions)) {
|
||||
actions[profile] = [];
|
||||
}
|
||||
|
||||
if (!action) {
|
||||
action = null;
|
||||
}
|
||||
|
||||
actions[profile][button] = action;
|
||||
|
||||
// Remove empty profiles
|
||||
for (const key in ControllerShortcut.#ACTIONS) {
|
||||
let empty = true;
|
||||
for (const value of ControllerShortcut.#ACTIONS[key]) {
|
||||
if (!!value) {
|
||||
empty = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty) {
|
||||
delete ControllerShortcut.#ACTIONS[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Save to storage
|
||||
window.localStorage.setItem(ControllerShortcut.#STORAGE_KEY, JSON.stringify(ControllerShortcut.#ACTIONS));
|
||||
|
||||
console.log(ControllerShortcut.#ACTIONS);
|
||||
}
|
||||
|
||||
static #updateProfileList(e?: GamepadEvent) {
|
||||
const $select = ControllerShortcut.#$selectProfile;
|
||||
const $container = ControllerShortcut.#$container;
|
||||
|
||||
const $fragment = document.createDocumentFragment();
|
||||
|
||||
// Remove old profiles
|
||||
removeChildElements($select);
|
||||
|
||||
const gamepads = navigator.getGamepads();
|
||||
let hasGamepad = false;
|
||||
|
||||
for (const gamepad of gamepads) {
|
||||
if (!gamepad || !gamepad.connected) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore emulated gamepad
|
||||
if (gamepad.id === VIRTUAL_GAMEPAD_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasGamepad = true;
|
||||
|
||||
const $option = CE<HTMLOptionElement>('option', {value: gamepad.id}, gamepad.id);
|
||||
$fragment.appendChild($option);
|
||||
}
|
||||
|
||||
$container.dataset.hasGamepad = hasGamepad.toString();
|
||||
if (hasGamepad) {
|
||||
$select.appendChild($fragment);
|
||||
|
||||
$select.selectedIndex = 0;
|
||||
$select.dispatchEvent(new Event('input'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static #switchProfile(profile: string) {
|
||||
let actions = ControllerShortcut.#ACTIONS![profile];
|
||||
if (!actions) {
|
||||
actions = [];
|
||||
}
|
||||
|
||||
// Reset selects' values
|
||||
let button: any;
|
||||
for (button in ControllerShortcut.#$selectActions) {
|
||||
const $select = ControllerShortcut.#$selectActions[button as GamepadKey]!;
|
||||
$select.value = actions[button] || '';
|
||||
|
||||
BxEvent.dispatch($select, 'input', {
|
||||
ignoreOnChange: true,
|
||||
manualTrigger: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static #getActionsFromStorage() {
|
||||
return JSON.parse(window.localStorage.getItem(ControllerShortcut.#STORAGE_KEY) || '{}');
|
||||
}
|
||||
|
||||
static renderSettings() {
|
||||
const PREF_CONTROLLER_FRIENDLY_UI = getPref(PrefKey.UI_CONTROLLER_FRIENDLY);
|
||||
|
||||
// Read actions from localStorage
|
||||
ControllerShortcut.#ACTIONS = ControllerShortcut.#getActionsFromStorage();
|
||||
|
||||
const buttons: Map<GamepadKey, PrompFont> = new Map();
|
||||
buttons.set(GamepadKey.Y, PrompFont.Y);
|
||||
buttons.set(GamepadKey.A, PrompFont.A);
|
||||
buttons.set(GamepadKey.B, PrompFont.B);
|
||||
buttons.set(GamepadKey.X, PrompFont.X);
|
||||
|
||||
buttons.set(GamepadKey.UP, PrompFont.UP);
|
||||
buttons.set(GamepadKey.DOWN, PrompFont.DOWN);
|
||||
buttons.set(GamepadKey.LEFT, PrompFont.LEFT);
|
||||
buttons.set(GamepadKey.RIGHT, PrompFont.RIGHT);
|
||||
|
||||
buttons.set(GamepadKey.SELECT, PrompFont.SELECT);
|
||||
buttons.set(GamepadKey.START, PrompFont.START);
|
||||
|
||||
buttons.set(GamepadKey.LB, PrompFont.LB);
|
||||
buttons.set(GamepadKey.RB, PrompFont.RB);
|
||||
|
||||
buttons.set(GamepadKey.LT, PrompFont.LT);
|
||||
buttons.set(GamepadKey.RT, PrompFont.RT);
|
||||
|
||||
buttons.set(GamepadKey.L3, PrompFont.L3);
|
||||
buttons.set(GamepadKey.R3, PrompFont.R3);
|
||||
|
||||
const actions: {[key: string]: Partial<{[key in ShortcutAction]: string | string[]}>} = {
|
||||
[t('better-xcloud')]: {
|
||||
[ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW]: [t('settings'), t('show')],
|
||||
},
|
||||
|
||||
[t('device')]: AppInterface && {
|
||||
[ShortcutAction.DEVICE_SOUND_TOGGLE]: [t('sound'), t('toggle')],
|
||||
[ShortcutAction.DEVICE_VOLUME_INC]: [t('volume'), t('increase')],
|
||||
[ShortcutAction.DEVICE_VOLUME_DEC]: [t('volume'), t('decrease')],
|
||||
|
||||
[ShortcutAction.DEVICE_BRIGHTNESS_INC]: [t('brightness'), t('increase')],
|
||||
[ShortcutAction.DEVICE_BRIGHTNESS_DEC]: [t('brightness'), t('decrease')],
|
||||
},
|
||||
|
||||
[t('stream')]: {
|
||||
[ShortcutAction.STREAM_SCREENSHOT_CAPTURE]: t('take-screenshot'),
|
||||
|
||||
[ShortcutAction.STREAM_SOUND_TOGGLE]: [t('sound'), t('toggle')],
|
||||
[ShortcutAction.STREAM_VOLUME_INC]: getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && [t('volume'), t('increase')],
|
||||
[ShortcutAction.STREAM_VOLUME_DEC]: getPref(PrefKey.AUDIO_ENABLE_VOLUME_CONTROL) && [t('volume'), t('decrease')],
|
||||
|
||||
[ShortcutAction.STREAM_MENU_SHOW]: [t('menu'), t('show')],
|
||||
[ShortcutAction.STREAM_STATS_TOGGLE]: [t('stats'), t('show-hide')],
|
||||
[ShortcutAction.STREAM_MICROPHONE_TOGGLE]: [t('microphone'), t('toggle')],
|
||||
},
|
||||
};
|
||||
|
||||
const $baseSelect = CE<HTMLSelectElement>('select', {autocomplete: 'off'}, CE('option', {value: ''}, '---'));
|
||||
for (const groupLabel in actions) {
|
||||
const items = actions[groupLabel];
|
||||
if (!items) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const $optGroup = CE<HTMLOptGroupElement>('optgroup', {'label': groupLabel});
|
||||
|
||||
for (const action in items) {
|
||||
let label = items[action as keyof typeof items];
|
||||
if (!label) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(label)) {
|
||||
label = label.join(' ❯ ');
|
||||
}
|
||||
|
||||
const $option = CE<HTMLOptionElement>('option', {value: action}, label);
|
||||
$optGroup.appendChild($option);
|
||||
}
|
||||
|
||||
$baseSelect.appendChild($optGroup);
|
||||
}
|
||||
|
||||
let $remap: HTMLElement;
|
||||
const $selectProfile = CE<HTMLSelectElement>('select', {class: 'bx-shortcut-profile', autocomplete: 'off'});
|
||||
|
||||
const $profile = PREF_CONTROLLER_FRIENDLY_UI ? BxSelectElement.wrap($selectProfile) : $selectProfile;
|
||||
$profile.classList.add('bx-full-width');
|
||||
|
||||
const $container = CE('div', {
|
||||
'data-has-gamepad': 'false',
|
||||
_nearby: {
|
||||
focus: $profile,
|
||||
},
|
||||
},
|
||||
CE('div', {},
|
||||
CE('p', {class: 'bx-shortcut-note'}, t('controller-shortcuts-connect-note')),
|
||||
),
|
||||
|
||||
$remap = CE('div', {},
|
||||
CE('div', {
|
||||
_nearby: {
|
||||
focus: $profile,
|
||||
},
|
||||
}, $profile),
|
||||
CE('p', {class: 'bx-shortcut-note'},
|
||||
CE('span', {class: 'bx-prompt'}, PrompFont.HOME),
|
||||
': ' + t('controller-shortcuts-xbox-note'),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$selectProfile.addEventListener('input', e => {
|
||||
ControllerShortcut.#switchProfile($selectProfile.value);
|
||||
});
|
||||
|
||||
const onActionChanged = (e: Event) => {
|
||||
const $target = e.target as HTMLSelectElement;
|
||||
|
||||
const profile = $selectProfile.value;
|
||||
const button: unknown = $target.dataset.button;
|
||||
const action = $target.value as ShortcutAction;
|
||||
|
||||
if (!PREF_CONTROLLER_FRIENDLY_UI) {
|
||||
const $fakeSelect = $target.previousElementSibling! as HTMLSelectElement;
|
||||
let fakeText = '---';
|
||||
if (action) {
|
||||
const $selectedOption = $target.options[$target.selectedIndex];
|
||||
const $optGroup = $selectedOption.parentElement as HTMLOptGroupElement;
|
||||
fakeText = $optGroup.label + ' ❯ ' + $selectedOption.text;
|
||||
}
|
||||
($fakeSelect.firstElementChild as HTMLOptionElement).text = fakeText;
|
||||
}
|
||||
|
||||
!(e as any).ignoreOnChange && ControllerShortcut.#updateAction(profile, button as GamepadKey, action);
|
||||
};
|
||||
|
||||
|
||||
// @ts-ignore
|
||||
for (const [button, prompt] of buttons) {
|
||||
const $row = CE('div', {
|
||||
class: 'bx-shortcut-row',
|
||||
});
|
||||
|
||||
const $label = CE('label', {class: 'bx-prompt'}, `${PrompFont.HOME} + ${prompt}`);
|
||||
|
||||
const $div = CE('div', {class: 'bx-shortcut-actions'});
|
||||
|
||||
if (!PREF_CONTROLLER_FRIENDLY_UI) {
|
||||
const $fakeSelect = CE<HTMLSelectElement>('select', {autocomplete: 'off'},
|
||||
CE('option', {}, '---'),
|
||||
);
|
||||
|
||||
$div.appendChild($fakeSelect);
|
||||
}
|
||||
|
||||
const $select = $baseSelect.cloneNode(true) as HTMLSelectElement;
|
||||
$select.dataset.button = button.toString();
|
||||
$select.addEventListener('input', onActionChanged);
|
||||
|
||||
ControllerShortcut.#$selectActions[button] = $select;
|
||||
|
||||
if (PREF_CONTROLLER_FRIENDLY_UI) {
|
||||
const $bxSelect = BxSelectElement.wrap($select);
|
||||
$bxSelect.classList.add('bx-full-width');
|
||||
|
||||
$div.appendChild($bxSelect);
|
||||
setNearby($row, {
|
||||
focus: $bxSelect,
|
||||
});
|
||||
} else {
|
||||
$div.appendChild($select);
|
||||
setNearby($row, {
|
||||
focus: $select,
|
||||
});
|
||||
}
|
||||
|
||||
$row.appendChild($label);
|
||||
$row.appendChild($div);
|
||||
|
||||
$remap.appendChild($row);
|
||||
}
|
||||
|
||||
$container.appendChild($remap);
|
||||
|
||||
ControllerShortcut.#$selectProfile = $selectProfile;
|
||||
ControllerShortcut.#$container = $container;
|
||||
|
||||
// Detect when gamepad connected/disconnect
|
||||
window.addEventListener('gamepadconnected', ControllerShortcut.#updateProfileList);
|
||||
window.addEventListener('gamepaddisconnected', ControllerShortcut.#updateProfileList);
|
||||
|
||||
ControllerShortcut.#updateProfileList();
|
||||
|
||||
return $container;
|
||||
}
|
||||
}
|
||||
|
145
src/modules/device-vibration-manager.ts
Executable file
@ -0,0 +1,145 @@
|
||||
import { AppInterface, STATES } from "@utils/global";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { StreamSettings } from "@/utils/stream-settings";
|
||||
|
||||
const VIBRATION_DATA_MAP = {
|
||||
gamepadIndex: 8,
|
||||
leftMotorPercent: 8,
|
||||
rightMotorPercent: 8,
|
||||
leftTriggerMotorPercent: 8,
|
||||
rightTriggerMotorPercent: 8,
|
||||
durationMs: 16,
|
||||
// delayMs: 16,
|
||||
// repeat: 8,
|
||||
};
|
||||
|
||||
type VibrationData = {
|
||||
[key in keyof typeof VIBRATION_DATA_MAP]?: number;
|
||||
}
|
||||
|
||||
export class DeviceVibrationManager {
|
||||
private static instance: DeviceVibrationManager | null | undefined;
|
||||
public static getInstance(): typeof DeviceVibrationManager['instance'] {
|
||||
if (typeof DeviceVibrationManager.instance === 'undefined') {
|
||||
if (STATES.browser.capabilities.deviceVibration) {
|
||||
DeviceVibrationManager.instance = new DeviceVibrationManager();
|
||||
} else {
|
||||
DeviceVibrationManager.instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceVibrationManager.instance;
|
||||
}
|
||||
|
||||
private dataChannel: RTCDataChannel | null = null;
|
||||
private boundOnMessage: (e: MessageEvent) => void;
|
||||
|
||||
constructor() {
|
||||
this.boundOnMessage = this.onMessage.bind(this);
|
||||
|
||||
window.addEventListener(BxEvent.DATA_CHANNEL_CREATED, e => {
|
||||
const dataChannel = (e as any).dataChannel as RTCDataChannel;
|
||||
if (dataChannel?.label === 'input') {
|
||||
this.reset();
|
||||
|
||||
this.dataChannel = dataChannel;
|
||||
this.setupDataChannel();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener(BxEvent.DEVICE_VIBRATION_CHANGED, e => {
|
||||
this.setupDataChannel();
|
||||
});
|
||||
}
|
||||
|
||||
private setupDataChannel() {
|
||||
if (!this.dataChannel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeEventListeners();
|
||||
|
||||
if (window.BX_STREAM_SETTINGS.deviceVibrationIntensity > 0) {
|
||||
this.dataChannel.addEventListener('message', this.boundOnMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private playVibration(data: Required<VibrationData>) {
|
||||
const vibrationIntensity = StreamSettings.settings.deviceVibrationIntensity;
|
||||
if (AppInterface) {
|
||||
AppInterface.vibrate(JSON.stringify(data), vibrationIntensity);
|
||||
return;
|
||||
}
|
||||
|
||||
const realIntensity = Math.min(100, data.leftMotorPercent + data.rightMotorPercent / 2) * vibrationIntensity;
|
||||
if (realIntensity === 0 || realIntensity === 100) {
|
||||
// Stop vibration
|
||||
window.navigator.vibrate(realIntensity ? data.durationMs : 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const pulseDuration = 200;
|
||||
const onDuration = Math.floor(pulseDuration * realIntensity / 100);
|
||||
const offDuration = pulseDuration - onDuration;
|
||||
|
||||
const repeats = Math.ceil(data.durationMs / pulseDuration);
|
||||
const pulses = Array(repeats).fill([onDuration, offDuration]).flat();
|
||||
|
||||
window.navigator.vibrate(pulses);
|
||||
}
|
||||
|
||||
onMessage(e: MessageEvent) {
|
||||
if (typeof e !== 'object' || !(e.data instanceof ArrayBuffer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataView = new DataView(e.data);
|
||||
let offset = 0;
|
||||
|
||||
let messageType;
|
||||
if (dataView.byteLength === 13) { // version >= 8
|
||||
messageType = dataView.getUint16(offset, true);
|
||||
offset += Uint16Array.BYTES_PER_ELEMENT;
|
||||
} else {
|
||||
messageType = dataView.getUint8(offset);
|
||||
offset += Uint8Array.BYTES_PER_ELEMENT;
|
||||
}
|
||||
|
||||
if (!(messageType & 128)) { // Vibration
|
||||
return;
|
||||
}
|
||||
|
||||
const vibrationType = dataView.getUint8(offset);
|
||||
offset += Uint8Array.BYTES_PER_ELEMENT;
|
||||
|
||||
if (vibrationType !== 0) { // FourMotorRumble
|
||||
return;
|
||||
}
|
||||
|
||||
const data: VibrationData = {};
|
||||
let key: keyof typeof VIBRATION_DATA_MAP;
|
||||
for (key in VIBRATION_DATA_MAP) {
|
||||
if (VIBRATION_DATA_MAP[key] === 16) {
|
||||
data[key] = dataView.getUint16(offset, true);
|
||||
offset += Uint16Array.BYTES_PER_ELEMENT;
|
||||
} else {
|
||||
data[key] = dataView.getUint8(offset);
|
||||
offset += Uint8Array.BYTES_PER_ELEMENT;
|
||||
}
|
||||
}
|
||||
|
||||
this.playVibration(data as Required<VibrationData>);
|
||||
}
|
||||
|
||||
private removeEventListeners() {
|
||||
// Clear event listeners in previous DataChannel
|
||||
try {
|
||||
this.dataChannel?.removeEventListener('message', this.boundOnMessage);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.removeEventListeners();
|
||||
this.dataChannel = null;
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
import { t } from "@utils/translation";
|
||||
import { CE, createButton, ButtonStyle } from "@utils/html";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
|
||||
type DialogOptions = Partial<{
|
||||
title: string;
|
||||
className: string;
|
||||
content: string | HTMLElement;
|
||||
hideCloseButton: boolean;
|
||||
onClose: string;
|
||||
helpUrl: string;
|
||||
}>;
|
||||
|
||||
export class Dialog {
|
||||
$dialog: HTMLElement;
|
||||
$title: HTMLElement;
|
||||
$content: HTMLElement;
|
||||
$overlay: HTMLElement;
|
||||
|
||||
onClose: any;
|
||||
|
||||
constructor(options: DialogOptions) {
|
||||
const {
|
||||
title,
|
||||
className,
|
||||
content,
|
||||
hideCloseButton,
|
||||
onClose,
|
||||
helpUrl,
|
||||
} = options;
|
||||
|
||||
// Create dialog overlay
|
||||
const $overlay = document.querySelector('.bx-dialog-overlay') as HTMLElement;
|
||||
|
||||
if (!$overlay) {
|
||||
this.$overlay = CE('div', {'class': 'bx-dialog-overlay bx-gone'});
|
||||
|
||||
// Disable right click
|
||||
this.$overlay.addEventListener('contextmenu', e => e.preventDefault());
|
||||
|
||||
document.documentElement.appendChild(this.$overlay);
|
||||
} else {
|
||||
this.$overlay = $overlay;
|
||||
}
|
||||
|
||||
let $close;
|
||||
this.onClose = onClose;
|
||||
this.$dialog = CE('div', {'class': `bx-dialog ${className || ''} bx-gone`},
|
||||
this.$title = CE('h2', {}, CE('b', {}, title),
|
||||
helpUrl && createButton({
|
||||
icon: BxIcon.QUESTION,
|
||||
style: ButtonStyle.GHOST,
|
||||
title: t('help'),
|
||||
url: helpUrl,
|
||||
}),
|
||||
),
|
||||
this.$content = CE('div', {'class': 'bx-dialog-content'}, content),
|
||||
!hideCloseButton && ($close = CE('button', {type: 'button'}, t('close'))),
|
||||
);
|
||||
|
||||
$close && $close.addEventListener('click', e => {
|
||||
this.hide(e);
|
||||
});
|
||||
|
||||
!title && this.$title.classList.add('bx-gone');
|
||||
!content && this.$content.classList.add('bx-gone');
|
||||
|
||||
// Disable right click
|
||||
this.$dialog.addEventListener('contextmenu', e => e.preventDefault());
|
||||
|
||||
document.documentElement.appendChild(this.$dialog);
|
||||
}
|
||||
|
||||
show(newOptions: DialogOptions) {
|
||||
// Clear focus
|
||||
document.activeElement && (document.activeElement as HTMLElement).blur();
|
||||
|
||||
if (newOptions && newOptions.title) {
|
||||
this.$title.querySelector('b')!.textContent = newOptions.title;
|
||||
this.$title.classList.remove('bx-gone');
|
||||
}
|
||||
|
||||
this.$dialog.classList.remove('bx-gone');
|
||||
this.$overlay.classList.remove('bx-gone');
|
||||
|
||||
document.body.classList.add('bx-no-scroll');
|
||||
}
|
||||
|
||||
hide(e?: any) {
|
||||
this.$dialog.classList.add('bx-gone');
|
||||
this.$overlay.classList.add('bx-gone');
|
||||
|
||||
document.body.classList.remove('bx-no-scroll');
|
||||
|
||||
this.onClose && this.onClose(e);
|
||||
}
|
||||
|
||||
toggle() {
|
||||
this.$dialog.classList.toggle('bx-gone');
|
||||
this.$overlay.classList.toggle('bx-gone');
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
export abstract class BaseGameBarAction {
|
||||
constructor() {}
|
||||
reset() {}
|
||||
|
||||
abstract render(): HTMLElement;
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { TouchController } from "@modules/touch-controller";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { t } from "@utils/translation";
|
||||
|
||||
export class TouchControlAction extends BaseGameBarAction {
|
||||
$content: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
|
||||
const $parent = (e as any).target.closest('div[data-enabled]');
|
||||
let enabled = $parent.getAttribute('data-enabled', 'true') === 'true';
|
||||
$parent.setAttribute('data-enabled', (!enabled).toString());
|
||||
|
||||
TouchController.toggleVisibility(enabled);
|
||||
};
|
||||
|
||||
const $btnEnable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TOUCH_CONTROL_ENABLE,
|
||||
title: t('show-touch-controller'),
|
||||
onClick: onClick,
|
||||
});
|
||||
|
||||
const $btnDisable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
||||
title: t('hide-touch-controller'),
|
||||
onClick: onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnEnable,
|
||||
$btnDisable,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.$content.setAttribute('data-enabled', 'true');
|
||||
}
|
||||
}
|
16
src/modules/game-bar/base-action.ts
Executable file
@ -0,0 +1,16 @@
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
export abstract class BaseGameBarAction {
|
||||
abstract $content: HTMLElement;
|
||||
|
||||
constructor() {}
|
||||
reset() {}
|
||||
|
||||
onClick(e: Event) {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
};
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
};
|
||||
}
|
111
src/modules/game-bar/game-bar.ts
Normal file → Executable file
@ -1,50 +1,62 @@
|
||||
import { CE, clearFocus, createSvgIcon } from "@utils/html";
|
||||
import { ScreenshotAction } from "./action-screenshot";
|
||||
import { TouchControlAction } from "./action-touch-control";
|
||||
import { CE, createSvgIcon } from "@utils/html";
|
||||
import { ScreenshotAction } from "./screenshot-action";
|
||||
import { TouchControlAction } from "./touch-control-action";
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import type { BaseGameBarAction } from "./action-base";
|
||||
import type { BaseGameBarAction } from "./base-action";
|
||||
import { STATES } from "@utils/global";
|
||||
import { MicrophoneAction } from "./action-microphone";
|
||||
import { MicrophoneAction } from "./microphone-action";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref, StreamTouchController } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { TrueAchievementsAction } from "./action-true-achievements";
|
||||
import { SpeakerAction } from "./action-speaker";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { TrueAchievementsAction } from "./true-achievements-action";
|
||||
import { SpeakerAction } from "./speaker-action";
|
||||
import { RendererAction } from "./renderer-action";
|
||||
import { BxLogger } from "@/utils/bx-logger";
|
||||
import { GameBarPosition, TouchControllerMode } from "@/enums/pref-values";
|
||||
|
||||
|
||||
export class GameBar {
|
||||
private static instance: GameBar;
|
||||
public static getInstance(): GameBar {
|
||||
if (!GameBar.instance) {
|
||||
GameBar.instance = new GameBar();
|
||||
private static instance: GameBar | null | undefined;
|
||||
public static getInstance(): typeof GameBar['instance'] {
|
||||
if (typeof GameBar.instance === 'undefined') {
|
||||
if (getPref<GameBarPosition>(PrefKey.GAME_BAR_POSITION) !== GameBarPosition.OFF) {
|
||||
GameBar.instance = new GameBar();
|
||||
} else {
|
||||
GameBar.instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
return GameBar.instance;
|
||||
}
|
||||
|
||||
private readonly LOG_TAG = 'GameBar';
|
||||
|
||||
private static readonly VISIBLE_DURATION = 2000;
|
||||
|
||||
private $gameBar: HTMLElement;
|
||||
private $container: HTMLElement;
|
||||
|
||||
private timeout: number | null = null;
|
||||
private timeoutId: number | null = null;
|
||||
|
||||
private actions: BaseGameBarAction[] = [];
|
||||
|
||||
private constructor() {
|
||||
BxLogger.info(this.LOG_TAG, 'constructor()');
|
||||
|
||||
let $container;
|
||||
|
||||
const position = getPref(PrefKey.GAME_BAR_POSITION);
|
||||
const position = getPref<GameBarPosition>(PrefKey.GAME_BAR_POSITION);
|
||||
|
||||
const $gameBar = CE('div', {id: 'bx-game-bar', class: 'bx-gone', 'data-position': position},
|
||||
$container = CE('div', {class: 'bx-game-bar-container bx-offscreen'}),
|
||||
createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT),
|
||||
);
|
||||
const $gameBar = CE('div', { id: 'bx-game-bar', class: 'bx-gone', 'data-position': position },
|
||||
$container = CE('div', { class: 'bx-game-bar-container bx-offscreen' }),
|
||||
createSvgIcon(position === 'bottom-left' ? BxIcon.CARET_RIGHT : BxIcon.CARET_LEFT),
|
||||
);
|
||||
|
||||
this.actions = [
|
||||
new ScreenshotAction(),
|
||||
...(STATES.userAgent.capabilities.touch && (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) !== StreamTouchController.OFF) ? [new TouchControlAction()] : []),
|
||||
...(STATES.userAgent.capabilities.touch && (getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) !== TouchControllerMode.OFF) ? [new TouchControlAction()] : []),
|
||||
new SpeakerAction(),
|
||||
new RendererAction(),
|
||||
new MicrophoneAction(),
|
||||
new TrueAchievementsAction(),
|
||||
];
|
||||
@ -69,18 +81,14 @@ export class GameBar {
|
||||
});
|
||||
|
||||
// Hide game bar after clicking on an action
|
||||
window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, this.hideBar.bind(this));
|
||||
window.addEventListener(BxEvent.GAME_BAR_ACTION_ACTIVATED, this.hideBar);
|
||||
|
||||
$container.addEventListener('pointerover', this.clearHideTimeout.bind(this));
|
||||
$container.addEventListener('pointerout', this.beginHideTimeout.bind(this));
|
||||
$container.addEventListener('pointerover', this.clearHideTimeout);
|
||||
$container.addEventListener('pointerout', this.beginHideTimeout);
|
||||
|
||||
// Add animation when hiding game bar
|
||||
$container.addEventListener('transitionend', e => {
|
||||
const classList = $container.classList;
|
||||
if (classList.contains('bx-hide')) {
|
||||
classList.remove('bx-hide');
|
||||
classList.add('bx-offscreen');
|
||||
}
|
||||
$container.classList.replace('bx-hide', 'bx-offscreen');
|
||||
});
|
||||
|
||||
document.documentElement.appendChild($gameBar);
|
||||
@ -88,64 +96,47 @@ export class GameBar {
|
||||
this.$container = $container;
|
||||
|
||||
// Enable/disable Game Bar when playing/pausing
|
||||
getPref(PrefKey.GAME_BAR_POSITION) !== 'off' && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => {
|
||||
if (!STATES.isPlaying) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
|
||||
position !== GameBarPosition.OFF && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => {
|
||||
// Toggle Game bar
|
||||
const mode = (e as any).mode;
|
||||
mode !== 'none' ? this.disable() : this.enable();
|
||||
if (STATES.isPlaying) {
|
||||
window.BX_STREAM_SETTINGS.xCloudPollingMode !== 'none' ? this.disable() : this.enable();
|
||||
}
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
private beginHideTimeout() {
|
||||
private beginHideTimeout = () => {
|
||||
this.clearHideTimeout();
|
||||
|
||||
this.timeout = window.setTimeout(() => {
|
||||
this.timeout = null;
|
||||
this.hideBar();
|
||||
}, GameBar.VISIBLE_DURATION);
|
||||
this.timeoutId = window.setTimeout(() => {
|
||||
this.timeoutId = null;
|
||||
this.hideBar();
|
||||
}, GameBar.VISIBLE_DURATION);
|
||||
}
|
||||
|
||||
private clearHideTimeout() {
|
||||
this.timeout && clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
private clearHideTimeout = () => {
|
||||
this.timeoutId && clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.$gameBar && this.$gameBar.classList.remove('bx-gone');
|
||||
this.$gameBar.classList.remove('bx-gone');
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.hideBar();
|
||||
this.$gameBar && this.$gameBar.classList.add('bx-gone');
|
||||
this.$gameBar.classList.add('bx-gone');
|
||||
}
|
||||
|
||||
showBar() {
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$container.classList.remove('bx-offscreen', 'bx-hide' , 'bx-gone');
|
||||
this.$container.classList.add('bx-show');
|
||||
|
||||
this.beginHideTimeout();
|
||||
}
|
||||
|
||||
hideBar() {
|
||||
hideBar = () => {
|
||||
this.clearHideTimeout();
|
||||
|
||||
// Stop focusing Game Bar
|
||||
clearFocus();
|
||||
|
||||
if (!this.$container) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$container.classList.remove('bx-show');
|
||||
this.$container.classList.add('bx-hide');
|
||||
this.$container.classList.replace('bx-show', 'bx-hide');
|
||||
}
|
||||
|
||||
// Reset all states
|
||||
|
36
src/modules/game-bar/action-microphone.ts → src/modules/game-bar/microphone-action.ts
Normal file → Executable file
@ -1,63 +1,49 @@
|
||||
import { BxEvent } from "@utils/bx-event";
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/shortcut-microphone";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { MicrophoneShortcut, MicrophoneState } from "../shortcuts/microphone-shortcut";
|
||||
|
||||
|
||||
export class MicrophoneAction extends BaseGameBarAction {
|
||||
$content: HTMLElement;
|
||||
|
||||
visible: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const onClick = (e: Event) => {
|
||||
BxEvent.dispatch(window, BxEvent.GAME_BAR_ACTION_ACTIVATED);
|
||||
|
||||
const enabled = MicrophoneShortcut.toggle(false);
|
||||
this.$content.setAttribute('data-enabled', enabled.toString());
|
||||
};
|
||||
|
||||
const $btnDefault = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE,
|
||||
onClick: onClick,
|
||||
onClick: this.onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE_MUTED,
|
||||
onClick: onClick,
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
this.$content = CE('div', {},
|
||||
$btnDefault,
|
||||
$btnMuted,
|
||||
);
|
||||
|
||||
this.reset();
|
||||
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||
|
||||
window.addEventListener(BxEvent.MICROPHONE_STATE_CHANGED, e => {
|
||||
const microphoneState = (e as any).microphoneState;
|
||||
const enabled = microphoneState === MicrophoneState.ENABLED;
|
||||
|
||||
this.$content.setAttribute('data-enabled', enabled.toString());
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
|
||||
// Show the button in Game Bar if the mic is enabled
|
||||
this.$content.classList.remove('bx-gone');
|
||||
});
|
||||
}
|
||||
|
||||
render(): HTMLElement {
|
||||
return this.$content;
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
const enabled = MicrophoneShortcut.toggle(false);
|
||||
this.$content.dataset.activated = enabled.toString();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.visible = false;
|
||||
this.$content.classList.add('bx-gone');
|
||||
this.$content.setAttribute('data-enabled', 'false');
|
||||
this.$content.dataset.activated = 'false';
|
||||
}
|
||||
}
|
43
src/modules/game-bar/renderer-action.ts
Executable file
@ -0,0 +1,43 @@
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { RendererShortcut } from "../shortcuts/renderer-shortcut";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
|
||||
export class RendererAction extends BaseGameBarAction {
|
||||
$content: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const $btnDefault = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.EYE,
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
const $btnActivated = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.EYE_SLASH,
|
||||
onClick: this.onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {}, $btnDefault, $btnActivated);
|
||||
|
||||
window.addEventListener(BxEvent.VIDEO_VISIBILITY_CHANGED, e => {
|
||||
const isShowing = (e as any).isShowing;
|
||||
this.$content.dataset.activated = (!isShowing).toString();
|
||||
});
|
||||
}
|
||||
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
RendererShortcut.toggleVisibility();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.$content.dataset.activated = 'false';
|
||||
}
|
||||
}
|