实现单实例检查机制,优化窗口激活逻辑,添加跨平台支持

This commit is contained in:
Ayndpa
2025-11-19 22:14:15 +08:00
parent f045632a78
commit c08a7753f3

View File

@@ -12,11 +12,23 @@
#include <cstring>
#include <boost/asio.hpp>
#include <memory>
#include <fstream>
#include <filesystem>
#include "tcp_server.h"
#include "steam/steam_networking_manager.h"
#include "steam/steam_room_manager.h"
#include "steam/steam_utils.h"
#ifdef _WIN32
#include <windows.h>
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#else
#include <sys/file.h>
#include <unistd.h>
#include <signal.h>
#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<TCPServer> 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, std::string>
{
int ping = 0;
std::string relayInfo = "N/A";
if (steamManager.isHost())
{
// Find connection for this member
std::lock_guard<std::mutex> 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<CSteamID> 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<std::mutex> 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());
}
@@ -305,5 +503,8 @@ int main()
glfwTerminate();
steamManager.shutdown();
// Cleanup single instance resources
cleanupSingleInstance();
return 0;
}