Compare commits

...

78 Commits

Author SHA1 Message Date
Evan Husted
139e86915b Merge branch 'master' into master 2025-02-13 19:42:56 -06:00
Daniel Nylander
91f73a4891 Updated Swedish translation (#594) 2025-02-13 19:28:39 -06:00
Evan Husted
883d4d863a i18n: chore: [ci skip] missing closing single quote in translation (not JSON breaking) 2025-02-13 19:16:05 -06:00
Dennis
ca5de909a1 Updated German translation (#626) 2025-02-13 19:14:02 -06:00
Willians
5172567b08 Update Brazilian Translation PT-BR (#633)
I updated the Brazilian Portuguese translation, I added the texts that
were not translated.
2025-02-13 19:08:48 -06:00
heihei123456780
6fe4cee7c0 Update zh_CN translation (#650) 2025-02-13 18:16:12 -06:00
Vladimir Sokolov
ca8329d3da Merge branch 'master' into master 2025-02-13 19:38:29 +10:00
Evan Husted
8623452abc docs: new discord invite 2025-02-13 03:23:02 -06:00
Vladimir Sokolov
4f02b6a6de Merge branch 'master' into master 2025-02-13 18:11:07 +10:00
Evan Husted
17e8ae1d9a UI: More advanced customization for what happens when Ryujinx loses focus 2025-02-13 01:57:08 -06:00
Vova
1dedf4c8d5 fixed: when loading a game with a custom configuration via a shortcut or rebooting, when entering the settings menu, a menu for global configuration was displayed. 2025-02-13 16:34:03 +10:00
Vladimir Sokolov
3f185f580c Merge branch 'master' into master 2025-02-13 16:12:19 +10:00
Evan Husted
7591b07fce infra: Update workflows to ubuntu 24.04 (20.04 will be deprecated soon) 2025-02-12 16:21:03 -06:00
Evan Husted
89b4389ed2 docs: compat: [ci skip] forgot to update time for FUSER 2025-02-12 16:03:26 -06:00
Evan Husted
d9ee729199 docs: compat: FUSER is now Ingame 2025-02-12 15:21:19 -06:00
Vova
2dd787fb7b Changed: Hid.DisableInputWhenOutOfFocus only for global config 2025-02-12 16:00:37 +10:00
Vladimir Sokolov
42bd99193f Merge branch 'master' into master 2025-02-12 15:18:56 +10:00
Evan Husted
ba0cd13cff misc: chore: fix warning [ci skip] 2025-02-11 22:54:24 -06:00
Evan Husted
501b199e24 UI: setting: Disable Input when Out of Focus 2025-02-11 22:12:05 -06:00
Vladimir Sokolov
22299e69c6 Merge branch 'master' into master 2025-02-12 13:27:41 +10:00
Evan Husted
8aecccadb8 misc: chore: Format AvaHostUIHandler 2025-02-11 21:23:04 -06:00
Evan Husted
e23d610f49 UI: Change Update Available button text color to white 2025-02-11 21:18:05 -06:00
Evan Husted
f6822f7358 misc: chore: add direct error code tuple to DisplayErrorAppletDialog
for use when i find the list of error codes -> causes
2025-02-11 21:09:55 -06:00
Evan Husted
d3f84a1305 misc: chore: Rename UserSelectorDialog to ProfileSelectorDialog 2025-02-11 20:50:06 -06:00
Vladimir Sokolov
acc06a8430 Merge branch 'master' into master 2025-02-12 12:45:34 +10:00
Vova
5d061a1871 Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-12 12:44:46 +10:00
Vova
c21e63eb02 Removed irrelevant functions.
Renamed variables to correct names
2025-02-12 12:44:38 +10:00
Evan Husted
06d34a5992 misc: chore: optimize UserSelectorDialog closed handler 2025-02-11 20:36:11 -06:00
Evan Husted
e8e1dc6619 misc: chore: this isn't even bound to a XAML element, why was this an async void 2025-02-11 20:30:46 -06:00
Evan Husted
c5603d4c36 UI: Make about window text bigger and add a separator in the right side 2025-02-11 20:22:29 -06:00
Evan Husted
05b56730d6 docs: compat: remove title ID column from compat entries for homebrew 2025-02-11 20:22:09 -06:00
Evan Husted
43f7b000ca misc: chore: Optimize AboutWindowViewModel resource disposal 2025-02-11 20:08:38 -06:00
Vladimir Sokolov
32f9b3357a Merge branch 'master' into master 2025-02-12 11:27:19 +10:00
Evan Husted
ad89cf39b6 UI: Change the Discord invite to link to the rules channel
When next stable comes out the old one will be invalidated
2025-02-11 18:21:12 -06:00
Evan Husted
96c33a0b92 misc: chore: [ci skip] AppletMetadata: Use previously retrieved content path 2025-02-11 12:20:52 -06:00
Evan Husted
e0db55df46 UI: When starting games in fullscreen, change ExtendsContentIntoTitleBar to true. 2025-02-11 12:04:34 -06:00
Vladimir Sokolov
7f7055a037 Merge branch 'master' into master 2025-02-11 17:27:56 +10:00
Evan Husted
30fef8e96e misc: chore: Use UpdateCommand instance for the normal Check for Updates button 2025-02-11 00:47:39 -06:00
Evan Husted
9cb5f5689b UI: Show Update Available button in the system accent color 2025-02-11 00:21:58 -06:00
Vova
af13f4bb3b fix latest changes 2025-02-11 15:32:17 +10:00
Vladimir Sokolov
03692e6072 Merge branch 'master' into master 2025-02-11 15:24:48 +10:00
Evan Husted
a205ec374b UI: More advanced IsVisible binding for update available button (idk why it's always showing) 2025-02-10 22:42:57 -06:00
themantim486
aab9b58542 compat: "Rustler": Playable (#649) 2025-02-10 22:32:39 -06:00
Evan Husted
daa648dc40 UI: Correct visibility for new background update button 2025-02-10 22:25:04 -06:00
Evan Husted
1024aa8757 UI: Change the background updater notification to a persistent button on the status bar when not in a game 2025-02-10 22:13:58 -06:00
Vova
6f4930d547 Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-11 14:07:03 +10:00
Vova
1ca5407c22 Added autorestart of the emulator if it is necessary to change the graphic multi-thread.
Code cleaning
2025-02-11 14:06:31 +10:00
Vladimir Sokolov
8efceb3c28 Merge branch 'master' into master 2025-02-11 08:56:57 +10:00
Vova
0399af0ff9 Replace the "delete" button with "apply" during the game in the custom configuration. 2025-02-10 21:02:26 +10:00
Vladimir Sokolov
3e40532dee Merge branch 'master' into master 2025-02-10 15:36:15 +10:00
Vova
1d68546aa0 Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-10 15:26:21 +10:00
Vova
966860a464 Change: initialization of user configuration is now a separate function 2025-02-10 15:25:19 +10:00
Vladimir Sokolov
b9982c02e3 Merge branch 'master' into master 2025-02-09 16:51:11 +10:00
Vladimir Sokolov
a301a14074 Merge branch 'master' into master 2025-02-09 16:50:08 +10:00
Vova
7568dd4d7a Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-09 15:30:57 +10:00
Vova
4bbcec2076 Fixed a bug where control would not return after changing settings.
Added a function to update the gamelist status after the game is released
2025-02-09 15:30:33 +10:00
Vladimir Sokolov
57ac90a55b Merge branch 'master' into master 2025-02-09 15:20:03 +10:00
Vladimir Sokolov
cdc4557d82 Merge branch 'master' into master 2025-02-09 09:15:26 +10:00
Vova
ae16360685 Code cleaning 2025-02-08 22:27:38 +10:00
Vova
45b1794a45 Returned an erroneously modified Convert time string 2025-02-08 20:41:16 +10:00
Vova
39fbbb39e0 Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-08 20:19:23 +10:00
Vova
11a68a204f Fixed bug crash due to incorrect System.SystemTimeOffset.Value,
hotkeys should now also be read from the global configuration file
2025-02-08 20:16:38 +10:00
Vladimir Sokolov
e4e38c4bdd Merge branch 'master' into master 2025-02-08 18:30:35 +10:00
Vova
5f5c76107c Fixed a ban where a custom setting was mistakenly created when starting the game if it did not exist.
Now when starting the game, if a custom setting was created, the current game will be displayed in the settings window.
Code cleanup.
2025-02-08 18:29:31 +10:00
Vova
a92475b8fd Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-08 10:51:57 +10:00
Vova
4d5ae23b0b Added game name to user settings window 2025-02-08 10:51:39 +10:00
Vladimir Sokolov
4cb2170ad1 Merge branch 'master' into master 2025-02-08 10:49:55 +10:00
Vladimir Sokolov
fc01f7b58f Merge branch 'master' into master 2025-02-08 10:17:13 +10:00
Vova
fa463c51f8 code cleaning 2025-02-07 23:37:35 +10:00
Vova
3a8e6e3117 Added gamepad configuration for custom configuration 2025-02-07 21:48:51 +10:00
Vladimir Sokolov
7f4a161ca5 Merge branch 'master' into master 2025-02-07 20:48:10 +10:00
Vova
df1c7613e8 returned the deleted field 2025-02-06 23:24:52 +10:00
Vova
6d37d79cc7 Ops 2025-02-06 22:58:43 +10:00
Vova
afdfcc1720 fix 2025-02-06 22:54:45 +10:00
Vova
fe94224d01 keys returned to their place, minor fixes, local ones added 2025-02-06 22:51:08 +10:00
Vova
fbe1a7d966 Added custom setting function for each game 2025-02-06 04:54:00 -06:00
Vova
5dc7fb4495 Merge branch 'master' of https://github.com/Goodfeat/Ryujinx_alt 2025-02-06 20:34:31 +10:00
Vova
2640f083b6 Added custom setting function for each game 2025-02-06 20:34:15 +10:00
49 changed files with 1839 additions and 504 deletions

View File

@@ -29,7 +29,7 @@ env:
jobs:
tag:
name: Create tag
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- name: Get version info
id: version_info
@@ -202,7 +202,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

View File

@@ -18,7 +18,7 @@ env:
jobs:
tag:
name: Create tag
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- name: Get version info
id: version_info
@@ -183,7 +183,7 @@ jobs:
macos_release:
name: Release MacOS universal
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

View File

@@ -39,12 +39,12 @@
<p align="center">
Click below to join the Discord:
<br>
<a href="https://discord.gg/dHPrkBkkyA">
<a href="https://discord.gg/PEuzjrFXUA">
<img src="https://img.shields.io/discord/1294443224030511104?color=5865F2&label=Ryubing&logo=discord&logoColor=white" alt="Discord">
</a>
<br>
<br>
<img src="https://raw.githubusercontent.com/GreemDev/Ryujinx/refs/heads/master/docs/shell.png">
<img src="https://raw.githubusercontent.com/Ryubing/Ryujinx/refs/heads/master/docs/shell.png">
</p>
## Usage

View File

@@ -1249,7 +1249,7 @@
0100A6B00D4EC000,"Furwind",,playable,2021-02-19 19:44:08
0100ECE00C0C4000,"Fury Unleashed",crash;services,ingame,2020-10-18 11:52:40
010070000ED9E000,"Fury Unleashed Demo",,playable,2020-10-08 20:09:21
0100E1F013674000,"FUSER™",nvdec;UE4,playable,2022-10-17 20:58:32
0100E1F013674000,"FUSER™",nvdec;UE4;slow;gpu,ingame,2025-02-12 16:03:00
0100A7A015E4C000,"Fushigi no Gensokyo Lotus Labyrinth",Needs Update;audio;gpu;nvdec,ingame,2021-01-20 15:30:02
01003C300B274000,"Futari de! Nyanko Daisensou",,playable,2024-01-05 22:26:52
010055801134E000,"FUZE Player",online-broken;vulkan-backend-bug,ingame,2022-10-18 12:23:53
@@ -2063,7 +2063,7 @@
010002700C34C000,"Numbala",,playable,2020-05-11 12:01:07
010020500C8C8000,"Number Place 10000",gpu,menus,2021-11-24 09:14:23
010003701002C000,"Nurse Love Syndrome",,playable,2022-10-13 10:05:22
0000000000000000,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
,"nx-hbmenu",Needs Update;homebrew,boots,2024-04-06 22:05:32
,"nxquake2",services;crash;homebrew,nothing,2022-08-04 23:14:04
010049F00EC30000,"Nyan Cat: Lost in Space",online,playable,2021-06-12 13:22:03
01002E6014FC4000,"O---O",,playable,2022-10-29 12:12:14
@@ -2471,7 +2471,7 @@
0100AFE00DDAC000,"Royal Roads",,playable,2020-11-17 12:54:38
0100E2C00B414000,"RPG Maker MV",nvdec,playable,2021-01-05 20:12:01
01005CD015986000,"rRootage Reloaded",,playable,2022-08-05 23:20:18
0000000000000000,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
,"RSDKv5u",homebrew,ingame,2024-04-01 16:25:34
010009B00D33C000,"Rugby Challenge 4",slow;online-broken;UE4,playable,2022-10-06 12:45:53
01006EC00F2CC000,"RUINER",UE4,playable,2022-10-03 14:11:33
010074F00DE4A000,"Run the Fan",,playable,2021-02-27 13:36:28
@@ -2480,6 +2480,7 @@
010081C0191D8000,"Rune Factory 3 Special",,playable,2023-10-15 08:32:49
010051D00E3A4000,"Rune Factory 4 Special",32-bit;crash;nvdec,ingame,2023-05-06 08:49:17
010014D01216E000,"Rune Factory 5 (JP)",gpu,ingame,2021-06-01 12:00:36
010071E0145F8000,"Rustler",,playable,2025-02-10 20:17:12
0100E21013908000,"RWBY: Grimm Eclipse - Definitive Edition",online-broken,playable,2022-11-03 10:44:01
010012C0060F0000,"RXN -Raijin-",nvdec,playable,2021-01-10 16:05:43
0100B8B012ECA000,"S.N.I.P.E.R. - Hunter Scope",,playable,2021-04-19 15:58:09
@@ -2673,10 +2674,10 @@
01004F401BEBE000,"Song of Nunu: A League of Legends Story",,ingame,2024-07-12 18:53:44
0100E5400BF94000,"Songbird Symphony",,playable,2021-02-27 02:44:04
010031D00A604000,"Songbringer",,playable,2020-06-22 10:42:02
0000000000000000,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
0000000000000000,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
0000000000000000,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
0000000000000000,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
,"Sonic 1 (2013)",crash;homebrew,ingame,2024-04-06 18:31:20
,"Sonic 2 (2013)",crash;homebrew,ingame,2024-04-01 16:25:30
,"Sonic A.I.R",homebrew,ingame,2024-04-01 16:25:32
,"Sonic CD",crash;homebrew,ingame,2024-04-01 16:25:31
010040E0116B8000,"Sonic Colors: Ultimate",,playable,2022-11-12 21:24:26
01001270012B6000,"SONIC FORCES™",,playable,2024-07-28 13:11:21
01004AD014BF0000,"Sonic Frontiers",gpu;deadlock;amd-vendor-bug;intel-vendor-bug,ingame,2024-09-05 09:18:53
@@ -2693,7 +2694,7 @@
0100707011722000,"Space Elite Force",,playable,2020-11-27 15:21:05
010047B010260000,"Space Pioneer",,playable,2022-10-20 12:24:37
010010A009830000,"Space Ribbon",,playable,2022-08-15 17:17:10
0000000000000000,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
,"SpaceCadetPinball",homebrew,ingame,2024-04-18 19:30:04
0100D9B0041CE000,"Spacecats with Lasers",,playable,2022-08-15 17:22:44
010034800FB60000,"Spaceland",,playable,2020-11-01 14:31:56
010028D0045CE000,"Sparkle 2",,playable,2020-10-19 11:51:39
@@ -2838,7 +2839,7 @@
0100000000010000,"Super Mario Odyssey™",nvdec;intel-vendor-bug;mac-bug,playable,2024-08-25 01:32:34
010036B0034E4000,"Super Mario Party™",gpu;Needs Update;ldn-works,ingame,2024-06-21 05:10:16
0100BC0018138000,"Super Mario RPG™",gpu;audio;nvdec,ingame,2024-06-19 17:43:42
0000000000000000,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
,"Super Mario World",homebrew,boots,2024-06-13 01:40:31
010049900F546000,"Super Mario™ 3D All-Stars",services-horizon;slow;vulkan;amd-vendor-bug,ingame,2024-05-07 02:38:16
010028600EBDA000,"Super Mario™ 3D World + Bowsers Fury",ldn-works,playable,2024-07-31 10:45:37
01004F8006A78000,"Super Meat Boy",services,playable,2020-04-02 23:10:07
1 title_id game_name labels status last_updated
1249 0100A6B00D4EC000 Furwind playable 2021-02-19 19:44:08
1250 0100ECE00C0C4000 Fury Unleashed crash;services ingame 2020-10-18 11:52:40
1251 010070000ED9E000 Fury Unleashed Demo playable 2020-10-08 20:09:21
1252 0100E1F013674000 FUSER™ nvdec;UE4 nvdec;UE4;slow;gpu playable ingame 2022-10-17 20:58:32 2025-02-12 16:03:00
1253 0100A7A015E4C000 Fushigi no Gensokyo Lotus Labyrinth Needs Update;audio;gpu;nvdec ingame 2021-01-20 15:30:02
1254 01003C300B274000 Futari de! Nyanko Daisensou playable 2024-01-05 22:26:52
1255 010055801134E000 FUZE Player online-broken;vulkan-backend-bug ingame 2022-10-18 12:23:53
2063 010002700C34C000 Numbala playable 2020-05-11 12:01:07
2064 010020500C8C8000 Number Place 10000 gpu menus 2021-11-24 09:14:23
2065 010003701002C000 Nurse Love Syndrome playable 2022-10-13 10:05:22
2066 0000000000000000 nx-hbmenu Needs Update;homebrew boots 2024-04-06 22:05:32
2067 nxquake2 services;crash;homebrew nothing 2022-08-04 23:14:04
2068 010049F00EC30000 Nyan Cat: Lost in Space online playable 2021-06-12 13:22:03
2069 01002E6014FC4000 O---O playable 2022-10-29 12:12:14
2471 0100AFE00DDAC000 Royal Roads playable 2020-11-17 12:54:38
2472 0100E2C00B414000 RPG Maker MV nvdec playable 2021-01-05 20:12:01
2473 01005CD015986000 rRootage Reloaded playable 2022-08-05 23:20:18
2474 0000000000000000 RSDKv5u homebrew ingame 2024-04-01 16:25:34
2475 010009B00D33C000 Rugby Challenge 4 slow;online-broken;UE4 playable 2022-10-06 12:45:53
2476 01006EC00F2CC000 RUINER UE4 playable 2022-10-03 14:11:33
2477 010074F00DE4A000 Run the Fan playable 2021-02-27 13:36:28
2480 010081C0191D8000 Rune Factory 3 Special playable 2023-10-15 08:32:49
2481 010051D00E3A4000 Rune Factory 4 Special 32-bit;crash;nvdec ingame 2023-05-06 08:49:17
2482 010014D01216E000 Rune Factory 5 (JP) gpu ingame 2021-06-01 12:00:36
2483 010071E0145F8000 Rustler playable 2025-02-10 20:17:12
2484 0100E21013908000 RWBY: Grimm Eclipse - Definitive Edition online-broken playable 2022-11-03 10:44:01
2485 010012C0060F0000 RXN -Raijin- nvdec playable 2021-01-10 16:05:43
2486 0100B8B012ECA000 S.N.I.P.E.R. - Hunter Scope playable 2021-04-19 15:58:09
2674 01004F401BEBE000 Song of Nunu: A League of Legends Story ingame 2024-07-12 18:53:44
2675 0100E5400BF94000 Songbird Symphony playable 2021-02-27 02:44:04
2676 010031D00A604000 Songbringer playable 2020-06-22 10:42:02
2677 0000000000000000 Sonic 1 (2013) crash;homebrew ingame 2024-04-06 18:31:20
2678 0000000000000000 Sonic 2 (2013) crash;homebrew ingame 2024-04-01 16:25:30
2679 0000000000000000 Sonic A.I.R homebrew ingame 2024-04-01 16:25:32
2680 0000000000000000 Sonic CD crash;homebrew ingame 2024-04-01 16:25:31
2681 010040E0116B8000 Sonic Colors: Ultimate playable 2022-11-12 21:24:26
2682 01001270012B6000 SONIC FORCES™ playable 2024-07-28 13:11:21
2683 01004AD014BF0000 Sonic Frontiers gpu;deadlock;amd-vendor-bug;intel-vendor-bug ingame 2024-09-05 09:18:53
2694 0100707011722000 Space Elite Force playable 2020-11-27 15:21:05
2695 010047B010260000 Space Pioneer playable 2022-10-20 12:24:37
2696 010010A009830000 Space Ribbon playable 2022-08-15 17:17:10
2697 0000000000000000 SpaceCadetPinball homebrew ingame 2024-04-18 19:30:04
2698 0100D9B0041CE000 Spacecats with Lasers playable 2022-08-15 17:22:44
2699 010034800FB60000 Spaceland playable 2020-11-01 14:31:56
2700 010028D0045CE000 Sparkle 2 playable 2020-10-19 11:51:39
2839 0100000000010000 Super Mario Odyssey™ nvdec;intel-vendor-bug;mac-bug playable 2024-08-25 01:32:34
2840 010036B0034E4000 Super Mario Party™ gpu;Needs Update;ldn-works ingame 2024-06-21 05:10:16
2841 0100BC0018138000 Super Mario RPG™ gpu;audio;nvdec ingame 2024-06-19 17:43:42
2842 0000000000000000 Super Mario World homebrew boots 2024-06-13 01:40:31
2843 010049900F546000 Super Mario™ 3D All-Stars services-horizon;slow;vulkan;amd-vendor-bug ingame 2024-05-07 02:38:16
2844 010028600EBDA000 Super Mario™ 3D World + Bowser’s Fury ldn-works playable 2024-07-31 10:45:37
2845 01004F8006A78000 Super Meat Boy services playable 2020-04-02 23:10:07

View File

@@ -158,13 +158,15 @@ namespace Ryujinx.HLE.HOS.Applets.Error
string[] buttons = GetButtonsText(module, description, "DlgBtn");
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons);
(uint Module, uint Description) errorCodeTuple = (module, uint.Parse(description.ToString("0000")));
bool showDetails = _horizon.Device.UIHandler.DisplayErrorAppletDialog($"Error Code: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
if (showDetails)
{
message = GetMessageText(module, description, "FlvMsg");
buttons = GetButtonsText(module, description, "FlvBtn");
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons);
_horizon.Device.UIHandler.DisplayErrorAppletDialog($"Details: {module}-{description:0000}", "\n" + message, buttons, errorCodeTuple);
}
}

View File

@@ -45,10 +45,12 @@ namespace Ryujinx.HLE.UI
/// <param name="value">The value associated to the <paramref name="kind"/>.</param>
void ExecuteProgram(Switch device, ProgramSpecifyKind kind, ulong value);
/// <summary>
/// Displays a Message Dialog box specific to Error Applet and blocks until it is closed.
/// </summary>
/// <returns>False when OK is pressed, True when another button (Details) is pressed.</returns>
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText);
// ReSharper disable once UnusedParameter.Global
bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null);
/// <summary>
/// Creates a handler to process keyboard inputs into text strings.

View File

@@ -185,6 +185,15 @@ namespace Ryujinx.Input.HLE
}
}
public bool InputUpdatesBlocked
{
get
{
lock (_lock)
return _blockInputUpdates;
}
}
public void BlockInputUpdates()
{
lock (_lock)

View File

@@ -517,7 +517,7 @@ namespace Ryujinx.Ava
Device?.System.ChangeDockedModeState(e.NewValue);
}
private void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
public void UpdateAudioVolumeState(object sender, ReactiveEventArgs<float> e)
{
Device?.SetVolume(e.NewValue);
@@ -582,6 +582,13 @@ namespace Ryujinx.Ava
Rainbow.Disable();
Rainbow.Reset();
// Reload settings when the game is turned off
// (resets custom settings if there were any)
Program.ReloadConfig();
//Updates the gameList (changes the status of the user setting if it was added or removed during the game)
RyujinxApp.MainWindow.GameListUpdate();
_isStopped = true;
Stop();
}
@@ -1041,6 +1048,7 @@ namespace Ryujinx.Ava
if (_viewModel.StartGamesInFullscreen)
{
_viewModel.WindowState = WindowState.FullScreen;
_viewModel.Window.TitleBar.ExtendsContentIntoTitleBar = true;
}
if (_viewModel.WindowState is WindowState.FullScreen || _viewModel.StartGamesWithoutUI)

View File

@@ -1,7 +1,8 @@
<Styles
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:windowing="clr-namespace:FluentAvalonia.UI.Windowing;assembly=FluentAvalonia">
<Design.PreviewWith>
<Border Height="2000"
@@ -30,7 +31,8 @@
<Button
Name="btnRem"
HorizontalAlignment="Right"
Content="Add" />
Content="Add"
Classes="red"/>
<TextBox
Width="100"
VerticalAlignment="Center"
@@ -41,7 +43,11 @@
</StackPanel>
</Grid>
<ui:NumberBox Value="1" />
<MenuItem
Header="123 0000"
ToolTip.Tip="What this"/>
</StackPanel>
</Border>
</Design.PreviewWith>
<Style Selector="DropDownButton">
@@ -373,6 +379,16 @@
<Setter Property="Background"
Value="{DynamicResource AppListHoverBackgroundColor}" />
</Style>
<Style Selector="Button.red /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="red"/>
<Setter Property="Foreground" Value="White"/>
</Style>
<Style Selector="Button.red:pointerover /template/ ContentPresenter">
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Background" Value="{DynamicResource WarningBackgroundColor}" />
<Setter Property="Foreground" Value="White"/>
</Style>
<Styles.Resources>
<SolidColorBrush x:Key="ThemeAccentColorBrush"
Color="{DynamicResource SystemAccentColor}" />

View File

@@ -1,4 +1,4 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">
@@ -12,11 +12,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Light">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -29,11 +31,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#C1C1C1</Color>
<Color x:Key="AppListBackgroundColor">#b3ffffff</Color>
<Color x:Key="AppListHoverBackgroundColor">#80cccccc</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0000000</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#13c3a4</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#800080</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
<SolidColorBrush x:Key="DataGridSelectionBackgroundBrush"
@@ -46,11 +50,13 @@
<Color x:Key="MenuFlyoutPresenterBorderColor">#3D3D3D</Color>
<Color x:Key="AppListBackgroundColor">#0FFFFFFF</Color>
<Color x:Key="AppListHoverBackgroundColor">#1EFFFFFF</Color>
<Color x:Key="WarningBackgroundColor">#FF6347</Color>
<Color x:Key="SecondaryTextColor">#A0FFFFFF</Color>
<Color x:Key="FavoriteApplicationIconColor">#fffcd12a</Color>
<Color x:Key="Switch">#FF2EEAC9</Color>
<Color x:Key="Unbounded">#FFFF4554</Color>
<Color x:Key="Custom">#6483F5</Color>
<Color x:Key="Warning">#FFA500</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>

File diff suppressed because it is too large Load Diff

View File

@@ -54,6 +54,7 @@ namespace Ryujinx.Ava.Common.Locale
SetDynamicValues(LocaleKeys.RyujinxInfo, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxConfirm, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxUpdater, RyujinxApp.FullAppName);
SetDynamicValues(LocaleKeys.RyujinxRebooter, RyujinxApp.FullAppName);
}
public string this[LocaleKeys key]

View File

@@ -1,14 +0,0 @@
using System;
namespace Ryujinx.Ava.Common
{
public static class ThemeManager
{
public static event Action ThemeChanged;
public static void OnThemeChanged()
{
ThemeChanged?.Invoke();
}
}
}

View File

@@ -513,7 +513,7 @@ namespace Ryujinx.Headless
Exit();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText)
public bool DisplayErrorAppletDialog(string title, string message, string[] buttonsText, (uint Module, uint Description)? errorCode = null)
{
SDL_MessageBoxData data = new()
{
@@ -521,7 +521,7 @@ namespace Ryujinx.Headless
message = message,
buttons = new SDL_MessageBoxButtonData[buttonsText.Length],
numbuttons = buttonsText.Length,
window = WindowHandle,
window = WindowHandle
};
for (int i = 0; i < buttonsText.Length; i++)

View File

@@ -32,8 +32,10 @@ namespace Ryujinx.Ava
public static double DesktopScaleFactor { get; set; } = 1.0;
public static string Version { get; private set; }
public static string ConfigurationPath { get; private set; }
public static string GlobalConfigurationPath { get; private set; }
public static bool PreviewerDetached { get; private set; }
public static bool UseHardwareAcceleration { get; private set; }
public static string BackendThreadingArg { get; private set; }
[LibraryImport("user32.dll", SetLastError = true)]
public static partial int MessageBoxA(nint hWnd, [MarshalAs(UnmanagedType.LPStr)] string text, [MarshalAs(UnmanagedType.LPStr)] string caption, uint type);
@@ -155,11 +157,43 @@ namespace Ryujinx.Ava
}
}
public static bool FindGameConfig(string gameDir)
{
if (File.Exists(gameDir))
{
return true;
}
return false;
}
public static string GetDirGameUserConfig(string gameId, bool rememberGlobalDir = false, bool changeFolderForGame = false)
{
string gameDir = Path.Combine(AppDataManager.GamesDirPath, gameId, ReleaseInformation.ConfigName);
// Should load with the game if there is a custom setting for the game
if (rememberGlobalDir)
{
GlobalConfigurationPath = ConfigurationPath;
}
if (changeFolderForGame)
{
ConfigurationPath = gameDir;
}
return gameDir;
}
public static void ReloadConfig()
{
//It is necessary that when a user setting appears, the global setting remains available
GlobalConfigurationPath = null;
string localConfigurationPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ReleaseInformation.ConfigName);
string appDataConfigurationPath = Path.Combine(AppDataManager.BaseDirPath, ReleaseInformation.ConfigName);
// Now load the configuration as the other subsystems are now registered
if (File.Exists(localConfigurationPath))
{
@@ -217,6 +251,11 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.Graphics.BackendThreading
};
if (CommandLineState.OverrideBackendThreadingAfterReboot is not null)
{
BackendThreadingArg = CommandLineState.OverrideBackendThreadingAfterReboot;
}
// Check if docked mode was overriden.
if (CommandLineState.OverrideDockedMode.HasValue)
ConfigurationState.Instance.System.EnableDockedMode.Value = CommandLineState.OverrideDockedMode.Value;
@@ -232,6 +271,33 @@ namespace Ryujinx.Ava
_ => ConfigurationState.Instance.HideCursor,
};
// Check if memoryManagerMode was overridden.
if (CommandLineState.OverrideMemoryManagerMode is not null)
if (Enum.TryParse(CommandLineState.OverrideMemoryManagerMode, true, out MemoryManagerMode result))
{
ConfigurationState.Instance.System.MemoryManagerMode.Value = result;
}
// Check if PPTC was overridden.
if (CommandLineState.OverridePPTC is not null)
if (Enum.TryParse(CommandLineState.OverridePPTC, true, out bool result))
{
ConfigurationState.Instance.System.EnablePtc.Value = result;
}
// Check if region was overridden.
if (CommandLineState.OverrideSystemRegion is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemRegion, true, out Ryujinx.HLE.HOS.SystemState.RegionCode result))
{
ConfigurationState.Instance.System.Region.Value = (Utilities.Configuration.System.Region)result;
}
//Check if language was overridden.
if (CommandLineState.OverrideSystemLanguage is not null)
if (Enum.TryParse(CommandLineState.OverrideSystemLanguage, true, out Ryujinx.HLE.HOS.SystemState.SystemLanguage result))
{
ConfigurationState.Instance.System.Language.Value = (Utilities.Configuration.System.Language)result;
}
// Check if hardware-acceleration was overridden.
if (CommandLineState.OverrideHardwareAcceleration != null)

95
src/Ryujinx/Rebooter.cs Normal file
View File

@@ -0,0 +1,95 @@
using FluentAvalonia.UI.Controls;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.Utilities;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace Ryujinx.Ava
{
internal static class Rebooter
{
private static readonly string _updateDir = Path.Combine(Path.GetTempPath(), "Ryujinx", "update");
public static void RebootAppWithGame(string gamePath, List<string> args)
{
_ = Reboot(gamePath, args);
}
private static async Task Reboot(string gamePath, List<string> args)
{
bool shouldRestart = true;
TaskDialog taskDialog = new()
{
Header = LocaleManager.Instance[LocaleKeys.RyujinxRebooter],
SubHeader = LocaleManager.Instance[LocaleKeys.DialogRebooterMessage],
IconSource = new SymbolIconSource { Symbol = Symbol.Games },
XamlRoot = RyujinxApp.MainWindow,
};
if (shouldRestart)
{
List<string> arguments = CommandLineState.Arguments.ToList();
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
// On macOS we perform the update at relaunch.
if (OperatingSystem.IsMacOS())
{
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
string newBundlePath = Path.Combine(_updateDir, "Ryujinx.app");
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
string currentPid = Environment.ProcessId.ToString();
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
Process.Start("/bin/bash", arguments);
}
else
{
var dialogTask = taskDialog.ShowAsync(true);
await Task.Delay(500);
// Find the process name.
string ryuName = Path.GetFileName(Environment.ProcessPath) ?? string.Empty;
// Some operating systems can see the renamed executable, so strip off the .ryuold if found.
if (ryuName.EndsWith(".ryuold"))
{
ryuName = ryuName[..^7];
}
// Fallback if the executable could not be found.
if (ryuName.Length == 0 || !Path.Exists(Path.Combine(executableDirectory, ryuName)))
{
ryuName = OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx";
}
ProcessStartInfo processStart = new(ryuName)
{
UseShellExecute = true,
WorkingDirectory = executableDirectory,
};
foreach (var arg in args)
{
processStart.ArgumentList.Add(arg);
}
processStart.ArgumentList.Add(gamePath);
Process.Start(processStart);
}
Environment.Exit(0);
}
}
}
}

View File

@@ -22,6 +22,8 @@ namespace Ryujinx.Ava
{
public class RyujinxApp : Application
{
public static event Action ThemeChanged;
internal static string FormatTitle(LocaleKeys? windowTitleKey = null, bool includeVersion = true)
=> windowTitleKey is null
? $"{FullAppName}{(includeVersion ? $" {Program.Version}" : string.Empty)}"
@@ -112,7 +114,7 @@ namespace Ryujinx.Ava
baseStyle = ConfigurationState.Instance.UI.BaseStyle;
}
ThemeManager.OnThemeChanged();
ThemeChanged?.Invoke();
RequestedThemeVariant = baseStyle switch
{

View File

@@ -75,31 +75,32 @@ namespace Ryujinx.Ava.UI.Applet
bool opened = false;
UserResult response = await ContentDialogHelper.ShowDeferredContentDialog(_parent,
title,
message,
string.Empty,
LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
string.Empty,
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
(int)Symbol.Important,
deferEvent,
async window =>
{
if (opened)
{
return;
}
title,
message,
string.Empty,
LocaleManager.Instance[LocaleKeys.DialogOpenSettingsWindowLabel],
string.Empty,
LocaleManager.Instance[LocaleKeys.SettingsButtonClose],
(int)Symbol.Important,
deferEvent,
async window =>
{
if (opened)
{
return;
}
opened = true;
opened = true;
_parent.SettingsWindow = new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
_parent.SettingsWindow =
new SettingsWindow(_parent.VirtualFileSystem, _parent.ContentManager);
await _parent.SettingsWindow.ShowDialog(window);
await _parent.SettingsWindow.ShowDialog(window);
_parent.SettingsWindow = null;
_parent.SettingsWindow = null;
opened = false;
});
opened = false;
});
if (response == UserResult.Ok)
{
@@ -110,7 +111,9 @@ namespace Ryujinx.Ava.UI.Applet
}
catch (Exception ex)
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogMessageDialogErrorExceptionMessage, ex));
dialogCloseEvent.Set();
}
@@ -134,7 +137,9 @@ namespace Ryujinx.Ava.UI.Applet
try
{
_parent.ViewModel.AppHost.NpadManager.BlockInputUpdates();
(UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard], args);
(UserResult result, string userInput) =
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.SoftwareKeyboard],
args);
if (result == UserResult.Ok)
{
@@ -146,7 +151,9 @@ namespace Ryujinx.Ava.UI.Applet
{
error = true;
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogSoftwareKeyboardErrorExceptionMessage, ex));
}
finally
{
@@ -177,7 +184,8 @@ namespace Ryujinx.Ava.UI.Applet
args.InitialText = "Ryujinx";
args.StringLengthMin = 1;
args.StringLengthMax = 25;
(UserResult result, string userInput) = await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
(UserResult result, string userInput) =
await SwkbdAppletDialog.ShowInputDialog(LocaleManager.Instance[LocaleKeys.CabinetDialog], args);
if (result == UserResult.Ok)
{
inputText = userInput;
@@ -201,11 +209,13 @@ namespace Ryujinx.Ava.UI.Applet
Dispatcher.UIThread.InvokeAsync(async () =>
{
dialogCloseEvent.Set();
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
string.Empty,
LocaleManager.Instance[LocaleKeys.CabinetTitle]);
await ContentDialogHelper.CreateInfoDialog(
LocaleManager.Instance[LocaleKeys.CabinetScanDialog],
string.Empty,
LocaleManager.Instance[LocaleKeys.InputDialogOk],
string.Empty,
LocaleManager.Instance[LocaleKeys.CabinetTitle]
);
});
dialogCloseEvent.WaitOne();
}
@@ -217,7 +227,8 @@ namespace Ryujinx.Ava.UI.Applet
_parent.ViewModel.AppHost?.Stop();
}
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons)
public bool DisplayErrorAppletDialog(string title, string message, string[] buttons,
(uint Module, uint Description)? errorCode = null)
{
ManualResetEvent dialogCloseEvent = new(false);
@@ -229,9 +240,7 @@ namespace Ryujinx.Ava.UI.Applet
{
ErrorAppletWindow msgDialog = new(_parent, buttons, message)
{
Title = title,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
Width = 400
Title = title, WindowStartupLocation = WindowStartupLocation.CenterScreen, Width = 400
};
object response = await msgDialog.Run();
@@ -249,7 +258,9 @@ namespace Ryujinx.Ava.UI.Applet
{
dialogCloseEvent.Set();
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
await ContentDialogHelper.CreateErrorDialog(
LocaleManager.Instance.UpdateAndGetDynamicValue(
LocaleKeys.DialogErrorAppletErrorExceptionMessage, ex));
}
});
@@ -259,38 +270,36 @@ namespace Ryujinx.Ava.UI.Applet
}
public IDynamicTextInputHandler CreateDynamicTextInputHandler() => new AvaloniaDynamicTextInputHandler(_parent);
public UserProfile ShowPlayerSelectDialog()
{
UserId selected = UserId.Null;
byte[] defaultGuestImage = EmbeddedResources.Read("Ryujinx.HLE/HOS/Services/Account/Acc/GuestUserImage.jpg");
UserProfile guest = new(new UserId("00000000000000000000000000000080"), "Guest", defaultGuestImage);
ManualResetEvent dialogCloseEvent = new(false);
Dispatcher.UIThread.InvokeAsync(async () =>
{
ObservableCollection<BaseModel> profiles = [];
NavigationDialogHost nav = new();
_parent.AccountManager.GetAllUsers()
.OrderBy(x => x.Name)
.ForEach(profile => profiles.Add(new Models.UserProfile(profile, nav)));
profiles.Add(new Models.UserProfile(guest, nav));
UserSelectorDialogViewModel viewModel = new()
ProfileSelectorDialogViewModel viewModel = new()
{
Profiles = profiles,
SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
Profiles = profiles, SelectedUserId = _parent.AccountManager.LastOpenedUser.UserId
};
UserSelectorDialog content = new(viewModel);
(selected, _) = await UserSelectorDialog.ShowInputDialog(content);
(selected, _) = await ProfileSelectorDialog.ShowInputDialog(viewModel);
dialogCloseEvent.Set();
});
dialogCloseEvent.WaitOne();
UserProfile profile = _parent.AccountManager.LastOpenedUser;
if (selected == guest.UserId)
{
@@ -311,6 +320,7 @@ namespace Ryujinx.Ava.UI.Applet
}
}
}
return profile;
}
}

View File

@@ -1,5 +1,5 @@
<UserControl
x:Class="Ryujinx.Ava.UI.Applet.UserSelectorDialog"
x:Class="Ryujinx.Ava.UI.Applet.ProfileSelectorDialog"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -12,9 +12,9 @@
d:DesignWidth="800"
mc:Ignorable="d"
Focusable="True"
x:DataType="viewModels:UserSelectorDialogViewModel">
x:DataType="viewModels:ProfileSelectorDialogViewModel">
<Design.DataContext>
<viewModels:UserSelectorDialogViewModel />
<viewModels:ProfileSelectorDialogViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">

View File

@@ -16,15 +16,15 @@ using UserProfileSft = Ryujinx.HLE.HOS.Services.Account.Acc.UserProfile;
namespace Ryujinx.Ava.UI.Applet
{
public partial class UserSelectorDialog : UserControl, INotifyPropertyChanged
public partial class ProfileSelectorDialog : UserControl
{
public UserSelectorDialogViewModel ViewModel { get; set; }
public ProfileSelectorDialogViewModel ViewModel { get; set; }
public UserSelectorDialog(UserSelectorDialogViewModel viewModel)
public ProfileSelectorDialog(ProfileSelectorDialogViewModel viewModel)
{
DataContext = ViewModel = viewModel;
InitializeComponent();
ViewModel = viewModel;
DataContext = ViewModel;
}
private void Grid_PointerEntered(object sender, PointerEventArgs e)
@@ -54,7 +54,7 @@ namespace Ryujinx.Ava.UI.Applet
if (ViewModel.Profiles[selectedIndex] is UserProfile userProfile)
{
ViewModel.SelectedUserId = userProfile.UserId;
Logger.Info?.Print(LogClass.UI, $"Selected user: {userProfile.UserId}");
Logger.Info?.Print(LogClass.UI, $"Selected: {userProfile.UserId}", "ProfileSelector");
ObservableCollection<BaseModel> newProfiles = [];
@@ -79,7 +79,7 @@ namespace Ryujinx.Ava.UI.Applet
}
}
public static async Task<(UserId Id, bool Result)> ShowInputDialog(UserSelectorDialog content)
public static async Task<(UserId Id, bool Result)> ShowInputDialog(ProfileSelectorDialogViewModel viewModel)
{
ContentDialog contentDialog = new()
{
@@ -87,22 +87,25 @@ namespace Ryujinx.Ava.UI.Applet
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.Continue],
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.Cancel],
Content = content,
Content = new ProfileSelectorDialog(viewModel),
Padding = new Thickness(0)
};
UserId result = UserId.Null;
bool input = false;
contentDialog.Closed += Handler;
await ContentDialogHelper.ShowAsync(contentDialog);
return (result, input);
void Handler(ContentDialog sender, ContentDialogClosedEventArgs eventArgs)
{
if (eventArgs.Result == ContentDialogResult.Primary)
{
if (contentDialog.Content is UserSelectorDialog view)
{
result = view.ViewModel.SelectedUserId;
input = true;
}
result = viewModel.SelectedUserId;
input = true;
}
else
{
@@ -110,12 +113,6 @@ namespace Ryujinx.Ava.UI.Applet
input = false;
}
}
contentDialog.Closed += Handler;
await ContentDialogHelper.ShowAsync(contentDialog);
return (result, input);
}
}
}

View File

@@ -19,6 +19,11 @@
Header="{ext:Locale GameListContextMenuCreateShortcut}"
Icon="{ext:Icon fa-solid fa-bookmark}"
ToolTip.Tip="{OnPlatform Default={ext:Locale GameListContextMenuCreateShortcutToolTip}, macOS={ext:Locale GameListContextMenuCreateShortcutToolTipMacOS}}" />
<MenuItem
Click="EditGameConfiguration_Click"
Header="{ext:Locale GameListContextMenuEditGameConfiguration}"
Icon="{ext:Icon fa-solid fa-gear}"
ToolTip.Tip="{ext:Locale EditGameConfigurationToolTip}" />
<MenuItem
IsVisible="{Binding HasCompatibilityEntry}"
Click="OpenApplicationCompatibility_Click"

View File

@@ -386,13 +386,26 @@ namespace Ryujinx.Ava.UI.Controls
viewModel.SelectedApplication.Icon
);
}
public async void EditGameConfiguration_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
{
await new GameSpecificSettingsWindow(viewModel).ShowDialog((Window)viewModel.TopLevel);
//just checking for file presence
viewModel.SelectedApplication.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(viewModel.SelectedApplication.IdString,false,false));
viewModel.RefreshView();
}
}
public async void OpenApplicationCompatibility_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })
await CompatibilityList.Show(viewModel.SelectedApplication.IdString);
}
public async void OpenApplicationData_Click(object sender, RoutedEventArgs args)
{
if (sender is MenuItem { DataContext: MainWindowViewModel { SelectedApplication: not null } viewModel })

View File

@@ -7,6 +7,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
@@ -73,12 +74,18 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsVisible="{Binding $parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
Text="{Binding Name}"
TextAlignment="Center"
TextWrapping="Wrap" />
<TextBlock
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale UserConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
</Panel>
</Grid>
</Border>
@@ -86,10 +93,28 @@
Margin="5,5,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
FontSize="16"
FontSize="18"
Foreground="{DynamicResource FavoriteApplicationIconColor}"
IsVisible="{Binding Favorite}"
Symbol="StarFilled" />
<Grid IsVisible="{Binding !$parent[UserControl].((viewModels:MainWindowViewModel)DataContext).ShowNames}">
<Border
Margin="15,35,5,15"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Width="90"
Height="20"
CornerRadius="4"
IsVisible="{Binding HasIndependentConfiguration}"
Background="{DynamicResource ThemeContentBackgroundColor}">
<TextBlock
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{ext:Locale UserConfigurationHeader}"
TextAlignment="Center"
TextWrapping="Wrap" />
</Border>
</Grid>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>

View File

@@ -6,6 +6,7 @@
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
d:DesignHeight="450"
d:DesignWidth="800"
Focusable="True"
@@ -156,6 +157,13 @@
Text="{Binding Converter={x:Static helpers:MultiplayerInfoConverter.Instance}}"
TextAlignment="Start"
TextWrapping="Wrap"/>
<TextBlock
HorizontalAlignment="Stretch"
IsVisible="{Binding HasIndependentConfiguration}"
Text="{ext:Locale UserConfigurationHeader}"
TextAlignment="Start"
TextWrapping="Wrap"
Foreground="{DynamicResource Warning}" />
</StackPanel>
<StackPanel
Grid.Column="4"

View File

@@ -2,6 +2,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Styling;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Gommon;
using Ryujinx.Ava.Common;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.Utilities.Configuration;
@@ -24,30 +25,35 @@ namespace Ryujinx.Ava.UI.ViewModels
Version = RyujinxApp.FullAppName + "\n" + Program.Version;
UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value);
ThemeManager.ThemeChanged += ThemeManager_ThemeChanged;
RyujinxApp.ThemeChanged += Ryujinx_ThemeChanged;
}
private void ThemeManager_ThemeChanged()
private void Ryujinx_ThemeChanged()
{
Dispatcher.UIThread.Post(() => UpdateLogoTheme(ConfigurationState.Instance.UI.BaseStyle.Value));
}
private const string LogoPathFormat = "resm:Ryujinx.Assets.UIImages.Logo_{0}_{1}.png?assembly=Ryujinx";
private void UpdateLogoTheme(string theme)
{
bool isDarkTheme = theme == "Dark" || (theme == "Auto" && RyujinxApp.DetectSystemTheme() == ThemeVariant.Dark);
string themeName = isDarkTheme ? "Dark" : "Light";
string basePath = "resm:Ryujinx.Assets.UIImages.";
string themeSuffix = isDarkTheme ? "Dark.png" : "Light.png";
GithubLogo = LoadBitmap($"{basePath}Logo_GitHub_{themeSuffix}?assembly=Ryujinx");
DiscordLogo = LoadBitmap($"{basePath}Logo_Discord_{themeSuffix}?assembly=Ryujinx");
GithubLogo = LoadBitmap(LogoPathFormat.Format("GitHub", themeName));
DiscordLogo = LoadBitmap(LogoPathFormat.Format("Discord", themeName));
}
private static Bitmap LoadBitmap(string uri) => new(Avalonia.Platform.AssetLoader.Open(new Uri(uri)));
public void Dispose()
{
ThemeManager.ThemeChanged -= ThemeManager_ThemeChanged;
RyujinxApp.ThemeChanged -= Ryujinx_ThemeChanged;
GithubLogo.Dispose();
DiscordLogo.Dispose();
GC.SuppressFinalize(this);
}
}

View File

@@ -264,7 +264,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Logger.Error?.Print(LogClass.Application, $"Couldn't get valid amiibo data: {exception}");
// Neither local or remote files are valid JSON, close window.
ShowInfoDialog();
await ShowInfoDialog();
Close();
}
else if (!remoteIsValid)
@@ -273,7 +273,7 @@ namespace Ryujinx.Ava.UI.ViewModels
// Only the local file is valid, the local one should be used
// but the user should be warned.
ShowInfoDialog();
await ShowInfoDialog();
}
}
@@ -525,7 +525,7 @@ namespace Ryujinx.Ava.UI.ViewModels
AmiiboImage = bitmap;
}
private static async void ShowInfoDialog()
private static async Task ShowInfoDialog()
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogAmiiboApiTitle],
LocaleManager.Instance[LocaleKeys.DialogAmiiboApiConnectErrorMessage],

View File

@@ -7,6 +7,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
@@ -104,6 +105,13 @@ namespace Ryujinx.Ava.UI.ViewModels
[ObservableProperty] private bool _isSubMenuOpen;
[ObservableProperty] private ApplicationContextMenu _listAppContextMenu;
[ObservableProperty] private ApplicationContextMenu _gridAppContextMenu;
[ObservableProperty] private bool _updateAvailable;
public static AsyncRelayCommand UpdateCommand { get; } = Commands.Create(async () =>
{
if (Updater.CanUpdate(true))
await Updater.BeginUpdateAsync(true);
});
private bool _showLoadProgress;
private bool _isGameRunning;
@@ -347,6 +355,11 @@ namespace Ryujinx.Ava.UI.ViewModels
_ => null,
};
}
set
{
ListSelectedApplication = value;
GridSelectedApplication = value;
}
}
public bool HasCompatibilityEntry => SelectedApplication.HasPlayabilityInfo;
@@ -1076,7 +1089,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.WaitOne();
AppHost?.Start();
AppHost?.DisposeContext();
}
@@ -1523,8 +1536,50 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public bool InitializeUserConfig(ApplicationData application)
{
// Code where conditions will be met before loading the user configuration (Global Config)
BackendThreading backendThreadingValue = ConfigurationState.Instance.Graphics.BackendThreading.Value;
string BackendThreadingInit = Program.BackendThreadingArg;
if (BackendThreadingInit is null)
{
BackendThreadingInit = ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString();
}
// If a configuration is found in the "/games/xxxxxxxxxxxxxx" folder, the program will load the user setting.
string idGame = application.IdBaseString;
if (ConfigurationFileFormat.TryLoad(Program.GetDirGameUserConfig(idGame), out ConfigurationFileFormat configurationFileFormat))
{
// Loads the user configuration, having previously changed the global configuration to the user configuration
ConfigurationState.Instance.Load(configurationFileFormat, Program.GetDirGameUserConfig(idGame, true, true), idGame);
}
// Code where conditions will be executed after loading user configuration
if (ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() != BackendThreadingInit)
{
List<string> Arguments = new List<string>
{
"--bt", ConfigurationState.Instance.Graphics.BackendThreading.Value.ToString() // BackendThreading
};
Rebooter.RebootAppWithGame(application.Path, Arguments);
return true;
}
return false;
}
public async Task LoadApplication(ApplicationData application, bool startFullscreen = false, BlitStruct<ApplicationControlProperty>? customNacpData = null)
{
if (InitializeUserConfig(application))
{
return;
}
if (AppHost != null)
{
await ContentDialogHelper.CreateInfoDialog(
@@ -1540,7 +1595,7 @@ namespace Ryujinx.Ava.UI.ViewModels
#if RELEASE
await PerformanceCheck();
#endif
Logger.RestartTime();
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id);
@@ -1585,6 +1640,7 @@ namespace Ryujinx.Ava.UI.ViewModels
Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" };
gameThread.Start();
}
public void SwitchToRenderer(bool startFullscreen) =>

View File

@@ -4,7 +4,7 @@ using System.Collections.ObjectModel;
namespace Ryujinx.Ava.UI.ViewModels
{
public partial class UserSelectorDialogViewModel : BaseModel
public partial class ProfileSelectorDialogViewModel : BaseModel
{
[ObservableProperty] private UserId _selectedUserId;

View File

@@ -1,8 +1,10 @@
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Humanizer;
using LibHac.Tools.FsSystem;
using Ryujinx.Audio.Backends.OpenAL;
using Ryujinx.Audio.Backends.SDL2;
@@ -27,6 +29,7 @@ using Ryujinx.HLE.HOS.Services.Time.TimeZone;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading.Tasks;
@@ -69,6 +72,17 @@ namespace Ryujinx.Ava.UI.ViewModels
public SettingsHacksViewModel DirtyHacks { get; }
private readonly bool _isGameRunning;
private Bitmap _gameIcon;
private string _gameTitle;
private string _gamePath;
private string _gameId;
public bool IsGameRunning => _isGameRunning;
public Bitmap GameIcon => _gameIcon;
public string GamePath => _gamePath;
public string GameTitle => _gameTitle;
public string GameId => _gameId;
public int ResolutionScale
{
get => _resolutionScale;
@@ -126,6 +140,10 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool EnableDockedMode { get; set; }
public bool EnableKeyboard { get; set; }
public bool EnableMouse { get; set; }
public bool DisableInputWhenOutOfFocus { get; set; }
public int FocusLostActionType { get; set; }
public VSyncMode VSyncMode
{
get => _vSyncMode;
@@ -332,7 +350,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsInvalidLdnPassphraseVisible { get; set; }
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this()
public SettingsViewModel(VirtualFileSystem virtualFileSystem, ContentManager contentManager) : this(false)
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
@@ -345,7 +363,51 @@ namespace Ryujinx.Ava.UI.ViewModels
}
}
public SettingsViewModel()
public SettingsViewModel(
VirtualFileSystem virtualFileSystem,
ContentManager contentManager,
bool gameRunning,
string gamePath,
string gameName,
string gameId,
byte[] gameIconData,
bool enableToLoadCustomConfig) : this(enableToLoadCustomConfig)
{
_virtualFileSystem = virtualFileSystem;
_contentManager = contentManager;
if (gameIconData != null && gameIconData.Length > 0)
{
using (var ms = new MemoryStream(gameIconData))
{
_gameIcon = new Bitmap(ms);
}
}
_isGameRunning = gameRunning;
_gamePath = gamePath;
_gameTitle = gameName;
_gameId = gameId;
if (enableToLoadCustomConfig) // During the game. If there is no user config, then load the global config window
{
string gameDir = Program.GetDirGameUserConfig(gameId, false, true);
if (ConfigurationFileFormat.TryLoad(gameDir, out ConfigurationFileFormat configurationFileFormat))
{
ConfigurationState.Instance.Load(configurationFileFormat, gameDir, gameId);
}
LoadCurrentConfiguration(); // Needed to load custom configuration
}
if (Program.PreviewerDetached)
{
Task.Run(LoadTimeZones);
}
}
public SettingsViewModel(bool noLoadGlobalConfig = false)
{
GameDirectories = [];
AutoloadDirectories = [];
@@ -360,7 +422,11 @@ namespace Ryujinx.Ava.UI.ViewModels
if (Program.PreviewerDetached)
{
Task.Run(LoadAvailableGpus);
LoadCurrentConfiguration();
if (!noLoadGlobalConfig)// Default is false, but loading custom config avoids double call
{
LoadCurrentConfiguration();
}
DirtyHacks = new SettingsHacksViewModel(this);
}
@@ -471,33 +537,41 @@ namespace Ryujinx.Ava.UI.ViewModels
{
ConfigurationState config = ConfigurationState.Instance;
// User Interface
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value);
AutoloadDirectories.Clear();
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
BaseStyleIndex = config.UI.BaseStyle.Value switch
//It is necessary that the data is used from the global configuration file
if (string.IsNullOrEmpty(GameId))
{
"Auto" => 0,
"Light" => 1,
"Dark" => 2,
_ => 0
};
// User Interface
EnableDiscordIntegration = config.EnableDiscordIntegration;
CheckUpdatesOnStart = config.CheckUpdatesOnStart;
ShowConfirmExit = config.ShowConfirmExit;
RememberWindowState = config.RememberWindowState;
ShowTitleBar = config.ShowTitleBar;
HideCursor = (int)config.HideCursor.Value;
UpdateCheckerType = (int)config.UpdateCheckerType.Value;
FocusLostActionType = (int)config.FocusLostActionType.Value;
GameDirectories.Clear();
GameDirectories.AddRange(config.UI.GameDirs.Value);
AutoloadDirectories.Clear();
AutoloadDirectories.AddRange(config.UI.AutoloadDirs.Value);
BaseStyleIndex = config.UI.BaseStyle.Value switch
{
"Auto" => 0,
"Light" => 1,
"Dark" => 2,
_ => 0
};
}
// Input
EnableDockedMode = config.System.EnableDockedMode;
EnableKeyboard = config.Hid.EnableKeyboard;
EnableMouse = config.Hid.EnableMouse;
DisableInputWhenOutOfFocus = config.Hid.DisableInputWhenOutOfFocus;
// Keyboard Hotkeys
KeyboardHotkey = new HotkeyConfig(config.Hid.Hotkeys.Value);
@@ -512,7 +586,6 @@ namespace Ryujinx.Ava.UI.ViewModels
DateTime currentDateTime = currentHostDateTime.Add(systemDateTimeOffset);
CurrentDate = currentDateTime.Date;
CurrentTime = currentDateTime.TimeOfDay;
MatchSystemTime = config.System.MatchSystemTime;
EnableCustomVSyncInterval = config.Graphics.EnableCustomVSyncInterval;
@@ -573,42 +646,49 @@ namespace Ryujinx.Ava.UI.ViewModels
LdnPassphrase = config.Multiplayer.LdnPassphrase;
LdnServer = config.Multiplayer.LdnServer;
}
public void SaveSettings()
{
ConfigurationState config = ConfigurationState.Instance;
bool userConfigFile = string.IsNullOrEmpty(GameId);
// User Interface
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
if (GameDirectoryChanged)
if (userConfigFile)
{
config.UI.GameDirs.Value = [..GameDirectories];
// User Interface
config.EnableDiscordIntegration.Value = EnableDiscordIntegration;
config.CheckUpdatesOnStart.Value = CheckUpdatesOnStart;
config.ShowConfirmExit.Value = ShowConfirmExit;
config.RememberWindowState.Value = RememberWindowState;
config.ShowTitleBar.Value = ShowTitleBar;
config.HideCursor.Value = (HideCursorMode)HideCursor;
config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType;
config.FocusLostActionType.Value = (FocusLostType)FocusLostActionType;
if (GameDirectoryChanged)
{
config.UI.GameDirs.Value = [.. GameDirectories];
}
if (AutoloadDirectoryChanged)
{
config.UI.AutoloadDirs.Value = [.. AutoloadDirectories];
}
config.UI.BaseStyle.Value = BaseStyleIndex switch
{
0 => "Auto",
1 => "Light",
2 => "Dark",
_ => "Auto"
};
}
if (AutoloadDirectoryChanged)
{
config.UI.AutoloadDirs.Value = [..AutoloadDirectories];
}
config.UI.BaseStyle.Value = BaseStyleIndex switch
{
0 => "Auto",
1 => "Light",
2 => "Dark",
_ => "Auto"
};
// Input
config.System.EnableDockedMode.Value = EnableDockedMode;
config.Hid.EnableKeyboard.Value = EnableKeyboard;
config.Hid.EnableMouse.Value = EnableMouse;
config.Hid.DisableInputWhenOutOfFocus.Value = DisableInputWhenOutOfFocus;
// Keyboard Hotkeys
config.Hid.Hotkeys.Value = KeyboardHotkey.GetConfig();
@@ -700,7 +780,9 @@ namespace Ryujinx.Ava.UI.ViewModels
config.Hacks.EnableShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelayEnabled;
config.Hacks.ShaderTranslationDelay.Value = DirtyHacks.ShaderTranslationDelay;
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
config.ToFileFormat().SaveConfig(Program.ConfigurationPath);
MainWindow.UpdateGraphicsConfig();
RyujinxApp.MainWindow.ViewModel.VSyncModeSettingChanged();
@@ -713,7 +795,11 @@ namespace Ryujinx.Ava.UI.ViewModels
private static void RevertIfNotSaved()
{
Program.ReloadConfig();
// maybe this is an unnecessary check(all options need to be tested)
if (string.IsNullOrEmpty(Program.GlobalConfigurationPath))
{
Program.ReloadConfig();
}
}
public void ApplyButton()
@@ -721,6 +807,26 @@ namespace Ryujinx.Ava.UI.ViewModels
SaveSettings();
}
public void DeleteConfigGame()
{
string gameDir = Program.GetDirGameUserConfig(GameId,false,false);
if (File.Exists(gameDir))
{
File.Delete(gameDir);
}
RevertIfNotSaved();
CloseWindow?.Invoke();
}
public void SaveUserConfig()
{
SaveSettings();
RevertIfNotSaved(); // Revert global configuration after saving user configuration
CloseWindow?.Invoke();
}
public void OkButton()
{
SaveSettings();

View File

@@ -51,12 +51,8 @@ namespace Ryujinx.Ava.UI.Views.Main
XciTrimmerMenuItem.Command = Commands.Create(XCITrimmerWindow.Show);
AboutWindowMenuItem.Command = Commands.Create(AboutWindow.Show);
CompatibilityListMenuItem.Command = Commands.Create(() => CompatibilityList.Show());
UpdateMenuItem.Command = Commands.Create(async () =>
{
if (Updater.CanUpdate(true))
await Updater.BeginUpdateAsync(true);
});
UpdateMenuItem.Command = MainWindowViewModel.UpdateCommand;
FaqMenuItem.Command =
SetupGuideMenuItem.Command =
@@ -134,9 +130,26 @@ namespace Ryujinx.Ava.UI.Views.Main
Window.SettingsWindow = new(Window.VirtualFileSystem, Window.ContentManager);
Rainbow.Enable();
await Window.SettingsWindow.ShowDialog(Window);
if (ViewModel.SelectedApplication is null) // Checks if game data exists
{
await Window.SettingsWindow.ShowDialog(Window);
}
else
{
bool userConfigExist = Program.FindGameConfig(Program.GetDirGameUserConfig(ViewModel.SelectedApplication.IdString, false, false));
if (!ViewModel.IsGameRunning || !userConfigExist)
{
await Window.SettingsWindow.ShowDialog(Window); // The game is not running, or if the user configuration does not exist
}
else
{
// If there is a custom configuration in the folder
await new GameSpecificSettingsWindow(ViewModel, userConfigExist).ShowDialog((Window)ViewModel.TopLevel);
}
}
Rainbow.Disable();
Rainbow.Reset();

View File

@@ -23,7 +23,7 @@
Background="{DynamicResource ThemeContentBackgroundColor}"
DockPanel.Dock="Bottom"
IsVisible="{Binding ShowMenuAndStatusBar}"
ColumnDefinitions="Auto,Auto,*,Auto,Auto">
ColumnDefinitions="Auto,Auto,*,Auto,Auto,Auto">
<StackPanel
Grid.Column="0"
Margin="5"
@@ -280,9 +280,31 @@
Text="{Binding GpuNameText}"
TextAlignment="Start" />
</StackPanel>
<StackPanel
<StackPanel
Grid.Column="4"
Margin="0,0,5,0"
Orientation="Horizontal">
<StackPanel.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="EnableNonGameRunningControls" />
<Binding Path="UpdateAvailable" />
</MultiBinding>
</StackPanel.IsVisible>
<Button Margin="0, 0, 5, -2"
Command="{Binding UpdateCommand}"
Background="{DynamicResource SystemAccentColor}">
<TextBlock
Margin="-5"
Foreground="{StaticResource SystemColorButtonTextColor}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{ext:Locale UpdaterBackgroundStatusBarButtonText}" />
</Button>
<controls:MiniVerticalSeparator Margin="5,0,0,0"/>
</StackPanel>
<StackPanel
Grid.Column="5"
Margin="0,0,5,0"
VerticalAlignment="Center"
IsVisible="{Binding ShowFirmwareStatus}"
Orientation="Horizontal">

View File

@@ -40,6 +40,30 @@
<CheckBox IsChecked="{Binding ShowTitleBar}" IsVisible="{x:Static helper:RunningPlatform.IsWindows}">
<TextBlock Text="{ext:Locale SettingsTabGeneralShowTitleBar}" />
</CheckBox>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralFocusLossType}"
Width="150" />
<ComboBox SelectedIndex="{Binding FocusLostActionType}"
HorizontalContentAlignment="Left"
MinWidth="100">
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeDoNothing}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeBlockInput}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeMuteAudio}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypeBlockInputAndMuteAudio}" />
</ComboBoxItem>
<ComboBoxItem>
<TextBlock Text="{ext:Locale SettingsTabGeneralFocusLossTypePauseEmulation}" />
</ComboBoxItem>
</ComboBox>
</StackPanel>
<StackPanel Margin="0, 15, 0, 0" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center"
Text="{ext:Locale SettingsTabGeneralCheckUpdatesOnLaunch}"

View File

@@ -125,7 +125,7 @@
Background="Transparent"
Click="Button_OnClick"
CornerRadius="15"
Tag="https://discord.gg/dHPrkBkkyA"
Tag="https://discord.gg/PEuzjrFXUA"
ToolTip.Tip="{ext:Locale AboutDiscordUrlTooltipMessage}">
<Image Source="{Binding DiscordLogo}" />
</Button>
@@ -142,42 +142,40 @@
<Grid
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" RowDefinitions="Auto,Auto">
VerticalAlignment="Stretch" RowDefinitions="Auto,Auto,Auto">
<StackPanel
Grid.Row="0"
Margin="0,10,0,0"
Spacing="2">
<TextBlock
FontSize="15"
Classes="h1"
FontWeight="Bold"
Text="{ext:Locale AboutRyujinxAboutTitle}" />
<TextBlock
FontSize="10"
Text="{ext:Locale AboutRyujinxAboutContent}"
TextWrapping="Wrap" />
</StackPanel>
<Separator Grid.Row="1" Margin="0,20" />
<StackPanel
Grid.Row="1"
Margin="0,10,0,0"
Grid.Row="2"
Spacing="2">
<TextBlock
FontSize="15"
Classes="h1"
FontWeight="Bold"
Text="{ext:Locale AboutRyujinxMaintainersTitle}" />
<TextBlock
FontSize="10"
Margin="0, 0, 0, 5"
TextWrapping="Wrap"
Text="{Binding Developers}"/>
<TextBlock
FontSize="15"
Classes="h1"
FontWeight="Bold"
Text="{ext:Locale AboutRyujinxFormerMaintainersTitle}" />
<TextBlock
FontSize="10"
FontSize="11"
Text="{Binding FormerDevelopers}"
TextWrapping="Wrap" />
<Button
Margin="0, 5, 0, 0"
Padding="5"
HorizontalAlignment="Left"
Background="Transparent"

View File

@@ -18,8 +18,6 @@ namespace Ryujinx.Ava.UI.Windows
{
public AboutWindow()
{
DataContext = new AboutWindowViewModel();
InitializeComponent();
GitHubRepoButton.Tag =
@@ -28,12 +26,14 @@ namespace Ryujinx.Ava.UI.Windows
public static async Task Show()
{
using AboutWindowViewModel viewModel = new();
ContentDialog contentDialog = new()
{
PrimaryButtonText = string.Empty,
SecondaryButtonText = string.Empty,
CloseButtonText = LocaleManager.Instance[LocaleKeys.UserProfilesClose],
Content = new AboutWindow()
Content = new AboutWindow { DataContext = viewModel }
};
Style closeButton = new(x => x.Name("CloseButton"));

View File

@@ -0,0 +1,149 @@
<window:StyleableAppWindow
x:Class="Ryujinx.Ava.UI.Windows.GameSpecificSettingsWindow"
xmlns="https://github.com/avaloniaui"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
xmlns:settings="clr-namespace:Ryujinx.Ava.UI.Views.Settings"
xmlns:helpers="clr-namespace:Ryujinx.Ava.UI.Helpers"
xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common"
Width="1100"
Height="768"
MinWidth="800"
MinHeight="480"
WindowStartupLocation="CenterOwner"
x:DataType="viewModels:SettingsViewModel"
mc:Ignorable="d"
Focusable="True">
<Design.DataContext>
<viewModels:SettingsViewModel />
</Design.DataContext>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" MinWidth="600" RowDefinitions="Auto,*,Auto">
<ContentPresenter
x:Name="ContentPresenter"
Grid.Row="1"
IsVisible="False"
KeyboardNavigation.IsTabStop="False"/>
<Grid Name="Pages" IsVisible="False" Grid.Row="2">
<settings:SettingsInputView Name="InputPage" />
<settings:SettingsSystemView Name="SystemPage" />
<settings:SettingsCPUView Name="CpuPage" />
<settings:SettingsGraphicsView Name="GraphicsPage" />
<settings:SettingsAudioView Name="AudioPage" />
<settings:SettingsNetworkView Name="NetworkPage" />
<settings:SettingsLoggingView Name="LoggingPage" />
<settings:SettingsHacksView Name="HacksPage" />
</Grid>
<ui:NavigationView
Grid.Row="1"
IsSettingsVisible="False"
Name="NavPanel"
IsBackEnabled="False"
PaneDisplayMode="Left"
Margin="2,10,10,0"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
OpenPaneLength="200"
IsPaneToggleButtonVisible="False">
<!-- For image -->
<ui:NavigationView.PaneHeader>
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding GameId}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="0" />
<Image Source="{Binding GameIcon}"
Width="160"
Height="160"
Grid.Row="1"
Margin="0,0,0,10"
HorizontalAlignment="Center" />
<TextBlock Text="{Binding GameTitle}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="0,0,0,10"
TextAlignment="Center" Grid.Row="2" />
<Separator Height="1" Grid.Row="3" Margin="0,0,0,10" HorizontalAlignment="Stretch"/>
</Grid>
</ui:NavigationView.PaneHeader>
<ui:NavigationView.MenuItems>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabInput}"
Tag="InputPage"
IconSource="Games" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabSystem}"
Tag="SystemPage"
IconSource="Settings" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabCpu}"
Tag="CpuPage">
<ui:NavigationViewItem.IconSource>
<ui:FontIconSource
FontFamily="avares://Ryujinx/Assets/Fonts#Segoe Fluent Icons"
Glyph="{helpers:GlyphValueConverter Chip}" />
</ui:NavigationViewItem.IconSource>
</ui:NavigationViewItem>
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabGraphics}"
Tag="GraphicsPage"
IconSource="Image" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabAudio}"
IconSource="Audio"
Tag="AudioPage" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabNetwork}"
Tag="NetworkPage"
IconSource="Globe" />
<ui:NavigationViewItem
Content="{ext:Locale SettingsTabLogging}"
Tag="LoggingPage"
IconSource="Document" />
<ui:NavigationViewItem
IsVisible="{Binding ShowDirtyHacks}"
Content="Dirty Hacks"
Tag="HacksPage"
IconSource="Code" />
</ui:NavigationView.MenuItems>
</ui:NavigationView>
<ReversibleStackPanel
Grid.Row="2"
Margin="10"
Spacing="10"
Orientation="Horizontal"
HorizontalAlignment="Right"
ReverseOrder="{x:Static helper:RunningPlatform.IsMacOS}">
<Button
Content="{ext:Locale SettingsButtonSave}"
Command="{Binding SaveUserConfig}" />
<Button
HotKey="Escape"
Content="{ext:Locale SettingsButtonClose}"
Command="{Binding CancelButton}" />
<Button
IsVisible="{Binding IsGameRunning}"
Content="{ext:Locale SettingsButtonApply}"
Command="{Binding ApplyButton}" />
<Button
IsVisible="{Binding !IsGameRunning}"
Content="{ext:Locale UserProfilesDelete}"
Command="{Binding DeleteConfigGame}"
Classes="red"/>
</ReversibleStackPanel>
</Grid>
</window:StyleableAppWindow>

View File

@@ -0,0 +1,117 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Input;
using Avalonia.Media.Imaging;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using Projektanker.Icons.Avalonia;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Models;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.ViewModels.Input;
using Ryujinx.Ava.Utilities;
using Ryujinx.Ava.Utilities.Configuration;
using Ryujinx.Common.Configuration;
using Ryujinx.HLE.FileSystem;
using Ryujinx.HLE.HOS.SystemState;
using Ryujinx.Input;
using System;
using System.IO;
using System.Linq;
using Key = Avalonia.Input.Key;
namespace Ryujinx.Ava.UI.Windows
{
public partial class GameSpecificSettingsWindow : StyleableAppWindow
{
internal readonly SettingsViewModel ViewModel;
public GameSpecificSettingsWindow(MainWindowViewModel viewModel, bool findUserConfigDir = true)
{
Title = string.Format(LocaleManager.Instance[LocaleKeys.SettingsWithInfo], viewModel.SelectedApplication.Name, viewModel.SelectedApplication.IdString);
DataContext = ViewModel = new SettingsViewModel(
viewModel.VirtualFileSystem,
viewModel.ContentManager,
viewModel.IsGameRunning,
viewModel.SelectedApplication.Path,
viewModel.SelectedApplication.Name,
viewModel.SelectedApplication.IdString,
viewModel.SelectedApplication.Icon,
findUserConfigDir);
ViewModel.CloseWindow += Close;
ViewModel.SaveSettingsEvent += SaveSettings;
InitializeComponent();
Load();
#if DEBUG
this.AttachDevTools(new KeyGesture(Key.F12, KeyModifiers.Alt));
#endif
}
public void SaveSettings()
{
InputPage.InputView?.SaveCurrentProfile();
}
private void Load()
{
Pages.Children.Clear();
NavPanel.SelectionChanged += NavPanelOnSelectionChanged;
NavPanel.SelectedItem = NavPanel.MenuItems.ElementAt(0);
}
private void NavPanelOnSelectionChanged(object sender, NavigationViewSelectionChangedEventArgs e)
{
if (e.SelectedItem is NavigationViewItem navItem && navItem.Tag is not null)
{
switch (navItem.Tag.ToString())
{
case nameof(InputPage):
NavPanel.Content = InputPage;
break;
case nameof(SystemPage):
SystemPage.ViewModel = ViewModel;
NavPanel.Content = SystemPage;
break;
case nameof(CpuPage):
NavPanel.Content = CpuPage;
break;
case nameof(GraphicsPage):
NavPanel.Content = GraphicsPage;
break;
case nameof(AudioPage):
NavPanel.Content = AudioPage;
break;
case nameof(NetworkPage):
NetworkPage.ViewModel = ViewModel;
NavPanel.Content = NetworkPage;
break;
case nameof(LoggingPage):
NavPanel.Content = LoggingPage;
break;
case nameof(HacksPage):
HacksPage.DataContext = ViewModel;
NavPanel.Content = HacksPage;
break;
default:
throw new NotImplementedException();
}
}
}
protected override void OnClosing(WindowClosingEventArgs e)
{
InputPage.Dispose(); // You need to unload the gamepad settings, otherwise the controls will be blocked
base.OnClosing(e);
}
}
}

View File

@@ -21,7 +21,9 @@
x:DataType="viewModels:MainWindowViewModel"
mc:Ignorable="d"
WindowStartupLocation="Manual"
Focusable="True">
Focusable="True"
GotFocus="InputElement_OnGotFocus"
LostFocus="InputElement_OnLostFocus">
<Window.Styles>
<Style Selector="TitleBar:fullscreen">
<Setter Property="Background" Value="#000000" />

View File

@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Threading;
@@ -222,7 +223,7 @@ namespace Ryujinx.Ava.UI.Windows
_deferLoad = true;
_launchPath = launchPathArg;
_launchApplicationId = launchApplicationId;
_startFullscreen = startFullscreenArg;
_startFullscreen = startFullscreenArg;
}
public void SwitchToGameControl(bool startFullscreen = false)
@@ -373,6 +374,7 @@ namespace Ryujinx.Ava.UI.Windows
if (applicationData != null)
{
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen);
}
else
@@ -384,6 +386,7 @@ namespace Ryujinx.Ava.UI.Windows
else
{
applicationData = applications[0];
ViewModel.SelectedApplication = applicationData;
await ViewModel.LoadApplication(applicationData, _startFullscreen);
}
}
@@ -413,15 +416,7 @@ namespace Ryujinx.Ava.UI.Windows
case UpdaterType.CheckInBackground:
if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions))
{
string newVersionString = ReleaseInformation.IsCanaryBuild
? $"Canary {versions.Current} -> Canary {versions.Incoming}"
: $"{versions.Current} -> {versions.Incoming}";
if (versions.Current < versions.Incoming)
NotificationHelper.ShowInformation(
title: "Update Available",
text: newVersionString,
onClick: () => _ = Updater.BeginUpdateAsync());
Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming);
}
break;
}
@@ -665,6 +660,11 @@ namespace Ryujinx.Ava.UI.Windows
ReloadGameList();
}
public void GameListUpdate()
{
ReloadGameList();
}
public void ToggleFileType(string fileType)
{
switch (fileType)
@@ -769,5 +769,119 @@ namespace Ryujinx.Ava.UI.Windows
_intelMacWarningShown = true;
}
private void InputElement_OnGotFocus(object sender, GotFocusEventArgs e)
{
if (ViewModel.AppHost is null) return;
if (!_focusLoss.Active)
return;
switch (_focusLoss.Type)
{
case FocusLostType.BlockInput:
{
if (!ViewModel.AppHost.NpadManager.InputUpdatesBlocked)
{
_focusLoss = default;
return;
}
ViewModel.AppHost.NpadManager.UnblockInputUpdates();
_focusLoss = default;
break;
}
case FocusLostType.MuteAudio:
{
if (!ViewModel.AppHost.Device.IsAudioMuted())
{
_focusLoss = default;
return;
}
ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
_focusLoss = default;
break;
}
case FocusLostType.BlockInputAndMuteAudio:
{
if (!ViewModel.AppHost.Device.IsAudioMuted())
goto case FocusLostType.BlockInput;
ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
ViewModel.AppHost.NpadManager.UnblockInputUpdates();
_focusLoss = default;
break;
}
case FocusLostType.PauseEmulation:
{
if (!ViewModel.AppHost.Device.System.IsPaused)
{
_focusLoss = default;
return;
}
ViewModel.AppHost.Resume();
_focusLoss = default;
break;
}
}
}
private (FocusLostType Type, bool Active) _focusLoss;
private void InputElement_OnLostFocus(object sender, RoutedEventArgs e)
{
if (ConfigurationState.Instance.FocusLostActionType.Value is FocusLostType.DoNothing)
return;
if (ViewModel.AppHost is null) return;
switch (ConfigurationState.Instance.FocusLostActionType.Value)
{
case FocusLostType.BlockInput:
{
if (ViewModel.AppHost.NpadManager.InputUpdatesBlocked)
return;
ViewModel.AppHost.NpadManager.BlockInputUpdates();
_focusLoss = (FocusLostType.BlockInput, ViewModel.AppHost.NpadManager.InputUpdatesBlocked);
break;
}
case FocusLostType.MuteAudio:
{
if (ViewModel.AppHost.Device.GetVolume() is 0)
return;
ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
ViewModel.AppHost.Device.SetVolume(0);
_focusLoss = (FocusLostType.MuteAudio, ViewModel.AppHost.Device.GetVolume() is 0f);
break;
}
case FocusLostType.BlockInputAndMuteAudio:
{
if (ViewModel.AppHost.Device.GetVolume() is 0)
goto case FocusLostType.BlockInput;
ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
ViewModel.AppHost.Device.SetVolume(0);
ViewModel.AppHost.NpadManager.BlockInputUpdates();
_focusLoss = (FocusLostType.BlockInputAndMuteAudio, ViewModel.AppHost.Device.GetVolume() is 0f && ViewModel.AppHost.NpadManager.InputUpdatesBlocked);
break;
}
case FocusLostType.PauseEmulation:
{
if (ViewModel.AppHost.Device.System.IsPaused)
return;
ViewModel.AppHost.Pause();
_focusLoss = (FocusLostType.PauseEmulation, ViewModel.AppHost.Device.System.IsPaused);
break;
}
}
}
}
}

View File

@@ -163,7 +163,7 @@ namespace Ryujinx.Ava
_running = false;
return (currentVersion, null);
return default;
}
return (currentVersion, newVersion);
@@ -178,7 +178,11 @@ namespace Ryujinx.Ava
_running = true;
(Version currentVersion, Version newVersion) = (await CheckVersionAsync(showVersionUpToDate)).OrDefault();
Optional<(Version, Version)> versionTuple = await CheckVersionAsync(showVersionUpToDate);
if (_running is false || !versionTuple.HasValue) return;
(Version currentVersion, Version newVersion) = versionTuple.Value;
if (newVersion <= currentVersion)
{

View File

@@ -23,6 +23,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public class ApplicationData
{
public bool Favorite { get; set; }
public bool HasIndependentConfiguration { get; set; }
public byte[] Icon { get; set; }
public string Name { get; set; } = "Unknown";
@@ -80,7 +81,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
public string LocalizedStatusTooltip =>
Compatibility.Convert(x =>
#pragma warning disable CS8509 It is exhaustive for all possible values this can contain.
#pragma warning disable CS8509 // It is exhaustive for all possible values this can contain.
LocaleManager.Instance[x.Status switch
#pragma warning restore CS8509
{

View File

@@ -505,7 +505,7 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
if (data.Id != 0)
{
ApplicationMetadata appMetadata = LoadAndSaveMetaData(data.IdString, appMetadata =>
{
{
appMetadata.Title = data.Name;
// Only do the migration if time_played has a value and timespan_played hasn't been updated yet.
@@ -529,10 +529,11 @@ namespace Ryujinx.Ava.Utilities.AppLibrary
}
});
data.Favorite = appMetadata.Favorite;
data.TimePlayed = appMetadata.TimePlayed;
data.LastPlayed = appMetadata.LastPlayed;
data.HasIndependentConfiguration = File.Exists(Program.GetDirGameUserConfig(data.IdBaseString, false, false)); // Just check user config
}
data.FileExtension = Path.GetExtension(applicationPath).TrimStart('.').ToUpper();

View File

@@ -45,7 +45,7 @@ namespace Ryujinx.Ava.Utilities
if (string.IsNullOrEmpty(contentPath))
goto BadData;
appData = new() { Name = Name, Id = ProgramId, Path = GetContentPath(contentManager) };
appData = new() { Name = Name, Id = ProgramId, Path = contentPath };
appControl = StructHelpers.CreateCustomNacpData(Name, Version);
return true;

View File

@@ -6,11 +6,16 @@ namespace Ryujinx.Ava.Utilities
public static class CommandLineState
{
public static string[] Arguments { get; private set; }
public static int CountArguments { get; private set; }
public static bool? OverrideDockedMode { get; private set; }
public static bool? OverrideHardwareAcceleration { get; private set; }
public static string OverrideGraphicsBackend { get; private set; }
public static string OverrideBackendThreading { get; private set; }
public static string OverrideBackendThreadingAfterReboot { get; private set; }
public static string OverridePPTC { get; private set; }
public static string OverrideMemoryManagerMode { get; private set; }
public static string OverrideSystemRegion { get; private set; }
public static string OverrideSystemLanguage { get; private set; }
public static string OverrideHideCursor { get; private set; }
public static string BaseDirPathArg { get; private set; }
public static string Profile { get; private set; }
@@ -19,6 +24,7 @@ namespace Ryujinx.Ava.Utilities
public static bool StartFullscreenArg { get; private set; }
public static bool HideAvailableUpdates { get; private set; }
public static void ParseArguments(string[] args)
{
List<string> arguments = [];
@@ -28,6 +34,11 @@ namespace Ryujinx.Ava.Utilities
{
string arg = args[i];
if (arg.Contains("-") || arg.Contains("--"))
{
CountArguments++;
}
switch (arg)
{
case "-r":
@@ -85,6 +96,57 @@ namespace Ryujinx.Ava.Utilities
OverrideBackendThreading = args[++i];
break;
case "--bt":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideBackendThreadingAfterReboot = args[++i];
break;
case "--pptc":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverridePPTC = args[++i];
break;
case "-m":
case "--memory-manager-mode":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideMemoryManagerMode = args[++i];
break;
case "--system-region":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemRegion = args[++i];
break;
case "--system-language":
if (i + 1 >= args.Length)
{
Logger.Error?.Print(LogClass.Application, $"Invalid option '{arg}'");
continue;
}
OverrideSystemLanguage = args[++i];
break;
case "-i":
case "--application-id":
LaunchApplicationId = args[++i];

View File

@@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// <summary>
/// The current version of the file format
/// </summary>
public const int CurrentVersion = 65;
public const int CurrentVersion = 67;
/// <summary>
/// Version of the configuration file format
@@ -171,6 +171,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
/// </summary>
public UpdaterType UpdateCheckerType { get; set; }
/// <summary>
/// How the emulator should behave when you click off/on the window.
/// </summary>
public FocusLostType FocusLostActionType { get; set; }
/// <summary>
/// Show "Confirm Exit" Dialog
@@ -383,6 +388,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enable or disable mouse support (Independent from controllers binding)
/// </summary>
public bool EnableMouse { get; set; }
/// <summary>
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
/// </summary>
public bool DisableInputWhenOutOfFocus { get; set; }
/// <summary>
/// Hotkey Keyboard Bindings

View File

@@ -18,9 +18,15 @@ namespace Ryujinx.Ava.Utilities.Configuration
{
public partial class ConfigurationState
{
public void Load(ConfigurationFileFormat cff, string configurationFilePath)
public void Load(ConfigurationFileFormat cff, string configurationFilePath, string titleId = "")
{
bool configurationFileUpdated = false;
bool shouldLoadFromFile = true;
if (!string.IsNullOrEmpty(titleId))
{
shouldLoadFromFile = false;
}
if (cff.Version is < 0 or > ConfigurationFileFormat.CurrentVersion)
{
@@ -43,15 +49,17 @@ namespace Ryujinx.Ava.Utilities.Configuration
configurationFileUpdated = true;
}
EnableDiscordIntegration.Value = cff.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart;
UpdateCheckerType.Value = cff.UpdateCheckerType;
ShowConfirmExit.Value = cff.ShowConfirmExit;
RememberWindowState.Value = cff.RememberWindowState;
ShowTitleBar.Value = cff.ShowTitleBar;
EnableHardwareAcceleration.Value = cff.EnableHardwareAcceleration;
HideCursor.Value = cff.HideCursor;
EnableDiscordIntegration.Value = shouldLoadFromFile ? cff.EnableDiscordIntegration : EnableDiscordIntegration.Value; // Get from global config only
CheckUpdatesOnStart.Value = shouldLoadFromFile ? cff.CheckUpdatesOnStart : CheckUpdatesOnStart.Value; // Get from global config only
UpdateCheckerType.Value = shouldLoadFromFile ? cff.UpdateCheckerType : UpdateCheckerType.Value; // Get from global config only
FocusLostActionType.Value = shouldLoadFromFile ? cff.FocusLostActionType : FocusLostActionType.Value; // Get from global config only
ShowConfirmExit.Value = shouldLoadFromFile ? cff.ShowConfirmExit : ShowConfirmExit.Value; // Get from global config only
RememberWindowState.Value = shouldLoadFromFile ? cff.RememberWindowState : RememberWindowState.Value; // Get from global config only
ShowTitleBar.Value = shouldLoadFromFile ? cff.ShowTitleBar : ShowTitleBar.Value; // Get from global config only
EnableHardwareAcceleration.Value = shouldLoadFromFile ? cff.EnableHardwareAcceleration : EnableHardwareAcceleration.Value; // Get from global config only
HideCursor.Value = shouldLoadFromFile ? cff.HideCursor : HideCursor.Value; // Get from global config only
Logger.EnableFileLog.Value = cff.EnableFileLog;
Logger.EnableDebug.Value = cff.LoggingEnableDebug;
Logger.EnableStub.Value = cff.LoggingEnableStub;
@@ -86,7 +94,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.Language.Value = cff.SystemLanguage;
System.Region.Value = cff.SystemRegion;
System.TimeZone.Value = cff.SystemTimeZone;
System.SystemTimeOffset.Value = cff.SystemTimeOffset;
System.SystemTimeOffset.Value = shouldLoadFromFile ? cff.SystemTimeOffset : System.SystemTimeOffset.Value; // Get from global config only
System.EnableDockedMode.Value = cff.DockedMode;
System.EnablePtc.Value = cff.EnablePtc;
System.EnableLowPowerPtc.Value = cff.EnableLowPowerPtc;
@@ -100,46 +108,48 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.IgnoreMissingServices.Value = cff.IgnoreMissingServices;
System.IgnoreApplet.Value = cff.IgnoreApplet;
System.UseHypervisor.Value = cff.UseHypervisor;
UI.GuiColumns.FavColumn.Value = cff.GuiColumns.FavColumn;
UI.GuiColumns.IconColumn.Value = cff.GuiColumns.IconColumn;
UI.GuiColumns.AppColumn.Value = cff.GuiColumns.AppColumn;
UI.GuiColumns.DevColumn.Value = cff.GuiColumns.DevColumn;
UI.GuiColumns.VersionColumn.Value = cff.GuiColumns.VersionColumn;
UI.GuiColumns.TimePlayedColumn.Value = cff.GuiColumns.TimePlayedColumn;
UI.GuiColumns.LastPlayedColumn.Value = cff.GuiColumns.LastPlayedColumn;
UI.GuiColumns.FileExtColumn.Value = cff.GuiColumns.FileExtColumn;
UI.GuiColumns.FileSizeColumn.Value = cff.GuiColumns.FileSizeColumn;
UI.GuiColumns.PathColumn.Value = cff.GuiColumns.PathColumn;
UI.ColumnSort.SortColumnId.Value = cff.ColumnSort.SortColumnId;
UI.ColumnSort.SortAscending.Value = cff.ColumnSort.SortAscending;
UI.GameDirs.Value = cff.GameDirs;
UI.AutoloadDirs.Value = cff.AutoloadDirs ?? [];
UI.ShownFileTypes.NSP.Value = cff.ShownFileTypes.NSP;
UI.ShownFileTypes.PFS0.Value = cff.ShownFileTypes.PFS0;
UI.ShownFileTypes.XCI.Value = cff.ShownFileTypes.XCI;
UI.ShownFileTypes.NCA.Value = cff.ShownFileTypes.NCA;
UI.ShownFileTypes.NRO.Value = cff.ShownFileTypes.NRO;
UI.ShownFileTypes.NSO.Value = cff.ShownFileTypes.NSO;
UI.LanguageCode.Value = cff.LanguageCode;
UI.BaseStyle.Value = cff.BaseStyle;
UI.GameListViewMode.Value = cff.GameListViewMode;
UI.ShowNames.Value = cff.ShowNames;
UI.IsAscendingOrder.Value = cff.IsAscendingOrder;
UI.GridSize.Value = cff.GridSize;
UI.ApplicationSort.Value = cff.ApplicationSort;
UI.StartFullscreen.Value = cff.StartFullscreen;
UI.StartNoUI.Value = cff.StartNoUI;
UI.ShowConsole.Value = cff.ShowConsole;
UI.WindowStartup.WindowSizeWidth.Value = cff.WindowStartup.WindowSizeWidth;
UI.WindowStartup.WindowSizeHeight.Value = cff.WindowStartup.WindowSizeHeight;
UI.WindowStartup.WindowPositionX.Value = cff.WindowStartup.WindowPositionX;
UI.WindowStartup.WindowPositionY.Value = cff.WindowStartup.WindowPositionY;
UI.WindowStartup.WindowMaximized.Value = cff.WindowStartup.WindowMaximized;
UI.GuiColumns.FavColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FavColumn : UI.GuiColumns.FavColumn.Value;
UI.GuiColumns.IconColumn.Value = shouldLoadFromFile ? cff.GuiColumns.IconColumn : UI.GuiColumns.IconColumn.Value;
UI.GuiColumns.AppColumn.Value = shouldLoadFromFile ? cff.GuiColumns.AppColumn : UI.GuiColumns.AppColumn.Value;
UI.GuiColumns.DevColumn.Value = shouldLoadFromFile ? cff.GuiColumns.DevColumn : UI.GuiColumns.DevColumn.Value;
UI.GuiColumns.VersionColumn.Value = shouldLoadFromFile ? cff.GuiColumns.VersionColumn : UI.GuiColumns.VersionColumn.Value;
UI.GuiColumns.TimePlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.TimePlayedColumn : UI.GuiColumns.TimePlayedColumn.Value;
UI.GuiColumns.LastPlayedColumn.Value = shouldLoadFromFile ? cff.GuiColumns.LastPlayedColumn : UI.GuiColumns.LastPlayedColumn.Value;
UI.GuiColumns.FileExtColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileExtColumn : UI.GuiColumns.FileExtColumn.Value;
UI.GuiColumns.FileSizeColumn.Value = shouldLoadFromFile ? cff.GuiColumns.FileSizeColumn : UI.GuiColumns.FileSizeColumn.Value;
UI.GuiColumns.PathColumn.Value = shouldLoadFromFile ? cff.GuiColumns.PathColumn : UI.GuiColumns.PathColumn.Value;
UI.ColumnSort.SortColumnId.Value = shouldLoadFromFile ? cff.ColumnSort.SortColumnId : UI.ColumnSort.SortColumnId.Value;
UI.ColumnSort.SortAscending.Value = shouldLoadFromFile ? cff.ColumnSort.SortAscending : UI.ColumnSort.SortAscending.Value;
UI.GameDirs.Value = shouldLoadFromFile ? cff.GameDirs : UI.GameDirs.Value;
UI.AutoloadDirs.Value = shouldLoadFromFile ? (cff.AutoloadDirs ?? []) : UI.AutoloadDirs.Value;
UI.ShownFileTypes.NSP.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSP : UI.ShownFileTypes.NSP.Value;
UI.ShownFileTypes.PFS0.Value = shouldLoadFromFile ? cff.ShownFileTypes.PFS0 : UI.ShownFileTypes.PFS0.Value;
UI.ShownFileTypes.XCI.Value = shouldLoadFromFile ? cff.ShownFileTypes.XCI : UI.ShownFileTypes.XCI.Value;
UI.ShownFileTypes.NCA.Value = shouldLoadFromFile ? cff.ShownFileTypes.NCA : UI.ShownFileTypes.NCA.Value;
UI.ShownFileTypes.NRO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NRO : UI.ShownFileTypes.NRO.Value;
UI.ShownFileTypes.NSO.Value = shouldLoadFromFile ? cff.ShownFileTypes.NSO : UI.ShownFileTypes.NSO.Value;
UI.LanguageCode.Value = shouldLoadFromFile ? cff.LanguageCode : UI.LanguageCode.Value;
UI.BaseStyle.Value = shouldLoadFromFile ? cff.BaseStyle : UI.BaseStyle.Value;
UI.GameListViewMode.Value = shouldLoadFromFile ? cff.GameListViewMode : UI.GameListViewMode.Value;
UI.ShowNames.Value = shouldLoadFromFile ? cff.ShowNames : UI.ShowNames.Value;
UI.IsAscendingOrder.Value = shouldLoadFromFile ? cff.IsAscendingOrder : UI.IsAscendingOrder.Value;
UI.GridSize.Value = shouldLoadFromFile ? cff.GridSize : UI.GridSize.Value;
UI.ApplicationSort.Value = shouldLoadFromFile ? cff.ApplicationSort : UI.ApplicationSort.Value;
UI.StartFullscreen.Value = shouldLoadFromFile ? cff.StartFullscreen : UI.StartFullscreen.Value;
UI.StartNoUI.Value = shouldLoadFromFile ? cff.StartNoUI : UI.StartNoUI.Value;
UI.ShowConsole.Value = shouldLoadFromFile ? cff.ShowConsole : UI.ShowConsole.Value;
UI.WindowStartup.WindowSizeWidth.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeWidth : UI.WindowStartup.WindowSizeWidth.Value;
UI.WindowStartup.WindowSizeHeight.Value = shouldLoadFromFile ? cff.WindowStartup.WindowSizeHeight : UI.WindowStartup.WindowSizeHeight.Value;
UI.WindowStartup.WindowPositionX.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionX : UI.WindowStartup.WindowPositionX.Value;
UI.WindowStartup.WindowPositionY.Value = shouldLoadFromFile ? cff.WindowStartup.WindowPositionY : UI.WindowStartup.WindowPositionY.Value;
UI.WindowStartup.WindowMaximized.Value = shouldLoadFromFile ? cff.WindowStartup.WindowMaximized : UI.WindowStartup.WindowMaximized.Value;
Hid.EnableKeyboard.Value = cff.EnableKeyboard;
Hid.EnableMouse.Value = cff.EnableMouse;
Hid.Hotkeys.Value = cff.Hotkeys;
Hid.DisableInputWhenOutOfFocus.Value = shouldLoadFromFile ? cff.DisableInputWhenOutOfFocus: Hid.DisableInputWhenOutOfFocus.Value; // Get from global config only
Hid.Hotkeys.Value = shouldLoadFromFile ? cff.Hotkeys : Hid.Hotkeys.Value; // Get from global config only
Hid.InputConfig.Value = cff.InputConfig ?? [];
Hid.RainbowSpeed.Value = cff.RainbowSpeed;
@@ -433,7 +443,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
(62, static cff => cff.RainbowSpeed = 1f),
(63, static cff => cff.MatchSystemTime = false),
(64, static cff => cff.LoggingEnableAvalonia = false),
(65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off)
(65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off),
(66, static cff => cff.DisableInputWhenOutOfFocus = false),
(67, static cff => cff.FocusLostActionType = cff.DisableInputWhenOutOfFocus ? FocusLostType.BlockInput : FocusLostType.DoNothing)
);
}
}

View File

@@ -447,6 +447,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Enable or disable mouse support (Independent from controllers binding)
/// </summary>
public ReactiveObject<bool> EnableMouse { get; private set; }
/// <summary>
/// Enable/disable the ability to control Ryujinx when it's not the currently focused window.
/// </summary>
public ReactiveObject<bool> DisableInputWhenOutOfFocus { get; private set; }
/// <summary>
/// Hotkey Keyboard Bindings
@@ -469,6 +474,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
{
EnableKeyboard = new ReactiveObject<bool>();
EnableMouse = new ReactiveObject<bool>();
DisableInputWhenOutOfFocus = new ReactiveObject<bool>();
Hotkeys = new ReactiveObject<KeyboardHotkeys>();
InputConfig = new ReactiveObject<List<InputConfig>>();
RainbowSpeed = new ReactiveObject<float>();
@@ -773,6 +779,11 @@ namespace Ryujinx.Ava.Utilities.Configuration
/// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification.
/// </summary>
public ReactiveObject<UpdaterType> UpdateCheckerType { get; private set; }
/// <summary>
/// How the emulator should behave when you click off/on the window.
/// </summary>
public ReactiveObject<FocusLostType> FocusLostActionType { get; private set; }
/// <summary>
/// Show "Confirm Exit" Dialog
@@ -811,6 +822,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration = new ReactiveObject<bool>();
CheckUpdatesOnStart = new ReactiveObject<bool>();
UpdateCheckerType = new ReactiveObject<UpdaterType>();
FocusLostActionType = new ReactiveObject<FocusLostType>();
ShowConfirmExit = new ReactiveObject<bool>();
RememberWindowState = new ReactiveObject<bool>();
ShowTitleBar = new ReactiveObject<bool>();

View File

@@ -57,6 +57,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart,
UpdateCheckerType = UpdateCheckerType,
FocusLostActionType = FocusLostActionType,
ShowConfirmExit = ShowConfirmExit,
RememberWindowState = RememberWindowState,
ShowTitleBar = ShowTitleBar,
@@ -131,6 +132,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
ShowConsole = UI.ShowConsole,
EnableKeyboard = Hid.EnableKeyboard,
EnableMouse = Hid.EnableMouse,
DisableInputWhenOutOfFocus = Hid.DisableInputWhenOutOfFocus,
Hotkeys = Hid.Hotkeys,
InputConfig = Hid.InputConfig,
RainbowSpeed = Hid.RainbowSpeed,
@@ -177,6 +179,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
System.EnableDockedMode.Value = true;
EnableDiscordIntegration.Value = true;
UpdateCheckerType.Value = UpdaterType.PromptAtStartup;
FocusLostActionType.Value = FocusLostType.DoNothing;
ShowConfirmExit.Value = true;
RememberWindowState.Value = true;
ShowTitleBar.Value = !OperatingSystem.IsWindows();
@@ -245,6 +248,7 @@ namespace Ryujinx.Ava.Utilities.Configuration
UI.WindowStartup.WindowMaximized.Value = false;
Hid.EnableKeyboard.Value = false;
Hid.EnableMouse.Value = false;
Hid.DisableInputWhenOutOfFocus.Value = false;
Hid.Hotkeys.Value = new KeyboardHotkeys
{
ToggleVSyncMode = Key.F1,

View File

@@ -0,0 +1,15 @@
using Ryujinx.Common.Utilities;
using System.Text.Json.Serialization;
namespace Ryujinx.Ava.Utilities.Configuration.UI
{
[JsonConverter(typeof(TypedStringEnumConverter<FocusLostType>))]
public enum FocusLostType
{
DoNothing,
BlockInput,
MuteAudio,
BlockInputAndMuteAudio,
PauseEmulation
}
}

View File

@@ -12,7 +12,7 @@ namespace Ryujinx.Ava.Utilities
public static class ShortcutHelper
{
[SupportedOSPlatform("windows")]
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
private static void CreateShortcutWindows(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath, string args = "")
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
iconPath += ".ico";
@@ -22,13 +22,13 @@ namespace Ryujinx.Ava.Utilities
image.Resize(new SKImageInfo(128, 128), SKFilterQuality.High);
SaveBitmapAsIcon(image, iconPath);
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId), iconPath, 0);
Shortcut shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(applicationFilePath, applicationId, args), iconPath, 0);
shortcut.StringData.NameString = cleanedAppName;
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
}
[SupportedOSPlatform("linux")]
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
private static void CreateShortcutLinux(string applicationFilePath, string applicationId, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName, string args = "")
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
string desktopFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.desktop");
@@ -40,11 +40,11 @@ namespace Ryujinx.Ava.Utilities
data.SaveTo(file);
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId)}");
outputFile.Write(desktopFile, cleanedAppName, iconPath, $"{basePath} {GetArgsString(applicationFilePath, applicationId, args)}");
}
[SupportedOSPlatform("macos")]
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName)
private static void CreateShortcutMacos(string appFilePath, string applicationId, byte[] iconData, string desktopPath, string cleanedAppName, string args = "")
{
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx");
string plistFile = EmbeddedResources.ReadAllText("Ryujinx/Assets/ShortcutFiles/shortcut-template.plist");
@@ -63,7 +63,7 @@ namespace Ryujinx.Ava.Utilities
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
using StreamWriter scriptFile = new(scriptPath);
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId));
scriptFile.Write(shortcutScript, basePath, GetArgsString(appFilePath, applicationId, args));
// Set execute permission
FileInfo fileInfo = new(scriptPath);
@@ -87,7 +87,7 @@ namespace Ryujinx.Ava.Utilities
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
}
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData, string args = "")
{
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
@@ -96,7 +96,7 @@ namespace Ryujinx.Ava.Utilities
{
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath);
CreateShortcutWindows(applicationFilePath, applicationId, iconData, iconPath, cleanedAppName, desktopPath, args);
return;
}
@@ -106,14 +106,14 @@ namespace Ryujinx.Ava.Utilities
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
Directory.CreateDirectory(iconPath);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
CreateShortcutLinux(applicationFilePath, applicationId, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName, args);
return;
}
if (OperatingSystem.IsMacOS())
{
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName);
CreateShortcutMacos(applicationFilePath, applicationId, iconData, desktopPath, cleanedAppName, args);
return;
}
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.Utilities
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
}
private static string GetArgsString(string appFilePath, string applicationId)
private static string GetArgsString(string appFilePath, string applicationId, string config = "")
{
// args are first defined as a list, for easier adjustments in the future
List<string> argsList = [];
@@ -132,6 +132,11 @@ namespace Ryujinx.Ava.Utilities
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
}
if (!string.IsNullOrEmpty(config))
{
argsList.Add(config);
}
if (appFilePath.ToLower().EndsWith(".xci"))
{
argsList.Add("--application-id");