Add "Create Shortcut" To app context menu (#4734)
* Added basic implementation for shortcut creation Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop. * Icons display properly in shortcut * code cleanup * Moved shortcut logic to specific file, added Ava UI for shortcuts * Added linux .desktop shortcut creation * fixes to .shortcut data * code issue fixes * Added basic implementation for shortcut creation Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop. * Icons display properly in shortcut * code cleanup * Moved shortcut logic to specific file, added Ava UI for shortcuts * Added linux .desktop shortcut creation * fixes to .shortcut data * code issue fixes * added back shortcut to new contextmenu file * Replaced COM reference with ComImport for shortcut functionality * remove specific platform values and regions * Move ShortcutHelper to Ryujinx.Ui.Common.Helpers * Adjust styling and structure * code feedback changes * Added MacOS support using .app folder * Added basic implementation for shortcut creation Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop. * Icons display properly in shortcut * code cleanup * Moved shortcut logic to specific file, added Ava UI for shortcuts * Added linux .desktop shortcut creation * fixes to .shortcut data * code issue fixes * Added basic implementation for shortcut creation Currently bitmaps (.bmp) are used as the source file, colours are good (unlike .ico rn) but are scaled poorly on desktop. * Icons display properly in shortcut * code cleanup * Moved shortcut logic to specific file, added Ava UI for shortcuts * Added linux .desktop shortcut creation * fixes to .shortcut data * code issue fixes * Replaced COM reference with ComImport for shortcut functionality * remove specific platform values and regions * Move ShortcutHelper to Ryujinx.Ui.Common.Helpers * Adjust styling and structure * code feedback changes * adjust tooltip message * added shortcut-template.desktop file * set shortcut icon location to .local/share/icons * Linux code feedback changes * change InteropServices to new securifybv.ShellLink Package * added ShellLink to readme, updated shortcut comment * Code feedback changes * Added MacOS Support (As per Jose Estrada's PR) * dotnet format * Small restructuring * Embed template files into Ryujinx.Ui.Common * Disable "CreateShortcut" option for flatpak builds --------- Co-authored-by: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Co-authored-by: Jose Estrada <joseestradacobo@gmail.com>
This commit is contained in:
parent
b4bb22ba06
commit
a42f0bbb87
@ -35,6 +35,7 @@
|
|||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.28.1-build28" />
|
||||||
|
<PackageVersion Include="securifybv.ShellLink" Version="0.1.0" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||||
|
@ -141,3 +141,4 @@ See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](distribution/legal/THIRDPARTY
|
|||||||
|
|
||||||
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
- [LibHac](https://github.com/Thealexbarney/LibHac) is used for our file-system.
|
||||||
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
- [AmiiboAPI](https://www.amiiboapi.com) is used in our Amiibo emulation.
|
||||||
|
- [ShellLink](https://github.com/securifybv/ShellLink) is used for Windows shortcut generation.
|
||||||
|
@ -681,4 +681,33 @@
|
|||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
END OF TERMS AND CONDITIONS
|
||||||
```
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
# ShellLink (MIT)
|
||||||
|
<details>
|
||||||
|
<summary>See License</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2017 Yorick Koster, Securify B.V.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
```
|
||||||
</details>
|
</details>
|
@ -3,8 +3,8 @@ Version=1.0
|
|||||||
Name=Ryujinx
|
Name=Ryujinx
|
||||||
Type=Application
|
Type=Application
|
||||||
Icon=Ryujinx
|
Icon=Ryujinx
|
||||||
Exec=env DOTNET_EnableAlternateStackCheck=1 Ryujinx %f
|
Exec=Ryujinx.sh %f
|
||||||
Comment=A Nintendo Switch Emulator
|
Comment=Plays Nintendo Switch applications
|
||||||
GenericName=Nintendo Switch Emulator
|
GenericName=Nintendo Switch Emulator
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Game;Emulator;
|
Categories=Game;Emulator;
|
||||||
|
13
distribution/linux/shortcut-template.desktop
Normal file
13
distribution/linux/shortcut-template.desktop
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Name={0}
|
||||||
|
Type=Application
|
||||||
|
Icon={1}
|
||||||
|
Exec={2} %f
|
||||||
|
Comment=Nintendo Switch application
|
||||||
|
GenericName=Nintendo Switch Emulator
|
||||||
|
Terminal=false
|
||||||
|
Categories=Game;Emulator;
|
||||||
|
Keywords=Switch;Nintendo;Emulator;
|
||||||
|
StartupWMClass=Ryujinx
|
||||||
|
PrefersNonDefaultGPU=true
|
35
distribution/macos/shortcut-template.plist
Normal file
35
distribution/macos/shortcut-template.plist
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{0}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{1}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>{2}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>Copyright © 2018 - 2023 Ryujinx Team and Contributors.</string>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.games</string>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>11.0</string>
|
||||||
|
<key>UIPrerenderedIcon</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSEnvironment</key>
|
||||||
|
<dict>
|
||||||
|
<key>DOTNET_DefaultStackSize</key>
|
||||||
|
<string>200000</string>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
@ -72,6 +72,8 @@
|
|||||||
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
|
"GameListContextMenuExtractDataRomFSToolTip": "Extract the RomFS section from Application's current config (including updates)",
|
||||||
"GameListContextMenuExtractDataLogo": "Logo",
|
"GameListContextMenuExtractDataLogo": "Logo",
|
||||||
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
||||||
|
"GameListContextMenuCreateShortcut": "Create Application Shortcut",
|
||||||
|
"GameListContextMenuCreateShortcutToolTip": "Create a Desktop Shortcut that launches the selected Application",
|
||||||
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
||||||
"StatusBarSystemVersion": "System Version: {0}",
|
"StatusBarSystemVersion": "System Version: {0}",
|
||||||
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
||||||
|
@ -145,4 +145,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="Assets\Locales\en_US.json" />
|
<AdditionalFiles Include="Assets\Locales\en_US.json" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -82,4 +82,9 @@
|
|||||||
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
Header="{locale:Locale GameListContextMenuExtractDataLogo}"
|
||||||
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
ToolTip.Tip="{locale:Locale GameListContextMenuExtractDataLogoToolTip}" />
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuFlyout>
|
<MenuItem
|
||||||
|
Click="CreateApplicationShortcut_Click"
|
||||||
|
Header="{locale:Locale GameListContextMenuCreateShortcut}"
|
||||||
|
IsEnabled="{Binding CreateShortcutEnabled}"
|
||||||
|
ToolTip.Tip="{locale:Locale GameListContextMenuCreateShortcutToolTip}" />
|
||||||
|
</MenuFlyout>
|
||||||
|
@ -337,6 +337,17 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CreateApplicationShortcut_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
||||||
|
|
||||||
|
if (viewModel?.SelectedApplication != null)
|
||||||
|
{
|
||||||
|
ApplicationData selectedApplication = viewModel.SelectedApplication;
|
||||||
|
ShortcutHelper.CreateAppShortcut(selectedApplication.Path, selectedApplication.TitleName, selectedApplication.TitleId, selectedApplication.Icon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async void RunApplication_Click(object sender, RoutedEventArgs args)
|
public async void RunApplication_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
||||||
|
@ -356,6 +356,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0;
|
||||||
|
|
||||||
|
public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild();
|
||||||
|
|
||||||
public string LoadHeading
|
public string LoadHeading
|
||||||
{
|
{
|
||||||
get => _loadHeading;
|
get => _loadHeading;
|
||||||
@ -1488,7 +1490,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
Logger.RestartTime();
|
Logger.RestartTime();
|
||||||
|
|
||||||
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path);
|
SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(path, ConfigurationState.Instance.System.Language);
|
||||||
|
|
||||||
PrepareLoadScreen();
|
PrepareLoadScreen();
|
||||||
|
|
||||||
@ -1696,7 +1698,6 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -546,7 +546,7 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
return appMetadata;
|
return appMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] GetApplicationIcon(string applicationPath)
|
public byte[] GetApplicationIcon(string applicationPath, Language desiredTitleLanguage)
|
||||||
{
|
{
|
||||||
byte[] applicationIcon = null;
|
byte[] applicationIcon = null;
|
||||||
|
|
||||||
@ -600,7 +600,7 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
{
|
{
|
||||||
using var icon = new UniqueRef<IFile>();
|
using var icon = new UniqueRef<IFile>();
|
||||||
|
|
||||||
controlFs.OpenFile(ref icon.Ref, $"/icon_{_desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
controlFs.OpenFile(ref icon.Ref, $"/icon_{desiredTitleLanguage}.dat".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
using MemoryStream stream = new();
|
using MemoryStream stream = new();
|
||||||
|
|
||||||
|
171
src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs
Normal file
171
src/Ryujinx.Ui.Common/Helper/ShortcutHelper.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using ShellLink;
|
||||||
|
using SixLabors.ImageSharp;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Drawing.Imaging;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using Image = System.Drawing.Image;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ui.Common.Helper
|
||||||
|
{
|
||||||
|
public static class ShortcutHelper
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
private static void CreateShortcutWindows(string applicationFilePath, byte[] iconData, string iconPath, string cleanedAppName, string desktopPath)
|
||||||
|
{
|
||||||
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName + ".exe");
|
||||||
|
iconPath += ".ico";
|
||||||
|
|
||||||
|
MemoryStream iconDataStream = new(iconData);
|
||||||
|
using Image image = Image.FromStream(iconDataStream);
|
||||||
|
using Bitmap bitmap = new(128, 128);
|
||||||
|
using System.Drawing.Graphics graphic = System.Drawing.Graphics.FromImage(bitmap);
|
||||||
|
graphic.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||||
|
graphic.DrawImage(image, 0, 0, 128, 128);
|
||||||
|
SaveBitmapAsIcon(bitmap, iconPath);
|
||||||
|
|
||||||
|
var shortcut = Shortcut.CreateShortcut(basePath, GetArgsString(basePath, applicationFilePath), iconPath, 0);
|
||||||
|
shortcut.StringData.NameString = cleanedAppName;
|
||||||
|
shortcut.WriteToFile(Path.Combine(desktopPath, cleanedAppName + ".lnk"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("linux")]
|
||||||
|
private static void CreateShortcutLinux(string applicationFilePath, byte[] iconData, string iconPath, string desktopPath, string cleanedAppName)
|
||||||
|
{
|
||||||
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Ryujinx.sh");
|
||||||
|
var desktopFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.desktop");
|
||||||
|
iconPath += ".png";
|
||||||
|
|
||||||
|
var image = SixLabors.ImageSharp.Image.Load<Rgba32>(iconData);
|
||||||
|
image.SaveAsPng(iconPath);
|
||||||
|
|
||||||
|
using StreamWriter outputFile = new(Path.Combine(desktopPath, cleanedAppName + ".desktop"));
|
||||||
|
outputFile.Write(desktopFile, cleanedAppName, iconPath, GetArgsString(basePath, applicationFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupportedOSPlatform("macos")]
|
||||||
|
private static void CreateShortcutMacos(string appFilePath, byte[] iconData, string desktopPath, string cleanedAppName)
|
||||||
|
{
|
||||||
|
string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, AppDomain.CurrentDomain.FriendlyName);
|
||||||
|
var plistFile = EmbeddedResources.ReadAllText("Ryujinx.Ui.Common/shortcut-template.plist");
|
||||||
|
// Macos .App folder
|
||||||
|
string contentFolderPath = Path.Combine(desktopPath, cleanedAppName + ".app", "Contents");
|
||||||
|
string scriptFolderPath = Path.Combine(contentFolderPath, "MacOS");
|
||||||
|
|
||||||
|
if (!Directory.Exists(scriptFolderPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(scriptFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runner script
|
||||||
|
const string ScriptName = "runner.sh";
|
||||||
|
string scriptPath = Path.Combine(scriptFolderPath, ScriptName);
|
||||||
|
using StreamWriter scriptFile = new(scriptPath);
|
||||||
|
|
||||||
|
scriptFile.WriteLine("#!/bin/sh");
|
||||||
|
scriptFile.WriteLine(GetArgsString(basePath, appFilePath));
|
||||||
|
|
||||||
|
// Set execute permission
|
||||||
|
FileInfo fileInfo = new(scriptPath);
|
||||||
|
fileInfo.UnixFileMode |= UnixFileMode.UserExecute;
|
||||||
|
|
||||||
|
// img
|
||||||
|
string resourceFolderPath = Path.Combine(contentFolderPath, "Resources");
|
||||||
|
if (!Directory.Exists(resourceFolderPath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(resourceFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const string IconName = "icon.png";
|
||||||
|
var image = SixLabors.ImageSharp.Image.Load<Rgba32>(iconData);
|
||||||
|
image.SaveAsPng(Path.Combine(resourceFolderPath, IconName));
|
||||||
|
|
||||||
|
// plist file
|
||||||
|
using StreamWriter outputFile = new(Path.Combine(contentFolderPath, "Info.plist"));
|
||||||
|
outputFile.Write(plistFile, ScriptName, cleanedAppName, IconName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateAppShortcut(string applicationFilePath, string applicationName, string applicationId, byte[] iconData)
|
||||||
|
{
|
||||||
|
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
|
||||||
|
string cleanedAppName = string.Join("_", applicationName.Split(Path.GetInvalidFileNameChars()));
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
{
|
||||||
|
string iconPath = Path.Combine(AppDataManager.BaseDirPath, "games", applicationId, "app");
|
||||||
|
|
||||||
|
CreateShortcutWindows(applicationFilePath, iconData, iconPath, cleanedAppName, desktopPath);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
{
|
||||||
|
string iconPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "icons", "Ryujinx");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(iconPath);
|
||||||
|
CreateShortcutLinux(applicationFilePath, iconData, Path.Combine(iconPath, applicationId), desktopPath, cleanedAppName);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
CreateShortcutMacos(applicationFilePath, iconData, desktopPath, cleanedAppName);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new NotImplementedException("Shortcut support has not been implemented yet for this OS.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetArgsString(string basePath, string appFilePath)
|
||||||
|
{
|
||||||
|
// args are first defined as a list, for easier adjustments in the future
|
||||||
|
var argsList = new List<string>
|
||||||
|
{
|
||||||
|
basePath,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(CommandLineState.BaseDirPathArg))
|
||||||
|
{
|
||||||
|
argsList.Add("--root-data-dir");
|
||||||
|
argsList.Add($"\"{CommandLineState.BaseDirPathArg}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
argsList.Add($"\"{appFilePath}\"");
|
||||||
|
|
||||||
|
|
||||||
|
return String.Join(" ", argsList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a Icon (.ico) file using the source bitmap image at the specified file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source bitmap image that will be saved as an .ico file</param>
|
||||||
|
/// <param name="filePath">The location that the new .ico file will be saved too (Make sure to include '.ico' in the path).</param>
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
private static void SaveBitmapAsIcon(Bitmap source, string filePath)
|
||||||
|
{
|
||||||
|
// Code Modified From https://stackoverflow.com/a/11448060/368354 by Benlitz
|
||||||
|
byte[] header = { 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 32, 0, 0, 0, 0, 0, 22, 0, 0, 0 };
|
||||||
|
using FileStream fs = new(filePath, FileMode.Create);
|
||||||
|
|
||||||
|
fs.Write(header);
|
||||||
|
// Writing actual data
|
||||||
|
source.Save(fs, ImageFormat.Png);
|
||||||
|
// Getting data length (file length minus header)
|
||||||
|
long dataLength = fs.Length - header.Length;
|
||||||
|
// Write it in the correct place
|
||||||
|
fs.Seek(14, SeekOrigin.Begin);
|
||||||
|
fs.WriteByte((byte)dataLength);
|
||||||
|
fs.WriteByte((byte)(dataLength >> 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -45,8 +45,18 @@
|
|||||||
<EmbeddedResource Include="Resources\Logo_Twitter_Light.png" />
|
<EmbeddedResource Include="Resources\Logo_Twitter_Light.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64' OR '$(RuntimeIdentifier)' == ''">
|
||||||
|
<EmbeddedResource Include="..\..\distribution\linux\shortcut-template.desktop" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'osx-x64' OR '$(RuntimeIdentifier)' == 'osx-arm64' OR '$(RuntimeIdentifier)' == ''">
|
||||||
|
<EmbeddedResource Include="..\..\distribution\macos\shortcut-template.plist" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DiscordRichPresence" />
|
<PackageReference Include="DiscordRichPresence" />
|
||||||
|
<PackageReference Include="securifybv.ShellLink" />
|
||||||
|
<PackageReference Include="System.Drawing.Common" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -63,15 +63,15 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
<ItemGroup Condition="'$(RuntimeIdentifier)' == 'linux-x64'">
|
||||||
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
<Content Include="..\..\distribution\linux\Ryujinx.sh">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
|
<Content Include="..\..\distribution\linux\mime\Ryujinx.xml">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
<TargetPath>mime\Ryujinx.xml</TargetPath>
|
<TargetPath>mime\Ryujinx.xml</TargetPath>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Due to .net core 3.1 embedded resource loading -->
|
<!-- Due to .net core 3.1 embedded resource loading -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -101,4 +101,4 @@
|
|||||||
<EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" />
|
<EmbeddedResource Include="Modules\Updater\UpdateDialog.glade" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -23,6 +23,7 @@ namespace Ryujinx.Ui.Widgets
|
|||||||
private MenuItem _purgeShaderCacheMenuItem;
|
private MenuItem _purgeShaderCacheMenuItem;
|
||||||
private MenuItem _openPtcDirMenuItem;
|
private MenuItem _openPtcDirMenuItem;
|
||||||
private MenuItem _openShaderCacheDirMenuItem;
|
private MenuItem _openShaderCacheDirMenuItem;
|
||||||
|
private MenuItem _createShortcutMenuItem;
|
||||||
|
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
@ -187,6 +188,15 @@ namespace Ryujinx.Ui.Widgets
|
|||||||
};
|
};
|
||||||
_openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked;
|
_openShaderCacheDirMenuItem.Activated += OpenShaderCacheDir_Clicked;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _createShortcutMenuItem
|
||||||
|
//
|
||||||
|
_createShortcutMenuItem = new MenuItem("Create Application Shortcut")
|
||||||
|
{
|
||||||
|
TooltipText = "Create a Desktop Shortcut that launches the selected Application."
|
||||||
|
};
|
||||||
|
_createShortcutMenuItem.Activated += CreateShortcut_Clicked;
|
||||||
|
|
||||||
ShowComponent();
|
ShowComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,6 +223,7 @@ namespace Ryujinx.Ui.Widgets
|
|||||||
Add(new SeparatorMenuItem());
|
Add(new SeparatorMenuItem());
|
||||||
Add(_manageCacheMenuItem);
|
Add(_manageCacheMenuItem);
|
||||||
Add(_extractMenuItem);
|
Add(_extractMenuItem);
|
||||||
|
Add(_createShortcutMenuItem);
|
||||||
|
|
||||||
ShowAll();
|
ShowAll();
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ using LibHac.Ns;
|
|||||||
using LibHac.Tools.Fs;
|
using LibHac.Tools.Fs;
|
||||||
using LibHac.Tools.FsSystem;
|
using LibHac.Tools.FsSystem;
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
@ -77,6 +78,8 @@ namespace Ryujinx.Ui.Widgets
|
|||||||
_extractExeFsMenuItem.Sensitive = hasNca;
|
_extractExeFsMenuItem.Sensitive = hasNca;
|
||||||
_extractLogoMenuItem.Sensitive = hasNca;
|
_extractLogoMenuItem.Sensitive = hasNca;
|
||||||
|
|
||||||
|
_createShortcutMenuItem.Sensitive = !ReleaseInformation.IsFlatHubBuild();
|
||||||
|
|
||||||
PopupAtPointer(null);
|
PopupAtPointer(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,5 +632,11 @@ namespace Ryujinx.Ui.Widgets
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CreateShortcut_Clicked(object sender, EventArgs args)
|
||||||
|
{
|
||||||
|
byte[] appIcon = new ApplicationLibrary(_virtualFileSystem).GetApplicationIcon(_titleFilePath, ConfigurationState.Instance.System.Language);
|
||||||
|
ShortcutHelper.CreateAppShortcut(_titleFilePath, _titleName, _titleIdText, appIcon);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user