1d4e30c4be
* Use m_connection.poll_for_event * fix: Handle X events before polling for IO Polling the XCB file descriptor for X events doesn't detect events that are already in XCB's event queue but not yet handled. If this happens, the eventloop polls for IO and the queued events wait until another event arrives, causing some desyncs in the bar. This can easily happen if something (e.g. a click event) triggers some xcb calls, which as a consequence buffer some incoming events. We "fix" this by adding a libuv prepare handle (which runs right before polling for IO) that processes pending X events.
431 lines
11 KiB
C++
431 lines
11 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 non_movable_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();
|
|
}
|
|
|
|
/**
|
|
* Close this handle and free associated memory.
|
|
*
|
|
* After this function returns, any reference to this object should be considered invalid.
|
|
*/
|
|
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 PrepareHandle : public Handle<PrepareHandle, uv_prepare_t> {
|
|
public:
|
|
using Handle::Handle;
|
|
using cb = cb_void;
|
|
|
|
void init();
|
|
void start(cb user_cb);
|
|
|
|
private:
|
|
static void connect_cb(uv_connect_t* req, int status);
|
|
|
|
cb callback;
|
|
};
|
|
|
|
class loop : public non_copyable_mixin, public non_movable_mixin {
|
|
public:
|
|
loop();
|
|
~loop();
|
|
void run();
|
|
void stop();
|
|
uint64_t now() const;
|
|
|
|
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));
|
|
}
|
|
|
|
uv_loop_t* get() const;
|
|
|
|
private:
|
|
std::unique_ptr<uv_loop_t> m_loop{nullptr};
|
|
};
|
|
|
|
} // namespace eventloop
|
|
|
|
POLYBAR_NS_END
|