Skip to content

Commit

Permalink
GUI: Add option to register file types (#4250)
Browse files Browse the repository at this point in the history
* Add FileAssociationHelper.cs

* Add register file types option to gtk

* Add register file types option to avalonia

* Add Windows support to FileAssociationHelper.cs

* linux: Add uninstall support for file types

* Ignore .glade~ backup files

* Rename Register/Unregister methods

* gtk: Add manage file types submenu

* ava: Add manage file types submenu

* windows: Add uninstall support for file types

* Don't invert uninstall condition (formatting change)

Co-authored-by: gdkchan <[email protected]>

* Add IsTypesRegisteredWindows & Fix Windows install function

* Add AreMimeTypesRegisteredLinux()

* Fix wrong indention

Co-authored-by: AcK77 <[email protected]>
Co-authored-by: gdkchan <[email protected]>
  • Loading branch information
3 people authored Jan 22, 2023
1 parent dc30d94 commit ad6ff6c
Show file tree
Hide file tree
Showing 10 changed files with 506 additions and 308 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,6 @@ launchSettings.json

# NetCore Publishing Profiles
PublishProfiles/

# Glade backup files
*.glade~
9 changes: 8 additions & 1 deletion Ryujinx.Ava/Assets/Locales/en_US.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"MenuBarToolsInstallFirmware": "Install Firmware",
"MenuBarFileToolsInstallFirmwareFromFile": "Install a firmware from XCI or ZIP",
"MenuBarFileToolsInstallFirmwareFromDirectory": "Install a firmware from a directory",
"MenuBarToolsManageFileTypes": "Manage file types",
"MenuBarToolsInstallFileTypes": "Install file types",
"MenuBarToolsUninstallFileTypes": "Uninstall file types",
"MenuBarHelp": "Help",
"MenuBarHelpCheckForUpdates": "Check for Updates",
"MenuBarHelpAbout": "About",
Expand Down Expand Up @@ -339,6 +342,10 @@
"DialogFirmwareInstallEmbeddedSuccessMessage": "No installed firmware was found but Ryujinx was able to install firmware {0} from the provided game.\\nThe emulator will now start.",
"DialogFirmwareNoFirmwareInstalledMessage": "No Firmware Installed",
"DialogFirmwareInstalledMessage": "Firmware {0} was installed",
"DialogInstallFileTypesSuccessMessage": "Successfully installed file types!",
"DialogInstallFileTypesErrorMessage": "Failed to install file types.",
"DialogUninstallFileTypesSuccessMessage": "Successfully uninstalled file types!",
"DialogUninstallFileTypesErrorMessage": "Failed to uninstall file types.",
"DialogOpenSettingsWindowLabel": "Open Settings Window",
"DialogControllerAppletTitle": "Controller Applet",
"DialogMessageDialogErrorExceptionMessage": "Error displaying Message Dialog: {0}",
Expand Down Expand Up @@ -619,4 +626,4 @@
"UserProfilesRecoverEmptyList": "No profiles to recover",
"UserEditorTitle" : "Edit User",
"UserEditorTitleCreate" : "Create User"
}
}
50 changes: 0 additions & 50 deletions Ryujinx.Ava/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@
using Ryujinx.Ui.Common.Configuration;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading.Tasks;

namespace Ryujinx.Ava
Expand All @@ -35,48 +33,6 @@ internal partial class Program

private const uint MB_ICONWARNING = 0x30;

[SupportedOSPlatform("linux")]
static void RegisterMimeTypes()
{
if (ReleaseInformation.IsFlatHubBuild())
{
return;
}

string mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");

if (!File.Exists(Path.Combine(mimeDbPath, "packages", "Ryujinx.xml")))
{
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
using Process mimeProcess = new();

mimeProcess.StartInfo.FileName = "xdg-mime";
mimeProcess.StartInfo.Arguments = $"install --novendor --mode user {mimeTypesFile}";

mimeProcess.Start();
mimeProcess.WaitForExit();

if (mimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to install mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");
return;
}

using Process updateMimeProcess = new();

updateMimeProcess.StartInfo.FileName = "update-mime-database";
updateMimeProcess.StartInfo.Arguments = mimeDbPath;

updateMimeProcess.Start();
updateMimeProcess.WaitForExit();

if (updateMimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
}
}
}

public static void Main(string[] args)
{
Version = ReleaseInformation.GetVersion();
Expand Down Expand Up @@ -139,12 +95,6 @@ private static void Initialize(string[] args)
// Initialize the logger system.
LoggerModule.Initialize();

// Register mime types on linux.
if (OperatingSystem.IsLinux())
{
RegisterMimeTypes();
}

// Initialize Discord integration.
DiscordIntegrationModule.Initialize();

Expand Down
5 changes: 5 additions & 0 deletions Ryujinx.Ava/UI/ViewModels/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,11 @@ public bool ShowConsoleVisible
get => ConsoleHelper.SetConsoleWindowStateSupported;
}

public bool ManageFileTypesVisible
{
get => FileAssociationHelper.IsTypeAssociationSupported;
}

public ObservableCollection<ApplicationData> Applications
{
get => _applications;
Expand Down
9 changes: 6 additions & 3 deletions Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}">
</MenuItem>
<MenuItem Name="ChangeLanguageMenuItem" Header="{locale:Locale MenuBarOptionsChangeLanguage}" />
<Separator />
<MenuItem
Click="OpenSettings"
Expand Down Expand Up @@ -141,6 +140,10 @@
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFile}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromFile}" />
<MenuItem Command="{ReflectionBinding InstallFirmwareFromFolder}" Header="{locale:Locale MenuBarFileToolsInstallFirmwareFromDirectory}" />
</MenuItem>
<MenuItem Header="{locale:Locale MenuBarToolsManageFileTypes}" IsVisible="{Binding ManageFileTypesVisible}">
<MenuItem Header="{locale:Locale MenuBarToolsInstallFileTypes}" Click="InstallFileTypes_Click"/>
<MenuItem Header="{locale:Locale MenuBarToolsUninstallFileTypes}" Click="UninstallFileTypes_Click"/>
</MenuItem>
</MenuItem>
<MenuItem VerticalAlignment="Center" Header="{locale:Locale MenuBarHelp}">
<MenuItem
Expand All @@ -157,4 +160,4 @@
</MenuItem>
</Menu>
</DockPanel>
</UserControl>
</UserControl>
28 changes: 28 additions & 0 deletions Ryujinx.Ava/UI/Views/Main/MainMenuBarView.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using Avalonia.Interactivity;
using LibHac.FsSystem;
using LibHac.Ncm;
using Ryujinx.Ava.Common.Locale;
using Ryujinx.Ava.UI.Helpers;
using Ryujinx.Ava.UI.ViewModels;
using Ryujinx.Ava.UI.Windows;
using Ryujinx.Common;
using Ryujinx.Common.Utilities;
using Ryujinx.HLE.HOS;
using Ryujinx.Modules;
using Ryujinx.Ui.Common.Helper;
using System;
using System.Collections.Generic;
using System.IO;
Expand Down Expand Up @@ -163,6 +165,32 @@ private void ScanAmiiboMenuItem_AttachedToVisualTree(object sender, VisualTreeAt
}
}

private async void InstallFileTypes_Click(object sender, RoutedEventArgs e)
{
if (FileAssociationHelper.Install())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesSuccessMessage],
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogInstallFileTypesErrorMessage]);
}
}

private async void UninstallFileTypes_Click(object sender, RoutedEventArgs e)
{
if (FileAssociationHelper.Uninstall())
{
await ContentDialogHelper.CreateInfoDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesSuccessMessage],
string.Empty, LocaleManager.Instance[LocaleKeys.InputDialogOk], string.Empty, string.Empty);
}
else
{
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogUninstallFileTypesErrorMessage]);
}
}

public async void CheckForUpdates(object sender, RoutedEventArgs e)
{
if (Updater.CanUpdate(true))
Expand Down
198 changes: 198 additions & 0 deletions Ryujinx.Ui.Common/Helper/FileAssociationHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
using Microsoft.Win32;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace Ryujinx.Ui.Common.Helper
{
public static partial class FileAssociationHelper
{
private static string[] _fileExtensions = new string[] { ".nca", ".nro", ".nso", ".nsp", ".xci" };

[SupportedOSPlatform("linux")]
private static string _mimeDbPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "mime");

private const int SHCNE_ASSOCCHANGED = 0x8000000;
private const int SHCNF_FLUSH = 0x1000;

[LibraryImport("shell32.dll", SetLastError = true)]
public static partial void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);

public static bool IsTypeAssociationSupported => (OperatingSystem.IsLinux() || OperatingSystem.IsWindows()) && !ReleaseInformation.IsFlatHubBuild();

[SupportedOSPlatform("linux")]
private static bool AreMimeTypesRegisteredLinux() => File.Exists(Path.Combine(_mimeDbPath, "packages", "Ryujinx.xml"));

[SupportedOSPlatform("linux")]
private static bool InstallLinuxMimeTypes(bool uninstall = false)
{
string installKeyword = uninstall ? "uninstall" : "install";

if (!AreMimeTypesRegisteredLinux())
{
string mimeTypesFile = Path.Combine(ReleaseInformation.GetBaseApplicationDirectory(), "mime", "Ryujinx.xml");
string additionalArgs = !uninstall ? "--novendor" : "";

using Process mimeProcess = new();

mimeProcess.StartInfo.FileName = "xdg-mime";
mimeProcess.StartInfo.Arguments = $"{installKeyword} {additionalArgs} --mode user {mimeTypesFile}";

mimeProcess.Start();
mimeProcess.WaitForExit();

if (mimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Unable to {installKeyword} mime types. Make sure xdg-utils is installed. Process exited with code: {mimeProcess.ExitCode}");

return false;
}

using Process updateMimeProcess = new();

updateMimeProcess.StartInfo.FileName = "update-mime-database";
updateMimeProcess.StartInfo.Arguments = _mimeDbPath;

updateMimeProcess.Start();
updateMimeProcess.WaitForExit();

if (updateMimeProcess.ExitCode != 0)
{
Logger.Error?.PrintMsg(LogClass.Application, $"Could not update local mime database. Process exited with code: {updateMimeProcess.ExitCode}");
}
}

return true;
}

[SupportedOSPlatform("windows")]
private static bool AreMimeTypesRegisteredWindows()
{
static bool CheckRegistering(string ext)
{
RegistryKey key = Registry.CurrentUser.OpenSubKey(@$"Software\Classes\{ext}");

if (key is null)
{
return false;
}

key.OpenSubKey(@"shell\open\command");

string keyValue = (string)key.GetValue("");

return keyValue is not null && (keyValue.Contains("Ryujinx") || keyValue.Contains(AppDomain.CurrentDomain.FriendlyName));
}

bool registered = false;

foreach (string ext in _fileExtensions)
{
registered |= CheckRegistering(ext);
}

return registered;
}

[SupportedOSPlatform("windows")]
private static bool InstallWindowsMimeTypes(bool uninstall = false)
{
static bool RegisterExtension(string ext, bool uninstall = false)
{
string keyString = @$"Software\Classes\{ext}";

if (uninstall)
{
if (!AreMimeTypesRegisteredWindows())
{
return false;
}

Registry.CurrentUser.DeleteSubKeyTree(keyString);
}
else
{
RegistryKey key = Registry.CurrentUser.CreateSubKey(keyString);
if (key is null)
{
return false;
}

key.CreateSubKey(@"shell\open\command");

key.SetValue("", $"\"{Environment.ProcessPath}\" \"%1\"");
key.Close();
}

// Notify Explorer the file association has been changed.
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_FLUSH, IntPtr.Zero, IntPtr.Zero);

return true;
}

bool registered = false;

foreach (string ext in _fileExtensions)
{
registered |= RegisterExtension(ext, uninstall);
}

return registered;
}

public static bool AreMimeTypesRegistered()
{
if (OperatingSystem.IsLinux())
{
return AreMimeTypesRegisteredLinux();
}

if (OperatingSystem.IsWindows())
{
return AreMimeTypesRegisteredWindows();
}

// TODO: Add macOS support.

return false;
}

public static bool Install()
{
if (OperatingSystem.IsLinux())
{
return InstallLinuxMimeTypes();
}

if (OperatingSystem.IsWindows())
{
return InstallWindowsMimeTypes();
}

// TODO: Add macOS support.

return false;
}

public static bool Uninstall()
{
if (OperatingSystem.IsLinux())
{
return InstallLinuxMimeTypes(true);
}

if (OperatingSystem.IsWindows())
{
return InstallWindowsMimeTypes(true);
}

// TODO: Add macOS support.

return false;
}
}
}
Loading

0 comments on commit ad6ff6c

Please sign in to comment.