实现单实例检查机制,优化窗口激活逻辑,添加跨平台支持
This commit is contained in:
@@ -12,11 +12,23 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <fstream>
|
||||||
|
#include <filesystem>
|
||||||
#include "tcp_server.h"
|
#include "tcp_server.h"
|
||||||
#include "steam/steam_networking_manager.h"
|
#include "steam/steam_networking_manager.h"
|
||||||
#include "steam/steam_room_manager.h"
|
#include "steam/steam_room_manager.h"
|
||||||
#include "steam/steam_utils.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;
|
using boost::asio::ip::tcp;
|
||||||
|
|
||||||
// New variables for multiple connections and TCP clients
|
// New variables for multiple connections and TCP clients
|
||||||
@@ -25,8 +37,169 @@ std::mutex connectionsMutex; // Add mutex for connections
|
|||||||
int localPort = 0;
|
int localPort = 0;
|
||||||
std::unique_ptr<TCPServer> server;
|
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()
|
int main()
|
||||||
{
|
{
|
||||||
|
// Check for single instance
|
||||||
|
if (!checkSingleInstance())
|
||||||
|
{
|
||||||
|
std::cout << "另一个实例已在运行,正在激活该窗口..." << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize Steam API first
|
// Initialize Steam API first
|
||||||
if (!SteamAPI_Init())
|
if (!SteamAPI_Init())
|
||||||
{
|
{
|
||||||
@@ -36,9 +209,8 @@ int main()
|
|||||||
|
|
||||||
boost::asio::io_context io_context;
|
boost::asio::io_context io_context;
|
||||||
auto work_guard = boost::asio::make_work_guard(io_context);
|
auto work_guard = boost::asio::make_work_guard(io_context);
|
||||||
std::thread io_thread([&io_context]() {
|
std::thread io_thread([&io_context]()
|
||||||
io_context.run();
|
{ io_context.run(); });
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize Steam Networking Manager
|
// Initialize Steam Networking Manager
|
||||||
SteamNetworkingManager steamManager;
|
SteamNetworkingManager steamManager;
|
||||||
@@ -66,12 +238,16 @@ int main()
|
|||||||
{
|
{
|
||||||
std::cerr << "Failed to create GLFW window" << std::endl;
|
std::cerr << "Failed to create GLFW window" << std::endl;
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
|
cleanupSingleInstance();
|
||||||
SteamAPI_Shutdown();
|
SteamAPI_Shutdown();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
glfwMakeContextCurrent(window);
|
glfwMakeContextCurrent(window);
|
||||||
glfwSwapInterval(1); // Enable vsync
|
glfwSwapInterval(1); // Enable vsync
|
||||||
|
|
||||||
|
// Store window handle for single instance activation
|
||||||
|
storeWindowHandle(window);
|
||||||
|
|
||||||
// Initialize ImGui
|
// Initialize ImGui
|
||||||
IMGUI_CHECKVERSION();
|
IMGUI_CHECKVERSION();
|
||||||
ImGui::CreateContext();
|
ImGui::CreateContext();
|
||||||
@@ -95,6 +271,46 @@ int main()
|
|||||||
char joinBuffer[256] = "";
|
char joinBuffer[256] = "";
|
||||||
char filterBuffer[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
|
// Lambda to render invite friends UI
|
||||||
auto renderInviteFriends = [&]()
|
auto renderInviteFriends = [&]()
|
||||||
{
|
{
|
||||||
@@ -213,6 +429,7 @@ int main()
|
|||||||
{
|
{
|
||||||
std::vector<CSteamID> members = roomManager.getLobbyMembers();
|
std::vector<CSteamID> members = roomManager.getLobbyMembers();
|
||||||
CSteamID mySteamID = SteamUser()->GetSteamID();
|
CSteamID mySteamID = SteamUser()->GetSteamID();
|
||||||
|
CSteamID hostSteamID = steamManager.getHostSteamID();
|
||||||
for (const auto &memberID : members)
|
for (const auto &memberID : members)
|
||||||
{
|
{
|
||||||
ImGui::TableNextRow();
|
ImGui::TableNextRow();
|
||||||
@@ -220,6 +437,7 @@ int main()
|
|||||||
const char *name = SteamFriends()->GetFriendPersonaName(memberID);
|
const char *name = SteamFriends()->GetFriendPersonaName(memberID);
|
||||||
ImGui::Text("%s", name);
|
ImGui::Text("%s", name);
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
if (memberID == mySteamID)
|
if (memberID == mySteamID)
|
||||||
{
|
{
|
||||||
ImGui::Text("-");
|
ImGui::Text("-");
|
||||||
@@ -228,36 +446,16 @@ int main()
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int ping = 0;
|
auto [ping, relayInfo] = getMemberConnectionInfo(memberID, hostSteamID);
|
||||||
std::string relayInfo = "N/A";
|
|
||||||
if (steamManager.isHost())
|
if (relayInfo != "N/A")
|
||||||
{
|
{
|
||||||
// Find connection for this member
|
ImGui::Text("%d", ping);
|
||||||
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
|
else
|
||||||
{
|
{
|
||||||
// Client shows ping to host
|
ImGui::Text("N/A");
|
||||||
ping = steamManager.getHostPing();
|
|
||||||
if (steamManager.getConnection() != k_HSteamNetConnection_Invalid)
|
|
||||||
{
|
|
||||||
relayInfo = steamManager.getConnectionRelayInfo(steamManager.getConnection());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ImGui::Text("%d", ping);
|
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("%s", relayInfo.c_str());
|
ImGui::Text("%s", relayInfo.c_str());
|
||||||
}
|
}
|
||||||
@@ -305,5 +503,8 @@ int main()
|
|||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
steamManager.shutdown();
|
steamManager.shutdown();
|
||||||
|
|
||||||
|
// Cleanup single instance resources
|
||||||
|
cleanupSingleInstance();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user