diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index 56bea1c7fe..2bb2f32b72 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -20,6 +20,12 @@ #include #endif +#ifdef __APPLE__ +#include +#include +#include +#endif + LOG_CHANNEL(sys_log, "SYS"); namespace rpcs3::utils @@ -115,7 +121,20 @@ namespace rpcs3::utils const usz last = path_to_exe.find_last_of('\\'); return last == std::string::npos ? std::string("") : path_to_exe.substr(0, last + 1); } -#elif !defined(__APPLE__) +#elif defined(__APPLE__) + std::string get_app_bundle_path() + { + char bin_path[PATH_MAX]; + uint32_t bin_path_size = sizeof(bin_path); + if (_NSGetExecutablePath(bin_path, &bin_path_size) != 0) + { + sys_log.error("Failed to find app binary path"); + return {}; + } + + return std::filesystem::path(bin_path).parent_path().parent_path().parent_path(); + } +#else std::string get_executable_path() { if (const char* appimage_path = ::getenv("APPIMAGE")) diff --git a/rpcs3/Emu/system_utils.hpp b/rpcs3/Emu/system_utils.hpp index 525cd959f9..a30dff5761 100644 --- a/rpcs3/Emu/system_utils.hpp +++ b/rpcs3/Emu/system_utils.hpp @@ -15,7 +15,9 @@ namespace rpcs3::utils #ifdef _WIN32 std::string get_exe_dir(); -#elif !defined(__APPLE__) +#elif defined(__APPLE__) + std::string get_app_bundle_path(); +#else std::string get_executable_path(); #endif diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index b9264adbdd..76bde1ab71 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -981,8 +981,9 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) : tr("&Create Custom Gamepad Configuration")); QAction* configure_patches = menu.addAction(tr("&Manage Game Patches")); QAction* create_ppu_cache = menu.addAction(tr("&Create PPU Cache")); -#ifndef __APPLE__ + menu.addSeparator(); + const auto on_shortcut = [this, gameinfo](bool is_desktop_shortcut) { const std::string target_cli_args = fmt::format("--no-gui \"%s\"", gameinfo->info.path); @@ -1004,12 +1005,15 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) connect(create_desktop_shortcut, &QAction::triggered, this, [this, gameinfo, on_shortcut](){ on_shortcut(true); }); #ifdef _WIN32 QAction* create_start_menu_shortcut = shortcut_menu->addAction(tr("&Create Start Menu Shortcut")); +#elif defined(__APPLE__) + QAction* create_start_menu_shortcut = shortcut_menu->addAction(tr("&Create Launchpad Shortcut")); #else QAction* create_start_menu_shortcut = shortcut_menu->addAction(tr("&Create Application Menu Shortcut")); #endif connect(create_start_menu_shortcut, &QAction::triggered, this, [this, gameinfo, on_shortcut](){ on_shortcut(false); }); -#endif + menu.addSeparator(); + QAction* rename_title = menu.addAction(tr("&Rename In Game List")); QAction* hide_serial = menu.addAction(tr("&Hide From Game List")); hide_serial->setCheckable(true); diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index 3037507491..92ee312eb9 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -14,7 +14,7 @@ #include #include #include -#elif !defined(__APPLE__) +#else #include #include #endif @@ -210,7 +210,112 @@ namespace gui::utils return cleanup(true, {}); -#elif !defined(__APPLE__) +#elif defined(__APPLE__) + + const std::string app_bundle_path = rpcs3::utils::get_app_bundle_path(); + if (app_bundle_path.empty()) + { + sys_log.error("Failed to create shortcut. App bundle path empty."); + return false; + } + + std::string link_path; + + if (const char* home = ::getenv("HOME")) + { + if (is_desktop_shortcut) + { + link_path = fmt::format("%s/Desktop/%s.app", home, simple_name); + } + else + { + link_path = fmt::format("%s/Applications/RPCS3/%s.app", home, simple_name); + } + } + else + { + sys_log.error("Failed to create shortcut. home path empty."); + return false; + } + + const std::string contents_dir = link_path + "/Contents/"; + const std::string macos_dir = contents_dir + "/MacOS/"; + const std::string resources_dir = contents_dir + "/Resources/"; + + if (!fs::create_path(contents_dir) || !fs::create_path(macos_dir) || !fs::create_path(resources_dir)) + { + sys_log.error("Failed to create shortcut. Could not create app bundle structure."); + return false; + } + + const std::string plist_path = contents_dir + "Info.plist"; + const std::string launcher_path = macos_dir + "launcher"; + + std::string launcher_content; + fmt::append(launcher_content, "#!/bin/bash\nopen \"%s\" --args %s", app_bundle_path, target_cli_args); + + fs::file launcher_file(launcher_path, fs::read + fs::rewrite); + if (!launcher_file) + { + sys_log.error("Failed to create launcher file: %s", launcher_path); + return false; + } + if (launcher_file.write(launcher_content.data(), launcher_content.size()) != launcher_content.size()) + { + sys_log.error("Failed to write launcher file: %s", launcher_path); + return false; + } + launcher_file.close(); + + if (chmod(launcher_path.c_str(), S_IRWXU) != 0) + { + sys_log.error("Failed to change file permissions for launcher file: %s (%d)", strerror(errno), errno); + return false; + } + + const std::string plist_content = "\n" + "\n" + "\n" + "\n" + "\tCFBundleExecutable\n" + "\tlauncher\n" + "\tCFBundleIconFile\n" + "\tshortcut.icns\n" + "\tCFBundleInfoDictionaryVersion\n" + "\t1.0\n" + "\tCFBundlePackageType\n" + "\tAPPL\n" + "\tCFBundleSignature\n" + "\t\?\?\?\?\n" + "\n" + "\n"; + + fs::file plist_file(plist_path, fs::read + fs::rewrite); + if (!plist_file) + { + sys_log.error("Failed to create plist file: %s", plist_path); + return false; + } + if (plist_file.write(plist_content.data(), plist_content.size()) != plist_content.size()) + { + sys_log.error("Failed to write plist file: %s", plist_path); + return false; + } + plist_file.close(); + + if (!src_icon_path.empty()) + { + std::string target_icon_path = resources_dir; + if (!create_square_shortcut_icon_file(src_icon_path, resources_dir, target_icon_path, "icns", 512)) + { + // Error is logged in create_square_shortcut_icon_file + return false; + } + } + + return true; + +#else const std::string exe_path = rpcs3::utils::get_executable_path(); if (exe_path.empty()) @@ -288,9 +393,6 @@ namespace gui::utils } return true; -#else - sys_log.error("Cannot create shortcuts on this operating system"); - return false; #endif } }