Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to game shortcuts and UTF8 gamelist name fixes #1126

Merged
merged 14 commits into from
Mar 24, 2024
7 changes: 5 additions & 2 deletions src/Cafe/TitleList/ParsedMetaXml.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ struct ParsedMetaXml
else if (boost::starts_with(name, "longname_"))
{
const sint32 index = GetLanguageIndex(name.substr(std::size("longname_") - 1));
if (index != -1)
parsedMetaXml->m_long_name[index] = child.text().as_string();
if (index != -1){
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_"))
{
Expand Down
4 changes: 2 additions & 2 deletions src/Cafe/TitleList/TitleInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,9 @@ std::string TitleInfo::GetMetaTitleName() const
if (m_parsedMetaXml)
{
std::string titleNameCfgLanguage;
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(GetConfig().console_language);
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(GetConfig().console_language);
if (titleNameCfgLanguage.empty()) //Get English Title
titleNameCfgLanguage = m_parsedMetaXml->GetShortName(CafeConsoleLanguage::EN);
titleNameCfgLanguage = m_parsedMetaXml->GetLongName(CafeConsoleLanguage::EN);
if (titleNameCfgLanguage.empty()) //Unknown Title
titleNameCfgLanguage = "Unknown Title";
return titleNameCfgLanguage;
Expand Down
263 changes: 142 additions & 121 deletions src/gui/components/wxGameList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
#include <wx/utils.h>
#include <wx/clipbrd.h>


#include <boost/algorithm/string.hpp>
#include <boost/tokenizer.hpp>

Expand Down Expand Up @@ -526,7 +525,6 @@ void wxGameList::OnKeyDown(wxListEvent& event)
}
}


enum ContextMenuEntries
{
kContextMenuRefreshGames = wxID_HIGHEST + 1,
Expand Down Expand Up @@ -1276,129 +1274,152 @@ void wxGameList::DeleteCachedStrings()
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
const wxString desktop_entry_name = wxString::Format("%s.desktop", title_name);
wxFileDialog entry_dialog(this, _("Choose desktop entry location"), "~/.local/share/applications", desktop_entry_name,
"Desktop file (*.desktop)|*.desktop", wxFD_SAVE | wxFD_CHANGE_DIR | wxFD_OVERWRITE_PROMPT);
#elif BOOST_OS_WINDOWS
// 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)
return;
const auto output_path = entry_dialog.GetPath();
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
{
const auto titleId = gameInfo.GetBaseTitleId();
const auto titleName = gameInfo.GetTitleName();
auto exePath = ActiveSettings::GetExecutablePath();
const char* flatpakId = getenv("FLATPAK_ID");

// GetExecutablePath returns the AppImage's temporary mount location, instead of its actual path
wxString appimagePath;
if (wxGetEnv(("APPIMAGE"), &appimagePath))
exePath = appimagePath.utf8_string();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to introduce a reusable ActiveSettings::GetRealExecutablePath instead of hardcoding the appimage path handling here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that ActiveSettings::GetExecutablePath is only used for shortcuts and the autoupdater, maybe it simply should be the executable path?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that makes more sense


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);
const auto result = entryDialog.ShowModal();
if (result == wxID_CANCEL)
return;
const auto output_path = entryDialog.GetPath();

#if BOOST_OS_LINUX
std::optional<fs::path> icon_path;
// Obtain and convert icon
{
m_icon_cache_mtx.lock();
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 (!result_index){
wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
}
else {
const fs::path out_icon_dir = ActiveSettings::GetUserDataPath("icons");

if (!fs::exists(out_icon_dir) && !fs::create_directories(out_icon_dir)){
wxMessageBox(_("Cannot access the icon directory, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
}
else {
icon_path = out_icon_dir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
std::optional<fs::path> iconPath;
// Obtain and convert icon
{
int iconIndex, smallIconIndex;

auto image = m_image_list->GetIcon(result_index.value()).ConvertToImage();
if (!QueryIconForTitle(titleId, iconIndex, smallIconIndex))
wxMessageBox(_("Icon is yet to load, so will not be used by the shortcut"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
else
{
const fs::path outIconDir = ActiveSettings::GetUserDataPath("icons");

wxFileOutputStream png_file(_pathToUtf8(icon_path.value()));
wxPNGHandler pngHandler;
if (!pngHandler.SaveFile(&image, png_file, false)) {
icon_path = std::nullopt;
wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
}
}
}
}

std::string desktop_exec_entry;
if (flatpak_id)
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
// 'Exec' does not accept non-escaped spaces, and can accept quoted file paths
auto desktop_entry_string =
fmt::format("[Desktop Entry]\n"
"Name={0}\n"
"Comment=Play {0} on Cemu\n"
"Exec={1}\n"
"Icon={2}\n"
"Terminal=false\n"
"Type=Application\n"
"Categories=Game;\n",
title_name,
desktop_exec_entry,
_pathToUtf8(icon_path.value_or("")));

if (flatpak_id)
desktop_entry_string += fmt::format("X-Flatpak={}\n", flatpak_id);

std::ofstream output_stream(output_path);
if (!output_stream.good())
{
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return;
}
output_stream << desktop_entry_string;
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);
else
{
iconPath = outIconDir / fmt::format("{:016x}.png", gameInfo.GetBaseTitleId());
wxFileOutputStream pngFileStream(_pathToUtf8(iconPath.value()));

auto image = m_image_list->GetIcon(iconIndex).ConvertToImage();
wxPNGHandler pngHandler;
if (!pngHandler.SaveFile(&image, pngFileStream, false))
{
iconPath = std::nullopt;
wxMessageBox(_("The icon was unable to be saved, the shortcut will have no icon"), _("Warning"), wxOK | wxCENTRE | wxICON_WARNING);
}
}
}
}

std::string desktopExecEntry = flatpakId ? fmt::format("/usr/bin/flatpak run {0} --title-id {1:016x}", flatpakId, titleId)
: fmt::format("{0:?} --title-id {1:016x}", _pathToUtf8(exePath), titleId);

// '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
auto desktopEntryString = fmt::format(
"[Desktop Entry]\n"
"Name={0}\n"
"Comment=Play {0} on Cemu\n"
"Exec={1}\n"
"Icon={2}\n"
"Terminal=false\n"
"Type=Application\n"
"Categories=Game;\n",
titleName,
desktopExecEntry,
_pathToUtf8(iconPath.value_or("")));

if (flatpakId)
desktopEntryString += fmt::format("X-Flatpak={}\n", flatpakId);

std::ofstream outputStream(output_path);
if (!outputStream.good())
{
auto errorMsg = formatWxString(_("Failed to save desktop entry to {}"), output_path.utf8_string());
wxMessageBox(errorMsg, _("Error"), wxOK | wxCENTRE | wxICON_ERROR);
return;
}
outputStream << desktopEntryString;
}
#elif BOOST_OS_WINDOWS
IShellLinkW *shell_link;
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shell_link));
if (SUCCEEDED(hres))
{
const auto description = wxString::Format("Play %s on Cemu", title_name);
const auto args = wxString::Format("-t %016llx", title_id);

shell_link->SetPath(exe_path.wstring().c_str());
shell_link->SetDescription(description.wc_str());
shell_link->SetArguments(args.wc_str());
shell_link->SetWorkingDirectory(exe_path.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;
// save the shortcut
hres = shell_link->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shell_link_file));
if (SUCCEEDED(hres))
{
hres = shell_link_file->Save(output_path.wc_str(), TRUE);
shell_link_file->Release();
}
shell_link->Release();
}
#endif
void wxGameList::CreateShortcut(GameInfo2& gameInfo)
{
const auto titleId = gameInfo.GetBaseTitleId();
const auto titleName = 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();

int iconIdx;
int smallIconIdx;
std::optional<fs::path> icon_path = std::nullopt;
if (QueryIconForTitle(titleId, iconIdx, smallIconIdx))
{
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) && bitmap.CopyFromIcon(icon) && (fs::exists(folder) || fs::create_directories(folder)))
{
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;
}
}

IShellLinkW* shellLink;
HRESULT hres = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast<LPVOID*>(&shellLink));
if (SUCCEEDED(hres))
{
const auto description = wxString::Format("Play %s on Cemu", titleName);
const auto args = wxString::Format("-t %016llx", titleId);

shellLink->SetPath(exePath.wstring().c_str());
shellLink->SetDescription(description.wc_str());
shellLink->SetArguments(args.wc_str());
shellLink->SetWorkingDirectory(exePath.parent_path().wstring().c_str());

if (icon_path)
shellLink->SetIconLocation(icon_path->wstring().c_str(), 0);
else
shellLink->SetIconLocation(exePath.wstring().c_str(), 0);

IPersistFile* shellLinkFile;
// save the shortcut
hres = shellLink->QueryInterface(IID_IPersistFile, reinterpret_cast<LPVOID*>(&shellLinkFile));
if (SUCCEEDED(hres))
{
hres = shellLinkFile->Save(outputPath.wc_str(), TRUE);
shellLinkFile->Release();
}
shellLink->Release();
}
}
#endif
#endif
Loading