Skip to content

Commit 241915e

Browse files
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
1 parent 1706075 commit 241915e

File tree

4 files changed

+173
-127
lines changed

4 files changed

+173
-127
lines changed

src/Cafe/TitleList/ParsedMetaXml.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,11 @@ struct ParsedMetaXml
9090
else if (boost::starts_with(name, "longname_"))
9191
{
9292
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
93-
if (index != -1)
94-
parsedMetaXml->m_long_name[index] = child.text().as_string();
93+
if (index != -1){
94+
std::string longname = child.text().as_string();
95+
std::replace_if(longname.begin(), longname.end(), [](char c) { return c == '\r' || c == '\n';}, ' ');
96+
parsedMetaXml->m_long_name[index] = longname;
97+
}
9598
}
9699
else if (boost::starts_with(name, L"shortname_"))
97100
{

src/Cafe/TitleList/TitleInfo.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const
637637
if (m_parsedMetaXml)
638638
{
639639
std::string titleNameCfgLanguage;
640-
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language);
640+
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language);
641641
if (titleNameCfgLanguage.empty()) //Get English Title
642-
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN);
642+
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN);
643643
if (titleNameCfgLanguage.empty()) //Unknown Title
644644
titleNameCfgLanguage = "Unknown Title";
645645
return titleNameCfgLanguage;

src/gui/CemuApp.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ bool CemuApp::OnInit()
5959
fs::path user_data_path, config_path, cache_path, data_path;
6060
auto standardPaths = wxStandardPaths::Get();
6161
fs::path exePath(wxHelper::MakeFSPath(standardPaths.GetExecutablePath()));
62-
62+
#if BOOST_OS_LINUX
63+
// GetExecutablePath returns the AppImage's temporary mount location
64+
wxString appImagePath;
65+
if (wxGetEnv(("APPIMAGE"), &appImagePath))
66+
exePath = wxHelper::MakeFSPath(appImagePath);
67+
#endif
6368
// Try a portable path first, if it exists.
6469
user_data_path = config_path = cache_path = data_path = exePath.parent_path() / "portable";
6570
#if BOOST_OS_MACOS

src/gui/components/wxGameList.cpp

+160-122
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include <wx/utils.h>
2020
#include <wx/clipbrd.h>
2121

22-
2322
#include <boost/algorithm/string.hpp>
2423
#include <boost/tokenizer.hpp>
2524

@@ -526,7 +525,6 @@ void wxGameList::OnKeyDown(wxListEvent& event)
526525
}
527526
}
528527

529-
530528
enum ContextMenuEntries
531529
{
532530
kContextMenuRefreshGames = wxID_HIGHEST + 1,
@@ -732,7 +730,7 @@ void wxGameList::OnContextMenuSelected(wxCommandEvent& event)
732730
{
733731
if (wxTheClipboard->Open())
734732
{
735-
wxTheClipboard->SetData(new wxTextDataObject(gameInfo.GetTitleName()));
733+
wxTheClipboard->SetData(new wxTextDataObject(wxString::FromUTF8(gameInfo.GetTitleName())));
736734
wxTheClipboard->Close();
737735
}
738736
break;
@@ -1276,129 +1274,169 @@ void wxGameList::DeleteCachedStrings()
12761274
m_name_cache.clear();
12771275
}
12781276

1279-
#if BOOST_OS_LINUX || BOOST_OS_WINDOWS
1280-
void wxGameList::CreateShortcut(GameInfo2& gameInfo) {
1281-
const auto title_id = gameInfo.GetBaseTitleId();
1282-
const auto title_name = gameInfo.GetTitleName();
1283-
auto exe_path = ActiveSettings::GetExecutablePath();
1284-
const char *flatpak_id = getenv("FLATPAK_ID");
1285-
1286-
// GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path
1287-
wxString appimage_path;
1288-
if (wxGetEnv(("APPIMAGE"), &appimage_path)) {
1289-
exe_path = appimage_path.utf8_string();
1290-
}
1291-
12921277
#if BOOST_OS_LINUX
1293-
const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name);
1294-
wxFileDialog entry_dialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktop_entry_name,
1295-
"Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
1296-
#elif BOOST_OS_WINDOWS
1297-
// Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path
1298-
PWSTR user_shortcut_folder;
1299-
SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &user_shortcut_folder);
1300-
const wxString shortcut_name = wxString::Format("%s.lnk", title_name);
1301-
wxFileDialog entry_dialog(this, _("Choose shortcut location"), _pathToUtf8(user_shortcut_folder), shortcut_name,
1302-
"Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
1303-
#endif
1304-
const auto result = entry_dialog.ShowModal();
1305-
if (result == wxID_CANCEL)
1306-
return;
1307-
const auto output_path = entry_dialog.GetPath();
1278+
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
1279+
{
1280+
const auto titleId = gameInfo.GetBaseTitleId();
1281+
const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName());
1282+
auto exePath = ActiveSettings::GetExecutablePath();
1283+
const char* flatpakId = getenv("FLATPAK_ID");
1284+
1285+
const wxString desktopEntryName = wxString::Format("%s.desktop", titleName);
1286+
wxFileDialog entryDialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktopEntryName,
1287+
"Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
1288+
const auto result = entryDialog.ShowModal();
1289+
if (result == wxID_CANCEL)
1290+
return;
1291+
const auto output_path = entryDialog.GetPath();
13081292

1309-
#if BOOST_OS_LINUX
1310-
std::optional<fs::path> icon_path;
1311-
// Obtain and convert icon
1312-
{
1313-
m_icon_cache_mtx.lock();
1314-
const auto icon_iter = m_icon_cache.find(title_id);
1315-
const auto result_index = (icon_iter != m_icon_cache.cend()) ? std::optional<int>(icon_iter->second.first) : std::nullopt;
1316-
m_icon_cache_mtx.unlock();
1317-
1318-
// In most cases it should find it
1319-
if (!result_index){
1320-
wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
1321-
}
1322-
else {
1323-
const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons");
1324-
1325-
if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){
1326-
wxMessageBox(_("Cannot access the icon directory, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
1327-
}
1328-
else {
1329-
icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
1293+
std::optional<fs::path> iconPath;
1294+
// Obtain and convert icon
1295+
[&]()
1296+
{
1297+
int iconIndex, smallIconIndex;
1298+
1299+
if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex))
1300+
{
1301+
cemuLog_log(LogType::Force, "Icon hasn't loaded");
1302+
return;
1303+
}
1304+
const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons");
13301305

1331-
auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage();
1306+
if (!fs::exists(outIconDir) && !fs::create_directories(outIconDir))
1307+
{
1308+
cemuLog_log(LogType::Force, "Failed to create icon directory");
1309+
return;
1310+
}
13321311

1333-
wxFileOutputStream png_file(_pathToUtf8(icon_path.value()));
1334-
wxPNGHandler pngHandler;
1335-
if (!pngHandler.SaveFile(&image, png_file, false)) {
1336-
icon_path = std::nullopt;
1337-
wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
1338-
}
1339-
}
1340-
}
1341-
}
1342-
1343-
std::string desktop_exec_entry;
1344-
if (flatpak_id)
1345-
desktop_exec_entry = fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpak_id, title_id);
1346-
else
1347-
desktop_exec_entry = fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exe_path), title_id);
1348-
1349-
// 'Icon' accepts spaces in file name, does not accept quoted file paths
1350-
// 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
1351-
auto desktop_entry_string =
1352-
fmt::format("[Desktop Entry]\n"
1353-
"Name={0}\n"
1354-
"Comment=Play {0} on Cemu\n"
1355-
"Exec={1}\n"
1356-
"Icon={2}\n"
1357-
"Terminal=false\n"
1358-
"Type=Application\n"
1359-
"Categories=Game;\n",
1360-
title_name,
1361-
desktop_exec_entry,
1362-
_pathToUtf8(icon_path.value_or("")));
1363-
1364-
if (flatpak_id)
1365-
desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id);
1366-
1367-
std::ofstream output_stream(output_path);
1368-
if (!output_stream.good())
1369-
{
1370-
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
1371-
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
1372-
return;
1373-
}
1374-
output_stream << desktop_entry_string;
1312+
iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
1313+
wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value()));
13751314

1315+
auto image = m_image_list->GetIcon(iconIndex).ConvertToImage();
1316+
wxPNGHandler pngHandler;
1317+
if (!pngHandler.SaveFile(&image, pngFileStream, false))
1318+
{
1319+
iconPath = std::nullopt;
1320+
cemuLog_log(LogType::Force, "Icon failed to save");
1321+
}
1322+
}();
1323+
1324+
std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId)
1325+
: fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId);
1326+
1327+
// 'Icon' accepts spaces in file name, does not accept quoted file paths
1328+
// 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
1329+
auto desktopEntryString = fmt::format(
1330+
"[Desktop Entry]\n"
1331+
"Name={0}\n"
1332+
"Comment=Play {0} on Cemu\n"
1333+
"Exec={1}\n"
1334+
"Icon={2}\n"
1335+
"Terminal=false\n"
1336+
"Type=Application\n"
1337+
"Categories=Game;\n",
1338+
titleName.utf8_string(),
1339+
desktopExecEntry,
1340+
_pathToUtf8(iconPath.value_or("")));
1341+
1342+
if (flatpakId)
1343+
desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId);
1344+
1345+
std::ofstream outputStream(output_path.utf8_string());
1346+
if (!outputStream.good())
1347+
{
1348+
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
1349+
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
1350+
return;
1351+
}
1352+
outputStream << desktopEntryString;
1353+
}
13761354
#elif BOOST_OS_WINDOWS
1377-
IShellLinkW *shell_link;
1378-
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shell_link));
1379-
if (SUCCEEDED(hres))
1380-
{
1381-
const auto description = wxString::Format("Play %s on Cemu", title_name);
1382-
const auto args = wxString::Format("-t %016llx", title_id);
1383-
1384-
shell_link->SetPath(exe_path.wstring().c_str());
1385-
shell_link->SetDescription(description.wc_str());
1386-
shell_link->SetArguments(args.wc_str());
1387-
shell_link->SetWorkingDirectory(exe_path.parent_path().wstring().c_str());
1388-
// Use icon from Cemu exe for now since we can't embed icons into the shortcut
1389-
// in the future we could convert and store icons in AppData or ProgramData
1390-
shell_link->SetIconLocation(exe_path.wstring().c_str(), 0);
1391-
1392-
IPersistFile *shell_link_file;
1393-
// save the shortcut
1394-
hres = shell_link->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shell_link_file));
1395-
if (SUCCEEDED(hres))
1396-
{
1397-
hres = shell_link_file->Save(output_path.wc_str(), TRUE);
1398-
shell_link_file->Release();
1399-
}
1400-
shell_link->Release();
1401-
}
1402-
#endif
1355+
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
1356+
{
1357+
const auto titleId = gameInfo.GetBaseTitleId();
1358+
const auto titleName = wxString::FromUTF8(gameInfo.GetTitleName());
1359+
auto exePath = ActiveSettings::GetExecutablePath();
1360+
1361+
// Get '%APPDATA%\Microsoft\Windows\Start Menu\Programs' path
1362+
PWSTR userShortcutFolder;
1363+
SHGetKnownFolderPath(FOLDERID_Programs, 0, NULL, &userShortcutFolder);
1364+
const wxString shortcutName = wxString::Format("%s.lnk", titleName);
1365+
wxFileDialog shortcutDialog(this, _("Choose shortcut location"), _pathToUtf8(userShortcutFolder), shortcutName,
1366+
"Shortcut (*.lnk)|*.lnk", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
1367+
1368+
const auto result = shortcutDialog.ShowModal();
1369+
if (result == wxID_CANCEL)
1370+
return;
1371+
const auto outputPath = shortcutDialog.GetPath();
1372+
1373+
std::optional<fs::path> icon_path = std::nullopt;
1374+
[&]()
1375+
{
1376+
int iconIdx;
1377+
int smallIconIdx;
1378+
if (!QueryIconForTitle(titleId, iconIdx, smallIconIdx))
1379+
{
1380+
cemuLog_log(LogType::Force, "Icon hasn't loaded");
1381+
return;
1382+
}
1383+
const auto icon = m_image_list->GetIcon(iconIdx);
1384+
PWSTR localAppData;
1385+
const auto hres = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &localAppData);
1386+
wxBitmap bitmap{};
1387+
auto folder = fs::path(localAppData) / "Cemu" / "icons";
1388+
if (!SUCCEEDED(hres) || (!fs::exists(folder) && !fs::create_directories(folder)))
1389+
{
1390+
cemuLog_log(LogType::Force, "Failed to create icon directory");
1391+
return;
1392+
}
1393+
if (!bitmap.CopyFromIcon(icon))
1394+
{
1395+
cemuLog_log(LogType::Force, "Failed to copy icon");
1396+
return;
1397+
}
1398+
1399+
icon_path = folder / fmt::format("{:016x}.ico", titleId);
1400+
auto stream = wxFileOutputStream(_pathToUtf8(*icon_path));
1401+
auto image = bitmap.ConvertToImage();
1402+
wxICOHandler icohandler{};
1403+
if (!icohandler.SaveFile(&image, stream, false))
1404+
{
1405+
icon_path = std::nullopt;
1406+
cemuLog_log(LogType::Force, "Icon failed to save");
1407+
}
1408+
}();
1409+
1410+
IShellLinkW* shellLink;
1411+
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink));
1412+
if (SUCCEEDED(hres))
1413+
{
1414+
const auto description = wxString::Format("Play %s on Cemu", titleName);
1415+
const auto args = wxString::Format("-t %016llx", titleId);
1416+
1417+
shellLink->SetPath(exePath.wstring().c_str());
1418+
shellLink->SetDescription(description.wc_str());
1419+
shellLink->SetArguments(args.wc_str());
1420+
shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str());
1421+
1422+
if (icon_path)
1423+
shellLink->SetIconLocation(icon_path->wstring().c_str(), 0);
1424+
else
1425+
shellLink->SetIconLocation(exePath.wstring().c_str(), 0);
1426+
1427+
IPersistFile* shellLinkFile;
1428+
// save the shortcut
1429+
hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shellLinkFile));
1430+
if (SUCCEEDED(hres))
1431+
{
1432+
hres = shellLinkFile->Save(outputPath.wc_str(), TRUE);
1433+
shellLinkFile->Release();
1434+
}
1435+
shellLink->Release();
1436+
}
1437+
if (!SUCCEEDED(hres)) {
1438+
auto errorMsg = formatWxString(_("Failed to save shortcut to {}"), outputPath);
1439+
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
1440+
}
14031441
}
1404-
#endif
1442+
#endif

0 commit comments

Comments
 (0)