From 249c3ec141daac1dc2a4c683cbe7fa9d8b7f340f Mon Sep 17 00:00:00 2001 From: patrick96 Date: Wed, 17 Feb 2021 19:48:46 +0100 Subject: [PATCH] Use libuv for the controller's event loop --- cmake/libpoly.cmake | 2 + cmake/modules/FindLibUV.cmake | 13 ++ include/components/controller.hpp | 5 +- include/modules/meta/base.hpp | 13 ++ src/CMakeLists.txt | 1 + src/components/controller.cpp | 216 ++++++++++++------------------ src/modules/xwindow.cpp | 6 +- 7 files changed, 118 insertions(+), 138 deletions(-) create mode 100644 cmake/modules/FindLibUV.cmake diff --git a/cmake/libpoly.cmake b/cmake/libpoly.cmake index b465919b..f30662a4 100644 --- a/cmake/libpoly.cmake +++ b/cmake/libpoly.cmake @@ -98,6 +98,8 @@ endif() find_package(Xcb ${XRANDR_VERSION} REQUIRED COMPONENTS RANDR) find_package(Xcb REQUIRED COMPONENTS ${XORG_EXTENSIONS}) +find_package(LibUV 1.10.0 REQUIRED) + # FreeBSD Support if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") find_package(LibInotify REQUIRED) diff --git a/cmake/modules/FindLibUV.cmake b/cmake/modules/FindLibUV.cmake new file mode 100644 index 00000000..a9356ae8 --- /dev/null +++ b/cmake/modules/FindLibUV.cmake @@ -0,0 +1,13 @@ +# This module defines +# LIBUV_FOUND +# LIBUV_INCLUDE_DIR +# LIBUV_INCLUDE_DIRS +# LIBUV_LIBRARY +# LIBUV_LIBRARIES +# LIBUV_VERSION + +find_package_impl("libuv" "LIBUV" "") + +if(LIBUV_FOUND AND NOT TARGET LibUV::LibUV) + create_imported_target("LibUV::LibUV" "${LIBUV_INCLUDE_DIR}" "${LIBUV_LIBRARY}") +endif() diff --git a/include/components/controller.hpp b/include/components/controller.hpp index a11eea02..e8d84c5b 100644 --- a/include/components/controller.hpp +++ b/include/components/controller.hpp @@ -52,6 +52,9 @@ class controller bool enqueue(event&& evt); bool enqueue(string&& input_data); + void conn_cb(int status, int events); + void ipc_cb(int status, int events); + protected: void read_events(); void process_eventqueue(); @@ -84,8 +87,6 @@ class controller unique_ptr m_ipc; unique_ptr m_confwatch; - array, 2> m_queuefd{}; - /** * \brief State flag */ diff --git a/include/modules/meta/base.hpp b/include/modules/meta/base.hpp index 84722aa5..9e40f2b1 100644 --- a/include/modules/meta/base.hpp +++ b/include/modules/meta/base.hpp @@ -169,6 +169,19 @@ namespace modules { void sleep(chrono::duration duration); template void sleep_until(chrono::time_point point); + + /** + * Wakes up the module. + * + * It should be possible to interrupt any blocking operation inside a + * module using this function. + * + * In addition, after a wake up whatever was woken up should immediately + * check whether the module is still running. + * + * Modules that don't follow this, could stall the operation of whatever + * code called this function. + */ void wakeup(); string get_format() const; string get_output(); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d4022142..3200d3a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -151,6 +151,7 @@ if(BUILD_LIBPOLY) Cairo::CairoFC moodycamel xpp + LibUV::LibUV ) if (TARGET i3ipc++) diff --git a/src/components/controller.cpp b/src/components/controller.cpp index 8d11e331..d9baebac 100644 --- a/src/components/controller.cpp +++ b/src/components/controller.cpp @@ -1,5 +1,7 @@ #include "components/controller.hpp" +#include + #include #include @@ -25,22 +27,9 @@ POLYBAR_NS -array g_eventpipe{{-1, -1}}; sig_atomic_t g_reload{0}; sig_atomic_t g_terminate{0}; -void interrupt_handler(int signum) { - if (g_reload || g_terminate) { - return; - } - - g_terminate = 1; - g_reload = (signum == SIGUSR1); - if (write(g_eventpipe[PIPE_WRITE], &g_terminate, 1) == -1) { - throw system_error("Failed to write to eventpipe"); - } -} - /** * Build controller instance */ @@ -70,23 +59,6 @@ controller::controller(connection& conn, signal_emitter& emitter, const logger& m_swallow_limit = m_conf.deprecated("settings", "eventqueue-swallow", "throttle-output", m_swallow_limit); m_swallow_update = m_conf.deprecated("settings", "eventqueue-swallow-time", "throttle-output-for", m_swallow_update); - if (pipe(g_eventpipe.data()) == 0) { - m_queuefd[PIPE_READ] = make_unique(g_eventpipe[PIPE_READ]); - m_queuefd[PIPE_WRITE] = make_unique(g_eventpipe[PIPE_WRITE]); - } else { - throw system_error("Failed to create event channel pipes"); - } - - m_log.trace("controller: Install signal handler"); - struct sigaction act {}; - memset(&act, 0, sizeof(act)); - act.sa_handler = &interrupt_handler; - sigaction(SIGINT, &act, nullptr); - sigaction(SIGQUIT, &act, nullptr); - sigaction(SIGTERM, &act, nullptr); - sigaction(SIGUSR1, &act, nullptr); - sigaction(SIGALRM, &act, nullptr); - m_log.trace("controller: Setup user-defined modules"); size_t created_modules{0}; created_modules += setup_modules(alignment::LEFT); @@ -102,17 +74,6 @@ controller::controller(connection& conn, signal_emitter& emitter, const logger& * Deconstruct controller */ controller::~controller() { - m_log.trace("controller: Uninstall sighandler"); - signal(SIGINT, SIG_DFL); - signal(SIGQUIT, SIG_DFL); - signal(SIGTERM, SIG_DFL); - signal(SIGALRM, SIG_DFL); - - if (g_reload) { - // Cause SIGUSR1 to be ignored until registered in the new polybar process - signal(SIGUSR1, SIG_IGN); - } - m_log.trace("controller: Detach signal receiver"); m_sig.detach(this); @@ -211,112 +172,101 @@ bool controller::enqueue(string&& input_data) { return false; } +void controller::conn_cb(int status, int events) { + // TODO handle negative status + + if (m_connection.connection_has_error()) { + g_terminate = 1; + g_reload = 0; + uv_stop(uv_default_loop()); + return; + } + + shared_ptr evt{}; + while ((evt = shared_ptr(xcb_poll_for_event(m_connection), free)) != nullptr) { + try { + m_connection.dispatch_event(evt); + } catch (xpp::connection_error& err) { + m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code())); + } catch (const exception& err) { + m_log.err("Error in X event loop: %s", err.what()); + } + } +} + +void controller::ipc_cb(int status, int events) { + // TODO handle negative status + m_ipc->receive_message(); +} + +static void conn_cb_wrapper(uv_poll_t* handle, int status, int events) { + static_cast(handle->data)->conn_cb(status, events); +} + +static void ipc_cb_wrapper(uv_poll_t* handle, int status, int events) { + static_cast(handle->data)->ipc_cb(status, events); +} + +static void signal_cb_wrapper(uv_signal_t* handle, int signum) { + g_terminate = 1; + g_reload = (signum == SIGUSR1); + uv_stop(handle->loop); +} + +static void confwatch_cb_wrapper(uv_fs_event_t* handle, const char* fname, int events, int status) { + // TODO handle error + std::cout << fname << std::endl; + g_terminate = 1; + g_reload = 1; + uv_stop(handle->loop); +} + /** * Read events from configured file descriptors */ void controller::read_events() { m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id()); - int fd_connection{-1}; - int fd_confwatch{-1}; - int fd_ipc{-1}; + auto loop = uv_default_loop(); - vector fds; - fds.emplace_back(*m_queuefd[PIPE_READ]); - fds.emplace_back((fd_connection = m_connection.get_file_descriptor())); + auto conn_handle = std::make_unique(); + uv_poll_init(loop, conn_handle.get(), m_connection.get_file_descriptor()); + conn_handle->data = this; + + uv_poll_start(conn_handle.get(), UV_READABLE, conn_cb_wrapper); + + std::vector> handles; + + for (auto s : {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGALRM}) { + auto signal_handle = std::make_unique(); + uv_signal_init(loop, signal_handle.get()); + signal_handle->data = this; + + uv_signal_start(signal_handle.get(), signal_cb_wrapper, s); + handles.push_back(std::move(signal_handle)); + } + + auto conf_handle = std::unique_ptr(nullptr); if (m_confwatch) { - m_log.trace("controller: Attach config watch"); - m_confwatch->attach(IN_MODIFY | IN_IGNORED); - fds.emplace_back((fd_confwatch = m_confwatch->get_file_descriptor())); + conf_handle = std::make_unique(); + uv_fs_event_init(loop, conf_handle.get()); + conf_handle->data = this; + + uv_fs_event_start(conf_handle.get(), confwatch_cb_wrapper, m_confwatch->path().c_str(), 0); } + auto ipc_handle = std::unique_ptr(nullptr); + if (m_ipc) { - fds.emplace_back((fd_ipc = m_ipc->get_file_descriptor())); + ipc_handle = std::make_unique(); + uv_poll_init(loop, ipc_handle.get(), m_ipc->get_file_descriptor()); + ipc_handle->data = this; + uv_poll_start(ipc_handle.get(), UV_READABLE, ipc_cb_wrapper); } - while (!g_terminate) { - fd_set readfds{}; - FD_ZERO(&readfds); - - int maxfd{0}; - for (auto&& fd : fds) { - FD_SET(fd, &readfds); - maxfd = std::max(maxfd, fd); - } - - // Wait until event is ready on one of the configured streams - int events = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr); - - // Check for errors - if (events == -1) { - /* - * The Interrupt errno is generated when polybar is stopped, so it - * shouldn't generate an error message - */ - if (errno != EINTR) { - m_log.err("select failed in event loop: %s", strerror(errno)); - } - - break; - } - - if (g_terminate || m_connection.connection_has_error()) { - break; - } - - // Process event on the internal fd - if (m_queuefd[PIPE_READ] && FD_ISSET(static_cast(*m_queuefd[PIPE_READ]), &readfds)) { - char buffer[BUFSIZ]; - if (read(static_cast(*m_queuefd[PIPE_READ]), &buffer, BUFSIZ) == -1) { - m_log.err("Failed to read from eventpipe (err: %s)", strerror(errno)); - } - } - - // Process event on the config inotify watch fd - unique_ptr confevent; - if (fd_confwatch > -1 && FD_ISSET(fd_confwatch, &readfds) && (confevent = m_confwatch->await_match())) { - if (confevent->mask & IN_IGNORED) { - // IN_IGNORED: file was deleted or filesystem was unmounted - // - // This happens in some configurations of vim when a file is saved, - // since it is not actually issuing calls to write() but rather - // moves a file into the original's place after moving the original - // file to a different location (and subsequently deleting it). - // - // We need to re-attach the watch to the new file in this case. - fds.erase( - std::remove_if(fds.begin(), fds.end(), [fd_confwatch](int fd) { return fd == fd_confwatch; }), fds.end()); - m_confwatch = inotify_util::make_watch(m_confwatch->path()); - m_confwatch->attach(IN_MODIFY | IN_IGNORED); - fds.emplace_back((fd_confwatch = m_confwatch->get_file_descriptor())); - } - m_log.info("Configuration file changed"); - g_terminate = 1; - g_reload = 1; - } - - // Process event on the xcb connection fd - if (fd_connection > -1 && FD_ISSET(fd_connection, &readfds)) { - shared_ptr evt{}; - while ((evt = shared_ptr(xcb_poll_for_event(m_connection), free)) != nullptr) { - try { - m_connection.dispatch_event(evt); - } catch (xpp::connection_error& err) { - m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code())); - } catch (const exception& err) { - m_log.err("Error in X event loop: %s", err.what()); - } - } - } - - // Process event on the ipc fd - if (fd_ipc > -1 && FD_ISSET(fd_ipc, &readfds)) { - m_ipc->receive_message(); - fds.erase(std::remove_if(fds.begin(), fds.end(), [fd_ipc](int fd) { return fd == fd_ipc; }), fds.end()); - fds.emplace_back((fd_ipc = m_ipc->get_file_descriptor())); - } - } + uv_run(loop, UV_RUN_DEFAULT); + uv_loop_close(loop); } /** diff --git a/src/modules/xwindow.cpp b/src/modules/xwindow.cpp index 4afde7d8..010750c1 100644 --- a/src/modules/xwindow.cpp +++ b/src/modules/xwindow.cpp @@ -1,11 +1,11 @@ #include "modules/xwindow.hpp" + #include "drawtypes/label.hpp" +#include "modules/meta/base.inl" #include "utils/factory.hpp" #include "x11/atoms.hpp" #include "x11/connection.hpp" -#include "modules/meta/base.inl" - POLYBAR_NS namespace modules { @@ -138,6 +138,6 @@ namespace modules { } return false; } -} +} // namespace modules POLYBAR_NS_END