mirror of
https://github.com/moonlight-stream/moonlight-qt.git
synced 2026-10-15 21:15:04 +00:00
Compare commits
496
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b100e674dd | ||
|
|
c0c4d60565 | ||
|
|
8b18d58aa0 | ||
|
|
5e1ac6df87 | ||
|
|
0c8970364c | ||
|
|
1516464f82 | ||
|
|
fd34a1fd28 | ||
|
|
9067db8915 | ||
|
|
177f845183 | ||
|
|
c9b03f80f8 | ||
|
|
a27589fe35 | ||
|
|
561d824814 | ||
|
|
b2d77f48f8 | ||
|
|
872731ceeb | ||
|
|
6cad296138 | ||
|
|
444c6ccf06 | ||
|
|
d35532b03e | ||
|
|
1e0110b0f4 | ||
|
|
e5353d9758 | ||
|
|
f7c660129b | ||
|
|
cdacb3d28d | ||
|
|
5034a324b4 | ||
|
|
e223bf9a1b | ||
|
|
6e231778a2 | ||
|
|
d997a0d0c8 | ||
|
|
06bd8a730b | ||
|
|
faa39875b6 | ||
|
|
170801ba9f | ||
|
|
1be234e96a | ||
|
|
50364fd522 | ||
|
|
bea5355c66 | ||
|
|
c6820d8f5d | ||
|
|
f58ae9cf56 | ||
|
|
ca7d61f528 | ||
|
|
2b5388e941 | ||
|
|
a9ad048252 | ||
|
|
51cc5a8537 | ||
|
|
8c6b422040 | ||
|
|
ec75f0d8de | ||
|
|
f2a512d3aa | ||
|
|
6ccd91d942 | ||
|
|
02004bac3f | ||
|
|
7c93aaf659 | ||
|
|
534ce02756 | ||
|
|
19d77704ba | ||
|
|
a2ef7a4a0c | ||
|
|
dc6c0d3315 | ||
|
|
cd5853c6ca | ||
|
|
e6da6fa11f | ||
|
|
81f20cdb66 | ||
|
|
adfc972371 | ||
|
|
53a14eda1c | ||
|
|
2eb6715be5 | ||
|
|
29294553bb | ||
|
|
97240cd5f2 | ||
|
|
8cc001768c | ||
|
|
d94b92e247 | ||
|
|
d9eca42311 | ||
|
|
63c48be6d4 | ||
|
|
459dcd7b2c | ||
|
|
b33a252739 | ||
|
|
1eb76bbdb0 | ||
|
|
bba2faa602 | ||
|
|
87c3179f77 | ||
|
|
073f892bb1 | ||
|
|
815db2b0d7 | ||
|
|
a8a66bfc3d | ||
|
|
8fe279c91b | ||
|
|
dedb0c0327 | ||
|
|
9cbba106ad | ||
|
|
7d6ce0b4cc | ||
|
|
92fb7f3b71 | ||
|
|
b0ac3713f2 | ||
|
|
96a211be11 | ||
|
|
4cf498b09d | ||
|
|
c21affd15a | ||
|
|
7de4e74829 | ||
|
|
9d6218d1ff | ||
|
|
8cc3b30642 | ||
|
|
d77e2cc913 | ||
|
|
453435f916 | ||
|
|
f222aa7985 | ||
|
|
5faa5fece8 | ||
|
|
51f86caac3 | ||
|
|
5d813ed3df | ||
|
|
d17575d47c | ||
|
|
0e50a9d457 | ||
|
|
ed029f194f | ||
|
|
569153220a | ||
|
|
e0367d05ae | ||
|
|
53e0c2534d | ||
|
|
a766c9cafc | ||
|
|
c685021f21 | ||
|
|
af03f57e08 | ||
|
|
1fc1122c65 | ||
|
|
94d47e95fe | ||
|
|
1cffa7b350 | ||
|
|
fae1cd3dee | ||
|
|
2160f029d2 | ||
|
|
1ec1a3d7ad | ||
|
|
eb3d8ad92c | ||
|
|
07b00a6987 | ||
|
|
4ec682dc72 | ||
|
|
a9a1ea0a0b | ||
|
|
c8d18f8a8e | ||
|
|
9c11dca454 | ||
|
|
9496c25271 | ||
|
|
6ff3309677 | ||
|
|
e4c211b987 | ||
|
|
cac3bb28d7 | ||
|
|
78bc2141f6 | ||
|
|
effac744c7 | ||
|
|
5676a076fd | ||
|
|
b6407492c7 | ||
|
|
91ce7a41e5 | ||
|
|
89c8462e9e | ||
|
|
53a7680a38 | ||
|
|
88b4a17fe1 | ||
|
|
8326223220 | ||
|
|
4aa5ad71c0 | ||
|
|
e785be03c2 | ||
|
|
db294fe4ae | ||
|
|
2e9fbecfea | ||
|
|
7c066485f3 | ||
|
|
11d51e121a | ||
|
|
09675bf56d | ||
|
|
8f994dd113 | ||
|
|
e596c2dcdc | ||
|
|
209511b2dc | ||
|
|
efa67fec1e | ||
|
|
be0aeb6cc2 | ||
|
|
8d5072533f | ||
|
|
4c14a24960 | ||
|
|
e3702dbab7 | ||
|
|
e4325b8736 | ||
|
|
eb294e63f4 | ||
|
|
9d597a666b | ||
|
|
33e0b08699 | ||
|
|
31898bbab4 | ||
|
|
34c9e01a96 | ||
|
|
ddc6675dc8 | ||
|
|
f0626e8cae | ||
|
|
6f4ced5ed6 | ||
|
|
0a2b134d49 | ||
|
|
f6a9e7694a | ||
|
|
8795fb54f4 | ||
|
|
092eb1b117 | ||
|
|
179857f17d | ||
|
|
d878447413 | ||
|
|
60fd840d7a | ||
|
|
5020fc6f48 | ||
|
|
a2122e17c6 | ||
|
|
6bb7c8bf2a | ||
|
|
2dda65078f | ||
|
|
2914ff678d | ||
|
|
a56efe5c36 | ||
|
|
b7a05d73b9 | ||
|
|
6d5b9e4706 | ||
|
|
9813932c1b | ||
|
|
d4d4ab5b06 | ||
|
|
402ac59390 | ||
|
|
c33c49e6fa | ||
|
|
e1bbf8144e | ||
|
|
f4712718cb | ||
|
|
d040bd24d1 | ||
|
|
6d40c61850 | ||
|
|
b4a5d21f97 | ||
|
|
792f762c15 | ||
|
|
8c9c799f81 | ||
|
|
4eaf037c75 | ||
|
|
b732b58807 | ||
|
|
d484ec3ac8 | ||
|
|
f5f06ae44e | ||
|
|
d2fa488979 | ||
|
|
cd9aceb62c | ||
|
|
d5b7ef0c58 | ||
|
|
e4be57db5d | ||
|
|
1b0c657864 | ||
|
|
64fea80ac9 | ||
|
|
990804fe6b | ||
|
|
05ef938e2c | ||
|
|
1e825c85e5 | ||
|
|
a0a4c1ea83 | ||
|
|
b5e7dec378 | ||
|
|
adbd19e6fa | ||
|
|
02a86167da | ||
|
|
a739c1cef8 | ||
|
|
f6e08f8a43 | ||
|
|
218ffc2d55 | ||
|
|
0f49dca4c0 | ||
|
|
30274f3ae8 | ||
|
|
c696ae7a7e | ||
|
|
66a30c66f3 | ||
|
|
198a208b44 | ||
|
|
21ce23e377 | ||
|
|
cd4ae1c0ed | ||
|
|
d865c77975 | ||
|
|
cd13910e6c | ||
|
|
b8e9262b1b | ||
|
|
c14ebb1ad7 | ||
|
|
d3957b3cbb | ||
|
|
a8e8efb17b | ||
|
|
75adc22d05 | ||
|
|
2549efc8b3 | ||
|
|
6f8e517ce1 | ||
|
|
09f8e7117b | ||
|
|
88719cc8bf | ||
|
|
3ea62c0800 | ||
|
|
7643cc929e | ||
|
|
a58d3cfe39 | ||
|
|
b41c4022ea | ||
|
|
788675ad59 | ||
|
|
be9f465008 | ||
|
|
8b6ad55c9b | ||
|
|
92b68a40d1 | ||
|
|
a931161923 | ||
|
|
37b9b6ed9e | ||
|
|
fad197fdce | ||
|
|
cda65ab58d | ||
|
|
9ffe5218d5 | ||
|
|
ed98f256e8 | ||
|
|
8aec35a650 | ||
|
|
a3237b741c | ||
|
|
377409f313 | ||
|
|
80ddd5ba13 | ||
|
|
4a547eb05e | ||
|
|
1c6ed8f390 | ||
|
|
0a58af4fa1 | ||
|
|
50a094c917 | ||
|
|
d14cf984f4 | ||
|
|
13d9d79fe3 | ||
|
|
70d4f24455 | ||
|
|
705cd3ddc4 | ||
|
|
ea5b062da3 | ||
|
|
e6f41f5574 | ||
|
|
9af6222039 | ||
|
|
476414ea71 | ||
|
|
83d770cdba | ||
|
|
25e2d39e26 | ||
|
|
bd32370b6d | ||
|
|
8726c1c7d0 | ||
|
|
34881599f5 | ||
|
|
de0fb72424 | ||
|
|
46c76534bc | ||
|
|
67cba9e397 | ||
|
|
2ab72375c6 | ||
|
|
d3118bd677 | ||
|
|
b8c3658c42 | ||
|
|
d3fb52c5a0 | ||
|
|
7924a5b6a7 | ||
|
|
2972022ced | ||
|
|
c0846155c3 | ||
|
|
ac6dadffc6 | ||
|
|
745ac34b15 | ||
|
|
5f89636ed7 | ||
|
|
46d58e55a3 | ||
|
|
be460970a8 | ||
|
|
d50ba06321 | ||
|
|
b108684edc | ||
|
|
b6d10a34c8 | ||
|
|
232ff0b3c6 | ||
|
|
c5960c3e15 | ||
|
|
b48cbd76f5 | ||
|
|
ceef792f04 | ||
|
|
f3f1d56e8d | ||
|
|
39e24e9c8c | ||
|
|
c26e437f40 | ||
|
|
e89f63b8fb | ||
|
|
cf8251b9e6 | ||
|
|
d6c9beffc1 | ||
|
|
93dc6d6b60 | ||
|
|
8ee82421b3 | ||
|
|
31c65e0863 | ||
|
|
3e5aa9b127 | ||
|
|
085480e9f4 | ||
|
|
c3ce0918b3 | ||
|
|
c5b7a9c679 | ||
|
|
f1451a0db3 | ||
|
|
8a5f34fb01 | ||
|
|
d1f43ca258 | ||
|
|
249c29b74c | ||
|
|
1cf207de07 | ||
|
|
4f08d52754 | ||
|
|
41ad3c0938 | ||
|
|
21fb2832e1 | ||
|
|
f0bb1031d8 | ||
|
|
b80d2a00c0 | ||
|
|
85ea28287e | ||
|
|
a6f8901a18 | ||
|
|
d501a627f0 | ||
|
|
526d815930 | ||
|
|
b448e5bbd7 | ||
|
|
741e360024 | ||
|
|
7edbede95d | ||
|
|
7f54662160 | ||
|
|
f66285ddba | ||
|
|
bf250a4edb | ||
|
|
bdfadb1cdc | ||
|
|
63a381a882 | ||
|
|
4a591069ac | ||
|
|
c71b513bc4 | ||
|
|
700655c051 | ||
|
|
3425fec33d | ||
|
|
de844f2a0c | ||
|
|
54163e30d0 | ||
|
|
bd6a1d9a9b | ||
|
|
b25801ff7d | ||
|
|
bc5e9917cd | ||
|
|
d8eb817ed9 | ||
|
|
c1e5b0bf66 | ||
|
|
26f96a20a9 | ||
|
|
dbcc6a9089 | ||
|
|
9c6d2bab19 | ||
|
|
c92dc2495e | ||
|
|
1c24aada0a | ||
|
|
58f9b2a83b | ||
|
|
5feb331c46 | ||
|
|
9d5f25f828 | ||
|
|
d3c23b55dc | ||
|
|
80b7a69cd2 | ||
|
|
296387345d | ||
|
|
42bf87753f | ||
|
|
d262e5d1f6 | ||
|
|
f0821d9543 | ||
|
|
4164a222ae | ||
|
|
245cd7f1a8 | ||
|
|
62816a6b2a | ||
|
|
f5ef201905 | ||
|
|
01288be937 | ||
|
|
f57257cbfd | ||
|
|
d40999ce06 | ||
|
|
cb6d770b92 | ||
|
|
4d6196c3d3 | ||
|
|
54283ce0a1 | ||
|
|
6313129603 | ||
|
|
84d7a22638 | ||
|
|
f0aff0b0fb | ||
|
|
be358e9503 | ||
|
|
379d5ca376 | ||
|
|
8ed7144751 | ||
|
|
66b96e75af | ||
|
|
1160f40655 | ||
|
|
d5abf568c6 | ||
|
|
8324f61db0 | ||
|
|
9106f2c420 | ||
|
|
a2670dd568 | ||
|
|
c54e6e4459 | ||
|
|
dd6f1c5873 | ||
|
|
7fab5007a8 | ||
|
|
7f7cc89e61 | ||
|
|
7116efd8de | ||
|
|
229f5e4cea | ||
|
|
af37002a60 | ||
|
|
181dba5864 | ||
|
|
fd314db27e | ||
|
|
f67272b153 | ||
|
|
9b3050514b | ||
|
|
2c12ad297f | ||
|
|
50223dbdb8 | ||
|
|
beadf7819b | ||
|
|
7d829ac0c4 | ||
|
|
2f6e7608f8 | ||
|
|
5244313b30 | ||
|
|
c13e6b08c1 | ||
|
|
151f5dd678 | ||
|
|
a34d40e3ee | ||
|
|
68aa04926e | ||
|
|
7d544c1ce4 | ||
|
|
ac7696ea8f | ||
|
|
e79bf664dd | ||
|
|
4d303cebee | ||
|
|
bd6235efba | ||
|
|
b1232e0ed4 | ||
|
|
c35f7086a0 | ||
|
|
c7bc76325f | ||
|
|
ccaca68570 | ||
|
|
57db20016a | ||
|
|
bdb6d02dac | ||
|
|
9bcc6291be | ||
|
|
4ec549650d | ||
|
|
14027f3c74 | ||
|
|
fb9a164111 | ||
|
|
200cab9d17 | ||
|
|
c52a57f0ec | ||
|
|
b4dc7ca7cb | ||
|
|
00c1dd0d0d | ||
|
|
ecfc24c5d4 | ||
|
|
e6e91ca88b | ||
|
|
260a0e0ae2 | ||
|
|
3f8f4744c5 | ||
|
|
4bbd02fb2d | ||
|
|
c5ca672865 | ||
|
|
4688f3650c | ||
|
|
2550f416f4 | ||
|
|
579ad25a01 | ||
|
|
1144dbccb3 | ||
|
|
ff81f74391 | ||
|
|
c0d38ee78f | ||
|
|
c9cb64f90b | ||
|
|
93e597a93f | ||
|
|
82b33c033a | ||
|
|
be266d3349 | ||
|
|
c618a0b5df | ||
|
|
a20d429bc1 | ||
|
|
2b3e0803de | ||
|
|
4189903233 | ||
|
|
1a0dd56bb9 | ||
|
|
3aa3bfa403 | ||
|
|
aa9a716bd5 | ||
|
|
6fd2b73e6b | ||
|
|
06b5c4631f | ||
|
|
997c4aa0ae | ||
|
|
f4343c5f29 | ||
|
|
3cdea6b039 | ||
|
|
ac2e10c712 | ||
|
|
61fa45ad21 | ||
|
|
f362e76127 | ||
|
|
749c69fc76 | ||
|
|
5dca30def1 | ||
|
|
502783a86b | ||
|
|
45989fdd6e | ||
|
|
490aa5082f | ||
|
|
ae1c65805c | ||
|
|
9cb4105aec | ||
|
|
1fd545ae1f | ||
|
|
3194cb09d8 | ||
|
|
f9bb45579b | ||
|
|
f7b2edc8e3 | ||
|
|
c9a3946d80 | ||
|
|
0c8354336b | ||
|
|
1bf86f52d3 | ||
|
|
504d42865d | ||
|
|
65c04fd560 | ||
|
|
11dc244857 | ||
|
|
b9cab4cac5 | ||
|
|
65647a32f4 | ||
|
|
777502b495 | ||
|
|
ce4309694f | ||
|
|
ccc64a5629 | ||
|
|
25f6c020b6 | ||
|
|
6a95742a92 | ||
|
|
1dbdcb5279 | ||
|
|
9ce0537587 | ||
|
|
76d03f015d | ||
|
|
edbc24fa3e | ||
|
|
d4fd50b973 | ||
|
|
d5ab795b36 | ||
|
|
c5fc5220f3 | ||
|
|
28dea31533 | ||
|
|
8ce6a9585f | ||
|
|
47452371db | ||
|
|
4001b05fca | ||
|
|
75e917622e | ||
|
|
75359bb1c4 | ||
|
|
c93f7e7385 | ||
|
|
48bcd725c9 | ||
|
|
e807a52cfa | ||
|
|
9c9bfd8428 | ||
|
|
29b1304337 | ||
|
|
cc0b574bb1 | ||
|
|
7a769172a1 | ||
|
|
d9c7a245ef | ||
|
|
469a5c78f1 | ||
|
|
fabb4fdadc | ||
|
|
9491884cb4 | ||
|
|
bf51577787 | ||
|
|
4c5bcee8dc | ||
|
|
fd70865026 | ||
|
|
351aaa6759 | ||
|
|
59bc625cc7 | ||
|
|
b2f765e8ef | ||
|
|
bdd9a3a994 | ||
|
|
2ecafabcab | ||
|
|
9ff2ac0974 | ||
|
|
5760d08c33 | ||
|
|
ff7e61c6d9 | ||
|
|
6ad96fba42 | ||
|
|
85856114b2 | ||
|
|
7decfae792 | ||
|
|
b6008b15dc | ||
|
|
260ec3d80d | ||
|
|
7ec2e50334 | ||
|
|
e532b9167a | ||
|
|
52d5890372 | ||
|
|
f34d11994f | ||
|
|
359c92340d | ||
|
|
f1d0e97681 | ||
|
|
208d048358 | ||
|
|
e571d5833c | ||
|
|
286676e5c1 | ||
|
|
6d47287b60 | ||
|
|
60fb6881b2 | ||
|
|
b79c116bd9 | ||
|
|
895d0a6bf3 | ||
|
|
ae2693a860 | ||
|
|
0783b28ba6 |
@@ -0,0 +1,9 @@
|
|||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gitsubmodule"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
---
|
||||||
|
name: Build - Linux AppImage
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ci_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
env:
|
||||||
|
CI_VERSION: ${{ inputs.ci_version }}
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Setup environment
|
||||||
|
run: |
|
||||||
|
wget -qO - https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo apt-key add -
|
||||||
|
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-1.4.313-jammy.list https://packages.lunarg.com/vulkan/1.4.313/lunarg-vulkan-1.4.313-jammy.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y qt6-base-dev qt6-declarative-dev libqt6svg6-dev qml6-module-qtquick-controls qml6-module-qtquick-templates qml6-module-qtquick-layouts \
|
||||||
|
qml6-module-qtqml-workerscript qml6-module-qtquick-window qml6-module-qtquick python3-pip nasm libgbm-dev libdrm-dev libfreetype-dev libasound2-dev \
|
||||||
|
libdbus-1-dev libegl1-mesa-dev libgl1-mesa-dev libgles2-mesa-dev libglu1-mesa-dev libibus-1.0-dev libpulse-dev libudev-dev libx11-dev libxcursor-dev \
|
||||||
|
libxext-dev libxi-dev libxinerama-dev libxkbcommon-dev libxrandr-dev libxss-dev libxt-dev libxv-dev libxxf86vm-dev libxcb-dri3-dev libx11-xcb-dev \
|
||||||
|
libxfixes-dev libxtst-dev wayland-protocols libopus-dev libvdpau-dev libgl-dev libpipewire-0.3-dev liburing-dev vulkan-sdk cmake
|
||||||
|
sudo pip3 install meson
|
||||||
|
mkdir -p dep_root/{bin,include,lib}
|
||||||
|
echo "DEP_ROOT=$PWD/dep_root" >> "${GITHUB_ENV}"
|
||||||
|
echo "$PWD/dep_root/bin" >> "${GITHUB_PATH}"
|
||||||
|
|
||||||
|
- name: Checkout SDL3
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: libsdl-org/SDL
|
||||||
|
ref: release-3.4.12
|
||||||
|
path: deps/SDL
|
||||||
|
|
||||||
|
- name: Build SDL3
|
||||||
|
working-directory: deps/SDL
|
||||||
|
run: |
|
||||||
|
cmake -DSDL_KMSDRM=OFF -DSDL_TEST_LIBRARY=OFF -DSDL_INSTALL_DOCS=OFF -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -S . -B build
|
||||||
|
cmake --build build -j
|
||||||
|
sudo cmake --install build
|
||||||
|
|
||||||
|
- name: Checkout sdl2-compat
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: libsdl-org/sdl2-compat
|
||||||
|
ref: release-2.32.70
|
||||||
|
path: deps/sdl2-compat
|
||||||
|
|
||||||
|
- name: Build sdl2-compat
|
||||||
|
working-directory: deps/sdl2-compat
|
||||||
|
run: |
|
||||||
|
cmake -DSDL2COMPAT_TESTS=OFF -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=ON -S . -B build
|
||||||
|
cmake --build build -j
|
||||||
|
sudo cmake --install build
|
||||||
|
|
||||||
|
- name: Checkout SDL_ttf
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: libsdl-org/SDL_ttf
|
||||||
|
ref: a883e490e30fb44a5336ea3dcb990c6982c5216f
|
||||||
|
path: deps/SDL_ttf
|
||||||
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- name: Build SDL_ttf
|
||||||
|
working-directory: deps/SDL_ttf
|
||||||
|
run: |
|
||||||
|
./autogen.sh
|
||||||
|
./configure
|
||||||
|
make -j$(nproc)
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
- name: Checkout libva
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: intel/libva
|
||||||
|
ref: 2.24.1
|
||||||
|
path: deps/libva
|
||||||
|
|
||||||
|
- name: Build libva
|
||||||
|
working-directory: deps/libva
|
||||||
|
run: |
|
||||||
|
./autogen.sh
|
||||||
|
./configure --enable-x11
|
||||||
|
make -j$(nproc)
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
- name: Checkout libplacebo
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: haasn/libplacebo
|
||||||
|
ref: 2d0979fb54e025e904c7372666fffbf5dae40f66
|
||||||
|
path: deps/libplacebo
|
||||||
|
submodules: 'recursive'
|
||||||
|
|
||||||
|
- name: Build libplacebo
|
||||||
|
working-directory: deps/libplacebo
|
||||||
|
run: |
|
||||||
|
meson setup build -Dvulkan=enabled -Dopengl=disabled -Ddemos=false
|
||||||
|
ninja -C build
|
||||||
|
sudo ninja install -C build
|
||||||
|
|
||||||
|
- name: Build dav1d
|
||||||
|
working-directory: deps
|
||||||
|
env:
|
||||||
|
DAV1D_VER: 1.5.3
|
||||||
|
run: |
|
||||||
|
git clone --branch $DAV1D_VER --depth 1 https://code.videolan.org/videolan/dav1d.git
|
||||||
|
pushd dav1d
|
||||||
|
meson setup build -Ddefault_library=static -Dbuildtype=release -Denable_tools=false -Denable_tests=false
|
||||||
|
ninja -C build
|
||||||
|
sudo ninja install -C build
|
||||||
|
popd
|
||||||
|
|
||||||
|
- name: Checkout FFmpeg
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: FFmpeg/FFmpeg
|
||||||
|
ref: n8.1.1
|
||||||
|
path: deps/FFmpeg
|
||||||
|
|
||||||
|
- name: Build FFmpeg
|
||||||
|
working-directory: deps/FFmpeg
|
||||||
|
run: |
|
||||||
|
./configure --enable-pic --enable-lto --disable-static --enable-shared --disable-all --disable-autodetect \
|
||||||
|
--enable-avcodec --enable-avformat --enable-swscale \
|
||||||
|
--enable-decoder=h264 --enable-decoder=hevc --enable-decoder=av1 \
|
||||||
|
--enable-vaapi --enable-hwaccel=h264_vaapi --enable-hwaccel=hevc_vaapi --enable-hwaccel=av1_vaapi \
|
||||||
|
--enable-vdpau --enable-hwaccel=h264_vdpau --enable-hwaccel=hevc_vdpau --enable-hwaccel=av1_vdpau \
|
||||||
|
--enable-libdrm --enable-vulkan --enable-hwaccel=h264_vulkan --enable-hwaccel=hevc_vulkan --enable-hwaccel=av1_vulkan \
|
||||||
|
--enable-libdav1d --enable-decoder=libdav1d
|
||||||
|
make -j$(nproc)
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
- name: Install linuxdeploy
|
||||||
|
working-directory: dep_root/bin
|
||||||
|
run: |
|
||||||
|
wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||||||
|
wget https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||||||
|
chmod +x linuxdeploy*.AppImage
|
||||||
|
|
||||||
|
- name: Build Binaries
|
||||||
|
run: |
|
||||||
|
sudo ldconfig
|
||||||
|
scripts/build-appimage.sh
|
||||||
|
|
||||||
|
- name: Upload Binaries
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: Moonlight-LinuxAppImage-${{ env.CI_VERSION }}
|
||||||
|
path: build/installer-release/Moonlight-${{ env.CI_VERSION }}-x86_64.AppImage
|
||||||
|
compression-level: 0
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
---
|
||||||
|
name: Build - Steam Link
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ci_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
env:
|
||||||
|
CI_VERSION: ${{ inputs.ci_version }}
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Checkout Steam Link SDK
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: ValveSoftware/steamlink-sdk
|
||||||
|
path: steamlink-sdk
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Build binaries
|
||||||
|
run: STEAMLINK_SDK_PATH=$PWD/steamlink-sdk scripts/build-steamlink-app.sh
|
||||||
|
|
||||||
|
- name: Upload Binaries
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: Moonlight-SteamLink-${{ env.CI_VERSION }}
|
||||||
|
path: build/deploy-release/*
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
name: Build - Windows and macOS
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ci_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
env:
|
||||||
|
CI_VERSION: ${{ inputs.ci_version }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
# Windows
|
||||||
|
- os: windows-2025
|
||||||
|
qt_version: 6.11.1
|
||||||
|
# macOS
|
||||||
|
- os: macos-26
|
||||||
|
qt_version: 6.11.1
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Download dependencies (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: powershell ./setup-deps.ps1
|
||||||
|
|
||||||
|
- name: Install create-dmg (macOS)
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: |
|
||||||
|
brew install node
|
||||||
|
npm install --global create-dmg
|
||||||
|
|
||||||
|
- name: Install Qt
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
cache: true
|
||||||
|
set-env: ${{ runner.os != 'Windows' }}
|
||||||
|
add-tools-to-path: ${{ runner.os != 'Windows' }}
|
||||||
|
version: ${{ matrix.qt_version }}
|
||||||
|
aqtsource: 'git+https://github.com/miurahr/aqtinstall.git@073e34d7c2ab4ae6961ed7cca690b3abd5ba5a7e'
|
||||||
|
|
||||||
|
- name: Install Qt ARM64 (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
cache: true
|
||||||
|
set-env: false
|
||||||
|
add-tools-to-path: false
|
||||||
|
arch: win64_msvc2022_arm64_cross_compiled
|
||||||
|
version: ${{ matrix.qt_version }}
|
||||||
|
|
||||||
|
- name: Build x64 binaries (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
set "PATH=%QT_PATH%;%PATH%"
|
||||||
|
scripts\build-arch.bat Release x64
|
||||||
|
env:
|
||||||
|
QT_PATH: ${{ runner.workspace }}\Qt\${{ matrix.qt_version }}\msvc2022_64\bin
|
||||||
|
|
||||||
|
- name: Build ARM64 binaries (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
set "PATH=%QT_PATH%;%PATH%"
|
||||||
|
scripts\build-arch.bat Release arm64
|
||||||
|
env:
|
||||||
|
QT_PATH: ${{ runner.workspace }}\Qt\${{ matrix.qt_version }}\msvc2022_arm64\bin
|
||||||
|
|
||||||
|
- name: Build Package
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
if ($Env:RUNNER_OS -eq "Windows") {
|
||||||
|
cmd /c scripts\generate-bundle.bat Release
|
||||||
|
} else {
|
||||||
|
bash scripts/generate-dmg.sh Release
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Upload Binaries (Windows x64)
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
with:
|
||||||
|
name: Moonlight-${{ runner.os }}-x64-${{ env.CI_VERSION }}
|
||||||
|
path: build/deploy-x64-release/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Binaries (Windows arm64)
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
with:
|
||||||
|
name: Moonlight-${{ runner.os }}-arm64-${{ env.CI_VERSION }}
|
||||||
|
path: build/deploy-arm64-release/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Binaries (macOS)
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
with:
|
||||||
|
name: Moonlight-${{ runner.os }}-${{ env.CI_VERSION }}
|
||||||
|
path: build/installer-Release/*.dmg
|
||||||
|
compression-level: 0
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Symbols
|
||||||
|
uses: actions/upload-artifact@v6
|
||||||
|
with:
|
||||||
|
name: zDevDbgSyms-${{ runner.os }}-${{ env.CI_VERSION }}
|
||||||
|
path: |
|
||||||
|
build/installer-*/*.dsym/*
|
||||||
|
build/symbols-*/*.pdb
|
||||||
|
if-no-files-found: error
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
name: Build
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: build-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
setup:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
ci_version: ${{ steps.set-ci-version.outputs.ci_version }}
|
||||||
|
steps:
|
||||||
|
- name: Set CI version
|
||||||
|
id: set-ci-version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
CI_VERSION=`echo ${GITHUB_SHA} | cut -c1-6`
|
||||||
|
echo "ci_version=${CI_VERSION}" >> "${GITHUB_OUTPUT}"
|
||||||
|
|
||||||
|
build-appimage:
|
||||||
|
needs: setup
|
||||||
|
uses: ./.github/workflows/build-appimage.yml
|
||||||
|
with:
|
||||||
|
ci_version: ${{ needs.setup.outputs.ci_version }}
|
||||||
|
|
||||||
|
build-steamlink:
|
||||||
|
needs: setup
|
||||||
|
uses: ./.github/workflows/build-steamlink.yml
|
||||||
|
with:
|
||||||
|
ci_version: ${{ needs.setup.outputs.ci_version }}
|
||||||
|
|
||||||
|
build-windows-macos:
|
||||||
|
needs: setup
|
||||||
|
uses: ./.github/workflows/build-win-mac.yml
|
||||||
|
with:
|
||||||
|
ci_version: ${{ needs.setup.outputs.ci_version }}
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
|
|
||||||
**/.vs/
|
**/.vs/
|
||||||
|
.vscode/
|
||||||
build/
|
build/
|
||||||
|
libs/
|
||||||
config.tests/*/.qmake.stash
|
config.tests/*/.qmake.stash
|
||||||
config.tests/*/Makefile
|
config.tests/*/Makefile
|
||||||
|
|||||||
@@ -7,13 +7,6 @@
|
|||||||
[submodule "app/SDL_GameControllerDB"]
|
[submodule "app/SDL_GameControllerDB"]
|
||||||
path = app/SDL_GameControllerDB
|
path = app/SDL_GameControllerDB
|
||||||
url = https://github.com/gabomdq/SDL_GameControllerDB.git
|
url = https://github.com/gabomdq/SDL_GameControllerDB.git
|
||||||
[submodule "soundio/libsoundio"]
|
|
||||||
path = soundio/libsoundio
|
|
||||||
url = https://github.com/cgutman/libsoundio.git
|
|
||||||
[submodule "h264bitstream/h264bitstream"]
|
[submodule "h264bitstream/h264bitstream"]
|
||||||
path = h264bitstream/h264bitstream
|
path = h264bitstream/h264bitstream
|
||||||
url = https://github.com/aizvorski/h264bitstream.git
|
url = https://github.com/aizvorski/h264bitstream.git
|
||||||
[submodule "libs"]
|
|
||||||
path = libs
|
|
||||||
url = https://github.com/cgutman/moonlight-qt-prebuilts.git
|
|
||||||
shallow = true
|
|
||||||
|
|||||||
@@ -207,6 +207,28 @@ private:
|
|||||||
// Since the RTSS OSD doesn't even work with DXVA2, we'll just block the hooks entirely.
|
// Since the RTSS OSD doesn't even work with DXVA2, we'll just block the hooks entirely.
|
||||||
L"RTSSHooks.dll",
|
L"RTSSHooks.dll",
|
||||||
L"RTSSHooks64.dll",
|
L"RTSSHooks64.dll",
|
||||||
|
|
||||||
|
// Bandicam's Vulkan layer DLL crashes during destruction of the Vulkan swapchain
|
||||||
|
// bdcamvk64+0x10b73:
|
||||||
|
// 00007ffa`be3b0b73 498b5630 mov rdx,qword ptr [r14+30h] ds:00000000`00000030=????????????????
|
||||||
|
//
|
||||||
|
// bdcamvk64+0x10b73
|
||||||
|
// libplacebo_357!vk_sw_destroy+0x14f [D:\a\moonlight-deps\moonlight-deps\libplacebo\src\vulkan\swapchain.c @ 460]
|
||||||
|
// libplacebo_357!pl_swapchain_destroy+0x1a [D:\a\moonlight-deps\moonlight-deps\libplacebo\src\swapchain.c @ 30]
|
||||||
|
// Moonlight!PlVkRenderer::~PlVkRenderer+0x159 [D:\a\moonlight-qt\app\streaming\video\ffmpeg-renderers\plvk.cpp @ 140]
|
||||||
|
// Moonlight!PlVkRenderer::`scalar deleting destructor'+0x17
|
||||||
|
// Moonlight!FFmpegVideoDecoder::reset+0x203 [D:\a\moonlight-qt\app\streaming\video\ffmpeg.cpp @ 301]
|
||||||
|
// Moonlight!FFmpegVideoDecoder::~FFmpegVideoDecoder+0x22 [D:\a\moonlight-qt\app\streaming\video\ffmpeg.cpp @ 257]
|
||||||
|
// Moonlight!FFmpegVideoDecoder::`scalar deleting destructor'+0x17
|
||||||
|
// Moonlight!Session::getDecoderInfo+0x19a [D:\a\moonlight-qt\app\streaming\session.cpp @ 415]
|
||||||
|
// Moonlight!SystemProperties::querySdlVideoInfoInternal+0x134 [D:\a\moonlight-qt\app\backend\systemproperties.cpp @ 161]
|
||||||
|
// Moonlight!SystemProperties::querySdlVideoInfo+0x97 [D:\a\moonlight-qt\app\backend\systemproperties.cpp @ 123]
|
||||||
|
// Moonlight!SystemProperties::SystemProperties+0x4bf [D:\a\moonlight-qt\app\backend\systemproperties.cpp @ 75]
|
||||||
|
//
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/1425
|
||||||
|
// https://github.com/moonlight-stream/moonlight-qt/issues/1579
|
||||||
|
L"bdcamvk32.dll",
|
||||||
|
L"bdcamvk64.dll",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Moonlight also has mobile versions for [Android](https://github.com/moonlight-st
|
|||||||
|
|
||||||
You can follow development on our [Discord server](https://moonlight-stream.org/discord) and help translate Moonlight into your language on [Weblate](https://hosted.weblate.org/projects/moonlight/moonlight-qt/).
|
You can follow development on our [Discord server](https://moonlight-stream.org/discord) and help translate Moonlight into your language on [Weblate](https://hosted.weblate.org/projects/moonlight/moonlight-qt/).
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/cgutman/moonlight-qt/branch/master)
|
[](https://github.com/moonlight-stream/moonlight-qt/actions/workflows/build.yml?query=branch%3Amaster)
|
||||||
[](https://github.com/moonlight-stream/moonlight-qt/releases)
|
[](https://github.com/moonlight-stream/moonlight-qt/releases)
|
||||||
[](https://hosted.weblate.org/projects/moonlight/moonlight-qt/)
|
[](https://hosted.weblate.org/projects/moonlight/moonlight-qt/)
|
||||||
|
|
||||||
@@ -31,6 +31,9 @@ You can follow development on our [Discord server](https://moonlight-stream.org/
|
|||||||
- [Experimental RISC-V Debian packages](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-RISC%E2%80%90V-Single-Board-Computers)
|
- [Experimental RISC-V Debian packages](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-RISC%E2%80%90V-Single-Board-Computers)
|
||||||
- [NVIDIA Jetson and Nintendo Switch (Ubuntu L4T)](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Linux4Tegra-(L4T)-Ubuntu)
|
- [NVIDIA Jetson and Nintendo Switch (Ubuntu L4T)](https://github.com/moonlight-stream/moonlight-docs/wiki/Installing-Moonlight-Qt-on-Linux4Tegra-(L4T)-Ubuntu)
|
||||||
|
|
||||||
|
### Nightly Builds
|
||||||
|
- [Downloads](https://nightly.link/moonlight-stream/moonlight-qt/workflows/build/master)
|
||||||
|
|
||||||
#### Special Thanks
|
#### Special Thanks
|
||||||
|
|
||||||
[](https://cloudsmith.com)
|
[](https://cloudsmith.com)
|
||||||
@@ -54,13 +57,13 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
|
|||||||
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
* [create-dmg](https://github.com/sindresorhus/create-dmg) (only if building DMGs for use on non-development Macs)
|
||||||
|
|
||||||
### Linux/Unix Build Requirements
|
### Linux/Unix Build Requirements
|
||||||
* Qt 6 is recommended, but Qt 5.9 or later is also supported (replace `qmake6` with `qmake` when using Qt 5).
|
* Qt 6 is recommended, but Qt 5.12 or later is also supported (replace `qmake6` with `qmake` when using Qt 5).
|
||||||
* GCC or Clang
|
* GCC or Clang
|
||||||
* FFmpeg 4.0 or later
|
* FFmpeg 4.0 or later
|
||||||
* Install the required packages:
|
* Install the required packages:
|
||||||
* Debian/Ubuntu:
|
* Debian/Ubuntu:
|
||||||
* Base Requirements: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libavformat-dev libswscale-dev libva-dev libvdpau-dev libxkbcommon-dev wayland-protocols libdrm-dev`
|
* Base Requirements: `libegl1-mesa-dev libgl1-mesa-dev libopus-dev libsdl2-dev libsdl2-ttf-dev libssl-dev libavcodec-dev libavformat-dev libswscale-dev libva-dev libvdpau-dev libxkbcommon-dev wayland-protocols libdrm-dev`
|
||||||
* Qt 6 (Recommended): `qt6-base-dev qt6-declarative-dev libqt6svg6-dev qml6-module-qtquick-controls qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qtqml-workerscript qml6-module-qtquick-window qml6-module-qtquick`
|
* Qt 6 (Recommended): `qt6-base-dev qt6-declarative-dev libqt6svg6-dev qt6-wayland qml6-module-qtquick-controls qml6-module-qtquick-templates qml6-module-qtquick-layouts qml6-module-qtqml-workerscript qml6-module-qtquick-window qml6-module-qtquick`
|
||||||
* Qt 5: `qtbase5-dev qt5-qmake qtdeclarative5-dev qtquickcontrols2-5-dev qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2 qtwayland5`
|
* Qt 5: `qtbase5-dev qt5-qmake qtdeclarative5-dev qtquickcontrols2-5-dev qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qtquick2 qtwayland5`
|
||||||
* RedHat/Fedora (RPM Fusion repo required):
|
* RedHat/Fedora (RPM Fusion repo required):
|
||||||
* Base Requirements: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel libdrm-devel`
|
* Base Requirements: `openssl-devel SDL2-devel SDL2_ttf-devel ffmpeg-devel libva-devel libvdpau-devel opus-devel pulseaudio-libs-devel alsa-lib-devel libdrm-devel`
|
||||||
@@ -72,18 +75,33 @@ Hosting for Moonlight's Debian and L4T package repositories is graciously provid
|
|||||||
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
|
* [Steam Link SDK](https://github.com/ValveSoftware/steamlink-sdk) cloned on your build system
|
||||||
* STEAMLINK_SDK_PATH environment variable set to the Steam Link SDK path
|
* STEAMLINK_SDK_PATH environment variable set to the Steam Link SDK path
|
||||||
|
|
||||||
|
**Steam Link Hardware Limitations**
|
||||||
|
Moonlight builds for Steam Link are subject to hardware limitations of the Steam Link device:
|
||||||
|
* Maximum resolution: **1080p (1920x1080)**
|
||||||
|
* Maximum framerate: **60 FPS**
|
||||||
|
* Maximum video bitrate: **40 Mbps**
|
||||||
|
* **HDR streaming is not supported** on the original hardware
|
||||||
|
|
||||||
|
### Docker containers
|
||||||
|
If you want to use Docker for building, look at [this repo](https://github.com/cgutman/moonlight-packaging) containing canonical containers
|
||||||
|
for different architectures, which handle building deps and extra linking for you.
|
||||||
|
|
||||||
### Build Setup Steps
|
### Build Setup Steps
|
||||||
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
1. Install the latest Qt SDK (and optionally, the Qt Creator IDE) from https://www.qt.io/download
|
||||||
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
* You can install Qt via Homebrew on macOS, but you will need to use `brew install qt --with-debug` to be able to create debug builds of Moonlight.
|
||||||
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.9 or later.
|
* You may also use your Linux distro's package manager for the Qt SDK as long as the packages are Qt 5.12 or later.
|
||||||
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.14.
|
* This step is not required for building on Steam Link, because the Steam Link SDK includes Qt 5.14.
|
||||||
2. Run `git submodule update --init --recursive` from within `moonlight-qt/`
|
2. Download submodules and dependencies
|
||||||
|
* Run `git submodule update --init --recursive` from within `moonlight-qt/`.
|
||||||
|
* On Windows and macOS, you must also run `setup-deps.ps1` (Windows) or `setup-deps.py` (macOS).
|
||||||
|
* Perform these steps each time you pull new changes from the Git repository.
|
||||||
3. Open the project in Qt Creator or build from qmake on the command line.
|
3. Open the project in Qt Creator or build from qmake on the command line.
|
||||||
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
* To build a binary for use on non-development machines, use the scripts in the `scripts` folder.
|
||||||
* For Windows builds, use `scripts\build-arch.bat` and `scripts\generate-bundle.bat`. Execute these scripts from the root of the repository within a Qt command prompt. Ensure 7-Zip binary directory is on your `%PATH%`.
|
* For Windows builds, use `scripts\build-arch.bat` and `scripts\generate-bundle.bat`. Execute these scripts from the root of the repository within a Qt command prompt. Ensure 7-Zip binary directory is on your `%PATH%`.
|
||||||
* For macOS builds, use `scripts/generate-dmg.sh`. Execute this script from the root of the repository and ensure Qt's `bin` folder is in your `$PATH`.
|
* For macOS builds, use `scripts/generate-dmg.sh`. Execute this script from the root of the repository and ensure Qt's `bin` folder is in your `$PATH`.
|
||||||
* For Steam Link builds, run `scripts/build-steamlink-app.sh` from the root of the repository.
|
* For Steam Link builds, run `scripts/build-steamlink-app.sh` from the root of the repository.
|
||||||
* To build from the command line for development use on macOS or Linux, run `qmake6 moonlight-qt.pro` then `make debug` or `make release`
|
* To build from the command line for development use on macOS or Linux, run `qmake6 moonlight-qt.pro` then `make debug` or `make release`.
|
||||||
|
* The final binary will be placed in `app/moonlight`.
|
||||||
* To create an embedded build for a single-purpose device, use `qmake6 "CONFIG+=embedded" moonlight-qt.pro` and build normally.
|
* To create an embedded build for a single-purpose device, use `qmake6 "CONFIG+=embedded" moonlight-qt.pro` and build normally.
|
||||||
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
|
* This build will lack windowed mode, Discord/Help links, and other features that don't make sense on an embedded device.
|
||||||
* For platforms with poor GPU performance, add `"CONFIG+=gpuslow"` to prefer direct KMSDRM rendering over GL/Vulkan renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
|
* For platforms with poor GPU performance, add `"CONFIG+=gpuslow"` to prefer direct KMSDRM rendering over GL/Vulkan renderers. Direct KMSDRM rendering can use dedicated YUV/RGB conversion and scaling hardware rather than slower GPU shaders for these operations.
|
||||||
|
|||||||
@@ -2,8 +2,12 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>GCSupportsGameMode</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>Moonlight</string>
|
<string>Moonlight</string>
|
||||||
|
<key>LSSupportsGameMode</key>
|
||||||
|
<true/>
|
||||||
<key>CFBundleGetInfoString</key>
|
<key>CFBundleGetInfoString</key>
|
||||||
<string>Stream games and other applications from another PC</string>
|
<string>Stream games and other applications from another PC</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
@@ -44,5 +48,7 @@
|
|||||||
<string>VERSION</string>
|
<string>VERSION</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Moonlight</string>
|
<string>Moonlight</string>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.games</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
<asmv3:application>
|
<asmv3:application>
|
||||||
<asmv3:windowsSettings>
|
<asmv3:windowsSettings>
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitor</dpiAwareness>
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2, permonitor</dpiAwareness>
|
||||||
|
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
|
||||||
</asmv3:windowsSettings>
|
</asmv3:windowsSettings>
|
||||||
</asmv3:application>
|
</asmv3:application>
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
|||||||
+1
-1
Submodule app/SDL_GameControllerDB updated: e5a5fa2ac6...1fc89ca578
@@ -1,320 +0,0 @@
|
|||||||
#include "SDL_compat.h"
|
|
||||||
|
|
||||||
#include <SDL_syswm.h>
|
|
||||||
|
|
||||||
void* SDLC_Win32_GetHwnd(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_WINDOWS
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_WINDOWS) {
|
|
||||||
return info.info.win.window;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* SDLC_MacOS_GetWindow(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_COCOA
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_COCOA) {
|
|
||||||
return info.info.cocoa.window;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* SDLC_X11_GetDisplay(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_X11
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_X11) {
|
|
||||||
return info.info.x11.display;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long SDLC_X11_GetWindow(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_X11
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_X11) {
|
|
||||||
return info.info.x11.window;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* SDLC_Wayland_GetDisplay(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_WAYLAND
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
|
||||||
return info.info.wl.display;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* SDLC_Wayland_GetSurface(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#ifdef SDL_VIDEO_DRIVER_WAYLAND
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_WAYLAND) {
|
|
||||||
return info.info.wl.surface;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SDLC_KMSDRM_GetFd(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#if defined(SDL_VIDEO_DRIVER_KMSDRM) && SDL_VERSION_ATLEAST(2, 0, 15)
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_KMSDRM) {
|
|
||||||
return info.info.kmsdrm.drm_fd;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int SDLC_KMSDRM_GetDevIndex(SDL_Window* window)
|
|
||||||
{
|
|
||||||
#if defined(SDL_VIDEO_DRIVER_KMSDRM) && SDL_VERSION_ATLEAST(2, 0, 15)
|
|
||||||
SDL_SysWMinfo info;
|
|
||||||
SDL_VERSION(&info.version);
|
|
||||||
if (!SDL_GetWindowWMInfo(window, &info)) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"SDL_GetWindowWMInfo() failed: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.subsystem == SDL_SYSWM_KMSDRM) {
|
|
||||||
return info.info.kmsdrm.dev_index;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
(void)window;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDLC_VideoDriver SDLC_GetVideoDriver(void)
|
|
||||||
{
|
|
||||||
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
|
|
||||||
return SDLC_VIDEO_WIN32;
|
|
||||||
#elif defined(SDL_VIDEO_DRIVER_COCOA)
|
|
||||||
return SDLC_VIDEO_MACOS;
|
|
||||||
#else
|
|
||||||
const char* videoDriver = SDL_GetCurrentVideoDriver();
|
|
||||||
if (SDL_strcmp(videoDriver, "x11") == 0) {
|
|
||||||
return SDLC_VIDEO_X11;
|
|
||||||
}
|
|
||||||
else if (SDL_strcmp(videoDriver, "wayland") == 0) {
|
|
||||||
return SDLC_VIDEO_WAYLAND;
|
|
||||||
}
|
|
||||||
else if (SDL_strcmp(videoDriver, "kmsdrm") == 0) {
|
|
||||||
return SDLC_VIDEO_KMSDRM;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
SDL_assert(0);
|
|
||||||
return SDLC_VIDEO_UNKNOWN;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLC_IsFullscreen(SDL_Window* window)
|
|
||||||
{
|
|
||||||
return !!(SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLC_IsFullscreenExclusive(SDL_Window* window)
|
|
||||||
{
|
|
||||||
return (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SDLC_IsFullscreenDesktop(SDL_Window* window)
|
|
||||||
{
|
|
||||||
return (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLC_EnterFullscreen(SDL_Window* window, bool exclusive)
|
|
||||||
{
|
|
||||||
SDL_SetWindowFullscreen(window, exclusive ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLC_LeaveFullscreen(SDL_Window* window)
|
|
||||||
{
|
|
||||||
SDL_SetWindowFullscreen(window, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_Window* SDLC_CreateWindowWithFallback(const char *title,
|
|
||||||
int x, int y, int w, int h,
|
|
||||||
Uint32 requiredFlags,
|
|
||||||
Uint32 optionalFlags)
|
|
||||||
{
|
|
||||||
SDL_Window* window = SDL_CreateWindow(title, x, y, w, h, requiredFlags | optionalFlags);
|
|
||||||
if (!window) {
|
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
|
||||||
"Failed to create window with optional flags: %s",
|
|
||||||
SDL_GetError());
|
|
||||||
|
|
||||||
// Try the fallback flags now
|
|
||||||
window = SDL_CreateWindow(title, x, y, w, h, requiredFlags);
|
|
||||||
}
|
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SDLC_FlushWindowEvents(void)
|
|
||||||
{
|
|
||||||
SDL_FlushEvent(SDL_WINDOWEVENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_JoystickID* SDL_GetGamepads(int *count)
|
|
||||||
{
|
|
||||||
int numJoysticks = SDL_NumJoysticks();
|
|
||||||
SDL_JoystickID* ids = SDL_calloc(numJoysticks + 1, sizeof(SDL_JoystickID));
|
|
||||||
|
|
||||||
int numGamepads = 0;
|
|
||||||
for (int i = 0; i < numJoysticks; i++) {
|
|
||||||
if (SDL_IsGameController(i)) {
|
|
||||||
ids[numGamepads++] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count != NULL) {
|
|
||||||
*count = numGamepads;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_JoystickID* SDL_GetJoysticks(int *count)
|
|
||||||
{
|
|
||||||
int numJoysticks = SDL_NumJoysticks();
|
|
||||||
SDL_JoystickID* ids = SDL_calloc(numJoysticks + 1, sizeof(SDL_JoystickID));
|
|
||||||
|
|
||||||
for (int i = 0; i < numJoysticks; i++) {
|
|
||||||
ids[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count != NULL) {
|
|
||||||
*count = numJoysticks;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
SDL_DisplayID * SDL_GetDisplays(int *count)
|
|
||||||
{
|
|
||||||
int numDisplays = SDL_GetNumVideoDisplays();
|
|
||||||
SDL_DisplayID* ids = SDL_calloc(numDisplays + 1, sizeof(SDL_DisplayID));
|
|
||||||
|
|
||||||
for (int i = 0; i < numDisplays; i++) {
|
|
||||||
ids[i] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (count != NULL) {
|
|
||||||
*count = numDisplays;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SDL_DisplayMode * SDL_GetWindowFullscreenMode(SDL_Window *window)
|
|
||||||
{
|
|
||||||
static SDL_DisplayMode mode;
|
|
||||||
|
|
||||||
if (SDL_GetWindowDisplayMode(window, &mode) == 0) {
|
|
||||||
return &mode;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-237
@@ -1,54 +1,11 @@
|
|||||||
//
|
//
|
||||||
// Compatibility header for older versions of SDL.
|
// Compatibility header for older version of SDL.
|
||||||
// Include this instead of SDL.h directly.
|
// Include this instead of SDL.h directly.
|
||||||
//
|
//
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
#include <stdbool.h>
|
|
||||||
|
|
||||||
// This is a pure C header for compatibility with SDL.h
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// These SLDC_* functions and constants are special SDL-like things
|
|
||||||
// used to abstract certain SDL2 vs SDL3 differences.
|
|
||||||
void* SDLC_Win32_GetHwnd(SDL_Window* window);
|
|
||||||
void* SDLC_MacOS_GetWindow(SDL_Window* window);
|
|
||||||
void* SDLC_X11_GetDisplay(SDL_Window* window);
|
|
||||||
unsigned long SDLC_X11_GetWindow(SDL_Window* window);
|
|
||||||
void* SDLC_Wayland_GetDisplay(SDL_Window* window);
|
|
||||||
void* SDLC_Wayland_GetSurface(SDL_Window* window);
|
|
||||||
int SDLC_KMSDRM_GetFd(SDL_Window* window);
|
|
||||||
int SDLC_KMSDRM_GetDevIndex(SDL_Window* window);
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
SDLC_VIDEO_UNKNOWN,
|
|
||||||
SDLC_VIDEO_WIN32,
|
|
||||||
SDLC_VIDEO_MACOS,
|
|
||||||
SDLC_VIDEO_X11,
|
|
||||||
SDLC_VIDEO_WAYLAND,
|
|
||||||
SDLC_VIDEO_KMSDRM,
|
|
||||||
} SDLC_VideoDriver;
|
|
||||||
SDLC_VideoDriver SDLC_GetVideoDriver();
|
|
||||||
|
|
||||||
bool SDLC_IsFullscreen(SDL_Window* window);
|
|
||||||
bool SDLC_IsFullscreenExclusive(SDL_Window* window);
|
|
||||||
bool SDLC_IsFullscreenDesktop(SDL_Window* window);
|
|
||||||
void SDLC_EnterFullscreen(SDL_Window* window, bool exclusive);
|
|
||||||
void SDLC_LeaveFullscreen(SDL_Window* window);
|
|
||||||
|
|
||||||
SDL_Window* SDLC_CreateWindowWithFallback(const char *title,
|
|
||||||
int x, int y, int w, int h,
|
|
||||||
Uint32 requiredFlags,
|
|
||||||
Uint32 optionalFlags);
|
|
||||||
|
|
||||||
void SDLC_FlushWindowEvents();
|
|
||||||
|
|
||||||
#define SDLC_SUCCESS(x) ((x) == 0)
|
|
||||||
#define SDLC_FAILURE(x) ((x) != 0)
|
|
||||||
|
|
||||||
// SDL_FRect wasn't added until 2.0.10
|
// SDL_FRect wasn't added until 2.0.10
|
||||||
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
#if !SDL_VERSION_ATLEAST(2, 0, 10)
|
||||||
@@ -61,10 +18,6 @@ typedef struct SDL_FRect
|
|||||||
} SDL_FRect;
|
} SDL_FRect;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef SDL_THREAD_PRIORITY_TIME_CRITICAL
|
|
||||||
#define SDL_THREAD_PRIORITY_TIME_CRITICAL SDL_THREAD_PRIORITY_HIGH
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef SDL_HINT_VIDEO_X11_FORCE_EGL
|
#ifndef SDL_HINT_VIDEO_X11_FORCE_EGL
|
||||||
#define SDL_HINT_VIDEO_X11_FORCE_EGL "SDL_VIDEO_X11_FORCE_EGL"
|
#define SDL_HINT_VIDEO_X11_FORCE_EGL "SDL_VIDEO_X11_FORCE_EGL"
|
||||||
#endif
|
#endif
|
||||||
@@ -112,192 +65,3 @@ typedef struct SDL_FRect
|
|||||||
#ifndef SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP
|
#ifndef SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP
|
||||||
#define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP"
|
#define SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP "SDL_VIDEO_WAYLAND_EMULATE_MOUSE_WARP"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// SDL3 renamed hints
|
|
||||||
#define SDL_HINT_VIDEO_FORCE_EGL SDL_HINT_VIDEO_X11_FORCE_EGL
|
|
||||||
|
|
||||||
// Events
|
|
||||||
#define SDL_EVENT_QUIT SDL_QUIT
|
|
||||||
#define SDL_EVENT_CLIPBOARD_UPDATE SDL_CLIPBOARDUPDATE
|
|
||||||
#define SDL_EVENT_GAMEPAD_ADDED SDL_CONTROLLERDEVICEADDED
|
|
||||||
#define SDL_EVENT_GAMEPAD_REMAPPED SDL_CONTROLLERDEVICEREMAPPED
|
|
||||||
#define SDL_EVENT_GAMEPAD_REMOVED SDL_CONTROLLERDEVICEREMOVED
|
|
||||||
#define SDL_EVENT_GAMEPAD_SENSOR_UPDATE SDL_CONTROLLERSENSORUPDATE
|
|
||||||
#define SDL_EVENT_GAMEPAD_BUTTON_DOWN SDL_CONTROLLERBUTTONDOWN
|
|
||||||
#define SDL_EVENT_GAMEPAD_BUTTON_UP SDL_CONTROLLERBUTTONUP
|
|
||||||
#define SDL_EVENT_GAMEPAD_AXIS_MOTION SDL_CONTROLLERAXISMOTION
|
|
||||||
#define SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN SDL_CONTROLLERTOUCHPADDOWN
|
|
||||||
#define SDL_EVENT_GAMEPAD_TOUCHPAD_UP SDL_CONTROLLERTOUCHPADUP
|
|
||||||
#define SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION SDL_CONTROLLERTOUCHPADMOTION
|
|
||||||
#define SDL_EVENT_JOYSTICK_ADDED SDL_JOYDEVICEADDED
|
|
||||||
#define SDL_EVENT_JOYSTICK_REMOVED SDL_JOYDEVICEREMOVED
|
|
||||||
#define SDL_EVENT_JOYSTICK_BATTERY_UPDATED SDL_JOYBATTERYUPDATED
|
|
||||||
#define SDL_EVENT_FINGER_DOWN SDL_FINGERDOWN
|
|
||||||
#define SDL_EVENT_FINGER_MOTION SDL_FINGERMOTION
|
|
||||||
#define SDL_EVENT_FINGER_UP SDL_FINGERUP
|
|
||||||
#define SDL_EVENT_KEY_DOWN SDL_KEYDOWN
|
|
||||||
#define SDL_EVENT_KEY_UP SDL_KEYUP
|
|
||||||
#define SDL_EVENT_MOUSE_BUTTON_DOWN SDL_MOUSEBUTTONDOWN
|
|
||||||
#define SDL_EVENT_MOUSE_BUTTON_UP SDL_MOUSEBUTTONUP
|
|
||||||
#define SDL_EVENT_MOUSE_MOTION SDL_MOUSEMOTION
|
|
||||||
#define SDL_EVENT_MOUSE_WHEEL SDL_MOUSEWHEEL
|
|
||||||
#define SDL_EVENT_RENDER_DEVICE_RESET SDL_RENDER_DEVICE_RESET
|
|
||||||
#define SDL_EVENT_RENDER_TARGETS_RESET SDL_RENDER_TARGETS_RESET
|
|
||||||
#define SDL_EVENT_USER SDL_USEREVENT
|
|
||||||
|
|
||||||
#define SDL_EVENT_WINDOW_FOCUS_GAINED SDL_WINDOWEVENT_FOCUS_GAINED
|
|
||||||
#define SDL_EVENT_WINDOW_FOCUS_LOST SDL_WINDOWEVENT_FOCUS_LOST
|
|
||||||
#define SDL_EVENT_WINDOW_MOUSE_ENTER SDL_WINDOWEVENT_ENTER
|
|
||||||
#define SDL_EVENT_WINDOW_MOUSE_LEAVE SDL_WINDOWEVENT_LEAVE
|
|
||||||
#define SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED SDL_WINDOWEVENT_SIZE_CHANGED
|
|
||||||
#define SDL_EVENT_WINDOW_SHOWN SDL_WINDOWEVENT_SHOWN
|
|
||||||
#define SDL_EVENT_WINDOW_DISPLAY_CHANGED SDL_WINDOWEVENT_DISPLAY_CHANGED
|
|
||||||
|
|
||||||
#define SDL_BUTTON_MASK(x) SDL_BUTTON(x)
|
|
||||||
|
|
||||||
#define gbutton cbutton
|
|
||||||
#define gaxis caxis
|
|
||||||
#define gdevice cdevice
|
|
||||||
#define gsensor csensor
|
|
||||||
#define gtouchpad ctouchpad
|
|
||||||
|
|
||||||
#define fingerID fingerId
|
|
||||||
#define touchID touchId
|
|
||||||
|
|
||||||
#define KEY_DOWN(x) ((x)->state == SDL_PRESSED)
|
|
||||||
#define KEY_KEY(x) ((x)->keysym.sym)
|
|
||||||
#define KEY_MOD(x) ((x)->keysym.mod)
|
|
||||||
#define KEY_SCANCODE(x) ((x)->keysym.scancode)
|
|
||||||
|
|
||||||
// Gamepad
|
|
||||||
#define SDL_INIT_GAMEPAD SDL_INIT_GAMECONTROLLER
|
|
||||||
|
|
||||||
#define SDL_GAMEPAD_BUTTON_SOUTH SDL_CONTROLLER_BUTTON_A
|
|
||||||
#define SDL_GAMEPAD_BUTTON_EAST SDL_CONTROLLER_BUTTON_B
|
|
||||||
#define SDL_GAMEPAD_BUTTON_WEST SDL_CONTROLLER_BUTTON_X
|
|
||||||
#define SDL_GAMEPAD_BUTTON_NORTH SDL_CONTROLLER_BUTTON_Y
|
|
||||||
#define SDL_GAMEPAD_BUTTON_LEFT_SHOULDER SDL_CONTROLLER_BUTTON_LEFTSHOULDER
|
|
||||||
#define SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
|
|
||||||
#define SDL_GAMEPAD_BUTTON_DPAD_UP SDL_CONTROLLER_BUTTON_DPAD_UP
|
|
||||||
#define SDL_GAMEPAD_BUTTON_DPAD_DOWN SDL_CONTROLLER_BUTTON_DPAD_DOWN
|
|
||||||
#define SDL_GAMEPAD_BUTTON_DPAD_LEFT SDL_CONTROLLER_BUTTON_DPAD_LEFT
|
|
||||||
#define SDL_GAMEPAD_BUTTON_DPAD_RIGHT SDL_CONTROLLER_BUTTON_DPAD_RIGHT
|
|
||||||
#define SDL_GAMEPAD_BUTTON_START SDL_CONTROLLER_BUTTON_START
|
|
||||||
#define SDL_GAMEPAD_BUTTON_TOUCHPAD SDL_CONTROLLER_BUTTON_TOUCHPAD
|
|
||||||
|
|
||||||
#define SDL_GAMEPAD_BINDTYPE_AXIS SDL_CONTROLLER_BINDTYPE_AXIS
|
|
||||||
#define SDL_GAMEPAD_BINDTYPE_BUTTON SDL_CONTROLLER_BINDTYPE_BUTTON
|
|
||||||
#define SDL_GAMEPAD_BINDTYPE_HAT SDL_CONTROLLER_BINDTYPE_HAT
|
|
||||||
#define SDL_GAMEPAD_BINDTYPE_NONE SDL_CONTROLLER_BINDTYPE_NONE
|
|
||||||
|
|
||||||
#define SDL_GAMEPAD_AXIS_LEFTX SDL_CONTROLLER_AXIS_LEFTX
|
|
||||||
#define SDL_GAMEPAD_AXIS_LEFTY SDL_CONTROLLER_AXIS_LEFTY
|
|
||||||
#define SDL_GAMEPAD_AXIS_RIGHTX SDL_CONTROLLER_AXIS_RIGHTX
|
|
||||||
#define SDL_GAMEPAD_AXIS_RIGHTY SDL_CONTROLLER_AXIS_RIGHTY
|
|
||||||
#define SDL_GAMEPAD_AXIS_LEFT_TRIGGER SDL_CONTROLLER_AXIS_TRIGGERLEFT
|
|
||||||
#define SDL_GAMEPAD_AXIS_RIGHT_TRIGGER SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
|
||||||
|
|
||||||
// SDL_OpenGamepad() not defined due to differing semantics
|
|
||||||
SDL_JoystickID* SDL_GetGamepads(int *count);
|
|
||||||
#define SDL_OpenGamepad(x) SDL_GameControllerOpen(x)
|
|
||||||
#define SDL_CloseGamepad(x) SDL_GameControllerClose(x)
|
|
||||||
#define SDL_GetGamepadMapping(x) SDL_GameControllerMapping(x)
|
|
||||||
#define SDL_GetGamepadName(x) SDL_GameControllerName(x)
|
|
||||||
#define SDL_GetGamepadVendor(x) SDL_GameControllerGetVendor(x)
|
|
||||||
#define SDL_GetGamepadProduct(x) SDL_GameControllerGetProduct(x)
|
|
||||||
#define SDL_GetGamepadJoystick(x) SDL_GameControllerGetJoystick(x)
|
|
||||||
#define SDL_GamepadHasButton(x, y) SDL_GameControllerHasButton(x, y)
|
|
||||||
#define SDL_GamepadHasSensor(x, y) SDL_GameControllerHasSensor(x, y)
|
|
||||||
#define SDL_SetGamepadPlayerIndex(x, y) SDL_GameControllerSetPlayerIndex(x, y)
|
|
||||||
#define SDL_GamepadSensorEnabled(x, y) SDL_GameControllerIsSensorEnabled(x, y)
|
|
||||||
#define SDL_SetGamepadSensorEnabled(x, y, z) SDL_GameControllerSetSensorEnabled(x, y, z)
|
|
||||||
#define SDL_GetNumGamepadTouchpads(x) SDL_GameControllerGetNumTouchpads(x)
|
|
||||||
#define SDL_SetGamepadLED(x, r, g, b) SDL_GameControllerSetLED(x, r, g, b)
|
|
||||||
#define SDL_RumbleGamepad(x, y, z, w) SDL_GameControllerRumble(x, y, z, w)
|
|
||||||
#define SDL_RumbleGamepadTriggers(x, y, z, w) SDL_GameControllerRumbleTriggers(x, y, z, w)
|
|
||||||
#define SDL_GetGamepadAxis(x, y) SDL_GameControllerGetAxis(x, y)
|
|
||||||
#define SDL_GetGamepadType(x) SDL_GameControllerGetType(x)
|
|
||||||
#define SDL_IsGamepad(x) SDL_IsGameController(x)
|
|
||||||
|
|
||||||
#define SDL_GAMEPAD_TYPE_STANDARD SDL_CONTROLLER_TYPE_UNKNOWN
|
|
||||||
#define SDL_GAMEPAD_TYPE_VIRTUAL SDL_CONTROLLER_TYPE_VIRTUAL
|
|
||||||
#define SDL_GAMEPAD_TYPE_XBOX360 SDL_CONTROLLER_TYPE_XBOX360
|
|
||||||
#define SDL_GAMEPAD_TYPE_XBOXONE SDL_CONTROLLER_TYPE_XBOXONE
|
|
||||||
#define SDL_GAMEPAD_TYPE_PS3 SDL_CONTROLLER_TYPE_PS3
|
|
||||||
#define SDL_GAMEPAD_TYPE_PS4 SDL_CONTROLLER_TYPE_PS4
|
|
||||||
#define SDL_GAMEPAD_TYPE_PS5 SDL_CONTROLLER_TYPE_PS5
|
|
||||||
#define SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT
|
|
||||||
#define SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR
|
|
||||||
#define SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT
|
|
||||||
#define SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO
|
|
||||||
|
|
||||||
// SDL_OpenGamepad() not defined due to differing semantics
|
|
||||||
SDL_JoystickID* SDL_GetJoysticks(int *count);
|
|
||||||
#define SDL_OpenJoystick(x) SDL_JoystickOpen(x)
|
|
||||||
#define SDL_CloseJoystick(x) SDL_JoystickClose(x)
|
|
||||||
#define SDL_GetJoystickID(x) SDL_JoystickInstanceID(x)
|
|
||||||
#define SDL_GetJoystickGUID(x) SDL_JoystickGetGUID(x)
|
|
||||||
#define SDL_GetJoystickPowerLevel(x) SDL_JoystickCurrentPowerLevel(x)
|
|
||||||
#define SDL_GetNumJoystickAxes(x) SDL_JoystickNumAxes(x)
|
|
||||||
#define SDL_GetNumJoystickBalls(x) SDL_JoystickNumBalls(x)
|
|
||||||
#define SDL_GetNumJoystickButtons(x) SDL_JoystickNumButtons(x)
|
|
||||||
#define SDL_GetNumJoystickHats(x) SDL_JoystickNumHats(x)
|
|
||||||
#define SDL_GetJoystickGUIDForID(x) SDL_JoystickGetDeviceGUID(x)
|
|
||||||
|
|
||||||
typedef SDL_ControllerAxisEvent SDL_GamepadAxisEvent;
|
|
||||||
typedef SDL_ControllerButtonEvent SDL_GamepadButtonEvent;
|
|
||||||
typedef SDL_ControllerSensorEvent SDL_GamepadSensorEvent;
|
|
||||||
typedef SDL_ControllerTouchpadEvent SDL_GamepadTouchpadEvent;
|
|
||||||
typedef SDL_ControllerDeviceEvent SDL_GamepadDeviceEvent;
|
|
||||||
typedef SDL_GameController SDL_Gamepad;
|
|
||||||
typedef SDL_GameControllerButton SDL_GamepadButton;
|
|
||||||
|
|
||||||
// Audio
|
|
||||||
#define SDL_AUDIO_F32 AUDIO_F32SYS
|
|
||||||
|
|
||||||
#define SDL_ResumeAudioDevice(x) SDL_PauseAudioDevice(x, 0)
|
|
||||||
|
|
||||||
// Atomics
|
|
||||||
#define SDL_GetAtomicInt(x) SDL_AtomicGet(x)
|
|
||||||
#define SDL_SetAtomicInt(x, y) SDL_AtomicSet(x, y)
|
|
||||||
#define SDL_GetAtomicPointer(x) SDL_AtomicGetPtr(x)
|
|
||||||
#define SDL_SetAtomicPointer(x, y) SDL_AtomicSetPtr(x, y)
|
|
||||||
|
|
||||||
#define SDL_LockSpinlock(x) SDL_AtomicLock(x)
|
|
||||||
#define SDL_TryLockSpinlock(x) SDL_AtomicTryLock(x)
|
|
||||||
#define SDL_UnlockSpinlock(x) SDL_AtomicUnlock(x)
|
|
||||||
|
|
||||||
typedef SDL_atomic_t SDL_AtomicInt;
|
|
||||||
|
|
||||||
// Video
|
|
||||||
#define SDL_KMOD_CTRL KMOD_CTRL
|
|
||||||
#define SDL_KMOD_ALT KMOD_ALT
|
|
||||||
#define SDL_KMOD_SHIFT KMOD_SHIFT
|
|
||||||
#define SDL_KMOD_GUI KMOD_GUI
|
|
||||||
|
|
||||||
#define SDL_WINDOW_HIGH_PIXEL_DENSITY SDL_WINDOW_ALLOW_HIGHDPI
|
|
||||||
|
|
||||||
#define SDL_SetWindowFullscreenMode(x, y) SDL_SetWindowDisplayMode(x, y)
|
|
||||||
#define SDL_GetDisplayForWindow(x) SDL_GetWindowDisplayIndex(x)
|
|
||||||
#define SDL_GetRenderViewport(x, y) SDL_RenderGetViewport(x, y)
|
|
||||||
#define SDL_DestroySurface(x) SDL_FreeSurface(x)
|
|
||||||
#define SDL_RenderTexture(x, y, z, w) SDL_RenderCopy(x, y, z, w)
|
|
||||||
#define SDL_GetPrimaryDisplay() (0)
|
|
||||||
|
|
||||||
#define SDL_CreateSurfaceFrom(w, h, fmt, pixels, pitch) SDL_CreateRGBSurfaceWithFormatFrom(pixels, w, h, SDL_BITSPERPIXEL(fmt), pitch, fmt)
|
|
||||||
|
|
||||||
#define SDLC_DEFAULT_RENDER_DRIVER -1
|
|
||||||
|
|
||||||
typedef int SDL_DisplayID;
|
|
||||||
SDL_DisplayID * SDL_GetDisplays(int *count);
|
|
||||||
const SDL_DisplayMode * SDL_GetWindowFullscreenMode(SDL_Window *window);
|
|
||||||
|
|
||||||
// Misc
|
|
||||||
#define SDL_GetNumLogicalCPUCores() SDL_GetCPUCount()
|
|
||||||
#define SDL_IOFromConstMem(x, y) SDL_RWFromConstMem(x, y)
|
|
||||||
#define SDL_SetCurrentThreadPriority(x) SDL_SetThreadPriority(x)
|
|
||||||
#define SDL_GUIDToString(x, y, z) SDL_JoystickGetGUIDString(x, y, z)
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|||||||
+34
-47
@@ -1,5 +1,5 @@
|
|||||||
QT += core quick network quickcontrols2 svg
|
QT += core quick network quickcontrols2 svg
|
||||||
CONFIG += c++11
|
CONFIG += c++17
|
||||||
|
|
||||||
unix:!macx {
|
unix:!macx {
|
||||||
TARGET = moonlight
|
TARGET = moonlight
|
||||||
@@ -36,35 +36,29 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||||||
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
win32 {
|
win32 {
|
||||||
contains(QT_ARCH, i386) {
|
!exists($$PWD/../libs/windows) {
|
||||||
LIBS += -L$$PWD/../libs/windows/lib/x86
|
error("Missing dependencies. Please run 'powershell .\setup-deps.ps1' to fetch prebuilt libraries.")
|
||||||
INCLUDEPATH += $$PWD/../libs/windows/include/x86
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contains(QT_ARCH, x86_64) {
|
contains(QT_ARCH, x86_64) {
|
||||||
LIBS += -L$$PWD/../libs/windows/lib/x64
|
LIBS += -L$$PWD/../libs/windows/lib/x64
|
||||||
INCLUDEPATH += $$PWD/../libs/windows/include/x64
|
INCLUDEPATH += $$PWD/../libs/windows/include/x64 $$PWD/../libs/windows/include/x64/SDL2
|
||||||
}
|
}
|
||||||
contains(QT_ARCH, arm64) {
|
contains(QT_ARCH, arm64) {
|
||||||
LIBS += -L$$PWD/../libs/windows/lib/arm64
|
LIBS += -L$$PWD/../libs/windows/lib/arm64
|
||||||
INCLUDEPATH += $$PWD/../libs/windows/include/arm64
|
INCLUDEPATH += $$PWD/../libs/windows/include/arm64 $$PWD/../libs/windows/include/arm64/SDL2
|
||||||
}
|
}
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../libs/windows/include
|
INCLUDEPATH += $$PWD/../libs/windows/include
|
||||||
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
|
LIBS += ws2_32.lib winmm.lib dxva2.lib ole32.lib gdi32.lib user32.lib d3d9.lib dwmapi.lib dbghelp.lib
|
||||||
|
|
||||||
# Work around a conflict with math.h inclusion between SDL and Qt 6
|
|
||||||
DEFINES += _USE_MATH_DEFINES
|
|
||||||
}
|
}
|
||||||
macx:!disable-prebuilts {
|
macx:!disable-prebuilts {
|
||||||
INCLUDEPATH += $$PWD/../libs/mac/include
|
!exists($$PWD/../libs/mac) {
|
||||||
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2.framework/Versions/A/Headers
|
error("Missing dependencies. Please run 'python3 setup-deps.py' to fetch prebuilt libraries.")
|
||||||
INCLUDEPATH += $$PWD/../libs/mac/Frameworks/SDL2_ttf.framework/Versions/A/Headers
|
}
|
||||||
LIBS += -L$$PWD/../libs/mac/lib -F$$PWD/../libs/mac/Frameworks
|
|
||||||
|
|
||||||
# QMake doesn't handle framework-style includes correctly on its own
|
INCLUDEPATH += $$PWD/../libs/mac/include $$PWD/../libs/mac/include/SDL2
|
||||||
QMAKE_CFLAGS += -F$$PWD/../libs/mac/Frameworks
|
LIBS += -L$$PWD/../libs/mac/lib
|
||||||
QMAKE_CXXFLAGS += -F$$PWD/../libs/mac/Frameworks
|
|
||||||
QMAKE_OBJECTIVE_CFLAGS += -F$$PWD/../libs/mac/Frameworks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unix:if(!macx|disable-prebuilts) {
|
unix:if(!macx|disable-prebuilts) {
|
||||||
@@ -122,7 +116,9 @@ unix:if(!macx|disable-prebuilts) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
!disable-cuda {
|
# Disabled by default due to reliability issues. See #1314.
|
||||||
|
# CUDA interop is superseded by VDPAU and Vulkan Video.
|
||||||
|
enable-cuda {
|
||||||
packagesExist(ffnvcodec) {
|
packagesExist(ffnvcodec) {
|
||||||
PKGCONFIG += ffnvcodec
|
PKGCONFIG += ffnvcodec
|
||||||
CONFIG += cuda
|
CONFIG += cuda
|
||||||
@@ -157,24 +153,19 @@ win32 {
|
|||||||
CONFIG += ffmpeg libplacebo
|
CONFIG += ffmpeg libplacebo
|
||||||
}
|
}
|
||||||
win32:!winrt {
|
win32:!winrt {
|
||||||
CONFIG += soundio discord-rpc
|
CONFIG += discord-rpc
|
||||||
}
|
}
|
||||||
macx {
|
macx {
|
||||||
!disable-prebuilts {
|
!disable-prebuilts {
|
||||||
LIBS += -lssl.3 -lcrypto.3 -lavcodec.61 -lavutil.59 -lswscale.8 -lopus -framework SDL2 -framework SDL2_ttf
|
LIBS += -lssl.3 -lcrypto.3 -lavcodec.62 -lavutil.60 -lswscale.9 -lopus.0 -lSDL2 -lSDL2_ttf -lplacebo
|
||||||
CONFIG += discord-rpc
|
CONFIG += discord-rpc libplacebo
|
||||||
}
|
}
|
||||||
|
|
||||||
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore
|
LIBS += -lobjc -framework VideoToolbox -framework AVFoundation -framework CoreVideo -framework CoreGraphics -framework CoreMedia -framework AppKit -framework Metal -framework QuartzCore
|
||||||
|
CONFIG += ffmpeg
|
||||||
# For libsoundio
|
|
||||||
LIBS += -framework CoreAudio -framework AudioUnit
|
|
||||||
|
|
||||||
CONFIG += ffmpeg soundio
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
SDL_compat.c \
|
|
||||||
backend/nvaddress.cpp \
|
backend/nvaddress.cpp \
|
||||||
backend/nvapp.cpp \
|
backend/nvapp.cpp \
|
||||||
cli/pair.cpp \
|
cli/pair.cpp \
|
||||||
@@ -205,6 +196,7 @@ SOURCES += \
|
|||||||
streaming/audio/renderers/sdlaud.cpp \
|
streaming/audio/renderers/sdlaud.cpp \
|
||||||
gui/computermodel.cpp \
|
gui/computermodel.cpp \
|
||||||
gui/appmodel.cpp \
|
gui/appmodel.cpp \
|
||||||
|
streaming/bandwidth.cpp \
|
||||||
streaming/streamutils.cpp \
|
streaming/streamutils.cpp \
|
||||||
backend/autoupdatechecker.cpp \
|
backend/autoupdatechecker.cpp \
|
||||||
path.cpp \
|
path.cpp \
|
||||||
@@ -242,6 +234,7 @@ HEADERS += \
|
|||||||
gui/computermodel.h \
|
gui/computermodel.h \
|
||||||
gui/appmodel.h \
|
gui/appmodel.h \
|
||||||
streaming/video/decoder.h \
|
streaming/video/decoder.h \
|
||||||
|
streaming/bandwidth.h \
|
||||||
streaming/streamutils.h \
|
streaming/streamutils.h \
|
||||||
backend/autoupdatechecker.h \
|
backend/autoupdatechecker.h \
|
||||||
path.h \
|
path.h \
|
||||||
@@ -330,9 +323,12 @@ libdrm {
|
|||||||
HEADERS += streaming/video/ffmpeg-renderers/drm.h
|
HEADERS += streaming/video/ffmpeg-renderers/drm.h
|
||||||
|
|
||||||
linux {
|
linux {
|
||||||
message(Master hooks enabled)
|
!disable-masterhooks {
|
||||||
SOURCES += masterhook.c masterhook_internal.c
|
message(Master hooks enabled)
|
||||||
LIBS += -ldl
|
DEFINES += HAVE_DRM_MASTER_HOOKS
|
||||||
|
SOURCES += masterhook.c masterhook_internal.c
|
||||||
|
LIBS += -ldl -pthread
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cuda {
|
cuda {
|
||||||
@@ -354,6 +350,10 @@ libplacebo {
|
|||||||
streaming/video/ffmpeg-renderers/plvk_c.c
|
streaming/video/ffmpeg-renderers/plvk_c.c
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
streaming/video/ffmpeg-renderers/plvk.h
|
streaming/video/ffmpeg-renderers/plvk.h
|
||||||
|
|
||||||
|
macx {
|
||||||
|
SOURCES += streaming/video/ffmpeg-renderers/plvk_objc.mm
|
||||||
|
}
|
||||||
}
|
}
|
||||||
config_EGL {
|
config_EGL {
|
||||||
message(EGL renderer selected)
|
message(EGL renderer selected)
|
||||||
@@ -415,13 +415,6 @@ macx {
|
|||||||
HEADERS += \
|
HEADERS += \
|
||||||
streaming/video/ffmpeg-renderers/vt.h
|
streaming/video/ffmpeg-renderers/vt.h
|
||||||
}
|
}
|
||||||
soundio {
|
|
||||||
message(libsoundio audio renderer selected)
|
|
||||||
|
|
||||||
DEFINES += HAVE_SOUNDIO SOUNDIO_STATIC_LIBRARY
|
|
||||||
SOURCES += streaming/audio/renderers/soundioaudiorenderer.cpp
|
|
||||||
HEADERS += streaming/audio/renderers/soundioaudiorenderer.h
|
|
||||||
}
|
|
||||||
discord-rpc {
|
discord-rpc {
|
||||||
message(Discord integration enabled)
|
message(Discord integration enabled)
|
||||||
|
|
||||||
@@ -487,7 +480,10 @@ TRANSLATIONS += \
|
|||||||
languages/qml_he.ts \
|
languages/qml_he.ts \
|
||||||
languages/qml_ckb.ts \
|
languages/qml_ckb.ts \
|
||||||
languages/qml_lt.ts \
|
languages/qml_lt.ts \
|
||||||
languages/qml_et.ts
|
languages/qml_et.ts \
|
||||||
|
languages/qml_bg.ts \
|
||||||
|
languages/qml_eo.ts \
|
||||||
|
languages/qml_ta.ts
|
||||||
|
|
||||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||||
QML_IMPORT_PATH =
|
QML_IMPORT_PATH =
|
||||||
@@ -509,15 +505,6 @@ else:unix: LIBS += -L$$OUT_PWD/../qmdnsengine/ -lqmdnsengine
|
|||||||
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
INCLUDEPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||||
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
DEPENDPATH += $$PWD/../qmdnsengine/qmdnsengine/src/include $$PWD/../qmdnsengine
|
||||||
|
|
||||||
soundio {
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../soundio/release/ -lsoundio
|
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../soundio/debug/ -lsoundio
|
|
||||||
else:unix: LIBS += -L$$OUT_PWD/../soundio/ -lsoundio
|
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../soundio/libsoundio
|
|
||||||
DEPENDPATH += $$PWD/../soundio/libsoundio
|
|
||||||
}
|
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/release/ -lh264bitstream
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/release/ -lh264bitstream
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/debug/ -lh264bitstream
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../h264bitstream/debug/ -lh264bitstream
|
||||||
else:unix: LIBS += -L$$OUT_PWD/../h264bitstream/ -lh264bitstream
|
else:unix: LIBS += -L$$OUT_PWD/../h264bitstream/ -lh264bitstream
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ void AutoUpdateChecker::start()
|
|||||||
void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>& version)
|
void AutoUpdateChecker::parseStringToVersionQuad(QString& string, QVector<int>& version)
|
||||||
{
|
{
|
||||||
QStringList list = string.split('.');
|
QStringList list = string.split('.');
|
||||||
for (const QString& component : list) {
|
for (const QString& component : std::as_const(list)) {
|
||||||
version.append(component.toInt());
|
version.append(component.toInt());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ void AutoUpdateChecker::handleUpdateCheckRequestFinished(QNetworkReply* reply)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (QJsonValueRef updateEntry : array) {
|
for (const auto& updateEntry : std::as_const(array)) {
|
||||||
if (updateEntry.isObject()) {
|
if (updateEntry.isObject()) {
|
||||||
QJsonObject updateObj = updateEntry.toObject();
|
QJsonObject updateObj = updateEntry.toObject();
|
||||||
if (!updateObj.contains("platform") ||
|
if (!updateObj.contains("platform") ||
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QRandomGenerator>
|
||||||
#include <random>
|
|
||||||
|
|
||||||
#define SER_HOSTS "hosts"
|
#define SER_HOSTS "hosts"
|
||||||
#define SER_HOSTS_BACKUP "hostsbackup"
|
#define SER_HOSTS_BACKUP "hostsbackup"
|
||||||
@@ -30,9 +29,9 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool tryPollComputer(NvAddress address, bool& changed)
|
bool tryPollComputer(QNetworkAccessManager* nam, NvAddress address, bool& changed)
|
||||||
{
|
{
|
||||||
NvHTTP http(address, 0, m_Computer->serverCert);
|
NvHTTP http(address, 0, m_Computer->serverCert, !m_Computer->isNvidiaServerSoftware, nam);
|
||||||
|
|
||||||
QString serverInfo;
|
QString serverInfo;
|
||||||
try {
|
try {
|
||||||
@@ -53,9 +52,9 @@ private:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool updateAppList(bool& changed)
|
bool updateAppList(QNetworkAccessManager* nam, bool& changed)
|
||||||
{
|
{
|
||||||
NvHTTP http(m_Computer);
|
NvHTTP http(m_Computer, nam);
|
||||||
|
|
||||||
QVector<NvApp> appList;
|
QVector<NvApp> appList;
|
||||||
|
|
||||||
@@ -75,6 +74,21 @@ private:
|
|||||||
|
|
||||||
void run() override
|
void run() override
|
||||||
{
|
{
|
||||||
|
// Reduce the power and performance impact of our
|
||||||
|
// computer status polling while it's running.
|
||||||
|
setPriority(QThread::LowPriority);
|
||||||
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
|
||||||
|
setServiceLevel(QThread::QualityOfService::Eco);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Share the QNetworkAccessManager to conserve resources when polling.
|
||||||
|
// Each instance creates a worker thread, so sharing them ensures that
|
||||||
|
// we are not spamming a new thread for every single polling attempt.
|
||||||
|
//
|
||||||
|
// Since QThread inherit the priority of the current thread, this also
|
||||||
|
// ensures that the NAM's worker thread will inherit our lower priority.
|
||||||
|
QNetworkAccessManager nam;
|
||||||
|
|
||||||
// Always fetch the applist the first time
|
// Always fetch the applist the first time
|
||||||
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
int pollsSinceLastAppListFetch = POLLS_PER_APPLIST_FETCH;
|
||||||
while (!isInterruptionRequested()) {
|
while (!isInterruptionRequested()) {
|
||||||
@@ -87,7 +101,7 @@ private:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tryPollComputer(address, stateChanged)) {
|
if (tryPollComputer(&nam, address, stateChanged)) {
|
||||||
if (!wasOnline) {
|
if (!wasOnline) {
|
||||||
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress.toString();
|
qInfo() << m_Computer->name << "is now online at" << m_Computer->activeAddress.toString();
|
||||||
}
|
}
|
||||||
@@ -118,7 +132,7 @@ private:
|
|||||||
stateChanged = false;
|
stateChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateAppList(stateChanged)) {
|
if (updateAppList(&nam, stateChanged)) {
|
||||||
pollsSinceLastAppListFetch = 0;
|
pollsSinceLastAppListFetch = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,17 +231,17 @@ ComputerManager::~ComputerManager()
|
|||||||
m_MdnsBrowser = nullptr;
|
m_MdnsBrowser = nullptr;
|
||||||
|
|
||||||
// Interrupt polling
|
// Interrupt polling
|
||||||
for (ComputerPollingEntry* entry : m_PollEntries) {
|
for (ComputerPollingEntry* entry : std::as_const(m_PollEntries)) {
|
||||||
entry->interrupt();
|
entry->interrupt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all polling entries (and associated threads)
|
// Delete all polling entries (and associated threads)
|
||||||
for (ComputerPollingEntry* entry : m_PollEntries) {
|
for (ComputerPollingEntry* entry : std::as_const(m_PollEntries)) {
|
||||||
delete entry;
|
delete entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destroy all NvComputer objects now that polling is halted
|
// Destroy all NvComputer objects now that polling is halted
|
||||||
for (NvComputer* computer : m_KnownHosts) {
|
for (NvComputer* computer : std::as_const(m_KnownHosts)) {
|
||||||
delete computer;
|
delete computer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,7 +268,7 @@ void DelayedFlushThread::run() {
|
|||||||
|
|
||||||
// Update the last serialized hosts map under the delayed flush mutex
|
// Update the last serialized hosts map under the delayed flush mutex
|
||||||
m_ComputerManager->m_LastSerializedHosts.clear();
|
m_ComputerManager->m_LastSerializedHosts.clear();
|
||||||
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
|
for (const NvComputer* computer : std::as_const(m_ComputerManager->m_KnownHosts)) {
|
||||||
// Copy the current state of the NvComputer to allow us to check later if we need
|
// Copy the current state of the NvComputer to allow us to check later if we need
|
||||||
// to serialize it again when attribute updates occur.
|
// to serialize it again when attribute updates occur.
|
||||||
QReadLocker computerLock(&computer->lock);
|
QReadLocker computerLock(&computer->lock);
|
||||||
@@ -271,7 +285,7 @@ void DelayedFlushThread::run() {
|
|||||||
{
|
{
|
||||||
QReadLocker lock(&m_ComputerManager->m_Lock);
|
QReadLocker lock(&m_ComputerManager->m_Lock);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
|
for (const NvComputer* computer : std::as_const(m_ComputerManager->m_KnownHosts)) {
|
||||||
settings.setArrayIndex(i++);
|
settings.setArrayIndex(i++);
|
||||||
computer->serialize(settings, false);
|
computer->serialize(settings, false);
|
||||||
}
|
}
|
||||||
@@ -284,7 +298,7 @@ void DelayedFlushThread::run() {
|
|||||||
{
|
{
|
||||||
QReadLocker lock(&m_ComputerManager->m_Lock);
|
QReadLocker lock(&m_ComputerManager->m_Lock);
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const NvComputer* computer : m_ComputerManager->m_KnownHosts) {
|
for (const NvComputer* computer : std::as_const(m_ComputerManager->m_KnownHosts)) {
|
||||||
settings.setArrayIndex(i++);
|
settings.setArrayIndex(i++);
|
||||||
computer->serialize(settings, true);
|
computer->serialize(settings, true);
|
||||||
}
|
}
|
||||||
@@ -410,13 +424,15 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
|||||||
bool added = false;
|
bool added = false;
|
||||||
|
|
||||||
// Add the host using the IPv4 address
|
// Add the host using the IPv4 address
|
||||||
for (const QHostAddress& address : addresses) {
|
for (const QHostAddress& address : std::as_const(addresses)) {
|
||||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||||
// NB: We don't just call addNewHost() here with v6Global because the IPv6
|
// NB: We don't just call addNewHost() here with v6Global because the IPv6
|
||||||
// address may not be reachable (if the user hasn't installed the IPv6 helper yet
|
// address may not be reachable (if the user hasn't installed the IPv6 helper yet
|
||||||
// or if this host lacks outbound IPv6 capability). We want to add IPv6 even if
|
// or if this host lacks outbound IPv6 capability). We want to add IPv6 even if
|
||||||
// it's not currently reachable.
|
// it's not currently reachable.
|
||||||
addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port()));
|
addNewHost(NvAddress(address, computer->port()),
|
||||||
|
true, computer->hostname(),
|
||||||
|
NvAddress(v6Global, computer->port()));
|
||||||
added = true;
|
added = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -424,13 +440,15 @@ void ComputerManager::handleMdnsServiceResolved(MdnsPendingComputer* computer,
|
|||||||
|
|
||||||
if (!added) {
|
if (!added) {
|
||||||
// If we get here, there wasn't an IPv4 address so we'll do it v6-only
|
// If we get here, there wasn't an IPv4 address so we'll do it v6-only
|
||||||
for (const QHostAddress& address : addresses) {
|
for (const QHostAddress& address : std::as_const(addresses)) {
|
||||||
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
if (address.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
// Use a link-local or site-local address for the "local address"
|
// Use a link-local or site-local address for the "local address"
|
||||||
if (address.isInSubnet(QHostAddress("fe80::"), 10) ||
|
if (address.isInSubnet(QHostAddress("fe80::"), 10) ||
|
||||||
address.isInSubnet(QHostAddress("fec0::"), 10) ||
|
address.isInSubnet(QHostAddress("fec0::"), 10) ||
|
||||||
address.isInSubnet(QHostAddress("fc00::"), 7)) {
|
address.isInSubnet(QHostAddress("fc00::"), 7)) {
|
||||||
addNewHost(NvAddress(address, computer->port()), true, NvAddress(v6Global, computer->port()));
|
addNewHost(NvAddress(address, computer->port()),
|
||||||
|
true, computer->hostname(),
|
||||||
|
NvAddress(v6Global, computer->port()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -551,7 +569,7 @@ void ComputerManager::handleAboutToQuit()
|
|||||||
|
|
||||||
// Interrupt polling threads immediately, so they
|
// Interrupt polling threads immediately, so they
|
||||||
// avoid making additional requests while quitting
|
// avoid making additional requests while quitting
|
||||||
for (ComputerPollingEntry* entry : m_PollEntries) {
|
for (ComputerPollingEntry* entry : std::as_const(m_PollEntries)) {
|
||||||
entry->interrupt();
|
entry->interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -703,7 +721,7 @@ void ComputerManager::stopPollingAsync()
|
|||||||
m_MdnsServer.reset();
|
m_MdnsServer.reset();
|
||||||
|
|
||||||
// Interrupt all threads, but don't wait for them to terminate
|
// Interrupt all threads, but don't wait for them to terminate
|
||||||
for (ComputerPollingEntry* entry : m_PollEntries) {
|
for (ComputerPollingEntry* entry : std::as_const(m_PollEntries)) {
|
||||||
entry->interrupt();
|
entry->interrupt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -715,6 +733,10 @@ void ComputerManager::addNewHostManually(QString address)
|
|||||||
// If there wasn't a port specified, use the default
|
// If there wasn't a port specified, use the default
|
||||||
addNewHost(NvAddress(url.host(), url.port(DEFAULT_HTTP_PORT)), false);
|
addNewHost(NvAddress(url.host(), url.port(DEFAULT_HTTP_PORT)), false);
|
||||||
}
|
}
|
||||||
|
else if (QHostAddress(address).protocol() == QAbstractSocket::IPv6Protocol) {
|
||||||
|
// The user specified an IPv6 literal without URL escaping, so use the default port
|
||||||
|
addNewHost(NvAddress(address, DEFAULT_HTTP_PORT), false);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
emit computerAddCompleted(false, false);
|
emit computerAddCompleted(false, false);
|
||||||
}
|
}
|
||||||
@@ -725,8 +747,9 @@ class PendingAddTask : public QObject, public QRunnable
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PendingAddTask(ComputerManager* computerManager, NvAddress address, NvAddress mdnsIpv6Address, bool mdns)
|
PendingAddTask(ComputerManager* computerManager, QString name, NvAddress address, NvAddress mdnsIpv6Address, bool mdns)
|
||||||
: m_ComputerManager(computerManager),
|
: m_ComputerManager(computerManager),
|
||||||
|
m_Name(name),
|
||||||
m_Address(address),
|
m_Address(address),
|
||||||
m_MdnsIpv6Address(mdnsIpv6Address),
|
m_MdnsIpv6Address(mdnsIpv6Address),
|
||||||
m_Mdns(mdns),
|
m_Mdns(mdns),
|
||||||
@@ -802,9 +825,21 @@ private:
|
|||||||
|
|
||||||
void run()
|
void run()
|
||||||
{
|
{
|
||||||
NvHTTP http(m_Address, 0, QSslCertificate());
|
// Use the placeholder UID for the initial poll, then we'll switch to the real one if it's not GFE
|
||||||
|
NvHTTP http(m_Address, 0, QSslCertificate(), false);
|
||||||
|
|
||||||
qInfo() << "Processing new PC at" << m_Address.toString() << "from" << (m_Mdns ? "mDNS" : "user") << "with IPv6 address" << m_MdnsIpv6Address.toString();
|
if (m_Mdns) {
|
||||||
|
if (m_MdnsIpv6Address.isNull()) {
|
||||||
|
qInfo() << "Processing new PC" << m_Name << "from mDNS with local address" << m_Address.toString();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qInfo() << "Processing new PC" << m_Name << "from mDNS with local address" << m_Address.toString()
|
||||||
|
<< "and IPv6 address" << m_MdnsIpv6Address.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
qInfo() << "Processing new PC at" << m_Address.toString() << "from user";
|
||||||
|
}
|
||||||
|
|
||||||
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
|
// Perform initial serverinfo fetch over HTTP since we don't know which cert to use
|
||||||
QString serverInfo = fetchServerInfo(http);
|
QString serverInfo = fetchServerInfo(http);
|
||||||
@@ -819,6 +854,7 @@ private:
|
|||||||
|
|
||||||
// Create initial newComputer using HTTP serverinfo with no pinned cert
|
// Create initial newComputer using HTTP serverinfo with no pinned cert
|
||||||
NvComputer* newComputer = new NvComputer(http, serverInfo);
|
NvComputer* newComputer = new NvComputer(http, serverInfo);
|
||||||
|
http.setTrueUid(!newComputer->isNvidiaServerSoftware);
|
||||||
|
|
||||||
// Check if we have a record of this host UUID to pull the pinned cert
|
// Check if we have a record of this host UUID to pull the pinned cert
|
||||||
NvComputer* existingComputer;
|
NvComputer* existingComputer;
|
||||||
@@ -952,28 +988,24 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
ComputerManager* m_ComputerManager;
|
ComputerManager* m_ComputerManager;
|
||||||
|
QString m_Name;
|
||||||
NvAddress m_Address;
|
NvAddress m_Address;
|
||||||
NvAddress m_MdnsIpv6Address;
|
NvAddress m_MdnsIpv6Address;
|
||||||
bool m_Mdns;
|
bool m_Mdns;
|
||||||
bool m_AboutToQuit;
|
bool m_AboutToQuit;
|
||||||
};
|
};
|
||||||
|
|
||||||
void ComputerManager::addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address)
|
void ComputerManager::addNewHost(NvAddress address, bool mdns, QString name, NvAddress mdnsIpv6Address)
|
||||||
{
|
{
|
||||||
// Punt to a worker thread to avoid stalling the
|
// Punt to a worker thread to avoid stalling the
|
||||||
// UI while waiting for serverinfo query to complete
|
// UI while waiting for serverinfo query to complete
|
||||||
PendingAddTask* addTask = new PendingAddTask(this, address, mdnsIpv6Address, mdns);
|
PendingAddTask* addTask = new PendingAddTask(this, name, address, mdnsIpv6Address, mdns);
|
||||||
QThreadPool::globalInstance()->start(addTask);
|
QThreadPool::globalInstance()->start(addTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use QRandomGenerator when we drop Qt 5.9 support
|
|
||||||
QString ComputerManager::generatePinString()
|
QString ComputerManager::generatePinString()
|
||||||
{
|
{
|
||||||
std::uniform_int_distribution<int> dist(0, 9999);
|
return QString::asprintf("%04u", QRandomGenerator::system()->bounded(10000));
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 engine(rd());
|
|
||||||
|
|
||||||
return QString::asprintf("%04u", dist(engine));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "computermanager.moc"
|
#include "computermanager.moc"
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ private slots:
|
|||||||
|
|
||||||
void handleResolvedAddress(const QHostAddress& address)
|
void handleResolvedAddress(const QHostAddress& address)
|
||||||
{
|
{
|
||||||
qInfo() << "Resolved" << hostname() << "to" << address;
|
|
||||||
m_Addresses.push_back(address);
|
m_Addresses.push_back(address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +150,7 @@ public:
|
|||||||
// interrupt() should have taken care of this
|
// interrupt() should have taken care of this
|
||||||
Q_ASSERT(m_ActiveThread == nullptr);
|
Q_ASSERT(m_ActiveThread == nullptr);
|
||||||
|
|
||||||
for (QThread* thread : m_InactiveList) {
|
for (QThread* thread : std::as_const(m_InactiveList)) {
|
||||||
thread->wait();
|
thread->wait();
|
||||||
delete thread;
|
delete thread;
|
||||||
}
|
}
|
||||||
@@ -228,7 +227,7 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void addNewHostManually(QString address);
|
Q_INVOKABLE void addNewHostManually(QString address);
|
||||||
|
|
||||||
void addNewHost(NvAddress address, bool mdns, NvAddress mdnsIpv6Address = NvAddress());
|
void addNewHost(NvAddress address, bool mdns, QString name = QString(), NvAddress mdnsIpv6Address = NvAddress());
|
||||||
|
|
||||||
QString generatePinString();
|
QString generatePinString();
|
||||||
|
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, Q
|
|||||||
m_TimeoutTimer(new QTimer(this))
|
m_TimeoutTimer(new QTimer(this))
|
||||||
{
|
{
|
||||||
// If we know this computer, send a WOL packet to wake it up in case it is asleep.
|
// If we know this computer, send a WOL packet to wake it up in case it is asleep.
|
||||||
for (NvComputer * computer: m_ComputerManager->getComputers()) {
|
NvComputer* matchingComputer = findMatchingComputer();
|
||||||
if (this->matchComputer(computer)) {
|
if (matchingComputer) {
|
||||||
computer->wake();
|
matchingComputer->wake();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_TimeoutTimer->setSingleShot(true);
|
m_TimeoutTimer->setSingleShot(true);
|
||||||
@@ -23,11 +22,16 @@ ComputerSeeker::ComputerSeeker(ComputerManager *manager, QString computerName, Q
|
|||||||
void ComputerSeeker::start(int timeout)
|
void ComputerSeeker::start(int timeout)
|
||||||
{
|
{
|
||||||
m_TimeoutTimer->start(timeout);
|
m_TimeoutTimer->start(timeout);
|
||||||
// Seek desired computer by both connecting to it directly (this may fail
|
|
||||||
// if m_ComputerName is UUID, or the name that doesn't resolve to an IP
|
// If we don't know this computer by name, address, or UUID, try adding it
|
||||||
// address) and by polling it using mDNS, hopefully one of these methods
|
// manually and see if we can find it by address, hostname, or mDNS.
|
||||||
// would find the host
|
//
|
||||||
m_ComputerManager->addNewHostManually(m_ComputerName);
|
// NB: We don't do this unconditionally because it will wipe out the user's
|
||||||
|
// manual address if they pass another reachable hostname/address.
|
||||||
|
if (!findMatchingComputer()) {
|
||||||
|
m_ComputerManager->addNewHostManually(m_ComputerName);
|
||||||
|
}
|
||||||
|
|
||||||
m_ComputerManager->startPolling();
|
m_ComputerManager->startPolling();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +55,8 @@ bool ComputerSeeker::matchComputer(NvComputer *computer) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const NvAddress& addr : computer->uniqueAddresses()) {
|
const auto uniqueAddresses = computer->uniqueAddresses();
|
||||||
|
for (const NvAddress& addr : uniqueAddresses) {
|
||||||
if (addr.address().toLower() == value || addr.toString().toLower() == value) {
|
if (addr.address().toLower() == value || addr.toString().toLower() == value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -60,6 +65,18 @@ bool ComputerSeeker::matchComputer(NvComputer *computer) const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NvComputer* ComputerSeeker::findMatchingComputer() const
|
||||||
|
{
|
||||||
|
const auto computers = m_ComputerManager->getComputers();
|
||||||
|
for (NvComputer* computer : computers) {
|
||||||
|
if (this->matchComputer(computer)) {
|
||||||
|
return computer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool ComputerSeeker::isOnline(NvComputer *computer) const
|
bool ComputerSeeker::isOnline(NvComputer *computer) const
|
||||||
{
|
{
|
||||||
return computer->state == NvComputer::CS_ONLINE;
|
return computer->state == NvComputer::CS_ONLINE;
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ private slots:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
bool matchComputer(NvComputer *computer) const;
|
bool matchComputer(NvComputer *computer) const;
|
||||||
|
NvComputer* findMatchingComputer() const;
|
||||||
bool isOnline(NvComputer *computer) const;
|
bool isOnline(NvComputer *computer) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ void IdentityManager::createCredentials(QSettings& settings)
|
|||||||
X509* cert = X509_new();
|
X509* cert = X509_new();
|
||||||
THROW_BAD_ALLOC_IF_NULL(cert);
|
THROW_BAD_ALLOC_IF_NULL(cert);
|
||||||
|
|
||||||
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
|
||||||
|
EVP_PKEY* pk = EVP_RSA_gen(2048);
|
||||||
|
THROW_BAD_ALLOC_IF_NULL(pk);
|
||||||
|
#else
|
||||||
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
|
||||||
THROW_BAD_ALLOC_IF_NULL(ctx);
|
THROW_BAD_ALLOC_IF_NULL(ctx);
|
||||||
|
|
||||||
@@ -43,6 +47,7 @@ void IdentityManager::createCredentials(QSettings& settings)
|
|||||||
|
|
||||||
EVP_PKEY_CTX_free(ctx);
|
EVP_PKEY_CTX_free(ctx);
|
||||||
THROW_BAD_ALLOC_IF_NULL(pk);
|
THROW_BAD_ALLOC_IF_NULL(pk);
|
||||||
|
#endif
|
||||||
|
|
||||||
X509_set_version(cert, 2);
|
X509_set_version(cert, 2);
|
||||||
ASN1_INTEGER_set(X509_get_serialNumber(cert), 0);
|
ASN1_INTEGER_set(X509_get_serialNumber(cert), 0);
|
||||||
@@ -50,28 +55,20 @@ void IdentityManager::createCredentials(QSettings& settings)
|
|||||||
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
X509_gmtime_adj(X509_get_notBefore(cert), 0);
|
||||||
X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365 * 20); // 20 yrs
|
X509_gmtime_adj(X509_get_notAfter(cert), 60 * 60 * 24 * 365 * 20); // 20 yrs
|
||||||
#else
|
#else
|
||||||
ASN1_TIME* before = ASN1_STRING_dup(X509_get0_notBefore(cert));
|
X509_gmtime_adj(X509_getm_notBefore(cert), 0);
|
||||||
THROW_BAD_ALLOC_IF_NULL(before);
|
X509_gmtime_adj(X509_getm_notAfter(cert), 60 * 60 * 24 * 365 * 20); // 20 yrs
|
||||||
ASN1_TIME* after = ASN1_STRING_dup(X509_get0_notAfter(cert));
|
|
||||||
THROW_BAD_ALLOC_IF_NULL(after);
|
|
||||||
|
|
||||||
X509_gmtime_adj(before, 0);
|
|
||||||
X509_gmtime_adj(after, 60 * 60 * 24 * 365 * 20); // 20 yrs
|
|
||||||
|
|
||||||
X509_set1_notBefore(cert, before);
|
|
||||||
X509_set1_notAfter(cert, after);
|
|
||||||
|
|
||||||
ASN1_STRING_free(before);
|
|
||||||
ASN1_STRING_free(after);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
X509_set_pubkey(cert, pk);
|
X509_set_pubkey(cert, pk);
|
||||||
|
|
||||||
X509_NAME* name = X509_get_subject_name(cert);
|
X509_NAME* name = X509_NAME_new();
|
||||||
|
THROW_BAD_ALLOC_IF_NULL(name);
|
||||||
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
|
||||||
reinterpret_cast<unsigned char *>(const_cast<char*>("NVIDIA GameStream Client")),
|
reinterpret_cast<unsigned char *>(const_cast<char*>("NVIDIA GameStream Client")),
|
||||||
-1, -1, 0);
|
-1, -1, 0);
|
||||||
|
X509_set_subject_name(cert, name);
|
||||||
X509_set_issuer_name(cert, name);
|
X509_set_issuer_name(cert, name);
|
||||||
|
X509_NAME_free(name);
|
||||||
|
|
||||||
X509_sign(cert, pk, EVP_sha256());
|
X509_sign(cert, pk, EVP_sha256());
|
||||||
|
|
||||||
@@ -136,6 +133,22 @@ IdentityManager::IdentityManager()
|
|||||||
if (getSslKey().isNull()) {
|
if (getSslKey().isNull()) {
|
||||||
qFatal("Private key is unreadable");
|
qFatal("Private key is unreadable");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load the unique ID from settings
|
||||||
|
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
||||||
|
if (!m_CachedUniqueId.isEmpty()) {
|
||||||
|
qInfo() << "Loaded unique ID from settings:" << m_CachedUniqueId;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Generate a new unique ID in base 16
|
||||||
|
uint64_t uid;
|
||||||
|
RAND_bytes(reinterpret_cast<unsigned char*>(&uid), sizeof(uid));
|
||||||
|
m_CachedUniqueId = QString::number(uid, 16);
|
||||||
|
|
||||||
|
qInfo() << "Generated new unique ID:" << m_CachedUniqueId;
|
||||||
|
|
||||||
|
settings.setValue(SER_UNIQUEID, m_CachedUniqueId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSslCertificate
|
QSslCertificate
|
||||||
@@ -173,7 +186,7 @@ IdentityManager::getSslKey()
|
|||||||
|
|
||||||
BUF_MEM* mem;
|
BUF_MEM* mem;
|
||||||
BIO_get_mem_ptr(bio, &mem);
|
BIO_get_mem_ptr(bio, &mem);
|
||||||
m_CachedSslKey = QSslKey(QByteArray::fromRawData(mem->data, (int)mem->length), QSsl::Rsa);
|
m_CachedSslKey = QSslKey(QByteArray(mem->data, (int)mem->length), QSsl::Rsa);
|
||||||
|
|
||||||
BIO_free(bio);
|
BIO_free(bio);
|
||||||
EVP_PKEY_free(pk);
|
EVP_PKEY_free(pk);
|
||||||
@@ -193,25 +206,6 @@ IdentityManager::getSslConfig()
|
|||||||
QString
|
QString
|
||||||
IdentityManager::getUniqueId()
|
IdentityManager::getUniqueId()
|
||||||
{
|
{
|
||||||
if (m_CachedUniqueId.isEmpty()) {
|
|
||||||
QSettings settings;
|
|
||||||
|
|
||||||
// Load the unique ID from settings
|
|
||||||
m_CachedUniqueId = settings.value(SER_UNIQUEID).toString();
|
|
||||||
if (!m_CachedUniqueId.isEmpty()) {
|
|
||||||
qInfo() << "Loaded unique ID from settings:" << m_CachedUniqueId;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Generate a new unique ID in base 16
|
|
||||||
uint64_t uid;
|
|
||||||
RAND_bytes(reinterpret_cast<unsigned char*>(&uid), sizeof(uid));
|
|
||||||
m_CachedUniqueId = QString::number(uid, 16);
|
|
||||||
|
|
||||||
qInfo() << "Generated new unique ID:" << m_CachedUniqueId;
|
|
||||||
|
|
||||||
settings.setValue(SER_UNIQUEID, m_CachedUniqueId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return m_CachedUniqueId;
|
return m_CachedUniqueId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -25,7 +25,9 @@ public:
|
|||||||
|
|
||||||
bool isInitialized()
|
bool isInitialized()
|
||||||
{
|
{
|
||||||
return id != 0 && !name.isEmpty();
|
// We use isNull() instead of isEmpty() here because we want
|
||||||
|
// to detect cases where the name is unassigned, not empty.
|
||||||
|
return id != 0 && !name.isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ void NvComputer::serialize(QSettings& settings, bool serializeApps) const
|
|||||||
settings.beginWriteArray(SER_APPLIST);
|
settings.beginWriteArray(SER_APPLIST);
|
||||||
for (int i = 0; i < appList.count(); i++) {
|
for (int i = 0; i < appList.count(); i++) {
|
||||||
settings.setArrayIndex(i);
|
settings.setArrayIndex(i);
|
||||||
appList[i].serialize(settings);
|
appList.at(i).serialize(settings);
|
||||||
}
|
}
|
||||||
settings.endArray();
|
settings.endArray();
|
||||||
}
|
}
|
||||||
@@ -141,7 +141,7 @@ NvComputer::NvComputer(NvHTTP& http, QString serverInfo)
|
|||||||
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
|
QString newMacString = NvHTTP::getXmlString(serverInfo, "mac");
|
||||||
if (newMacString != "00:00:00:00:00:00") {
|
if (newMacString != "00:00:00:00:00:00") {
|
||||||
QStringList macOctets = newMacString.split(':');
|
QStringList macOctets = newMacString.split(':');
|
||||||
for (const QString& macOctet : macOctets) {
|
for (const QString& macOctet : std::as_const(macOctets)) {
|
||||||
this->macAddress.append((char) macOctet.toInt(nullptr, 16));
|
this->macAddress.append((char) macOctet.toInt(nullptr, 16));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,14 +254,16 @@ bool NvComputer::wake() const
|
|||||||
// case the host has timed out in ARP entries.
|
// case the host has timed out in ARP entries.
|
||||||
QMap<QString, quint16> addressMap;
|
QMap<QString, quint16> addressMap;
|
||||||
QSet<quint16> basePortSet;
|
QSet<quint16> basePortSet;
|
||||||
for (const NvAddress& addr : uniqueAddresses()) {
|
const auto uniqueHostAddresses = uniqueAddresses();
|
||||||
|
for (const NvAddress& addr : uniqueHostAddresses) {
|
||||||
addressMap.insert(addr.address(), addr.port());
|
addressMap.insert(addr.address(), addr.port());
|
||||||
basePortSet.insert(addr.port());
|
basePortSet.insert(addr.port());
|
||||||
}
|
}
|
||||||
addressMap.insert("255.255.255.255", 0);
|
addressMap.insert("255.255.255.255", 0);
|
||||||
|
|
||||||
// Try to broadcast on all available NICs
|
// Try to broadcast on all available NICs
|
||||||
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
const auto allInterfaces = QNetworkInterface::allInterfaces();
|
||||||
|
for (const QNetworkInterface& nic : allInterfaces) {
|
||||||
// Ensure the interface is up and skip the loopback adapter
|
// Ensure the interface is up and skip the loopback adapter
|
||||||
if ((nic.flags() & QNetworkInterface::IsUp) == 0 ||
|
if ((nic.flags() & QNetworkInterface::IsUp) == 0 ||
|
||||||
(nic.flags() & QNetworkInterface::IsLoopBack) != 0) {
|
(nic.flags() & QNetworkInterface::IsLoopBack) != 0) {
|
||||||
@@ -269,7 +271,8 @@ bool NvComputer::wake() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
QHostAddress allNodesMulticast("FF02::1");
|
QHostAddress allNodesMulticast("FF02::1");
|
||||||
for (const QNetworkAddressEntry& addr : nic.addressEntries()) {
|
const auto allInterfaceAddresses = nic.addressEntries();
|
||||||
|
for (const QNetworkAddressEntry& addr : allInterfaceAddresses) {
|
||||||
// Store the scope ID for this NIC if IPv6 is enabled
|
// Store the scope ID for this NIC if IPv6 is enabled
|
||||||
if (!addr.ip().scopeId().isEmpty()) {
|
if (!addr.ip().scopeId().isEmpty()) {
|
||||||
allNodesMulticast.setScopeId(addr.ip().scopeId());
|
allNodesMulticast.setScopeId(addr.ip().scopeId());
|
||||||
@@ -375,13 +378,15 @@ NvComputer::ReachabilityType NvComputer::getActiveAddressReachability() const
|
|||||||
Q_ASSERT(!s.localAddress().isNull());
|
Q_ASSERT(!s.localAddress().isNull());
|
||||||
Q_ASSERT(!s.peerAddress().isNull());
|
Q_ASSERT(!s.peerAddress().isNull());
|
||||||
|
|
||||||
for (const QNetworkInterface& nic : QNetworkInterface::allInterfaces()) {
|
const auto allInterfaces = QNetworkInterface::allInterfaces();
|
||||||
|
for (const QNetworkInterface& nic : allInterfaces) {
|
||||||
// Ensure the interface is up
|
// Ensure the interface is up
|
||||||
if ((nic.flags() & QNetworkInterface::IsUp) == 0) {
|
if ((nic.flags() & QNetworkInterface::IsUp) == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const QNetworkAddressEntry& addr : nic.addressEntries()) {
|
const auto allInterfaceAddresses = nic.addressEntries();
|
||||||
|
for (const QNetworkAddressEntry& addr : allInterfaceAddresses) {
|
||||||
if (addr.ip() == s.localAddress()) {
|
if (addr.ip() == s.localAddress()) {
|
||||||
qInfo() << "Found matching interface:" << nic.humanReadableName() << nic.hardwareAddress() << nic.flags();
|
qInfo() << "Found matching interface:" << nic.humanReadableName() << nic.hardwareAddress() << nic.flags();
|
||||||
|
|
||||||
@@ -463,7 +468,7 @@ bool NvComputer::updateAppList(QVector<NvApp> newAppList) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Propagate client-side attributes to the new app list
|
// Propagate client-side attributes to the new app list
|
||||||
for (const NvApp& existingApp : appList) {
|
for (const NvApp& existingApp : std::as_const(appList)) {
|
||||||
for (NvApp& newApp : newAppList) {
|
for (NvApp& newApp : newAppList) {
|
||||||
if (existingApp.id == newApp.id) {
|
if (existingApp.id == newApp.id) {
|
||||||
newApp.hidden = existingApp.hidden;
|
newApp.hidden = existingApp.hidden;
|
||||||
|
|||||||
+72
-56
@@ -18,8 +18,16 @@
|
|||||||
#define RESUME_TIMEOUT_MS 30000
|
#define RESUME_TIMEOUT_MS 30000
|
||||||
#define QUIT_TIMEOUT_MS 30000
|
#define QUIT_TIMEOUT_MS 30000
|
||||||
|
|
||||||
NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert) :
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||||
m_ServerCert(serverCert)
|
#define XML_NAME_EQUALS(x, y) ((x) == (y))
|
||||||
|
#else
|
||||||
|
#define XML_NAME_EQUALS(x, y) ((x) == (u##y))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert, bool useTrueUid, QNetworkAccessManager* nam) :
|
||||||
|
m_Nam(nam ? nam : new QNetworkAccessManager(this)),
|
||||||
|
m_ServerCert(serverCert),
|
||||||
|
m_UseTrueUid(useTrueUid)
|
||||||
{
|
{
|
||||||
m_BaseUrlHttp.setScheme("http");
|
m_BaseUrlHttp.setScheme("http");
|
||||||
m_BaseUrlHttps.setScheme("https");
|
m_BaseUrlHttps.setScheme("https");
|
||||||
@@ -29,15 +37,12 @@ NvHTTP::NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert
|
|||||||
|
|
||||||
// Never use a proxy server
|
// Never use a proxy server
|
||||||
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
QNetworkProxy noProxy(QNetworkProxy::NoProxy);
|
||||||
m_Nam.setProxy(noProxy);
|
m_Nam->setProxy(noProxy);
|
||||||
|
|
||||||
connect(&m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NvHTTP::NvHTTP(NvComputer* computer) :
|
NvHTTP::NvHTTP(NvComputer* computer, QNetworkAccessManager* nam) :
|
||||||
NvHTTP(computer->activeAddress, computer->activeHttpsPort, computer->serverCert)
|
NvHTTP(computer->activeAddress, computer->activeHttpsPort, computer->serverCert, !computer->isNvidiaServerSoftware, nam)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvHTTP::setServerCert(QSslCertificate serverCert)
|
void NvHTTP::setServerCert(QSslCertificate serverCert)
|
||||||
@@ -62,6 +67,11 @@ void NvHTTP::setHttpsPort(uint16_t port)
|
|||||||
m_BaseUrlHttps.setPort(port);
|
m_BaseUrlHttps.setPort(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NvHTTP::setTrueUid(bool useTrueUid)
|
||||||
|
{
|
||||||
|
m_UseTrueUid = useTrueUid;
|
||||||
|
}
|
||||||
|
|
||||||
NvAddress NvHTTP::address()
|
NvAddress NvHTTP::address()
|
||||||
{
|
{
|
||||||
return m_Address;
|
return m_Address;
|
||||||
@@ -110,7 +120,7 @@ NvHTTP::getCurrentGame(QString serverInfo)
|
|||||||
// has the semantics that its name would indicate. To contain the effects of this change as much
|
// has the semantics that its name would indicate. To contain the effects of this change as much
|
||||||
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
|
// as possible, we'll force the current game to zero if the server isn't in a streaming session.
|
||||||
QString serverState = getXmlString(serverInfo, "state");
|
QString serverState = getXmlString(serverInfo, "state");
|
||||||
if (serverState != nullptr && serverState.endsWith("_SERVER_BUSY"))
|
if (serverState.endsWith("_SERVER_BUSY"))
|
||||||
{
|
{
|
||||||
return getXmlString(serverInfo, "currentgame").toInt();
|
return getXmlString(serverInfo, "currentgame").toInt();
|
||||||
}
|
}
|
||||||
@@ -267,17 +277,19 @@ NvHTTP::getDisplayModeList(QString serverInfo)
|
|||||||
while (!xmlReader.atEnd()) {
|
while (!xmlReader.atEnd()) {
|
||||||
while (xmlReader.readNextStartElement()) {
|
while (xmlReader.readNextStartElement()) {
|
||||||
auto name = xmlReader.name();
|
auto name = xmlReader.name();
|
||||||
if (name == QString("DisplayMode")) {
|
if (XML_NAME_EQUALS(name, "DisplayMode")) {
|
||||||
modes.append(NvDisplayMode());
|
modes.append(NvDisplayMode());
|
||||||
}
|
}
|
||||||
else if (name == QString("Width")) {
|
else if (!modes.isEmpty()) {
|
||||||
modes.last().width = xmlReader.readElementText().toInt();
|
if (XML_NAME_EQUALS(name, "Width")) {
|
||||||
}
|
modes.last().width = xmlReader.readElementText().toInt();
|
||||||
else if (name == QString("Height")) {
|
}
|
||||||
modes.last().height = xmlReader.readElementText().toInt();
|
else if (XML_NAME_EQUALS(name, "Height")) {
|
||||||
}
|
modes.last().height = xmlReader.readElementText().toInt();
|
||||||
else if (name == QString("RefreshRate")) {
|
}
|
||||||
modes.last().refreshRate = xmlReader.readElementText().toInt();
|
else if (XML_NAME_EQUALS(name, "RefreshRate")) {
|
||||||
|
modes.last().refreshRate = xmlReader.readElementText().toInt();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,26 +312,36 @@ NvHTTP::getAppList()
|
|||||||
while (!xmlReader.atEnd()) {
|
while (!xmlReader.atEnd()) {
|
||||||
while (xmlReader.readNextStartElement()) {
|
while (xmlReader.readNextStartElement()) {
|
||||||
auto name = xmlReader.name();
|
auto name = xmlReader.name();
|
||||||
if (name == QString("App")) {
|
if (XML_NAME_EQUALS(name, "App")) {
|
||||||
// We must have a valid app before advancing to the next one
|
// We must have a valid app before advancing to the next one
|
||||||
if (!apps.isEmpty() && !apps.last().isInitialized()) {
|
if (!apps.isEmpty() && !apps.last().isInitialized()) {
|
||||||
qWarning() << "Invalid applist XML";
|
qWarning() << "Invalid applist XML";
|
||||||
Q_ASSERT(false);
|
throw std::runtime_error("Invalid applist XML");
|
||||||
return QVector<NvApp>();
|
|
||||||
}
|
}
|
||||||
apps.append(NvApp());
|
apps.append(NvApp());
|
||||||
}
|
}
|
||||||
else if (name == QString("AppTitle")) {
|
else if (!apps.isEmpty()) {
|
||||||
apps.last().name = xmlReader.readElementText();
|
if (XML_NAME_EQUALS(name, "AppTitle")) {
|
||||||
}
|
// If an app has no name, Sunshine may send us <AppTitle/>,
|
||||||
else if (name == QString("ID")) {
|
// which readElementText() returns as a null QString.
|
||||||
apps.last().id = xmlReader.readElementText().toInt();
|
// We want to treat this as an empty QString instead, so we
|
||||||
}
|
// will explicitly convert it. An empty string will satisfy
|
||||||
else if (name == QString("IsHdrSupported")) {
|
// NvApp's isInitialized() check.
|
||||||
apps.last().hdrSupported = xmlReader.readElementText() == "1";
|
QString name = xmlReader.readElementText();
|
||||||
}
|
if (name.isNull()) {
|
||||||
else if (name == QString("IsAppCollectorGame")) {
|
name = "";
|
||||||
apps.last().isAppCollectorGame = xmlReader.readElementText() == "1";
|
}
|
||||||
|
apps.last().name = name;
|
||||||
|
}
|
||||||
|
else if (XML_NAME_EQUALS(name, "ID")) {
|
||||||
|
apps.last().id = xmlReader.readElementText().toInt();
|
||||||
|
}
|
||||||
|
else if (XML_NAME_EQUALS(name, "IsHdrSupported")) {
|
||||||
|
apps.last().hdrSupported = xmlReader.readElementText() == "1";
|
||||||
|
}
|
||||||
|
else if (XML_NAME_EQUALS(name, "IsAppCollectorGame")) {
|
||||||
|
apps.last().isAppCollectorGame = xmlReader.readElementText() == "1";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,7 +356,7 @@ NvHTTP::verifyResponseStatus(QString xml)
|
|||||||
|
|
||||||
while (xmlReader.readNextStartElement())
|
while (xmlReader.readNextStartElement())
|
||||||
{
|
{
|
||||||
if (xmlReader.name() == QString("root"))
|
if (XML_NAME_EQUALS(xmlReader.name(), "root"))
|
||||||
{
|
{
|
||||||
// Status code can be 0xFFFFFFFF in some rare cases on GFE 3.20.3, and
|
// Status code can be 0xFFFFFFFF in some rare cases on GFE 3.20.3, and
|
||||||
// QString::toInt() will fail in that case, so use QString::toUInt()
|
// QString::toInt() will fail in that case, so use QString::toUInt()
|
||||||
@@ -385,13 +407,7 @@ QByteArray
|
|||||||
NvHTTP::getXmlStringFromHex(QString xml,
|
NvHTTP::getXmlStringFromHex(QString xml,
|
||||||
QString tagName)
|
QString tagName)
|
||||||
{
|
{
|
||||||
QString str = getXmlString(xml, tagName);
|
return QByteArray::fromHex(getXmlString(xml, tagName).toUtf8());
|
||||||
if (str == nullptr)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return QByteArray::fromHex(str.toLatin1());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString
|
QString
|
||||||
@@ -413,7 +429,7 @@ NvHTTP::getXmlString(QString xml,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NvHTTP::handleSslErrors(QNetworkReply* reply, const QList<QSslError>& errors)
|
void NvHTTP::handleSslErrors(QNetworkReply* reply, const QList<QSslError>& errors)
|
||||||
@@ -476,11 +492,9 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
QUrl url(baseUrl);
|
QUrl url(baseUrl);
|
||||||
url.setPath("/" + command);
|
url.setPath("/" + command);
|
||||||
|
|
||||||
// Use a common UID for Moonlight clients to allow them to quit
|
// Use a placeholder UID for GFE allow them to quit games for each other.
|
||||||
// games for each other (otherwise GFE gets screwed up and it requires
|
url.setQuery("uniqueid=" + (m_UseTrueUid ? IdentityManager::get()->getUniqueId() : "0123456789ABCDEF") +
|
||||||
// manual intervention to solve).
|
"&uuid=" + QUuid::createUuid().toRfc4122().toHex() +
|
||||||
url.setQuery("uniqueid=0123456789ABCDEF&uuid=" +
|
|
||||||
QUuid::createUuid().toRfc4122().toHex() +
|
|
||||||
((arguments != nullptr) ? ("&" + arguments) : ""));
|
((arguments != nullptr) ? ("&" + arguments) : ""));
|
||||||
|
|
||||||
QNetworkRequest request(url);
|
QNetworkRequest request(url);
|
||||||
@@ -493,15 +507,15 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
|
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && QT_VERSION < QT_VERSION_CHECK(5, 15, 1) && !defined(QT_NO_BEARERMANAGEMENT)
|
#if QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)
|
||||||
// HACK: Set network accessibility to work around QTBUG-80947 (introduced in Qt 5.14.0 and fixed in Qt 5.15.1)
|
// Use fine-grained idle timeouts to avoid calling QNetworkAccessManager::clearAccessCache(),
|
||||||
QT_WARNING_PUSH
|
// which tears down the NAM's global thread each time. We must not keep persistent connections
|
||||||
QT_WARNING_DISABLE_DEPRECATED
|
// or GFE will puke.
|
||||||
m_Nam.setNetworkAccessible(QNetworkAccessManager::Accessible);
|
request.setAttribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute, 0);
|
||||||
QT_WARNING_POP
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QNetworkReply* reply = m_Nam.get(request);
|
auto sslErrorsConnection = connect(m_Nam, &QNetworkAccessManager::sslErrors, this, &NvHTTP::handleSslErrors);
|
||||||
|
QNetworkReply* reply = m_Nam->get(request);
|
||||||
|
|
||||||
// Run the request with a timeout if requested
|
// Run the request with a timeout if requested
|
||||||
QEventLoop loop;
|
QEventLoop loop;
|
||||||
@@ -524,9 +538,11 @@ NvHTTP::openConnection(QUrl baseUrl,
|
|||||||
reply->abort();
|
reply->abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We must clear out cached authentication and connections or
|
#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
|
||||||
// GFE will puke next time
|
// If we couldn't use fine-grained connection idle timeouts, kill them all now
|
||||||
m_Nam.clearAccessCache();
|
m_Nam->clearAccessCache();
|
||||||
|
#endif
|
||||||
|
disconnect(sslErrorsConnection);
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
if (reply->error() != QNetworkReply::NoError)
|
if (reply->error() != QNetworkReply::NoError)
|
||||||
|
|||||||
+15
-14
@@ -33,19 +33,19 @@ class GfeHttpResponseException : public std::exception
|
|||||||
public:
|
public:
|
||||||
GfeHttpResponseException(int statusCode, QString message) :
|
GfeHttpResponseException(int statusCode, QString message) :
|
||||||
m_StatusCode(statusCode),
|
m_StatusCode(statusCode),
|
||||||
m_StatusMessage(message)
|
m_StatusMessage(message.toUtf8())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* what() const throw()
|
const char* what() const throw()
|
||||||
{
|
{
|
||||||
return m_StatusMessage.toLatin1();
|
return m_StatusMessage.constData();
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* getStatusMessage() const
|
const char* getStatusMessage() const
|
||||||
{
|
{
|
||||||
return m_StatusMessage.toLatin1();
|
return m_StatusMessage.constData();
|
||||||
}
|
}
|
||||||
|
|
||||||
int getStatusCode() const
|
int getStatusCode() const
|
||||||
@@ -55,12 +55,12 @@ public:
|
|||||||
|
|
||||||
QString toQString() const
|
QString toQString() const
|
||||||
{
|
{
|
||||||
return m_StatusMessage + " (Error " + QString::number(m_StatusCode) + ")";
|
return QString::fromUtf8(m_StatusMessage) + " (Error " + QString::number(m_StatusCode) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_StatusCode;
|
int m_StatusCode;
|
||||||
QString m_StatusMessage;
|
QByteArray m_StatusMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
class QtNetworkReplyException : public std::exception
|
class QtNetworkReplyException : public std::exception
|
||||||
@@ -68,19 +68,19 @@ class QtNetworkReplyException : public std::exception
|
|||||||
public:
|
public:
|
||||||
QtNetworkReplyException(QNetworkReply::NetworkError error, QString errorText) :
|
QtNetworkReplyException(QNetworkReply::NetworkError error, QString errorText) :
|
||||||
m_Error(error),
|
m_Error(error),
|
||||||
m_ErrorText(errorText)
|
m_ErrorText(errorText.toUtf8())
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* what() const throw()
|
const char* what() const throw()
|
||||||
{
|
{
|
||||||
return m_ErrorText.toLatin1();
|
return m_ErrorText.constData();
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* getErrorText() const
|
const char* getErrorText() const
|
||||||
{
|
{
|
||||||
return m_ErrorText.toLatin1();
|
return m_ErrorText.constData();
|
||||||
}
|
}
|
||||||
|
|
||||||
QNetworkReply::NetworkError getError() const
|
QNetworkReply::NetworkError getError() const
|
||||||
@@ -90,12 +90,12 @@ public:
|
|||||||
|
|
||||||
QString toQString() const
|
QString toQString() const
|
||||||
{
|
{
|
||||||
return m_ErrorText + " (Error " + QString::number(m_Error) + ")";
|
return QString::fromUtf8(m_ErrorText) + " (Error " + QString::number(m_Error) + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkReply::NetworkError m_Error;
|
QNetworkReply::NetworkError m_Error;
|
||||||
QString m_ErrorText;
|
QByteArray m_ErrorText;
|
||||||
};
|
};
|
||||||
|
|
||||||
class NvHTTP : public QObject
|
class NvHTTP : public QObject
|
||||||
@@ -109,9 +109,9 @@ public:
|
|||||||
NVLL_VERBOSE
|
NVLL_VERBOSE
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert);
|
explicit NvHTTP(NvAddress address, uint16_t httpsPort, QSslCertificate serverCert, bool useTrueUid, QNetworkAccessManager* nam = nullptr);
|
||||||
|
|
||||||
explicit NvHTTP(NvComputer* computer);
|
explicit NvHTTP(NvComputer* computer, QNetworkAccessManager* nam = nullptr);
|
||||||
|
|
||||||
static
|
static
|
||||||
int
|
int
|
||||||
@@ -142,9 +142,9 @@ public:
|
|||||||
NvLogLevel logLevel = NvLogLevel::NVLL_VERBOSE);
|
NvLogLevel logLevel = NvLogLevel::NVLL_VERBOSE);
|
||||||
|
|
||||||
void setServerCert(QSslCertificate serverCert);
|
void setServerCert(QSslCertificate serverCert);
|
||||||
|
|
||||||
void setAddress(NvAddress address);
|
void setAddress(NvAddress address);
|
||||||
void setHttpsPort(uint16_t port);
|
void setHttpsPort(uint16_t port);
|
||||||
|
void setTrueUid(bool useTrueUid);
|
||||||
|
|
||||||
NvAddress address();
|
NvAddress address();
|
||||||
|
|
||||||
@@ -196,6 +196,7 @@ private:
|
|||||||
NvLogLevel logLevel);
|
NvLogLevel logLevel);
|
||||||
|
|
||||||
NvAddress m_Address;
|
NvAddress m_Address;
|
||||||
QNetworkAccessManager m_Nam;
|
QNetworkAccessManager* m_Nam;
|
||||||
QSslCertificate m_ServerCert;
|
QSslCertificate m_ServerCert;
|
||||||
|
bool m_UseTrueUid;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,16 +54,18 @@ NvPairingManager::generateRandomBytes(int length)
|
|||||||
QByteArray
|
QByteArray
|
||||||
NvPairingManager::encrypt(const QByteArray& plaintext, const QByteArray& key)
|
NvPairingManager::encrypt(const QByteArray& plaintext, const QByteArray& key)
|
||||||
{
|
{
|
||||||
QByteArray ciphertext(plaintext.size(), 0);
|
if (plaintext.isEmpty()) {
|
||||||
EVP_CIPHER_CTX* cipher;
|
return QByteArray();
|
||||||
int ciphertextLen;
|
}
|
||||||
|
|
||||||
cipher = EVP_CIPHER_CTX_new();
|
EVP_CIPHER_CTX* cipher = EVP_CIPHER_CTX_new();
|
||||||
THROW_BAD_ALLOC_IF_NULL(cipher);
|
THROW_BAD_ALLOC_IF_NULL(cipher);
|
||||||
|
|
||||||
EVP_EncryptInit(cipher, EVP_aes_128_ecb(), reinterpret_cast<const unsigned char*>(key.data()), NULL);
|
EVP_EncryptInit(cipher, EVP_aes_128_ecb(), reinterpret_cast<const unsigned char*>(key.data()), NULL);
|
||||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||||
|
|
||||||
|
QByteArray ciphertext(plaintext.size(), 0);
|
||||||
|
int ciphertextLen;
|
||||||
EVP_EncryptUpdate(cipher,
|
EVP_EncryptUpdate(cipher,
|
||||||
reinterpret_cast<unsigned char*>(ciphertext.data()),
|
reinterpret_cast<unsigned char*>(ciphertext.data()),
|
||||||
&ciphertextLen,
|
&ciphertextLen,
|
||||||
@@ -79,16 +81,18 @@ NvPairingManager::encrypt(const QByteArray& plaintext, const QByteArray& key)
|
|||||||
QByteArray
|
QByteArray
|
||||||
NvPairingManager::decrypt(const QByteArray& ciphertext, const QByteArray& key)
|
NvPairingManager::decrypt(const QByteArray& ciphertext, const QByteArray& key)
|
||||||
{
|
{
|
||||||
QByteArray plaintext(ciphertext.size(), 0);
|
if (ciphertext.isEmpty()) {
|
||||||
EVP_CIPHER_CTX* cipher;
|
return QByteArray();
|
||||||
int plaintextLen;
|
}
|
||||||
|
|
||||||
cipher = EVP_CIPHER_CTX_new();
|
EVP_CIPHER_CTX* cipher = EVP_CIPHER_CTX_new();
|
||||||
THROW_BAD_ALLOC_IF_NULL(cipher);
|
THROW_BAD_ALLOC_IF_NULL(cipher);
|
||||||
|
|
||||||
EVP_DecryptInit(cipher, EVP_aes_128_ecb(), reinterpret_cast<const unsigned char*>(key.data()), NULL);
|
EVP_DecryptInit(cipher, EVP_aes_128_ecb(), reinterpret_cast<const unsigned char*>(key.data()), NULL);
|
||||||
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
EVP_CIPHER_CTX_set_padding(cipher, 0);
|
||||||
|
|
||||||
|
QByteArray plaintext(ciphertext.size(), 0);
|
||||||
|
int plaintextLen;
|
||||||
EVP_DecryptUpdate(cipher,
|
EVP_DecryptUpdate(cipher,
|
||||||
reinterpret_cast<unsigned char*>(plaintext.data()),
|
reinterpret_cast<unsigned char*>(plaintext.data()),
|
||||||
&plaintextLen,
|
&plaintextLen,
|
||||||
@@ -101,6 +105,29 @@ NvPairingManager::decrypt(const QByteArray& ciphertext, const QByteArray& key)
|
|||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray
|
||||||
|
NvPairingManager::getSignatureFromCert(X509* cert)
|
||||||
|
{
|
||||||
|
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
||||||
|
ASN1_BIT_STRING *asnSignature = cert->signature;
|
||||||
|
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||||
|
ASN1_BIT_STRING *asnSignature;
|
||||||
|
X509_get0_signature(&asnSignature, NULL, cert);
|
||||||
|
#else
|
||||||
|
const ASN1_BIT_STRING *asnSignature;
|
||||||
|
X509_get0_signature(&asnSignature, NULL, cert);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return QByteArray(
|
||||||
|
#if (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
||||||
|
reinterpret_cast<const char*>(ASN1_STRING_data(asnSignature)),
|
||||||
|
#else
|
||||||
|
reinterpret_cast<const char*>(ASN1_STRING_get0_data(asnSignature)),
|
||||||
|
#endif
|
||||||
|
ASN1_STRING_length(asnSignature)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray
|
QByteArray
|
||||||
NvPairingManager::getSignatureFromPemCert(const QByteArray& certificate)
|
NvPairingManager::getSignatureFromPemCert(const QByteArray& certificate)
|
||||||
{
|
{
|
||||||
@@ -114,18 +141,7 @@ NvPairingManager::getSignatureFromPemCert(const QByteArray& certificate)
|
|||||||
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
X509* cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
|
||||||
BIO_free_all(bio);
|
BIO_free_all(bio);
|
||||||
|
|
||||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
QByteArray signature = getSignatureFromCert(cert);
|
||||||
ASN1_BIT_STRING *asnSignature = cert->signature;
|
|
||||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
|
||||||
ASN1_BIT_STRING *asnSignature;
|
|
||||||
X509_get0_signature(&asnSignature, NULL, cert);
|
|
||||||
#else
|
|
||||||
const ASN1_BIT_STRING *asnSignature;
|
|
||||||
X509_get0_signature(&asnSignature, NULL, cert);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QByteArray signature(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
|
||||||
|
|
||||||
X509_free(cert);
|
X509_free(cert);
|
||||||
|
|
||||||
return signature;
|
return signature;
|
||||||
@@ -184,7 +200,7 @@ NvPairingManager::signMessage(const QByteArray& message)
|
|||||||
QByteArray
|
QByteArray
|
||||||
NvPairingManager::saltPin(const QByteArray& salt, QString pin)
|
NvPairingManager::saltPin(const QByteArray& salt, QString pin)
|
||||||
{
|
{
|
||||||
return QByteArray().append(salt).append(pin.toLatin1());
|
return QByteArray().append(salt).append(pin.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
NvPairingManager::PairState
|
NvPairingManager::PairState
|
||||||
@@ -227,8 +243,7 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
|||||||
}
|
}
|
||||||
|
|
||||||
QByteArray serverCertStr = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
QByteArray serverCertStr = NvHTTP::getXmlStringFromHex(getCert, "plaincert");
|
||||||
if (serverCertStr == nullptr)
|
if (serverCertStr.isEmpty()) {
|
||||||
{
|
|
||||||
qCritical() << "Server likely already pairing";
|
qCritical() << "Server likely already pairing";
|
||||||
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||||
return PairState::ALREADY_IN_PROGRESS;
|
return PairState::ALREADY_IN_PROGRESS;
|
||||||
@@ -263,22 +278,18 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
|||||||
}
|
}
|
||||||
|
|
||||||
QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), aesKey);
|
QByteArray challengeResponseData = decrypt(m_Http.getXmlStringFromHex(challengeXml, "challengeresponse"), aesKey);
|
||||||
|
if (challengeResponseData.size() < hashLength) {
|
||||||
|
qCritical() << "Invalid challengeresponse at stage #2";
|
||||||
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||||
|
return PairState::FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray clientSecretData = generateRandomBytes(16);
|
QByteArray clientSecretData = generateRandomBytes(16);
|
||||||
QByteArray challengeResponse;
|
QByteArray challengeResponse;
|
||||||
QByteArray serverResponse(challengeResponseData.data(), hashLength);
|
QByteArray serverResponse(challengeResponseData.data(), hashLength);
|
||||||
|
|
||||||
#if (OPENSSL_VERSION_NUMBER < 0x10002000L)
|
|
||||||
ASN1_BIT_STRING *asnSignature = m_Cert->signature;
|
|
||||||
#elif (OPENSSL_VERSION_NUMBER < 0x10100000L)
|
|
||||||
ASN1_BIT_STRING *asnSignature;
|
|
||||||
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
|
||||||
#else
|
|
||||||
const ASN1_BIT_STRING *asnSignature;
|
|
||||||
X509_get0_signature(&asnSignature, NULL, m_Cert);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
challengeResponse.append(challengeResponseData.data() + hashLength, 16);
|
challengeResponse.append(challengeResponseData.data() + hashLength, 16);
|
||||||
challengeResponse.append(reinterpret_cast<char*>(asnSignature->data), asnSignature->length);
|
challengeResponse.append(getSignatureFromCert(m_Cert));
|
||||||
challengeResponse.append(clientSecretData);
|
challengeResponse.append(clientSecretData);
|
||||||
|
|
||||||
QByteArray paddedHash = QCryptographicHash::hash(challengeResponse, hashAlgo);
|
QByteArray paddedHash = QCryptographicHash::hash(challengeResponse, hashAlgo);
|
||||||
@@ -298,6 +309,12 @@ NvPairingManager::pair(QString appVersion, QString pin, QSslCertificate& serverC
|
|||||||
}
|
}
|
||||||
|
|
||||||
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
|
QByteArray pairingSecret = NvHTTP::getXmlStringFromHex(respXml, "pairingsecret");
|
||||||
|
if (pairingSecret.size() <= 16) {
|
||||||
|
qCritical() << "Invalid pairingsecret at stage #3";
|
||||||
|
m_Http.openConnectionToString(m_Http.m_BaseUrlHttp, "unpair", nullptr, REQUEST_TIMEOUT_MS);
|
||||||
|
return PairState::FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray serverSecret = pairingSecret.left(16);
|
QByteArray serverSecret = pairingSecret.left(16);
|
||||||
QByteArray serverSignature = pairingSecret.mid(16);
|
QByteArray serverSignature = pairingSecret.mid(16);
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ private:
|
|||||||
QByteArray
|
QByteArray
|
||||||
decrypt(const QByteArray& ciphertext, const QByteArray& key);
|
decrypt(const QByteArray& ciphertext, const QByteArray& key);
|
||||||
|
|
||||||
|
QByteArray
|
||||||
|
getSignatureFromCert(X509* cert);
|
||||||
|
|
||||||
QByteArray
|
QByteArray
|
||||||
getSignatureFromPemCert(const QByteArray& certificate);
|
getSignatureFromPemCert(const QByteArray& certificate);
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,38 @@
|
|||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class SystemPropertyQueryThread : public QThread
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SystemPropertyQueryThread(SystemProperties* properties)
|
||||||
|
: QThread(properties), m_Properties(properties)
|
||||||
|
{
|
||||||
|
setObjectName("System Properties Async Query Thread");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run() override
|
||||||
|
{
|
||||||
|
bool hasHardwareAcceleration;
|
||||||
|
bool rendererAlwaysFullScreen;
|
||||||
|
bool supportsHdr;
|
||||||
|
QSize maximumResolution;
|
||||||
|
|
||||||
|
Session::getDecoderInfo(m_Properties->testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, supportsHdr, maximumResolution);
|
||||||
|
|
||||||
|
// Propagate the decoder properties to the SystemProperties singleton and emit any change signals on the main thread
|
||||||
|
QMetaObject::invokeMethod(m_Properties, "updateDecoderProperties",
|
||||||
|
Qt::QueuedConnection,
|
||||||
|
Q_ARG(bool, hasHardwareAcceleration),
|
||||||
|
Q_ARG(bool, rendererAlwaysFullScreen),
|
||||||
|
Q_ARG(QSize, maximumResolution),
|
||||||
|
Q_ARG(bool, supportsHdr));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SystemProperties* m_Properties;
|
||||||
|
};
|
||||||
|
|
||||||
SystemProperties::SystemProperties()
|
SystemProperties::SystemProperties()
|
||||||
{
|
{
|
||||||
versionString = QString(VERSION_STR);
|
versionString = QString(VERSION_STR);
|
||||||
@@ -25,9 +57,8 @@ SystemProperties::SystemProperties()
|
|||||||
{
|
{
|
||||||
USHORT processArch, machineArch;
|
USHORT processArch, machineArch;
|
||||||
|
|
||||||
// Use IsWow64Process2 on TH2 and later, because it supports ARM64
|
// Use IsWow64Process2() because it doesn't lie on ARM64
|
||||||
auto fnIsWow64Process2 = (decltype(IsWow64Process2)*)GetProcAddress(GetModuleHandleA("kernel32.dll"), "IsWow64Process2");
|
if (IsWow64Process2(GetCurrentProcess(), &processArch, &machineArch)) {
|
||||||
if (fnIsWow64Process2 != nullptr && fnIsWow64Process2(GetCurrentProcess(), &processArch, &machineArch)) {
|
|
||||||
switch (machineArch) {
|
switch (machineArch) {
|
||||||
case IMAGE_FILE_MACHINE_I386:
|
case IMAGE_FILE_MACHINE_I386:
|
||||||
nativeArch = "i386";
|
nativeArch = "i386";
|
||||||
@@ -66,82 +97,94 @@ SystemProperties::SystemProperties()
|
|||||||
hasDiscordIntegration = false;
|
hasDiscordIntegration = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
|
// These will be queried asynchronously to avoid blocking the UI
|
||||||
|
hasHardwareAcceleration = true;
|
||||||
|
rendererAlwaysFullScreen = false;
|
||||||
|
supportsHdr = true;
|
||||||
|
maximumResolution = QSize(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// Populate data that requires talking to SDL. We do it all in one shot
|
SystemProperties::~SystemProperties()
|
||||||
// and cache the results to speed up future queries on this data.
|
{
|
||||||
querySdlVideoInfo();
|
waitForAsyncLoad();
|
||||||
|
}
|
||||||
|
|
||||||
Q_ASSERT(!monitorRefreshRates.isEmpty());
|
void SystemProperties::updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr)
|
||||||
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
{
|
||||||
Q_ASSERT(!monitorSafeAreaResolutions.isEmpty());
|
SDL_assert(testWindow);
|
||||||
|
|
||||||
|
if (hasHardwareAcceleration != this->hasHardwareAcceleration) {
|
||||||
|
this->hasHardwareAcceleration = hasHardwareAcceleration;
|
||||||
|
emit hasHardwareAccelerationChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rendererAlwaysFullScreen != this->rendererAlwaysFullScreen) {
|
||||||
|
this->rendererAlwaysFullScreen = rendererAlwaysFullScreen;
|
||||||
|
emit rendererAlwaysFullScreenChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maximumResolution != this->maximumResolution) {
|
||||||
|
this->maximumResolution = maximumResolution;
|
||||||
|
emit maximumResolutionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsHdr != this->supportsHdr) {
|
||||||
|
this->supportsHdr = supportsHdr;
|
||||||
|
emit supportsHdrChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_DestroyWindow(testWindow);
|
||||||
|
testWindow = nullptr;
|
||||||
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect SystemProperties::getNativeResolution(int displayIndex)
|
QRect SystemProperties::getNativeResolution(int displayIndex)
|
||||||
{
|
{
|
||||||
// Returns default constructed QRect if out of bounds
|
// Returns default constructed QRect if out of bounds
|
||||||
|
Q_ASSERT(!monitorNativeResolutions.isEmpty());
|
||||||
return monitorNativeResolutions.value(displayIndex);
|
return monitorNativeResolutions.value(displayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
QRect SystemProperties::getSafeAreaResolution(int displayIndex)
|
QRect SystemProperties::getSafeAreaResolution(int displayIndex)
|
||||||
{
|
{
|
||||||
// Returns default constructed QRect if out of bounds
|
// Returns default constructed QRect if out of bounds
|
||||||
|
Q_ASSERT(!monitorSafeAreaResolutions.isEmpty());
|
||||||
return monitorSafeAreaResolutions.value(displayIndex);
|
return monitorSafeAreaResolutions.value(displayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
int SystemProperties::getRefreshRate(int displayIndex)
|
int SystemProperties::getRefreshRate(int displayIndex)
|
||||||
{
|
{
|
||||||
// Returns 0 if out of bounds
|
// Returns 0 if out of bounds
|
||||||
|
Q_ASSERT(!monitorRefreshRates.isEmpty());
|
||||||
return monitorRefreshRates.value(displayIndex);
|
return monitorRefreshRates.value(displayIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuerySdlVideoThread : public QThread
|
void SystemProperties::startAsyncLoad()
|
||||||
{
|
{
|
||||||
public:
|
if (systemPropertyQueryThread) {
|
||||||
QuerySdlVideoThread(SystemProperties* me) :
|
// Already started/completed
|
||||||
QThread(nullptr),
|
return;
|
||||||
m_Me(me) {}
|
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
m_Me->querySdlVideoInfoInternal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemProperties* m_Me;
|
// This isn't actually asynchronous (due to the need to synchronize with
|
||||||
};
|
// SdlGamepadKeyNavigation), but we don't query it in the constructor
|
||||||
|
// because it's expensive.
|
||||||
void SystemProperties::querySdlVideoInfo()
|
unmappedGamepads = SdlInputHandler::getUnmappedGamepads();
|
||||||
{
|
if (!unmappedGamepads.isEmpty()) {
|
||||||
if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
|
emit unmappedGamepadsChanged();
|
||||||
// Use a separate thread to temporarily initialize SDL
|
|
||||||
// video to avoid stomping on Qt's X11 and OGL state.
|
|
||||||
QuerySdlVideoThread thread(this);
|
|
||||||
thread.start();
|
|
||||||
thread.wait();
|
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
querySdlVideoInfoInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemProperties::querySdlVideoInfoInternal()
|
// We initialize the video subsystem and test window on the main thread
|
||||||
{
|
// because some platforms (macOS) do not support window creation on
|
||||||
hasHardwareAcceleration = false;
|
// non-main threads.
|
||||||
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
if (SDLC_FAILURE(SDL_InitSubSystem(SDL_INIT_VIDEO))) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display related attributes (max FPS, native resolution, etc).
|
testWindow = StreamUtils::createTestWindow();
|
||||||
// We call the internal variant because we're already in a safe thread context.
|
|
||||||
refreshDisplaysInternal();
|
|
||||||
|
|
||||||
SDL_Window* testWindow = SDLC_CreateWindowWithFallback("", 0, 0, 1280, 720,
|
|
||||||
SDL_WINDOW_HIDDEN,
|
|
||||||
StreamUtils::getPlatformWindowFlags());
|
|
||||||
if (!testWindow) {
|
if (!testWindow) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"Failed to create window for hardware decode test: %s",
|
"Failed to create window for hardware decode test: %s",
|
||||||
@@ -150,45 +193,27 @@ void SystemProperties::querySdlVideoInfoInternal()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Session::getDecoderInfo(testWindow, hasHardwareAcceleration, rendererAlwaysFullScreen, supportsHdr, maximumResolution);
|
// Update display related attributes (max FPS, native resolution, etc).
|
||||||
|
//
|
||||||
|
// NB: SDL3 will forcefully refresh displays when a window is created,
|
||||||
|
// so we place this after the window creation to ensure we don't pay
|
||||||
|
// the penalty for mode enumeration twice.
|
||||||
|
refreshDisplays();
|
||||||
|
|
||||||
SDL_DestroyWindow(testWindow);
|
systemPropertyQueryThread = new SystemPropertyQueryThread(this);
|
||||||
|
systemPropertyQueryThread->start();
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class RefreshDisplaysThread : public QThread
|
void SystemProperties::waitForAsyncLoad()
|
||||||
{
|
{
|
||||||
public:
|
if (systemPropertyQueryThread) {
|
||||||
RefreshDisplaysThread(SystemProperties* me) :
|
systemPropertyQueryThread->wait();
|
||||||
QThread(nullptr),
|
|
||||||
m_Me(me) {}
|
|
||||||
|
|
||||||
void run() override
|
|
||||||
{
|
|
||||||
m_Me->refreshDisplaysInternal();
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
SystemProperties* m_Me;
|
|
||||||
};
|
|
||||||
|
|
||||||
void SystemProperties::refreshDisplays()
|
void SystemProperties::refreshDisplays()
|
||||||
{
|
{
|
||||||
if (WMUtils::isRunningX11() || WMUtils::isRunningWayland()) {
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
// Use a separate thread to temporarily initialize SDL
|
|
||||||
// video to avoid stomping on Qt's X11 and OGL state.
|
|
||||||
RefreshDisplaysThread thread(this);
|
|
||||||
thread.start();
|
|
||||||
thread.wait();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
refreshDisplaysInternal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SystemProperties::refreshDisplaysInternal()
|
|
||||||
{
|
|
||||||
if (SDLC_FAILURE(SDL_InitSubSystem(SDL_INIT_VIDEO))) {
|
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
"SDL_InitSubSystem(SDL_INIT_VIDEO) failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
@@ -197,18 +222,15 @@ void SystemProperties::refreshDisplaysInternal()
|
|||||||
|
|
||||||
monitorNativeResolutions.clear();
|
monitorNativeResolutions.clear();
|
||||||
|
|
||||||
int numDisplays = 0;
|
|
||||||
SDL_DisplayID *displays = SDL_GetDisplays(&numDisplays);
|
|
||||||
|
|
||||||
SDL_DisplayMode bestMode;
|
SDL_DisplayMode bestMode;
|
||||||
for (int i = 0; i < numDisplays; i++) {
|
for (int displayIndex = 0; displayIndex < SDL_GetNumVideoDisplays(); displayIndex++) {
|
||||||
SDL_DisplayMode desktopMode;
|
SDL_DisplayMode desktopMode;
|
||||||
SDL_Rect safeArea;
|
SDL_Rect safeArea;
|
||||||
|
|
||||||
if (StreamUtils::getNativeDesktopMode(displays[i], &desktopMode, &safeArea)) {
|
if (StreamUtils::getNativeDesktopMode(displayIndex, &desktopMode, &safeArea)) {
|
||||||
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
if (desktopMode.w <= 8192 && desktopMode.h <= 8192) {
|
||||||
monitorNativeResolutions.insert(i, QRect(0, 0, desktopMode.w, desktopMode.h));
|
monitorNativeResolutions.insert(displayIndex, QRect(0, 0, desktopMode.w, desktopMode.h));
|
||||||
monitorSafeAreaResolutions.insert(i, QRect(0, 0, safeArea.w, safeArea.h));
|
monitorSafeAreaResolutions.insert(displayIndex, QRect(0, 0, safeArea.w, safeArea.h));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
@@ -218,10 +240,10 @@ void SystemProperties::refreshDisplaysInternal()
|
|||||||
|
|
||||||
// Start at desktop mode and work our way up
|
// Start at desktop mode and work our way up
|
||||||
bestMode = desktopMode;
|
bestMode = desktopMode;
|
||||||
int numDisplayModes = SDL_GetNumDisplayModes(displays[i]);
|
int numDisplayModes = SDL_GetNumDisplayModes(displayIndex);
|
||||||
for (int i = 0; i < numDisplayModes; i++) {
|
for (int i = 0; i < numDisplayModes; i++) {
|
||||||
SDL_DisplayMode mode;
|
SDL_DisplayMode mode;
|
||||||
if (SDL_GetDisplayMode(displays[i], i, &mode) == 0) {
|
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0) {
|
||||||
if (mode.w == desktopMode.w && mode.h == desktopMode.h) {
|
if (mode.w == desktopMode.w && mode.h == desktopMode.h) {
|
||||||
if (mode.refresh_rate > bestMode.refresh_rate) {
|
if (mode.refresh_rate > bestMode.refresh_rate) {
|
||||||
bestMode = mode;
|
bestMode = mode;
|
||||||
@@ -244,6 +266,5 @@ void SystemProperties::refreshDisplaysInternal()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_free(displays);
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,19 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QRect>
|
#include <QRect>
|
||||||
|
|
||||||
|
#include "SDL_compat.h"
|
||||||
|
|
||||||
class SystemProperties : public QObject
|
class SystemProperties : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
friend class QuerySdlVideoThread;
|
friend class SystemPropertyQueryThread;
|
||||||
friend class RefreshDisplaysThread;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SystemProperties();
|
SystemProperties();
|
||||||
|
~SystemProperties();
|
||||||
|
|
||||||
Q_PROPERTY(bool hasHardwareAcceleration MEMBER hasHardwareAcceleration CONSTANT)
|
// Static properties queried synchronously during the constructor
|
||||||
Q_PROPERTY(bool rendererAlwaysFullScreen MEMBER rendererAlwaysFullScreen CONSTANT)
|
|
||||||
Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT)
|
Q_PROPERTY(bool isRunningWayland MEMBER isRunningWayland CONSTANT)
|
||||||
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
|
Q_PROPERTY(bool isRunningXWayland MEMBER isRunningXWayland CONSTANT)
|
||||||
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
|
Q_PROPERTY(bool isWow64 MEMBER isWow64 CONSTANT)
|
||||||
@@ -22,27 +23,40 @@ public:
|
|||||||
Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT)
|
Q_PROPERTY(bool hasDesktopEnvironment MEMBER hasDesktopEnvironment CONSTANT)
|
||||||
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
|
Q_PROPERTY(bool hasBrowser MEMBER hasBrowser CONSTANT)
|
||||||
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
|
Q_PROPERTY(bool hasDiscordIntegration MEMBER hasDiscordIntegration CONSTANT)
|
||||||
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
|
|
||||||
Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution CONSTANT)
|
|
||||||
Q_PROPERTY(QString versionString MEMBER versionString CONSTANT)
|
|
||||||
Q_PROPERTY(bool supportsHdr MEMBER supportsHdr CONSTANT)
|
|
||||||
Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT)
|
Q_PROPERTY(bool usesMaterial3Theme MEMBER usesMaterial3Theme CONSTANT)
|
||||||
|
Q_PROPERTY(QString versionString MEMBER versionString CONSTANT)
|
||||||
|
|
||||||
Q_INVOKABLE void refreshDisplays();
|
// Properties queried asynchronously (startAsyncLoad() must be called!)
|
||||||
|
Q_PROPERTY(bool hasHardwareAcceleration MEMBER hasHardwareAcceleration NOTIFY hasHardwareAccelerationChanged)
|
||||||
|
Q_PROPERTY(bool rendererAlwaysFullScreen MEMBER rendererAlwaysFullScreen NOTIFY rendererAlwaysFullScreenChanged)
|
||||||
|
Q_PROPERTY(QString unmappedGamepads MEMBER unmappedGamepads NOTIFY unmappedGamepadsChanged)
|
||||||
|
Q_PROPERTY(QSize maximumResolution MEMBER maximumResolution NOTIFY maximumResolutionChanged)
|
||||||
|
Q_PROPERTY(bool supportsHdr MEMBER supportsHdr NOTIFY supportsHdrChanged)
|
||||||
|
|
||||||
|
// Either startAsyncLoad()+waitForAsyncLoad() or refreshDisplays() must be invoked first
|
||||||
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
Q_INVOKABLE QRect getNativeResolution(int displayIndex);
|
||||||
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
|
Q_INVOKABLE QRect getSafeAreaResolution(int displayIndex);
|
||||||
Q_INVOKABLE int getRefreshRate(int displayIndex);
|
Q_INVOKABLE int getRefreshRate(int displayIndex);
|
||||||
|
|
||||||
|
Q_INVOKABLE void startAsyncLoad();
|
||||||
|
Q_INVOKABLE void waitForAsyncLoad();
|
||||||
|
Q_INVOKABLE void refreshDisplays();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void unmappedGamepadsChanged();
|
void unmappedGamepadsChanged();
|
||||||
|
void hasHardwareAccelerationChanged();
|
||||||
|
void rendererAlwaysFullScreenChanged();
|
||||||
|
void maximumResolutionChanged();
|
||||||
|
void supportsHdrChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateDecoderProperties(bool hasHardwareAcceleration, bool rendererAlwaysFullScreen, QSize maximumResolution, bool supportsHdr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void querySdlVideoInfo();
|
QThread* systemPropertyQueryThread = nullptr;
|
||||||
void querySdlVideoInfoInternal();
|
SDL_Window* testWindow = nullptr;
|
||||||
void refreshDisplaysInternal();
|
|
||||||
|
|
||||||
bool hasHardwareAcceleration;
|
// Properties set by the constructor
|
||||||
bool rendererAlwaysFullScreen;
|
|
||||||
bool isRunningWayland;
|
bool isRunningWayland;
|
||||||
bool isRunningXWayland;
|
bool isRunningXWayland;
|
||||||
bool isWow64;
|
bool isWow64;
|
||||||
@@ -50,13 +64,19 @@ private:
|
|||||||
bool hasDesktopEnvironment;
|
bool hasDesktopEnvironment;
|
||||||
bool hasBrowser;
|
bool hasBrowser;
|
||||||
bool hasDiscordIntegration;
|
bool hasDiscordIntegration;
|
||||||
QString unmappedGamepads;
|
QString versionString;
|
||||||
|
bool usesMaterial3Theme;
|
||||||
|
|
||||||
|
// Properties only set if startAsyncLoad() is called
|
||||||
|
bool hasHardwareAcceleration;
|
||||||
|
bool rendererAlwaysFullScreen;
|
||||||
QSize maximumResolution;
|
QSize maximumResolution;
|
||||||
|
bool supportsHdr;
|
||||||
|
QString unmappedGamepads;
|
||||||
|
|
||||||
|
// Properties set by refreshDisplays()
|
||||||
QList<QRect> monitorNativeResolutions;
|
QList<QRect> monitorNativeResolutions;
|
||||||
QList<QRect> monitorSafeAreaResolutions;
|
QList<QRect> monitorSafeAreaResolutions;
|
||||||
QList<int> monitorRefreshRates;
|
QList<int> monitorRefreshRates;
|
||||||
QString versionString;
|
|
||||||
bool supportsHdr;
|
|
||||||
bool usesMaterial3Theme;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -59,13 +59,6 @@ public:
|
|||||||
|
|
||||||
void showMessage(QString message, MessageType type) const
|
void showMessage(QString message, MessageType type) const
|
||||||
{
|
{
|
||||||
#if defined(Q_OS_WIN32)
|
|
||||||
UINT flags = MB_OK | MB_TOPMOST | MB_SETFOREGROUND;
|
|
||||||
flags |= (type == Info ? MB_ICONINFORMATION : MB_ICONERROR);
|
|
||||||
QString title = "Moonlight";
|
|
||||||
MessageBoxW(nullptr, reinterpret_cast<const wchar_t *>(message.utf16()),
|
|
||||||
reinterpret_cast<const wchar_t *>(title.utf16()), flags);
|
|
||||||
#endif
|
|
||||||
message = message.endsWith('\n') ? message : message + '\n';
|
message = message.endsWith('\n') ? message : message + '\n';
|
||||||
fputs(qPrintable(message), type == Info ? stdout : stderr);
|
fputs(qPrintable(message), type == Info ? stdout : stderr);
|
||||||
}
|
}
|
||||||
@@ -113,7 +106,7 @@ public:
|
|||||||
|
|
||||||
QPair<int,int> getResolutionOptionValue(QString name) const
|
QPair<int,int> getResolutionOptionValue(QString name) const
|
||||||
{
|
{
|
||||||
QRegularExpression re("^(\\d+)x(\\d+)$", QRegularExpression::CaseInsensitiveOption);
|
static QRegularExpression re("^(\\d+)x(\\d+)$", QRegularExpression::CaseInsensitiveOption);
|
||||||
auto match = re.match(value(name));
|
auto match = re.match(value(name));
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
showError(QString("Invalid %1 format: %2").arg(name, value(name)));
|
showError(QString("Invalid %1 format: %2").arg(name, value(name)));
|
||||||
@@ -202,7 +195,7 @@ GlobalCommandLineParser::ParseResult GlobalCommandLineParser::parse(const QStrin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parser.showError(QString("Invalid action"));
|
parser.showError("Invalid action");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -386,7 +379,7 @@ void StreamCommandLineParser::parse(const QStringList &args, StreamingPreference
|
|||||||
parser.handleUnknownOptions();
|
parser.handleUnknownOptions();
|
||||||
|
|
||||||
// Resolve display's width and height
|
// Resolve display's width and height
|
||||||
QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
|
static QRegularExpression resolutionRexExp("^(720|1080|1440|4K|resolution)$");
|
||||||
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
|
QStringList resoOptions = parser.optionNames().filter(resolutionRexExp);
|
||||||
bool displaySet = !resoOptions.isEmpty();
|
bool displaySet = !resoOptions.isEmpty();
|
||||||
if (displaySet) {
|
if (displaySet) {
|
||||||
|
|||||||
+19
-31
@@ -5,10 +5,8 @@
|
|||||||
#include "backend/computerseeker.h"
|
#include "backend/computerseeker.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#define COMPUTER_SEEK_TIMEOUT 30000
|
#define COMPUTER_SEEK_TIMEOUT 30000
|
||||||
#define APP_SEEK_TIMEOUT 10000
|
|
||||||
|
|
||||||
namespace CliListApps
|
namespace CliListApps
|
||||||
{
|
{
|
||||||
@@ -16,8 +14,7 @@ namespace CliListApps
|
|||||||
enum State {
|
enum State {
|
||||||
StateInit,
|
StateInit,
|
||||||
StateSeekComputer,
|
StateSeekComputer,
|
||||||
StateListApp,
|
StateListApps,
|
||||||
StateSeekApp,
|
|
||||||
StateFailure,
|
StateFailure,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -26,7 +23,6 @@ class Event
|
|||||||
public:
|
public:
|
||||||
enum Type {
|
enum Type {
|
||||||
ComputerFound,
|
ComputerFound,
|
||||||
ComputerUpdated,
|
|
||||||
ComputerSeekTimedout,
|
ComputerSeekTimedout,
|
||||||
Executed,
|
Executed,
|
||||||
};
|
};
|
||||||
@@ -66,9 +62,6 @@ public:
|
|||||||
q, &Launcher::onComputerSeekTimeout);
|
q, &Launcher::onComputerSeekTimeout);
|
||||||
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
|
m_ComputerSeeker->start(COMPUTER_SEEK_TIMEOUT);
|
||||||
|
|
||||||
q->connect(m_ComputerManager, &ComputerManager::computerStateChanged,
|
|
||||||
q, &Launcher::onComputerUpdated);
|
|
||||||
|
|
||||||
m_BoxArtManager = new BoxArtManager(q);
|
m_BoxArtManager = new BoxArtManager(q);
|
||||||
|
|
||||||
if (m_Arguments.isVerbose()) {
|
if (m_Arguments.isVerbose()) {
|
||||||
@@ -88,12 +81,28 @@ public:
|
|||||||
case Event::ComputerFound:
|
case Event::ComputerFound:
|
||||||
if (m_State == StateSeekComputer) {
|
if (m_State == StateSeekComputer) {
|
||||||
if (event.computer->pairState == NvComputer::PS_PAIRED) {
|
if (event.computer->pairState == NvComputer::PS_PAIRED) {
|
||||||
m_State = StateSeekApp;
|
m_State = StateListApps;
|
||||||
m_Computer = event.computer;
|
m_Computer = event.computer;
|
||||||
m_TimeoutTimer->start(APP_SEEK_TIMEOUT);
|
|
||||||
if (m_Arguments.isVerbose()) {
|
if (m_Arguments.isVerbose()) {
|
||||||
fprintf(stdout, "Loading app list...\n");
|
fprintf(stdout, "Loading app list...\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// To avoid race conditions where ComputerSeeker stops async polling, but
|
||||||
|
// ComputerManager is yet to update the app list, we will explicitly fetch the latest app list.
|
||||||
|
// Otherwise, it becomes complicated as we would have to guess whether ComputerManager
|
||||||
|
// would emit 1 signal (the list did not change) or 2 signals (indicating that the list has changed)
|
||||||
|
try {
|
||||||
|
NvHTTP http{m_Computer};
|
||||||
|
|
||||||
|
const auto appList = http.getAppList();
|
||||||
|
m_Arguments.isPrintCSV() ? printAppsCSV(appList) : printApps(appList);
|
||||||
|
|
||||||
|
QCoreApplication::exit(0);
|
||||||
|
} catch (std::exception& exception) {
|
||||||
|
fprintf(stderr, "%s\n", exception.what());
|
||||||
|
QCoreApplication::exit(1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
m_State = StateFailure;
|
m_State = StateFailure;
|
||||||
fprintf(stderr, "%s\n", qPrintable(QObject::tr("Computer %1 has not been paired. "
|
fprintf(stderr, "%s\n", qPrintable(QObject::tr("Computer %1 has not been paired. "
|
||||||
@@ -104,14 +113,6 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
// Occurs when a computer is updated
|
|
||||||
case Event::ComputerUpdated:
|
|
||||||
if (m_State == StateSeekApp) {
|
|
||||||
m_Arguments.isPrintCSV() ? printAppsCSV(m_Computer->appList) : printApps(m_Computer->appList);
|
|
||||||
|
|
||||||
QCoreApplication::exit(0);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +147,6 @@ public:
|
|||||||
BoxArtManager *m_BoxArtManager;
|
BoxArtManager *m_BoxArtManager;
|
||||||
NvComputer *m_Computer;
|
NvComputer *m_Computer;
|
||||||
State m_State;
|
State m_State;
|
||||||
QTimer *m_TimeoutTimer;
|
|
||||||
ListCommandLineParser m_Arguments;
|
ListCommandLineParser m_Arguments;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -157,11 +157,7 @@ Launcher::Launcher(QString computer, ListCommandLineParser arguments, QObject *p
|
|||||||
Q_D(Launcher);
|
Q_D(Launcher);
|
||||||
d->m_ComputerName = computer;
|
d->m_ComputerName = computer;
|
||||||
d->m_State = StateInit;
|
d->m_State = StateInit;
|
||||||
d->m_TimeoutTimer = new QTimer(this);
|
|
||||||
d->m_TimeoutTimer->setSingleShot(true);
|
|
||||||
d->m_Arguments = arguments;
|
d->m_Arguments = arguments;
|
||||||
connect(d->m_TimeoutTimer, &QTimer::timeout,
|
|
||||||
this, &Launcher::onComputerSeekTimeout);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Launcher::~Launcher()
|
Launcher::~Launcher()
|
||||||
@@ -197,12 +193,4 @@ void Launcher::onComputerSeekTimeout()
|
|||||||
d->handleEvent(event);
|
d->handleEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Launcher::onComputerUpdated(NvComputer *computer)
|
|
||||||
{
|
|
||||||
Q_D(Launcher);
|
|
||||||
Event event(Event::ComputerUpdated);
|
|
||||||
event.computer = computer;
|
|
||||||
d->handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ public:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onComputerFound(NvComputer *computer);
|
void onComputerFound(NvComputer *computer);
|
||||||
void onComputerUpdated(NvComputer *computer);
|
|
||||||
void onComputerSeekTimeout();
|
void onComputerSeekTimeout();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public:
|
|||||||
|
|
||||||
QString getCurrentAppName() const
|
QString getCurrentAppName() const
|
||||||
{
|
{
|
||||||
for (const NvApp& app : m_Computer->appList) {
|
for (const NvApp& app : std::as_const(m_Computer->appList)) {
|
||||||
if (m_Computer->currentGameId == app.id) {
|
if (m_Computer->currentGameId == app.id) {
|
||||||
return app.name;
|
return app.name;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
From 78261eb6e2fe728fa5d3843b5b8962b1ca559b7e Mon Sep 17 00:00:00 2001
|
|
||||||
From: David Rosca <nowrep@gmail.com>
|
|
||||||
Date: Sat, 16 Dec 2023 14:52:01 +0100
|
|
||||||
Subject: [PATCH] Vulkan: Don't try to reuse old swapchain
|
|
||||||
|
|
||||||
---
|
|
||||||
src/vulkan/swapchain.c | 12 ++++++++++--
|
|
||||||
1 file changed, 10 insertions(+), 2 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/src/vulkan/swapchain.c b/src/vulkan/swapchain.c
|
|
||||||
index 0646e57c..c0b07ce4 100644
|
|
||||||
--- a/src/vulkan/swapchain.c
|
|
||||||
+++ b/src/vulkan/swapchain.c
|
|
||||||
@@ -612,13 +612,21 @@ static bool vk_sw_recreate(pl_swapchain sw, int w, int h)
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
+ {
|
|
||||||
+ pl_gpu_flush(gpu);
|
|
||||||
+ vk_wait_idle(vk);
|
|
||||||
+ for (int i = 0; i < vk->pool_graphics->num_queues; i++)
|
|
||||||
+ vk->QueueWaitIdle(vk->pool_graphics->queues[i]);
|
|
||||||
+ vk->DestroySwapchainKHR(vk->dev, p->swapchain, PL_VK_ALLOC);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
// Calling `vkCreateSwapchainKHR` puts sinfo.oldSwapchain into a retired
|
|
||||||
// state whether the call succeeds or not, so we always need to garbage
|
|
||||||
// collect it afterwards - asynchronously as it may still be in use
|
|
||||||
- sinfo.oldSwapchain = p->swapchain;
|
|
||||||
+ /* sinfo.oldSwapchain = p->swapchain; */
|
|
||||||
p->swapchain = VK_NULL_HANDLE;
|
|
||||||
VkResult res = vk->CreateSwapchainKHR(vk->dev, &sinfo, PL_VK_ALLOC, &p->swapchain);
|
|
||||||
- vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain));
|
|
||||||
+ /* vk_dev_callback(vk, VK_CB_FUNC(destroy_swapchain), vk, vk_wrap_handle(sinfo.oldSwapchain)); */
|
|
||||||
PL_VK_ASSERT(res, "vk->CreateSwapchainKHR(...)");
|
|
||||||
|
|
||||||
// Get the new swapchain images
|
|
||||||
--
|
|
||||||
2.43.0
|
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<component type="desktop">
|
<component type="desktop-application">
|
||||||
<id>com.moonlight_stream.Moonlight</id>
|
<id>com.moonlight_stream.Moonlight</id>
|
||||||
<metadata_license>CC0-1.0</metadata_license>
|
<metadata_license>CC0-1.0</metadata_license>
|
||||||
<project_license>GPL-3.0+</project_license>
|
<project_license>GPL-3.0+</project_license>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<p>Moonlight can stream games and other applications from another PC with Sunshine or GeForce Experience software installed.</p>
|
<p>Moonlight can stream games and other applications from another PC with Sunshine or GeForce Experience software installed.</p>
|
||||||
<p>Features include:</p>
|
<p>Features include:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and NVDEC support</li>
|
<li>Hardware accelerated video decoding with VAAPI, VDPAU, and Vulkan Video support</li>
|
||||||
<li>H.264, HEVC, and AV1 codec support</li>
|
<li>H.264, HEVC, and AV1 codec support</li>
|
||||||
<li>7.1 surround sound audio support</li>
|
<li>7.1 surround sound audio support</li>
|
||||||
<li>10-point multitouch support</li>
|
<li>10-point multitouch support</li>
|
||||||
@@ -23,13 +23,30 @@
|
|||||||
<url type="homepage">https://moonlight-stream.org</url>
|
<url type="homepage">https://moonlight-stream.org</url>
|
||||||
<url type="bugtracker">https://github.com/moonlight-stream/moonlight-qt/issues</url>
|
<url type="bugtracker">https://github.com/moonlight-stream/moonlight-qt/issues</url>
|
||||||
<url type="help">https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide</url>
|
<url type="help">https://github.com/moonlight-stream/moonlight-docs/wiki/Setup-Guide</url>
|
||||||
|
<url type="vcs-browser">https://github.com/moonlight-stream/moonlight-qt</url>
|
||||||
|
<developer id="org.moonlight-stream">
|
||||||
|
<name>Moonlight Game Streaming Team</name>
|
||||||
|
</developer>
|
||||||
|
<update_contact>cameron@moonlight-stream.org</update_contact>
|
||||||
|
<launchable type="desktop-id">com.moonlight_stream.Moonlight.desktop</launchable>
|
||||||
|
<provides>
|
||||||
|
<binary>moonlight</binary>
|
||||||
|
</provides>
|
||||||
|
<content_rating type="oars-1.1" />
|
||||||
|
|
||||||
|
<branding>
|
||||||
|
<color type="primary" scheme_preference="light">#710085</color>
|
||||||
|
<color type="primary" scheme_preference="dark">#55285d</color>
|
||||||
|
</branding>
|
||||||
|
|
||||||
<screenshots>
|
<screenshots>
|
||||||
<screenshot type="default">
|
<screenshot type="default">
|
||||||
<image>https://moonlight-stream.org/appstream_resources/moonlight_screenshot_1.png</image>
|
<image>https://moonlight-stream.org/appstream_resources/moonlight_screenshot_1.png</image>
|
||||||
|
<caption>Host selection page showing local PCs</caption>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
<screenshot>
|
<screenshot>
|
||||||
<image>https://moonlight-stream.org/appstream_resources/moonlight_screenshot_2.png</image>
|
<image>https://moonlight-stream.org/appstream_resources/moonlight_screenshot_2.png</image>
|
||||||
|
<caption>Settings page displaying streaming options</caption>
|
||||||
</screenshot>
|
</screenshot>
|
||||||
</screenshots>
|
</screenshots>
|
||||||
|
|
||||||
@@ -816,19 +833,4 @@
|
|||||||
</release>
|
</release>
|
||||||
</releases>
|
</releases>
|
||||||
|
|
||||||
<categories>
|
|
||||||
<category>Game</category>
|
|
||||||
<category>Qt</category>
|
|
||||||
</categories>
|
|
||||||
|
|
||||||
<developer_name>Moonlight Game Streaming Team</developer_name>
|
|
||||||
<update_contact>cameron@moonlight-stream.org</update_contact>
|
|
||||||
|
|
||||||
<launchable type="desktop-id">com.moonlight_stream.Moonlight.desktop</launchable>
|
|
||||||
|
|
||||||
<provides>
|
|
||||||
<binary>moonlight</binary>
|
|
||||||
</provides>
|
|
||||||
|
|
||||||
<content_rating type="oars-1.0" />
|
|
||||||
</component>
|
</component>
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ Exec=moonlight
|
|||||||
Icon=moonlight
|
Icon=moonlight
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Qt;Game;
|
Categories=Qt;Game;Network;RemoteAccess;
|
||||||
Keywords=nvidia;gamestream;stream;sunshine;remote play;
|
Keywords=nvidia;gamestream;stream;sunshine;remote play;
|
||||||
|
|||||||
+30
-24
@@ -38,7 +38,7 @@ CenteredGridView {
|
|||||||
activated = true
|
activated = true
|
||||||
|
|
||||||
// Highlight the first item if a gamepad is connected
|
// Highlight the first item if a gamepad is connected
|
||||||
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
if (currentIndex === -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,9 +94,9 @@ CenteredGridView {
|
|||||||
// the image size checks if this is not an app collector game. We know the officially
|
// the image size checks if this is not an app collector game. We know the officially
|
||||||
// supported games all have box art, so this check is not required.
|
// supported games all have box art, so this check is not required.
|
||||||
if (!model.isAppCollectorGame &&
|
if (!model.isAppCollectorGame &&
|
||||||
((sourceSize.width == 130 && sourceSize.height == 180) || // GFE 2.0 placeholder image
|
((sourceSize.width === 130 && sourceSize.height === 180) || // GFE 2.0 placeholder image
|
||||||
(sourceSize.width == 628 && sourceSize.height == 888) || // GFE 3.0 placeholder image
|
(sourceSize.width === 628 && sourceSize.height === 888) || // GFE 3.0 placeholder image
|
||||||
(sourceSize.width == 200 && sourceSize.height == 266))) // Our no_app_image.png
|
(sourceSize.width === 200 && sourceSize.height === 266))) // Our no_app_image.png
|
||||||
{
|
{
|
||||||
isPlaceholder = true
|
isPlaceholder = true
|
||||||
}
|
}
|
||||||
@@ -123,20 +123,18 @@ CenteredGridView {
|
|||||||
|
|
||||||
sourceComponent: Item {
|
sourceComponent: Item {
|
||||||
RoundButton {
|
RoundButton {
|
||||||
|
// Don't steal focus from the toolbar buttons
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
|
||||||
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
|
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? -47 : 0
|
||||||
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
|
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : -60
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
implicitWidth: 85
|
implicitWidth: 85
|
||||||
implicitHeight: 85
|
implicitHeight: 85
|
||||||
|
|
||||||
Image {
|
icon.source: "qrc:/res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg"
|
||||||
source: "qrc:/res/play_arrow_FILL1_wght700_GRAD200_opsz48.svg"
|
icon.width: 75
|
||||||
anchors.centerIn: parent
|
icon.height: 75
|
||||||
sourceSize {
|
|
||||||
width: 75
|
|
||||||
height: 75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
launchOrResumeSelectedApp(true)
|
launchOrResumeSelectedApp(true)
|
||||||
@@ -151,20 +149,18 @@ CenteredGridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RoundButton {
|
RoundButton {
|
||||||
|
// Don't steal focus from the toolbar buttons
|
||||||
|
focusPolicy: Qt.NoFocus
|
||||||
|
|
||||||
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
|
anchors.horizontalCenterOffset: appIcon.isPlaceholder ? 47 : 0
|
||||||
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
|
anchors.verticalCenterOffset: appIcon.isPlaceholder ? -75 : 60
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
implicitWidth: 85
|
implicitWidth: 85
|
||||||
implicitHeight: 85
|
implicitHeight: 85
|
||||||
|
|
||||||
Image {
|
icon.source: "qrc:/res/stop_FILL1_wght700_GRAD200_opsz48.svg"
|
||||||
source: "qrc:/res/stop_FILL1_wght700_GRAD200_opsz48.svg"
|
icon.width: 75
|
||||||
anchors.centerIn: parent
|
icon.height: 75
|
||||||
sourceSize {
|
|
||||||
width: 75
|
|
||||||
height: 75
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
doQuitGame()
|
doQuitGame()
|
||||||
@@ -298,19 +294,17 @@ CenteredGridView {
|
|||||||
asynchronous: true
|
asynchronous: true
|
||||||
sourceComponent: NavigableMenu {
|
sourceComponent: NavigableMenu {
|
||||||
id: appContextMenu
|
id: appContextMenu
|
||||||
|
initiator: appContextMenuLoader.parent
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: appContextMenu
|
|
||||||
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
|
text: model.running ? qsTr("Resume Game") : qsTr("Launch Game")
|
||||||
onTriggered: launchOrResumeSelectedApp(true)
|
onTriggered: launchOrResumeSelectedApp(true)
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: appContextMenu
|
|
||||||
text: qsTr("Quit Game")
|
text: qsTr("Quit Game")
|
||||||
onTriggered: doQuitGame()
|
onTriggered: doQuitGame()
|
||||||
visible: model.running
|
visible: model.running
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: appContextMenu
|
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: model.directLaunch
|
checked: model.directLaunch
|
||||||
text: qsTr("Direct Launch")
|
text: qsTr("Direct Launch")
|
||||||
@@ -323,7 +317,6 @@ CenteredGridView {
|
|||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: appContextMenu
|
|
||||||
checkable: true
|
checkable: true
|
||||||
checked: model.hidden
|
checked: model.hidden
|
||||||
text: qsTr("Hide Game")
|
text: qsTr("Hide Game")
|
||||||
@@ -339,6 +332,19 @@ CenteredGridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 5
|
||||||
|
visible: appGrid.count === 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: qsTr("This computer doesn't seem to have any applications or some applications are hidden")
|
||||||
|
font.pointSize: 20
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NavigableMessageDialog {
|
NavigableMessageDialog {
|
||||||
id: quitAppDialog
|
id: quitAppDialog
|
||||||
property string appName : ""
|
property string appName : ""
|
||||||
|
|||||||
@@ -50,4 +50,12 @@ ComboBox {
|
|||||||
popup.onAboutToHide: {
|
popup.onAboutToHide: {
|
||||||
SdlGamepadKeyNavigation.setUiNavMode(true)
|
SdlGamepadKeyNavigation.setUiNavMode(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keys.onLeftPressed: {
|
||||||
|
decrementCurrentIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onRightPressed: {
|
||||||
|
incrementCurrentIndex()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ import QtQuick 2.9
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
// Detect Qt 5.11 or earlier using presence of synchronousDrag.
|
|
||||||
// Prior to 5.12, the leftMargin and rightMargin values did not work.
|
|
||||||
property bool hasBrokenMargins: this.synchronousDrag === undefined
|
|
||||||
|
|
||||||
property int minMargin: 10
|
property int minMargin: 10
|
||||||
property real availableWidth: (parent.width - 2 * minMargin)
|
property real availableWidth: (parent.width - 2 * minMargin)
|
||||||
property int itemsPerRow: availableWidth / cellWidth
|
property int itemsPerRow: availableWidth / cellWidth
|
||||||
@@ -15,11 +11,6 @@ GridView {
|
|||||||
function updateMargins() {
|
function updateMargins() {
|
||||||
leftMargin = horizontalMargin
|
leftMargin = horizontalMargin
|
||||||
rightMargin = horizontalMargin
|
rightMargin = horizontalMargin
|
||||||
|
|
||||||
if (hasBrokenMargins) {
|
|
||||||
anchors.leftMargin = leftMargin
|
|
||||||
anchors.rightMargin = rightMargin
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onHorizontalMarginChanged: {
|
onHorizontalMarginChanged: {
|
||||||
@@ -27,15 +18,6 @@ GridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (hasBrokenMargins) {
|
|
||||||
// This will cause an anchor conflict with the parent StackView
|
|
||||||
// which breaks animation, but otherwise the grid will not be
|
|
||||||
// centered in the window.
|
|
||||||
anchors.fill = parent
|
|
||||||
anchors.topMargin = topMargin
|
|
||||||
anchors.bottomMargin = bottomMargin
|
|
||||||
}
|
|
||||||
|
|
||||||
updateMargins()
|
updateMargins()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ Item {
|
|||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: stageSpinner
|
id: stageSpinner
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Item {
|
|||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: stageSpinner
|
id: stageSpinner
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ Item {
|
|||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: stageSpinner
|
id: stageSpinner
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
import QtQuick 2.0
|
import QtQuick 2.0
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.5
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
// We should use Overlay.overlay here but that's not available in Qt 5.9 :(
|
modal: true
|
||||||
parent: ApplicationWindow.contentItem
|
anchors.centerIn: Overlay.overlay
|
||||||
|
|
||||||
x: Math.round((parent.width - width) / 2)
|
onClosed: {
|
||||||
y: Math.round((parent.height - height) / 2)
|
// We must force focus back to the last item. If we don't,
|
||||||
|
// gamepad and keyboard navigation will break after a
|
||||||
onAboutToHide: {
|
|
||||||
// We must force focus back to the last item for platforms without
|
|
||||||
// support for more than one active window like Steam Link. If
|
|
||||||
// we don't, gamepad and keyboard navigation will break after a
|
|
||||||
// dialog appears.
|
// dialog appears.
|
||||||
stackView.forceActiveFocus()
|
stackView.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ ItemDelegate {
|
|||||||
}
|
}
|
||||||
Keys.onUpPressed: {
|
Keys.onUpPressed: {
|
||||||
grid.moveCurrentIndexUp()
|
grid.moveCurrentIndexUp()
|
||||||
|
|
||||||
|
// If we've reached the top of the grid, move focus to the toolbar
|
||||||
|
if (grid.currentItem === this) {
|
||||||
|
nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
clicked()
|
clicked()
|
||||||
|
|||||||
@@ -2,13 +2,18 @@ import QtQuick 2.0
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
Menu {
|
Menu {
|
||||||
|
property var initiator
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
// Give focus to the first visible and enabled menu item
|
// If the initiating object currently has keyboard focus,
|
||||||
for (var i = 0; i < count; i++) {
|
// give focus to the first visible and enabled menu item
|
||||||
var item = itemAt(i)
|
if (initiator.focus) {
|
||||||
if (item.visible && item.enabled) {
|
for (var i = 0; i < count; i++) {
|
||||||
item.forceActiveFocus(Qt.TabFocusReason)
|
var item = itemAt(i)
|
||||||
break
|
if (item.visible && item.enabled) {
|
||||||
|
item.forceActiveFocus(Qt.TabFocusReason)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ import QtQuick 2.0
|
|||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
MenuItem {
|
MenuItem {
|
||||||
// Qt 5.10 has a menu property, but we need to support 5.9
|
|
||||||
// so we must make our own.
|
|
||||||
property Menu parentMenu
|
|
||||||
|
|
||||||
// Ensure focus can't be given to an invisible item
|
// Ensure focus can't be given to an invisible item
|
||||||
enabled: visible
|
enabled: visible
|
||||||
height: visible ? implicitHeight : 0
|
height: visible ? implicitHeight : 0
|
||||||
@@ -15,7 +11,7 @@ MenuItem {
|
|||||||
// We must close the context menu first or
|
// We must close the context menu first or
|
||||||
// it can steal focus from any dialogs that
|
// it can steal focus from any dialogs that
|
||||||
// onTriggered may spawn.
|
// onTriggered may spawn.
|
||||||
parentMenu.close()
|
menu.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
@@ -27,6 +23,6 @@ MenuItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onEscapePressed: {
|
Keys.onEscapePressed: {
|
||||||
parentMenu.close()
|
menu.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ NavigableDialog {
|
|||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
// Force keyboard focus on the label so keyboard navigation works
|
// Force keyboard focus on the label so keyboard navigation works
|
||||||
dialogLabel.forceActiveFocus()
|
if (dialogButtonBox.count > 0) {
|
||||||
|
dialogButtonBox.itemAt(dialogButtonBox.count - 1).forceActiveFocus(Qt.TabFocus)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
@@ -24,6 +26,7 @@ NavigableDialog {
|
|||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: dialogSpinner
|
id: dialogSpinner
|
||||||
visible: false
|
visible: false
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
@@ -51,18 +54,6 @@ NavigableDialog {
|
|||||||
// will cause word wrap to kick in.
|
// will cause word wrap to kick in.
|
||||||
Layout.maximumWidth: 400
|
Layout.maximumWidth: 400
|
||||||
Layout.maximumHeight: 400
|
Layout.maximumHeight: 400
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
|
||||||
accept()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEnterPressed: {
|
|
||||||
accept()
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onEscapePressed: {
|
|
||||||
reject()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +61,15 @@ NavigableDialog {
|
|||||||
id: dialogButtonBox
|
id: dialogButtonBox
|
||||||
standardButtons: dialog.standardButtons
|
standardButtons: dialog.standardButtons
|
||||||
|
|
||||||
|
delegate: Button {
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onRightPressed: nextItemInFocusChain(true).forceActiveFocus(Qt.TabFocus)
|
||||||
|
Keys.onLeftPressed: nextItemInFocusChain(false).forceActiveFocus(Qt.TabFocus)
|
||||||
|
}
|
||||||
|
|
||||||
onHelpRequested: {
|
onHelpRequested: {
|
||||||
Qt.openUrlExternally(helpUrl)
|
Qt.openUrlExternally(helpUrl)
|
||||||
close()
|
close()
|
||||||
|
|||||||
@@ -7,17 +7,9 @@ ToolButton {
|
|||||||
|
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
|
|
||||||
// FIXME: We're using an Image here rather than icon.source because
|
icon.source: iconSource
|
||||||
// icons don't work on Qt 5.9 LTS.
|
icon.width: background.width
|
||||||
Image {
|
icon.height: background.height
|
||||||
id: image
|
|
||||||
source: iconSource
|
|
||||||
anchors.centerIn: parent.background
|
|
||||||
sourceSize {
|
|
||||||
width: parent.background.width * 1.10
|
|
||||||
height: parent.background.height * 1.10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This determines the size of the Material highlight. We increase it
|
// This determines the size of the Material highlight. We increase it
|
||||||
// from the default because we use larger than normal icons for TV readability.
|
// from the default because we use larger than normal icons for TV readability.
|
||||||
|
|||||||
+4
-11
@@ -35,7 +35,7 @@ CenteredGridView {
|
|||||||
ComputerManager.computerAddCompleted.connect(addComplete)
|
ComputerManager.computerAddCompleted.connect(addComplete)
|
||||||
|
|
||||||
// Highlight the first item if a gamepad is connected
|
// Highlight the first item if a gamepad is connected
|
||||||
if (currentIndex == -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
if (currentIndex === -1 && SdlGamepadKeyNavigation.getConnectedGamepads() > 0) {
|
||||||
currentIndex = 0
|
currentIndex = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,6 +90,7 @@ CenteredGridView {
|
|||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: searchSpinner
|
id: searchSpinner
|
||||||
visible: StreamingPreferences.enableMdns
|
visible: StreamingPreferences.enableMdns
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -143,6 +144,7 @@ CenteredGridView {
|
|||||||
width: 75
|
width: 75
|
||||||
height: 75
|
height: 75
|
||||||
visible: model.statusUnknown
|
visible: model.statusUnknown
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
@@ -163,13 +165,13 @@ CenteredGridView {
|
|||||||
asynchronous: true
|
asynchronous: true
|
||||||
sourceComponent: NavigableMenu {
|
sourceComponent: NavigableMenu {
|
||||||
id: pcContextMenu
|
id: pcContextMenu
|
||||||
|
initiator: pcContextMenuLoader.parent
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
|
text: qsTr("PC Status: %1").arg(model.online ? qsTr("Online") : qsTr("Offline"))
|
||||||
font.bold: true
|
font.bold: true
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: pcContextMenu
|
|
||||||
text: qsTr("View All Apps")
|
text: qsTr("View All Apps")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
var component = Qt.createComponent("AppView.qml")
|
var component = Qt.createComponent("AppView.qml")
|
||||||
@@ -179,13 +181,11 @@ CenteredGridView {
|
|||||||
visible: model.online && model.paired
|
visible: model.online && model.paired
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: pcContextMenu
|
|
||||||
text: qsTr("Wake PC")
|
text: qsTr("Wake PC")
|
||||||
onTriggered: computerModel.wakeComputer(index)
|
onTriggered: computerModel.wakeComputer(index)
|
||||||
visible: !model.online && model.wakeable
|
visible: !model.online && model.wakeable
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: pcContextMenu
|
|
||||||
text: qsTr("Test Network")
|
text: qsTr("Test Network")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
computerModel.testConnectionForComputer(index)
|
computerModel.testConnectionForComputer(index)
|
||||||
@@ -194,7 +194,6 @@ CenteredGridView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: pcContextMenu
|
|
||||||
text: qsTr("Rename PC")
|
text: qsTr("Rename PC")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
renamePcDialog.pcIndex = index
|
renamePcDialog.pcIndex = index
|
||||||
@@ -203,7 +202,6 @@ CenteredGridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: pcContextMenu
|
|
||||||
text: qsTr("Delete PC")
|
text: qsTr("Delete PC")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
deletePcDialog.pcIndex = index
|
deletePcDialog.pcIndex = index
|
||||||
@@ -212,7 +210,6 @@ CenteredGridView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
NavigableMenuItem {
|
NavigableMenuItem {
|
||||||
parentMenu: pcContextMenu
|
|
||||||
text: qsTr("View Details")
|
text: qsTr("View Details")
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
showPcDetailsDialog.pcDetails = model.details
|
showPcDetailsDialog.pcDetails = model.details
|
||||||
@@ -293,10 +290,6 @@ CenteredGridView {
|
|||||||
|
|
||||||
NavigableMessageDialog {
|
NavigableMessageDialog {
|
||||||
id: pairDialog
|
id: pairDialog
|
||||||
|
|
||||||
// Pairing dialog must be modal to prevent double-clicks from triggering
|
|
||||||
// pairing twice
|
|
||||||
modal: true
|
|
||||||
closePolicy: Popup.CloseOnEscape
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
|
||||||
// don't allow edits to the rest of the window while open
|
// don't allow edits to the rest of the window while open
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ Item {
|
|||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: stageSpinner
|
id: stageSpinner
|
||||||
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
+129
-76
@@ -281,11 +281,13 @@ Flickable {
|
|||||||
StreamingPreferences.width = selectedWidth
|
StreamingPreferences.width = selectedWidth
|
||||||
StreamingPreferences.height = selectedHeight
|
StreamingPreferences.height = selectedHeight
|
||||||
|
|
||||||
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
if (StreamingPreferences.autoAdjustBitrate) {
|
||||||
StreamingPreferences.height,
|
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||||
StreamingPreferences.fps,
|
StreamingPreferences.height,
|
||||||
StreamingPreferences.enableYUV444);
|
StreamingPreferences.fps,
|
||||||
slider.value = StreamingPreferences.bitrateKbps
|
StreamingPreferences.enableYUV444);
|
||||||
|
slider.value = StreamingPreferences.bitrateKbps
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndexValue = currentIndex
|
lastIndexValue = currentIndex
|
||||||
@@ -447,11 +449,13 @@ Flickable {
|
|||||||
if (StreamingPreferences.fps !== selectedFps) {
|
if (StreamingPreferences.fps !== selectedFps) {
|
||||||
StreamingPreferences.fps = selectedFps
|
StreamingPreferences.fps = selectedFps
|
||||||
|
|
||||||
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
if (StreamingPreferences.autoAdjustBitrate) {
|
||||||
StreamingPreferences.height,
|
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||||
StreamingPreferences.fps,
|
StreamingPreferences.height,
|
||||||
StreamingPreferences.enableYUV444);
|
StreamingPreferences.fps,
|
||||||
slider.value = StreamingPreferences.bitrateKbps
|
StreamingPreferences.enableYUV444);
|
||||||
|
slider.value = StreamingPreferences.bitrateKbps
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastIndexValue = currentIndex
|
lastIndexValue = currentIndex
|
||||||
@@ -678,26 +682,47 @@ Flickable {
|
|||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
Slider {
|
Row {
|
||||||
id: slider
|
width: parent.width
|
||||||
|
spacing: 5
|
||||||
|
|
||||||
value: StreamingPreferences.bitrateKbps
|
Slider {
|
||||||
|
id: slider
|
||||||
|
|
||||||
stepSize: 500
|
value: StreamingPreferences.bitrateKbps
|
||||||
from : 500
|
|
||||||
to: StreamingPreferences.unlockBitrate ? 500000 : 150000
|
|
||||||
|
|
||||||
snapMode: "SnapOnRelease"
|
stepSize: 500
|
||||||
width: Math.min(bitrateDesc.implicitWidth, parent.width)
|
from : 500
|
||||||
|
to: StreamingPreferences.unlockBitrate ? 500000 : 150000
|
||||||
|
|
||||||
onValueChanged: {
|
snapMode: "SnapOnRelease"
|
||||||
bitrateTitle.text = qsTr("Video bitrate: %1 Mbps").arg(value / 1000.0)
|
width: Math.min(bitrateDesc.implicitWidth, parent.width - (resetBitrateButton.visible ? resetBitrateButton.width + parent.spacing : 0))
|
||||||
StreamingPreferences.bitrateKbps = value
|
|
||||||
|
onValueChanged: {
|
||||||
|
bitrateTitle.text = qsTr("Video bitrate: %1 Mbps").arg(value / 1000.0)
|
||||||
|
StreamingPreferences.bitrateKbps = value
|
||||||
|
}
|
||||||
|
|
||||||
|
onMoved: {
|
||||||
|
StreamingPreferences.autoAdjustBitrate = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// Refresh the text after translations change
|
||||||
|
languageChanged.connect(valueChanged)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Button {
|
||||||
// Refresh the text after translations change
|
id: resetBitrateButton
|
||||||
languageChanged.connect(valueChanged)
|
text: qsTr("Use Default (%1 Mbps)").arg(StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444) / 1000.0)
|
||||||
|
visible: StreamingPreferences.bitrateKbps !== StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444)
|
||||||
|
onClicked: {
|
||||||
|
var defaultBitrate = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width, StreamingPreferences.height, StreamingPreferences.fps, StreamingPreferences.enableYUV444)
|
||||||
|
StreamingPreferences.bitrateKbps = defaultBitrate
|
||||||
|
StreamingPreferences.autoAdjustBitrate = true
|
||||||
|
slider.value = defaultBitrate
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,38 +812,64 @@ Flickable {
|
|||||||
ToolTip.text: qsTr("Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.")
|
ToolTip.text: qsTr("Fullscreen generally provides the best performance, but borderless windowed may work better with features like macOS Spaces, Alt+Tab, screenshot tools, on-screen overlays, etc.")
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
Row {
|
||||||
id: vsyncCheck
|
spacing: 5
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hoverEnabled: true
|
|
||||||
text: qsTr("V-Sync")
|
CheckBox {
|
||||||
font.pointSize: 12
|
id: vsyncCheck
|
||||||
checked: StreamingPreferences.enableVsync
|
hoverEnabled: true
|
||||||
onCheckedChanged: {
|
text: qsTr("V-Sync")
|
||||||
StreamingPreferences.enableVsync = checked
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.enableVsync
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.enableVsync = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing")
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
CheckBox {
|
||||||
ToolTip.timeout: 5000
|
id: framePacingCheck
|
||||||
ToolTip.visible: hovered
|
hoverEnabled: true
|
||||||
ToolTip.text: qsTr("Disabling V-Sync allows sub-frame rendering latency, but it can display visible tearing")
|
text: qsTr("Frame pacing")
|
||||||
|
font.pointSize: 12
|
||||||
|
enabled: StreamingPreferences.enableVsync
|
||||||
|
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.framePacing = checked
|
||||||
|
}
|
||||||
|
ToolTip.delay: 1000
|
||||||
|
ToolTip.timeout: 5000
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: framePacingCheck
|
id: enableHdr
|
||||||
width: parent.width
|
width: parent.width
|
||||||
hoverEnabled: true
|
text: qsTr("Enable HDR")
|
||||||
text: qsTr("Frame pacing")
|
font.pointSize: 12
|
||||||
font.pointSize: 12
|
|
||||||
enabled: StreamingPreferences.enableVsync
|
enabled: SystemProperties.supportsHdr
|
||||||
checked: StreamingPreferences.enableVsync && StreamingPreferences.framePacing
|
checked: enabled && StreamingPreferences.enableHdr
|
||||||
onCheckedChanged: {
|
onCheckedChanged: {
|
||||||
StreamingPreferences.framePacing = checked
|
StreamingPreferences.enableHdr = checked
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Updating StreamingPreferences.videoCodecConfig is handled above
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
ToolTip.delay: 1000
|
||||||
ToolTip.timeout: 5000
|
ToolTip.timeout: 5000
|
||||||
ToolTip.visible: hovered
|
ToolTip.visible: hovered
|
||||||
ToolTip.text: qsTr("Frame pacing reduces micro-stutter by delaying frames that come in too early")
|
ToolTip.text: enabled ?
|
||||||
|
qsTr("The stream will be HDR-capable, but some games may require an HDR monitor on your host PC to enable HDR mode.")
|
||||||
|
:
|
||||||
|
qsTr("HDR streaming is not supported on this PC.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1072,10 +1123,10 @@ Flickable {
|
|||||||
text: "Português" // Portuguese
|
text: "Português" // Portuguese
|
||||||
val: StreamingPreferences.LANG_PT
|
val: StreamingPreferences.LANG_PT
|
||||||
}
|
}
|
||||||
/* ListElement {
|
ListElement {
|
||||||
text: "Português do Brasil" // Brazilian Portuguese
|
text: "Português do Brasil" // Brazilian Portuguese
|
||||||
val: StreamingPreferences.LANG_PT_BR
|
val: StreamingPreferences.LANG_PT_BR
|
||||||
} */
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Ελληνικά" // Greek
|
text: "Ελληνικά" // Greek
|
||||||
val: StreamingPreferences.LANG_EL
|
val: StreamingPreferences.LANG_EL
|
||||||
@@ -1112,6 +1163,18 @@ Flickable {
|
|||||||
text: "Eesti" // Estonian
|
text: "Eesti" // Estonian
|
||||||
val: StreamingPreferences.LANG_ET
|
val: StreamingPreferences.LANG_ET
|
||||||
} */
|
} */
|
||||||
|
ListElement {
|
||||||
|
text: "Български" // Bulgarian
|
||||||
|
val: StreamingPreferences.LANG_BG
|
||||||
|
}
|
||||||
|
/* ListElement {
|
||||||
|
text: "Esperanto"
|
||||||
|
val: StreamingPreferences.LANG_EO
|
||||||
|
} */
|
||||||
|
ListElement {
|
||||||
|
text: "தமிழ்" // Tamil
|
||||||
|
val: StreamingPreferences.LANG_TA
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
// ::onActivated must be used, as it only listens for when the index is changed by a human
|
||||||
onActivated : {
|
onActivated : {
|
||||||
@@ -1199,6 +1262,17 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBox {
|
||||||
|
id: configurationWarningsCheck
|
||||||
|
width: parent.width
|
||||||
|
text: qsTr("Show configuration warnings")
|
||||||
|
font.pointSize: 12
|
||||||
|
checked: StreamingPreferences.configurationWarnings
|
||||||
|
onCheckedChanged: {
|
||||||
|
StreamingPreferences.configurationWarnings = checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
visible: SystemProperties.hasDiscordIntegration
|
visible: SystemProperties.hasDiscordIntegration
|
||||||
id: discordPresenceCheck
|
id: discordPresenceCheck
|
||||||
@@ -1570,7 +1644,7 @@ Flickable {
|
|||||||
val: StreamingPreferences.VCC_FORCE_HEVC
|
val: StreamingPreferences.VCC_FORCE_HEVC
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: qsTr("AV1 (Experimental)")
|
text: qsTr("AV1")
|
||||||
val: StreamingPreferences.VCC_FORCE_AV1
|
val: StreamingPreferences.VCC_FORCE_AV1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1582,33 +1656,10 @@ Flickable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
|
||||||
id: enableHdr
|
|
||||||
width: parent.width
|
|
||||||
text: qsTr("Enable HDR (Experimental)")
|
|
||||||
font.pointSize: 12
|
|
||||||
|
|
||||||
enabled: SystemProperties.supportsHdr
|
|
||||||
checked: enabled && StreamingPreferences.enableHdr
|
|
||||||
onCheckedChanged: {
|
|
||||||
StreamingPreferences.enableHdr = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
// Updating StreamingPreferences.videoCodecConfig is handled above
|
|
||||||
|
|
||||||
ToolTip.delay: 1000
|
|
||||||
ToolTip.timeout: 5000
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
ToolTip.text: enabled ?
|
|
||||||
qsTr("The stream will be HDR-capable, but some games may require an HDR monitor on your host PC to enable HDR mode.")
|
|
||||||
:
|
|
||||||
qsTr("HDR streaming is not supported on this PC.")
|
|
||||||
}
|
|
||||||
|
|
||||||
CheckBox {
|
CheckBox {
|
||||||
id: enableYUV444
|
id: enableYUV444
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: qsTr("Enable YUV 4:4:4 (Experimental)")
|
text: qsTr("Enable YUV 4:4:4")
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
|
|
||||||
checked: StreamingPreferences.enableYUV444
|
checked: StreamingPreferences.enableYUV444
|
||||||
@@ -1616,11 +1667,13 @@ Flickable {
|
|||||||
// This is called on init, so only reset to default bitrate when checked state changes.
|
// This is called on init, so only reset to default bitrate when checked state changes.
|
||||||
if (StreamingPreferences.enableYUV444 != checked) {
|
if (StreamingPreferences.enableYUV444 != checked) {
|
||||||
StreamingPreferences.enableYUV444 = checked
|
StreamingPreferences.enableYUV444 = checked
|
||||||
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
if (StreamingPreferences.autoAdjustBitrate) {
|
||||||
StreamingPreferences.height,
|
StreamingPreferences.bitrateKbps = StreamingPreferences.getDefaultBitrate(StreamingPreferences.width,
|
||||||
StreamingPreferences.fps,
|
StreamingPreferences.height,
|
||||||
StreamingPreferences.enableYUV444);
|
StreamingPreferences.fps,
|
||||||
slider.value = StreamingPreferences.bitrateKbps
|
StreamingPreferences.enableYUV444);
|
||||||
|
slider.value = StreamingPreferences.bitrateKbps
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+60
-33
@@ -4,6 +4,7 @@ import QtQuick.Window 2.2
|
|||||||
|
|
||||||
import SdlGamepadKeyNavigation 1.0
|
import SdlGamepadKeyNavigation 1.0
|
||||||
import Session 1.0
|
import Session 1.0
|
||||||
|
import SystemProperties 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
property Session session
|
property Session session
|
||||||
@@ -48,18 +49,6 @@ Item {
|
|||||||
console.error(text)
|
console.error(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayLaunchWarning(text)
|
|
||||||
{
|
|
||||||
// This toast appears for 3 seconds, just shorter than how long
|
|
||||||
// Session will wait for it to be displayed. This gives it time
|
|
||||||
// to transition to invisible before continuing.
|
|
||||||
var toast = Qt.createQmlObject('import QtQuick.Controls 2.2; ToolTip {}', parent, '')
|
|
||||||
toast.text = text
|
|
||||||
toast.timeout = 3000
|
|
||||||
toast.visible = true
|
|
||||||
console.warn(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
function quitStarting()
|
function quitStarting()
|
||||||
{
|
{
|
||||||
// Avoid the push transition animation
|
// Avoid the push transition animation
|
||||||
@@ -79,20 +68,16 @@ Item {
|
|||||||
// Re-enable GUI gamepad usage now
|
// Re-enable GUI gamepad usage now
|
||||||
SdlGamepadKeyNavigation.enable()
|
SdlGamepadKeyNavigation.enable()
|
||||||
|
|
||||||
if (quitAfter) {
|
// Pop the StreamSegue off the stack if this is a GUI-based app launch
|
||||||
if (streamSegueErrorDialog.text) {
|
if (!quitAfter) {
|
||||||
// Quit when the error dialog is acknowledged
|
|
||||||
streamSegueErrorDialog.quitAfter = quitAfter
|
|
||||||
streamSegueErrorDialog.open()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Quit immediately
|
|
||||||
Qt.quit()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Exit this view
|
|
||||||
stackView.pop()
|
stackView.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quitAfter && !streamSegueErrorDialog.text) {
|
||||||
|
// If this was a CLI launch without errors, exit now
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
else {
|
||||||
// Show the Qt window again after streaming
|
// Show the Qt window again after streaming
|
||||||
window.visible = true
|
window.visible = true
|
||||||
|
|
||||||
@@ -132,11 +117,14 @@ Item {
|
|||||||
session.stageFailed.connect(stageFailed)
|
session.stageFailed.connect(stageFailed)
|
||||||
session.connectionStarted.connect(connectionStarted)
|
session.connectionStarted.connect(connectionStarted)
|
||||||
session.displayLaunchError.connect(displayLaunchError)
|
session.displayLaunchError.connect(displayLaunchError)
|
||||||
session.displayLaunchWarning.connect(displayLaunchWarning)
|
|
||||||
session.quitStarting.connect(quitStarting)
|
session.quitStarting.connect(quitStarting)
|
||||||
session.sessionFinished.connect(sessionFinished)
|
session.sessionFinished.connect(sessionFinished)
|
||||||
session.readyForDeletion.connect(sessionReadyForDeletion)
|
session.readyForDeletion.connect(sessionReadyForDeletion)
|
||||||
|
|
||||||
|
// Ensure the SystemProperties async thread is finished,
|
||||||
|
// since it may currently be using the SDL video subsystem
|
||||||
|
SystemProperties.waitForAsyncLoad()
|
||||||
|
|
||||||
// Kick off the stream
|
// Kick off the stream
|
||||||
spinnerTimer.start()
|
spinnerTimer.start()
|
||||||
streamLoader.active = true
|
streamLoader.active = true
|
||||||
@@ -151,7 +139,20 @@ Item {
|
|||||||
// middle of the animation on Windows, which looks very
|
// middle of the animation on Windows, which looks very
|
||||||
// obviously broken.
|
// obviously broken.
|
||||||
interval: 100
|
interval: 100
|
||||||
onTriggered: stageSpinner.running = true
|
onTriggered: stageSpinner.visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: startSessionTimer
|
||||||
|
onTriggered: {
|
||||||
|
// Garbage collect QML stuff before we start streaming,
|
||||||
|
// since we'll probably be streaming for a while and we
|
||||||
|
// won't be able to GC during the stream.
|
||||||
|
gc()
|
||||||
|
|
||||||
|
// Run the streaming session to completion
|
||||||
|
session.start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -170,13 +171,38 @@ Item {
|
|||||||
// Stop GUI gamepad usage now
|
// Stop GUI gamepad usage now
|
||||||
SdlGamepadKeyNavigation.disable()
|
SdlGamepadKeyNavigation.disable()
|
||||||
|
|
||||||
// Garbage collect QML stuff before we start streaming,
|
// Initialize the session and probe for host/client capabilities
|
||||||
// since we'll probably be streaming for a while and we
|
if (!session.initialize(window)) {
|
||||||
// won't be able to GC during the stream.
|
sessionFinished(0);
|
||||||
gc()
|
sessionReadyForDeletion();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Run the streaming session to completion
|
// Don't wait unless we have toasts to display
|
||||||
session.exec(Window.window)
|
startSessionTimer.interval = 0
|
||||||
|
|
||||||
|
// Display the toasts together in a vertical centered arrangement
|
||||||
|
var yOffset = 0
|
||||||
|
for (var i = 0; i < session.launchWarnings.length; i++) {
|
||||||
|
var text = session.launchWarnings[i]
|
||||||
|
console.warn(text)
|
||||||
|
|
||||||
|
// Show the tooltip for 3 seconds
|
||||||
|
var toast = Qt.createQmlObject('import QtQuick.Controls 2.2; ToolTip {}', parent, '')
|
||||||
|
toast.timeout = 3000
|
||||||
|
toast.text = text
|
||||||
|
toast.y += yOffset
|
||||||
|
toast.visible = true
|
||||||
|
|
||||||
|
// Offset the next toast below the previous one
|
||||||
|
yOffset = toast.y + toast.padding + toast.height
|
||||||
|
|
||||||
|
// Allow an extra 500 ms for the tooltip's fade-out animation to finish
|
||||||
|
startSessionTimer.interval = toast.timeout + 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the timer to wait for toasts (or start the session immediately)
|
||||||
|
startSessionTimer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceComponent: Item {}
|
sourceComponent: Item {}
|
||||||
@@ -188,7 +214,8 @@ Item {
|
|||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
id: stageSpinner
|
id: stageSpinner
|
||||||
running: false
|
running: visible
|
||||||
|
visible: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ void AppModel::quitRunningApp()
|
|||||||
|
|
||||||
bool AppModel::isAppCurrentlyVisible(const NvApp& app)
|
bool AppModel::isAppCurrentlyVisible(const NvApp& app)
|
||||||
{
|
{
|
||||||
for (const NvApp& visibleApp : m_VisibleApps) {
|
for (const NvApp& visibleApp : std::as_const(m_VisibleApps)) {
|
||||||
if (app.id == visibleApp.id) {
|
if (app.id == visibleApp.id) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ void AppModel::updateAppList(QVector<NvApp> newList)
|
|||||||
const NvApp& existingApp = m_VisibleApps.at(i);
|
const NvApp& existingApp = m_VisibleApps.at(i);
|
||||||
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (const NvApp& newApp : newVisibleList) {
|
for (const NvApp& newApp : std::as_const(newVisibleList)) {
|
||||||
if (existingApp.id == newApp.id) {
|
if (existingApp.id == newApp.id) {
|
||||||
// If the data changed, update it in our list
|
// If the data changed, update it in our list
|
||||||
if (existingApp != newApp) {
|
if (existingApp != newApp) {
|
||||||
@@ -178,7 +178,7 @@ void AppModel::updateAppList(QVector<NvApp> newList)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process additions now
|
// Process additions now
|
||||||
for (const NvApp& newApp : newVisibleList) {
|
for (const NvApp& newApp : std::as_const(newVisibleList)) {
|
||||||
int insertionIndex = m_VisibleApps.size();
|
int insertionIndex = m_VisibleApps.size();
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
|
||||||
|
|||||||
+33
-21
@@ -51,10 +51,20 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Display any modal dialogs for configuration warnings
|
// Display any modal dialogs for configuration warnings
|
||||||
if (SystemProperties.isWow64) {
|
if (runConfigChecks) {
|
||||||
wow64Dialog.open()
|
if (SystemProperties.isWow64) {
|
||||||
|
wow64Dialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hardware acceleration and unmapped gamepads are checked asynchronously
|
||||||
|
SystemProperties.hasHardwareAccelerationChanged.connect(hasHardwareAccelerationChanged)
|
||||||
|
SystemProperties.unmappedGamepadsChanged.connect(hasUnmappedGamepadsChanged)
|
||||||
|
SystemProperties.startAsyncLoad()
|
||||||
}
|
}
|
||||||
else if (!SystemProperties.hasHardwareAcceleration) {
|
}
|
||||||
|
|
||||||
|
function hasHardwareAccelerationChanged() {
|
||||||
|
if (!SystemProperties.hasHardwareAcceleration && StreamingPreferences.videoDecoderSelection !== StreamingPreferences.VDS_FORCE_SOFTWARE) {
|
||||||
if (SystemProperties.isRunningXWayland) {
|
if (SystemProperties.isRunningXWayland) {
|
||||||
xWaylandDialog.open()
|
xWaylandDialog.open()
|
||||||
}
|
}
|
||||||
@@ -62,16 +72,28 @@ ApplicationWindow {
|
|||||||
noHwDecoderDialog.open()
|
noHwDecoderDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasUnmappedGamepadsChanged() {
|
||||||
if (SystemProperties.unmappedGamepads) {
|
if (SystemProperties.unmappedGamepads) {
|
||||||
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
unmappedGamepadDialog.unmappedGamepads = SystemProperties.unmappedGamepads
|
||||||
unmappedGamepadDialog.open()
|
unmappedGamepadDialog.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It would be better to use TextMetrics here, but it always lays out
|
||||||
|
// the text slightly more compactly than real Text does in ToolTip,
|
||||||
|
// causing unexpected line breaks to be inserted
|
||||||
|
Text {
|
||||||
|
id: tooltipTextLayoutHelper
|
||||||
|
visible: false
|
||||||
|
font: ToolTip.toolTip.font
|
||||||
|
text: ToolTip.toolTip.text
|
||||||
|
}
|
||||||
|
|
||||||
// This configures the maximum width of the singleton attached QML ToolTip. If left unconstrained,
|
// This configures the maximum width of the singleton attached QML ToolTip. If left unconstrained,
|
||||||
// it will never insert a line break and just extend on forever.
|
// it will never insert a line break and just extend on forever.
|
||||||
ToolTip.toolTip.contentWidth: ToolTip.toolTip.implicitContentWidth < 400 ? ToolTip.toolTip.implicitContentWidth : 400
|
ToolTip.toolTip.contentWidth: Math.min(tooltipTextLayoutHelper.width, 400)
|
||||||
|
|
||||||
function goBack() {
|
function goBack() {
|
||||||
if (clearOnBack) {
|
if (clearOnBack) {
|
||||||
@@ -195,20 +217,10 @@ ApplicationWindow {
|
|||||||
SdlGamepadKeyNavigation.notifyWindowFocus(visible && active)
|
SdlGamepadKeyNavigation.notifyWindowFocus(visible && active)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for lack of instanceof in Qt 5.9.
|
|
||||||
//
|
|
||||||
// Based on https://stackoverflow.com/questions/13923794/how-to-do-a-is-a-typeof-or-instanceof-in-qml
|
|
||||||
function qmltypeof(obj, className) { // QtObject, string -> bool
|
|
||||||
// className plus "(" is the class instance without modification
|
|
||||||
// className plus "_QML" is the class instance with user-defined properties
|
|
||||||
var str = obj.toString();
|
|
||||||
return str.startsWith(className + "(") || str.startsWith(className + "_QML");
|
|
||||||
}
|
|
||||||
|
|
||||||
function navigateTo(url, objectType)
|
function navigateTo(url, objectType)
|
||||||
{
|
{
|
||||||
var existingItem = stackView.find(function(item, index) {
|
var existingItem = stackView.find(function(item, index) {
|
||||||
return qmltypeof(item, objectType)
|
return item instanceof objectType
|
||||||
})
|
})
|
||||||
|
|
||||||
if (existingItem !== null) {
|
if (existingItem !== null) {
|
||||||
@@ -275,7 +287,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
Label {
|
Label {
|
||||||
id: versionLabel
|
id: versionLabel
|
||||||
visible: qmltypeof(stackView.currentItem, "SettingsView")
|
visible: stackView.currentItem instanceof SettingsView
|
||||||
text: qsTr("Version %1").arg(SystemProperties.versionString)
|
text: qsTr("Version %1").arg(SystemProperties.versionString)
|
||||||
font.pointSize: 12
|
font.pointSize: 12
|
||||||
horizontalAlignment: Qt.AlignRight
|
horizontalAlignment: Qt.AlignRight
|
||||||
@@ -285,7 +297,7 @@ ApplicationWindow {
|
|||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
id: discordButton
|
id: discordButton
|
||||||
visible: SystemProperties.hasBrowser &&
|
visible: SystemProperties.hasBrowser &&
|
||||||
qmltypeof(stackView.currentItem, "SettingsView")
|
stackView.currentItem instanceof SettingsView
|
||||||
|
|
||||||
iconSource: "qrc:/res/discord.svg"
|
iconSource: "qrc:/res/discord.svg"
|
||||||
|
|
||||||
@@ -304,7 +316,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
NavigableToolButton {
|
NavigableToolButton {
|
||||||
id: addPcButton
|
id: addPcButton
|
||||||
visible: qmltypeof(stackView.currentItem, "PcView")
|
visible: stackView.currentItem instanceof PcView
|
||||||
|
|
||||||
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
|
iconSource: "qrc:/res/ic_add_to_queue_white_48px.svg"
|
||||||
|
|
||||||
@@ -402,7 +414,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
iconSource: "qrc:/res/ic_videogame_asset_white_48px.svg"
|
||||||
|
|
||||||
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", "GamepadMapper")
|
onClicked: navigateTo("qrc:/gui/GamepadMapper.qml", GamepadMapper)
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
Keys.onDownPressed: {
|
||||||
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
||||||
@@ -414,7 +426,7 @@ ApplicationWindow {
|
|||||||
|
|
||||||
iconSource: "qrc:/res/settings.svg"
|
iconSource: "qrc:/res/settings.svg"
|
||||||
|
|
||||||
onClicked: navigateTo("qrc:/gui/SettingsView.qml", "SettingsView")
|
onClicked: navigateTo("qrc:/gui/SettingsView.qml", SettingsView)
|
||||||
|
|
||||||
Keys.onDownPressed: {
|
Keys.onDownPressed: {
|
||||||
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
stackView.currentItem.forceActiveFocus(Qt.TabFocus)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ void SdlGamepadKeyNavigation::enable()
|
|||||||
// arrival events. Additionally, there's a race condition between
|
// arrival events. Additionally, there's a race condition between
|
||||||
// our QML objects being destroyed and SDL being deinitialized that
|
// our QML objects being destroyed and SDL being deinitialized that
|
||||||
// this solves too.
|
// this solves too.
|
||||||
if (SDLC_FAILURE(SDL_InitSubSystem(SDL_INIT_GAMEPAD))) {
|
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0) {
|
||||||
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,
|
||||||
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
|
"SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) failed: %s",
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
@@ -51,19 +51,23 @@ void SdlGamepadKeyNavigation::enable()
|
|||||||
// on first init of the GC subsystem. We can't depend on them due to
|
// on first init of the GC subsystem. We can't depend on them due to
|
||||||
// overlapping lifetimes of SdlGamepadKeyNavigation instances, so we
|
// overlapping lifetimes of SdlGamepadKeyNavigation instances, so we
|
||||||
// will attach ourselves.
|
// will attach ourselves.
|
||||||
SDL_PumpEvents();
|
//
|
||||||
SDL_FlushEvent(SDL_EVENT_GAMEPAD_ADDED);
|
// NB: We use SDL_JoystickUpdate() instead of SDL_PumpEvents() because
|
||||||
|
// the latter can do a bit more work that we want (like handling video
|
||||||
|
// events that we intentionally do not want to process yet).
|
||||||
|
SDL_JoystickUpdate();
|
||||||
|
SDL_FlushEvent(SDL_CONTROLLERDEVICEADDED);
|
||||||
|
|
||||||
// Open all currently attached game controllers
|
// Open all currently attached game controllers
|
||||||
int numGamepads = 0;
|
int numJoysticks = SDL_NumJoysticks();
|
||||||
SDL_JoystickID* gamepads = SDL_GetGamepads(&numGamepads);
|
for (int i = 0; i < numJoysticks; i++) {
|
||||||
for (int i = 0; i < numGamepads; i++) {
|
if (SDL_IsGameController(i)) {
|
||||||
SDL_Gamepad * gc = SDL_OpenGamepad(i);
|
SDL_GameController* gc = SDL_GameControllerOpen(i);
|
||||||
if (gc != nullptr) {
|
if (gc != nullptr) {
|
||||||
m_Gamepads.append(gc);
|
m_Gamepads.append(gc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SDL_free(gamepads);
|
|
||||||
|
|
||||||
m_Enabled = true;
|
m_Enabled = true;
|
||||||
|
|
||||||
@@ -82,11 +86,11 @@ void SdlGamepadKeyNavigation::disable()
|
|||||||
Q_ASSERT(!m_PollingTimer->isActive());
|
Q_ASSERT(!m_PollingTimer->isActive());
|
||||||
|
|
||||||
while (!m_Gamepads.isEmpty()) {
|
while (!m_Gamepads.isEmpty()) {
|
||||||
SDL_CloseGamepad(m_Gamepads[0]);
|
SDL_GameControllerClose(m_Gamepads[0]);
|
||||||
m_Gamepads.removeAt(0);
|
m_Gamepads.removeAt(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_QuitSubSystem(SDL_INIT_GAMEPAD);
|
SDL_QuitSubSystem(SDL_INIT_GAMECONTROLLER);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SdlGamepadKeyNavigation::notifyWindowFocus(bool hasFocus)
|
void SdlGamepadKeyNavigation::notifyWindowFocus(bool hasFocus)
|
||||||
@@ -99,51 +103,53 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
{
|
{
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
|
|
||||||
|
// Update joystick state without pumping other events (see enable() comment)
|
||||||
|
SDL_JoystickUpdate();
|
||||||
|
|
||||||
// Discard any pending button events on the first poll to avoid picking up
|
// Discard any pending button events on the first poll to avoid picking up
|
||||||
// stale input data from the stream session (like the quit combo).
|
// stale input data from the stream session (like the quit combo).
|
||||||
if (m_FirstPoll) {
|
if (m_FirstPoll) {
|
||||||
SDL_PumpEvents();
|
SDL_FlushEvent(SDL_CONTROLLERBUTTONDOWN);
|
||||||
SDL_FlushEvent(SDL_EVENT_GAMEPAD_BUTTON_DOWN);
|
SDL_FlushEvent(SDL_CONTROLLERBUTTONUP);
|
||||||
SDL_FlushEvent(SDL_EVENT_GAMEPAD_BUTTON_UP);
|
|
||||||
|
|
||||||
m_FirstPoll = false;
|
m_FirstPoll = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (SDL_PollEvent(&event)) {
|
// Peep events rather than polling to avoid calling SDL_PumpEvents()
|
||||||
|
while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) == 1) {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case SDL_EVENT_QUIT :
|
case SDL_QUIT:
|
||||||
// SDL may send us a quit event since we initialize
|
// SDL may send us a quit event since we initialize
|
||||||
// the video subsystem on startup. If we get one,
|
// the video subsystem on startup. If we get one,
|
||||||
// forward it on for Qt to take care of.
|
// forward it on for Qt to take care of.
|
||||||
QCoreApplication::instance()->quit();
|
QCoreApplication::instance()->quit();
|
||||||
break;
|
break;
|
||||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN :
|
case SDL_CONTROLLERBUTTONDOWN:
|
||||||
case SDL_EVENT_GAMEPAD_BUTTON_UP :
|
case SDL_CONTROLLERBUTTONUP:
|
||||||
{
|
{
|
||||||
QEvent::Type type =
|
QEvent::Type type =
|
||||||
event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN ?
|
event.type == SDL_CONTROLLERBUTTONDOWN ?
|
||||||
QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
|
QEvent::Type::KeyPress : QEvent::Type::KeyRelease;
|
||||||
|
|
||||||
// Swap face buttons if needed
|
// Swap face buttons if needed
|
||||||
if (m_Prefs->swapFaceButtons) {
|
if (m_Prefs->swapFaceButtons) {
|
||||||
switch (event.gbutton.button) {
|
switch (event.cbutton.button) {
|
||||||
case SDL_GAMEPAD_BUTTON_SOUTH :
|
case SDL_CONTROLLER_BUTTON_A:
|
||||||
event.gbutton.button = SDL_GAMEPAD_BUTTON_EAST;
|
event.cbutton.button = SDL_CONTROLLER_BUTTON_B;
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_EAST :
|
case SDL_CONTROLLER_BUTTON_B:
|
||||||
event.gbutton.button = SDL_GAMEPAD_BUTTON_SOUTH;
|
event.cbutton.button = SDL_CONTROLLER_BUTTON_A;
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_WEST :
|
case SDL_CONTROLLER_BUTTON_X:
|
||||||
event.gbutton.button = SDL_GAMEPAD_BUTTON_NORTH;
|
event.cbutton.button = SDL_CONTROLLER_BUTTON_Y;
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_NORTH :
|
case SDL_CONTROLLER_BUTTON_Y:
|
||||||
event.gbutton.button = SDL_GAMEPAD_BUTTON_WEST;
|
event.cbutton.button = SDL_CONTROLLER_BUTTON_X;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.gbutton.button) {
|
switch (event.cbutton.button) {
|
||||||
case SDL_GAMEPAD_BUTTON_DPAD_UP :
|
case SDL_CONTROLLER_BUTTON_DPAD_UP:
|
||||||
if (m_UiNavMode) {
|
if (m_UiNavMode) {
|
||||||
// Back-tab
|
// Back-tab
|
||||||
sendKey(type, Qt::Key_Tab, Qt::ShiftModifier);
|
sendKey(type, Qt::Key_Tab, Qt::ShiftModifier);
|
||||||
@@ -152,7 +158,7 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
sendKey(type, Qt::Key_Up);
|
sendKey(type, Qt::Key_Up);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN :
|
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
|
||||||
if (m_UiNavMode) {
|
if (m_UiNavMode) {
|
||||||
sendKey(type, Qt::Key_Tab);
|
sendKey(type, Qt::Key_Tab);
|
||||||
}
|
}
|
||||||
@@ -160,13 +166,13 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
sendKey(type, Qt::Key_Down);
|
sendKey(type, Qt::Key_Down);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT :
|
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
|
||||||
sendKey(type, Qt::Key_Left);
|
sendKey(type, Qt::Key_Left);
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT :
|
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
|
||||||
sendKey(type, Qt::Key_Right);
|
sendKey(type, Qt::Key_Right);
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_SOUTH :
|
case SDL_CONTROLLER_BUTTON_A:
|
||||||
if (m_UiNavMode) {
|
if (m_UiNavMode) {
|
||||||
sendKey(type, Qt::Key_Space);
|
sendKey(type, Qt::Key_Space);
|
||||||
}
|
}
|
||||||
@@ -174,14 +180,14 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
sendKey(type, Qt::Key_Return);
|
sendKey(type, Qt::Key_Return);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_EAST :
|
case SDL_CONTROLLER_BUTTON_B:
|
||||||
sendKey(type, Qt::Key_Escape);
|
sendKey(type, Qt::Key_Escape);
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_WEST :
|
case SDL_CONTROLLER_BUTTON_X:
|
||||||
sendKey(type, Qt::Key_Menu);
|
sendKey(type, Qt::Key_Menu);
|
||||||
break;
|
break;
|
||||||
case SDL_GAMEPAD_BUTTON_NORTH :
|
case SDL_CONTROLLER_BUTTON_Y:
|
||||||
case SDL_GAMEPAD_BUTTON_START :
|
case SDL_CONTROLLER_BUTTON_START:
|
||||||
// HACK: We use this keycode to inform main.qml
|
// HACK: We use this keycode to inform main.qml
|
||||||
// to show the settings when Key_Menu is handled
|
// to show the settings when Key_Menu is handled
|
||||||
// by the control in focus.
|
// by the control in focus.
|
||||||
@@ -192,18 +198,11 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case SDL_EVENT_GAMEPAD_ADDED :
|
case SDL_CONTROLLERDEVICEADDED:
|
||||||
#if SDL_VERSION_ATLEAST(3, 0, 0)
|
SDL_GameController* gc = SDL_GameControllerOpen(event.cdevice.which);
|
||||||
SDL_Gamepad* gc = SDL_OpenGamepad(event.gdevice.which);
|
|
||||||
if (gc != nullptr) {
|
|
||||||
SDL_assert(!m_Gamepads.contains(gc));
|
|
||||||
m_Gamepads.append(gc);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
SDL_Gamepad* gc = SDL_GameControllerOpen(event.cdevice.which);
|
|
||||||
if (gc != nullptr) {
|
if (gc != nullptr) {
|
||||||
// SDL_CONTROLLERDEVICEADDED can be reported multiple times for the same
|
// SDL_CONTROLLERDEVICEADDED can be reported multiple times for the same
|
||||||
// gamepad in rare cases, because SDL2 doesn't fixup the device index in
|
// gamepad in rare cases, because SDL doesn't fixup the device index in
|
||||||
// the SDL_CONTROLLERDEVICEADDED event if an unopened gamepad disappears
|
// the SDL_CONTROLLERDEVICEADDED event if an unopened gamepad disappears
|
||||||
// before we've processed the add event.
|
// before we've processed the add event.
|
||||||
if (!m_Gamepads.contains(gc)) {
|
if (!m_Gamepads.contains(gc)) {
|
||||||
@@ -211,18 +210,17 @@ void SdlGamepadKeyNavigation::onPollingTimerFired()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// We already have this game controller open
|
// We already have this game controller open
|
||||||
SDL_CloseGamepad(gc);
|
SDL_GameControllerClose(gc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle analog sticks by polling
|
// Handle analog sticks by polling
|
||||||
for (auto gc : m_Gamepads) {
|
for (auto gc : std::as_const(m_Gamepads)) {
|
||||||
short leftX = SDL_GetGamepadAxis(gc, SDL_GAMEPAD_AXIS_LEFTX);
|
short leftX = SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_LEFTX);
|
||||||
short leftY = SDL_GetGamepadAxis(gc, SDL_GAMEPAD_AXIS_LEFTY);
|
short leftY = SDL_GameControllerGetAxis(gc, SDL_CONTROLLER_AXIS_LEFTY);
|
||||||
if (SDL_GetTicks() - m_LastAxisNavigationEventTime < AXIS_NAVIGATION_REPEAT_DELAY) {
|
if (SDL_GetTicks() - m_LastAxisNavigationEventTime < AXIS_NAVIGATION_REPEAT_DELAY) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
@@ -298,6 +296,12 @@ int SdlGamepadKeyNavigation::getConnectedGamepads()
|
|||||||
Q_ASSERT(m_Enabled);
|
Q_ASSERT(m_Enabled);
|
||||||
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
SDL_free(SDL_GetGamepads(&count));
|
int numJoysticks = SDL_NumJoysticks();
|
||||||
|
for (int i = 0; i < numJoysticks; i++) {
|
||||||
|
if (SDL_IsGameController(i)) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ private slots:
|
|||||||
private:
|
private:
|
||||||
StreamingPreferences* m_Prefs;
|
StreamingPreferences* m_Prefs;
|
||||||
QTimer* m_PollingTimer;
|
QTimer* m_PollingTimer;
|
||||||
QList<SDL_Gamepad*> m_Gamepads;
|
QList<SDL_GameController*> m_Gamepads;
|
||||||
bool m_Enabled;
|
bool m_Enabled;
|
||||||
bool m_UiNavMode;
|
bool m_UiNavMode;
|
||||||
bool m_FirstPoll;
|
bool m_FirstPoll;
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
+243
-228
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+268
-261
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+253
-246
File diff suppressed because it is too large
Load Diff
+240
-229
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+243
-236
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+244
-229
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+244
-237
File diff suppressed because it is too large
Load Diff
+243
-228
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+246
-231
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+480
-473
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+252
-245
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+268
-261
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+251
-244
File diff suppressed because it is too large
Load Diff
+243
-228
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+465
-454
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+415
-408
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+251
-244
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+313
-306
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+489
-474
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+244
-237
File diff suppressed because it is too large
Load Diff
Binary file not shown.
+372
-365
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user