diff --git a/Common/System/Request.cpp b/Common/System/Request.cpp index 13e12f1502..9c2a989152 100644 --- a/Common/System/Request.cpp +++ b/Common/System/Request.cpp @@ -1,6 +1,7 @@ #include "Common/System/Request.h" #include "Common/System/System.h" #include "Common/Log.h" +#include "Common/File/Path.h" RequestManager g_requestManager; @@ -80,3 +81,7 @@ void RequestManager::Clear() { pendingResponses_.clear(); callbackMap_.clear(); } + +void System_CreateGameShortcut(const Path &path, const std::string &title) { + g_requestManager.MakeSystemRequest(SystemRequestType::CREATE_GAME_SHORTCUT, nullptr, path.ToString(), title, 0); +} diff --git a/Common/System/Request.h b/Common/System/Request.h index 5c925184a4..58ce9065b8 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -7,6 +7,8 @@ #include "Common/System/System.h" +class Path; + typedef std::function RequestCallback; // Platforms often have to process requests asynchronously, on wildly different threads. @@ -125,3 +127,6 @@ inline void System_NotifyUIState(const std::string &state) { inline void System_SetWindowTitle(const std::string ¶m) { g_requestManager.MakeSystemRequest(SystemRequestType::SET_WINDOW_TITLE, nullptr, param, "", 0); } + +// Non-inline to avoid including Path.h +void System_CreateGameShortcut(const Path &path, const std::string &title); diff --git a/Common/System/System.h b/Common/System/System.h index 229adce957..9dd6182f60 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -68,6 +68,7 @@ enum class SystemRequestType { SET_WINDOW_TITLE, TOGGLE_FULLSCREEN_STATE, GRAPHICS_BACKEND_FAILED_ALERT, + CREATE_GAME_SHORTCUT, NOTIFY_UI_STATE, // Used on Android only. Not a SystemNotification since it takes a parameter. diff --git a/Core/Host.h b/Core/Host.h index 52e5d21152..5a0f5aaea8 100644 --- a/Core/Host.h +++ b/Core/Host.h @@ -30,8 +30,6 @@ public: virtual void UpdateSound() {} // still needed for libretro, will need a proper effort. virtual void ToggleDebugConsoleVisibility() {} - virtual bool CreateDesktopShortcut(std::string argumentPath, std::string title) {return false;} - virtual void NotifyUserMessage(const std::string &message, float duration = 1.0f, u32 color = 0x00FFFFFF, const char *id = nullptr) {} virtual void SendUIMessage(const std::string &message, const std::string &value) {} diff --git a/UI/GameScreen.cpp b/UI/GameScreen.cpp index 0b2781419d..7b7a0b8561 100644 --- a/UI/GameScreen.cpp +++ b/UI/GameScreen.cpp @@ -29,6 +29,7 @@ #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" #include "Common/System/System.h" +#include "Common/System/Request.h" #include "Common/System/NativeApp.h" #include "Core/Host.h" #include "Core/Config.h" @@ -417,7 +418,7 @@ void GameScreen::CallbackDeleteGame(bool yes) { UI::EventReturn GameScreen::OnCreateShortcut(UI::EventParams &e) { std::shared_ptr info = g_gameInfoCache->GetInfo(NULL, gamePath_, 0); if (info) { - host->CreateDesktopShortcut(gamePath_.ToString(), info->GetTitle()); + System_CreateGameShortcut(gamePath_, info->GetTitle()); } return UI::EVENT_DONE; } diff --git a/Windows/W32Util/ShellUtil.cpp b/Windows/W32Util/ShellUtil.cpp index 2111a24674..835ca1930e 100644 --- a/Windows/W32Util/ShellUtil.cpp +++ b/Windows/W32Util/ShellUtil.cpp @@ -216,4 +216,79 @@ namespace W32Util PostMessage(parent_, completeMsg_, 0, 0); } + + +// http://msdn.microsoft.com/en-us/library/aa969393.aspx +HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc) { + HRESULT hres; + IShellLink *psl = nullptr; + hres = CoInitializeEx(NULL, COINIT_MULTITHREADED); + if (FAILED(hres)) + return hres; + + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl); + if (SUCCEEDED(hres) && psl) { + IPersistFile *ppf = nullptr; + + // Set the path to the shortcut target and add the description. + psl->SetPath(lpszPathObj); + psl->SetArguments(lpszArguments); + psl->SetDescription(lpszDesc); + + // Query IShellLink for the IPersistFile interface, used for saving the + // shortcut in persistent storage. + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf); + + if (SUCCEEDED(hres) && ppf) { + // Save the link by calling IPersistFile::Save. + hres = ppf->Save(lpszPathLink, TRUE); + ppf->Release(); + } + psl->Release(); + } + CoUninitialize(); + + return hres; } + +bool CreateDesktopShortcut(const std::string &argumentPath, std::string gameTitle) { + // TODO: not working correctly + return false; + + // Get the desktop folder + // TODO: Not long path safe. + wchar_t *pathbuf = new wchar_t[MAX_PATH + gameTitle.size() + 100]; + SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf); + + // Sanitize the game title for banned characters. + const char bannedChars[] = "<>:\"/\\|?*"; + for (size_t i = 0; i < gameTitle.size(); i++) { + for (char c : bannedChars) { + if (gameTitle[i] == c) { + gameTitle[i] = '_'; + break; + } + } + } + + wcscat(pathbuf, L"\\"); + wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); + + std::wstring moduleFilename; + size_t sz; + do { + moduleFilename.resize(moduleFilename.size() + MAX_PATH); + // On failure, this will return the same value as passed in, but success will always be one lower. + sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size()); + } while (sz >= moduleFilename.size()); + moduleFilename.resize(sz); + + CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(argumentPath).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); + + delete[] pathbuf; + return false; +} + +} // namespace diff --git a/Windows/W32Util/ShellUtil.h b/Windows/W32Util/ShellUtil.h index 8231d8c31c..a2c46a0579 100644 --- a/Windows/W32Util/ShellUtil.h +++ b/Windows/W32Util/ShellUtil.h @@ -51,4 +51,6 @@ namespace W32Util bool result_; std::string filename_; }; + + bool CreateDesktopShortcut(const std::string &argumentPath, std::string gameTitle); } diff --git a/Windows/WindowsHost.cpp b/Windows/WindowsHost.cpp index c69c04acd4..0ed0669869 100644 --- a/Windows/WindowsHost.cpp +++ b/Windows/WindowsHost.cpp @@ -154,80 +154,6 @@ void WindowsInputManager::PollControllers() { HLEPlugins::PluginDataAxis[JOYSTICK_AXIS_MOUSE_REL_Y] = mouseDeltaY_; } -// http://msdn.microsoft.com/en-us/library/aa969393.aspx -HRESULT CreateLink(LPCWSTR lpszPathObj, LPCWSTR lpszArguments, LPCWSTR lpszPathLink, LPCWSTR lpszDesc) { - HRESULT hres; - IShellLink *psl = nullptr; - hres = CoInitializeEx(NULL, COINIT_MULTITHREADED); - if (FAILED(hres)) - return hres; - - // Get a pointer to the IShellLink interface. It is assumed that CoInitialize - // has already been called. - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); - if (SUCCEEDED(hres) && psl) { - IPersistFile *ppf = nullptr; - - // Set the path to the shortcut target and add the description. - psl->SetPath(lpszPathObj); - psl->SetArguments(lpszArguments); - psl->SetDescription(lpszDesc); - - // Query IShellLink for the IPersistFile interface, used for saving the - // shortcut in persistent storage. - hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); - - if (SUCCEEDED(hres) && ppf) { - // Save the link by calling IPersistFile::Save. - hres = ppf->Save(lpszPathLink, TRUE); - ppf->Release(); - } - psl->Release(); - } - CoUninitialize(); - - return hres; -} - -bool WindowsHost::CreateDesktopShortcut(std::string argumentPath, std::string gameTitle) { - // TODO: not working correctly - return false; - - - // Get the desktop folder - // TODO: Not long path safe. - wchar_t *pathbuf = new wchar_t[MAX_PATH + gameTitle.size() + 100]; - SHGetFolderPath(0, CSIDL_DESKTOPDIRECTORY, NULL, SHGFP_TYPE_CURRENT, pathbuf); - - // Sanitize the game title for banned characters. - const char bannedChars[] = "<>:\"/\\|?*"; - for (size_t i = 0; i < gameTitle.size(); i++) { - for (char c : bannedChars) { - if (gameTitle[i] == c) { - gameTitle[i] = '_'; - break; - } - } - } - - wcscat(pathbuf, L"\\"); - wcscat(pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); - - std::wstring moduleFilename; - size_t sz; - do { - moduleFilename.resize(moduleFilename.size() + MAX_PATH); - // On failure, this will return the same value as passed in, but success will always be one lower. - sz = GetModuleFileName(nullptr, &moduleFilename[0], (DWORD)moduleFilename.size()); - } while (sz >= moduleFilename.size()); - moduleFilename.resize(sz); - - CreateLink(moduleFilename.c_str(), ConvertUTF8ToWString(argumentPath).c_str(), pathbuf, ConvertUTF8ToWString(gameTitle).c_str()); - - delete [] pathbuf; - return false; -} - void WindowsHost::ToggleDebugConsoleVisibility() { MainWindow::ToggleDebugConsoleVisibility(); } diff --git a/Windows/WindowsHost.h b/Windows/WindowsHost.h index dae5c330e4..4aac342645 100644 --- a/Windows/WindowsHost.h +++ b/Windows/WindowsHost.h @@ -31,8 +31,6 @@ public: void ToggleDebugConsoleVisibility() override; - bool CreateDesktopShortcut(std::string argumentPath, std::string title) override; - void NotifyUserMessage(const std::string &message, float duration = 1.0f, u32 color = 0x00FFFFFF, const char *id = nullptr) override; private: diff --git a/Windows/main.cpp b/Windows/main.cpp index 305177745b..b16e15985d 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -587,6 +587,9 @@ bool System_MakeRequest(SystemRequestType type, int requestId, const std::string MessageBox(MainWindow::GetHWND(), full_error.c_str(), title.c_str(), MB_OK); return true; } + case SystemRequestType::CREATE_GAME_SHORTCUT: + // This is not actually working, but ported it to the request framework anyway. + return W32Util::CreateDesktopShortcut(param1, param2); default: return false; }