#pragma once #include #include #include #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; template using cb_event = std::function; template class Handle : public non_copyable_mixin, public non_movable_mixin { public: Handle(uv_loop_t* l) : uv_loop(l) { get()->data = this; } void leak(std::shared_ptr h) { lifetime_extender = std::move(h); } void unleak() { reset_callbacks(); 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(handle->data)); }); } } bool is_closing() const { return uv_is_closing(this->template get()); } bool is_active() { return uv_is_active(this->template get()) != 0; } protected: ~Handle() = default; /** * Resets all callbacks stored in the handle as part of closing the handle. * * This releases any lambda captures, breaking possible cyclic dependencies in shared_ptr. */ virtual void reset_callbacks() = 0; /** * 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 Self::*Member, typename... Args> static void event_cb(H* handle, Args... args) { Self& This = *static_cast(handle->data); (This.*Member)(Event{std::forward(args)...}); } /** * Same as event_cb except that no event is constructed. */ template static void void_event_cb(H* handle) { Self& This = *static_cast(handle->data); (This.*Member)(); } static Self& cast(H* handle) { return *static_cast(handle->data); } template T* get() { return reinterpret_cast(&uv_handle); } template const T* get() const { return reinterpret_cast(&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 shared_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::shared_ptr lifetime_extender; }; struct ErrorEvent { int status; }; using cb_error = cb_event; class WriteRequest : public non_copyable_mixin, public non_movable_mixin { public: using cb_write = cb_void; WriteRequest(cb_write&& user_cb, cb_error&& err_cb); static WriteRequest& create(cb_write&& user_cb, cb_error&& err_cb); uv_write_t* get(); /** * Trigger the write callback. * * After that, this object is destroyed. */ void trigger(int status); protected: WriteRequest& leak(std::unique_ptr h); void unleak(); void reset_callbacks(); 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 lifetime_extender; }; struct SignalEvent { int signum; }; class SignalHandle final : public Handle { public: using Handle::Handle; using cb = cb_event; void init(); void start(int signum, cb&& user_cb); protected: void reset_callbacks() override; private: cb callback; }; struct PollEvent { uv_poll_event event; }; class PollHandle final : public Handle { public: using Handle::Handle; using cb = cb_event; 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); protected: void reset_callbacks() override; private: cb callback; cb_error err_cb; }; struct FSEvent { const char* path; uv_fs_event event; }; class FSEventHandle final : public Handle { public: using Handle::Handle; using cb = cb_event; 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); protected: void reset_callbacks() override; private: cb callback; cb_error err_cb; }; class TimerHandle final : public Handle { public: using Handle::Handle; using cb = cb_void; void init(); void start(uint64_t timeout, uint64_t repeat, cb&& user_cb); void stop(); protected: void reset_callbacks() override; private: cb callback; }; class AsyncHandle final : public Handle { public: using Handle::Handle; using cb = cb_void; void init(cb&& user_cb); void send(); protected: void reset_callbacks() override; private: cb callback; }; struct ReadEvent { const char* data; size_t len; }; template class StreamHandle : public Handle { public: using Handle::Handle; using cb_read = cb_event; 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 = std::move(user_cb); this->connection_err_cb = std::move(err_cb); UV(uv_listen, this->template get(), 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 void accept(StreamHandle& client) { UV(uv_accept, this->template get(), client.template get()); } void read_start(cb_read &&fun, cb_void&& eof_cb, cb_error&& err_cb) { this->read_callback = std::move(fun); this->read_eof_cb = std::move(eof_cb); this->read_err_cb = std::move(err_cb); UV(uv_read_start, this->template get(), &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(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& data, WriteRequest::cb_write&& user_cb = {}, cb_error&& err_cb = {}) { WriteRequest& req = WriteRequest::create(std::move(user_cb), std::move(err_cb)); uv_buf_t buf{(char*)data.data(), data.size()}; UV(uv_write, req.get(), this->template get(), &buf, 1, [](uv_write_t* r, int status) { static_cast(r->data)->trigger(status); }); } protected: ~StreamHandle() = default; void reset_callbacks() override { read_callback = nullptr; read_eof_cb = nullptr; read_err_cb = nullptr; connection_callback = nullptr; connection_err_cb = nullptr; } 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 final : public StreamHandle { 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); protected: void reset_callbacks() override; private: static void connect_cb(uv_connect_t* req, int status); cb_error connect_err_cb; cb_connect connect_callback; }; class PrepareHandle final : public Handle { public: using Handle::Handle; using cb = cb_void; void init(); void start(cb&& user_cb); protected: void reset_callbacks() override; private: static void connect_cb(uv_connect_t* req, int status); cb callback; }; using signal_handle_t = shared_ptr; using poll_handle_t = shared_ptr; using fs_event_handle_t = shared_ptr; using timer_handle_t = shared_ptr; using async_handle_t = shared_ptr; using pipe_handle_t = shared_ptr; using prepare_handle_t = shared_ptr; class loop : public non_copyable_mixin, public non_movable_mixin { public: loop(); ~loop(); void run(); void stop(); uint64_t now() const; template shared_ptr handle(Args&&... args) { auto ptr = make_shared(get()); ptr->init(std::forward(args)...); ptr->leak(ptr); return ptr; } uv_loop_t* get() const; private: std::unique_ptr m_loop{nullptr}; }; } // namespace eventloop POLYBAR_NS_END