6.0
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
5294
dist/better-xcloud.lite.user.js
vendored
Normal file → Executable file
818
dist/better-xcloud.user.js
vendored
Normal file → Executable file
0
eslint.config.mjs
Normal file → Executable file
2
package.json
Normal file → Executable file
@ -13,7 +13,7 @@
|
||||
"@types/bun": "^1.1.14",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/stylus": "^0.48.43",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint": "^9.16.0",
|
||||
"eslint-plugin-compat": "^6.0.1",
|
||||
"stylus": "^0.64.0"
|
||||
},
|
||||
|
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 {
|
||||
|
0
src/assets/css/game-bar.styl
Normal file → Executable file
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: 13px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
0
src/assets/css/stream-stats.styl
Normal file → Executable file
0
src/assets/css/stream.styl
Normal file → Executable file
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 |
0
src/assets/svg/eye-slash.svg
Normal file → Executable file
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
0
src/assets/svg/eye.svg
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB 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;
|
||||
|
0
src/enums/game-pass-gallery.ts
Normal file → Executable file
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,102 +1,114 @@
|
||||
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',
|
||||
FORCE_NATIVE_MKB_GAMES = 'nativeMkb.forcedGames',
|
||||
NATIVE_MKB_SCROLL_HORIZONTAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityX',
|
||||
NATIVE_MKB_SCROLL_VERTICAL_SENSITIVITY = 'nativeMkb.scroll.sensitivityY',
|
||||
|
||||
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',
|
||||
CONTROLLER_POLLING_RATE = 'controller_polling_rate',
|
||||
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_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_MAX_FPS = 'video_max_fps',
|
||||
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',
|
||||
|
||||
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_MSFS2020_FORCE_NATIVE_MKB = 'game_msfs2020_force_native_mkb',
|
||||
GAME_FORTNITE_FORCE_CONSOLE = 'game.fortnite.forceConsole',
|
||||
}
|
||||
|
104
src/enums/pref-values.ts
Executable file
@ -0,0 +1,104 @@
|
||||
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 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
88
src/index.ts
Normal file → Executable file
@ -7,7 +7,7 @@ import { BxExposed } from "@utils/bx-exposed";
|
||||
import { t } from "@utils/translation";
|
||||
import { interceptHttpRequests } from "@utils/network";
|
||||
import { CE } from "@utils/html";
|
||||
import { showGamepadToast, updatePollingRate } from "@utils/gamepad";
|
||||
import { showGamepadToast } from "@utils/gamepad";
|
||||
import { EmulatedMkbHandler } from "@modules/mkb/mkb-handler";
|
||||
import { StreamBadges } from "@modules/stream/stream-badges";
|
||||
import { StreamStats } from "@modules/stream/stream-stats";
|
||||
@ -19,7 +19,6 @@ import { checkForUpdate, disablePwa, productTitleToSlug } from "@utils/utils";
|
||||
import { Patcher } from "@modules/patcher";
|
||||
import { RemotePlayManager } from "@/modules/remote-play-manager";
|
||||
import { onHistoryChanged, patchHistoryMethod } from "@utils/history";
|
||||
import { VibrationManager } from "@modules/vibration-manager";
|
||||
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";
|
||||
@ -28,19 +27,23 @@ 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')) {
|
||||
@ -160,14 +163,14 @@ 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)) {
|
||||
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');
|
||||
}
|
||||
@ -194,7 +197,7 @@ window.addEventListener(BxEvent.XCLOUD_SERVERS_UNAVAILABLE, e => {
|
||||
// Open Settings dialog on Unsupported page
|
||||
const $unsupportedPage = document.querySelector<HTMLElement>('div[class^=UnsupportedMarketPage-module__container]');
|
||||
if ($unsupportedPage) {
|
||||
SettingsNavigationDialog.getInstance().show();
|
||||
SettingsDialog.getInstance().show();
|
||||
}
|
||||
}, {once: true});
|
||||
|
||||
@ -213,35 +216,49 @@ 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') {
|
||||
const gameBar = GameBar.getInstance();
|
||||
gameBar.reset();
|
||||
gameBar.enable();
|
||||
gameBar.showBar();
|
||||
}
|
||||
|
||||
if (isFullVersion()) {
|
||||
const gameBar = GameBar.getInstance();
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
updateVideoPlayer();
|
||||
});
|
||||
|
||||
@ -294,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
|
||||
@ -312,9 +333,10 @@ function unload() {
|
||||
StreamBadges.getInstance().destroy();
|
||||
|
||||
if (isFullVersion()) {
|
||||
MouseCursorHider.stop();
|
||||
MouseCursorHider.getInstance()?.stop();
|
||||
TouchController.reset();
|
||||
(getPref(PrefKey.GAME_BAR_POSITION) !== 'off') && GameBar.getInstance().disable();
|
||||
|
||||
GameBar.getInstance()?.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,10 +351,15 @@ isFullVersion() && window.addEventListener(BxEvent.CAPTURE_SCREENSHOT, e => {
|
||||
|
||||
|
||||
function main() {
|
||||
if (getPref(PrefKey.GAME_MSFS2020_FORCE_NATIVE_MKB)) {
|
||||
BX_FLAGS.ForceNativeMkbTitles.push('9PMQDM08SNK9');
|
||||
GhPagesUtils.fetchLatestCommit();
|
||||
|
||||
if (getPref<NativeMkbMode>(PrefKey.NATIVE_MKB_MODE) === NativeMkbMode.ON) {
|
||||
const customList = getPref<string[]>(PrefKey.FORCE_NATIVE_MKB_GAMES);
|
||||
BX_FLAGS.ForceNativeMkbTitles.push(...customList);
|
||||
}
|
||||
|
||||
StreamSettings.setup();
|
||||
|
||||
// Monkey patches
|
||||
patchRtcPeerConnection();
|
||||
patchRtcCodecs();
|
||||
@ -341,7 +368,7 @@ 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();
|
||||
@ -359,10 +386,9 @@ function main() {
|
||||
StreamStats.setupEvents();
|
||||
|
||||
if (isFullVersion()) {
|
||||
updatePollingRate();
|
||||
STATES.userAgent.capabilities.touch && TouchController.updateCustomList();
|
||||
|
||||
VibrationManager.initialSetup();
|
||||
DeviceVibrationManager.getInstance();
|
||||
|
||||
// Check for Update
|
||||
BX_FLAGS.CheckForUpdate && checkForUpdate();
|
||||
@ -375,7 +401,7 @@ function main() {
|
||||
RemotePlayManager.detect();
|
||||
}
|
||||
|
||||
if (getPref(PrefKey.STREAM_TOUCH_CONTROLLER) === StreamTouchController.ALL) {
|
||||
if (getPref<TouchControllerMode>(PrefKey.TOUCH_CONTROLLER_MODE) === TouchControllerMode.ALL) {
|
||||
TouchController.setup();
|
||||
}
|
||||
|
||||
@ -392,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
396
src/modules/controller-shortcut.ts
Normal file → Executable file
@ -1,70 +1,29 @@
|
||||
import { ScreenshotManager } from "@/utils/screenshot-manager";
|
||||
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 {
|
||||
private static readonly STORAGE_KEY = 'better_xcloud_controller_shortcuts';
|
||||
|
||||
private static buttonsCache: {[key: string]: boolean[]} = {};
|
||||
private static buttonsStatus: {[key: string]: boolean[]} = {};
|
||||
|
||||
private static $selectProfile: HTMLSelectElement;
|
||||
private static $selectActions: Partial<{[key in GamepadKey]: HTMLSelectElement}> = {};
|
||||
private static $container: HTMLElement;
|
||||
|
||||
private static ACTIONS: {[key: string]: (ShortcutAction | null)[]} | null = null;
|
||||
|
||||
static reset(index: number) {
|
||||
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);
|
||||
// Clear the buttons status
|
||||
@ -74,7 +33,9 @@ export class ControllerShortcut {
|
||||
let otherButtonPressed = false;
|
||||
|
||||
const entries = gamepad.buttons.entries();
|
||||
for (const [index, button] of 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;
|
||||
@ -82,7 +43,8 @@ export class ControllerShortcut {
|
||||
|
||||
// If this is newly pressed button -> run action
|
||||
if (actions[index] && !ControllerShortcut.buttonsCache[gamepadIndex][index]) {
|
||||
setTimeout(() => ControllerShortcut.runAction(actions[index]!), 0);
|
||||
const idx = index;
|
||||
setTimeout(() => ShortcutHandler.runAction(actions[idx]!), 0);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -90,336 +52,4 @@ export class ControllerShortcut {
|
||||
ControllerShortcut.buttonsStatus[gamepadIndex] = pressed;
|
||||
return otherButtonPressed;
|
||||
}
|
||||
|
||||
private static runAction(action: ShortcutAction) {
|
||||
switch (action) {
|
||||
case ShortcutAction.BETTER_XCLOUD_SETTINGS_SHOW:
|
||||
SettingsNavigationDialog.getInstance().show();
|
||||
break;
|
||||
|
||||
case ShortcutAction.STREAM_SCREENSHOT_CAPTURE:
|
||||
ScreenshotManager.getInstance().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;
|
||||
}
|
||||
}
|
||||
|
||||
private 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));
|
||||
}
|
||||
|
||||
private 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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private 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<HTMLElement>('.bx-dialog-overlay');
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
0
src/modules/game-bar/action-base.ts → src/modules/game-bar/base-action.ts
Normal file → Executable file
53
src/modules/game-bar/game-bar.ts
Normal file → Executable file
@ -1,22 +1,34 @@
|
||||
import { CE, createSvgIcon } from "@utils/html";
|
||||
import { ScreenshotAction } from "./action-screenshot";
|
||||
import { TouchControlAction } from "./action-touch-control";
|
||||
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, type GameBarPosition } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { TrueAchievementsAction } from "./action-true-achievements";
|
||||
import { SpeakerAction } from "./action-speaker";
|
||||
import { RendererAction } from "./action-renderer";
|
||||
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.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;
|
||||
@ -33,7 +45,7 @@ export class GameBar {
|
||||
|
||||
let $container;
|
||||
|
||||
const position = getPref(PrefKey.GAME_BAR_POSITION) as GameBarPosition;
|
||||
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'}),
|
||||
@ -42,7 +54,7 @@ export class GameBar {
|
||||
|
||||
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(),
|
||||
@ -69,10 +81,10 @@ 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 => {
|
||||
@ -84,16 +96,15 @@ 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) => {
|
||||
position !== GameBarPosition.OFF && window.addEventListener(BxEvent.XCLOUD_POLLING_MODE_CHANGED, ((e: Event) => {
|
||||
// Toggle Game bar
|
||||
if (STATES.isPlaying) {
|
||||
const mode = (e as any).mode;
|
||||
mode !== 'none' ? this.disable() : this.enable();
|
||||
window.BX_STREAM_SETTINGS.xCloudPollingMode !== 'none' ? this.disable() : this.enable();
|
||||
}
|
||||
}).bind(this));
|
||||
}
|
||||
|
||||
private beginHideTimeout() {
|
||||
private beginHideTimeout = () => {
|
||||
this.clearHideTimeout();
|
||||
|
||||
this.timeoutId = window.setTimeout(() => {
|
||||
@ -102,7 +113,7 @@ export class GameBar {
|
||||
}, GameBar.VISIBLE_DURATION);
|
||||
}
|
||||
|
||||
private clearHideTimeout() {
|
||||
private clearHideTimeout = () => {
|
||||
this.timeoutId && clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
}
|
||||
@ -123,7 +134,7 @@ export class GameBar {
|
||||
this.beginHideTimeout();
|
||||
}
|
||||
|
||||
hideBar() {
|
||||
hideBar = () => {
|
||||
this.clearHideTimeout();
|
||||
this.$container.classList.replace('bx-show', 'bx-hide');
|
||||
}
|
||||
|
10
src/modules/game-bar/action-microphone.ts → src/modules/game-bar/microphone-action.ts
Normal file → Executable file
@ -1,8 +1,8 @@
|
||||
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 {
|
||||
@ -14,14 +14,14 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
const $btnDefault = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE,
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.MICROPHONE_MUTED,
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
this.$content = CE('div', {}, $btnMuted, $btnDefault);
|
||||
@ -36,7 +36,7 @@ export class MicrophoneAction extends BaseGameBarAction {
|
||||
});
|
||||
}
|
||||
|
||||
onClick(e: Event) {
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
const enabled = MicrophoneShortcut.toggle(false);
|
||||
this.$content.dataset.activated = enabled.toString();
|
19
src/modules/game-bar/action-renderer.ts → src/modules/game-bar/renderer-action.ts
Normal file → Executable file
@ -1,7 +1,8 @@
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle, CE } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { RendererShortcut } from "../shortcuts/shortcut-renderer";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { RendererShortcut } from "../shortcuts/renderer-shortcut";
|
||||
import { BxEvent } from "@/utils/bx-event";
|
||||
|
||||
|
||||
export class RendererAction extends BaseGameBarAction {
|
||||
@ -13,23 +14,27 @@ export class RendererAction extends BaseGameBarAction {
|
||||
const $btnDefault = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.EYE,
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
const $btnActivated = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.EYE_SLASH,
|
||||
onClick: this.onClick.bind(this),
|
||||
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) {
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
const isVisible = RendererShortcut.toggleVisibility();
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
||||
RendererShortcut.toggleVisibility();
|
||||
}
|
||||
|
||||
reset(): void {
|
6
src/modules/game-bar/action-screenshot.ts → src/modules/game-bar/screenshot-action.ts
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
import { BxIcon } from "@utils/bx-icon";
|
||||
import { createButton, ButtonStyle } from "@utils/html";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { t } from "@utils/translation";
|
||||
import { ScreenshotManager } from "@/utils/screenshot-manager";
|
||||
|
||||
@ -14,11 +14,11 @@ export class ScreenshotAction extends BaseGameBarAction {
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.SCREENSHOT,
|
||||
title: t('take-screenshot'),
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
});
|
||||
}
|
||||
|
||||
onClick(e: Event): void {
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
ScreenshotManager.getInstance().takeScreenshot();
|
||||
}
|
10
src/modules/game-bar/action-speaker.ts → src/modules/game-bar/speaker-action.ts
Normal file → Executable file
@ -1,8 +1,8 @@
|
||||
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 { SoundShortcut, SpeakerState } from "../shortcuts/shortcut-sound";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { SoundShortcut, SpeakerState } from "../shortcuts/sound-shortcut";
|
||||
|
||||
|
||||
export class SpeakerAction extends BaseGameBarAction {
|
||||
@ -14,13 +14,13 @@ export class SpeakerAction extends BaseGameBarAction {
|
||||
const $btnEnable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.AUDIO,
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
const $btnMuted = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.SPEAKER_MUTED,
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
@ -34,7 +34,7 @@ export class SpeakerAction extends BaseGameBarAction {
|
||||
});
|
||||
}
|
||||
|
||||
onClick(e: Event) {
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
SoundShortcut.muteUnmute();
|
||||
}
|
8
src/modules/game-bar/action-touch-control.ts → src/modules/game-bar/touch-control-action.ts
Normal file → Executable file
@ -1,7 +1,7 @@
|
||||
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 { BaseGameBarAction } from "./base-action";
|
||||
import { t } from "@utils/translation";
|
||||
|
||||
export class TouchControlAction extends BaseGameBarAction {
|
||||
@ -14,21 +14,21 @@ export class TouchControlAction extends BaseGameBarAction {
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TOUCH_CONTROL_ENABLE,
|
||||
title: t('show-touch-controller'),
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
});
|
||||
|
||||
const $btnDisable = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TOUCH_CONTROL_DISABLE,
|
||||
title: t('hide-touch-controller'),
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
classes: ['bx-activated'],
|
||||
});
|
||||
|
||||
this.$content = CE('div', {}, $btnEnable, $btnDisable);
|
||||
}
|
||||
|
||||
onClick(e: Event) {
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
const isVisible = TouchController.toggleVisibility();
|
||||
this.$content.dataset.activated = (!isVisible).toString();
|
6
src/modules/game-bar/action-true-achievements.ts → src/modules/game-bar/true-achievements-action.ts
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
import { BxIcon } from "@/utils/bx-icon";
|
||||
import { createButton, ButtonStyle } from "@/utils/html";
|
||||
import { BaseGameBarAction } from "./action-base";
|
||||
import { BaseGameBarAction } from "./base-action";
|
||||
import { TrueAchievements } from "@/utils/true-achievements";
|
||||
|
||||
export class TrueAchievementsAction extends BaseGameBarAction {
|
||||
@ -12,11 +12,11 @@ export class TrueAchievementsAction extends BaseGameBarAction {
|
||||
this.$content = createButton({
|
||||
style: ButtonStyle.GHOST,
|
||||
icon: BxIcon.TRUE_ACHIEVEMENTS,
|
||||
onClick: this.onClick.bind(this),
|
||||
onClick: this.onClick,
|
||||
});
|
||||
}
|
||||
|
||||
onClick(e: Event) {
|
||||
onClick = (e: Event) => {
|
||||
super.onClick(e);
|
||||
TrueAchievements.getInstance().open(false);
|
||||
}
|
23
src/modules/loading-screen.ts
Normal file → Executable file
@ -5,6 +5,7 @@ import { STATES } from "@utils/global";
|
||||
import { PrefKey } from "@/enums/pref-keys";
|
||||
import { getPref } from "@/utils/settings-storages/global-settings-storage";
|
||||
import { compressCss } from "@macros/build" with {type: "macro"};
|
||||
import { LoadingScreenRocket } from "@/enums/pref-values";
|
||||
|
||||
export class LoadingScreen {
|
||||
private static $bgStyle: HTMLElement;
|
||||
@ -36,7 +37,7 @@ export class LoadingScreen {
|
||||
|
||||
LoadingScreen.setBackground(titleInfo.product.heroImageUrl || titleInfo.product.titledHeroImageUrl || titleInfo.product.tileImageUrl);
|
||||
|
||||
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide') {
|
||||
if (getPref<LoadingScreenRocket>(PrefKey.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE) {
|
||||
LoadingScreen.hideRocket();
|
||||
}
|
||||
}
|
||||
@ -88,7 +89,7 @@ export class LoadingScreen {
|
||||
|
||||
static setupWaitTime(waitTime: number) {
|
||||
// Hide rocket when queing
|
||||
if (getPref(PrefKey.UI_LOADING_SCREEN_ROCKET) === 'hide-queue') {
|
||||
if (getPref<LoadingScreenRocket>(PrefKey.LOADING_SCREEN_ROCKET) === LoadingScreenRocket.HIDE_QUEUE) {
|
||||
LoadingScreen.hideRocket();
|
||||
}
|
||||
|
||||
@ -108,14 +109,14 @@ export class LoadingScreen {
|
||||
|
||||
let $waitTimeBox = LoadingScreen.$waitTimeBox;
|
||||
if (!$waitTimeBox) {
|
||||
$waitTimeBox = CE('div', {'class': 'bx-wait-time-box'},
|
||||
CE('label', {}, t('server')),
|
||||
CE('span', {}, getPreferredServerRegion()),
|
||||
CE('label', {}, t('wait-time-estimated')),
|
||||
$estimated = CE('span', {}),
|
||||
CE('label', {}, t('wait-time-countdown')),
|
||||
$countDown = CE('span', {}),
|
||||
);
|
||||
$waitTimeBox = CE('div', { class: 'bx-wait-time-box' },
|
||||
CE('label', {}, t('server')),
|
||||
CE('span', {}, getPreferredServerRegion()),
|
||||
CE('label', {}, t('wait-time-estimated')),
|
||||
$estimated = CE('span', {}),
|
||||
CE('label', {}, t('wait-time-countdown')),
|
||||
$countDown = CE('span', {}),
|
||||
);
|
||||
|
||||
document.documentElement.appendChild($waitTimeBox);
|
||||
LoadingScreen.$waitTimeBox = $waitTimeBox;
|
||||
@ -145,7 +146,7 @@ export class LoadingScreen {
|
||||
LoadingScreen.orgWebTitle && (document.title = LoadingScreen.orgWebTitle);
|
||||
LoadingScreen.$waitTimeBox && LoadingScreen.$waitTimeBox.classList.add('bx-gone');
|
||||
|
||||
if (getPref(PrefKey.UI_LOADING_SCREEN_GAME_ART) && LoadingScreen.$bgStyle) {
|
||||
if (getPref(PrefKey.LOADING_SCREEN_GAME_ART) && LoadingScreen.$bgStyle) {
|
||||
const $rocketBg = document.querySelector('#game-stream rect[width="800"]');
|
||||
$rocketBg && $rocketBg.addEventListener('transitionend', e => {
|
||||
LoadingScreen.$bgStyle.textContent += compressCss(`
|
||||
|