diff --git a/online_game_tool.cpp b/online_game_tool.cpp index 3a3725f..8ee9604 100644 --- a/online_game_tool.cpp +++ b/online_game_tool.cpp @@ -12,11 +12,23 @@ #include #include #include +#include +#include #include "tcp_server.h" #include "steam/steam_networking_manager.h" #include "steam/steam_room_manager.h" #include "steam/steam_utils.h" +#ifdef _WIN32 +#include +#define GLFW_EXPOSE_NATIVE_WIN32 +#include +#else +#include +#include +#include +#endif + using boost::asio::ip::tcp; // New variables for multiple connections and TCP clients @@ -25,8 +37,169 @@ std::mutex connectionsMutex; // Add mutex for connections int localPort = 0; std::unique_ptr server; +#ifdef _WIN32 +// Windows implementation using mutex and shared memory +HANDLE g_hMutex = nullptr; +HANDLE g_hMapFile = nullptr; +HWND* g_pSharedHwnd = nullptr; + +bool checkSingleInstance() +{ + g_hMutex = CreateMutexW(nullptr, FALSE, L"Global\\OnlineGameTool_SingleInstance_Mutex"); + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + // Another instance exists, try to find and activate it + g_hMapFile = OpenFileMappingW(FILE_MAP_READ, FALSE, L"Global\\OnlineGameTool_HWND_Share"); + if (g_hMapFile != nullptr) + { + HWND* pHwnd = (HWND*)MapViewOfFile(g_hMapFile, FILE_MAP_READ, 0, 0, sizeof(HWND)); + if (pHwnd != nullptr && *pHwnd != nullptr && IsWindow(*pHwnd)) + { + // Restore and bring to front + if (IsIconic(*pHwnd)) + { + ShowWindow(*pHwnd, SW_RESTORE); + } + SetForegroundWindow(*pHwnd); + UnmapViewOfFile(pHwnd); + } + CloseHandle(g_hMapFile); + } + if (g_hMutex) + { + CloseHandle(g_hMutex); + } + return false; + } + + // Create shared memory for HWND + g_hMapFile = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, sizeof(HWND), L"Global\\OnlineGameTool_HWND_Share"); + if (g_hMapFile != nullptr) + { + g_pSharedHwnd = (HWND*)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(HWND)); + } + return true; +} + +void storeWindowHandle(GLFWwindow* window) +{ + if (g_pSharedHwnd != nullptr) + { + *g_pSharedHwnd = glfwGetWin32Window(window); + } +} + +void cleanupSingleInstance() +{ + if (g_pSharedHwnd != nullptr) + { + UnmapViewOfFile(g_pSharedHwnd); + g_pSharedHwnd = nullptr; + } + if (g_hMapFile != nullptr) + { + CloseHandle(g_hMapFile); + g_hMapFile = nullptr; + } + if (g_hMutex != nullptr) + { + CloseHandle(g_hMutex); + g_hMutex = nullptr; + } +} + +#else +// Unix/Linux/macOS implementation using file lock and signal +int g_lockfd = -1; +std::string g_lockFilePath; + +void signalHandler(int signum) +{ + // Signal received to bring window to front + std::cout << "Received signal to activate window" << std::endl; +} + +bool checkSingleInstance() +{ + std::string tempDir; +#ifdef __APPLE__ + const char* tmpdir = getenv("TMPDIR"); + tempDir = tmpdir ? tmpdir : "/tmp"; +#else + tempDir = "/tmp"; +#endif + + g_lockFilePath = tempDir + "/OnlineGameTool.lock"; + + g_lockfd = open(g_lockFilePath.c_str(), O_CREAT | O_RDWR, 0666); + if (g_lockfd < 0) + { + std::cerr << "Failed to open lock file" << std::endl; + return false; + } + + // Try to acquire exclusive lock + if (flock(g_lockfd, LOCK_EX | LOCK_NB) != 0) + { + // Lock failed, another instance is running + // Read PID and send signal + char pidBuf[32]; + ssize_t bytesRead = read(g_lockfd, pidBuf, sizeof(pidBuf) - 1); + if (bytesRead > 0) + { + pidBuf[bytesRead] = '\0'; + pid_t existingPid = atoi(pidBuf); + if (existingPid > 0) + { + // Send SIGUSR1 to existing instance + kill(existingPid, SIGUSR1); + } + } + close(g_lockfd); + g_lockfd = -1; + return false; + } + + // Write our PID to the lock file + ftruncate(g_lockfd, 0); + pid_t myPid = getpid(); + std::string pidStr = std::to_string(myPid); + write(g_lockfd, pidStr.c_str(), pidStr.length()); + + // Set up signal handler + signal(SIGUSR1, signalHandler); + + return true; +} + +void storeWindowHandle(GLFWwindow* window) +{ + // GLFW doesn't provide a standard way to bring window to front on Unix + // but we can request attention + glfwRequestWindowAttention(window); +} + +void cleanupSingleInstance() +{ + if (g_lockfd >= 0) + { + flock(g_lockfd, LOCK_UN); + close(g_lockfd); + g_lockfd = -1; + unlink(g_lockFilePath.c_str()); + } +} +#endif + int main() { + // Check for single instance + if (!checkSingleInstance()) + { + std::cout << "另一个实例已在运行,正在激活该窗口..." << std::endl; + return 0; + } + // Initialize Steam API first if (!SteamAPI_Init()) { @@ -36,9 +209,8 @@ int main() boost::asio::io_context io_context; auto work_guard = boost::asio::make_work_guard(io_context); - std::thread io_thread([&io_context]() { - io_context.run(); - }); + std::thread io_thread([&io_context]() + { io_context.run(); }); // Initialize Steam Networking Manager SteamNetworkingManager steamManager; @@ -66,12 +238,16 @@ int main() { std::cerr << "Failed to create GLFW window" << std::endl; glfwTerminate(); + cleanupSingleInstance(); SteamAPI_Shutdown(); return -1; } glfwMakeContextCurrent(window); glfwSwapInterval(1); // Enable vsync + // Store window handle for single instance activation + storeWindowHandle(window); + // Initialize ImGui IMGUI_CHECKVERSION(); ImGui::CreateContext(); @@ -95,6 +271,46 @@ int main() char joinBuffer[256] = ""; char filterBuffer[256] = ""; + // Lambda to get connection info for a member + auto getMemberConnectionInfo = [&](const CSteamID &memberID, const CSteamID &hostSteamID) -> std::pair + { + int ping = 0; + std::string relayInfo = "N/A"; + + if (steamManager.isHost()) + { + // Find connection for this member + std::lock_guard lockConn(connectionsMutex); + for (const auto &conn : steamManager.getConnections()) + { + SteamNetConnectionInfo_t info; + if (steamManager.getInterface()->GetConnectionInfo(conn, &info)) + { + if (info.m_identityRemote.GetSteamID() == memberID) + { + ping = steamManager.getConnectionPing(conn); + relayInfo = steamManager.getConnectionRelayInfo(conn); + break; + } + } + } + } + else + { + // Client only shows ping to host, not to other clients + if (memberID == hostSteamID) + { + ping = steamManager.getHostPing(); + if (steamManager.getConnection() != k_HSteamNetConnection_Invalid) + { + relayInfo = steamManager.getConnectionRelayInfo(steamManager.getConnection()); + } + } + } + + return {ping, relayInfo}; + }; + // Lambda to render invite friends UI auto renderInviteFriends = [&]() { @@ -213,6 +429,7 @@ int main() { std::vector members = roomManager.getLobbyMembers(); CSteamID mySteamID = SteamUser()->GetSteamID(); + CSteamID hostSteamID = steamManager.getHostSteamID(); for (const auto &memberID : members) { ImGui::TableNextRow(); @@ -220,6 +437,7 @@ int main() const char *name = SteamFriends()->GetFriendPersonaName(memberID); ImGui::Text("%s", name); ImGui::TableNextColumn(); + if (memberID == mySteamID) { ImGui::Text("-"); @@ -228,36 +446,16 @@ int main() } else { - int ping = 0; - std::string relayInfo = "N/A"; - if (steamManager.isHost()) + auto [ping, relayInfo] = getMemberConnectionInfo(memberID, hostSteamID); + + if (relayInfo != "N/A") { - // Find connection for this member - std::lock_guard lockConn(connectionsMutex); - for (const auto &conn : steamManager.getConnections()) - { - SteamNetConnectionInfo_t info; - if (steamManager.getInterface()->GetConnectionInfo(conn, &info)) - { - if (info.m_identityRemote.GetSteamID() == memberID) - { - ping = steamManager.getConnectionPing(conn); - relayInfo = steamManager.getConnectionRelayInfo(conn); - break; - } - } - } + ImGui::Text("%d", ping); } else { - // Client shows ping to host - ping = steamManager.getHostPing(); - if (steamManager.getConnection() != k_HSteamNetConnection_Invalid) - { - relayInfo = steamManager.getConnectionRelayInfo(steamManager.getConnection()); - } + ImGui::Text("N/A"); } - ImGui::Text("%d", ping); ImGui::TableNextColumn(); ImGui::Text("%s", relayInfo.c_str()); } @@ -289,7 +487,7 @@ int main() { server->stop(); } - + // Stop io_context and join thread work_guard.reset(); io_context.stop(); @@ -297,7 +495,7 @@ int main() { io_thread.join(); } - + ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); @@ -305,5 +503,8 @@ int main() glfwTerminate(); steamManager.shutdown(); + // Cleanup single instance resources + cleanupSingleInstance(); + return 0; } \ No newline at end of file