diff --git a/platform/macosx/love-framework.xcodeproj/project.pbxproj b/platform/macosx/love-framework.xcodeproj/project.pbxproj index 352ad4137..b6ed48d4e 100644 --- a/platform/macosx/love-framework.xcodeproj/project.pbxproj +++ b/platform/macosx/love-framework.xcodeproj/project.pbxproj @@ -272,6 +272,7 @@ FA9B4A0816E1578300074F42 /* SDL2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA9B4A0716E1578300074F42 /* SDL2.framework */; }; FA9FC0B0173D6E3E005027FF /* wrap_Window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FA9FC0AE173D6E3E005027FF /* wrap_Window.cpp */; }; FA9FC0B1173D6E3E005027FF /* wrap_Window.h in Headers */ = {isa = PBXBuildFile; fileRef = FA9FC0AF173D6E3E005027FF /* wrap_Window.h */; }; + FAA627CE18E7E1560080752D /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAA627CD18E7E1560080752D /* CoreServices.framework */; }; FAAC6B02170A373B008A61C5 /* CompressedData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAAC6B00170A373A008A61C5 /* CompressedData.cpp */; }; FAAC6B03170A373B008A61C5 /* CompressedData.h in Headers */ = {isa = PBXBuildFile; fileRef = FAAC6B01170A373A008A61C5 /* CompressedData.h */; }; FAAFF04416CB11C700CCDE45 /* OpenAL-Soft.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FAAFF04316CB11C700CCDE45 /* OpenAL-Soft.framework */; }; @@ -831,6 +832,7 @@ FA9B4A0716E1578300074F42 /* SDL2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SDL2.framework; path = /Library/Frameworks/SDL2.framework; sourceTree = ""; }; FA9FC0AE173D6E3E005027FF /* wrap_Window.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = wrap_Window.cpp; sourceTree = ""; }; FA9FC0AF173D6E3E005027FF /* wrap_Window.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wrap_Window.h; sourceTree = ""; }; + FAA627CD18E7E1560080752D /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; }; FAAC6B00170A373A008A61C5 /* CompressedData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CompressedData.cpp; sourceTree = ""; }; FAAC6B01170A373A008A61C5 /* CompressedData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CompressedData.h; sourceTree = ""; }; FAAFF04316CB11C700CCDE45 /* OpenAL-Soft.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = "OpenAL-Soft.framework"; path = "/Library/Frameworks/OpenAL-Soft.framework"; sourceTree = ""; }; @@ -895,6 +897,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + FAA627CE18E7E1560080752D /* CoreServices.framework in Frameworks */, FA9B4A0816E1578300074F42 /* SDL2.framework in Frameworks */, FAAFF04416CB11C700CCDE45 /* OpenAL-Soft.framework in Frameworks */, FA577AB016C7507900860150 /* Cocoa.framework in Frameworks */, @@ -1830,8 +1833,8 @@ FA577A6616C7199700860150 /* Frameworks */ = { isa = PBXGroup; children = ( - FA9B4A0716E1578300074F42 /* SDL2.framework */, FA577A7916C71A1700860150 /* Cocoa.framework */, + FAA627CD18E7E1560080752D /* CoreServices.framework */, FA577A6716C719D900860150 /* FreeType.framework */, FA577A6916C719DE00860150 /* IL.framework */, FA577A8216C71A5300860150 /* libmodplug.framework */, @@ -1841,6 +1844,7 @@ FAAFF04316CB11C700CCDE45 /* OpenAL-Soft.framework */, FA577A7C16C71A2600860150 /* OpenGL.framework */, FA577A7316C719F900860150 /* physfs.framework */, + FA9B4A0716E1578300074F42 /* SDL2.framework */, FA577A7716C71A0800860150 /* Vorbis.framework */, ); name = Frameworks; diff --git a/src/common/utf8.cpp b/src/common/utf8.cpp old mode 100644 new mode 100755 index fb12997aa..2d593c752 --- a/src/common/utf8.cpp +++ b/src/common/utf8.cpp @@ -45,6 +45,27 @@ std::string to_utf8(LPCWSTR wstr) return ret; } +std::wstring to_widestr(const std::string &str) +{ + if (str.empty()) + return std::wstring(); + + int wide_size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.length(), nullptr, 0); + + if (wide_size == 0) + return std::wstring(); + + std::wstring widestr; + widestr.resize(wide_size); + + int ok = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.length(), &widestr[0], widestr.length()); + + if (!ok) + return std::wstring(); + + return widestr; +} + void replace_char(std::string &str, char find, char replace) { int length = str.length(); diff --git a/src/common/utf8.h b/src/common/utf8.h old mode 100644 new mode 100755 index 9a2c51e9b..cd7867ecc --- a/src/common/utf8.h +++ b/src/common/utf8.h @@ -35,6 +35,13 @@ namespace love **/ std::string to_utf8(LPCWSTR wstr); +/** + * Convert a UTF-8 encoded string to a wide string. + * @param str The UTF-8 string. + * @return A wide string. +**/ +std::wstring to_widestr(const std::string &str); + /** * Replace all occurences of 'find' with 'replace' in a string. * @param str The string to modify. diff --git a/src/modules/system/System.cpp b/src/modules/system/System.cpp old mode 100644 new mode 100755 index 78b3fea7a..0aacda0b7 --- a/src/modules/system/System.cpp +++ b/src/modules/system/System.cpp @@ -18,8 +18,22 @@ * 3. This notice may not be removed or altered from any source distribution. **/ +// LOVE +#include "common/config.h" #include "System.h" +#if defined(LOVE_MACOSX) +#include +#elif defined(LOVE_LINUX) +#include +#include +#elif defined(LOVE_WINDOWS) +#include "common/utf8.h" +#include +#include +#pragma comment(lib, "shell32.lib") +#endif + namespace love { namespace system @@ -27,17 +41,80 @@ namespace system std::string System::getOS() const { -#ifdef LOVE_MACOSX +#if defined(LOVE_MACOSX) return "OS X"; -#elif LOVE_WINDOWS +#elif defined(LOVE_WINDOWS) return "Windows"; -#elif LOVE_LINUX +#elif defined(LOVE_LINUX) return "Linux"; #else return "Unknown"; #endif } +bool System::openURL(const std::string &url) const +{ + bool success = false; + +#if defined(LOVE_MACOSX) + + // We could be lazy and use system("open " + url), but this is safer. + CFURLRef cfurl = CFURLCreateWithBytes(nullptr, + (const UInt8 *) url.c_str(), + url.length(), + kCFStringEncodingUTF8, + nullptr); + + success = LSOpenCFURLRef(cfurl, nullptr) == noErr; + CFRelease(cfurl); + +#elif defined(LOVE_LINUX) + + // Spawn a child process, which we'll replace with xdg-open. + pid_t pid = vfork(); + + if (pid == 0) // Child process. + { + // Replace the child process with xdg-open and pass in the URL. + execlp("xdg-open", "xdg-open", url.c_str(), nullptr); + + // exec will only return if it errored, so we should exit with non-zero. + _exit(1); + } + else if (pid > 0) // Parent process. + { + // Wait for xdg-open to complete (or fail.) + int status = 0; + if (waitpid(pid, &status, 0) == pid) + success = (status == 0); + else + success = false; + } + else + { + // vfork() failed. + success = false; + } + +#elif defined(LOVE_WINDOWS) + + // Unicode-aware WinAPI functions don't accept UTF-8, so we need to convert. + std::wstring wurl = to_widestr(url); + + HINSTANCE result = ShellExecuteW(nullptr, + L"open", + wurl.c_str(), + nullptr, + nullptr, + SW_SHOW); + + success = (int) result > 32; + +#endif + + return success; +} + bool System::getConstant(const char *in, System::PowerState &out) { return powerStates.find(in, out); diff --git a/src/modules/system/System.h b/src/modules/system/System.h index 03fa26332..212a4be04 100644 --- a/src/modules/system/System.h +++ b/src/modules/system/System.h @@ -85,6 +85,16 @@ public: **/ virtual PowerState getPowerInfo(int &seconds, int &percent) const = 0; + /** + * Opens the specified URL with the user's default program to handle that + * particular URL type. + * + * @param url The URL to open. + * + * @return Whether the URL was opened successfully. + **/ + virtual bool openURL(const std::string &url) const; + static bool getConstant(const char *in, PowerState &out); static bool getConstant(PowerState in, const char *&out); diff --git a/src/modules/system/wrap_System.cpp b/src/modules/system/wrap_System.cpp index c17c29c3c..6cea1be76 100644 --- a/src/modules/system/wrap_System.cpp +++ b/src/modules/system/wrap_System.cpp @@ -79,6 +79,13 @@ int w_getPowerInfo(lua_State *L) return 3; } +int w_openURL(lua_State *L) +{ + std::string url = luax_checkstring(L, 1); + luax_pushboolean(L, instance->openURL(url)); + return 1; +} + static const luaL_Reg functions[] = { { "getOS", w_getOS }, @@ -86,6 +93,7 @@ static const luaL_Reg functions[] = { "setClipboardText", w_setClipboardText }, { "getClipboardText", w_getClipboardText }, { "getPowerInfo", w_getPowerInfo }, + { "openURL", w_openURL }, { 0, 0 } }; diff --git a/src/modules/system/wrap_System.h b/src/modules/system/wrap_System.h index 3cb89d424..9d89240a8 100644 --- a/src/modules/system/wrap_System.h +++ b/src/modules/system/wrap_System.h @@ -35,6 +35,7 @@ int w_getProcessorCount(lua_State *L); int w_setClipboardText(lua_State *L); int w_getClipboardText(lua_State *L); int w_getPowerInfo(lua_State *L); +int w_openURL(lua_State *L); extern "C" LOVE_EXPORT int luaopen_love_system(lua_State *L); } // system