Skip to content

Commit bcb0611

Browse files
committed
core: use ProcLock for single-instance
1 parent 8009978 commit bcb0611

9 files changed

Lines changed: 90 additions & 28 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pkg_check_modules(
2626
hyprtoolkit
2727
pixman-1
2828
libdrm
29-
hyprutils>=0.12.0
29+
hyprutils>=0.14.1
3030
hyprwire
3131
icu-uc
3232
hyprlang
@@ -90,4 +90,4 @@ target_include_directories(hyprlauncher PRIVATE "./protocols")
9090

9191
target_link_libraries(hyprlauncher PkgConfig::deps)
9292

93-
install(TARGETS hyprlauncher)
93+
install(TARGETS hyprlauncher)

flake.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.cpp

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@
1212
#include "i18n/Engine.hpp"
1313

1414
#include <iostream>
15+
#include <chrono>
16+
#include <cstdlib>
17+
#include <thread>
1518

19+
#include <hyprutils/os/ProcLock.hpp>
1620
#include <hyprutils/string/ConstVarList.hpp>
1721

1822
using namespace Hyprutils::String;
23+
using namespace Hyprutils::OS;
1924

2025
static void printHelp() {
2126
std::cout << "Hyprlauncher usage: hyprlauncher [arg [...]].\n\nArguments:\n"
@@ -45,6 +50,18 @@ static std::vector<std::string> parseExplicitFromStdin() {
4550
return result;
4651
}
4752

53+
static const char* procLockErrorString(CProcLock::eProcLockObtainingError error) {
54+
switch (error) {
55+
case CProcLock::eProcLockObtainingError::ALREADY_TAKEN: return "lock already taken by this process";
56+
case CProcLock::eProcLockObtainingError::NO_ENVIRONMENT: return "XDG_RUNTIME_DIR is unset";
57+
case CProcLock::eProcLockObtainingError::ALREADY_RUNNING: return "another instance is running";
58+
case CProcLock::eProcLockObtainingError::PERMISSIONS_INSUFFICIENT: return "insufficient permissions for runtime lock";
59+
case CProcLock::eProcLockObtainingError::UNKNOWN: return "unknown error";
60+
}
61+
62+
return "unknown error";
63+
}
64+
4865
int main(int argc, char** argv, char** envp) {
4966

5067
bool openByDefault = true, dmenuMode = false, toggle = false;
@@ -89,12 +106,28 @@ int main(int argc, char** argv, char** envp) {
89106
}
90107
}
91108

92-
auto socket = makeShared<CClientIPCSocket>();
93-
94109
if (dmenuMode)
95110
explicitOptions = parseExplicitFromStdin();
96111

97-
if (socket->m_connected) {
112+
const auto WAYLAND_DISPLAY_ENV = getenv("WAYLAND_DISPLAY");
113+
const auto WAYLAND_DISPLAY = WAYLAND_DISPLAY_ENV ? std::string{WAYLAND_DISPLAY_ENV} : std::string{};
114+
115+
CProcLock procLock{"hyprlauncher", {{"WAYLAND_DISPLAY", WAYLAND_DISPLAY}}};
116+
const auto lockResult = procLock.obtain(CProcLock::eProcLockFlags::EXCLUSIVE);
117+
118+
if (!lockResult) {
119+
if (lockResult.error() != CProcLock::eProcLockObtainingError::ALREADY_RUNNING) {
120+
Debug::log(ERR, "Failed to obtain process lock: {}", procLockErrorString(lockResult.error()));
121+
return 1;
122+
}
123+
124+
SP<CClientIPCSocket> socket = makeShared<CClientIPCSocket>(WAYLAND_DISPLAY);
125+
126+
if (!socket->m_connected) {
127+
Debug::log(ERR, "Another instance is running, but its IPC socket is unavailable (probably starting up)");
128+
return 1;
129+
}
130+
98131
Debug::log(TRACE, "Active instance already, opening launcher.");
99132
if (!explicitOptions.empty())
100133
socket->sendOpenWithOptions(explicitOptions);
@@ -103,7 +136,11 @@ int main(int argc, char** argv, char** envp) {
103136
return 0;
104137
}
105138

106-
g_serverIPCSocket = makeUnique<CServerIPCSocket>();
139+
g_serverIPCSocket = makeUnique<CServerIPCSocket>(WAYLAND_DISPLAY);
140+
if (!g_serverIPCSocket->valid()) {
141+
Debug::log(ERR, "Failed to open IPC server socket");
142+
return 1;
143+
}
107144

108145
g_desktopFinder = makeUnique<CDesktopFinder>();
109146
g_unicodeFinder = makeUnique<CUnicodeFinder>();
@@ -117,8 +154,6 @@ int main(int argc, char** argv, char** envp) {
117154
g_ipcFinder->init();
118155
g_fontFinder->init();
119156

120-
socket.reset();
121-
122157
I18n::initEngine();
123158

124159
if (!explicitOptions.empty()) {

src/socket/ClientSocket.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
#include "ClientSocket.hpp"
2+
#include "SocketPath.hpp"
23

3-
#include <cstdlib>
44
#include <print>
55

6-
constexpr const char* SOCKET_NAME = ".hyprlauncher.sock";
76
static SP<CCHyprlauncherCoreImpl> g_coreImpl;
87

9-
CClientIPCSocket::CClientIPCSocket() {
10-
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
11-
12-
if (!RTDIR)
8+
CClientIPCSocket::CClientIPCSocket(const std::string& waylandDisplay) {
9+
const auto socketPath = socketPathForDisplay(waylandDisplay);
10+
if (!socketPath)
1311
return;
1412

15-
m_socketPath = RTDIR + std::string{"/"} + SOCKET_NAME;
13+
m_socketPath = *socketPath;
1614

1715
m_socket = Hyprwire::IClientSocket::open(m_socketPath);
1816

src/socket/ClientSocket.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class CClientIPCSocket {
99
public:
10-
CClientIPCSocket();
10+
CClientIPCSocket(const std::string& waylandDisplay);
1111
~CClientIPCSocket() = default;
1212

1313
bool m_connected = false;

src/socket/ServerSocket.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
#include "ServerSocket.hpp"
2+
#include "SocketPath.hpp"
23
#include "../ui/UI.hpp"
34
#include "../query/QueryProcessor.hpp"
45
#include "../finders/ipc/IPCFinder.hpp"
56

6-
#include <cstdlib>
77
#include <filesystem>
88

9-
constexpr const char* SOCKET_NAME = ".hyprlauncher.sock";
10-
119
static SP<CHyprlauncherCoreImpl> g_coreImpl;
1210

13-
CServerIPCSocket::CServerIPCSocket() {
14-
const auto RTDIR = getenv("XDG_RUNTIME_DIR");
15-
16-
if (!RTDIR)
11+
CServerIPCSocket::CServerIPCSocket(const std::string& waylandDisplay) {
12+
const auto socketPath = socketPathForDisplay(waylandDisplay);
13+
if (!socketPath)
1714
return;
1815

19-
m_socketPath = RTDIR + std::string{"/"} + SOCKET_NAME;
16+
m_socketPath = *socketPath;
2017

2118
std::error_code ec;
2219
std::filesystem::remove(m_socketPath, ec);
@@ -49,6 +46,10 @@ CServerIPCSocket::CServerIPCSocket() {
4946
m_socket->addImplementation(g_coreImpl);
5047
}
5148

49+
bool CServerIPCSocket::valid() const {
50+
return !!m_socket;
51+
}
52+
5253
void CServerIPCSocket::setOpenState(uint32_t state) {
5354
switch (state) {
5455
case 0: g_ui->setWindowOpen(!g_ui->windowOpen()); break;

src/socket/ServerSocket.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77

88
class CServerIPCSocket {
99
public:
10-
CServerIPCSocket();
10+
CServerIPCSocket(const std::string& waylandDisplay);
1111
~CServerIPCSocket() = default;
1212

13+
bool valid() const;
14+
1315
void sendOpenState(bool open);
1416
void sendSelectionMade(const std::string& s);
1517

src/socket/SocketPath.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#include "SocketPath.hpp"
2+
3+
#include <cstdint>
4+
#include <cstdlib>
5+
#include <format>
6+
7+
std::optional<std::string> socketPathForDisplay(std::string_view waylandDisplay) {
8+
const auto runtimeDir = getenv("XDG_RUNTIME_DIR");
9+
if (!runtimeDir || runtimeDir[0] == '\0')
10+
return std::nullopt;
11+
12+
uint64_t displayHash = 14695981039346656037ULL;
13+
for (const unsigned char c : waylandDisplay) {
14+
displayHash ^= c;
15+
displayHash *= 1099511628211ULL;
16+
}
17+
18+
return std::format("{}/.hyprlauncher-{:016x}.sock", runtimeDir, displayHash);
19+
}

src/socket/SocketPath.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
#include <optional>
4+
#include <string>
5+
#include <string_view>
6+
7+
std::optional<std::string> socketPathForDisplay(std::string_view waylandDisplay);

0 commit comments

Comments
 (0)