diff --git a/3rdparty/qt/.gitignore b/3rdparty/qt/.gitignore
new file mode 100644
index 000000000..2d7fc1300
--- /dev/null
+++ b/3rdparty/qt/.gitignore
@@ -0,0 +1,3 @@
+/*
+!.gitignore
+!README.md
diff --git a/3rdparty/qt/README.md b/3rdparty/qt/README.md
new file mode 100644
index 000000000..2d19550e4
--- /dev/null
+++ b/3rdparty/qt/README.md
@@ -0,0 +1,11 @@
+# PCSX2 Qt Binaries
+
+## Windows
+
+You can download the binaries from https://github.com/PCSX2/pcsx2-windows-dependencies
+
+Grab the latest release without symbols (or, if you want to debug Qt, with symbols), and extract it to this directory.
+
+## Linux
+
+TODO
\ No newline at end of file
diff --git a/PCSX2_qt.sln b/PCSX2_qt.sln
new file mode 100644
index 000000000..e68711dad
--- /dev/null
+++ b/PCSX2_qt.sln
@@ -0,0 +1,656 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31606.5
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "3rdparty", "3rdparty", "{78EBE642-7A4D-4EA7-86BE-5639C6646C38}"
+ ProjectSection(SolutionItems) = preProject
+ 3rdparty\svn_readme.txt = 3rdparty\svn_readme.txt
+ EndProjectSection
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{2D6F0A62-A247-4CCF-947F-FCD54BE16103}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SoundTouch", "3rdparty\soundtouch\SoundTouch.vcxproj", "{E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "3rdparty\zlib\zlib.vcxproj", "{2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bin2cpp", "tools\bin2cpp\bin2c.vcxproj", "{677B7D11-D5E1-40B3-88B1-9A4DF83D2213}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libjpeg", "3rdparty\libjpeg\libjpeg.vcxproj", "{BC236261-77E8-4567-8D09-45CD02965EB6}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wx30_config", "3rdparty\wxwidgets3.0\build\msw\wx30_config.vcxproj", "{01F4CE10-2CFB-41A8-B41F-E54337868A1D}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "wx30_base", "3rdparty\wxwidgets3.0\build\msw\wx30_base.vcxproj", "{3FCC50C2-81E9-5DB2-B8D8-2129427568B1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libpng", "3rdparty\libpng\projects\vstudio\libpng\libpng.vcxproj", "{D6973076-9317-4EF2-A0B8-B7A18AC0713E}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pthreads4w", "3rdparty\pthreads4w\build\pthreads4w.vcxproj", "{0FAE817D-9A32-4830-857E-81DA57246E16}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "baseclasses", "3rdparty\baseclasses\baseclasses.vcxproj", "{27F17499-A372-4408-8AFA-4F9F4584FBD3}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "liblzma", "3rdparty\xz\liblzma.vcxproj", "{12728250-16EC-4DC6-94D7-E21DD88947F8}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fmt", "3rdparty\fmt\fmt.vcxproj", "{449AD25E-424A-4714-BABC-68706CDCC33B}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsamplerate", "3rdparty\libsamplerate\libsamplerate.vcxproj", "{47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libchdr", "3rdparty\libchdr\libchdr.vcxproj", "{A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jpgd", "3rdparty\jpgd\jpgd.vcxproj", "{ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common\common.vcxproj", "{4639972E-424E-4E13-8B07-CA403C481346}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2core", "pcsx2\pcsx2core.vcxproj", "{6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glad", "3rdparty\glad\glad.vcxproj", "{C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "imgui", "3rdparty\imgui\imgui.vcxproj", "{88FB34EC-845E-4F21-A552-F1573B9ED167}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2-qt", "pcsx2-qt\pcsx2-qt.vcxproj", "{2A016F21-87AE-4154-8271-1F57E91408E9}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "simpleini", "3rdparty\simpleini\simpleini.vcxproj", "{1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cubeb", "3rdparty\cubeb\cubeb.vcxproj", "{BF74C473-DC04-44B3-92E8-4145F4E77342}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ryml", "3rdparty\rapidyaml\ryml.vcxproj", "{DE9653B6-17DD-356A-9EE0-28A731772587}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glslang", "3rdparty\glslang\glslang.vcxproj", "{EF6834A9-11F3-4331-BC34-21B325ABB180}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug AVX2|Win32 = Debug AVX2|Win32
+ Debug AVX2|x64 = Debug AVX2|x64
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Devel AVX2|Win32 = Devel AVX2|Win32
+ Devel AVX2|x64 = Devel AVX2|x64
+ Devel|Win32 = Devel|Win32
+ Devel|x64 = Devel|x64
+ Release AVX2|Win32 = Release AVX2|Win32
+ Release AVX2|x64 = Release AVX2|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug AVX2|x64.Build.0 = Debug|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|Win32.Build.0 = Debug|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|x64.ActiveCfg = Debug|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Debug|x64.Build.0 = Debug|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel AVX2|x64.Build.0 = Devel|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|Win32.ActiveCfg = Devel|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|Win32.Build.0 = Devel|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|x64.ActiveCfg = Devel|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Devel|x64.Build.0 = Devel|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|Win32.Build.0 = Release|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|x64.ActiveCfg = Release|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release AVX2|x64.Build.0 = Release|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|Win32.ActiveCfg = Release|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|Win32.Build.0 = Release|Win32
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|x64.ActiveCfg = Release|x64
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D}.Release|x64.Build.0 = Release|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug AVX2|x64.Build.0 = Debug|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|Win32.ActiveCfg = Debug|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|Win32.Build.0 = Debug|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|x64.ActiveCfg = Debug|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Debug|x64.Build.0 = Debug|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel AVX2|x64.Build.0 = Devel|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|Win32.ActiveCfg = Devel|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|Win32.Build.0 = Devel|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|x64.ActiveCfg = Devel|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Devel|x64.Build.0 = Devel|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|Win32.Build.0 = Release|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|x64.ActiveCfg = Release|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release AVX2|x64.Build.0 = Release|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|Win32.ActiveCfg = Release|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|Win32.Build.0 = Release|Win32
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|x64.ActiveCfg = Release|x64
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47}.Release|x64.Build.0 = Release|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug AVX2|x64.Build.0 = Debug|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|Win32.ActiveCfg = Debug|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|Win32.Build.0 = Debug|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|x64.ActiveCfg = Debug|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Debug|x64.Build.0 = Debug|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel AVX2|x64.Build.0 = Devel|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|Win32.ActiveCfg = Devel|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|Win32.Build.0 = Devel|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|x64.ActiveCfg = Devel|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Devel|x64.Build.0 = Devel|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|Win32.Build.0 = Release|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|x64.ActiveCfg = Release|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release AVX2|x64.Build.0 = Release|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|Win32.ActiveCfg = Release|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|Win32.Build.0 = Release|Win32
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|x64.ActiveCfg = Release|x64
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213}.Release|x64.Build.0 = Release|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug AVX2|x64.Build.0 = Debug|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|Win32.ActiveCfg = Debug|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|Win32.Build.0 = Debug|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|x64.ActiveCfg = Debug|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Debug|x64.Build.0 = Debug|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel AVX2|x64.Build.0 = Devel|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|Win32.ActiveCfg = Devel|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|Win32.Build.0 = Devel|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|x64.ActiveCfg = Devel|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Devel|x64.Build.0 = Devel|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|Win32.Build.0 = Release|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|x64.ActiveCfg = Release|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release AVX2|x64.Build.0 = Release|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|Win32.ActiveCfg = Release|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|Win32.Build.0 = Release|Win32
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|x64.ActiveCfg = Release|x64
+ {BC236261-77E8-4567-8D09-45CD02965EB6}.Release|x64.Build.0 = Release|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug AVX2|x64.Build.0 = Debug|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|Win32.Build.0 = Debug|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|x64.ActiveCfg = Debug|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Debug|x64.Build.0 = Debug|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel AVX2|x64.Build.0 = Devel|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|Win32.ActiveCfg = Devel|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|Win32.Build.0 = Devel|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|x64.ActiveCfg = Devel|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Devel|x64.Build.0 = Devel|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|Win32.Build.0 = Release|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|x64.ActiveCfg = Release|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release AVX2|x64.Build.0 = Release|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|Win32.ActiveCfg = Release|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|Win32.Build.0 = Release|Win32
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|x64.ActiveCfg = Release|x64
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D}.Release|x64.Build.0 = Release|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug AVX2|x64.Build.0 = Debug|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|Win32.ActiveCfg = Debug|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|Win32.Build.0 = Debug|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|x64.ActiveCfg = Debug|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Debug|x64.Build.0 = Debug|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel AVX2|x64.Build.0 = Devel|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|Win32.ActiveCfg = Devel|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|Win32.Build.0 = Devel|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|x64.ActiveCfg = Devel|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Devel|x64.Build.0 = Devel|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|Win32.Build.0 = Release|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|x64.ActiveCfg = Release|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release AVX2|x64.Build.0 = Release|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|Win32.ActiveCfg = Release|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|Win32.Build.0 = Release|Win32
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|x64.ActiveCfg = Release|x64
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1}.Release|x64.Build.0 = Release|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug AVX2|x64.Build.0 = Debug|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Win32.ActiveCfg = Debug|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|Win32.Build.0 = Debug|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x64.ActiveCfg = Debug|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Debug|x64.Build.0 = Debug|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel AVX2|x64.Build.0 = Devel|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|Win32.ActiveCfg = Devel|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|Win32.Build.0 = Devel|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|x64.ActiveCfg = Devel|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Devel|x64.Build.0 = Devel|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|Win32.Build.0 = Release|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|x64.ActiveCfg = Release|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release AVX2|x64.Build.0 = Release|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Win32.ActiveCfg = Release|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|Win32.Build.0 = Release|Win32
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x64.ActiveCfg = Release|x64
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E}.Release|x64.Build.0 = Release|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug AVX2|x64.Build.0 = Debug|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|Win32.ActiveCfg = Debug|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|Win32.Build.0 = Debug|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|x64.ActiveCfg = Debug|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Debug|x64.Build.0 = Debug|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel AVX2|x64.Build.0 = Devel|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|Win32.ActiveCfg = Devel|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|Win32.Build.0 = Devel|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|x64.ActiveCfg = Devel|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Devel|x64.Build.0 = Devel|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|Win32.Build.0 = Release|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|x64.ActiveCfg = Release|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release AVX2|x64.Build.0 = Release|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|Win32.ActiveCfg = Release|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|Win32.Build.0 = Release|Win32
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|x64.ActiveCfg = Release|x64
+ {0FAE817D-9A32-4830-857E-81DA57246E16}.Release|x64.Build.0 = Release|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug AVX2|x64.Build.0 = Debug|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|Win32.ActiveCfg = Debug|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|Win32.Build.0 = Debug|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|x64.ActiveCfg = Debug|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Debug|x64.Build.0 = Debug|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel AVX2|x64.Build.0 = Devel|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|Win32.ActiveCfg = Devel|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|Win32.Build.0 = Devel|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|x64.ActiveCfg = Devel|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Devel|x64.Build.0 = Devel|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|Win32.Build.0 = Release|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|x64.ActiveCfg = Release|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release AVX2|x64.Build.0 = Release|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|Win32.ActiveCfg = Release|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|Win32.Build.0 = Release|Win32
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|x64.ActiveCfg = Release|x64
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3}.Release|x64.Build.0 = Release|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug AVX2|x64.Build.0 = Debug|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|Win32.ActiveCfg = Debug|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|Win32.Build.0 = Debug|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|x64.ActiveCfg = Debug|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Debug|x64.Build.0 = Debug|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel AVX2|x64.Build.0 = Devel|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|Win32.ActiveCfg = Devel|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|Win32.Build.0 = Devel|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|x64.ActiveCfg = Devel|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Devel|x64.Build.0 = Devel|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|Win32.Build.0 = Release|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|x64.ActiveCfg = Release|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release AVX2|x64.Build.0 = Release|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.ActiveCfg = Release|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|Win32.Build.0 = Release|Win32
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.ActiveCfg = Release|x64
+ {12728250-16EC-4DC6-94D7-E21DD88947F8}.Release|x64.Build.0 = Release|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug AVX2|x64.Build.0 = Debug|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|Win32.ActiveCfg = Debug|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|Win32.Build.0 = Debug|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|x64.ActiveCfg = Debug|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Debug|x64.Build.0 = Debug|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel AVX2|x64.Build.0 = Devel|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|Win32.ActiveCfg = Devel|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|Win32.Build.0 = Devel|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|x64.ActiveCfg = Devel|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Devel|x64.Build.0 = Devel|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|Win32.Build.0 = Release|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|x64.ActiveCfg = Release|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release AVX2|x64.Build.0 = Release|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|Win32.ActiveCfg = Release|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|Win32.Build.0 = Release|Win32
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|x64.ActiveCfg = Release|x64
+ {449AD25E-424A-4714-BABC-68706CDCC33B}.Release|x64.Build.0 = Release|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug AVX2|x64.Build.0 = Debug|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|Win32.Build.0 = Debug|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|x64.ActiveCfg = Debug|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Debug|x64.Build.0 = Debug|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel AVX2|x64.Build.0 = Devel|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|Win32.ActiveCfg = Devel|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|Win32.Build.0 = Devel|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|x64.ActiveCfg = Devel|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Devel|x64.Build.0 = Devel|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|Win32.Build.0 = Release|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|x64.ActiveCfg = Release|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release AVX2|x64.Build.0 = Release|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|Win32.ActiveCfg = Release|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|Win32.Build.0 = Release|Win32
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|x64.ActiveCfg = Release|x64
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F}.Release|x64.Build.0 = Release|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug AVX2|x64.Build.0 = Debug|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|Win32.ActiveCfg = Debug|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|Win32.Build.0 = Debug|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|x64.ActiveCfg = Debug|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Debug|x64.Build.0 = Debug|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel AVX2|x64.Build.0 = Devel|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|Win32.ActiveCfg = Devel|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|Win32.Build.0 = Devel|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|x64.ActiveCfg = Devel|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Devel|x64.Build.0 = Devel|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|Win32.Build.0 = Release|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|x64.ActiveCfg = Release|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release AVX2|x64.Build.0 = Release|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|Win32.ActiveCfg = Release|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|Win32.Build.0 = Release|Win32
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|x64.ActiveCfg = Release|x64
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0}.Release|x64.Build.0 = Release|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug AVX2|x64.Build.0 = Debug|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|Win32.ActiveCfg = Debug|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|Win32.Build.0 = Debug|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|x64.ActiveCfg = Debug|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Debug|x64.Build.0 = Debug|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel AVX2|x64.Build.0 = Devel|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|Win32.ActiveCfg = Devel|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|Win32.Build.0 = Devel|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|x64.ActiveCfg = Devel|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Devel|x64.Build.0 = Devel|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|Win32.Build.0 = Release|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|x64.ActiveCfg = Release|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release AVX2|x64.Build.0 = Release|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|Win32.ActiveCfg = Release|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|Win32.Build.0 = Release|Win32
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|x64.ActiveCfg = Release|x64
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63}.Release|x64.Build.0 = Release|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug AVX2|x64.Build.0 = Debug|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug|Win32.ActiveCfg = Debug|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug|Win32.Build.0 = Debug|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug|x64.ActiveCfg = Debug|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Debug|x64.Build.0 = Debug|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel AVX2|x64.Build.0 = Devel|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel|Win32.ActiveCfg = Devel|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel|Win32.Build.0 = Devel|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel|x64.ActiveCfg = Devel|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Devel|x64.Build.0 = Devel|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|Win32.Build.0 = Release|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|x64.ActiveCfg = Release|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release AVX2|x64.Build.0 = Release|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release|Win32.ActiveCfg = Release|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release|Win32.Build.0 = Release|Win32
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release|x64.ActiveCfg = Release|x64
+ {4639972E-424E-4E13-8B07-CA403C481346}.Release|x64.Build.0 = Release|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|Win32.ActiveCfg = Debug AVX2|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|Win32.Build.0 = Debug AVX2|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|x64.ActiveCfg = Debug AVX2|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug AVX2|x64.Build.0 = Debug AVX2|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|Win32.ActiveCfg = Debug|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|Win32.Build.0 = Debug|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|x64.ActiveCfg = Debug|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Debug|x64.Build.0 = Debug|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|Win32.ActiveCfg = Devel AVX2|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|Win32.Build.0 = Devel AVX2|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|x64.ActiveCfg = Devel AVX2|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel AVX2|x64.Build.0 = Devel AVX2|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|Win32.ActiveCfg = Devel|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|Win32.Build.0 = Devel|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|x64.ActiveCfg = Devel|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Devel|x64.Build.0 = Devel|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|Win32.ActiveCfg = Release AVX2|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|Win32.Build.0 = Release AVX2|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|x64.ActiveCfg = Release AVX2|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release AVX2|x64.Build.0 = Release AVX2|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|Win32.ActiveCfg = Release|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|Win32.Build.0 = Release|Win32
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|x64.ActiveCfg = Release|x64
+ {6C7986C4-3E4D-4DCC-B3C6-6BB12B238995}.Release|x64.Build.0 = Release|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug AVX2|x64.Build.0 = Debug|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|Win32.ActiveCfg = Debug|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|Win32.Build.0 = Debug|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|x64.ActiveCfg = Debug|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Debug|x64.Build.0 = Debug|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel AVX2|x64.Build.0 = Devel|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|Win32.ActiveCfg = Devel|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|Win32.Build.0 = Devel|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|x64.ActiveCfg = Devel|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Devel|x64.Build.0 = Devel|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|Win32.Build.0 = Release|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|x64.ActiveCfg = Release|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release AVX2|x64.Build.0 = Release|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|Win32.ActiveCfg = Release|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|Win32.Build.0 = Release|Win32
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|x64.ActiveCfg = Release|x64
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A}.Release|x64.Build.0 = Release|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug AVX2|x64.Build.0 = Debug|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|Win32.ActiveCfg = Debug|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|Win32.Build.0 = Debug|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|x64.ActiveCfg = Debug|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Debug|x64.Build.0 = Debug|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel AVX2|x64.Build.0 = Devel|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|Win32.ActiveCfg = Devel|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|Win32.Build.0 = Devel|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|x64.ActiveCfg = Devel|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Devel|x64.Build.0 = Devel|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|Win32.Build.0 = Release|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|x64.ActiveCfg = Release|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release AVX2|x64.Build.0 = Release|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|Win32.ActiveCfg = Release|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|Win32.Build.0 = Release|Win32
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|x64.ActiveCfg = Release|x64
+ {88FB34EC-845E-4F21-A552-F1573B9ED167}.Release|x64.Build.0 = Release|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|Win32.ActiveCfg = Debug AVX2|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|Win32.Build.0 = Debug AVX2|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|x64.ActiveCfg = Debug AVX2|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug AVX2|x64.Build.0 = Debug AVX2|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|Win32.ActiveCfg = Debug|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|Win32.Build.0 = Debug|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|x64.ActiveCfg = Debug|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Debug|x64.Build.0 = Debug|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|Win32.ActiveCfg = Devel AVX2|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|Win32.Build.0 = Devel AVX2|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|x64.ActiveCfg = Devel AVX2|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel AVX2|x64.Build.0 = Devel AVX2|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|Win32.ActiveCfg = Devel|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|Win32.Build.0 = Devel|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|x64.ActiveCfg = Devel|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Devel|x64.Build.0 = Devel|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|Win32.ActiveCfg = Release AVX2|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|Win32.Build.0 = Release AVX2|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|x64.ActiveCfg = Release AVX2|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release AVX2|x64.Build.0 = Release AVX2|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|Win32.ActiveCfg = Release|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|Win32.Build.0 = Release|Win32
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|x64.ActiveCfg = Release|x64
+ {2A016F21-87AE-4154-8271-1F57E91408E9}.Release|x64.Build.0 = Release|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug AVX2|x64.Build.0 = Debug|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|Win32.ActiveCfg = Debug|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|Win32.Build.0 = Debug|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|x64.ActiveCfg = Debug|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Debug|x64.Build.0 = Debug|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel AVX2|x64.Build.0 = Devel|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|Win32.ActiveCfg = Devel|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|Win32.Build.0 = Devel|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|x64.ActiveCfg = Devel|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Devel|x64.Build.0 = Devel|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|Win32.Build.0 = Release|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|x64.ActiveCfg = Release|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release AVX2|x64.Build.0 = Release|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|Win32.ActiveCfg = Release|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|Win32.Build.0 = Release|Win32
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|x64.ActiveCfg = Release|x64
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266}.Release|x64.Build.0 = Release|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug AVX2|x64.Build.0 = Debug|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|Win32.ActiveCfg = Debug|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|Win32.Build.0 = Debug|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|x64.ActiveCfg = Debug|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Debug|x64.Build.0 = Debug|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel AVX2|x64.Build.0 = Devel|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|Win32.ActiveCfg = Devel|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|Win32.Build.0 = Devel|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|x64.ActiveCfg = Devel|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Devel|x64.Build.0 = Devel|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|Win32.Build.0 = Release|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|x64.ActiveCfg = Release|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release AVX2|x64.Build.0 = Release|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|Win32.ActiveCfg = Release|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|Win32.Build.0 = Release|Win32
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|x64.ActiveCfg = Release|x64
+ {BF74C473-DC04-44B3-92E8-4145F4E77342}.Release|x64.Build.0 = Release|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug AVX2|x64.Build.0 = Debug|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|Win32.Build.0 = Debug|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|x64.ActiveCfg = Debug|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Debug|x64.Build.0 = Debug|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel AVX2|x64.Build.0 = Devel|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|Win32.ActiveCfg = Devel|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|Win32.Build.0 = Devel|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|x64.ActiveCfg = Devel|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Devel|x64.Build.0 = Devel|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|Win32.Build.0 = Release|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|x64.ActiveCfg = Release|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release AVX2|x64.Build.0 = Release|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|Win32.ActiveCfg = Release|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|Win32.Build.0 = Release|Win32
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|x64.ActiveCfg = Release|x64
+ {DE9653B6-17DD-356A-9EE0-28A731772587}.Release|x64.Build.0 = Release|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|Win32.ActiveCfg = Debug|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|Win32.Build.0 = Debug|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|x64.ActiveCfg = Debug|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug AVX2|x64.Build.0 = Debug|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|Win32.ActiveCfg = Debug|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|Win32.Build.0 = Debug|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|x64.ActiveCfg = Debug|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Debug|x64.Build.0 = Debug|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|Win32.ActiveCfg = Devel|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|Win32.Build.0 = Devel|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|x64.ActiveCfg = Devel|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel AVX2|x64.Build.0 = Devel|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|Win32.ActiveCfg = Devel|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|Win32.Build.0 = Devel|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|x64.ActiveCfg = Devel|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Devel|x64.Build.0 = Devel|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|Win32.ActiveCfg = Release|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|Win32.Build.0 = Release|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|x64.ActiveCfg = Release|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release AVX2|x64.Build.0 = Release|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|Win32.ActiveCfg = Release|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|Win32.Build.0 = Release|Win32
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|x64.ActiveCfg = Release|x64
+ {EF6834A9-11F3-4331-BC34-21B325ABB180}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {E9B51944-7E6D-4BCD-83F2-7BBD5A46182D} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {2F6C0388-20CB-4242-9F6C-A6EBB6A83F47} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {677B7D11-D5E1-40B3-88B1-9A4DF83D2213} = {2D6F0A62-A247-4CCF-947F-FCD54BE16103}
+ {BC236261-77E8-4567-8D09-45CD02965EB6} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {01F4CE10-2CFB-41A8-B41F-E54337868A1D} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {3FCC50C2-81E9-5DB2-B8D8-2129427568B1} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {D6973076-9317-4EF2-A0B8-B7A18AC0713E} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {0FAE817D-9A32-4830-857E-81DA57246E16} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {27F17499-A372-4408-8AFA-4F9F4584FBD3} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {12728250-16EC-4DC6-94D7-E21DD88947F8} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {449AD25E-424A-4714-BABC-68706CDCC33B} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {47AFDBEF-F15F-4BC0-B436-5BE443C3F80F} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {A0D2B3AD-1F72-4EE3-8B5C-F2C358DA35F0} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {ED2F21FD-0A36-4A8F-9B90-E7D92A2ACB63} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {C0293B32-5ACF-40F0-AA6C-E6DA6F3BF33A} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {88FB34EC-845E-4F21-A552-F1573B9ED167} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {1EC8B3C0-8FB3-46DE-A2E0-A9121203F266} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {BF74C473-DC04-44B3-92E8-4145F4E77342} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {DE9653B6-17DD-356A-9EE0-28A731772587} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ {EF6834A9-11F3-4331-BC34-21B325ABB180} = {78EBE642-7A4D-4EA7-86BE-5639C6646C38}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0BC474EA-3628-45D3-9DBC-E22D0B7E0F77}
+ EndGlobalSection
+EndGlobal
diff --git a/bin/resources/cover-placeholder.png b/bin/resources/cover-placeholder.png
new file mode 100644
index 000000000..2108b4656
Binary files /dev/null and b/bin/resources/cover-placeholder.png differ
diff --git a/cmake/SearchForStuff.cmake b/cmake/SearchForStuff.cmake
index 1e4e07333..63934ff50 100644
--- a/cmake/SearchForStuff.cmake
+++ b/cmake/SearchForStuff.cmake
@@ -232,6 +232,10 @@ if(NOT USE_SYSTEM_YAML)
endif()
endif()
+if(QT_BUILD)
+ find_package(Qt6 COMPONENTS Core Gui Widgets Network LinguistTools REQUIRED)
+endif()
+
add_subdirectory(3rdparty/libchdr/libchdr EXCLUDE_FROM_ALL)
if(USE_NATIVE_TOOLS)
diff --git a/common/vsprops/QtCompile.props b/common/vsprops/QtCompile.props
new file mode 100644
index 000000000..a3bdfb5d2
--- /dev/null
+++ b/common/vsprops/QtCompile.props
@@ -0,0 +1,197 @@
+
+
+
+ $(SolutionDir)bin\
+ $(SolutionDir)3rdparty\qt\6.2.2\msvc2019_64\
+ $(SolutionDir)3rdparty\qt\6.2.2\msvc2019_arm64\
+ $(QTDIRDefault)
+ $(QTDIR)\
+ $(SolutionDir)3rdparty\qt\6.2.2\msvc2019_64\
+ false
+ true
+ $(QTDIR)include\
+ $(QTDIR)lib\
+ $(QTDIR)bin\
+ $(QTDIRHost)bin\
+ $(QTDIR)plugins\
+ $(QTDIR)translations\
+ $(IntDir)
+ $(QtToolOutDir)moc_
+ $(BinaryOutputDir)translations\
+ d
+ $(QtDebugSuffix)
+ QtPlugins
+
+
+
+ QT_NO_DEBUG;%(PreprocessorDefinitions)
+ $(ProjectDir);%(AdditionalIncludeDirectories)
+ $(QtToolOutDir);%(AdditionalIncludeDirectories)
+ $(QtIncludeDir);%(AdditionalIncludeDirectories)
+
+
+ $(QtLibDir);%(AdditionalLibraryDirectories)
+ Qt6Core$(QtLibSuffix).lib;Qt6Gui$(QtLibSuffix).lib;Qt6Widgets$(QtLibSuffix).lib;Qt6Network$(QtLibSuffix).lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -DQT_NO_DEBUG -DNDEBUG $(MocDefines)
+ "-I$(QtIncludeDir)" "-I$(SolutionDir)pcsx2" "-I$(SolutionDir)" -I.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ QtResource
+
+
+ QtUi
+
+
+ QtMoc
+
+
+ QtTs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(BinaryOutputDir)qt.conf
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/common/vsprops/QtCompile.targets b/common/vsprops/QtCompile.targets
new file mode 100644
index 000000000..75ac29f30
--- /dev/null
+++ b/common/vsprops/QtCompile.targets
@@ -0,0 +1,10 @@
+
+
+
+ QtResourceClean;QtUiClean;QtMocClean;QtTsClean;$(CleanDependsOn)
+
+
+
\ No newline at end of file
diff --git a/common/vsprops/QtCompile.xml b/common/vsprops/QtCompile.xml
new file mode 100644
index 000000000..376118df7
--- /dev/null
+++ b/common/vsprops/QtCompile.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/pcsx2-qt/AboutDialog.cpp b/pcsx2-qt/AboutDialog.cpp
new file mode 100644
index 000000000..5818bf45e
--- /dev/null
+++ b/pcsx2-qt/AboutDialog.cpp
@@ -0,0 +1,76 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "AboutDialog.h"
+#include "QtUtils.h"
+#include "svnrev.h"
+#include
+#include
+
+AboutDialog::AboutDialog(QWidget* parent)
+ : QDialog(parent)
+{
+ m_ui.setupUi(this);
+
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+ setFixedSize(geometry().width(), geometry().height());
+
+ m_ui.scmversion->setTextInteractionFlags(Qt::TextSelectableByMouse);
+ m_ui.scmversion->setText(GIT_REV);
+
+ m_ui.links->setTextInteractionFlags(Qt::TextBrowserInteraction);
+ m_ui.links->setOpenExternalLinks(true);
+ m_ui.links->setText(QStringLiteral(
+ R"(%2 | %4 | %6 | %8)")
+ .arg(getWebsiteUrl())
+ .arg(tr("Website"))
+ .arg(getSupportForumsUrl())
+ .arg(tr("Support Forums"))
+ .arg(getGitHubRepositoryUrl())
+ .arg(tr("GitHub Repository"))
+ .arg(getLicenseUrl())
+ .arg(tr("License")));
+
+ connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close);
+}
+
+AboutDialog::~AboutDialog() = default;
+
+QString AboutDialog::getWebsiteUrl()
+{
+ return QStringLiteral("https://pcsx2.net/");
+}
+
+QString AboutDialog::getSupportForumsUrl()
+{
+ return QStringLiteral("https://forums.pcsx2.net/");
+}
+
+QString AboutDialog::getGitHubRepositoryUrl()
+{
+ return QStringLiteral("https://github.com/PCSX2/pcsx2");
+}
+
+QString AboutDialog::getLicenseUrl()
+{
+ return QStringLiteral("https://github.com/PCSX2/pcsx2/blob/master/pcsx2/Docs/License.txt");
+}
+
+QString AboutDialog::getDiscordServerUrl()
+{
+ return QStringLiteral("https://discord.com/invite/TCz3t9k");
+}
diff --git a/pcsx2-qt/AboutDialog.h b/pcsx2-qt/AboutDialog.h
new file mode 100644
index 000000000..74109bc05
--- /dev/null
+++ b/pcsx2-qt/AboutDialog.h
@@ -0,0 +1,37 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include "ui_AboutDialog.h"
+#include
+
+class AboutDialog final : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit AboutDialog(QWidget* parent = nullptr);
+ ~AboutDialog();
+
+ static QString getWebsiteUrl();
+ static QString getSupportForumsUrl();
+ static QString getGitHubRepositoryUrl();
+ static QString getLicenseUrl();
+ static QString getDiscordServerUrl();
+
+private:
+ Ui::AboutDialog m_ui;
+};
diff --git a/pcsx2-qt/AboutDialog.ui b/pcsx2-qt/AboutDialog.ui
new file mode 100644
index 000000000..1212f9587
--- /dev/null
+++ b/pcsx2-qt/AboutDialog.ui
@@ -0,0 +1,168 @@
+
+
+ AboutDialog
+
+
+
+ 0
+ 0
+ 576
+ 294
+
+
+
+ About PCSX2
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+
+ 260
+ 260
+
+
+
+
+
+
+ :/icons/logo.png
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ SCM Version
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ <html><head/><body><p>PCSX2 is a free and open-source PlayStation 2 (PS2) emulator. Its purpose is to emulate the PS2's hardware, using a combination of MIPS CPU Interpreters, Recompilers and a Virtual Machine which manages hardware states and PS2 system memory. This allows you to play PS2 games on your PC, with many additional features and benefits.</p></body></html>
+
+
+ Qt::AlignJustify|Qt::AlignVCenter
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ <html><head/><body><p>PlayStation 2 and PS2 are registered trademarks of Sony Interactive Entertainment. This application is not affiliated in any way with Sony Interactive Entertainment.</p></body></html>
+
+
+ Qt::AlignJustify|Qt::AlignVCenter
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ TextLabel
+
+
+ Qt::AlignCenter
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ QDialogButtonBox::Close
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt
new file mode 100644
index 000000000..0beb5bed9
--- /dev/null
+++ b/pcsx2-qt/CMakeLists.txt
@@ -0,0 +1,102 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+add_executable(pcsx2-qt)
+
+if (PACKAGE_MODE)
+ install(TARGETS pcsx2-qt DESTINATION ${CMAKE_INSTALL_BINDIR})
+else()
+ install(TARGETS pcsx2-qt DESTINATION ${CMAKE_SOURCE_DIR}/bin)
+endif()
+
+target_sources(pcsx2-qt PRIVATE
+ AboutDialog.cpp
+ AboutDialog.h
+ AboutDialog.ui
+ DisplayWidget.cpp
+ DisplayWidget.h
+ EmuThread.cpp
+ EmuThread.h
+ Main.cpp
+ MainWindow.cpp
+ MainWindow.h
+ MainWindow.ui
+ PrecompiledHeader.cpp
+ PrecompiledHeader.h
+ QtHost.cpp
+ QtHost.h
+ QtKeyCodes.cpp
+ QtUtils.cpp
+ QtUtils.h
+ SettingWidgetBinder.h
+ GameList/GameListModel.cpp
+ GameList/GameListModel.h
+ GameList/GameListRefreshThread.cpp
+ GameList/GameListRefreshThread.h
+ GameList/GameListWidget.cpp
+ GameList/GameListWidget.h
+ Settings/AdvancedSystemSettingsWidget.cpp
+ Settings/AdvancedSystemSettingsWidget.h
+ Settings/AdvancedSystemSettingsWidget.ui
+ Settings/BIOSSettingsWidget.cpp
+ Settings/BIOSSettingsWidget.h
+ Settings/BIOSSettingsWidget.ui
+ Settings/ControllerBindingWidget.ui
+ Settings/ControllerBindingWidget_DualShock2.ui
+ Settings/ControllerBindingWidgets.cpp
+ Settings/ControllerBindingWidgets.h
+ Settings/ControllerGlobalSettingsWidget.cpp
+ Settings/ControllerGlobalSettingsWidget.h
+ Settings/ControllerGlobalSettingsWidget.ui
+ Settings/ControllerSettingsDialog.cpp
+ Settings/ControllerSettingsDialog.h
+ Settings/ControllerSettingsDialog.ui
+ Settings/EmulationSettingsWidget.cpp
+ Settings/EmulationSettingsWidget.h
+ Settings/EmulationSettingsWidget.ui
+ Settings/GameFixSettingsWidget.cpp
+ Settings/GameFixSettingsWidget.h
+ Settings/GameFixSettingsWidget.ui
+ Settings/GameListSettingsWidget.cpp
+ Settings/GameListSettingsWidget.h
+ Settings/GameListSettingsWidget.ui
+ Settings/GraphicsSettingsWidget.cpp
+ Settings/GraphicsSettingsWidget.h
+ Settings/GraphicsSettingsWidget.ui
+ Settings/HotkeySettingsWidget.cpp
+ Settings/HotkeySettingsWidget.h
+ Settings/InputBindingDialog.cpp
+ Settings/InputBindingDialog.h
+ Settings/InputBindingDialog.ui
+ Settings/InputBindingWidget.cpp
+ Settings/InputBindingWidget.h
+ Settings/InterfaceSettingsWidget.cpp
+ Settings/InterfaceSettingsWidget.h
+ Settings/InterfaceSettingsWidget.ui
+ Settings/SettingsDialog.cpp
+ Settings/SettingsDialog.h
+ Settings/SettingsDialog.ui
+ Settings/SystemSettingsWidget.cpp
+ Settings/SystemSettingsWidget.h
+ Settings/SystemSettingsWidget.ui
+ resources/resources.qrc
+)
+
+target_precompile_headers(pcsx2-qt PRIVATE PrecompiledHeader.h)
+
+target_include_directories(pcsx2-qt PRIVATE
+ ${Qt6Gui_PRIVATE_INCLUDE_DIRS}
+ "${CMAKE_BINARY_DIR}/common/include"
+ "${CMAKE_SOURCE_DIR}/pcsx2"
+ "${CMAKE_SOURCE_DIR}/pcsx2-qt"
+)
+
+target_link_libraries(pcsx2-qt PRIVATE
+ PCSX2_FLAGS
+ PCSX2
+ Qt6::Core
+ Qt6::Gui
+ Qt6::Widgets
+ Qt6::Network
+)
diff --git a/pcsx2-qt/DisplayWidget.cpp b/pcsx2-qt/DisplayWidget.cpp
new file mode 100644
index 000000000..f96ba2596
--- /dev/null
+++ b/pcsx2-qt/DisplayWidget.cpp
@@ -0,0 +1,334 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "DisplayWidget.h"
+#include "EmuThread.h"
+#include "QtHost.h"
+
+#include "pcsx2/GS/GSIntrin.h" // _BitScanForward
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if !defined(_WIN32) && !defined(APPLE)
+#include
+#endif
+
+DisplayWidget::DisplayWidget(QWidget* parent)
+ : QWidget(parent)
+{
+ // We want a native window for both D3D and OpenGL.
+ setAutoFillBackground(false);
+ setAttribute(Qt::WA_NativeWindow, true);
+ setAttribute(Qt::WA_NoSystemBackground, true);
+ setAttribute(Qt::WA_PaintOnScreen, true);
+ setFocusPolicy(Qt::StrongFocus);
+ setMouseTracking(true);
+}
+
+DisplayWidget::~DisplayWidget() = default;
+
+qreal DisplayWidget::devicePixelRatioFromScreen() const
+{
+ QScreen* screen_for_ratio;
+#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
+ screen_for_ratio = windowHandle()->screen();
+#else
+ screen_for_ratio = screen();
+#endif
+ if (!screen_for_ratio)
+ screen_for_ratio = QGuiApplication::primaryScreen();
+
+ return screen_for_ratio ? screen_for_ratio->devicePixelRatio() : static_cast(1);
+}
+
+int DisplayWidget::scaledWindowWidth() const
+{
+ return static_cast(std::ceil(static_cast(width()) * devicePixelRatioFromScreen()));
+}
+
+int DisplayWidget::scaledWindowHeight() const
+{
+ return static_cast(std::ceil(static_cast(height()) * devicePixelRatioFromScreen()));
+}
+
+std::optional DisplayWidget::getWindowInfo() const
+{
+ WindowInfo wi;
+
+ // Windows and Apple are easy here since there's no display connection.
+#if defined(_WIN32)
+ wi.type = WindowInfo::Type::Win32;
+ wi.window_handle = reinterpret_cast(winId());
+#elif defined(__APPLE__)
+ wi.type = WindowInfo::Type::MacOS;
+ wi.window_handle = reinterpret_cast(winId());
+#else
+ QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
+ const QString platform_name = QGuiApplication::platformName();
+ if (platform_name == QStringLiteral("xcb"))
+ {
+ wi.type = WindowInfo::Type::X11;
+ wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
+ wi.window_handle = reinterpret_cast(winId());
+ }
+ else if (platform_name == QStringLiteral("wayland"))
+ {
+ wi.type = WindowInfo::Type::Wayland;
+ wi.display_connection = pni->nativeResourceForWindow("display", windowHandle());
+ wi.window_handle = pni->nativeResourceForWindow("surface", windowHandle());
+ }
+ else
+ {
+ qCritical() << "Unknown PNI platform " << platform_name;
+ return std::nullopt;
+ }
+#endif
+
+ wi.surface_width = scaledWindowWidth();
+ wi.surface_height = scaledWindowHeight();
+ wi.surface_scale = devicePixelRatioFromScreen();
+ return wi;
+}
+
+void DisplayWidget::setRelativeMode(bool enabled)
+{
+ if (m_relative_mouse_enabled == enabled)
+ return;
+
+ if (enabled)
+ {
+ m_relative_mouse_start_position = QCursor::pos();
+
+ const QPoint center_pos = mapToGlobal(QPoint(width() / 2, height() / 2));
+ QCursor::setPos(center_pos);
+ m_relative_mouse_last_position = center_pos;
+ grabMouse();
+ }
+ else
+ {
+ QCursor::setPos(m_relative_mouse_start_position);
+ releaseMouse();
+ }
+
+ m_relative_mouse_enabled = enabled;
+}
+
+QPaintEngine* DisplayWidget::paintEngine() const
+{
+ return nullptr;
+}
+
+bool DisplayWidget::event(QEvent* event)
+{
+ switch (event->type())
+ {
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ {
+ const QKeyEvent* key_event = static_cast(event);
+ if (!key_event->isAutoRepeat())
+ {
+ emit windowKeyEvent(key_event->key(), static_cast(key_event->modifiers()),
+ event->type() == QEvent::KeyPress);
+ }
+
+ return true;
+ }
+
+ case QEvent::MouseMove:
+ {
+ const QMouseEvent* mouse_event = static_cast(event);
+
+ if (!m_relative_mouse_enabled)
+ {
+ const qreal dpr = devicePixelRatioFromScreen();
+ const QPoint mouse_pos = mouse_event->pos();
+ const int scaled_x = static_cast(static_cast(mouse_pos.x()) * dpr);
+ const int scaled_y = static_cast(static_cast(mouse_pos.y()) * dpr);
+
+ windowMouseMoveEvent(scaled_x, scaled_y);
+ }
+ else
+ {
+ const QPoint center_pos = mapToGlobal(QPoint((width() + 1) / 2, (height() + 1) / 2));
+ const QPoint mouse_pos = mapToGlobal(mouse_event->pos());
+
+ const int dx = mouse_pos.x() - center_pos.x();
+ const int dy = mouse_pos.y() - center_pos.y();
+ m_relative_mouse_last_position.setX(m_relative_mouse_last_position.x() + dx);
+ m_relative_mouse_last_position.setY(m_relative_mouse_last_position.y() + dy);
+ windowMouseMoveEvent(m_relative_mouse_last_position.x(), m_relative_mouse_last_position.y());
+ QCursor::setPos(center_pos);
+ }
+
+ return true;
+ }
+
+ case QEvent::MouseButtonPress:
+ case QEvent::MouseButtonDblClick:
+ case QEvent::MouseButtonRelease:
+ {
+ unsigned long button_index;
+ if (_BitScanForward(&button_index, static_cast(static_cast(event)->button())))
+ emit windowMouseButtonEvent(static_cast(button_index + 1u), event->type() != QEvent::MouseButtonRelease);
+
+ if (event->type() == QEvent::MouseButtonDblClick &&
+ static_cast(event)->button() == Qt::LeftButton &&
+ Host::GetBoolSettingValue("UI", "DoubleClickTogglesFullscreen", true))
+ {
+ g_emu_thread->toggleFullscreen();
+ }
+
+ return true;
+ }
+
+ case QEvent::Wheel:
+ {
+ const QWheelEvent* wheel_event = static_cast(event);
+ emit windowMouseWheelEvent(wheel_event->angleDelta());
+ return true;
+ }
+
+ case QEvent::Resize:
+ {
+ QWidget::event(event);
+
+ emit windowResizedEvent(scaledWindowWidth(), scaledWindowHeight(), devicePixelRatioFromScreen());
+ return true;
+ }
+
+ case QEvent::Close:
+ {
+ emit windowClosedEvent();
+ QWidget::event(event);
+ return true;
+ }
+
+ case QEvent::WindowStateChange:
+ {
+ QWidget::event(event);
+
+ if (static_cast(event)->oldState() & Qt::WindowMinimized)
+ emit windowRestoredEvent();
+
+ return true;
+ }
+
+ case QEvent::FocusIn:
+ {
+ QWidget::event(event);
+ emit windowFocusEvent();
+ return true;
+ }
+
+ case QEvent::ActivationChange:
+ {
+ QWidget::event(event);
+ if (isActiveWindow())
+ emit windowFocusEvent();
+
+ return true;
+ }
+
+ default:
+ return QWidget::event(event);
+ }
+}
+
+DisplayContainer::DisplayContainer()
+ : QStackedWidget(nullptr)
+{
+}
+
+DisplayContainer::~DisplayContainer() = default;
+
+bool DisplayContainer::IsNeeded(bool fullscreen, bool render_to_main)
+{
+#if defined(_WIN32) || defined(__APPLE__)
+ return false;
+#else
+ if (fullscreen || render_to_main)
+ return false;
+
+ // We only need this on Wayland because of client-side decorations...
+ const QString platform_name = QGuiApplication::platformName();
+ return (platform_name == QStringLiteral("wayland"));
+#endif
+}
+
+void DisplayContainer::setDisplayWidget(DisplayWidget* widget)
+{
+ pxAssert(!m_display_widget);
+ m_display_widget = widget;
+ addWidget(widget);
+}
+
+DisplayWidget* DisplayContainer::removeDisplayWidget()
+{
+ DisplayWidget* widget = m_display_widget;
+ pxAssert(widget);
+ m_display_widget = nullptr;
+ removeWidget(widget);
+ return widget;
+}
+
+bool DisplayContainer::event(QEvent* event)
+{
+ const bool res = QStackedWidget::event(event);
+ if (!m_display_widget)
+ return res;
+
+ switch (event->type())
+ {
+ case QEvent::Close:
+ {
+ emit m_display_widget->windowClosedEvent();
+ }
+ break;
+
+ case QEvent::WindowStateChange:
+ {
+ if (static_cast(event)->oldState() & Qt::WindowMinimized)
+ emit m_display_widget->windowRestoredEvent();
+ }
+ break;
+
+ case QEvent::FocusIn:
+ {
+ emit m_display_widget->windowFocusEvent();
+ }
+ break;
+
+ case QEvent::ActivationChange:
+ {
+ if (isActiveWindow())
+ emit m_display_widget->windowFocusEvent();
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return res;
+}
diff --git a/pcsx2-qt/DisplayWidget.h b/pcsx2-qt/DisplayWidget.h
new file mode 100644
index 000000000..cffc351f9
--- /dev/null
+++ b/pcsx2-qt/DisplayWidget.h
@@ -0,0 +1,77 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "common/WindowInfo.h"
+#include
+#include
+#include
+
+class DisplayWidget final : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit DisplayWidget(QWidget* parent);
+ ~DisplayWidget();
+
+ QPaintEngine* paintEngine() const override;
+
+ int scaledWindowWidth() const;
+ int scaledWindowHeight() const;
+ qreal devicePixelRatioFromScreen() const;
+
+ std::optional getWindowInfo() const;
+
+ void setRelativeMode(bool enabled);
+
+Q_SIGNALS:
+ void windowFocusEvent();
+ void windowResizedEvent(int width, int height, float scale);
+ void windowRestoredEvent();
+ void windowClosedEvent();
+ void windowKeyEvent(int key_code, int mods, bool pressed);
+ void windowMouseMoveEvent(int x, int y);
+ void windowMouseButtonEvent(int button, bool pressed);
+ void windowMouseWheelEvent(const QPoint& angle_delta);
+
+protected:
+ bool event(QEvent* event) override;
+
+private:
+ QPoint m_relative_mouse_start_position{};
+ QPoint m_relative_mouse_last_position{};
+ bool m_relative_mouse_enabled = false;
+};
+
+class DisplayContainer final : public QStackedWidget
+{
+ Q_OBJECT
+
+public:
+ DisplayContainer();
+ ~DisplayContainer();
+
+ static bool IsNeeded(bool fullscreen, bool render_to_main);
+
+ void setDisplayWidget(DisplayWidget* widget);
+ DisplayWidget* removeDisplayWidget();
+
+protected:
+ bool event(QEvent* event) override;
+
+private:
+ DisplayWidget* m_display_widget = nullptr;
+};
diff --git a/pcsx2-qt/EmuThread.cpp b/pcsx2-qt/EmuThread.cpp
new file mode 100644
index 000000000..705553189
--- /dev/null
+++ b/pcsx2-qt/EmuThread.cpp
@@ -0,0 +1,755 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include
+
+#include "common/Assertions.h"
+#include "common/Console.h"
+#include "common/Exceptions.h"
+#include "common/SettingsWrapper.h"
+#include "common/StringUtil.h"
+
+#include "pcsx2/CDVD/CDVD.h"
+#include "pcsx2/Frontend/InputManager.h"
+#include "pcsx2/Frontend/ImGuiManager.h"
+#include "pcsx2/GS.h"
+#include "pcsx2/HostDisplay.h"
+#include "pcsx2/PAD/Host/PAD.h"
+#include "pcsx2/PerformanceMetrics.h"
+#include "pcsx2/VMManager.h"
+
+#include "pcsx2/Frontend/OpenGLHostDisplay.h"
+
+#ifdef _WIN32
+#include "pcsx2/Frontend/D3D11HostDisplay.h"
+#endif
+
+#include "DisplayWidget.h"
+#include "EmuThread.h"
+#include "MainWindow.h"
+#include "QtHost.h"
+
+EmuThread* g_emu_thread = nullptr;
+WindowInfo g_gs_window_info;
+
+static std::unique_ptr s_host_display;
+
+EmuThread::EmuThread(QThread* ui_thread)
+ : QThread()
+ , m_ui_thread(ui_thread)
+{
+}
+
+EmuThread::~EmuThread() = default;
+
+bool EmuThread::isOnEmuThread() const
+{
+ return QThread::currentThread() == this;
+}
+
+void EmuThread::start()
+{
+ pxAssertRel(!g_emu_thread, "Emu thread does not exist");
+
+ g_emu_thread = new EmuThread(QThread::currentThread());
+ g_emu_thread->QThread::start();
+ g_emu_thread->m_started_semaphore.acquire();
+ g_emu_thread->moveToThread(g_emu_thread);
+ g_main_window->connectVMThreadSignals(g_emu_thread);
+}
+
+void EmuThread::stop()
+{
+ pxAssertRel(g_emu_thread, "Emu thread exists");
+ pxAssertRel(!g_emu_thread->isOnEmuThread(), "Not called on the emu thread");
+
+ QMetaObject::invokeMethod(g_emu_thread, &EmuThread::stopInThread, Qt::QueuedConnection);
+ while (g_emu_thread->isRunning())
+ QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
+}
+
+void EmuThread::stopInThread()
+{
+ if (VMManager::HasValidVM())
+ destroyVM();
+
+ m_event_loop->quit();
+ m_shutdown_flag.store(true);
+}
+
+void EmuThread::startVM(std::shared_ptr boot_params)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "startVM", Qt::QueuedConnection,
+ Q_ARG(std::shared_ptr, boot_params));
+ return;
+ }
+
+ pxAssertRel(!VMManager::HasValidVM(), "VM is shut down");
+
+ emit onVMStarting();
+
+ // create the display, this may take a while...
+ m_is_fullscreen = boot_params->fullscreen.value_or(QtHost::GetBaseBoolSettingValue("UI", "StartFullscreen", false));
+ m_is_rendering_to_main = !m_is_fullscreen && QtHost::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true);
+ if (!VMManager::Initialize(*boot_params))
+ return;
+
+ VMManager::SetState(VMState::Running);
+ m_event_loop->quit();
+}
+
+void EmuThread::resetVM()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::resetVM, Qt::QueuedConnection);
+ return;
+ }
+
+ VMManager::Reset();
+}
+
+void EmuThread::setVMPaused(bool paused)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "setVMPaused", Qt::QueuedConnection, Q_ARG(bool, paused));
+ return;
+ }
+
+ VMManager::SetPaused(paused);
+}
+
+void EmuThread::shutdownVM(bool allow_save_to_state /* = true */, bool blocking /* = false */)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "shutdownVM", Qt::QueuedConnection, Q_ARG(bool, allow_save_to_state),
+ Q_ARG(bool, blocking));
+
+ if (blocking)
+ {
+ // we need to yield here, since the display gets destroyed
+ while (VMManager::HasValidVM())
+ QApplication::processEvents(QEventLoop::ExcludeUserInputEvents, 1);
+ }
+
+ return;
+ }
+
+ const VMState state = VMManager::GetState();
+ if (state == VMState::Paused)
+ m_event_loop->quit();
+ else if (state != VMState::Running)
+ return;
+
+ VMManager::SetState(VMState::Stopping);
+}
+
+void EmuThread::loadState(const QString& filename)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "loadState", Qt::QueuedConnection, Q_ARG(const QString&, filename));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ VMManager::LoadState(filename.toUtf8().constData());
+}
+
+void EmuThread::loadStateFromSlot(qint32 slot)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "loadStateFromSlot", Qt::QueuedConnection, Q_ARG(qint32, slot));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ VMManager::LoadStateFromSlot(slot);
+}
+
+void EmuThread::saveState(const QString& filename)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "saveState", Qt::QueuedConnection, Q_ARG(const QString&, filename));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ if (!VMManager::SaveState(filename.toUtf8().constData()))
+ {
+ // this one is usually the result of a user-chosen path, so we can display a message box safely here
+ Console.Error("Failed to save state");
+ }
+}
+
+void EmuThread::saveStateToSlot(qint32 slot)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "saveStateToSlot", Qt::QueuedConnection, Q_ARG(qint32, slot));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ VMManager::SaveStateToSlot(slot);
+}
+
+void EmuThread::run()
+{
+ PerformanceMetrics::SetCPUThreadTimer(Common::ThreadCPUTimer::GetForCallingThread());
+ m_event_loop = new QEventLoop();
+ m_started_semaphore.release();
+
+ if (!VMManager::InitializeMemory())
+ pxFailRel("Failed to allocate memory map");
+
+ // we need input sources ready for binding
+ reloadInputSources();
+ createBackgroundControllerPollTimer();
+ startBackgroundControllerPollTimer();
+
+ while (!m_shutdown_flag.load())
+ {
+ if (!VMManager::HasValidVM())
+ {
+ m_event_loop->exec();
+ continue;
+ }
+
+ executeVM();
+ }
+
+ stopBackgroundControllerPollTimer();
+ destroyBackgroundControllerPollTimer();
+ InputManager::CloseSources();
+ VMManager::ReleaseMemory();
+ PerformanceMetrics::SetCPUThreadTimer(Common::ThreadCPUTimer());
+ moveToThread(m_ui_thread);
+ deleteLater();
+}
+
+void EmuThread::destroyVM()
+{
+ VMManager::Shutdown();
+}
+
+void EmuThread::executeVM()
+{
+ for (;;)
+ {
+ switch (VMManager::GetState())
+ {
+ case VMState::Initializing:
+ pxFailRel("Shouldn't be in the starting state state");
+ continue;
+
+ case VMState::Paused:
+ m_event_loop->exec();
+ continue;
+
+ case VMState::Running:
+ m_event_loop->processEvents(QEventLoop::AllEvents);
+ VMManager::Execute();
+ continue;
+
+ case VMState::Stopping:
+ destroyVM();
+ m_event_loop->processEvents(QEventLoop::AllEvents);
+ return;
+ }
+ }
+}
+
+void EmuThread::createBackgroundControllerPollTimer()
+{
+ pxAssert(!m_background_controller_polling_timer);
+ m_background_controller_polling_timer = new QTimer(this);
+ m_background_controller_polling_timer->setSingleShot(false);
+ m_background_controller_polling_timer->setTimerType(Qt::CoarseTimer);
+ connect(m_background_controller_polling_timer, &QTimer::timeout, this, &EmuThread::doBackgroundControllerPoll);
+}
+
+void EmuThread::destroyBackgroundControllerPollTimer()
+{
+ delete m_background_controller_polling_timer;
+ m_background_controller_polling_timer = nullptr;
+}
+
+void EmuThread::startBackgroundControllerPollTimer()
+{
+ if (m_background_controller_polling_timer->isActive())
+ return;
+
+ m_background_controller_polling_timer->start(BACKGROUND_CONTROLLER_POLLING_INTERVAL);
+}
+
+void EmuThread::stopBackgroundControllerPollTimer()
+{
+ if (!m_background_controller_polling_timer->isActive())
+ return;
+
+ m_background_controller_polling_timer->stop();
+}
+
+void EmuThread::doBackgroundControllerPoll()
+{
+ InputManager::PollSources();
+}
+
+void EmuThread::toggleFullscreen()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::toggleFullscreen, Qt::QueuedConnection);
+ return;
+ }
+
+ setFullscreen(!m_is_fullscreen);
+}
+
+void EmuThread::setFullscreen(bool fullscreen)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "setFullscreen", Qt::QueuedConnection, Q_ARG(bool, fullscreen));
+ return;
+ }
+
+ if (!VMManager::HasValidVM() || m_is_fullscreen == fullscreen)
+ return;
+
+ // This will call back to us on the MTGS thread.
+ m_is_fullscreen = fullscreen;
+ GetMTGS().UpdateDisplayWindow();
+ GetMTGS().WaitGS();
+}
+
+void EmuThread::applySettings()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::applySettings, Qt::QueuedConnection);
+ return;
+ }
+
+ checkForSettingChanges();
+ VMManager::ApplySettings();
+}
+
+void EmuThread::checkForSettingChanges()
+{
+ if (VMManager::HasValidVM())
+ {
+ const bool render_to_main = QtHost::GetBaseBoolSettingValue("UI", "RenderToMainWindow", true);
+ if (!m_is_fullscreen && m_is_rendering_to_main != render_to_main)
+ {
+ m_is_rendering_to_main = render_to_main;
+ GetMTGS().UpdateDisplayWindow();
+ GetMTGS().WaitGS();
+ }
+ }
+}
+
+void EmuThread::toggleSoftwareRendering()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::toggleSoftwareRendering, Qt::QueuedConnection);
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ GetMTGS().ToggleSoftwareRendering();
+}
+
+void EmuThread::switchRenderer(GSRendererType renderer)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "switchRenderer", Qt::QueuedConnection, Q_ARG(GSRendererType, renderer));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ GetMTGS().SwitchRenderer(renderer);
+}
+
+void EmuThread::changeDisc(const QString& path)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "changeDisc", Qt::QueuedConnection, Q_ARG(const QString&, path));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ VMManager::ChangeDisc(path.toStdString());
+}
+
+void EmuThread::reloadPatches()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::reloadPatches, Qt::QueuedConnection);
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ VMManager::ReloadPatches(true);
+}
+
+void EmuThread::reloadInputSources()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::reloadInputSources, Qt::QueuedConnection);
+ return;
+ }
+
+ auto lock = Host::GetSettingsLock();
+ SettingsInterface* si = Host::GetSettingsInterface();
+ InputManager::ReloadSources(*si);
+
+ // skip loading bindings if we're not running, since it'll get done on startup anyway
+ if (VMManager::HasValidVM())
+ InputManager::ReloadBindings(*si);
+}
+
+void EmuThread::reloadInputBindings()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::reloadInputBindings, Qt::QueuedConnection);
+ return;
+ }
+
+ // skip loading bindings if we're not running, since it'll get done on startup anyway
+ if (!VMManager::HasValidVM())
+ return;
+
+ auto lock = Host::GetSettingsLock();
+ SettingsInterface* si = Host::GetSettingsInterface();
+ InputManager::ReloadBindings(*si);
+}
+
+void EmuThread::requestDisplaySize(float scale)
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, "requestDisplaySize", Qt::QueuedConnection, Q_ARG(float, scale));
+ return;
+ }
+
+ if (!VMManager::HasValidVM())
+ return;
+
+ VMManager::RequestDisplaySize(scale);
+}
+
+void EmuThread::enumerateInputDevices()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::enumerateInputDevices, Qt::QueuedConnection);
+ return;
+ }
+
+ const std::vector> devs(InputManager::EnumerateDevices());
+ QList> qdevs;
+ qdevs.reserve(devs.size());
+ for (const std::pair& dev : devs)
+ qdevs.emplace_back(QString::fromStdString(dev.first), QString::fromStdString(dev.second));
+
+ onInputDevicesEnumerated(qdevs);
+}
+
+void EmuThread::enumerateVibrationMotors()
+{
+ if (!isOnEmuThread())
+ {
+ QMetaObject::invokeMethod(this, &EmuThread::enumerateVibrationMotors, Qt::QueuedConnection);
+ return;
+ }
+
+ const std::vector motors(InputManager::EnumerateMotors());
+ QList qmotors;
+ qmotors.reserve(motors.size());
+ for (InputBindingKey key : motors)
+ qmotors.push_back(key);
+
+ onVibrationMotorsEnumerated(qmotors);
+}
+
+void EmuThread::connectDisplaySignals(DisplayWidget* widget)
+{
+ widget->disconnect(this);
+
+ connect(widget, &DisplayWidget::windowFocusEvent, this, &EmuThread::onDisplayWindowFocused);
+ connect(widget, &DisplayWidget::windowResizedEvent, this, &EmuThread::onDisplayWindowResized);
+ // connect(widget, &DisplayWidget::windowRestoredEvent, this, &EmuThread::redrawDisplayWindow);
+ connect(widget, &DisplayWidget::windowClosedEvent, []() { g_emu_thread->shutdownVM(true, true); });
+ connect(widget, &DisplayWidget::windowKeyEvent, this, &EmuThread::onDisplayWindowKeyEvent);
+ connect(widget, &DisplayWidget::windowMouseMoveEvent, this, &EmuThread::onDisplayWindowMouseMoveEvent);
+ connect(widget, &DisplayWidget::windowMouseButtonEvent, this, &EmuThread::onDisplayWindowMouseButtonEvent);
+ connect(widget, &DisplayWidget::windowMouseWheelEvent, this, &EmuThread::onDisplayWindowMouseWheelEvent);
+}
+
+void EmuThread::onDisplayWindowMouseMoveEvent(int x, int y) {}
+
+void EmuThread::onDisplayWindowMouseButtonEvent(int button, bool pressed)
+{
+ InputManager::InvokeEvents(InputManager::MakeHostMouseButtonKey(button), pressed ? 1.0f : 0.0f);
+}
+
+void EmuThread::onDisplayWindowMouseWheelEvent(const QPoint& delta_angle) {}
+
+void EmuThread::onDisplayWindowKeyEvent(int key, int mods, bool pressed)
+{
+ InputManager::InvokeEvents(InputManager::MakeHostKeyboardKey(key), pressed ? 1.0f : 0.0f);
+}
+
+void EmuThread::onDisplayWindowResized(int width, int height, float scale)
+{
+ if (!VMManager::HasValidVM())
+ return;
+
+ GetMTGS().ResizeDisplayWindow(width, height, scale);
+}
+
+void EmuThread::onDisplayWindowFocused() {}
+
+void EmuThread::updateDisplay()
+{
+ pxAssertRel(!isOnEmuThread(), "Not on emu thread");
+
+ // finished with the display for now
+ HostDisplay* display = Host::GetHostDisplay();
+ display->DoneRenderContextCurrent();
+
+ // but we should get it back after this call
+ DisplayWidget* widget = onUpdateDisplayRequested(m_is_fullscreen, !m_is_fullscreen && m_is_rendering_to_main);
+ if (!widget || !display->MakeRenderContextCurrent())
+ {
+ pxFailRel("Failed to recreate context after updating");
+ return;
+ }
+
+ // this is always a new widget, so reconnect it
+ connectDisplaySignals(widget);
+}
+
+HostDisplay* EmuThread::acquireHostDisplay(HostDisplay::RenderAPI api)
+{
+ s_host_display = HostDisplay::CreateDisplayForAPI(api);
+ if (!s_host_display)
+ return nullptr;
+
+ DisplayWidget* widget = emit onCreateDisplayRequested(m_is_fullscreen, m_is_rendering_to_main);
+ if (!widget)
+ {
+ s_host_display.reset();
+ return nullptr;
+ }
+
+ connectDisplaySignals(widget);
+
+ if (!s_host_display->MakeRenderContextCurrent())
+ {
+ Console.Error("Failed to make render context current");
+ releaseHostDisplay();
+ return nullptr;
+ }
+
+ if (!s_host_display->InitializeRenderDevice(StringUtil::wxStringToUTF8String(EmuFolders::Cache.ToString()), false) ||
+ !ImGuiManager::Initialize())
+ {
+ Console.Error("Failed to initialize device/imgui");
+ releaseHostDisplay();
+ return nullptr;
+ }
+
+ g_gs_window_info = s_host_display->GetWindowInfo();
+
+ return s_host_display.get();
+}
+
+void EmuThread::releaseHostDisplay()
+{
+ ImGuiManager::Shutdown();
+
+ if (s_host_display)
+ {
+ s_host_display->DestroyRenderSurface();
+ s_host_display->DestroyRenderDevice();
+ }
+
+ g_gs_window_info = WindowInfo();
+
+ emit onDestroyDisplayRequested();
+
+ s_host_display.reset();
+}
+
+HostDisplay* Host::GetHostDisplay()
+{
+ return s_host_display.get();
+}
+
+HostDisplay* Host::AcquireHostDisplay(HostDisplay::RenderAPI api)
+{
+ return g_emu_thread->acquireHostDisplay(api);
+}
+
+void Host::ReleaseHostDisplay()
+{
+ g_emu_thread->releaseHostDisplay();
+}
+
+bool Host::BeginPresentFrame(bool frame_skip)
+{
+ return s_host_display->BeginPresent(frame_skip);
+}
+
+void Host::EndPresentFrame()
+{
+ ImGuiManager::RenderOSD();
+ s_host_display->EndPresent();
+ ImGuiManager::NewFrame();
+}
+
+void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale)
+{
+ s_host_display->ResizeRenderWindow(new_window_width, new_window_height, new_window_scale);
+ ImGuiManager::WindowResized();
+}
+
+void Host::RequestResizeHostDisplay(s32 width, s32 height)
+{
+ g_emu_thread->onResizeDisplayRequested(width, height);
+}
+
+void Host::UpdateHostDisplay()
+{
+ g_emu_thread->updateDisplay();
+ ImGuiManager::WindowResized();
+}
+
+void Host::OnVMStarting()
+{
+ g_emu_thread->stopBackgroundControllerPollTimer();
+ emit g_emu_thread->onVMStarting();
+}
+
+void Host::OnVMStarted()
+{
+ emit g_emu_thread->onVMStarted();
+}
+
+void Host::OnVMDestroyed()
+{
+ emit g_emu_thread->onVMStopped();
+ g_emu_thread->startBackgroundControllerPollTimer();
+}
+
+void Host::OnVMPaused()
+{
+ g_emu_thread->startBackgroundControllerPollTimer();
+ emit g_emu_thread->onVMPaused();
+}
+
+void Host::OnVMResumed()
+{
+ // exit the event loop when we eventually return
+ g_emu_thread->getEventLoop()->quit();
+ g_emu_thread->stopBackgroundControllerPollTimer();
+ emit g_emu_thread->onVMResumed();
+}
+
+void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name,
+ u32 game_crc)
+{
+ emit g_emu_thread->onGameChanged(QString::fromStdString(disc_path), QString::fromStdString(game_serial),
+ QString::fromStdString(game_name), game_crc);
+}
+
+void Host::PumpMessagesOnCPUThread()
+{
+ g_emu_thread->getEventLoop()->processEvents(QEventLoop::AllEvents);
+}
+
+ScopedVMPause::ScopedVMPause(bool was_paused)
+ : m_was_paused(was_paused)
+{
+ if (!m_was_paused)
+ g_emu_thread->setVMPaused(true);
+}
+
+ScopedVMPause::~ScopedVMPause()
+{
+ if (m_was_paused)
+ g_emu_thread->setVMPaused(false);
+}
+
+alignas(16) static SysMtgsThread s_mtgs_thread;
+
+SysMtgsThread& GetMTGS()
+{
+ return s_mtgs_thread;
+}
+
+// ------------------------------------------------------------------------
+// Hotkeys
+// ------------------------------------------------------------------------
+
+BEGIN_HOTKEY_LIST(g_host_hotkeys)
+DEFINE_HOTKEY("Screenshot", "General", "Save Screenshot", [](bool pressed) {
+ if (!pressed)
+ {
+ // TODO
+ }
+})
+DEFINE_HOTKEY("TogglePause", "System", "Toggle Pause", [](bool pressed) {
+ if (!pressed)
+ g_emu_thread->setVMPaused(VMManager::GetState() != VMState::Paused);
+})
+DEFINE_HOTKEY("ToggleFullscreen", "General", "Toggle Fullscreen", [](bool pressed) {
+ if (!pressed)
+ g_emu_thread->toggleFullscreen();
+})
+END_HOTKEY_LIST()
diff --git a/pcsx2-qt/EmuThread.h b/pcsx2-qt/EmuThread.h
new file mode 100644
index 000000000..04da15a10
--- /dev/null
+++ b/pcsx2-qt/EmuThread.h
@@ -0,0 +1,144 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "pcsx2/Host.h"
+#include "pcsx2/HostDisplay.h"
+#include "pcsx2/Frontend/InputManager.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class DisplayWidget;
+struct VMBootParameters;
+
+class EmuThread : public QThread
+{
+ Q_OBJECT
+
+public:
+ explicit EmuThread(QThread* ui_thread);
+ ~EmuThread();
+
+ static void start();
+ static void stop();
+
+ __fi QEventLoop* getEventLoop() const { return m_event_loop; }
+
+ bool isOnEmuThread() const;
+
+ /// Called back from the GS thread when the display state changes (e.g. fullscreen, render to main).
+ HostDisplay* acquireHostDisplay(HostDisplay::RenderAPI api);
+ void releaseHostDisplay();
+ void updateDisplay();
+
+ void startBackgroundControllerPollTimer();
+ void stopBackgroundControllerPollTimer();
+
+public Q_SLOTS:
+ void startVM(std::shared_ptr boot_params);
+ void resetVM();
+ void setVMPaused(bool paused);
+ void shutdownVM(bool allow_save_to_state = true, bool blocking = false);
+ void loadState(const QString& filename);
+ void loadStateFromSlot(qint32 slot);
+ void saveState(const QString& filename);
+ void saveStateToSlot(qint32 slot);
+ void toggleFullscreen();
+ void setFullscreen(bool fullscreen);
+ void applySettings();
+ void toggleSoftwareRendering();
+ void switchRenderer(GSRendererType renderer);
+ void changeDisc(const QString& path);
+ void reloadPatches();
+ void reloadInputSources();
+ void reloadInputBindings();
+ void requestDisplaySize(float scale);
+ void enumerateInputDevices();
+ void enumerateVibrationMotors();
+
+Q_SIGNALS:
+ DisplayWidget* onCreateDisplayRequested(bool fullscreen, bool render_to_main);
+ DisplayWidget* onUpdateDisplayRequested(bool fullscreen, bool render_to_main);
+ void onResizeDisplayRequested(qint32 width, qint32 height);
+ void onDestroyDisplayRequested();
+ void onVMStarting();
+ void onVMStarted();
+ void onVMPaused();
+ void onVMResumed();
+ void onVMStopped();
+ void onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc);
+ void onInputDevicesEnumerated(const QList>& devices);
+ void onInputDeviceConnected(const QString& identifier, const QString& device_name);
+ void onInputDeviceDisconnected(const QString& identifier);
+ void onVibrationMotorsEnumerated(const QList& motors);
+
+protected:
+ void run();
+
+private:
+ static constexpr u32 BACKGROUND_CONTROLLER_POLLING_INTERVAL =
+ 100; /// Interval at which the controllers are polled when the system is not active.
+
+ void connectDisplaySignals(DisplayWidget* widget);
+ void destroyVM();
+ void executeVM();
+ void checkForSettingChanges();
+
+ void createBackgroundControllerPollTimer();
+ void destroyBackgroundControllerPollTimer();
+
+private Q_SLOTS:
+ void stopInThread();
+ void doBackgroundControllerPoll();
+ void onDisplayWindowMouseMoveEvent(int x, int y);
+ void onDisplayWindowMouseButtonEvent(int button, bool pressed);
+ void onDisplayWindowMouseWheelEvent(const QPoint& delta_angle);
+ void onDisplayWindowResized(int width, int height, float scale);
+ void onDisplayWindowFocused();
+ void onDisplayWindowKeyEvent(int key, int mods, bool pressed);
+
+private:
+ QThread* m_ui_thread;
+ QSemaphore m_started_semaphore;
+ QEventLoop* m_event_loop = nullptr;
+ QTimer* m_background_controller_polling_timer = nullptr;
+
+ std::atomic_bool m_shutdown_flag{false};
+
+ bool m_is_rendering_to_main = false;
+ bool m_is_fullscreen = false;
+};
+
+///
+/// Helper class to pause/unpause the emulation thread.
+///
+class ScopedVMPause
+{
+public:
+ ScopedVMPause(bool was_paused);
+ ~ScopedVMPause();
+
+private:
+ bool m_was_paused;
+};
+
+extern EmuThread* g_emu_thread;
diff --git a/pcsx2-qt/GameList/GameListModel.cpp b/pcsx2-qt/GameList/GameListModel.cpp
new file mode 100644
index 000000000..663cec25f
--- /dev/null
+++ b/pcsx2-qt/GameList/GameListModel.cpp
@@ -0,0 +1,490 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "GameListModel.h"
+#include "common/FileSystem.h"
+#include "common/StringUtil.h"
+#include
+#include
+#include
+#include
+#include
+
+static constexpr std::array s_column_names = {
+ {"Type", "Code", "Title", "File Title", "CRC", "Size", "Region", "Compatibility", "Cover"}};
+
+static constexpr int COVER_ART_WIDTH = 350;
+static constexpr int COVER_ART_HEIGHT = 512;
+static constexpr int COVER_ART_SPACING = 32;
+
+static int DPRScale(int size, float dpr)
+{
+ return static_cast(static_cast(size) * dpr);
+}
+
+static int DPRUnscale(int size, float dpr)
+{
+ return static_cast(static_cast(size) / dpr);
+}
+
+static void resizeAndPadPixmap(QPixmap* pm, int expected_width, int expected_height, float dpr)
+{
+ const int dpr_expected_width = DPRScale(expected_width, dpr);
+ const int dpr_expected_height = DPRScale(expected_height, dpr);
+ if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
+ return;
+
+ *pm = pm->scaled(dpr_expected_width, dpr_expected_height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ if (pm->width() == dpr_expected_width && pm->height() == dpr_expected_height)
+ return;
+
+ // QPainter works in unscaled coordinates.
+ int xoffs = 0;
+ int yoffs = 0;
+ if (pm->width() < dpr_expected_width)
+ xoffs = DPRUnscale((dpr_expected_width - pm->width()) / 2, dpr);
+ if (pm->height() < dpr_expected_height)
+ yoffs = DPRUnscale((dpr_expected_height - pm->height()) / 2, dpr);
+
+ QPixmap padded_image(dpr_expected_width, dpr_expected_height);
+ padded_image.setDevicePixelRatio(dpr);
+ padded_image.fill(Qt::transparent);
+ QPainter painter;
+ if (painter.begin(&padded_image))
+ {
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.drawPixmap(xoffs, yoffs, *pm);
+ painter.setCompositionMode(QPainter::CompositionMode_Destination);
+ painter.fillRect(padded_image.rect(), QColor(0, 0, 0, 0));
+ painter.end();
+ }
+
+ *pm = padded_image;
+}
+
+static QPixmap createPlaceholderImage(const QPixmap& placeholder_pixmap, int width, int height, float scale,
+ const std::string& title)
+{
+ const float dpr = qApp->devicePixelRatio();
+ QPixmap pm(placeholder_pixmap.copy());
+ pm.setDevicePixelRatio(dpr);
+ if (pm.isNull())
+ return QPixmap();
+
+ resizeAndPadPixmap(&pm, width, height, dpr);
+ QPainter painter;
+ if (painter.begin(&pm))
+ {
+ QFont font;
+ font.setPointSize(std::max(static_cast(32.0f * scale), 1));
+ painter.setFont(font);
+ painter.setPen(Qt::white);
+
+ const QRect text_rc(0, 0, static_cast(static_cast(width)),
+ static_cast(static_cast(height)));
+ painter.drawText(text_rc, Qt::AlignCenter | Qt::TextWordWrap, QString::fromStdString(title));
+ painter.end();
+ }
+
+ return pm;
+}
+
+std::optional GameListModel::getColumnIdForName(std::string_view name)
+{
+ for (int column = 0; column < Column_Count; column++)
+ {
+ if (name == s_column_names[column])
+ return static_cast(column);
+ }
+
+ return std::nullopt;
+}
+
+const char* GameListModel::getColumnName(Column col)
+{
+ return s_column_names[static_cast(col)];
+}
+
+GameListModel::GameListModel(QObject* parent /* = nullptr */)
+ : QAbstractTableModel(parent)
+{
+ loadCommonImages();
+ setColumnDisplayNames();
+}
+GameListModel::~GameListModel() = default;
+
+void GameListModel::setCoverScale(float scale)
+{
+ if (m_cover_scale == scale)
+ return;
+
+ m_cover_pixmap_cache.clear();
+ m_cover_scale = scale;
+}
+
+void GameListModel::refreshCovers()
+{
+ m_cover_pixmap_cache.clear();
+ refresh();
+}
+
+int GameListModel::getCoverArtWidth() const
+{
+ return std::max(static_cast(static_cast(COVER_ART_WIDTH) * m_cover_scale), 1);
+}
+
+int GameListModel::getCoverArtHeight() const
+{
+ return std::max(static_cast(static_cast(COVER_ART_HEIGHT) * m_cover_scale), 1);
+}
+
+int GameListModel::getCoverArtSpacing() const
+{
+ return std::max(static_cast(static_cast(COVER_ART_SPACING) * m_cover_scale), 1);
+}
+
+int GameListModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return static_cast(GameList::GetEntryCount());
+}
+
+int GameListModel::columnCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+
+ return Column_Count;
+}
+
+QVariant GameListModel::data(const QModelIndex& index, int role) const
+{
+ if (!index.isValid())
+ return {};
+
+ const int row = index.row();
+ if (row < 0 || row >= static_cast(GameList::GetEntryCount()))
+ return {};
+
+ const auto lock = GameList::GetLock();
+ const GameList::Entry* ge = GameList::GetEntryByIndex(row);
+ if (!ge)
+ return {};
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ {
+ switch (index.column())
+ {
+ case Column_Serial:
+ return QString::fromStdString(ge->serial);
+
+ case Column_Title:
+ return QString::fromStdString(ge->title);
+
+ case Column_FileTitle:
+ {
+ const std::string_view file_title(FileSystem::GetFileTitleFromPath(ge->path));
+ return QString::fromUtf8(file_title.data(), static_cast(file_title.length()));
+ }
+
+ case Column_CRC:
+ return QStringLiteral("%1").arg(ge->crc, 8, 16, QChar('0'));
+
+ case Column_Size:
+ return QString("%1 MB").arg(static_cast(ge->total_size) / 1048576.0, 0, 'f', 2);
+
+ case Column_Cover:
+ {
+ if (m_show_titles_for_covers)
+ return QString::fromStdString(ge->title);
+ else
+ return {};
+ }
+
+ default:
+ return {};
+ }
+ }
+
+ case Qt::InitialSortOrderRole:
+ {
+ switch (index.column())
+ {
+ case Column_Type:
+ return static_cast(ge->type);
+
+ case Column_Serial:
+ return QString::fromStdString(ge->serial);
+
+ case Column_Title:
+ case Column_Cover:
+ return QString::fromStdString(ge->title);
+
+ case Column_FileTitle:
+ {
+ const std::string_view file_title(FileSystem::GetFileTitleFromPath(ge->path));
+ return QString::fromUtf8(file_title.data(), static_cast(file_title.length()));
+ }
+
+ case Column_CRC:
+ return static_cast(ge->crc);
+
+ case Column_Region:
+ return static_cast(ge->region);
+
+ case Column_Compatibility:
+ return static_cast(ge->compatibility_rating);
+
+ case Column_Size:
+ return static_cast(ge->total_size);
+
+ default:
+ return {};
+ }
+ }
+
+ case Qt::DecorationRole:
+ {
+ switch (index.column())
+ {
+ case Column_Type:
+ {
+ switch (ge->type)
+ {
+ case GameList::EntryType::PS1Disc:
+ case GameList::EntryType::PS2Disc:
+ // return ((ge->settings.GetUserSettingsCount() > 0) ? m_type_disc_with_settings_pixmap : // m_type_disc_pixmap);
+ return m_type_disc_pixmap;
+ case GameList::EntryType::Playlist:
+ return m_type_playlist_pixmap;
+ case GameList::EntryType::ELF:
+ default:
+ return m_type_exe_pixmap;
+ }
+ }
+
+ case Column_Region:
+ {
+ switch (ge->region)
+ {
+ case GameList::Region::NTSC_J:
+ return m_region_jp_pixmap;
+ case GameList::Region::NTSC_UC:
+ return m_region_us_pixmap;
+ case GameList::Region::PAL:
+ return m_region_eu_pixmap;
+ case GameList::Region::Other:
+ default:
+ return m_region_other_pixmap;
+ }
+ }
+
+ case Column_Compatibility:
+ {
+ return m_compatibiliy_pixmaps[static_cast(
+ (static_cast(ge->compatibility_rating) >= GameList::CompatibilityRatingCount) ?
+ GameList::CompatibilityRating::Unknown :
+ ge->compatibility_rating)];
+ }
+
+ case Column_Cover:
+ {
+ auto it = m_cover_pixmap_cache.find(ge->path);
+ if (it != m_cover_pixmap_cache.end())
+ return it->second;
+
+ QPixmap image;
+ std::string path = GameList::GetCoverImagePathForEntry(ge);
+ if (!path.empty())
+ {
+ const float dpr = qApp->devicePixelRatio();
+ image = QPixmap(QString::fromStdString(path));
+ if (!image.isNull())
+ {
+ image.setDevicePixelRatio(dpr);
+ resizeAndPadPixmap(&image, getCoverArtWidth(), getCoverArtHeight(), dpr);
+ }
+ }
+
+ if (image.isNull())
+ {
+ image = createPlaceholderImage(m_placeholder_pixmap, getCoverArtWidth(), getCoverArtHeight(), m_cover_scale,
+ ge->title);
+ }
+
+ m_cover_pixmap_cache.emplace(ge->path, image);
+ return image;
+ }
+ break;
+
+ default:
+ return {};
+ }
+
+ default:
+ return {};
+ }
+ }
+}
+
+QVariant GameListModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation != Qt::Horizontal || role != Qt::DisplayRole || section < 0 || section >= Column_Count)
+ return {};
+
+ return m_column_display_names[section];
+}
+
+void GameListModel::refresh()
+{
+ beginResetModel();
+ endResetModel();
+}
+
+bool GameListModel::titlesLessThan(int left_row, int right_row) const
+{
+ if (left_row < 0 || left_row >= static_cast(GameList::GetEntryCount()) || right_row < 0 ||
+ right_row >= static_cast(GameList::GetEntryCount()))
+ {
+ return false;
+ }
+
+ const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
+ const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
+ return (StringUtil::Strcasecmp(left->title.c_str(), right->title.c_str()) < 0);
+}
+
+bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const
+{
+ if (!left_index.isValid() || !right_index.isValid())
+ return false;
+
+ const int left_row = left_index.row();
+ const int right_row = right_index.row();
+ if (left_row < 0 || left_row >= static_cast(GameList::GetEntryCount()) || right_row < 0 ||
+ right_row >= static_cast(GameList::GetEntryCount()))
+ {
+ return false;
+ }
+
+ const auto lock = GameList::GetLock();
+ const GameList::Entry* left = GameList::GetEntryByIndex(left_row);
+ const GameList::Entry* right = GameList::GetEntryByIndex(right_row);
+ if (!left || !right)
+ return false;
+
+ switch (column)
+ {
+ case Column_Type:
+ {
+ if (left->type == right->type)
+ return titlesLessThan(left_row, right_row);
+
+ return static_cast(left->type) < static_cast(right->type);
+ }
+
+ case Column_Serial:
+ {
+ if (left->serial == right->serial)
+ return titlesLessThan(left_row, right_row);
+ return (StringUtil::Strcasecmp(left->serial.c_str(), right->serial.c_str()) < 0);
+ }
+
+ case Column_Title:
+ {
+ return titlesLessThan(left_row, right_row);
+ }
+
+ case Column_FileTitle:
+ {
+ const std::string_view file_title_left(FileSystem::GetFileTitleFromPath(left->path));
+ const std::string_view file_title_right(FileSystem::GetFileTitleFromPath(right->path));
+ if (file_title_left == file_title_right)
+ return titlesLessThan(left_row, right_row);
+
+ const std::size_t smallest = std::min(file_title_left.size(), file_title_right.size());
+ return (StringUtil::Strncasecmp(file_title_left.data(), file_title_right.data(), smallest) < 0);
+ }
+
+ case Column_Region:
+ {
+ if (left->region == right->region)
+ return titlesLessThan(left_row, right_row);
+ return (static_cast(left->region) < static_cast(right->region));
+ }
+
+ case Column_Compatibility:
+ {
+ if (left->compatibility_rating == right->compatibility_rating)
+ return titlesLessThan(left_row, right_row);
+
+ return (static_cast(left->compatibility_rating) < static_cast(right->compatibility_rating));
+ }
+
+ case Column_Size:
+ {
+ if (left->total_size == right->total_size)
+ return titlesLessThan(left_row, right_row);
+
+ return (left->total_size < right->total_size);
+ }
+
+ case Column_CRC:
+ {
+ if (left->crc == right->crc)
+ return titlesLessThan(left_row, right_row);
+
+ return (left->crc < right->crc);
+ }
+
+ default:
+ return false;
+ }
+}
+
+void GameListModel::loadCommonImages()
+{
+ m_type_disc_pixmap = QIcon(QStringLiteral(":/icons/media-optical-24.png")).pixmap(QSize(24, 24));
+ m_type_disc_with_settings_pixmap = QIcon(QStringLiteral(":/icons/media-optical-gear-24.png")).pixmap(QSize(24, 24));
+ m_type_exe_pixmap = QIcon(QStringLiteral(":/icons/applications-system-24.png")).pixmap(QSize(24, 24));
+ m_type_playlist_pixmap = QIcon(QStringLiteral(":/icons/address-book-new-22.png")).pixmap(QSize(22, 22));
+ m_region_eu_pixmap = QIcon(QStringLiteral(":/icons/flag-eu.png")).pixmap(QSize(42, 30));
+ m_region_jp_pixmap = QIcon(QStringLiteral(":/icons/flag-jp.png")).pixmap(QSize(42, 30));
+ m_region_us_pixmap = QIcon(QStringLiteral(":/icons/flag-us.png")).pixmap(QSize(42, 30));
+ m_region_other_pixmap = QIcon(QStringLiteral(":/icons/flag-other.png")).pixmap(QSize(42, 30));
+
+ for (u32 i = 1; i < GameList::CompatibilityRatingCount; i++)
+ m_compatibiliy_pixmaps[i].load(QStringLiteral(":/icons/star-%1.png").arg(i - 1));
+
+ m_placeholder_pixmap.load(QString::fromStdString(Path::CombineStdString(EmuFolders::Resources, "cover-placeholder.png")));
+}
+
+void GameListModel::setColumnDisplayNames()
+{
+ m_column_display_names[Column_Type] = tr("Type");
+ m_column_display_names[Column_Serial] = tr("Code");
+ m_column_display_names[Column_Title] = tr("Title");
+ m_column_display_names[Column_FileTitle] = tr("File Title");
+ m_column_display_names[Column_CRC] = tr("CRC");
+ m_column_display_names[Column_Size] = tr("Size");
+ m_column_display_names[Column_Region] = tr("Region");
+ m_column_display_names[Column_Compatibility] = tr("Compatibility");
+}
diff --git a/pcsx2-qt/GameList/GameListModel.h b/pcsx2-qt/GameList/GameListModel.h
new file mode 100644
index 000000000..09a3396b7
--- /dev/null
+++ b/pcsx2-qt/GameList/GameListModel.h
@@ -0,0 +1,97 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "pcsx2/Frontend/GameList.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+class GameListModel final : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ enum Column : int
+ {
+ Column_Type,
+ Column_Serial,
+ Column_Title,
+ Column_FileTitle,
+ Column_CRC,
+ Column_Size,
+ Column_Region,
+ Column_Compatibility,
+ Column_Cover,
+
+ Column_Count
+ };
+
+ static std::optional getColumnIdForName(std::string_view name);
+ static const char* getColumnName(Column col);
+
+ GameListModel(QObject* parent = nullptr);
+ ~GameListModel();
+
+ int rowCount(const QModelIndex& parent = QModelIndex()) const override;
+ int columnCount(const QModelIndex& parent = QModelIndex()) const override;
+ QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+
+ __fi const QString& getColumnDisplayName(int column) { return m_column_display_names[column]; }
+
+ void refresh();
+
+ bool titlesLessThan(int left_row, int right_row) const;
+
+ bool lessThan(const QModelIndex& left_index, const QModelIndex& right_index, int column) const;
+
+ bool getShowCoverTitles() const { return m_show_titles_for_covers; }
+ void setShowCoverTitles(bool enabled) { m_show_titles_for_covers = enabled; }
+
+ float getCoverScale() const { return m_cover_scale; }
+ void setCoverScale(float scale);
+ int getCoverArtWidth() const;
+ int getCoverArtHeight() const;
+ int getCoverArtSpacing() const;
+ void refreshCovers();
+
+private:
+ void loadCommonImages();
+ void setColumnDisplayNames();
+
+ float m_cover_scale = 1.0f;
+ bool m_show_titles_for_covers = false;
+
+ std::array m_column_display_names;
+
+ QPixmap m_type_disc_pixmap;
+ QPixmap m_type_disc_with_settings_pixmap;
+ QPixmap m_type_exe_pixmap;
+ QPixmap m_type_playlist_pixmap;
+
+ QPixmap m_region_jp_pixmap;
+ QPixmap m_region_eu_pixmap;
+ QPixmap m_region_us_pixmap;
+ QPixmap m_region_other_pixmap;
+
+ QPixmap m_placeholder_pixmap;
+
+ std::array(GameList::CompatibilityRatingCount)> m_compatibiliy_pixmaps;
+ mutable std::unordered_map m_cover_pixmap_cache;
+};
\ No newline at end of file
diff --git a/pcsx2-qt/GameList/GameListRefreshThread.cpp b/pcsx2-qt/GameList/GameListRefreshThread.cpp
new file mode 100644
index 000000000..255dc7ef4
--- /dev/null
+++ b/pcsx2-qt/GameList/GameListRefreshThread.cpp
@@ -0,0 +1,130 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "common/ProgressCallback.h"
+#include "common/Timer.h"
+#include "pcsx2/Frontend/GameList.h"
+
+#include "GameListRefreshThread.h"
+
+#include
+
+// Limit UI update times to 4 per second, so we don't spend longer redrawing the UI than scanning
+static constexpr float UI_UPDATE_INTERVAL = 0.25f;
+
+AsyncRefreshProgressCallback::AsyncRefreshProgressCallback(GameListRefreshThread* parent)
+ : m_parent(parent)
+{
+}
+
+void AsyncRefreshProgressCallback::Cancel()
+{
+ // Not atomic, but we don't need to cancel immediately.
+ m_cancelled = true;
+}
+
+void AsyncRefreshProgressCallback::SetStatusText(const char* text)
+{
+ QString new_text(QString::fromUtf8(text));
+ if (new_text == m_status_text)
+ return;
+
+ m_status_text = new_text;
+ fireUpdate();
+}
+
+void AsyncRefreshProgressCallback::SetProgressRange(u32 range)
+{
+ BaseProgressCallback::SetProgressRange(range);
+ if (static_cast(m_progress_range) == m_last_range)
+ return;
+
+ m_last_range = static_cast(m_progress_range);
+ fireUpdate();
+}
+
+void AsyncRefreshProgressCallback::SetProgressValue(u32 value)
+{
+ BaseProgressCallback::SetProgressValue(value);
+ if (static_cast(m_progress_value) == m_last_value)
+ return;
+
+ m_last_value = static_cast(m_progress_value);
+ fireUpdate();
+}
+
+void AsyncRefreshProgressCallback::SetTitle(const char* title) {}
+
+void AsyncRefreshProgressCallback::DisplayError(const char* message)
+{
+ QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
+}
+
+void AsyncRefreshProgressCallback::DisplayWarning(const char* message)
+{
+ QMessageBox::warning(nullptr, QStringLiteral("Warning"), QString::fromUtf8(message));
+}
+
+void AsyncRefreshProgressCallback::DisplayInformation(const char* message)
+{
+ QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
+}
+
+void AsyncRefreshProgressCallback::DisplayDebugMessage(const char* message)
+{
+ qDebug() << message;
+}
+
+void AsyncRefreshProgressCallback::ModalError(const char* message)
+{
+ QMessageBox::critical(nullptr, QStringLiteral("Error"), QString::fromUtf8(message));
+}
+
+bool AsyncRefreshProgressCallback::ModalConfirmation(const char* message)
+{
+ return QMessageBox::question(nullptr, QStringLiteral("Question"), QString::fromUtf8(message)) == QMessageBox::Yes;
+}
+
+void AsyncRefreshProgressCallback::ModalInformation(const char* message)
+{
+ QMessageBox::information(nullptr, QStringLiteral("Information"), QString::fromUtf8(message));
+}
+
+void AsyncRefreshProgressCallback::fireUpdate()
+{
+ m_parent->refreshProgress(m_status_text, m_last_value, m_last_range);
+}
+
+GameListRefreshThread::GameListRefreshThread(bool invalidate_cache)
+ : QThread()
+ , m_progress(this)
+ , m_invalidate_cache(invalidate_cache)
+{
+}
+
+GameListRefreshThread::~GameListRefreshThread() = default;
+
+void GameListRefreshThread::cancel()
+{
+ m_progress.Cancel();
+}
+
+void GameListRefreshThread::run()
+{
+ GameList::Refresh(m_invalidate_cache, &m_progress);
+ emit refreshComplete();
+}
diff --git a/pcsx2-qt/GameList/GameListRefreshThread.h b/pcsx2-qt/GameList/GameListRefreshThread.h
new file mode 100644
index 000000000..f98f03168
--- /dev/null
+++ b/pcsx2-qt/GameList/GameListRefreshThread.h
@@ -0,0 +1,75 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "common/ProgressCallback.h"
+#include "common/Timer.h"
+
+class GameListRefreshThread;
+
+class AsyncRefreshProgressCallback : public BaseProgressCallback
+{
+public:
+ AsyncRefreshProgressCallback(GameListRefreshThread* parent);
+
+ void Cancel();
+
+ void SetStatusText(const char* text) override;
+ void SetProgressRange(u32 range) override;
+ void SetProgressValue(u32 value) override;
+ void SetTitle(const char* title) override;
+ void DisplayError(const char* message) override;
+ void DisplayWarning(const char* message) override;
+ void DisplayInformation(const char* message) override;
+ void DisplayDebugMessage(const char* message) override;
+ void ModalError(const char* message) override;
+ bool ModalConfirmation(const char* message) override;
+ void ModalInformation(const char* message) override;
+
+private:
+ void fireUpdate();
+
+ GameListRefreshThread* m_parent;
+ Common::Timer m_last_update_time;
+ QString m_status_text;
+ int m_last_range = 1;
+ int m_last_value = 0;
+};
+
+class GameListRefreshThread final : public QThread
+{
+ Q_OBJECT
+
+public:
+ GameListRefreshThread(bool invalidate_cache);
+ ~GameListRefreshThread();
+
+ void cancel();
+
+Q_SIGNALS:
+ void refreshProgress(const QString& status, int current, int total);
+ void refreshComplete();
+
+protected:
+ void run();
+
+private:
+ AsyncRefreshProgressCallback m_progress;
+ bool m_invalidate_cache;
+};
diff --git a/pcsx2-qt/GameList/GameListWidget.cpp b/pcsx2-qt/GameList/GameListWidget.cpp
new file mode 100644
index 000000000..5aefe8ee0
--- /dev/null
+++ b/pcsx2-qt/GameList/GameListWidget.cpp
@@ -0,0 +1,457 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "common/StringUtil.h"
+
+#include "pcsx2/Frontend/GameList.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "GameListModel.h"
+#include "GameListRefreshThread.h"
+#include "GameListWidget.h"
+#include "QtHost.h"
+#include "QtUtils.h"
+
+class GameListSortModel final : public QSortFilterProxyModel
+{
+public:
+ explicit GameListSortModel(GameListModel* parent)
+ : QSortFilterProxyModel(parent)
+ , m_model(parent)
+ {
+ }
+
+ bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override
+ {
+ // TODO: Search
+ return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
+ }
+
+ bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override
+ {
+ return m_model->lessThan(source_left, source_right, source_left.column());
+ }
+
+private:
+ GameListModel* m_model;
+};
+
+GameListWidget::GameListWidget(QWidget* parent /* = nullptr */)
+ : QStackedWidget(parent)
+{
+}
+
+GameListWidget::~GameListWidget() = default;
+
+void GameListWidget::initialize()
+{
+ m_model = new GameListModel(this);
+ m_model->setCoverScale(QtHost::GetBaseFloatSettingValue("UI", "GameListCoverArtScale", 0.45f));
+ m_model->setShowCoverTitles(QtHost::GetBaseBoolSettingValue("UI", "GameListShowCoverTitles", true));
+ m_model->setCoverScale(0.45f);
+ m_model->setShowCoverTitles(true);
+
+ m_sort_model = new GameListSortModel(m_model);
+ m_sort_model->setSourceModel(m_model);
+ m_table_view = new QTableView(this);
+ m_table_view->setModel(m_sort_model);
+ m_table_view->setSortingEnabled(true);
+ m_table_view->setSelectionMode(QAbstractItemView::SingleSelection);
+ m_table_view->setSelectionBehavior(QAbstractItemView::SelectRows);
+ m_table_view->setContextMenuPolicy(Qt::CustomContextMenu);
+ m_table_view->setAlternatingRowColors(true);
+ m_table_view->setShowGrid(false);
+ m_table_view->setCurrentIndex({});
+ m_table_view->horizontalHeader()->setHighlightSections(false);
+ m_table_view->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
+ m_table_view->verticalHeader()->hide();
+ m_table_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+
+ loadTableViewColumnVisibilitySettings();
+ loadTableViewColumnSortSettings();
+
+ connect(m_table_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
+ &GameListWidget::onSelectionModelCurrentChanged);
+ connect(m_table_view, &QTableView::activated, this, &GameListWidget::onTableViewItemActivated);
+ connect(m_table_view, &QTableView::customContextMenuRequested, this,
+ &GameListWidget::onTableViewContextMenuRequested);
+ connect(m_table_view->horizontalHeader(), &QHeaderView::customContextMenuRequested, this,
+ &GameListWidget::onTableViewHeaderContextMenuRequested);
+ connect(m_table_view->horizontalHeader(), &QHeaderView::sortIndicatorChanged, this,
+ &GameListWidget::onTableViewHeaderSortIndicatorChanged);
+
+ insertWidget(0, m_table_view);
+
+ m_list_view = new GameListGridListView(this);
+ m_list_view->setModel(m_sort_model);
+ m_list_view->setModelColumn(GameListModel::Column_Cover);
+ m_list_view->setSelectionMode(QAbstractItemView::ExtendedSelection);
+ m_list_view->setViewMode(QListView::IconMode);
+ m_list_view->setResizeMode(QListView::Adjust);
+ m_list_view->setUniformItemSizes(true);
+ m_list_view->setItemAlignment(Qt::AlignHCenter);
+ m_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
+ m_list_view->setFrameStyle(QFrame::NoFrame);
+ m_list_view->setSpacing(m_model->getCoverArtSpacing());
+ updateListFont();
+
+ connect(m_list_view->selectionModel(), &QItemSelectionModel::currentChanged, this,
+ &GameListWidget::onSelectionModelCurrentChanged);
+ connect(m_list_view, &GameListGridListView::zoomIn, this, &GameListWidget::gridZoomIn);
+ connect(m_list_view, &GameListGridListView::zoomOut, this, &GameListWidget::gridZoomOut);
+ connect(m_list_view, &QListView::activated, this, &GameListWidget::onListViewItemActivated);
+ connect(m_list_view, &QListView::customContextMenuRequested, this, &GameListWidget::onListViewContextMenuRequested);
+
+ insertWidget(1, m_list_view);
+
+ if (QtHost::GetBaseBoolSettingValue("UI", "GameListGridView", false))
+ setCurrentIndex(1);
+ else
+ setCurrentIndex(0);
+
+ resizeTableViewColumnsToFit();
+}
+
+bool GameListWidget::isShowingGameList() const
+{
+ return currentIndex() == 0;
+}
+
+bool GameListWidget::isShowingGameGrid() const
+{
+ return currentIndex() == 1;
+}
+
+bool GameListWidget::getShowGridCoverTitles() const
+{
+ return m_model->getShowCoverTitles();
+}
+
+void GameListWidget::refresh(bool invalidate_cache)
+{
+ if (m_refresh_thread)
+ {
+ m_refresh_thread->cancel();
+ m_refresh_thread->wait();
+ QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+ pxAssertRel(!m_refresh_thread, "Game list thread should be unreferenced by now");
+ }
+
+ m_refresh_thread = new GameListRefreshThread(invalidate_cache);
+ connect(m_refresh_thread, &GameListRefreshThread::refreshProgress, this, &GameListWidget::onRefreshProgress,
+ Qt::QueuedConnection);
+ connect(m_refresh_thread, &GameListRefreshThread::refreshComplete, this, &GameListWidget::onRefreshComplete,
+ Qt::QueuedConnection);
+ m_refresh_thread->start();
+}
+
+void GameListWidget::onRefreshProgress(const QString& status, int current, int total)
+{
+ m_model->refresh();
+ emit refreshProgress(status, current, total);
+}
+
+void GameListWidget::onRefreshComplete()
+{
+ m_model->refresh();
+ emit refreshComplete();
+
+ pxAssertRel(m_refresh_thread, "Has a refresh thread");
+ m_refresh_thread->wait();
+ delete m_refresh_thread;
+ m_refresh_thread = nullptr;
+}
+
+void GameListWidget::onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous)
+{
+ const QModelIndex source_index = m_sort_model->mapToSource(current);
+ if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount()))
+ return;
+
+ emit selectionChanged();
+}
+
+void GameListWidget::onTableViewItemActivated(const QModelIndex& index)
+{
+ const QModelIndex source_index = m_sort_model->mapToSource(index);
+ if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount()))
+ return;
+
+ emit entryActivated();
+}
+
+void GameListWidget::onTableViewContextMenuRequested(const QPoint& point)
+{
+ emit entryContextMenuRequested(m_table_view->mapToGlobal(point));
+}
+
+void GameListWidget::onListViewItemActivated(const QModelIndex& index)
+{
+ const QModelIndex source_index = m_sort_model->mapToSource(index);
+ if (!source_index.isValid() || source_index.row() >= static_cast(GameList::GetEntryCount()))
+ return;
+
+ emit entryActivated();
+}
+
+void GameListWidget::onListViewContextMenuRequested(const QPoint& point)
+{
+ emit entryContextMenuRequested(m_list_view->mapToGlobal(point));
+}
+
+void GameListWidget::onTableViewHeaderContextMenuRequested(const QPoint& point)
+{
+ QMenu menu;
+
+ for (int column = 0; column < GameListModel::Column_Count; column++)
+ {
+ if (column == GameListModel::Column_Cover)
+ continue;
+
+ QAction* action = menu.addAction(m_model->getColumnDisplayName(column));
+ action->setCheckable(true);
+ action->setChecked(!m_table_view->isColumnHidden(column));
+ connect(action, &QAction::toggled, [this, column](bool enabled) {
+ m_table_view->setColumnHidden(column, !enabled);
+ saveTableViewColumnVisibilitySettings(column);
+ resizeTableViewColumnsToFit();
+ });
+ }
+
+ menu.exec(m_table_view->mapToGlobal(point));
+}
+
+void GameListWidget::onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder)
+{
+ saveTableViewColumnSortSettings();
+}
+
+void GameListWidget::listZoom(float delta)
+{
+ static constexpr float MIN_SCALE = 0.1f;
+ static constexpr float MAX_SCALE = 2.0f;
+
+ const float new_scale = std::clamp(m_model->getCoverScale() + delta, MIN_SCALE, MAX_SCALE);
+ QtHost::SetBaseFloatSettingValue("UI", "GameListCoverArtScale", new_scale);
+ m_model->setCoverScale(new_scale);
+ updateListFont();
+
+ m_model->refresh();
+}
+
+void GameListWidget::gridZoomIn()
+{
+ listZoom(0.05f);
+}
+
+void GameListWidget::gridZoomOut()
+{
+ listZoom(-0.05f);
+}
+
+void GameListWidget::refreshGridCovers()
+{
+ m_model->refreshCovers();
+}
+
+void GameListWidget::showGameList()
+{
+ if (currentIndex() == 0)
+ return;
+
+ QtHost::SetBaseBoolSettingValue("UI", "GameListGridView", false);
+ setCurrentIndex(0);
+ resizeTableViewColumnsToFit();
+}
+
+void GameListWidget::showGameGrid()
+{
+ if (currentIndex() == 1)
+ return;
+
+ QtHost::SetBaseBoolSettingValue("UI", "GameListGridView", true);
+ setCurrentIndex(1);
+}
+
+void GameListWidget::setShowCoverTitles(bool enabled)
+{
+ if (m_model->getShowCoverTitles() == enabled)
+ return;
+
+ QtHost::SetBaseBoolSettingValue("UI", "GameListShowCoverTitles", enabled);
+ m_model->setShowCoverTitles(enabled);
+ if (isShowingGameGrid())
+ m_model->refresh();
+}
+
+void GameListWidget::updateListFont()
+{
+ QFont font;
+ font.setPointSizeF(16.0f * m_model->getCoverScale());
+ m_list_view->setFont(font);
+}
+
+void GameListWidget::resizeEvent(QResizeEvent* event)
+{
+ QStackedWidget::resizeEvent(event);
+ resizeTableViewColumnsToFit();
+}
+
+void GameListWidget::resizeTableViewColumnsToFit()
+{
+ QtUtils::ResizeColumnsForTableView(m_table_view, {
+ 32, // type
+ 80, // code
+ -1, // title
+ -1, // file title
+ 50, // crc
+ 80, // size
+ 50, // region
+ 100 // compatibility
+ });
+}
+
+static std::string getColumnVisibilitySettingsKeyName(int column)
+{
+ return StringUtil::StdStringFromFormat("Show%s",
+ GameListModel::getColumnName(static_cast(column)));
+}
+
+void GameListWidget::loadTableViewColumnVisibilitySettings()
+{
+ static constexpr std::array DEFAULT_VISIBILITY = {{
+ true, // type
+ true, // code
+ true, // title
+ false, // file title
+ false, // crc
+ true, // size
+ true, // region
+ true // compatibility
+ }};
+
+ for (int column = 0; column < GameListModel::Column_Count; column++)
+ {
+ const bool visible = QtHost::GetBaseBoolSettingValue(
+ "GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), DEFAULT_VISIBILITY[column]);
+ m_table_view->setColumnHidden(column, !visible);
+ }
+}
+
+void GameListWidget::saveTableViewColumnVisibilitySettings()
+{
+ for (int column = 0; column < GameListModel::Column_Count; column++)
+ {
+ const bool visible = !m_table_view->isColumnHidden(column);
+ QtHost::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
+ }
+}
+
+void GameListWidget::saveTableViewColumnVisibilitySettings(int column)
+{
+ const bool visible = !m_table_view->isColumnHidden(column);
+ QtHost::SetBaseBoolSettingValue("GameListTableView", getColumnVisibilitySettingsKeyName(column).c_str(), visible);
+}
+
+void GameListWidget::loadTableViewColumnSortSettings()
+{
+ const GameListModel::Column DEFAULT_SORT_COLUMN = GameListModel::Column_Type;
+ const bool DEFAULT_SORT_DESCENDING = false;
+
+ const GameListModel::Column sort_column =
+ GameListModel::getColumnIdForName(QtHost::GetBaseStringSettingValue("GameListTableView", "SortColumn"))
+ .value_or(DEFAULT_SORT_COLUMN);
+ const bool sort_descending =
+ QtHost::GetBaseBoolSettingValue("GameListTableView", "SortDescending", DEFAULT_SORT_DESCENDING);
+ m_table_view->sortByColumn(sort_column, sort_descending ? Qt::DescendingOrder : Qt::AscendingOrder);
+}
+
+void GameListWidget::saveTableViewColumnSortSettings()
+{
+ const int sort_column = m_table_view->horizontalHeader()->sortIndicatorSection();
+ const bool sort_descending = (m_table_view->horizontalHeader()->sortIndicatorOrder() == Qt::DescendingOrder);
+
+ if (sort_column >= 0 && sort_column < GameListModel::Column_Count)
+ {
+ QtHost::SetBaseStringSettingValue(
+ "GameListTableView", "SortColumn", GameListModel::getColumnName(static_cast(sort_column)));
+ }
+
+ QtHost::SetBaseBoolSettingValue("GameListTableView", "SortDescending", sort_descending);
+}
+
+const GameList::Entry* GameListWidget::getSelectedEntry() const
+{
+ if (currentIndex() == 0)
+ {
+ const QItemSelectionModel* selection_model = m_table_view->selectionModel();
+ if (!selection_model->hasSelection())
+ return nullptr;
+
+ const QModelIndexList selected_rows = selection_model->selectedRows();
+ if (selected_rows.empty())
+ return nullptr;
+
+ const QModelIndex source_index = m_sort_model->mapToSource(selected_rows[0]);
+ if (!source_index.isValid())
+ return nullptr;
+
+ return GameList::GetEntryByIndex(source_index.row());
+ }
+ else
+ {
+ const QItemSelectionModel* selection_model = m_list_view->selectionModel();
+ if (!selection_model->hasSelection())
+ return nullptr;
+
+ const QModelIndex source_index = m_sort_model->mapToSource(selection_model->currentIndex());
+ if (!source_index.isValid())
+ return nullptr;
+
+ return GameList::GetEntryByIndex(source_index.row());
+ }
+}
+
+GameListGridListView::GameListGridListView(QWidget* parent /*= nullptr*/)
+ : QListView(parent)
+{
+}
+
+void GameListGridListView::wheelEvent(QWheelEvent* e)
+{
+ if (e->modifiers() & Qt::ControlModifier)
+ {
+ int dy = e->angleDelta().y();
+ if (dy != 0)
+ {
+ if (dy < 0)
+ zoomOut();
+ else
+ zoomIn();
+
+ return;
+ }
+ }
+
+ QListView::wheelEvent(e);
+}
diff --git a/pcsx2-qt/GameList/GameListWidget.h b/pcsx2-qt/GameList/GameListWidget.h
new file mode 100644
index 000000000..46567d42a
--- /dev/null
+++ b/pcsx2-qt/GameList/GameListWidget.h
@@ -0,0 +1,111 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "pcsx2/Frontend/GameList.h"
+#include
+#include
+#include
+
+Q_DECLARE_METATYPE(const GameList::Entry*);
+
+class GameListModel;
+class GameListSortModel;
+class GameListRefreshThread;
+
+class GameListGridListView : public QListView
+{
+ Q_OBJECT
+
+public:
+ GameListGridListView(QWidget* parent = nullptr);
+
+Q_SIGNALS:
+ void zoomOut();
+ void zoomIn();
+
+protected:
+ void wheelEvent(QWheelEvent* e);
+};
+
+class GameListWidget : public QStackedWidget
+{
+ Q_OBJECT
+
+public:
+ GameListWidget(QWidget* parent = nullptr);
+ ~GameListWidget();
+
+ __fi GameListModel* getModel() const { return m_model; }
+
+ void initialize();
+
+ void refresh(bool invalidate_cache);
+
+ bool isShowingGameList() const;
+ bool isShowingGameGrid() const;
+
+ bool getShowGridCoverTitles() const;
+
+ const GameList::Entry* getSelectedEntry() const;
+
+Q_SIGNALS:
+ void refreshProgress(const QString& status, int current, int total);
+ void refreshComplete();
+
+ void selectionChanged();
+ void entryActivated();
+ void entryContextMenuRequested(const QPoint& point);
+
+private Q_SLOTS:
+ void onRefreshProgress(const QString& status, int current, int total);
+ void onRefreshComplete();
+
+ void onSelectionModelCurrentChanged(const QModelIndex& current, const QModelIndex& previous);
+ void onTableViewItemActivated(const QModelIndex& index);
+ void onTableViewContextMenuRequested(const QPoint& point);
+ void onTableViewHeaderContextMenuRequested(const QPoint& point);
+ void onTableViewHeaderSortIndicatorChanged(int, Qt::SortOrder);
+ void onListViewItemActivated(const QModelIndex& index);
+ void onListViewContextMenuRequested(const QPoint& point);
+
+public Q_SLOTS:
+ void showGameList();
+ void showGameGrid();
+ void setShowCoverTitles(bool enabled);
+ void gridZoomIn();
+ void gridZoomOut();
+ void refreshGridCovers();
+
+protected:
+ void resizeEvent(QResizeEvent* event);
+
+private:
+ void resizeTableViewColumnsToFit();
+ void loadTableViewColumnVisibilitySettings();
+ void saveTableViewColumnVisibilitySettings();
+ void saveTableViewColumnVisibilitySettings(int column);
+ void loadTableViewColumnSortSettings();
+ void saveTableViewColumnSortSettings();
+ void listZoom(float delta);
+ void updateListFont();
+
+ GameListModel* m_model = nullptr;
+ GameListSortModel* m_sort_model = nullptr;
+ QTableView* m_table_view = nullptr;
+ GameListGridListView* m_list_view = nullptr;
+
+ GameListRefreshThread* m_refresh_thread = nullptr;
+};
diff --git a/pcsx2-qt/Main.cpp b/pcsx2-qt/Main.cpp
new file mode 100644
index 000000000..80e286f9c
--- /dev/null
+++ b/pcsx2-qt/Main.cpp
@@ -0,0 +1,264 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include
+#include
+
+#include "MainWindow.h"
+#include "EmuThread.h"
+#include "QtHost.h"
+
+#include "CDVD/CDVD.h"
+#include "Frontend/GameList.h"
+#include "svnrev.h"
+
+static void PrintCommandLineVersion()
+{
+ std::fprintf(stderr, "PCSX2 Version %s\n", GIT_REV);
+ std::fprintf(stderr, "https://pcsx2.net/\n");
+ std::fprintf(stderr, "\n");
+}
+
+static void PrintCommandLineHelp(const char* progname)
+{
+ PrintCommandLineVersion();
+ std::fprintf(stderr, "Usage: %s [parameters] [--] [boot filename]\n", progname);
+ std::fprintf(stderr, "\n");
+ std::fprintf(stderr, " -help: Displays this information and exits.\n");
+ std::fprintf(stderr, " -version: Displays version information and exits.\n");
+ std::fprintf(stderr, " -batch: Enables batch mode (exits after powering off).\n");
+ std::fprintf(stderr, " -fastboot: Force fast boot for provided filename.\n");
+ std::fprintf(stderr, " -slowboot: Force slow boot for provided filename.\n");
+ std::fprintf(stderr, " -resume: Load resume save state. If a boot filename is provided,\n"
+ " that game's resume state will be loaded, otherwise the most\n"
+ " recent resume save state will be loaded.\n");
+ std::fprintf(stderr, " -state : Loads specified save state by index.\n");
+ std::fprintf(stderr, " -statefile : Loads state from the specified filename.\n"
+ " No boot filename is required with this option.\n");
+ std::fprintf(stderr, " -fullscreen: Enters fullscreen mode immediately after starting.\n");
+ std::fprintf(stderr, " -nofullscreen: Prevents fullscreen mode from triggering if enabled.\n");
+ std::fprintf(stderr, " -portable: Forces \"portable mode\", data in same directory.\n");
+ std::fprintf(stderr, " -settings : Loads a custom settings configuration from the\n"
+ " specified filename. Default settings applied if file not found.\n");
+ std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
+ " parameters make up the filename. Use when the filename contains\n"
+ " spaces or starts with a dash.\n");
+ std::fprintf(stderr, "\n");
+}
+
+static std::shared_ptr& AutoBoot(std::shared_ptr& autoboot)
+{
+ if (!autoboot)
+ {
+ autoboot = std::make_shared();
+ autoboot->source_type = CDVD_SourceType::NoDisc;
+ }
+ return autoboot;
+}
+
+static bool ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr& autoboot)
+{
+ std::optional fast_boot;
+ std::optional start_fullscreen;
+ std::optional state_index;
+ std::string state_filename;
+ bool no_more_args = false;
+
+ for (int i = 1; i < argc; i++)
+ {
+ if (!no_more_args)
+ {
+#define CHECK_ARG(str) !std::strcmp(argv[i], str)
+#define CHECK_ARG_PARAM(str) (!std::strcmp(argv[i], str) && ((i + 1) < argc))
+
+ if (CHECK_ARG("-help"))
+ {
+ PrintCommandLineHelp(argv[0]);
+ return false;
+ }
+ else if (CHECK_ARG("-version"))
+ {
+ PrintCommandLineVersion();
+ return false;
+ }
+ else if (CHECK_ARG("-batch"))
+ {
+ Console.WriteLn("Enabling batch mode.");
+ AutoBoot(autoboot)->batch_mode = true;
+ continue;
+ }
+ else if (CHECK_ARG("-fastboot"))
+ {
+ Console.WriteLn("Forcing fast boot.");
+ fast_boot = true;
+ continue;
+ }
+ else if (CHECK_ARG("-slowboot"))
+ {
+ Console.WriteLn("Forcing slow boot.");
+ fast_boot = false;
+ continue;
+ }
+ else if (CHECK_ARG("-resume"))
+ {
+ state_index = -1;
+ continue;
+ }
+ else if (CHECK_ARG_PARAM("-state"))
+ {
+ state_index = std::atoi(argv[++i]);
+ continue;
+ }
+ else if (CHECK_ARG_PARAM("-statefile"))
+ {
+ AutoBoot(autoboot)->save_state = argv[++i];
+ continue;
+ }
+ else if (CHECK_ARG_PARAM("-elf"))
+ {
+ AutoBoot(autoboot)->elf_override = argv[++i];
+ continue;
+ }
+ else if (CHECK_ARG("-fullscreen"))
+ {
+ Console.WriteLn("Going fullscreen after booting.");
+ start_fullscreen = true;
+ continue;
+ }
+ else if (CHECK_ARG("-nofullscreen"))
+ {
+ Console.WriteLn("Preventing fullscreen after booting.");
+ start_fullscreen = false;
+ continue;
+ }
+ else if (CHECK_ARG("-portable"))
+ {
+ Console.WriteLn("Using portable mode.");
+ // SetUserDirectoryToProgramDirectory();
+ continue;
+ }
+ else if (CHECK_ARG("-resume"))
+ {
+ state_index = -1;
+ continue;
+ }
+ else if (CHECK_ARG("--"))
+ {
+ no_more_args = true;
+ continue;
+ }
+ else if (argv[i][0] == '-')
+ {
+ Console.Error("Unknown parameter: '%s'", argv[i]);
+ return false;
+ }
+
+#undef CHECK_ARG
+#undef CHECK_ARG_PARAM
+ }
+
+ if (!AutoBoot(autoboot)->source.empty())
+ AutoBoot(autoboot)->source += ' ';
+ else
+ AutoBoot(autoboot)->source_type = CDVD_SourceType::Iso;
+
+ AutoBoot(autoboot)->source += argv[i];
+ }
+
+ return true;
+}
+
+int main(int argc, char* argv[])
+{
+#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
+ QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+ QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+#endif
+#endif
+
+ QApplication app(argc, argv);
+ std::shared_ptr autoboot;
+ if (!ParseCommandLineOptions(argc, argv, autoboot))
+ return EXIT_FAILURE;
+
+ MainWindow* main_window = new MainWindow(QApplication::style()->objectName());
+
+ if (!QtHost::Initialize())
+ {
+ delete main_window;
+ return EXIT_FAILURE;
+ }
+
+ main_window->initialize();
+ EmuThread::start();
+
+ main_window->refreshGameList(false);
+ main_window->show();
+
+ if (autoboot)
+ g_emu_thread->startVM(std::move(autoboot));
+
+ const int result = app.exec();
+
+ EmuThread::stop();
+ QtHost::Shutdown();
+ return result;
+}
+
+#ifdef _WIN32
+
+// Apparently Qt6 got rid of this?
+#include "common/RedtapeWindows.h"
+#include
+
+/*
+ WinMain() - Initializes Windows and calls user's startup function main().
+ NOTE: WinMain() won't be called if the application was linked as a "console"
+ application.
+*/
+
+// Convert a wchar_t to char string, equivalent to QString::toLocal8Bit()
+// when passed CP_ACP.
+static inline char* wideToMulti(unsigned int codePage, const wchar_t* aw)
+{
+ const int required = WideCharToMultiByte(codePage, 0, aw, -1, nullptr, 0, nullptr, nullptr);
+ char* result = new char[required];
+ WideCharToMultiByte(codePage, 0, aw, -1, result, required, nullptr, nullptr);
+ return result;
+}
+
+extern "C" int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR /*cmdParamarg*/, int /* cmdShow */)
+{
+ int argc = 0;
+ wchar_t** argvW = CommandLineToArgvW(GetCommandLineW(), &argc);
+ if (argvW == nullptr)
+ return -1;
+ char** argv = new char*[argc + 1];
+ for (int i = 0; i != argc; ++i)
+ argv[i] = wideToMulti(CP_ACP, argvW[i]);
+ argv[argc] = nullptr;
+ LocalFree(argvW);
+ const int exitCode = main(argc, argv);
+ for (int i = 0; (i != argc) && (argv[i] != nullptr); ++i)
+ delete[] argv[i];
+ delete[] argv;
+ return exitCode;
+}
+
+#endif
\ No newline at end of file
diff --git a/pcsx2-qt/MainWindow.cpp b/pcsx2-qt/MainWindow.cpp
new file mode 100644
index 000000000..9660c1cea
--- /dev/null
+++ b/pcsx2-qt/MainWindow.cpp
@@ -0,0 +1,1372 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "common/FileSystem.h"
+
+#include "pcsx2/CDVD/CDVDaccess.h"
+#include "pcsx2/Frontend/GameList.h"
+#include "pcsx2/HostDisplay.h"
+
+#include "AboutDialog.h"
+#include "DisplayWidget.h"
+#include "EmuThread.h"
+#include "GameList/GameListRefreshThread.h"
+#include "GameList/GameListWidget.h"
+#include "MainWindow.h"
+#include "QtHost.h"
+#include "QtUtils.h"
+#include "Settings/ControllerSettingsDialog.h"
+#include "Settings/GameListSettingsWidget.h"
+#include "Settings/InterfaceSettingsWidget.h"
+#include "svnrev.h"
+
+static constexpr char DISC_IMAGE_FILTER[] = QT_TRANSLATE_NOOP(
+ "MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u);;Single-Track Raw Images (*.bin "
+ "*.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;CSO Images (*.cso);;"
+ "ELF Executables (*.elf);;IRX Executables (*.irx);;Playlists (*.m3u)");
+
+const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion";
+
+MainWindow* g_main_window = nullptr;
+
+MainWindow::MainWindow(const QString& unthemed_style_name)
+ : m_unthemed_style_name(unthemed_style_name)
+{
+ pxAssert(!g_main_window);
+ g_main_window = this;
+}
+
+MainWindow::~MainWindow()
+{
+ // we compare here, since recreate destroys the window later
+ if (g_main_window == this)
+ g_main_window = nullptr;
+}
+
+void MainWindow::initialize()
+{
+ setIconThemeFromSettings();
+ m_ui.setupUi(this);
+ setupAdditionalUi();
+ setStyleFromSettings();
+ connectSignals();
+
+ restoreStateFromConfig();
+ switchToGameListView();
+ updateWindowTitle();
+ updateSaveStateMenus(QString(), QString(), 0);
+}
+
+void MainWindow::setupAdditionalUi()
+{
+ const bool toolbar_visible = QtHost::GetBaseBoolSettingValue("UI", "ShowToolbar", false);
+ m_ui.actionViewToolbar->setChecked(toolbar_visible);
+ m_ui.toolBar->setVisible(toolbar_visible);
+
+ const bool toolbars_locked = QtHost::GetBaseBoolSettingValue("UI", "LockToolbar", false);
+ m_ui.actionViewLockToolbar->setChecked(toolbars_locked);
+ m_ui.toolBar->setMovable(!toolbars_locked);
+ m_ui.toolBar->setContextMenuPolicy(Qt::PreventContextMenu);
+
+ const bool status_bar_visible = QtHost::GetBaseBoolSettingValue("UI", "ShowStatusBar", true);
+ m_ui.actionViewStatusBar->setChecked(status_bar_visible);
+ m_ui.statusBar->setVisible(status_bar_visible);
+
+ m_game_list_widget = new GameListWidget(m_ui.mainContainer);
+ m_game_list_widget->initialize();
+ m_ui.mainContainer->insertWidget(0, m_game_list_widget);
+ m_ui.mainContainer->setCurrentIndex(0);
+ m_ui.actionGridViewShowTitles->setChecked(m_game_list_widget->getShowGridCoverTitles());
+
+ m_status_progress_widget = new QProgressBar(m_ui.statusBar);
+ m_status_progress_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
+ m_status_progress_widget->setFixedSize(140, 16);
+ m_status_progress_widget->hide();
+
+ for (u32 scale = 0; scale <= 10; scale++)
+ {
+ QAction* action =
+ m_ui.menuWindowSize->addAction((scale == 0) ? tr("Internal Resolution") : tr("%1x Scale").arg(scale));
+ connect(action, &QAction::triggered, [scale]() { g_emu_thread->requestDisplaySize(static_cast(scale)); });
+ }
+
+ updateEmulationActions(false, false);
+}
+
+void MainWindow::connectSignals()
+{
+ connect(m_ui.actionStartFile, &QAction::triggered, this, &MainWindow::onStartFileActionTriggered);
+ connect(m_ui.actionStartBios, &QAction::triggered, this, &MainWindow::onStartBIOSActionTriggered);
+ connect(m_ui.actionChangeDisc, &QAction::triggered, [this] { m_ui.menuChangeDisc->exec(QCursor::pos()); });
+ connect(m_ui.actionChangeDiscFromFile, &QAction::triggered, this, &MainWindow::onChangeDiscFromFileActionTriggered);
+ connect(m_ui.actionChangeDiscFromDevice, &QAction::triggered, this,
+ &MainWindow::onChangeDiscFromDeviceActionTriggered);
+ connect(m_ui.actionChangeDiscFromGameList, &QAction::triggered, this,
+ &MainWindow::onChangeDiscFromGameListActionTriggered);
+ connect(m_ui.menuChangeDisc, &QMenu::aboutToShow, this, &MainWindow::onChangeDiscMenuAboutToShow);
+ connect(m_ui.menuChangeDisc, &QMenu::aboutToHide, this, &MainWindow::onChangeDiscMenuAboutToHide);
+ connect(m_ui.actionPowerOff, &QAction::triggered, []() { g_emu_thread->shutdownVM(); });
+ connect(m_ui.actionLoadState, &QAction::triggered, this, [this]() { m_ui.menuLoadState->exec(QCursor::pos()); });
+ connect(m_ui.actionSaveState, &QAction::triggered, this, [this]() { m_ui.menuSaveState->exec(QCursor::pos()); });
+ connect(m_ui.actionExit, &QAction::triggered, this, &MainWindow::close);
+ connect(m_ui.menuLoadState, &QMenu::aboutToShow, this, &MainWindow::onLoadStateMenuAboutToShow);
+ connect(m_ui.menuSaveState, &QMenu::aboutToShow, this, &MainWindow::onSaveStateMenuAboutToShow);
+ connect(m_ui.actionSettings, &QAction::triggered, [this]() { doSettings(SettingsDialog::Category::Count); });
+ connect(m_ui.actionInterfaceSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::InterfaceSettings); });
+ connect(m_ui.actionGameListSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::GameListSettings); });
+ connect(m_ui.actionEmulationSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::EmulationSettings); });
+ connect(m_ui.actionBIOSSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::BIOSSettings); });
+ connect(m_ui.actionSystemSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::SystemSettings); });
+ connect(m_ui.actionGraphicsSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::GraphicsSettings); });
+ connect(m_ui.actionAudioSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::AudioSettings); });
+ connect(m_ui.actionMemoryCardSettings, &QAction::triggered,
+ [this]() { doSettings(SettingsDialog::Category::MemoryCardSettings); });
+ connect(m_ui.actionControllerSettings, &QAction::triggered,
+ [this]() { doControllerSettings(ControllerSettingsDialog::Category::GlobalSettings); });
+ connect(m_ui.actionHotkeySettings, &QAction::triggered,
+ [this]() { doControllerSettings(ControllerSettingsDialog::Category::HotkeySettings); });
+ connect(m_ui.actionAddGameDirectory, &QAction::triggered,
+ [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
+ connect(m_ui.actionScanForNewGames, &QAction::triggered, [this]() { refreshGameList(false); });
+ connect(m_ui.actionRescanAllGames, &QAction::triggered, [this]() { refreshGameList(true); });
+ connect(m_ui.actionViewToolbar, &QAction::toggled, this, &MainWindow::onViewToolbarActionToggled);
+ connect(m_ui.actionViewLockToolbar, &QAction::toggled, this, &MainWindow::onViewLockToolbarActionToggled);
+ connect(m_ui.actionViewStatusBar, &QAction::toggled, this, &MainWindow::onViewStatusBarActionToggled);
+ connect(m_ui.actionViewGameList, &QAction::triggered, this, &MainWindow::onViewGameListActionTriggered);
+ connect(m_ui.actionViewGameGrid, &QAction::triggered, this, &MainWindow::onViewGameGridActionTriggered);
+ connect(m_ui.actionViewSystemDisplay, &QAction::triggered, this, &MainWindow::onViewSystemDisplayTriggered);
+ connect(m_ui.actionViewGameProperties, &QAction::triggered, this, &MainWindow::onViewGamePropertiesActionTriggered);
+ connect(m_ui.actionGitHubRepository, &QAction::triggered, this, &MainWindow::onGitHubRepositoryActionTriggered);
+ connect(m_ui.actionSupportForums, &QAction::triggered, this, &MainWindow::onSupportForumsActionTriggered);
+ connect(m_ui.actionDiscordServer, &QAction::triggered, this, &MainWindow::onDiscordServerActionTriggered);
+ connect(m_ui.actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
+ connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
+ connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
+ connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
+ connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
+ connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
+ if (isShowingGameList())
+ m_game_list_widget->gridZoomIn();
+ });
+ connect(m_ui.actionGridViewZoomOut, &QAction::triggered, m_game_list_widget, [this]() {
+ if (isShowingGameList())
+ m_game_list_widget->gridZoomOut();
+ });
+ connect(m_ui.actionGridViewRefreshCovers, &QAction::triggered, m_game_list_widget,
+ &GameListWidget::refreshGridCovers);
+
+ // These need to be queued connections to stop crashing due to menus opening/closing and switching focus.
+ connect(m_game_list_widget, &GameListWidget::refreshProgress, this, &MainWindow::onGameListRefreshProgress);
+ connect(m_game_list_widget, &GameListWidget::refreshComplete, this, &MainWindow::onGameListRefreshComplete);
+ connect(m_game_list_widget, &GameListWidget::selectionChanged, this, &MainWindow::onGameListSelectionChanged,
+ Qt::QueuedConnection);
+ connect(m_game_list_widget, &GameListWidget::entryActivated, this, &MainWindow::onGameListEntryActivated,
+ Qt::QueuedConnection);
+ connect(m_game_list_widget, &GameListWidget::entryContextMenuRequested, this,
+ &MainWindow::onGameListEntryContextMenuRequested, Qt::QueuedConnection);
+}
+
+void MainWindow::connectVMThreadSignals(EmuThread* thread)
+{
+ connect(thread, &EmuThread::onCreateDisplayRequested, this, &MainWindow::createDisplay, Qt::BlockingQueuedConnection);
+ connect(thread, &EmuThread::onUpdateDisplayRequested, this, &MainWindow::updateDisplay, Qt::BlockingQueuedConnection);
+ connect(thread, &EmuThread::onDestroyDisplayRequested, this, &MainWindow::destroyDisplay,
+ Qt::BlockingQueuedConnection);
+ connect(thread, &EmuThread::onResizeDisplayRequested, this, &MainWindow::displayResizeRequested);
+ connect(thread, &EmuThread::onVMStarting, this, &MainWindow::onVMStarting);
+ connect(thread, &EmuThread::onVMStarted, this, &MainWindow::onVMStarted);
+ connect(thread, &EmuThread::onVMPaused, this, &MainWindow::onVMPaused);
+ connect(thread, &EmuThread::onVMResumed, this, &MainWindow::onVMResumed);
+ connect(thread, &EmuThread::onVMStopped, this, &MainWindow::onVMStopped);
+ connect(thread, &EmuThread::onGameChanged, this, &MainWindow::onGameChanged);
+
+ connect(m_ui.actionReset, &QAction::triggered, thread, &EmuThread::resetVM);
+ connect(m_ui.actionPause, &QAction::toggled, thread, &EmuThread::setVMPaused);
+ connect(m_ui.actionFullscreen, &QAction::triggered, thread, &EmuThread::toggleFullscreen);
+ connect(m_ui.actionToggleSoftwareRendering, &QAction::triggered, thread, &EmuThread::toggleSoftwareRendering);
+ connect(m_ui.actionReloadPatches, &QAction::triggered, thread, &EmuThread::reloadPatches);
+
+ static constexpr GSRendererType renderers[] = {
+#ifdef _WIN32
+ GSRendererType::DX11,
+#endif
+ GSRendererType::OGL, GSRendererType::VK, GSRendererType::SW, GSRendererType::Null};
+ for (GSRendererType renderer : renderers)
+ {
+ connect(
+ m_ui.menuDebugSwitchRenderer->addAction(QString::fromUtf8(Pcsx2Config::GSOptions::GetRendererName(renderer))),
+ &QAction::triggered, [renderer] { g_emu_thread->switchRenderer(renderer); });
+ }
+}
+
+void MainWindow::recreate()
+{
+ if (m_vm_valid)
+ g_emu_thread->shutdownVM(true, true);
+
+ close();
+ g_main_window = nullptr;
+
+ MainWindow* new_main_window = new MainWindow(m_unthemed_style_name);
+ new_main_window->initialize();
+ new_main_window->refreshGameList(false);
+ new_main_window->show();
+ deleteLater();
+}
+
+void MainWindow::setStyleFromSettings()
+{
+ const std::string theme(QtHost::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME));
+
+ if (theme == "fusion")
+ {
+ qApp->setPalette(QApplication::style()->standardPalette());
+ qApp->setStyleSheet(QString());
+ qApp->setStyle(QStyleFactory::create("Fusion"));
+ }
+ else if (theme == "darkfusion")
+ {
+ // adapted from https://gist.github.com/QuantumCD/6245215
+ qApp->setStyle(QStyleFactory::create("Fusion"));
+
+ const QColor lighterGray(75, 75, 75);
+ const QColor darkGray(53, 53, 53);
+ const QColor gray(128, 128, 128);
+ const QColor black(25, 25, 25);
+ const QColor blue(198, 238, 255);
+
+ QPalette darkPalette;
+ darkPalette.setColor(QPalette::Window, darkGray);
+ darkPalette.setColor(QPalette::WindowText, Qt::white);
+ darkPalette.setColor(QPalette::Base, black);
+ darkPalette.setColor(QPalette::AlternateBase, darkGray);
+ darkPalette.setColor(QPalette::ToolTipBase, darkGray);
+ darkPalette.setColor(QPalette::ToolTipText, Qt::white);
+ darkPalette.setColor(QPalette::Text, Qt::white);
+ darkPalette.setColor(QPalette::Button, darkGray);
+ darkPalette.setColor(QPalette::ButtonText, Qt::white);
+ darkPalette.setColor(QPalette::Link, blue);
+ darkPalette.setColor(QPalette::Highlight, lighterGray);
+ darkPalette.setColor(QPalette::HighlightedText, Qt::white);
+
+ darkPalette.setColor(QPalette::Active, QPalette::Button, gray.darker());
+ darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
+ darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
+ darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
+ darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
+
+ qApp->setPalette(darkPalette);
+
+ qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
+ }
+ else if (theme == "darkfusionblue")
+ {
+ // adapted from https://gist.github.com/QuantumCD/6245215
+ qApp->setStyle(QStyleFactory::create("Fusion"));
+
+ const QColor lighterGray(75, 75, 75);
+ const QColor darkGray(53, 53, 53);
+ const QColor gray(128, 128, 128);
+ const QColor black(25, 25, 25);
+ const QColor blue(198, 238, 255);
+ const QColor blue2(0, 88, 208);
+
+ QPalette darkPalette;
+ darkPalette.setColor(QPalette::Window, darkGray);
+ darkPalette.setColor(QPalette::WindowText, Qt::white);
+ darkPalette.setColor(QPalette::Base, black);
+ darkPalette.setColor(QPalette::AlternateBase, darkGray);
+ darkPalette.setColor(QPalette::ToolTipBase, blue2);
+ darkPalette.setColor(QPalette::ToolTipText, Qt::white);
+ darkPalette.setColor(QPalette::Text, Qt::white);
+ darkPalette.setColor(QPalette::Button, darkGray);
+ darkPalette.setColor(QPalette::ButtonText, Qt::white);
+ darkPalette.setColor(QPalette::Link, blue);
+ darkPalette.setColor(QPalette::Highlight, blue2);
+ darkPalette.setColor(QPalette::HighlightedText, Qt::white);
+
+ darkPalette.setColor(QPalette::Active, QPalette::Button, gray.darker());
+ darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, gray);
+ darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, gray);
+ darkPalette.setColor(QPalette::Disabled, QPalette::Text, gray);
+ darkPalette.setColor(QPalette::Disabled, QPalette::Light, darkGray);
+
+ qApp->setPalette(darkPalette);
+
+ qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
+ }
+ else
+ {
+ qApp->setPalette(QApplication::style()->standardPalette());
+ qApp->setStyleSheet(QString());
+ qApp->setStyle(m_unthemed_style_name);
+ }
+}
+
+void MainWindow::setIconThemeFromSettings()
+{
+ const std::string theme(QtHost::GetBaseStringSettingValue("UI", "Theme", DEFAULT_THEME_NAME));
+ QString icon_theme;
+
+ if (theme == "darkfusion" || theme == "darkfusionblue")
+ icon_theme = QStringLiteral("white");
+ else
+ icon_theme = QStringLiteral("black");
+
+ QIcon::setThemeName(icon_theme);
+}
+
+void MainWindow::saveStateToConfig()
+{
+ {
+ const QByteArray geometry = saveGeometry();
+ const QByteArray geometry_b64 = geometry.toBase64();
+ const std::string old_geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowGeometry");
+ if (old_geometry_b64 != geometry_b64.constData())
+ QtHost::SetBaseStringSettingValue("UI", "MainWindowGeometry", geometry_b64.constData());
+ }
+
+ {
+ const QByteArray state = saveState();
+ const QByteArray state_b64 = state.toBase64();
+ const std::string old_state_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowState");
+ if (old_state_b64 != state_b64.constData())
+ QtHost::SetBaseStringSettingValue("UI", "MainWindowState", state_b64.constData());
+ }
+}
+
+void MainWindow::restoreStateFromConfig()
+{
+ {
+ const std::string geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowGeometry");
+ const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
+ if (!geometry.isEmpty())
+ restoreGeometry(geometry);
+ }
+
+ {
+ const std::string state_b64 = QtHost::GetBaseStringSettingValue("UI", "MainWindowState");
+ const QByteArray state = QByteArray::fromBase64(QByteArray::fromStdString(state_b64));
+ if (!state.isEmpty())
+ restoreState(state);
+
+ {
+ QSignalBlocker sb(m_ui.actionViewToolbar);
+ m_ui.actionViewToolbar->setChecked(!m_ui.toolBar->isHidden());
+ }
+ {
+ QSignalBlocker sb(m_ui.actionViewStatusBar);
+ m_ui.actionViewStatusBar->setChecked(!m_ui.statusBar->isHidden());
+ }
+ }
+}
+
+void MainWindow::updateEmulationActions(bool starting, bool running)
+{
+ const bool starting_or_running = starting || running;
+
+ m_ui.actionStartFile->setDisabled(starting_or_running);
+ m_ui.actionStartDisc->setDisabled(starting_or_running);
+ m_ui.actionStartBios->setDisabled(starting_or_running);
+
+ m_ui.actionPowerOff->setEnabled(running);
+ m_ui.actionReset->setEnabled(running);
+ m_ui.actionPause->setEnabled(running);
+ m_ui.actionChangeDisc->setEnabled(running);
+ m_ui.actionCheats->setEnabled(running);
+ m_ui.actionScreenshot->setEnabled(running);
+ m_ui.actionViewSystemDisplay->setEnabled(starting_or_running);
+ m_ui.menuChangeDisc->setEnabled(running);
+ m_ui.menuCheats->setEnabled(running);
+
+ m_ui.actionSaveState->setEnabled(running);
+ m_ui.menuSaveState->setEnabled(running);
+ m_ui.menuWindowSize->setEnabled(starting_or_running);
+
+ m_ui.actionFullscreen->setEnabled(starting_or_running);
+ m_ui.actionViewGameProperties->setEnabled(running);
+
+ m_game_list_widget->setDisabled(starting && !running);
+}
+
+void MainWindow::updateWindowTitle()
+{
+ QString title;
+ if (!m_vm_valid || m_current_game_name.isEmpty())
+ {
+#if defined(_DEBUG)
+ title = QStringLiteral("PCSX2 [Debug] %1").arg(GIT_REV);
+#else
+ title = QStringLiteral("PCSX2 %1").arg(GIT_REV);
+#endif
+ }
+ else
+ {
+#if defined(_DEBUG)
+ title = QStringLiteral("%1 [Debug]").arg(m_current_game_name);
+#else
+ title = m_current_game_name;
+#endif
+ }
+
+ if (windowTitle() != title)
+ setWindowTitle(title);
+}
+
+void MainWindow::setProgressBar(int current, int total)
+{
+ m_status_progress_widget->setValue(current);
+ m_status_progress_widget->setMaximum(total);
+
+ if (m_status_progress_widget->isVisible())
+ return;
+
+ m_status_progress_widget->show();
+ m_ui.statusBar->addPermanentWidget(m_status_progress_widget);
+}
+
+void MainWindow::clearProgressBar()
+{
+ if (!m_status_progress_widget->isVisible())
+ return;
+
+ m_status_progress_widget->hide();
+ m_ui.statusBar->removeWidget(m_status_progress_widget);
+}
+
+bool MainWindow::isShowingGameList() const
+{
+ return m_ui.mainContainer->currentIndex() == 0;
+}
+
+void MainWindow::switchToGameListView()
+{
+ if ((m_display_widget && !m_display_widget->parent()) || m_ui.mainContainer->currentIndex() == 0)
+ return;
+
+ if (m_vm_valid)
+ {
+ m_was_focused_on_container_switch = m_vm_paused;
+ if (!m_vm_paused)
+ g_emu_thread->setVMPaused(true);
+ }
+
+ m_ui.mainContainer->setCurrentIndex(0);
+ m_game_list_widget->setFocus();
+}
+
+void MainWindow::switchToEmulationView()
+{
+ if (!m_display_widget || !m_display_widget->parent() || m_ui.mainContainer->currentIndex() == 1)
+ return;
+
+ if (m_vm_valid)
+ {
+ m_ui.mainContainer->setCurrentIndex(1);
+ if (m_vm_paused && !m_was_focused_on_container_switch)
+ g_emu_thread->setVMPaused(false);
+ }
+
+ m_display_widget->setFocus();
+}
+
+void MainWindow::refreshGameList(bool invalidate_cache)
+{
+ m_game_list_widget->refresh(invalidate_cache);
+}
+
+void MainWindow::invalidateSaveStateCache()
+{
+ m_save_states_invalidated = true;
+}
+
+void MainWindow::reportError(const QString& title, const QString& message)
+{
+ QMessageBox::critical(this, title, message);
+}
+
+void Host::InvalidateSaveStateCache()
+{
+ QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection);
+}
+
+void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total)
+{
+ m_ui.statusBar->showMessage(status);
+ setProgressBar(current, total);
+}
+
+void MainWindow::onGameListRefreshComplete()
+{
+ clearProgressBar();
+}
+
+void MainWindow::onGameListSelectionChanged()
+{
+ auto lock = GameList::GetLock();
+ const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
+ if (!entry)
+ return;
+
+ m_ui.statusBar->showMessage(QString::fromStdString(entry->path));
+}
+
+void MainWindow::onGameListEntryActivated()
+{
+ auto lock = GameList::GetLock();
+ const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
+ if (!entry)
+ return;
+
+ if (m_vm_valid)
+ {
+ // change disc on double click
+ g_emu_thread->changeDisc(QString::fromStdString(entry->path));
+ switchToEmulationView();
+ return;
+ }
+
+ // only resume if the option is enabled, and we have one for this game
+ const bool resume =
+ (VMManager::ShouldSaveResumeState() && VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1));
+ startGameListEntry(entry, resume ? std::optional(-1) : std::optional(), std::nullopt);
+}
+
+void MainWindow::onGameListEntryContextMenuRequested(const QPoint& point)
+{
+ auto lock = GameList::GetLock();
+ const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
+
+ QMenu menu;
+
+ if (entry)
+ {
+ QAction* action = menu.addAction(tr("Properties..."));
+ // connect(action, &QAction::triggered, [this, entry]() { GamePropertiesDialog::showForEntry(entry, this); });
+
+ action = menu.addAction(tr("Open Containing Directory..."));
+ connect(action, &QAction::triggered, [this, entry]() {
+ const QFileInfo fi(QString::fromStdString(entry->path));
+ QtUtils::OpenURL(this, QUrl::fromLocalFile(fi.absolutePath()));
+ });
+
+ action = menu.addAction(tr("Set Cover Image..."));
+ connect(action, &QAction::triggered, [this, entry]() { setGameListEntryCoverImage(entry); });
+
+ connect(menu.addAction(tr("Exclude From List")), &QAction::triggered,
+ [this, entry]() { getSettingsDialog()->getGameListSettingsWidget()->addExcludedPath(entry->path); });
+
+ menu.addSeparator();
+
+ if (!m_vm_valid)
+ {
+ action = menu.addAction(tr("Default Boot"));
+ connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry); });
+
+ // Make bold to indicate it's the default choice when double-clicking
+ if (!VMManager::ShouldSaveResumeState() || !VMManager::HasSaveStateInSlot(entry->serial.c_str(), entry->crc, -1))
+ QtUtils::MarkActionAsDefault(action);
+
+ action = menu.addAction(tr("Fast Boot"));
+ connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, true); });
+
+ action = menu.addAction(tr("Full Boot"));
+ connect(action, &QAction::triggered, [this, entry]() { startGameListEntry(entry, std::nullopt, false); });
+
+ if (m_ui.menuDebug->menuAction()->isVisible())
+ {
+ // TODO: Hook this up once it's implemented.
+ action = menu.addAction(tr("Boot and Debug"));
+ }
+
+ menu.addSeparator();
+ populateLoadStateMenu(&menu, QString::fromStdString(entry->path), QString::fromStdString(entry->serial),
+ entry->crc);
+ }
+ else
+ {
+ action = menu.addAction(tr("Change Disc"));
+ connect(action, &QAction::triggered, [this, entry]() {
+ g_emu_thread->changeDisc(QString::fromStdString(entry->path));
+ switchToEmulationView();
+ });
+ QtUtils::MarkActionAsDefault(action);
+ }
+
+ menu.addSeparator();
+ }
+
+ connect(menu.addAction(tr("Add Search Directory...")), &QAction::triggered,
+ [this]() { getSettingsDialog()->getGameListSettingsWidget()->addSearchDirectory(this); });
+
+ menu.exec(point);
+}
+
+void MainWindow::onStartFileActionTriggered()
+{
+ QString filename = QDir::toNativeSeparators(
+ QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr));
+ if (filename.isEmpty())
+ return;
+
+ std::shared_ptr params = std::make_shared();
+ VMManager::SetBootParametersForPath(filename.toStdString(), params.get());
+ g_emu_thread->startVM(std::move(params));
+}
+
+void MainWindow::onStartBIOSActionTriggered()
+{
+ std::shared_ptr params = std::make_shared();
+ params->source_type = CDVD_SourceType::NoDisc;
+ g_emu_thread->startVM(std::move(params));
+}
+
+void MainWindow::onChangeDiscFromFileActionTriggered()
+{
+ ScopedVMPause pauser(m_vm_paused);
+
+ QString filename =
+ QFileDialog::getOpenFileName(this, tr("Select Disc Image"), QString(), tr(DISC_IMAGE_FILTER), nullptr);
+ if (filename.isEmpty())
+ return;
+
+ g_emu_thread->changeDisc(filename);
+}
+
+void MainWindow::onChangeDiscFromGameListActionTriggered()
+{
+ switchToGameListView();
+}
+
+void MainWindow::onChangeDiscFromDeviceActionTriggered()
+{
+ // TODO
+}
+
+void MainWindow::onChangeDiscMenuAboutToShow()
+{
+ // TODO: This is where we would populate the playlist if there is one.
+}
+
+void MainWindow::onChangeDiscMenuAboutToHide()
+{
+}
+
+void MainWindow::onLoadStateMenuAboutToShow()
+{
+ if (m_save_states_invalidated)
+ updateSaveStateMenus(m_current_disc_path, m_current_game_serial, m_current_game_crc);
+}
+
+void MainWindow::onSaveStateMenuAboutToShow()
+{
+ if (m_save_states_invalidated)
+ updateSaveStateMenus(m_current_disc_path, m_current_game_serial, m_current_game_crc);
+}
+
+void MainWindow::onViewToolbarActionToggled(bool checked)
+{
+ QtHost::SetBaseBoolSettingValue("UI", "ShowToolbar", checked);
+ m_ui.toolBar->setVisible(checked);
+}
+
+void MainWindow::onViewLockToolbarActionToggled(bool checked)
+{
+ QtHost::SetBaseBoolSettingValue("UI", "LockToolbar", checked);
+ m_ui.toolBar->setMovable(!checked);
+}
+
+void MainWindow::onViewStatusBarActionToggled(bool checked)
+{
+ QtHost::SetBaseBoolSettingValue("UI", "ShowStatusBar", checked);
+ m_ui.statusBar->setVisible(checked);
+}
+
+void MainWindow::onViewGameListActionTriggered()
+{
+ switchToGameListView();
+ m_game_list_widget->showGameList();
+}
+
+void MainWindow::onViewGameGridActionTriggered()
+{
+ switchToGameListView();
+ m_game_list_widget->showGameGrid();
+}
+
+void MainWindow::onViewSystemDisplayTriggered()
+{
+ if (m_vm_valid)
+ switchToEmulationView();
+}
+
+void MainWindow::onViewGamePropertiesActionTriggered()
+{
+ if (!m_vm_valid)
+ return;
+}
+
+void MainWindow::onGitHubRepositoryActionTriggered()
+{
+ QtUtils::OpenURL(this, AboutDialog::getGitHubRepositoryUrl());
+}
+
+void MainWindow::onSupportForumsActionTriggered()
+{
+ QtUtils::OpenURL(this, AboutDialog::getSupportForumsUrl());
+}
+
+void MainWindow::onDiscordServerActionTriggered()
+{
+ QtUtils::OpenURL(this, AboutDialog::getDiscordServerUrl());
+}
+
+void MainWindow::onAboutActionTriggered()
+{
+ AboutDialog about(this);
+ about.exec();
+}
+
+void MainWindow::onCheckForUpdatesActionTriggered()
+{
+}
+
+void MainWindow::onToolsOpenDataDirectoryTriggered()
+{
+ const QString path(QtUtils::WxStringToQString(EmuFolders::DataRoot.ToString()));
+ QtUtils::OpenURL(this, QUrl::fromLocalFile(path));
+}
+
+void MainWindow::onThemeChanged()
+{
+ setStyleFromSettings();
+ setIconThemeFromSettings();
+ recreate();
+}
+
+void MainWindow::onThemeChangedFromSettings()
+{
+ // reopen the settings dialog after recreating
+ onThemeChanged();
+ g_main_window->doSettings(SettingsDialog::Category::InterfaceSettings);
+}
+
+void MainWindow::onVMStarting()
+{
+ m_vm_valid = true;
+ updateEmulationActions(true, false);
+ updateWindowTitle();
+
+ // prevent loading state until we're fully initialized
+ updateSaveStateMenus(QString(), QString(), 0);
+}
+
+void MainWindow::onVMStarted()
+{
+ m_vm_valid = true;
+ updateEmulationActions(true, true);
+ updateWindowTitle();
+}
+
+void MainWindow::onVMPaused()
+{
+ // update UI
+ {
+ QSignalBlocker sb(m_ui.actionPause);
+ m_ui.actionPause->setChecked(true);
+ }
+
+ m_vm_paused = true;
+ updateWindowTitle();
+}
+
+void MainWindow::onVMResumed()
+{
+ // update UI
+ {
+ QSignalBlocker sb(m_ui.actionPause);
+ m_ui.actionPause->setChecked(false);
+ }
+
+ m_vm_paused = false;
+ updateWindowTitle();
+}
+
+void MainWindow::onVMStopped()
+{
+ m_vm_valid = false;
+ m_vm_paused = false;
+ updateEmulationActions(false, false);
+ updateWindowTitle();
+ switchToGameListView();
+}
+
+void MainWindow::onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc)
+{
+ m_current_disc_path = path;
+ m_current_game_serial = serial;
+ m_current_game_name = name;
+ m_current_game_crc = crc;
+ updateWindowTitle();
+ updateSaveStateMenus(path, serial, crc);
+}
+
+void MainWindow::closeEvent(QCloseEvent* event)
+{
+ g_emu_thread->shutdownVM(true, true);
+ saveStateToConfig();
+ QMainWindow::closeEvent(event);
+}
+
+DisplayWidget* MainWindow::createDisplay(bool fullscreen, bool render_to_main)
+{
+ pxAssertRel(!fullscreen || !render_to_main, "Not rendering to main and fullscreen");
+
+ HostDisplay* host_display = Host::GetHostDisplay();
+ if (!host_display)
+ return nullptr;
+
+ const std::string fullscreen_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""));
+ const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen());
+
+ QWidget* container;
+ if (DisplayContainer::IsNeeded(fullscreen, render_to_main))
+ {
+ m_display_container = new DisplayContainer();
+ m_display_widget = new DisplayWidget(m_display_container);
+ m_display_container->setDisplayWidget(m_display_widget);
+ container = m_display_container;
+ }
+ else
+ {
+ m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr);
+ container = m_display_widget;
+ }
+
+ container->setWindowTitle(windowTitle());
+ container->setWindowIcon(windowIcon());
+
+ if (fullscreen)
+ {
+ if (!is_exclusive_fullscreen)
+ container->showFullScreen();
+ else
+ container->showNormal();
+
+ // updateMouseMode(System::IsPaused());
+ }
+ else if (!render_to_main)
+ {
+ restoreDisplayWindowGeometryFromConfig();
+ container->showNormal();
+ }
+ else
+ {
+ m_ui.mainContainer->insertWidget(1, m_display_widget);
+ switchToEmulationView();
+ }
+
+ // we need the surface visible.. this might be able to be replaced with something else
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+
+ std::optional wi = m_display_widget->getWindowInfo();
+ if (!wi.has_value())
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Failed to get window info from widget"));
+ destroyDisplayWidget();
+ return nullptr;
+ }
+
+ if (!host_display->CreateRenderDevice(wi.value(), Host::GetStringSettingValue("EmuCore/GS", "Adapter", ""),
+ Host::GetBoolSettingValue("EmuCore/GS", "ThreadedPresentation", false),
+ Host::GetBoolSettingValue("EmuCore/GS", "UseDebugDevice", false)))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Failed to create host display device context."));
+ destroyDisplayWidget();
+ return nullptr;
+ }
+
+ if (is_exclusive_fullscreen)
+ setDisplayFullscreen(fullscreen_mode);
+
+ host_display->DoneRenderContextCurrent();
+ return m_display_widget;
+}
+
+DisplayWidget* MainWindow::updateDisplay(bool fullscreen, bool render_to_main)
+{
+ HostDisplay* host_display = Host::GetHostDisplay();
+ const bool is_fullscreen = m_display_widget->isFullScreen();
+ const bool is_rendering_to_main = (!is_fullscreen && m_display_widget->parent());
+ const std::string fullscreen_mode(QtHost::GetBaseStringSettingValue("EmuCore/GS", "FullscreenMode", ""));
+ const bool is_exclusive_fullscreen = (fullscreen && !fullscreen_mode.empty() && host_display->SupportsFullscreen());
+ if (fullscreen == is_fullscreen && is_rendering_to_main == render_to_main)
+ return m_display_widget;
+
+ // Skip recreating the surface if we're just transitioning between fullscreen and windowed with render-to-main off.
+ const bool has_container = (m_display_container != nullptr);
+ const bool needs_container = DisplayContainer::IsNeeded(fullscreen, render_to_main);
+ if (!is_rendering_to_main && !render_to_main && !is_exclusive_fullscreen && has_container == needs_container)
+ {
+ qDebug() << "Toggling to" << (fullscreen ? "fullscreen" : "windowed") << "without recreating surface";
+ if (host_display->IsFullscreen())
+ host_display->SetFullscreen(false, 0, 0, 0.0f);
+
+ if (fullscreen)
+ {
+ m_display_widget->showFullScreen();
+ }
+ else
+ {
+ restoreDisplayWindowGeometryFromConfig();
+ m_display_widget->showNormal();
+ }
+
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+ // updateMouseMode(System::IsPaused());
+ return m_display_widget;
+ }
+
+ host_display->DestroyRenderSurface();
+
+ destroyDisplayWidget();
+
+ QWidget* container;
+ if (DisplayContainer::IsNeeded(fullscreen, render_to_main))
+ {
+ m_display_container = new DisplayContainer();
+ m_display_widget = new DisplayWidget(m_display_container);
+ m_display_container->setDisplayWidget(m_display_widget);
+ container = m_display_container;
+ }
+ else
+ {
+ m_display_widget = new DisplayWidget((!fullscreen && render_to_main) ? m_ui.mainContainer : nullptr);
+ container = m_display_widget;
+ }
+
+ container->setWindowTitle(windowTitle());
+ container->setWindowIcon(windowIcon());
+
+ if (fullscreen)
+ {
+ if (!is_exclusive_fullscreen)
+ container->showFullScreen();
+ else
+ container->showNormal();
+
+ // updateMouseMode(System::IsPaused());
+ }
+ else if (!render_to_main)
+ {
+ restoreDisplayWindowGeometryFromConfig();
+ container->showNormal();
+ }
+ else
+ {
+ m_ui.mainContainer->insertWidget(1, m_display_widget);
+ switchToEmulationView();
+ }
+
+ // we need the surface visible.. this might be able to be replaced with something else
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+
+ std::optional wi = m_display_widget->getWindowInfo();
+ if (!wi.has_value())
+ {
+ QMessageBox::critical(this, tr("Error"), tr("Failed to get new window info from widget"));
+ destroyDisplayWidget();
+ return nullptr;
+ }
+
+ if (!host_display->ChangeRenderWindow(wi.value()))
+ pxFailRel("Failed to recreate surface on new widget.");
+
+ if (is_exclusive_fullscreen)
+ setDisplayFullscreen(fullscreen_mode);
+
+ m_display_widget->setFocus();
+
+ QSignalBlocker blocker(m_ui.actionFullscreen);
+ m_ui.actionFullscreen->setChecked(fullscreen);
+ return m_display_widget;
+}
+
+void MainWindow::displayResizeRequested(qint32 width, qint32 height)
+{
+ if (!m_display_widget)
+ return;
+
+ // unapply the pixel scaling factor for hidpi
+ const float dpr = devicePixelRatioF();
+ width = static_cast(std::max(static_cast(std::lroundf(static_cast(width) / dpr)), 1));
+ height = static_cast(std::max(static_cast(std::lroundf(static_cast(height) / dpr)), 1));
+
+ if (m_display_container || !m_display_widget->parent())
+ {
+ // no parent - rendering to separate window. easy.
+ getDisplayContainer()->resize(QSize(std::max(width, 1), std::max(height, 1)));
+ return;
+ }
+
+ // we are rendering to the main window. we have to add in the extra height from the toolbar/status bar.
+ const s32 extra_height = this->height() - m_display_widget->height();
+ resize(QSize(std::max(width, 1), std::max(height + extra_height, 1)));
+}
+
+void MainWindow::destroyDisplay()
+{
+ destroyDisplayWidget();
+}
+
+void MainWindow::focusDisplayWidget()
+{
+ if (m_ui.mainContainer->currentIndex() != 1)
+ return;
+
+ m_display_widget->setFocus();
+}
+
+QWidget* MainWindow::getDisplayContainer() const
+{
+ return (m_display_container ? static_cast(m_display_container) : static_cast(m_display_widget));
+}
+
+void MainWindow::saveDisplayWindowGeometryToConfig()
+{
+ const QByteArray geometry = getDisplayContainer()->saveGeometry();
+ const QByteArray geometry_b64 = geometry.toBase64();
+ const std::string old_geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
+ if (old_geometry_b64 != geometry_b64.constData())
+ QtHost::SetBaseStringSettingValue("UI", "DisplayWindowGeometry", geometry_b64.constData());
+}
+
+void MainWindow::restoreDisplayWindowGeometryFromConfig()
+{
+ const std::string geometry_b64 = QtHost::GetBaseStringSettingValue("UI", "DisplayWindowGeometry");
+ const QByteArray geometry = QByteArray::fromBase64(QByteArray::fromStdString(geometry_b64));
+ QWidget* container = getDisplayContainer();
+ if (!geometry.isEmpty())
+ container->restoreGeometry(geometry);
+ else
+ container->resize(640, 480);
+}
+
+void MainWindow::destroyDisplayWidget()
+{
+ if (!m_display_widget)
+ return;
+
+ if (m_display_container || (!m_display_widget->parent() && !m_display_widget->isFullScreen()))
+ saveDisplayWindowGeometryToConfig();
+
+ if (m_display_container)
+ m_display_container->removeDisplayWidget();
+
+ if (m_display_widget->parent())
+ {
+ m_ui.mainContainer->removeWidget(m_display_widget);
+ m_ui.mainContainer->setCurrentIndex(0);
+ m_game_list_widget->setFocus();
+ }
+
+ delete m_display_widget;
+ m_display_widget = nullptr;
+
+ delete m_display_container;
+ m_display_container = nullptr;
+}
+
+void MainWindow::setDisplayFullscreen(const std::string& fullscreen_mode)
+{
+ u32 width, height;
+ float refresh_rate;
+ if (HostDisplay::ParseFullscreenMode(fullscreen_mode, &width, &height, &refresh_rate))
+ {
+ if (Host::GetHostDisplay()->SetFullscreen(true, width, height, refresh_rate))
+ {
+ Host::AddOSDMessage("Acquired exclusive fullscreen.", 10.0f);
+ }
+ else
+ {
+ Host::AddOSDMessage("Failed to acquire exclusive fullscreen.", 10.0f);
+ }
+ }
+}
+
+SettingsDialog* MainWindow::getSettingsDialog()
+{
+ if (!m_settings_dialog)
+ {
+ m_settings_dialog = new SettingsDialog(this);
+ connect(m_settings_dialog->getInterfaceSettingsWidget(), &InterfaceSettingsWidget::themeChanged, this,
+ &MainWindow::onThemeChangedFromSettings);
+ }
+
+ return m_settings_dialog;
+}
+
+void MainWindow::doSettings(SettingsDialog::Category category)
+{
+ SettingsDialog* dlg = getSettingsDialog();
+ if (!dlg->isVisible())
+ {
+ dlg->setModal(false);
+ dlg->show();
+ }
+
+ if (category != SettingsDialog::Category::Count)
+ dlg->setCategory(category);
+}
+
+ControllerSettingsDialog* MainWindow::getControllerSettingsDialog()
+{
+ if (!m_controller_settings_dialog)
+ m_controller_settings_dialog = new ControllerSettingsDialog(this);
+
+ return m_controller_settings_dialog;
+}
+
+void MainWindow::doControllerSettings(ControllerSettingsDialog::Category category)
+{
+ ControllerSettingsDialog* dlg = getControllerSettingsDialog();
+ if (!dlg->isVisible())
+ {
+ dlg->setModal(false);
+ dlg->show();
+ }
+
+ if (category != ControllerSettingsDialog::Category::Count)
+ dlg->setCategory(category);
+}
+
+void MainWindow::startGameListEntry(const GameList::Entry* entry, std::optional save_slot,
+ std::optional fast_boot)
+{
+ std::shared_ptr params = std::make_shared();
+ params->fast_boot = fast_boot;
+
+ GameList::FillBootParametersForEntry(params.get(), entry);
+
+ if (save_slot.has_value() && !entry->serial.empty())
+ {
+ std::string state_filename = VMManager::GetSaveStateFileName(entry->serial.c_str(), entry->crc, save_slot.value());
+ if (!FileSystem::FileExists(state_filename.c_str()))
+ {
+ QMessageBox::critical(this, tr("Error"), tr("This save state does not exist."));
+ return;
+ }
+
+ params->save_state = std::move(state_filename);
+ }
+
+ g_emu_thread->startVM(std::move(params));
+}
+
+void MainWindow::setGameListEntryCoverImage(const GameList::Entry* entry)
+{
+ const QString filename(QFileDialog::getOpenFileName(this, tr("Select Cover Image"), QString(),
+ tr("All Cover Image Types (*.jpg *.jpeg *.png)")));
+ if (filename.isEmpty())
+ return;
+
+ if (!GameList::GetCoverImagePathForEntry(entry).empty())
+ {
+ if (QMessageBox::question(this, tr("Cover Already Exists"),
+ tr("A cover image for this game already exists, do you wish to replace it?"),
+ QMessageBox::Yes, QMessageBox::No) != QMessageBox::Yes)
+ {
+ return;
+ }
+ }
+
+ const QString new_filename(QString::fromStdString(GameList::GetNewCoverImagePathForEntry(entry, filename.toUtf8().constData())));
+ if (new_filename.isEmpty())
+ return;
+
+ if (QFile::exists(new_filename) && !QFile::remove(new_filename))
+ {
+ QMessageBox::critical(this, tr("Copy Error"), tr("Failed to remove existing cover '%1'").arg(new_filename));
+ return;
+ }
+
+ if (!QFile::copy(filename, new_filename))
+ {
+ QMessageBox::critical(this, tr("Copy Error"), tr("Failed to copy '%1' to '%2'").arg(filename).arg(new_filename));
+ return;
+ }
+
+ m_game_list_widget->refreshGridCovers();
+}
+
+void MainWindow::loadSaveStateSlot(s32 slot)
+{
+ if (m_vm_valid)
+ {
+ // easy when we're running
+ g_emu_thread->loadStateFromSlot(slot);
+ return;
+ }
+ else
+ {
+ // we're not currently running, therefore we must've right clicked in the game list
+ const GameList::Entry* entry = m_game_list_widget->getSelectedEntry();
+ if (!entry)
+ return;
+
+ startGameListEntry(entry, slot, std::nullopt);
+ }
+}
+
+void MainWindow::loadSaveStateFile(const QString& filename, const QString& state_filename)
+{
+ if (m_vm_valid)
+ {
+ g_emu_thread->loadState(filename);
+ }
+ else
+ {
+ std::shared_ptr params = std::make_shared();
+ VMManager::SetBootParametersForPath(filename.toStdString(), params.get());
+ params->save_state = state_filename.toStdString();
+ g_emu_thread->startVM(std::move(params));
+ }
+}
+
+static QString formatTimestampForSaveStateMenu(time_t timestamp)
+{
+ const QDateTime qtime(QDateTime::fromSecsSinceEpoch(static_cast(timestamp)));
+ return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
+}
+
+void MainWindow::populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc)
+{
+ if (serial.isEmpty())
+ return;
+
+ const bool is_right_click_menu = (menu != m_ui.menuLoadState);
+
+ QAction* action = menu->addAction(is_right_click_menu ? tr("Load State File...") : tr("Load From File..."));
+ connect(action, &QAction::triggered, [this, filename]() {
+ const QString path(
+ QFileDialog::getOpenFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s)")));
+ if (path.isEmpty())
+ return;
+
+ loadSaveStateFile(filename, path);
+ });
+
+ // don't include undo in the right click menu
+ if (!is_right_click_menu)
+ {
+ QAction* load_undo_state = menu->addAction(tr("Undo Load State"));
+ load_undo_state->setEnabled(false); // CanUndoLoadState()
+ // connect(load_undo_state, &QAction::triggered, this, &QtHostInterface::undoLoadState);
+ menu->addSeparator();
+ }
+
+ const QByteArray game_serial_utf8(serial.toUtf8());
+ std::string state_filename;
+ FILESYSTEM_STAT_DATA sd;
+ if (is_right_click_menu)
+ {
+ state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, -1);
+ if (FileSystem::StatFile(state_filename.c_str(), &sd))
+ {
+ action = menu->addAction(tr("Resume (%2)").arg(formatTimestampForSaveStateMenu(sd.ModificationTime)));
+ connect(action, &QAction::triggered, [this]() { loadSaveStateSlot(-1); });
+
+ // Make bold to indicate it's the default choice when double-clicking
+ if (VMManager::ShouldSaveResumeState())
+ QtUtils::MarkActionAsDefault(action);
+ }
+ }
+
+ for (s32 i = 1; i <= NUM_SAVE_STATE_SLOTS; i++)
+ {
+ FILESYSTEM_STAT_DATA sd;
+ state_filename = VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i);
+ if (!FileSystem::StatFile(state_filename.c_str(), &sd))
+ continue;
+
+ action = menu->addAction(tr("Save Slot %1 (%2)").arg(i).arg(formatTimestampForSaveStateMenu(sd.ModificationTime)));
+ connect(action, &QAction::triggered, [this, i]() { loadSaveStateSlot(i); });
+ }
+}
+
+void MainWindow::populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc)
+{
+ if (serial.isEmpty())
+ return;
+
+ connect(menu->addAction(tr("Save To File...")), &QAction::triggered, [this]() {
+ const QString path(
+ QFileDialog::getSaveFileName(this, tr("Select Save State File"), QString(), tr("Save States (*.p2s)")));
+ if (path.isEmpty())
+ return;
+
+ g_emu_thread->saveState(path);
+ });
+
+ menu->addSeparator();
+
+ const QByteArray game_serial_utf8(serial.toUtf8());
+ for (s32 i = 1; i <= NUM_SAVE_STATE_SLOTS; i++)
+ {
+ std::string filename(VMManager::GetSaveStateFileName(game_serial_utf8.constData(), crc, i));
+ FILESYSTEM_STAT_DATA sd;
+ QString timestamp;
+ if (FileSystem::StatFile(filename.c_str(), &sd))
+ timestamp = formatTimestampForSaveStateMenu(sd.ModificationTime);
+ else
+ timestamp = tr("Empty");
+
+ QString title(tr("Save Slot %1 (%2)").arg(i).arg(timestamp));
+ connect(menu->addAction(title), &QAction::triggered, [this, i]() { g_emu_thread->saveStateToSlot(i); });
+ }
+}
+
+void MainWindow::updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc)
+{
+ const bool load_enabled = !serial.isEmpty();
+ const bool save_enabled = !serial.isEmpty() && m_vm_valid;
+ m_ui.menuLoadState->clear();
+ m_ui.menuLoadState->setEnabled(load_enabled);
+ m_ui.actionLoadState->setEnabled(load_enabled);
+ m_ui.menuSaveState->clear();
+ m_ui.menuSaveState->setEnabled(save_enabled);
+ m_ui.actionSaveState->setEnabled(save_enabled);
+ m_save_states_invalidated = false;
+ if (load_enabled)
+ populateLoadStateMenu(m_ui.menuLoadState, filename, serial, crc);
+ if (save_enabled)
+ populateSaveStateMenu(m_ui.menuSaveState, serial, crc);
+}
diff --git a/pcsx2-qt/MainWindow.h b/pcsx2-qt/MainWindow.h
new file mode 100644
index 000000000..b6bd5db76
--- /dev/null
+++ b/pcsx2-qt/MainWindow.h
@@ -0,0 +1,177 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+
+#include
+#include
+
+#include "Settings/ControllerSettingsDialog.h"
+#include "Settings/SettingsDialog.h"
+#include "ui_MainWindow.h"
+
+class QProgressBar;
+
+class DisplayWidget;
+class DisplayContainer;
+class GameListWidget;
+class ControllerSettingsDialog;
+
+class EmuThread;
+
+namespace GameList
+{
+ struct Entry;
+}
+
+class MainWindow final : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ static const char* DEFAULT_THEME_NAME;
+
+public:
+ explicit MainWindow(const QString& unthemed_style_name);
+ ~MainWindow();
+
+ void initialize();
+ void connectVMThreadSignals(EmuThread* thread);
+
+public Q_SLOTS:
+ void refreshGameList(bool invalidate_cache);
+ void invalidateSaveStateCache();
+ void reportError(const QString& title, const QString& message);
+
+private Q_SLOTS:
+ DisplayWidget* createDisplay(bool fullscreen, bool render_to_main);
+ DisplayWidget* updateDisplay(bool fullscreen, bool render_to_main);
+ void displayResizeRequested(qint32 width, qint32 height);
+ void destroyDisplay();
+ void focusDisplayWidget();
+
+ void onGameListRefreshComplete();
+ void onGameListRefreshProgress(const QString& status, int current, int total);
+ void onGameListSelectionChanged();
+ void onGameListEntryActivated();
+ void onGameListEntryContextMenuRequested(const QPoint& point);
+
+ void onStartFileActionTriggered();
+ void onStartBIOSActionTriggered();
+ void onChangeDiscFromFileActionTriggered();
+ void onChangeDiscFromGameListActionTriggered();
+ void onChangeDiscFromDeviceActionTriggered();
+ void onChangeDiscMenuAboutToShow();
+ void onChangeDiscMenuAboutToHide();
+ void onLoadStateMenuAboutToShow();
+ void onSaveStateMenuAboutToShow();
+ void onViewToolbarActionToggled(bool checked);
+ void onViewLockToolbarActionToggled(bool checked);
+ void onViewStatusBarActionToggled(bool checked);
+ void onViewGameListActionTriggered();
+ void onViewGameGridActionTriggered();
+ void onViewSystemDisplayTriggered();
+ void onViewGamePropertiesActionTriggered();
+ void onGitHubRepositoryActionTriggered();
+ void onSupportForumsActionTriggered();
+ void onDiscordServerActionTriggered();
+ void onAboutActionTriggered();
+ void onCheckForUpdatesActionTriggered();
+ void onToolsOpenDataDirectoryTriggered();
+ void onThemeChanged();
+ void onThemeChangedFromSettings();
+
+ void onVMStarting();
+ void onVMStarted();
+ void onVMPaused();
+ void onVMResumed();
+ void onVMStopped();
+
+ void onGameChanged(const QString& path, const QString& serial, const QString& name, quint32 crc);
+
+ void recreate();
+
+protected:
+ void closeEvent(QCloseEvent* event);
+
+private:
+ enum : s32
+ {
+ NUM_SAVE_STATE_SLOTS = 10,
+ };
+
+ void setupAdditionalUi();
+ void connectSignals();
+ void setStyleFromSettings();
+ void setIconThemeFromSettings();
+
+ void saveStateToConfig();
+ void restoreStateFromConfig();
+
+ void updateEmulationActions(bool starting, bool running);
+ void updateWindowTitle();
+ void setProgressBar(int current, int total);
+ void clearProgressBar();
+
+ bool isShowingGameList() const;
+ void switchToGameListView();
+ void switchToEmulationView();
+
+ QWidget* getDisplayContainer() const;
+ void saveDisplayWindowGeometryToConfig();
+ void restoreDisplayWindowGeometryFromConfig();
+ void destroyDisplayWidget();
+ void setDisplayFullscreen(const std::string& fullscreen_mode);
+
+ SettingsDialog* getSettingsDialog();
+ void doSettings(SettingsDialog::Category category = SettingsDialog::Category::Count);
+
+ ControllerSettingsDialog* getControllerSettingsDialog();
+ void doControllerSettings(ControllerSettingsDialog::Category category = ControllerSettingsDialog::Category::Count);
+
+ void startGameListEntry(const GameList::Entry* entry, std::optional save_slot = std::nullopt,
+ std::optional fast_boot = std::nullopt);
+ void setGameListEntryCoverImage(const GameList::Entry* entry);
+
+ void loadSaveStateSlot(s32 slot);
+ void loadSaveStateFile(const QString& filename, const QString& state_filename);
+ void populateLoadStateMenu(QMenu* menu, const QString& filename, const QString& serial, quint32 crc);
+ void populateSaveStateMenu(QMenu* menu, const QString& serial, quint32 crc);
+ void updateSaveStateMenus(const QString& filename, const QString& serial, quint32 crc);
+
+ Ui::MainWindow m_ui;
+
+ QString m_unthemed_style_name;
+
+ GameListWidget* m_game_list_widget = nullptr;
+ DisplayWidget* m_display_widget = nullptr;
+ DisplayContainer* m_display_container = nullptr;
+
+ SettingsDialog* m_settings_dialog = nullptr;
+ ControllerSettingsDialog* m_controller_settings_dialog = nullptr;
+
+ QProgressBar* m_status_progress_widget = nullptr;
+
+ QString m_current_disc_path;
+ QString m_current_game_serial;
+ QString m_current_game_name;
+ quint32 m_current_game_crc;
+ bool m_vm_valid = false;
+ bool m_vm_paused = false;
+ bool m_save_states_invalidated = false;
+ bool m_was_focused_on_container_switch = false;
+};
+
+extern MainWindow* g_main_window;
diff --git a/pcsx2-qt/MainWindow.ui b/pcsx2-qt/MainWindow.ui
new file mode 100644
index 000000000..1d9240603
--- /dev/null
+++ b/pcsx2-qt/MainWindow.ui
@@ -0,0 +1,648 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 800
+ 700
+
+
+
+ true
+
+
+ PCSX2
+
+
+
+ :/icons/AppIcon.png:/icons/AppIcon.png
+
+
+
+ 0
+
+
+
+
+
+
+
+ toolBar
+
+
+
+ 32
+ 32
+
+
+
+ Qt::ToolButtonTextUnderIcon
+
+
+ TopToolBarArea
+
+
+ false
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Start &File...
+
+
+
+
+
+
+
+ Start &Disc...
+
+
+
+
+
+
+
+ Start &BIOS
+
+
+
+
+
+
+
+ &Scan For New Games
+
+
+
+
+
+
+
+ &Rescan All Games
+
+
+
+
+
+
+
+ Shut &Down
+
+
+
+
+
+
+
+ &Reset
+
+
+
+
+
+
+
+ true
+
+
+ &Pause
+
+
+
+
+
+
+
+ &Load State
+
+
+
+
+
+
+
+ &Save State
+
+
+
+
+
+
+
+ E&xit
+
+
+
+
+
+
+
+ &BIOS
+
+
+
+
+
+
+
+ System
+
+
+
+
+
+
+
+ Emulation
+
+
+
+
+
+
+
+ &Controllers
+
+
+
+
+
+
+
+ &Hotkeys
+
+
+
+
+
+
+
+ &Graphics
+
+
+
+
+
+
+
+ &Post-Processing Settings...
+
+
+
+
+ Fullscreen
+
+
+
+
+
+
+
+ Resolution Scale
+
+
+
+
+ &GitHub Repository...
+
+
+
+
+ Support &Forums...
+
+
+
+
+ &Discord Server...
+
+
+
+
+
+
+
+ Check for &Updates...
+
+
+
+
+
+ :/icons/QT.png:/icons/QT.png
+
+
+ About &Qt...
+
+
+
+
+ &About PCSX2...
+
+
+
+
+ Change Disc...
+
+
+
+
+
+
+
+ Cheats...
+
+
+
+
+
+
+
+ &Audio
+
+
+
+
+
+
+
+ Game List
+
+
+
+
+
+
+
+ Interface
+
+
+
+
+
+
+
+ Add Game Directory...
+
+
+
+
+
+
+
+ &Settings...
+
+
+
+
+
+
+
+ From File...
+
+
+
+
+ From Device...
+
+
+
+
+ From Game List...
+
+
+
+
+ Remove Disc
+
+
+
+
+ Global State
+
+
+
+
+ &Screenshot
+
+
+
+
+
+
+
+ &Memory Cards
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ &Toolbar
+
+
+
+
+ true
+
+
+ false
+
+
+ Lock Toolbar
+
+
+
+
+ true
+
+
+ true
+
+
+ &Status Bar
+
+
+
+
+ Game &List
+
+
+
+
+
+
+
+ false
+
+
+ System &Display
+
+
+
+
+
+
+
+ false
+
+
+ Game &Properties
+
+
+
+
+
+
+
+ Game &Grid
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+ Show Titles (Grid View)
+
+
+
+
+ Zoom &In (Grid View)
+
+
+ Ctrl++
+
+
+
+
+ Zoom &Out (Grid View)
+
+
+ Ctrl+-
+
+
+
+
+ Refresh &Covers (Grid View)
+
+
+
+
+ Open Memory Card Directory...
+
+
+
+
+ Open Data Directory...
+
+
+
+
+ Toggle Software Rendering
+
+
+
+
+ Reload Cheats/Patches
+
+
+
+
+
+
+
+
diff --git a/pcsx2-qt/PrecompiledHeader.cpp b/pcsx2-qt/PrecompiledHeader.cpp
new file mode 100644
index 000000000..a11c9e9a2
--- /dev/null
+++ b/pcsx2-qt/PrecompiledHeader.cpp
@@ -0,0 +1,16 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
diff --git a/pcsx2-qt/PrecompiledHeader.h b/pcsx2-qt/PrecompiledHeader.h
new file mode 100644
index 000000000..518d3f9cc
--- /dev/null
+++ b/pcsx2-qt/PrecompiledHeader.h
@@ -0,0 +1,21 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "pcsx2/PrecompiledHeader.h"
+
+// Needed because of moc shenanigans with pch.
+#include
+
+#include
\ No newline at end of file
diff --git a/pcsx2-qt/QtHost.cpp b/pcsx2-qt/QtHost.cpp
new file mode 100644
index 000000000..e44dafb43
--- /dev/null
+++ b/pcsx2-qt/QtHost.cpp
@@ -0,0 +1,506 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "common/Assertions.h"
+#include "common/Console.h"
+#include "common/FileSystem.h"
+#include "common/SettingsWrapper.h"
+#include "common/StringUtil.h"
+
+#include "pcsx2/Frontend/GameList.h"
+#include "pcsx2/Frontend/INISettingsInterface.h"
+#include "pcsx2/HostSettings.h"
+#include "pcsx2/PAD/Host/PAD.h"
+
+#include
+#include
+
+#include "EmuThread.h"
+#include "GameList/GameListWidget.h"
+#include "MainWindow.h"
+#include "QtHost.h"
+
+#include "pcsx2/DebugTools/Debug.h"
+
+static constexpr u32 SETTINGS_VERSION = 1;
+static constexpr u32 SETTINGS_SAVE_DELAY = 1000;
+
+//////////////////////////////////////////////////////////////////////////
+// Local function declarations
+//////////////////////////////////////////////////////////////////////////
+static void InitializeWxRubbish();
+static bool InitializeConfig();
+static void SetDefaultConfig();
+static void QueueSettingsSave();
+static void SaveSettings();
+
+//////////////////////////////////////////////////////////////////////////
+// Local variable declarations
+//////////////////////////////////////////////////////////////////////////
+static std::unique_ptr s_settings_save_timer;
+static std::unique_ptr s_base_settings_interface;
+
+//////////////////////////////////////////////////////////////////////////
+// Initialization/Shutdown
+//////////////////////////////////////////////////////////////////////////
+
+bool QtHost::Initialize()
+{
+ qRegisterMetaType>();
+ qRegisterMetaType();
+ qRegisterMetaType();
+ qRegisterMetaType();
+
+ InitializeWxRubbish();
+ if (!InitializeConfig())
+ {
+ Console.WriteLn("Failed to initialize config.");
+ return false;
+ }
+
+ return true;
+}
+
+void QtHost::Shutdown() {}
+
+static bool SetCriticalFolders()
+{
+ std::string program_path(FileSystem::GetProgramPath());
+ EmuFolders::AppRoot = wxDirName(wxFileName(StringUtil::UTF8StringToWxString(program_path)));
+ EmuFolders::DataRoot = EmuFolders::AppRoot;
+#ifndef _WIN32
+ const char* homedir = getenv("HOME");
+ if (homedir)
+ EmuFolders::DataRoot = Path::Combine(wxString::FromUTF8(homedir), wxString(L"PCSX2"));
+#endif
+
+ EmuFolders::Settings = EmuFolders::DataRoot.Combine(wxDirName(L"inis"));
+ EmuFolders::Resources = EmuFolders::AppRoot.Combine(wxDirName(L"resources"));
+
+ // the resources directory should exist, bail out if not
+ if (!EmuFolders::Resources.Exists())
+ {
+ QMessageBox::critical(nullptr, QStringLiteral("Error"),
+ QStringLiteral("Resources directory is missing, your installation is incomplete."));
+ return false;
+ }
+
+ return true;
+}
+
+void QtHost::UpdateFolders()
+{
+ // TODO: This should happen with the VM thread paused.
+ auto lock = Host::GetSettingsLock();
+ EmuFolders::LoadConfig(*s_base_settings_interface.get());
+ EmuFolders::EnsureFoldersExist();
+}
+
+bool InitializeConfig()
+{
+ if (!SetCriticalFolders())
+ return false;
+
+ const std::string path(Path::CombineStdString(EmuFolders::Settings, "PCSX2.ini"));
+ s_base_settings_interface = std::make_unique(std::move(path));
+ Host::Internal::SetBaseSettingsLayer(s_base_settings_interface.get());
+
+ uint settings_version;
+ if (!s_base_settings_interface->Load() ||
+ !s_base_settings_interface->GetUIntValue("UI", "SettingsVersion", &settings_version) ||
+ settings_version != SETTINGS_VERSION)
+ {
+ QMessageBox::critical(
+ g_main_window, qApp->translate("QtHost", "Settings Reset"),
+ qApp->translate("QtHost", "Settings do not exist or are the incorrect version, resetting to defaults."));
+ SetDefaultConfig();
+ s_base_settings_interface->Save();
+ }
+
+ // TODO: Handle reset to defaults if load fails.
+ EmuFolders::LoadConfig(*s_base_settings_interface.get());
+ EmuFolders::EnsureFoldersExist();
+ QtHost::UpdateLogging();
+ return true;
+}
+
+void SetDefaultConfig()
+{
+ EmuConfig = Pcsx2Config();
+ EmuFolders::SetDefaults();
+
+ SettingsInterface& si = *s_base_settings_interface.get();
+ si.SetUIntValue("UI", "SettingsVersion", SETTINGS_VERSION);
+
+ {
+ SettingsSaveWrapper wrapper(si);
+ EmuConfig.LoadSave(wrapper);
+ }
+
+ EmuFolders::Save(si);
+ PAD::SetDefaultConfig(si);
+}
+
+std::string QtHost::GetBaseStringSettingValue(const char* section, const char* key, const char* default_value /*= ""*/)
+{
+ auto lock = Host::GetSettingsLock();
+ return s_base_settings_interface->GetStringValue(section, key, default_value);
+}
+
+bool QtHost::GetBaseBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/)
+{
+ auto lock = Host::GetSettingsLock();
+ return s_base_settings_interface->GetBoolValue(section, key, default_value);
+}
+
+int QtHost::GetBaseIntSettingValue(const char* section, const char* key, int default_value /*= 0*/)
+{
+ auto lock = Host::GetSettingsLock();
+ return s_base_settings_interface->GetIntValue(section, key, default_value);
+}
+
+float QtHost::GetBaseFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/)
+{
+ auto lock = Host::GetSettingsLock();
+ return s_base_settings_interface->GetFloatValue(section, key, default_value);
+}
+
+std::vector QtHost::GetBaseStringListSetting(const char* section, const char* key)
+{
+ auto lock = Host::GetSettingsLock();
+ return s_base_settings_interface->GetStringList(section, key);
+}
+
+void QtHost::SetBaseBoolSettingValue(const char* section, const char* key, bool value)
+{
+ auto lock = Host::GetSettingsLock();
+ s_base_settings_interface->SetBoolValue(section, key, value);
+ QueueSettingsSave();
+}
+
+void QtHost::SetBaseIntSettingValue(const char* section, const char* key, int value)
+{
+ auto lock = Host::GetSettingsLock();
+ s_base_settings_interface->SetIntValue(section, key, value);
+ QueueSettingsSave();
+}
+
+void QtHost::SetBaseFloatSettingValue(const char* section, const char* key, float value)
+{
+ auto lock = Host::GetSettingsLock();
+ s_base_settings_interface->SetFloatValue(section, key, value);
+ QueueSettingsSave();
+}
+
+void QtHost::SetBaseStringSettingValue(const char* section, const char* key, const char* value)
+{
+ auto lock = Host::GetSettingsLock();
+ s_base_settings_interface->SetStringValue(section, key, value);
+ QueueSettingsSave();
+}
+
+void QtHost::SetBaseStringListSettingValue(const char* section, const char* key, const std::vector& values)
+{
+ auto lock = Host::GetSettingsLock();
+ s_base_settings_interface->SetStringList(section, key, values);
+ QueueSettingsSave();
+}
+
+bool QtHost::AddBaseValueToStringList(const char* section, const char* key, const char* value)
+{
+ auto lock = Host::GetSettingsLock();
+ if (!s_base_settings_interface->AddToStringList(section, key, value))
+ return false;
+
+ QueueSettingsSave();
+ return true;
+}
+
+bool QtHost::RemoveBaseValueFromStringList(const char* section, const char* key, const char* value)
+{
+ auto lock = Host::GetSettingsLock();
+ if (!s_base_settings_interface->RemoveFromStringList(section, key, value))
+ return false;
+
+ QueueSettingsSave();
+ return true;
+}
+
+void QtHost::RemoveBaseSettingValue(const char* section, const char* key)
+{
+ auto lock = Host::GetSettingsLock();
+ s_base_settings_interface->DeleteValue(section, key);
+ QueueSettingsSave();
+}
+
+void SaveSettings()
+{
+ pxAssertRel(!g_emu_thread->isOnEmuThread(), "Saving should happen on the UI thread.");
+
+ {
+ auto lock = Host::GetSettingsLock();
+ if (!s_base_settings_interface->Save())
+ Console.Error("Failed to save settings.");
+ }
+
+ s_settings_save_timer->deleteLater();
+ s_settings_save_timer.release();
+}
+
+void QueueSettingsSave()
+{
+ if (s_settings_save_timer)
+ return;
+
+ s_settings_save_timer = std::make_unique();
+ s_settings_save_timer->connect(s_settings_save_timer.get(), &QTimer::timeout, SaveSettings);
+ s_settings_save_timer->setSingleShot(true);
+ s_settings_save_timer->start(SETTINGS_SAVE_DELAY);
+}
+
+std::optional> Host::ReadResourceFile(const char* filename)
+{
+ const std::string path(Path::CombineStdString(EmuFolders::Resources, filename));
+ std::optional> ret(FileSystem::ReadBinaryFile(path.c_str()));
+ if (!ret.has_value())
+ Console.Error("Failed to read resource file '%s'", filename);
+ return ret;
+}
+
+std::optional Host::ReadResourceFileToString(const char* filename)
+{
+ const std::string path(Path::CombineStdString(EmuFolders::Resources, filename));
+ std::optional ret(FileSystem::ReadFileToString(path.c_str()));
+ if (!ret.has_value())
+ Console.Error("Failed to read resource file to string '%s'", filename);
+ return ret;
+}
+
+void Host::ReportErrorAsync(const std::string_view& title, const std::string_view& message)
+{
+ if (!title.empty() && !message.empty())
+ {
+ Console.Error("ReportErrorAsync: %*s: %*s",
+ static_cast(title.size()), title.data(),
+ static_cast(message.size()), message.data());
+ }
+ else if (!message.empty())
+ {
+ Console.Error("ReportErrorAsync: %*s",
+ static_cast(message.size()), message.data());
+ }
+
+ QMetaObject::invokeMethod(g_main_window, "reportError", Qt::QueuedConnection,
+ Q_ARG(const QString&, title.empty() ? QString() : QString::fromUtf8(title.data(), title.size())),
+ Q_ARG(const QString&, message.empty() ? QString() : QString::fromUtf8(message.data(), message.size())));
+}
+
+void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name)
+{
+ emit g_emu_thread->onInputDeviceConnected(
+ identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()),
+ device_name.empty() ? QString() : QString::fromUtf8(device_name.data(), device_name.size()));
+}
+
+void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
+{
+ emit g_emu_thread->onInputDeviceDisconnected(
+ identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size()));
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Interface Stuff
+//////////////////////////////////////////////////////////////////////////
+
+const IConsoleWriter* PatchesCon = &Console;
+
+void LoadAllPatchesAndStuff(const Pcsx2Config& cfg)
+{
+ // FIXME
+}
+
+void PatchesVerboseReset()
+{
+ // FIXME
+}
+
+// Replacement for Console so we actually get output to our console window on Windows.
+#ifdef _WIN32
+#include "common/RedtapeWindows.h"
+
+static bool s_debugger_attached = false;
+static bool s_console_handle_set = false;
+static HANDLE s_console_handle = nullptr;
+
+static void __concall ConsoleWinQt_SetTitle(const wxString& title)
+{
+ SetConsoleTitleW(title.wc_str());
+}
+
+static void __concall ConsoleWinQt_DoSetColor(ConsoleColors color)
+{
+ if (!s_console_handle)
+ return;
+
+ static constexpr WORD colors[ConsoleColors_Count] = {
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // default
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // black
+ FOREGROUND_GREEN, // green
+ FOREGROUND_RED, // red
+ FOREGROUND_BLUE, // blue
+ FOREGROUND_RED | FOREGROUND_BLUE, // magenta
+ FOREGROUND_RED | FOREGROUND_GREEN, // orange
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // gray
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY, // cyan
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // yellow
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // white
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // strong black
+ FOREGROUND_RED | FOREGROUND_INTENSITY, // strong red
+ FOREGROUND_GREEN | FOREGROUND_INTENSITY, // strong green
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong blue
+ FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong magenta
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // strong orange
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, // strong gray
+ FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong cyan
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, // strong yellow
+ FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, // strong white
+ };
+
+ SetConsoleTextAttribute(s_console_handle, colors[static_cast(color)]);
+}
+
+static void __concall ConsoleWinQt_Newline()
+{
+ if (!s_console_handle)
+ return;
+
+ if (s_debugger_attached)
+ OutputDebugStringW(L"\n");
+
+ DWORD written;
+ WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr);
+}
+
+static void __concall ConsoleWinQt_DoWrite(const wxString& fmt)
+{
+ if (!s_console_handle)
+ return;
+
+ if (s_debugger_attached)
+ OutputDebugStringW(fmt.wc_str());
+
+ DWORD written;
+ WriteConsoleW(s_console_handle, fmt.wc_str(), static_cast(fmt.size()), &written, nullptr);
+}
+
+static void __concall ConsoleWinQt_DoWriteLn(const wxString& fmt)
+{
+ if (!s_console_handle)
+ return;
+
+ if (s_debugger_attached)
+ {
+ OutputDebugStringW(fmt.wc_str());
+ OutputDebugStringW(L"\n");
+ }
+
+ DWORD written;
+ WriteConsoleW(s_console_handle, fmt.wc_str(), static_cast(fmt.size()), &written, nullptr);
+ WriteConsoleW(s_console_handle, L"\n", 1, &written, nullptr);
+}
+
+static const IConsoleWriter ConsoleWriter_WinQt =
+ {
+ ConsoleWinQt_DoWrite,
+ ConsoleWinQt_DoWriteLn,
+ ConsoleWinQt_DoSetColor,
+
+ ConsoleWinQt_DoWrite,
+ ConsoleWinQt_Newline,
+ ConsoleWinQt_SetTitle,
+};
+#endif
+
+void QtHost::UpdateLogging()
+{
+ // TODO: Make this an actual option.
+ bool console_logging_enabled = false;
+
+#if defined(_DEBUG) || defined(PCSX2_DEVBUILD)
+ console_logging_enabled = true;
+#endif
+
+ DevConWriterEnabled = console_logging_enabled;
+ SysConsole.eeConsole.Enabled = console_logging_enabled;
+ SysConsole.iopConsole.Enabled = console_logging_enabled;
+
+ if (console_logging_enabled)
+ {
+#ifdef _WIN32
+ s_debugger_attached = IsDebuggerPresent();
+ if (!s_console_handle_set)
+ {
+ bool handle_valid = (GetConsoleWindow() != NULL);
+ if (!handle_valid)
+ {
+ AllocConsole();
+ handle_valid = (freopen("CONIN$", "r", stdin) != nullptr);
+ handle_valid = (freopen("CONOUT$", "w", stdout) != nullptr);
+ handle_valid = (freopen("CONOUT$", "w", stderr) != nullptr);
+ }
+
+ if (handle_valid)
+ {
+ s_console_handle = GetStdHandle(STD_OUTPUT_HANDLE);
+ s_console_handle_set = true;
+ }
+ }
+
+ if (!s_console_handle && !s_debugger_attached)
+ Console_SetActiveHandler(ConsoleWriter_Null);
+ else
+ Console_SetActiveHandler(ConsoleWriter_WinQt);
+#else
+ Console_SetActiveHandler(ConsoleWriter_Stdout);
+#endif
+ }
+ else
+ {
+ Console_SetActiveHandler(ConsoleWriter_Null);
+ }
+}
+
+#include
+
+#ifdef _WIN32
+extern "C" HINSTANCE wxGetInstance();
+extern void wxSetInstance(HINSTANCE hInst);
+#endif
+
+void InitializeWxRubbish()
+{
+ wxLog::DoCreateOnDemand();
+ wxLog::GetActiveTarget();
+
+#ifdef _WIN32
+ if (!wxGetInstance())
+ wxSetInstance(::GetModuleHandle(NULL));
+#endif // _WIN32
+
+ wxModule::RegisterModules();
+ wxModule::InitializeModules();
+}
diff --git a/pcsx2-qt/QtHost.h b/pcsx2-qt/QtHost.h
new file mode 100644
index 000000000..91108cde8
--- /dev/null
+++ b/pcsx2-qt/QtHost.h
@@ -0,0 +1,54 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#pragma once
+#include "pcsx2/Host.h"
+#include "pcsx2/HostDisplay.h"
+#include "pcsx2/HostSettings.h"
+#include "pcsx2/Frontend/InputManager.h"
+#include "pcsx2/VMManager.h"
+#include
+
+class SettingsInterface;
+
+class EmuThread;
+
+Q_DECLARE_METATYPE(GSRendererType);
+Q_DECLARE_METATYPE(std::shared_ptr);
+Q_DECLARE_METATYPE(InputBindingKey);
+
+namespace QtHost
+{
+ bool Initialize();
+ void Shutdown();
+
+ void UpdateFolders();
+ void UpdateLogging();
+
+ /// Thread-safe settings access.
+ std::string GetBaseStringSettingValue(const char* section, const char* key, const char* default_value = "");
+ bool GetBaseBoolSettingValue(const char* section, const char* key, bool default_value = false);
+ int GetBaseIntSettingValue(const char* section, const char* key, int default_value = 0);
+ float GetBaseFloatSettingValue(const char* section, const char* key, float default_value = 0.0f);
+ std::vector GetBaseStringListSetting(const char* section, const char* key);
+ void SetBaseBoolSettingValue(const char* section, const char* key, bool value);
+ void SetBaseIntSettingValue(const char* section, const char* key, int value);
+ void SetBaseFloatSettingValue(const char* section, const char* key, float value);
+ void SetBaseStringSettingValue(const char* section, const char* key, const char* value);
+ void SetBaseStringListSettingValue(const char* section, const char* key, const std::vector& values);
+ bool AddBaseValueToStringList(const char* section, const char* key, const char* value);
+ bool RemoveBaseValueFromStringList(const char* section, const char* key, const char* value);
+ void RemoveBaseSettingValue(const char* section, const char* key);
+} // namespace QtHost
diff --git a/pcsx2-qt/QtKeyCodes.cpp b/pcsx2-qt/QtKeyCodes.cpp
new file mode 100644
index 000000000..525652cef
--- /dev/null
+++ b/pcsx2-qt/QtKeyCodes.cpp
@@ -0,0 +1,486 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include "pcsx2/Frontend/InputManager.h"
+
+struct KeyCodeName
+{
+ int code;
+ const char* name;
+};
+
+static constexpr KeyCodeName s_qt_key_names[] = {
+ {Qt::Key_Escape, "Escape"},
+ {Qt::Key_Tab, "Tab"},
+ {Qt::Key_Backtab, "Backtab"},
+ {Qt::Key_Backspace, "Backspace"},
+ {Qt::Key_Return, "Return"},
+ {Qt::Key_Enter, "Enter"},
+ {Qt::Key_Insert, "Insert"},
+ {Qt::Key_Delete, "Delete"},
+ {Qt::Key_Pause, "Pause"},
+ {Qt::Key_Print, "Print"},
+ {Qt::Key_SysReq, "SysReq"},
+ {Qt::Key_Clear, "Clear"},
+ {Qt::Key_Home, "Home"},
+ {Qt::Key_End, "End"},
+ {Qt::Key_Left, "Left"},
+ {Qt::Key_Up, "Up"},
+ {Qt::Key_Right, "Right"},
+ {Qt::Key_Down, "Down"},
+ {Qt::Key_PageUp, "PageUp"},
+ {Qt::Key_PageDown, "PageDown"},
+ {Qt::Key_Shift, "Shift"},
+ {Qt::Key_Control, "Control"},
+ {Qt::Key_Meta, "Meta"},
+ {Qt::Key_Alt, "Alt"},
+ {Qt::Key_CapsLock, "CapsLock"},
+ {Qt::Key_NumLock, "NumLock"},
+ {Qt::Key_ScrollLock, "ScrollLock"},
+ {Qt::Key_F1, "F1"},
+ {Qt::Key_F2, "F2"},
+ {Qt::Key_F3, "F3"},
+ {Qt::Key_F4, "F4"},
+ {Qt::Key_F5, "F5"},
+ {Qt::Key_F6, "F6"},
+ {Qt::Key_F7, "F7"},
+ {Qt::Key_F8, "F8"},
+ {Qt::Key_F9, "F9"},
+ {Qt::Key_F10, "F10"},
+ {Qt::Key_F11, "F11"},
+ {Qt::Key_F12, "F12"},
+ {Qt::Key_F13, "F13"},
+ {Qt::Key_F14, "F14"},
+ {Qt::Key_F15, "F15"},
+ {Qt::Key_F16, "F16"},
+ {Qt::Key_F17, "F17"},
+ {Qt::Key_F18, "F18"},
+ {Qt::Key_F19, "F19"},
+ {Qt::Key_F20, "F20"},
+ {Qt::Key_F21, "F21"},
+ {Qt::Key_F22, "F22"},
+ {Qt::Key_F23, "F23"},
+ {Qt::Key_F24, "F24"},
+ {Qt::Key_F25, "F25"},
+ {Qt::Key_F26, "F26"},
+ {Qt::Key_F27, "F27"},
+ {Qt::Key_F28, "F28"},
+ {Qt::Key_F29, "F29"},
+ {Qt::Key_F30, "F30"},
+ {Qt::Key_F31, "F31"},
+ {Qt::Key_F32, "F32"},
+ {Qt::Key_F33, "F33"},
+ {Qt::Key_F34, "F34"},
+ {Qt::Key_F35, "F35"},
+ {Qt::Key_Super_L, "Super_L"},
+ {Qt::Key_Super_R, "Super_R"},
+ {Qt::Key_Menu, "Menu"},
+ {Qt::Key_Hyper_L, "Hyper_L"},
+ {Qt::Key_Hyper_R, "Hyper_R"},
+ {Qt::Key_Help, "Help"},
+ {Qt::Key_Direction_L, "Direction_L"},
+ {Qt::Key_Direction_R, "Direction_R"},
+ {Qt::Key_Space, "Space"},
+ {Qt::Key_Any, "Any"},
+ {Qt::Key_Exclam, "Exclam"},
+ {Qt::Key_QuoteDbl, "QuoteDbl"},
+ {Qt::Key_NumberSign, "NumberSign"},
+ {Qt::Key_Dollar, "Dollar"},
+ {Qt::Key_Percent, "Percent"},
+ {Qt::Key_Ampersand, "Ampersand"},
+ {Qt::Key_Apostrophe, "Apostrophe"},
+ {Qt::Key_ParenLeft, "ParenLeft"},
+ {Qt::Key_ParenRight, "ParenRight"},
+ {Qt::Key_Asterisk, "Asterisk"},
+ {Qt::Key_Plus, "Plus"},
+ {Qt::Key_Comma, "Comma"},
+ {Qt::Key_Minus, "Minus"},
+ {Qt::Key_Period, "Period"},
+ {Qt::Key_Slash, "Slash"},
+ {Qt::Key_0, "0"},
+ {Qt::Key_1, "1"},
+ {Qt::Key_2, "2"},
+ {Qt::Key_3, "3"},
+ {Qt::Key_4, "4"},
+ {Qt::Key_5, "5"},
+ {Qt::Key_6, "6"},
+ {Qt::Key_7, "7"},
+ {Qt::Key_8, "8"},
+ {Qt::Key_9, "9"},
+ {Qt::Key_Colon, "Colon"},
+ {Qt::Key_Semicolon, "Semicolon"},
+ {Qt::Key_Less, "Less"},
+ {Qt::Key_Equal, "Equal"},
+ {Qt::Key_Greater, "Greater"},
+ {Qt::Key_Question, "Question"},
+ {Qt::Key_At, "At"},
+ {Qt::Key_A, "A"},
+ {Qt::Key_B, "B"},
+ {Qt::Key_C, "C"},
+ {Qt::Key_D, "D"},
+ {Qt::Key_E, "E"},
+ {Qt::Key_F, "F"},
+ {Qt::Key_G, "G"},
+ {Qt::Key_H, "H"},
+ {Qt::Key_I, "I"},
+ {Qt::Key_J, "J"},
+ {Qt::Key_K, "K"},
+ {Qt::Key_L, "L"},
+ {Qt::Key_M, "M"},
+ {Qt::Key_N, "N"},
+ {Qt::Key_O, "O"},
+ {Qt::Key_P, "P"},
+ {Qt::Key_Q, "Q"},
+ {Qt::Key_R, "R"},
+ {Qt::Key_S, "S"},
+ {Qt::Key_T, "T"},
+ {Qt::Key_U, "U"},
+ {Qt::Key_V, "V"},
+ {Qt::Key_W, "W"},
+ {Qt::Key_X, "X"},
+ {Qt::Key_Y, "Y"},
+ {Qt::Key_Z, "Z"},
+ {Qt::Key_BracketLeft, "BracketLeft"},
+ {Qt::Key_Backslash, "Backslash"},
+ {Qt::Key_BracketRight, "BracketRight"},
+ {Qt::Key_AsciiCircum, "AsciiCircum"},
+ {Qt::Key_Underscore, "Underscore"},
+ {Qt::Key_QuoteLeft, "QuoteLeft"},
+ {Qt::Key_BraceLeft, "BraceLeft"},
+ {Qt::Key_Bar, "Bar"},
+ {Qt::Key_BraceRight, "BraceRight"},
+ {Qt::Key_AsciiTilde, "AsciiTilde"},
+ {Qt::Key_nobreakspace, "nobreakspace"},
+ {Qt::Key_exclamdown, "exclamdown"},
+ {Qt::Key_cent, "cent"},
+ {Qt::Key_sterling, "sterling"},
+ {Qt::Key_currency, "currency"},
+ {Qt::Key_yen, "yen"},
+ {Qt::Key_brokenbar, "brokenbar"},
+ {Qt::Key_section, "section"},
+ {Qt::Key_diaeresis, "diaeresis"},
+ {Qt::Key_copyright, "copyright"},
+ {Qt::Key_ordfeminine, "ordfeminine"},
+ {Qt::Key_guillemotleft, "guillemotleft"},
+ {Qt::Key_notsign, "notsign"},
+ {Qt::Key_hyphen, "hyphen"},
+ {Qt::Key_registered, "registered"},
+ {Qt::Key_macron, "macron"},
+ {Qt::Key_degree, "degree"},
+ {Qt::Key_plusminus, "plusminus"},
+ {Qt::Key_twosuperior, "twosuperior"},
+ {Qt::Key_threesuperior, "threesuperior"},
+ {Qt::Key_acute, "acute"},
+ {Qt::Key_mu, "mu"},
+ {Qt::Key_paragraph, "paragraph"},
+ {Qt::Key_periodcentered, "periodcentered"},
+ {Qt::Key_cedilla, "cedilla"},
+ {Qt::Key_onesuperior, "onesuperior"},
+ {Qt::Key_masculine, "masculine"},
+ {Qt::Key_guillemotright, "guillemotright"},
+ {Qt::Key_onequarter, "onequarter"},
+ {Qt::Key_onehalf, "onehalf"},
+ {Qt::Key_threequarters, "threequarters"},
+ {Qt::Key_questiondown, "questiondown"},
+ {Qt::Key_Agrave, "Agrave"},
+ {Qt::Key_Aacute, "Aacute"},
+ {Qt::Key_Acircumflex, "Acircumflex"},
+ {Qt::Key_Atilde, "Atilde"},
+ {Qt::Key_Adiaeresis, "Adiaeresis"},
+ {Qt::Key_Aring, "Aring"},
+ {Qt::Key_AE, "AE"},
+ {Qt::Key_Ccedilla, "Ccedilla"},
+ {Qt::Key_Egrave, "Egrave"},
+ {Qt::Key_Eacute, "Eacute"},
+ {Qt::Key_Ecircumflex, "Ecircumflex"},
+ {Qt::Key_Ediaeresis, "Ediaeresis"},
+ {Qt::Key_Igrave, "Igrave"},
+ {Qt::Key_Iacute, "Iacute"},
+ {Qt::Key_Icircumflex, "Icircumflex"},
+ {Qt::Key_Idiaeresis, "Idiaeresis"},
+ {Qt::Key_ETH, "ETH"},
+ {Qt::Key_Ntilde, "Ntilde"},
+ {Qt::Key_Ograve, "Ograve"},
+ {Qt::Key_Oacute, "Oacute"},
+ {Qt::Key_Ocircumflex, "Ocircumflex"},
+ {Qt::Key_Otilde, "Otilde"},
+ {Qt::Key_Odiaeresis, "Odiaeresis"},
+ {Qt::Key_multiply, "multiply"},
+ {Qt::Key_Ooblique, "Ooblique"},
+ {Qt::Key_Ugrave, "Ugrave"},
+ {Qt::Key_Uacute, "Uacute"},
+ {Qt::Key_Ucircumflex, "Ucircumflex"},
+ {Qt::Key_Udiaeresis, "Udiaeresis"},
+ {Qt::Key_Yacute, "Yacute"},
+ {Qt::Key_THORN, "THORN"},
+ {Qt::Key_ssharp, "ssharp"},
+ {Qt::Key_division, "division"},
+ {Qt::Key_ydiaeresis, "ydiaeresis"},
+ {Qt::Key_AltGr, "AltGr"},
+ {Qt::Key_Multi_key, "Multi_key"},
+ {Qt::Key_Codeinput, "Codeinput"},
+ {Qt::Key_SingleCandidate, "SingleCandidate"},
+ {Qt::Key_MultipleCandidate, "MultipleCandidate"},
+ {Qt::Key_PreviousCandidate, "PreviousCandidate"},
+ {Qt::Key_Mode_switch, "Mode_switch"},
+ {Qt::Key_Kanji, "Kanji"},
+ {Qt::Key_Muhenkan, "Muhenkan"},
+ {Qt::Key_Henkan, "Henkan"},
+ {Qt::Key_Romaji, "Romaji"},
+ {Qt::Key_Hiragana, "Hiragana"},
+ {Qt::Key_Katakana, "Katakana"},
+ {Qt::Key_Hiragana_Katakana, "Hiragana_Katakana"},
+ {Qt::Key_Zenkaku, "Zenkaku"},
+ {Qt::Key_Hankaku, "Hankaku"},
+ {Qt::Key_Zenkaku_Hankaku, "Zenkaku_Hankaku"},
+ {Qt::Key_Touroku, "Touroku"},
+ {Qt::Key_Massyo, "Massyo"},
+ {Qt::Key_Kana_Lock, "Kana_Lock"},
+ {Qt::Key_Kana_Shift, "Kana_Shift"},
+ {Qt::Key_Eisu_Shift, "Eisu_Shift"},
+ {Qt::Key_Eisu_toggle, "Eisu_toggle"},
+ {Qt::Key_Hangul, "Hangul"},
+ {Qt::Key_Hangul_Start, "Hangul_Start"},
+ {Qt::Key_Hangul_End, "Hangul_End"},
+ {Qt::Key_Hangul_Hanja, "Hangul_Hanja"},
+ {Qt::Key_Hangul_Jamo, "Hangul_Jamo"},
+ {Qt::Key_Hangul_Romaja, "Hangul_Romaja"},
+ {Qt::Key_Hangul_Jeonja, "Hangul_Jeonja"},
+ {Qt::Key_Hangul_Banja, "Hangul_Banja"},
+ {Qt::Key_Hangul_PreHanja, "Hangul_PreHanja"},
+ {Qt::Key_Hangul_PostHanja, "Hangul_PostHanja"},
+ {Qt::Key_Hangul_Special, "Hangul_Special"},
+ {Qt::Key_Dead_Grave, "Dead_Grave"},
+ {Qt::Key_Dead_Acute, "Dead_Acute"},
+ {Qt::Key_Dead_Circumflex, "Dead_Circumflex"},
+ {Qt::Key_Dead_Tilde, "Dead_Tilde"},
+ {Qt::Key_Dead_Macron, "Dead_Macron"},
+ {Qt::Key_Dead_Breve, "Dead_Breve"},
+ {Qt::Key_Dead_Abovedot, "Dead_Abovedot"},
+ {Qt::Key_Dead_Diaeresis, "Dead_Diaeresis"},
+ {Qt::Key_Dead_Abovering, "Dead_Abovering"},
+ {Qt::Key_Dead_Doubleacute, "Dead_Doubleacute"},
+ {Qt::Key_Dead_Caron, "Dead_Caron"},
+ {Qt::Key_Dead_Cedilla, "Dead_Cedilla"},
+ {Qt::Key_Dead_Ogonek, "Dead_Ogonek"},
+ {Qt::Key_Dead_Iota, "Dead_Iota"},
+ {Qt::Key_Dead_Voiced_Sound, "Dead_Voiced_Sound"},
+ {Qt::Key_Dead_Semivoiced_Sound, "Dead_Semivoiced_Sound"},
+ {Qt::Key_Dead_Belowdot, "Dead_Belowdot"},
+ {Qt::Key_Dead_Hook, "Dead_Hook"},
+ {Qt::Key_Dead_Horn, "Dead_Horn"},
+ {Qt::Key_Back, "Back"},
+ {Qt::Key_Forward, "Forward"},
+ {Qt::Key_Stop, "Stop"},
+ {Qt::Key_Refresh, "Refresh"},
+ {Qt::Key_VolumeDown, "VolumeDown"},
+ {Qt::Key_VolumeMute, "VolumeMute"},
+ {Qt::Key_VolumeUp, "VolumeUp"},
+ {Qt::Key_BassBoost, "BassBoost"},
+ {Qt::Key_BassUp, "BassUp"},
+ {Qt::Key_BassDown, "BassDown"},
+ {Qt::Key_TrebleUp, "TrebleUp"},
+ {Qt::Key_TrebleDown, "TrebleDown"},
+ {Qt::Key_MediaPlay, "MediaPlay"},
+ {Qt::Key_MediaStop, "MediaStop"},
+ {Qt::Key_MediaPrevious, "MediaPrevious"},
+ {Qt::Key_MediaNext, "MediaNext"},
+ {Qt::Key_MediaRecord, "MediaRecord"},
+ {Qt::Key_MediaPause, "MediaPause"},
+ {Qt::Key_MediaTogglePlayPause, "MediaTogglePlayPause"},
+ {Qt::Key_HomePage, "HomePage"},
+ {Qt::Key_Favorites, "Favorites"},
+ {Qt::Key_Search, "Search"},
+ {Qt::Key_Standby, "Standby"},
+ {Qt::Key_OpenUrl, "OpenUrl"},
+ {Qt::Key_LaunchMail, "LaunchMail"},
+ {Qt::Key_LaunchMedia, "LaunchMedia"},
+ {Qt::Key_Launch0, "Launch0"},
+ {Qt::Key_Launch1, "Launch1"},
+ {Qt::Key_Launch2, "Launch2"},
+ {Qt::Key_Launch3, "Launch3"},
+ {Qt::Key_Launch4, "Launch4"},
+ {Qt::Key_Launch5, "Launch5"},
+ {Qt::Key_Launch6, "Launch6"},
+ {Qt::Key_Launch7, "Launch7"},
+ {Qt::Key_Launch8, "Launch8"},
+ {Qt::Key_Launch9, "Launch9"},
+ {Qt::Key_LaunchA, "LaunchA"},
+ {Qt::Key_LaunchB, "LaunchB"},
+ {Qt::Key_LaunchC, "LaunchC"},
+ {Qt::Key_LaunchD, "LaunchD"},
+ {Qt::Key_LaunchE, "LaunchE"},
+ {Qt::Key_LaunchF, "LaunchF"},
+ {Qt::Key_MonBrightnessUp, "MonBrightnessUp"},
+ {Qt::Key_MonBrightnessDown, "MonBrightnessDown"},
+ {Qt::Key_KeyboardLightOnOff, "KeyboardLightOnOff"},
+ {Qt::Key_KeyboardBrightnessUp, "KeyboardBrightnessUp"},
+ {Qt::Key_KeyboardBrightnessDown, "KeyboardBrightnessDown"},
+ {Qt::Key_PowerOff, "PowerOff"},
+ {Qt::Key_WakeUp, "WakeUp"},
+ {Qt::Key_Eject, "Eject"},
+ {Qt::Key_ScreenSaver, "ScreenSaver"},
+ {Qt::Key_WWW, "WWW"},
+ {Qt::Key_Memo, "Memo"},
+ {Qt::Key_LightBulb, "LightBulb"},
+ {Qt::Key_Shop, "Shop"},
+ {Qt::Key_History, "History"},
+ {Qt::Key_AddFavorite, "AddFavorite"},
+ {Qt::Key_HotLinks, "HotLinks"},
+ {Qt::Key_BrightnessAdjust, "BrightnessAdjust"},
+ {Qt::Key_Finance, "Finance"},
+ {Qt::Key_Community, "Community"},
+ {Qt::Key_AudioRewind, "AudioRewind"},
+ {Qt::Key_BackForward, "BackForward"},
+ {Qt::Key_ApplicationLeft, "ApplicationLeft"},
+ {Qt::Key_ApplicationRight, "ApplicationRight"},
+ {Qt::Key_Book, "Book"},
+ {Qt::Key_CD, "CD"},
+ {Qt::Key_Calculator, "Calculator"},
+ {Qt::Key_ToDoList, "ToDoList"},
+ {Qt::Key_ClearGrab, "ClearGrab"},
+ {Qt::Key_Close, "Close"},
+ {Qt::Key_Copy, "Copy"},
+ {Qt::Key_Cut, "Cut"},
+ {Qt::Key_Display, "Display"},
+ {Qt::Key_DOS, "DOS"},
+ {Qt::Key_Documents, "Documents"},
+ {Qt::Key_Excel, "Excel"},
+ {Qt::Key_Explorer, "Explorer"},
+ {Qt::Key_Game, "Game"},
+ {Qt::Key_Go, "Go"},
+ {Qt::Key_iTouch, "iTouch"},
+ {Qt::Key_LogOff, "LogOff"},
+ {Qt::Key_Market, "Market"},
+ {Qt::Key_Meeting, "Meeting"},
+ {Qt::Key_MenuKB, "MenuKB"},
+ {Qt::Key_MenuPB, "MenuPB"},
+ {Qt::Key_MySites, "MySites"},
+ {Qt::Key_News, "News"},
+ {Qt::Key_OfficeHome, "OfficeHome"},
+ {Qt::Key_Option, "Option"},
+ {Qt::Key_Paste, "Paste"},
+ {Qt::Key_Phone, "Phone"},
+ {Qt::Key_Calendar, "Calendar"},
+ {Qt::Key_Reply, "Reply"},
+ {Qt::Key_Reload, "Reload"},
+ {Qt::Key_RotateWindows, "RotateWindows"},
+ {Qt::Key_RotationPB, "RotationPB"},
+ {Qt::Key_RotationKB, "RotationKB"},
+ {Qt::Key_Save, "Save"},
+ {Qt::Key_Send, "Send"},
+ {Qt::Key_Spell, "Spell"},
+ {Qt::Key_SplitScreen, "SplitScreen"},
+ {Qt::Key_Support, "Support"},
+ {Qt::Key_TaskPane, "TaskPane"},
+ {Qt::Key_Terminal, "Terminal"},
+ {Qt::Key_Tools, "Tools"},
+ {Qt::Key_Travel, "Travel"},
+ {Qt::Key_Video, "Video"},
+ {Qt::Key_Word, "Word"},
+ {Qt::Key_Xfer, "Xfer"},
+ {Qt::Key_ZoomIn, "ZoomIn"},
+ {Qt::Key_ZoomOut, "ZoomOut"},
+ {Qt::Key_Away, "Away"},
+ {Qt::Key_Messenger, "Messenger"},
+ {Qt::Key_WebCam, "WebCam"},
+ {Qt::Key_MailForward, "MailForward"},
+ {Qt::Key_Pictures, "Pictures"},
+ {Qt::Key_Music, "Music"},
+ {Qt::Key_Battery, "Battery"},
+ {Qt::Key_Bluetooth, "Bluetooth"},
+ {Qt::Key_WLAN, "WLAN"},
+ {Qt::Key_UWB, "UWB"},
+ {Qt::Key_AudioForward, "AudioForward"},
+ {Qt::Key_AudioRepeat, "AudioRepeat"},
+ {Qt::Key_AudioRandomPlay, "AudioRandomPlay"},
+ {Qt::Key_Subtitle, "Subtitle"},
+ {Qt::Key_AudioCycleTrack, "AudioCycleTrack"},
+ {Qt::Key_Time, "Time"},
+ {Qt::Key_Hibernate, "Hibernate"},
+ {Qt::Key_View, "View"},
+ {Qt::Key_TopMenu, "TopMenu"},
+ {Qt::Key_PowerDown, "PowerDown"},
+ {Qt::Key_Suspend, "Suspend"},
+ {Qt::Key_ContrastAdjust, "ContrastAdjust"},
+ {Qt::Key_LaunchG, "LaunchG"},
+ {Qt::Key_LaunchH, "LaunchH"},
+ {Qt::Key_TouchpadToggle, "TouchpadToggle"},
+ {Qt::Key_TouchpadOn, "TouchpadOn"},
+ {Qt::Key_TouchpadOff, "TouchpadOff"},
+ {Qt::Key_MicMute, "MicMute"},
+ {Qt::Key_Red, "Red"},
+ {Qt::Key_Green, "Green"},
+ {Qt::Key_Yellow, "Yellow"},
+ {Qt::Key_Blue, "Blue"},
+ {Qt::Key_ChannelUp, "ChannelUp"},
+ {Qt::Key_ChannelDown, "ChannelDown"},
+ {Qt::Key_Guide, "Guide"},
+ {Qt::Key_Info, "Info"},
+ {Qt::Key_Settings, "Settings"},
+ {Qt::Key_MicVolumeUp, "MicVolumeUp"},
+ {Qt::Key_MicVolumeDown, "MicVolumeDown"},
+ {Qt::Key_New, "New"},
+ {Qt::Key_Open, "Open"},
+ {Qt::Key_Find, "Find"},
+ {Qt::Key_Undo, "Undo"},
+ {Qt::Key_Redo, "Redo"},
+ {Qt::Key_MediaLast, "MediaLast"},
+ {Qt::Key_Select, "Select"},
+ {Qt::Key_Yes, "Yes"},
+ {Qt::Key_No, "No"},
+ {Qt::Key_Cancel, "Cancel"},
+ {Qt::Key_Printer, "Printer"},
+ {Qt::Key_Execute, "Execute"},
+ {Qt::Key_Sleep, "Sleep"},
+ {Qt::Key_Play, "Play"},
+ {Qt::Key_Zoom, "Zoom"},
+ {Qt::Key_Exit, "Exit"},
+ {Qt::Key_Context1, "Context1"},
+ {Qt::Key_Context2, "Context2"},
+ {Qt::Key_Context3, "Context3"},
+ {Qt::Key_Context4, "Context4"},
+ {Qt::Key_Call, "Call"},
+ {Qt::Key_Hangup, "Hangup"},
+ {Qt::Key_Flip, "Flip"},
+ {Qt::Key_ToggleCallHangup, "ToggleCallHangup"},
+ {Qt::Key_VoiceDial, "VoiceDial"},
+ {Qt::Key_LastNumberRedial, "LastNumberRedial"},
+ {Qt::Key_Camera, "Camera"},
+ {Qt::Key_CameraFocus, "CameraFocus"}};
+
+std::optional InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str)
+{
+ for (const KeyCodeName& name : s_qt_key_names)
+ {
+ if (str == name.name)
+ return static_cast(name.code);
+ }
+
+ return std::nullopt;
+}
+
+std::optional InputManager::ConvertHostKeyboardCodeToString(u32 code)
+{
+ for (const KeyCodeName& name : s_qt_key_names)
+ {
+ if (static_cast(code) == name.code)
+ return std::string(name.name);
+ }
+
+ return std::nullopt;
+}
diff --git a/pcsx2-qt/QtUtils.cpp b/pcsx2-qt/QtUtils.cpp
new file mode 100644
index 000000000..c01a5e474
--- /dev/null
+++ b/pcsx2-qt/QtUtils.cpp
@@ -0,0 +1,703 @@
+/* PCSX2 - PS2 Emulator for PCs
+ * Copyright (C) 2002-2022 PCSX2 Dev Team
+ *
+ * PCSX2 is free software: you can redistribute it and/or modify it under the terms
+ * of the GNU Lesser General Public License as published by the Free Software Found-
+ * ation, either version 3 of the License, or (at your option) any later version.
+ *
+ * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
+ * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCSX2.
+ * If not, see .
+ */
+
+#include "PrecompiledHeader.h"
+
+#include
+#include
+#include
+#include
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+#include
+#else
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include