mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-22 17:19:18 +01:00
Gamelist: Display title long names + improvements for shortcuts (#1126)
- Windows icons are stored as .ico files to %LOCALAPPDATA%/Cemu/icons/ - Long title names chosen as some games (NSMBU + NSLU) add trailing dots for their shortnames - Long title names have their newlines replaced with spaces at parsing - Linux shortcut paths are saved with UTF-8 encoding - Game titles are copied and saved with UTF-8 encoding
This commit is contained in:
parent
17060752b6
commit
241915e1a6
@ -90,8 +90,11 @@ struct ParsedMetaXml
|
|||||||
else if (boost::starts_with(name, "longname_"))
|
else if (boost::starts_with(name, "longname_"))
|
||||||
{
|
{
|
||||||
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
|
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
|
||||||
if (index != -1)
|
if (index != -1){
|
||||||
parsedMetaXml->m_long_name[index] = child.text().as_string();
|
std::string longname = child.text().as_string();
|
||||||
|
std::replace_if(longname.begin(), longname.end(), [](char c) { return c == '\r' || c == '\n';}, ' ');
|
||||||
|
parsedMetaXml->m_long_name[index] = longname;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (boost::starts_with(name, L"shortname_"))
|
else if (boost::starts_with(name, L"shortname_"))
|
||||||
{
|
{
|
||||||
|
@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const
|
|||||||
if (m_parsedMetaXml)
|
if (m_parsedMetaXml)
|
||||||
{
|
{
|
||||||
std::string titleNameCfgLanguage;
|
std::string titleNameCfgLanguage;
|
||||||
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language);
|
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language);
|
||||||
if (titleNameCfgLanguage.empty()) //Get English Title
|
if (titleNameCfgLanguage.empty()) //Get English Title
|
||||||
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN);
|
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN);
|
||||||
if (titleNameCfgLanguage.empty()) //Unknown Title
|
if (titleNameCfgLanguage.empty()) //Unknown Title
|
||||||
titleNameCfgLanguage = "Unknown Title";
|
titleNameCfgLanguage = "Unknown Title";
|
||||||
return titleNameCfgLanguage;
|
return titleNameCfgLanguage;
|
||||||
|
@ -59,7 +59,12 @@ bool CemuApp::OnInit()
|
|||||||
fs::path user_data_path, config_path, cache_path, data_path;
|
fs::path user_data_path, config_path, cache_path, data_path;
|
||||||
auto standardPaths = wxStandardPaths::Get();
|
auto standardPaths = wxStandardPaths::Get();
|
||||||
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
|
||||||
|
#if BOOST_OS_LINUX
|
||||||
|
// GetExecutablePath returns the AppImage's temporary mount location
|
||||||
|
wxString appImagePath;
|
||||||
|
if (wxGetEnv(("APPIMAGE"), &appImagePath))
|
||||||
|
exePath = wxHelper::MakeFSPath(appImagePath);
|
||||||
|
#endif
|
||||||
// Try a portable path first, if it exists.
|
// Try a portable path first, if it exists.
|
||||||
user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable";
|
user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable";
|
||||||
#if BOOST_OS_MACOS
|
#if BOOST_OS_MACOS
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
#include <wx/utils.h>
|
#include <wx/utils.h>
|
||||||
#include <wx/clipbrd.h>
|
#include <wx/clipbrd.h>
|
||||||
|
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
#include <boost/tokenizer.hpp>
|
#include <boost/tokenizer.hpp>
|
||||||
|
|
||||||
@ -526,7 +525,6 @@ void wxGameList::OnKeyDown(wxListEvent& event)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
enum ContextMenuEntries
|
enum ContextMenuEntries
|
||||||
{
|
{
|
||||||
kContextMenuRefreshGames = wxID_HIGHEST + 1,
|
kContextMenuRefreshGames = wxID_HIGHEST + 1,
|
||||||
@ -732,7 +730,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event)
|
|||||||
{
|
{
|
||||||
if (wxTheClipboard->Open())
|
if (wxTheClipboard->Open())
|
||||||
{
|
{
|
||||||
wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName()));
|
wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName())));
|
||||||
wxTheClipboard->Close();
|
wxTheClipboard->Close();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -1276,80 +1274,60 @@ void wxGameList::DeleteCachedStrings()
|
|||||||
m_name_cache.clear();
|
m_name_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if BOOST_OS_LINUX || BOOST_OS_WINDOWS
|
|
||||||
void wxGameList::CreateShortcut(GameInfo2& gameInfo) {
|
|
||||||
const auto title_id = gameInfo.GetBaseTitleId();
|
|
||||||
const auto title_name = gameInfo.GetTitleName();
|
|
||||||
auto exe_path = ActiveSettings::GetExecutablePath();
|
|
||||||
const char *flatpak_id = getenv("FLATPAK_ID");
|
|
||||||
|
|
||||||
// GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path
|
|
||||||
wxString appimage_path;
|
|
||||||
if (wxGetEnv(("APPIMAGE"), &appimage_path)) {
|
|
||||||
exe_path = appimage_path.utf8_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
#if BOOST_OS_LINUX
|
#if BOOST_OS_LINUX
|
||||||
const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name);
|
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
|
||||||
wxFileDialog entry_dialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktop_entry_name,
|
{
|
||||||
|
const auto titleId = gameInfo.GetBaseTitleId();
|
||||||
|
const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName());
|
||||||
|
auto exePath = ActiveSettings::GetExecutablePath();
|
||||||
|
const char* flatpakId = getenv("FLATPAK_ID");
|
||||||
|
|
||||||
|
const wxString desktopEntryName = wxString::Format("%s.desktop", titleName);
|
||||||
|
wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName,
|
||||||
"Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
|
"Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
|
||||||
#elif BOOST_OS_WINDOWS
|
const auto result = entryDialog.ShowModal();
|
||||||
// Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path
|
|
||||||
PWSTR user_shortcut_folder;
|
|
||||||
SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &user_shortcut_folder);
|
|
||||||
const wxString shortcut_name = wxString::Format("%s.lnk", title_name);
|
|
||||||
wxFileDialog entry_dialog(this, _("Choose shortcut location"), _pathToUtf8(user_shortcut_folder), shortcut_name,
|
|
||||||
"Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
|
|
||||||
#endif
|
|
||||||
const auto result = entry_dialog.ShowModal();
|
|
||||||
if (result == wxID_CANCEL)
|
if (result == wxID_CANCEL)
|
||||||
return;
|
return;
|
||||||
const auto output_path = entry_dialog.GetPath();
|
const auto output_path = entryDialog.GetPath();
|
||||||
|
|
||||||
#if BOOST_OS_LINUX
|
std::optional<fs::path> iconPath;
|
||||||
std::optional<fs::path> icon_path;
|
|
||||||
// Obtain and convert icon
|
// Obtain and convert icon
|
||||||
|
[&]()
|
||||||
{
|
{
|
||||||
m_icon_cache_mtx.lock();
|
int iconIndex, smallIconIndex;
|
||||||
const auto icon_iter = m_icon_cache.find(title_id);
|
|
||||||
const auto result_index = (icon_iter != m_icon_cache.cend()) ? std::optional<int>(icon_iter->second.first) : std::nullopt;
|
|
||||||
m_icon_cache_mtx.unlock();
|
|
||||||
|
|
||||||
// In most cases it should find it
|
if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex))
|
||||||
if (!result_index){
|
{
|
||||||
wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
|
cemuLog_log(LogType::Force, "Icon hasn't loaded");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons");
|
||||||
const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons");
|
|
||||||
|
|
||||||
if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){
|
if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir))
|
||||||
wxMessageBox(_("Cannot access the icon directory, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to create icon directory");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
|
|
||||||
|
|
||||||
auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage();
|
iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
|
||||||
|
wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value()));
|
||||||
|
|
||||||
wxFileOutputStream png_file(_pathToUtf8(icon_path.value()));
|
auto image = m_image_list->GetIcon(iconIndex).ConvertToImage();
|
||||||
wxPNGHandler pngHandler;
|
wxPNGHandler pngHandler;
|
||||||
if (!pngHandler.SaveFile(&image, png_file, false)) {
|
if (!pngHandler.SaveFile(&image, pngFileStream, false))
|
||||||
icon_path = std::nullopt;
|
{
|
||||||
wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
|
iconPath = std::nullopt;
|
||||||
}
|
cemuLog_log(LogType::Force, "Icon failed to save");
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
std::string desktop_exec_entry;
|
std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId)
|
||||||
if (flatpak_id)
|
: fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId);
|
||||||
desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id);
|
|
||||||
else
|
|
||||||
desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id);
|
|
||||||
|
|
||||||
// 'Icon' accepts spaces in file name, does not accept quoted file paths
|
// 'Icon' accepts spaces in file name, does not accept quoted file paths
|
||||||
// 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
|
// 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
|
||||||
auto desktop_entry_string =
|
auto desktopEntryString = fmt::format(
|
||||||
fmt::format("[Desktop Entry]\n"
|
"[Desktop Entry]\n"
|
||||||
"Name={0}\n"
|
"Name={0}\n"
|
||||||
"Comment=Play {0} on Cemu\n"
|
"Comment=Play {0} on Cemu\n"
|
||||||
"Exec={1}\n"
|
"Exec={1}\n"
|
||||||
@ -1357,48 +1335,108 @@ void wxGameList::CreateShortcut(GameInfo2& gameInfo) {
|
|||||||
"Terminal=false\n"
|
"Terminal=false\n"
|
||||||
"Type=Application\n"
|
"Type=Application\n"
|
||||||
"Categories=Game;\n",
|
"Categories=Game;\n",
|
||||||
title_name,
|
titleName.utf8_string(),
|
||||||
desktop_exec_entry,
|
desktopExecEntry,
|
||||||
_pathToUtf8(icon_path.value_or("")));
|
_pathToUtf8(iconPath.value_or("")));
|
||||||
|
|
||||||
if (flatpak_id)
|
if (flatpakId)
|
||||||
desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id);
|
desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId);
|
||||||
|
|
||||||
std::ofstream output_stream(output_path);
|
std::ofstream outputStream(output_path.utf8_string());
|
||||||
if (!output_stream.good())
|
if (!outputStream.good())
|
||||||
{
|
{
|
||||||
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
|
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
|
||||||
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
output_stream << desktop_entry_string;
|
outputStream << desktopEntryString;
|
||||||
|
}
|
||||||
#elif BOOST_OS_WINDOWS
|
#elif BOOST_OS_WINDOWS
|
||||||
IShellLinkW *shell_link;
|
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
|
||||||
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shell_link));
|
{
|
||||||
|
const auto titleId = gameInfo.GetBaseTitleId();
|
||||||
|
const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName());
|
||||||
|
auto exePath = ActiveSettings::GetExecutablePath();
|
||||||
|
|
||||||
|
// Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path
|
||||||
|
PWSTR userShortcutFolder;
|
||||||
|
SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder);
|
||||||
|
const wxString shortcutName = wxString::Format("%s.lnk", titleName);
|
||||||
|
wxFileDialog shortcutDialog(this, _("Choose shortcut location"), _pathToUtf8(userShortcutFolder), shortcutName,
|
||||||
|
"Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
|
||||||
|
|
||||||
|
const auto result = shortcutDialog.ShowModal();
|
||||||
|
if (result == wxID_CANCEL)
|
||||||
|
return;
|
||||||
|
const auto outputPath = shortcutDialog.GetPath();
|
||||||
|
|
||||||
|
std::optional<fs::path> icon_path = std::nullopt;
|
||||||
|
[&]()
|
||||||
|
{
|
||||||
|
int iconIdx;
|
||||||
|
int smallIconIdx;
|
||||||
|
if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Icon hasn't loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto icon = m_image_list->GetIcon(iconIdx);
|
||||||
|
PWSTR localAppData;
|
||||||
|
const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData);
|
||||||
|
wxBitmap bitmap{};
|
||||||
|
auto folder = fs::path(localAppData) / "Cemu" / "icons";
|
||||||
|
if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder)))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to create icon directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!bitmap.CopyFromIcon(icon))
|
||||||
|
{
|
||||||
|
cemuLog_log(LogType::Force, "Failed to copy icon");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
icon_path = folder / fmt::format("{:016x}.ico", titleId);
|
||||||
|
auto stream = wxFileOutputStream(_pathToUtf8(*icon_path));
|
||||||
|
auto image = bitmap.ConvertToImage();
|
||||||
|
wxICOHandler icohandler{};
|
||||||
|
if (!icohandler.SaveFile(&image, stream, false))
|
||||||
|
{
|
||||||
|
icon_path = std::nullopt;
|
||||||
|
cemuLog_log(LogType::Force, "Icon failed to save");
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
IShellLinkW* shellLink;
|
||||||
|
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink));
|
||||||
if (SUCCEEDED(hres))
|
if (SUCCEEDED(hres))
|
||||||
{
|
{
|
||||||
const auto description = wxString::Format("Play %s on Cemu", title_name);
|
const auto description = wxString::Format("Play %s on Cemu", titleName);
|
||||||
const auto args = wxString::Format("-t %016llx", title_id);
|
const auto args = wxString::Format("-t %016llx", titleId);
|
||||||
|
|
||||||
shell_link->SetPath(exe_path.wstring().c_str());
|
shellLink->SetPath(exePath.wstring().c_str());
|
||||||
shell_link->SetDescription(description.wc_str());
|
shellLink->SetDescription(description.wc_str());
|
||||||
shell_link->SetArguments(args.wc_str());
|
shellLink->SetArguments(args.wc_str());
|
||||||
shell_link->SetWorkingDirectory(exe_path.parent_path().wstring().c_str());
|
shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str());
|
||||||
// Use icon from Cemu exe for now since we can't embed icons into the shortcut
|
|
||||||
// in the future we could convert and store icons in AppData or ProgramData
|
|
||||||
shell_link->SetIconLocation(exe_path.wstring().c_str(), 0);
|
|
||||||
|
|
||||||
IPersistFile *shell_link_file;
|
if (icon_path)
|
||||||
|
shellLink->SetIconLocation(icon_path->wstring().c_str(), 0);
|
||||||
|
else
|
||||||
|
shellLink->SetIconLocation(exePath.wstring().c_str(), 0);
|
||||||
|
|
||||||
|
IPersistFile* shellLinkFile;
|
||||||
// save the shortcut
|
// save the shortcut
|
||||||
hres = shell_link->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shell_link_file));
|
hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shellLinkFile));
|
||||||
if (SUCCEEDED(hres))
|
if (SUCCEEDED(hres))
|
||||||
{
|
{
|
||||||
hres = shell_link_file->Save(output_path.wc_str(), TRUE);
|
hres = shellLinkFile->Save(outputPath.wc_str(), TRUE);
|
||||||
shell_link_file->Release();
|
shellLinkFile->Release();
|
||||||
|
}
|
||||||
|
shellLink->Release();
|
||||||
|
}
|
||||||
|
if (!SUCCEEDED(hres)) {
|
||||||
|
auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath);
|
||||||
|
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
|
||||||
}
|
}
|
||||||
shell_link->Release();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
Loading…
Reference in New Issue
Block a user