FileAssociationHelper.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. using Microsoft.Win32;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Logging;
  4. using System;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Runtime.InteropServices;
  8. using System.Runtime.Versioning;
  9. namespace Ryujinx.Ui.Common.Helper
  10. {
  11. public static partial class FileAssociationHelper
  12. {
  13. private static string[] _fileExtensions = new string[] { ".nca", ".nro", ".nso", ".nsp", ".xci" };
  14. [SupportedOSPlatform("linux")]
  15. private static string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");
  16. private const int SHCNE_ASSOCCHANGED = 0x8000000;
  17. private const int SHCNF_FLUSH = 0x1000;
  18. [LibraryImport("shell32.dll", SetLastError = true)]
  19. public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
  20. public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild();
  21. [SupportedOSPlatform("linux")]
  22. private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));
  23. [SupportedOSPlatform("linux")]
  24. private static bool InstallLinuxMimeTypes(bool uninstall = false)
  25. {
  26. string installKeyword = uninstall ? "uninstall" : "install";
  27. if (!AreMimeTypesRegisteredLinux())
  28. {
  29. string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
  30. string additionalArgs = !uninstall ? "--novendor" : "";
  31. using Process mimeProcess = new();
  32. mimeProcess.StartInfo.FileName = "xdg-mime";
  33. mimeProcess.StartInfo.Arguments = $"{installKeyword} {additionalArgs} --mode user {mimeTypesFile}";
  34. mimeProcess.Start();
  35. mimeProcess.WaitForExit();
  36. if (mimeProcess.ExitCode != 0)
  37. {
  38. Logger.Error?.PrintMsg(LogClass.Application, $"Unable to {installKeyword} mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
  39. return false;
  40. }
  41. using Process updateMimeProcess = new();
  42. updateMimeProcess.StartInfo.FileName = "update-mime-database";
  43. updateMimeProcess.StartInfo.Arguments = _mimeDbPath;
  44. updateMimeProcess.Start();
  45. updateMimeProcess.WaitForExit();
  46. if (updateMimeProcess.ExitCode != 0)
  47. {
  48. Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
  49. }
  50. }
  51. return true;
  52. }
  53. [SupportedOSPlatform("windows")]
  54. private static bool AreMimeTypesRegisteredWindows()
  55. {
  56. static bool CheckRegistering(string ext)
  57. {
  58. RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}");
  59. if (key is null)
  60. {
  61. return false;
  62. }
  63. key.OpenSubKey(@"shell\open\command");
  64. string keyValue = (string)key.GetValue("");
  65. return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
  66. }
  67. bool registered = false;
  68. foreach (string ext in _fileExtensions)
  69. {
  70. registered |= CheckRegistering(ext);
  71. }
  72. return registered;
  73. }
  74. [SupportedOSPlatform("windows")]
  75. private static bool InstallWindowsMimeTypes(bool uninstall = false)
  76. {
  77. static bool RegisterExtension(string ext, bool uninstall = false)
  78. {
  79. string keyString = @$"Software\Classes\{ext}";
  80. if (uninstall)
  81. {
  82. if (!AreMimeTypesRegisteredWindows())
  83. {
  84. return false;
  85. }
  86. Registry.CurrentUser.DeleteSubKeyTree(keyString);
  87. }
  88. else
  89. {
  90. RegistryKey key = Registry.CurrentUser.CreateSubKey(keyString);
  91. if (key is null)
  92. {
  93. return false;
  94. }
  95. key.CreateSubKey(@"shell\open\command");
  96. key.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
  97. key.Close();
  98. }
  99. // Notify Explorer the file association has been changed.
  100. SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);
  101. return true;
  102. }
  103. bool registered = false;
  104. foreach (string ext in _fileExtensions)
  105. {
  106. registered |= RegisterExtension(ext, uninstall);
  107. }
  108. return registered;
  109. }
  110. public static bool AreMimeTypesRegistered()
  111. {
  112. if (OperatingSystem.IsLinux())
  113. {
  114. return AreMimeTypesRegisteredLinux();
  115. }
  116. if (OperatingSystem.IsWindows())
  117. {
  118. return AreMimeTypesRegisteredWindows();
  119. }
  120. // TODO: Add macOS support.
  121. return false;
  122. }
  123. public static bool Install()
  124. {
  125. if (OperatingSystem.IsLinux())
  126. {
  127. return InstallLinuxMimeTypes();
  128. }
  129. if (OperatingSystem.IsWindows())
  130. {
  131. return InstallWindowsMimeTypes();
  132. }
  133. // TODO: Add macOS support.
  134. return false;
  135. }
  136. public static bool Uninstall()
  137. {
  138. if (OperatingSystem.IsLinux())
  139. {
  140. return InstallLinuxMimeTypes(true);
  141. }
  142. if (OperatingSystem.IsWindows())
  143. {
  144. return InstallWindowsMimeTypes(true);
  145. }
  146. // TODO: Add macOS support.
  147. return false;
  148. }
  149. }
  150. }