Skip to content

Commit 05d9d7d

Browse files
committed
Internal IPC for dispatch
1 parent 1581bfc commit 05d9d7d

13 files changed

Lines changed: 379 additions & 65 deletions

File tree

.clang-format

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
BasedOnStyle: Google
1+
BasedOnStyle: LLVM
22
IndentWidth: 4
33
TabWidth: 4
44
UseTab: Never

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/cppzmq"]
2+
path = lib/cppzmq
3+
url = https://github.com/zeromq/cppzmq.git

CMakeLists.txt

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ project(wss)
33

44
set(CMAKE_CXX_STANDARD 23)
55
set(CMAKE_AUTOMOC ON)
6+
set(CPPZMQ_BUILD_TESTS OFF CACHE INTERNAL "")
67
set(JSON_BuildTests OFF CACHE INTERNAL "")
78

89
find_package(sdbus-c++ REQUIRED)
@@ -12,6 +13,7 @@ find_package(Qt6 REQUIRED COMPONENTS Widgets WebEngineWidgets)
1213
add_subdirectory(lib/spdlog)
1314
add_subdirectory(lib/tomlplusplus)
1415
add_subdirectory(lib/nlohmann_json)
16+
add_subdirectory(lib/cppzmq)
1517

1618
include_directories(/usr/include/gtk4-layer-shell)
1719
link_directories(/usr/lib)
@@ -20,30 +22,26 @@ include_directories(src)
2022

2123
add_executable(${PROJECT_NAME}
2224
src/main.cpp
23-
src/pch.h
2425
src/shell.cpp
25-
src/shell.h
2626
src/widget.cpp
27-
src/widget.h
2827
src/ipc.cpp
29-
src/ipc.h
3028
src/modules/notifd.cpp
31-
src/modules/notifd.h
32-
src/util/dbus_vreader.h
3329
src/modules/appd.cpp
34-
src/modules/appd.h
35-
src/config.h
30+
src/dispatch/dispatcher.cpp
31+
src/dispatch/dispatcher.h
3632
)
3733

3834
target_precompile_headers(${PROJECT_NAME} PRIVATE src/pch.h)
3935

4036
target_include_directories(${PROJECT_NAME} PRIVATE
37+
{LIBZMQ_INCLUDE_DIRS}
4138
/usr/include/LayerShellQt
4239
/usr/include/uWebSockets
4340
lib/cli11
4441
lib/spdlog/include
4542
lib/tomlplusplus/include
4643
lib/nlohmann_json/include
44+
lib/cppzmq
4745
)
4846

4947
target_link_libraries(${PROJECT_NAME} PRIVATE
@@ -56,7 +54,8 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
5654
Qt6::WebEngineWidgets
5755
LayerShellQt::Interface
5856
${NLOHMANN_JSON_TARGET_NAME}
59-
57+
${CPPZMQ_LIBRARIES}
58+
zmq
6059
)
6160

6261
set_target_properties(${PROJECT_NAME} PROPERTIES

config/default.toml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ click_regions = []
4949
hidden = false
5050
__QT_auto_click_region_padding = 20
5151

52-
#[widgets.debug]
53-
#route = "_DEBUG"
54-
#width = "600"
55-
#height = "600"
56-
#layer = "top"
57-
#anchor = ["left", "top"]
58-
#monitors = [0]
59-
#exclusivity = false
60-
#click_regions = [
61-
# { name = "area", x = "0", y = "0", width = "600", height = "600" }
62-
#]
63-
#hidden = false
52+
[widgets.debug]
53+
route = "_DEBUG"
54+
width = "600"
55+
height = "600"
56+
layer = "top"
57+
anchor = ["left", "top"]
58+
monitors = [0]
59+
exclusivity = false
60+
click_regions = [
61+
{ name = "area", x = "0", y = "0", width = "600", height = "600" }
62+
]
63+
hidden = true

lib/cppzmq

Submodule cppzmq added at 3bcbd9d

src/dispatch/dispatcher.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#include "dispatcher.h"
2+
3+
void WSS::Dispatcher::InitCommands(CLI::App& app) {
4+
const auto dispatch = app.add_subcommand("dispatch", "Run the WSS dispatch server");
5+
6+
const auto show = dispatch->add_subcommand("show", "Show a widget");
7+
show->add_option("widget", "The name of the widget to show")->required();
8+
show->add_option("monitor", "The monitor ID to show the widget on")->default_val(0);
9+
show->callback([this, show]() {
10+
std::string widgetName = show->get_option("widget")->as<std::string>();
11+
int monitorId = show->get_option("monitor")->as<int>();
12+
13+
json payload = {{"widgetName", widgetName}, {"monitorId", monitorId}, {"visible", true}};
14+
15+
m_ZMQReq.Request("widget-set-visible", payload);
16+
});
17+
18+
const auto hide = dispatch->add_subcommand("hide", "Hide a widget");
19+
hide->add_option("widget", "The name of the widget to hide")->required();
20+
hide->add_option("monitor", "The monitor ID to show the widget on")->default_val(0);
21+
hide->callback([this, hide]() {
22+
std::string widgetName = hide->get_option("widget")->as<std::string>();
23+
int monitorId = hide->get_option("monitor")->as<int>();
24+
25+
json payload = {{"widgetName", widgetName}, {"monitorId", monitorId}, {"visible", false}};
26+
27+
m_ZMQReq.Request("widget-set-visible", payload);
28+
});
29+
30+
const auto toggle = dispatch->add_subcommand("toggle", "Toggle visibility of a widget");
31+
toggle->add_option("widget", "The name of the widget to hide")->required();
32+
toggle->add_option("monitor", "The monitor ID to show the widget on")->default_val(0);
33+
toggle->callback([this, toggle]() {
34+
std::string widgetName = toggle->get_option("widget")->as<std::string>();
35+
int monitorId = toggle->get_option("monitor")->as<int>();
36+
37+
json payload = {{"widgetName", widgetName}, {"monitorId", monitorId}};
38+
39+
m_ZMQReq.Request("widget-toggle-visible", payload);
40+
});
41+
}

src/dispatch/dispatcher.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#ifndef DISPATCH_H
2+
#define DISPATCH_H
3+
#include "CLI/CLI11.hpp"
4+
#include "zmq_req.h"
5+
6+
namespace WSS {
7+
class Dispatcher {
8+
private:
9+
ZMQReq m_ZMQReq;
10+
11+
public:
12+
Dispatcher() = default;
13+
~Dispatcher() = default;
14+
15+
void InitCommands(CLI::App& app);
16+
};
17+
} // namespace WSS
18+
19+
#endif // DISPATCH_H

src/dispatch/zmq_rep.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#ifndef ZMQ_REP_H
2+
#define ZMQ_REP_H
3+
4+
#include <functional>
5+
#include <pch.h>
6+
#include <string>
7+
#include <thread>
8+
#include <zmq.hpp>
9+
10+
namespace WSS {
11+
12+
class ZMQRep {
13+
zmq::context_t m_Context;
14+
zmq::socket_t m_Socket;
15+
16+
std::atomic_bool m_Running{false};
17+
std::thread m_Thread;
18+
19+
std::unordered_map<std::string, std::function<void(const json&)>> m_Listeners;
20+
std::mutex m_ListenersMutex;
21+
22+
public:
23+
ZMQRep() = default;
24+
~ZMQRep();
25+
26+
void RunAsync();
27+
void Listen(const std::string& type, std::function<void(const json&)> listener);
28+
};
29+
30+
inline ZMQRep::~ZMQRep() {
31+
if (m_Running) {
32+
m_Running = false;
33+
if (m_Thread.joinable()) {
34+
m_Thread.join();
35+
}
36+
}
37+
38+
if (m_Socket.handle() != nullptr)
39+
m_Socket.close();
40+
if (m_Context.handle() != nullptr)
41+
m_Context.close();
42+
}
43+
44+
inline void ZMQRep::RunAsync() {
45+
m_Socket = zmq::socket_t(m_Context, zmq::socket_type::rep);
46+
m_Socket.set(zmq::sockopt::linger, 0); // Set linger to 0 to avoid blocking on close
47+
m_Thread = std::thread([this]() {
48+
try {
49+
m_Running = true;
50+
m_Socket.bind("ipc:///tmp/wss_ipc");
51+
52+
while (m_Running) {
53+
zmq::message_t request;
54+
auto result = m_Socket.recv(request, zmq::recv_flags::none);
55+
if (!result) {
56+
fprintf(stderr, "WSS-ZMQRep recv failed: %s\n", zmq_strerror(zmq_errno()));
57+
continue;
58+
}
59+
60+
std::string message(static_cast<char*>(request.data()), request.size());
61+
WSS_DEBUG("[WSS-ZMQ] Received message: {}", message);
62+
json response;
63+
{
64+
json msg = json::parse(message, nullptr, false);
65+
66+
std::lock_guard lock(m_ListenersMutex);
67+
auto it = m_Listeners.find(msg["type"]);
68+
if (it != m_Listeners.end()) {
69+
try {
70+
it->second(msg["payload"]);
71+
response = {{"status", "success"}, {"message", "Listener executed successfully"}};
72+
} catch (const std::exception& e) {
73+
response = {{"error", "Listener execution failed"}, {"details", e.what()}};
74+
}
75+
} else {
76+
response = {{"error", "No listener for this message type"}};
77+
}
78+
}
79+
80+
std::string responseStr = response.dump();
81+
zmq::message_t zmqResponse(responseStr.size());
82+
memcpy(zmqResponse.data(), responseStr.data(), responseStr.size());
83+
if (!m_Socket.send(zmqResponse, zmq::send_flags::none))
84+
fprintf(stderr, "WSS-ZMQRep send failed: %s\n", zmq_strerror(zmq_errno()));
85+
WSS_DEBUG("[WSS-ZMQ] Sent response: {}", responseStr);
86+
}
87+
88+
} catch (const std::exception& e) {
89+
WSS_ERROR("[WSS-ZMQ] Exception in ZMQRep thread: {}", e.what());
90+
} catch (...) {
91+
WSS_ERROR("[WSS-ZMQ] Unknown exception in ZMQRep thread.");
92+
}
93+
});
94+
}
95+
96+
inline void ZMQRep::Listen(const std::string& type, std::function<void(const json&)> listener) {
97+
std::lock_guard lock(m_ListenersMutex);
98+
if (m_Listeners.find(type) != m_Listeners.end()) {
99+
WSS_WARN("[WSS-ZMQ] Listener for type '{}' already exists, replacing it.", type);
100+
}
101+
m_Listeners[type] = std::move(listener);
102+
WSS_DEBUG("[WSS-ZMQ] Registered listener for type '{}'", type);
103+
}
104+
} // namespace WSS
105+
106+
#endif // ZMQ_REP_H

src/dispatch/zmq_req.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#ifndef ZMQ_REQ_H
2+
#define ZMQ_REQ_H
3+
4+
#include <pch.h>
5+
#include <stdexcept>
6+
#include <string>
7+
#include <zmq.hpp>
8+
9+
namespace WSS {
10+
11+
class ZMQReq {
12+
zmq::context_t m_Context;
13+
zmq::socket_t m_Socket;
14+
15+
public:
16+
ZMQReq();
17+
~ZMQReq();
18+
19+
json Request(const std::string& message, const json request, int timeout_ms = 1000);
20+
};
21+
22+
inline ZMQReq::ZMQReq() : m_Context(1), m_Socket(m_Context, zmq::socket_type::req) {
23+
m_Socket.connect("ipc:///tmp/wss_ipc");
24+
25+
// Optional: Set socket receive timeout
26+
m_Socket.set(zmq::sockopt::rcvtimeo, 1000); // default timeout 1000ms
27+
}
28+
29+
inline ZMQReq::~ZMQReq() {
30+
if (m_Socket.handle() != nullptr)
31+
m_Socket.close();
32+
if (m_Context.handle() != nullptr)
33+
m_Context.close();
34+
}
35+
36+
inline json ZMQReq::Request(const std::string& message, const json request, int timeout_ms) {
37+
try {
38+
json req;
39+
req["type"] = message;
40+
req["payload"] = request;
41+
42+
const std::string reqStr = req.dump();
43+
zmq::message_t zmqReq(reqStr.size());
44+
memcpy(zmqReq.data(), reqStr.data(), reqStr.size());
45+
m_Socket.send(zmqReq, zmq::send_flags::none);
46+
47+
zmq::message_t zmq_resp;
48+
if (!m_Socket.recv(zmq_resp, zmq::recv_flags::none))
49+
throw std::runtime_error("ZMQ receive timeout or error");
50+
std::string resp_str(static_cast<char*>(zmq_resp.data()), zmq_resp.size());
51+
52+
return json::parse(resp_str);
53+
} catch (const std::exception& e) {
54+
throw std::runtime_error("[WSS-ZMQ] Request failed: " + std::string(e.what()));
55+
}
56+
}
57+
58+
} // namespace WSS
59+
60+
#endif // ZMQ_REQ_H

src/main.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <CLI/CLI11.hpp>
55
#include <iostream>
66

7+
#include "dispatch/dispatcher.h"
78
#include "shell.h"
89

910
int LaunchApplication(const std::string& configPath) {
@@ -70,6 +71,9 @@ int main(int argc, char* argv[]) {
7071
exit(LaunchApplication(customConfigPath));
7172
});
7273

74+
WSS::Dispatcher dispatcher;
75+
dispatcher.InitCommands(app);
76+
7377
CLI11_PARSE(app, argc, argv);
7478
spdlog::shutdown();
7579
return 0;

0 commit comments

Comments
 (0)