3356188056
Deprecates not using `polybar-msg` for IPC. Fixes #2532 Closes #2465 Fixes #2504 * Create FIFO specific NamedPipeHandle subclass to PipeHandle * Prototype SocketHandle * Move mainloop up to main.cpp * Pass eventloop to ipc class * Deprecate sending ipc over the named pipe Unfortunately, we can only show the warning in the polybar log and not give the user any feedback because the pipe is one-way * Move eventloop into its own namespace * Prototype ipc socket handling * Remove handles from ipc_client Should be independent from eventloop logic * Remove ipc clients when finished * Add tests for ipc_client decoding * Add callback for complete ipc messages * Remove template param from mixins * Move signal handler to new callback system * Move poll handle to new callback system * Move FSEventHandle to new callback system * Move TimerHandle and AsyncHandle to new callback system * Move PipeHandle to new callback system * Implement socket functionality in new callback system * Correctly reset ipc named pipe handle * Let client close handles in error callback * Wrap client pipe and ipc::client in connection class * Better decoder log messages * Socket path logic * Fix CI warnings * Remove UVHandleGeneric * Fix error when socket folder already exists * Proof of concept message writeback * Restructure ipc files * polybar-msg: Use sockets * polybar-msg: Better syntax for actions * Fix memory leak with fifo After EOF, the pipe wasn't closed and EOF was called all the time, each time allocating a new pipe. * Make polybar-msg compile on its own * Rudimentary writeback for polybar-msg * Fix payload reference going out of scope. * Add IPC documentation * Cleanup polybar-msg code * Specify the v0 ipc message format * Close ipc connection after message * Fix ipc tests * Properly close ipc connections * Fix polybar-msg not working with action string * Write polybar-msg manpage * polybar-msg: Stop using exit() * ipc: Print log message with PID * Add tests for ipc util * polybar-msg: Print PID with success message * ipc: Propagate message errors * Rename ipc::client to ipc::decoder * Rename ipc.cpp to polybar-msg.cpp * ipc: Write encoder function and fix decoder bugs * ipc: Use message format for responses * ipc: Handle wrong message types * ipc: Write back error message if ipc message cannot be processed This only happens for commands and empty actions. Non-empty actions are not immediately executed, but deferred until the next loop iteration. * Remove TODO about deleting runtime directory The socket file is not deleted after socket.close() is called, only after libuv executes the close callback. So we can't just call rmdir because it will probably always fail. * CLeanup WriteRequest * Update manpage authors * Cleanup
412 lines
10 KiB
C++
412 lines
10 KiB
C++
#pragma once
|
|
|
|
#include <uv.h>
|
|
|
|
#include <stdexcept>
|
|
|
|
#include "common.hpp"
|
|
#include "components/logger.hpp"
|
|
#include "utils/mixins.hpp"
|
|
|
|
POLYBAR_NS
|
|
|
|
namespace eventloop {
|
|
/**
|
|
* Runs any libuv function with an integer error code return value and throws an
|
|
* exception on error.
|
|
*/
|
|
#define UV(fun, ...) \
|
|
do { \
|
|
int res = fun(__VA_ARGS__); \
|
|
if (res < 0) { \
|
|
throw std::runtime_error(__FILE__ ":"s + std::to_string(__LINE__) + \
|
|
": libuv error for '" #fun "(" #__VA_ARGS__ ")': "s + uv_strerror(res)); \
|
|
} \
|
|
} while (0);
|
|
|
|
using cb_void = function<void(void)>;
|
|
|
|
template <typename Event>
|
|
using cb_event = std::function<void(const Event&)>;
|
|
|
|
template <typename Self, typename H>
|
|
class Handle : public non_copyable_mixin {
|
|
public:
|
|
Handle(uv_loop_t* l) : uv_loop(l) {
|
|
get()->data = this;
|
|
}
|
|
|
|
Self& leak(std::unique_ptr<Self> h) {
|
|
lifetime_extender = std::move(h);
|
|
return *lifetime_extender;
|
|
}
|
|
|
|
void unleak() {
|
|
lifetime_extender.reset();
|
|
}
|
|
|
|
H* raw() {
|
|
return get();
|
|
}
|
|
|
|
const H* raw() const {
|
|
return get();
|
|
}
|
|
|
|
void close() {
|
|
if (!is_closing()) {
|
|
uv_close((uv_handle_t*)get(), [](uv_handle_t* handle) { close_callback(*static_cast<Self*>(handle->data)); });
|
|
}
|
|
}
|
|
|
|
bool is_closing() const {
|
|
return uv_is_closing(this->template get<uv_handle_t>());
|
|
}
|
|
|
|
bool is_active() {
|
|
return uv_is_active(this->template get<uv_handle_t>()) != 0;
|
|
}
|
|
|
|
protected:
|
|
/**
|
|
* Generic callback function that can be used for all uv handle callbacks.
|
|
*
|
|
* \tparam Event Event class/struct. Must have a constructor that takes all arguments passed to the uv callback,
|
|
* except for the handle argument.
|
|
* \tparam Member Pointer to class member where callback function is stored
|
|
* \tparam Args Additional arguments in the uv callback. Inferred by the compiler
|
|
*/
|
|
template <typename Event, cb_event<Event> Self::*Member, typename... Args>
|
|
static void event_cb(H* handle, Args... args) {
|
|
Self& This = *static_cast<Self*>(handle->data);
|
|
(This.*Member)(Event{std::forward<Args>(args)...});
|
|
}
|
|
|
|
/**
|
|
* Same as event_cb except that no event is constructed.
|
|
*/
|
|
template <cb_void Self::*Member>
|
|
static void void_event_cb(H* handle) {
|
|
Self& This = *static_cast<Self*>(handle->data);
|
|
(This.*Member)();
|
|
}
|
|
|
|
static Self& cast(H* handle) {
|
|
return *static_cast<Self*>(handle->data);
|
|
}
|
|
|
|
template <typename T = H>
|
|
T* get() {
|
|
return reinterpret_cast<T*>(&uv_handle);
|
|
}
|
|
|
|
template <typename T = H>
|
|
const T* get() const {
|
|
return reinterpret_cast<const T*>(&uv_handle);
|
|
}
|
|
|
|
uv_loop_t* loop() const {
|
|
return uv_loop;
|
|
}
|
|
|
|
static void close_callback(Self& self) {
|
|
self.unleak();
|
|
}
|
|
|
|
static void alloc_callback(uv_handle_t*, size_t, uv_buf_t* buf) {
|
|
buf->base = new char[BUFSIZ];
|
|
buf->len = BUFSIZ;
|
|
}
|
|
|
|
private:
|
|
H uv_handle;
|
|
uv_loop_t* uv_loop;
|
|
|
|
/**
|
|
* The handle stores the unique_ptr to itself so that it effectively leaks memory.
|
|
*
|
|
* This saves us from having to guarantee that the handle's lifetime extends to at least after it is closed.
|
|
*
|
|
* Once the handle is closed, either explicitly or by walking all handles when the loop shuts down, this reference
|
|
* is reset and the object is explicitly destroyed.
|
|
*/
|
|
std::unique_ptr<Self> lifetime_extender;
|
|
};
|
|
|
|
struct ErrorEvent {
|
|
int status;
|
|
};
|
|
|
|
using cb_error = cb_event<ErrorEvent>;
|
|
|
|
class WriteRequest : public non_copyable_mixin {
|
|
public:
|
|
using cb_write = cb_void;
|
|
|
|
WriteRequest(cb_write user_cb, cb_error err_cb) : write_callback(user_cb), write_err_cb(err_cb) {
|
|
get()->data = this;
|
|
};
|
|
|
|
static WriteRequest& create(cb_write user_cb, cb_error err_cb) {
|
|
auto r = std::make_unique<WriteRequest>(user_cb, err_cb);
|
|
return r->leak(std::move(r));
|
|
};
|
|
|
|
uv_write_t* get() {
|
|
return &req;
|
|
}
|
|
|
|
/**
|
|
* Trigger the write callback.
|
|
*
|
|
* After that, this object is destroyed.
|
|
*/
|
|
void trigger(int status) {
|
|
if (status < 0) {
|
|
if (write_err_cb) {
|
|
write_err_cb(ErrorEvent{status});
|
|
}
|
|
} else {
|
|
if (write_callback) {
|
|
write_callback();
|
|
}
|
|
}
|
|
|
|
unleak();
|
|
}
|
|
|
|
protected:
|
|
WriteRequest& leak(std::unique_ptr<WriteRequest> h) {
|
|
lifetime_extender = std::move(h);
|
|
return *lifetime_extender;
|
|
}
|
|
|
|
void unleak() {
|
|
lifetime_extender.reset();
|
|
}
|
|
|
|
private:
|
|
uv_write_t req;
|
|
|
|
cb_write write_callback;
|
|
cb_error write_err_cb;
|
|
|
|
/**
|
|
* The handle stores the unique_ptr to itself so that it effectively leaks memory.
|
|
*
|
|
* This means that each instance manages its own lifetime.
|
|
*/
|
|
std::unique_ptr<WriteRequest> lifetime_extender;
|
|
};
|
|
|
|
struct SignalEvent {
|
|
int signum;
|
|
};
|
|
|
|
class SignalHandle : public Handle<SignalHandle, uv_signal_t> {
|
|
public:
|
|
using Handle::Handle;
|
|
using cb = cb_event<SignalEvent>;
|
|
|
|
void init();
|
|
void start(int signum, cb user_cb);
|
|
|
|
private:
|
|
cb callback;
|
|
};
|
|
|
|
struct PollEvent {
|
|
uv_poll_event event;
|
|
};
|
|
|
|
class PollHandle : public Handle<PollHandle, uv_poll_t> {
|
|
public:
|
|
using Handle::Handle;
|
|
using cb = cb_event<PollEvent>;
|
|
|
|
void init(int fd);
|
|
void start(int events, cb user_cb, cb_error err_cb);
|
|
static void poll_callback(uv_poll_t*, int status, int events);
|
|
|
|
private:
|
|
cb callback;
|
|
cb_error err_cb;
|
|
};
|
|
|
|
struct FSEvent {
|
|
const char* path;
|
|
uv_fs_event event;
|
|
};
|
|
|
|
class FSEventHandle : public Handle<FSEventHandle, uv_fs_event_t> {
|
|
public:
|
|
using Handle::Handle;
|
|
using cb = cb_event<FSEvent>;
|
|
|
|
void init();
|
|
void start(const string& path, int flags, cb user_cb, cb_error err_cb);
|
|
static void fs_event_callback(uv_fs_event_t*, const char* path, int events, int status);
|
|
|
|
private:
|
|
cb callback;
|
|
cb_error err_cb;
|
|
};
|
|
|
|
class TimerHandle : public Handle<TimerHandle, uv_timer_t> {
|
|
public:
|
|
using Handle::Handle;
|
|
using cb = cb_void;
|
|
|
|
void init();
|
|
void start(uint64_t timeout, uint64_t repeat, cb user_cb);
|
|
void stop();
|
|
|
|
private:
|
|
cb callback;
|
|
};
|
|
|
|
class AsyncHandle : public Handle<AsyncHandle, uv_async_t> {
|
|
public:
|
|
using Handle::Handle;
|
|
using cb = cb_void;
|
|
|
|
void init(cb user_cb);
|
|
void send();
|
|
|
|
private:
|
|
cb callback;
|
|
};
|
|
|
|
struct ReadEvent {
|
|
const char* data;
|
|
size_t len;
|
|
};
|
|
|
|
template <typename Self, typename H>
|
|
class StreamHandle : public Handle<Self, H> {
|
|
public:
|
|
using Handle<Self, H>::Handle;
|
|
using cb_read = cb_event<ReadEvent>;
|
|
using cb_read_eof = cb_void;
|
|
using cb_connection = cb_void;
|
|
|
|
void listen(int backlog, cb_connection user_cb, cb_error err_cb) {
|
|
this->connection_callback = user_cb;
|
|
this->connection_err_cb = err_cb;
|
|
UV(uv_listen, this->template get<uv_stream_t>(), backlog, connection_cb);
|
|
};
|
|
|
|
static void connection_cb(uv_stream_t* server, int status) {
|
|
auto& self = Self::cast((H*)server);
|
|
|
|
if (status == 0) {
|
|
self.connection_callback();
|
|
} else {
|
|
self.connection_err_cb(ErrorEvent{status});
|
|
}
|
|
}
|
|
|
|
template <typename ClientSelf, typename ClientH>
|
|
void accept(StreamHandle<ClientSelf, ClientH>& client) {
|
|
UV(uv_accept, this->template get<uv_stream_t>(), client.template get<uv_stream_t>());
|
|
}
|
|
|
|
void read_start(cb_read fun, cb_void eof_cb, cb_error err_cb) {
|
|
this->read_callback = fun;
|
|
this->read_eof_cb = eof_cb;
|
|
this->read_err_cb = err_cb;
|
|
UV(uv_read_start, this->template get<uv_stream_t>(), &this->alloc_callback, read_cb);
|
|
};
|
|
|
|
static void read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) {
|
|
auto& self = Self::cast((H*)handle);
|
|
/*
|
|
* Wrap pointer so that it gets automatically freed once the function returns (even with exceptions)
|
|
*/
|
|
auto buf_ptr = unique_ptr<char[]>(buf->base);
|
|
if (nread > 0) {
|
|
self.read_callback(ReadEvent{buf->base, (size_t)nread});
|
|
} else if (nread < 0) {
|
|
if (nread != UV_EOF) {
|
|
self.read_err_cb(ErrorEvent{(int)nread});
|
|
} else {
|
|
self.read_eof_cb();
|
|
}
|
|
}
|
|
};
|
|
|
|
void write(const vector<uint8_t>& data, WriteRequest::cb_write user_cb = {}, cb_error err_cb = {}) {
|
|
WriteRequest& req = WriteRequest::create(user_cb, err_cb);
|
|
|
|
uv_buf_t buf{(char*)data.data(), data.size()};
|
|
|
|
UV(uv_write, req.get(), this->template get<uv_stream_t>(), &buf, 1,
|
|
[](uv_write_t* r, int status) { static_cast<WriteRequest*>(r->data)->trigger(status); });
|
|
}
|
|
|
|
private:
|
|
/**
|
|
* Callback for receiving data
|
|
*/
|
|
cb_read read_callback;
|
|
|
|
/**
|
|
* Callback for receiving EOF.
|
|
*
|
|
* Called after the associated handle has been closed.
|
|
*/
|
|
cb_read_eof read_eof_cb;
|
|
|
|
/**
|
|
* Called if an error occurs.
|
|
*/
|
|
cb_error read_err_cb;
|
|
|
|
cb_connection connection_callback;
|
|
cb_error connection_err_cb;
|
|
};
|
|
|
|
class PipeHandle : public StreamHandle<PipeHandle, uv_pipe_t> {
|
|
public:
|
|
using StreamHandle::StreamHandle;
|
|
using cb_connect = cb_void;
|
|
|
|
void init(bool ipc = false);
|
|
void open(int fd);
|
|
|
|
void bind(const string& path);
|
|
|
|
void connect(const string& name, cb_connect user_cb, cb_error err_cb);
|
|
|
|
private:
|
|
static void connect_cb(uv_connect_t* req, int status);
|
|
|
|
cb_error connect_err_cb;
|
|
cb_connect connect_callback;
|
|
};
|
|
|
|
class eventloop {
|
|
public:
|
|
eventloop();
|
|
~eventloop();
|
|
void run();
|
|
void stop();
|
|
|
|
template <typename H, typename... Args>
|
|
H& handle(Args... args) {
|
|
auto ptr = make_unique<H>(get());
|
|
ptr->init(std::forward<Args>(args)...);
|
|
return ptr->leak(std::move(ptr));
|
|
}
|
|
|
|
protected:
|
|
uv_loop_t* get() const;
|
|
|
|
private:
|
|
std::unique_ptr<uv_loop_t> m_loop{nullptr};
|
|
};
|
|
|
|
} // namespace eventloop
|
|
|
|
POLYBAR_NS_END
|