From 3356188056a3c0de5dce63fa6f253bfe749d645e Mon Sep 17 00:00:00 2001
From: Patrick Ziegler
Date: Sat, 22 Jan 2022 20:35:37 +0100
Subject: [PATCH] Use sockets for IPC (#2539)
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
---
.clang-tidy | 2 -
.github/workflows/ci.yml | 16 +
CHANGELOG.md | 8 +
cmake/01-core.cmake | 4 +-
doc/conf.py | 1 +
doc/index.rst | 2 +
doc/man/polybar-msg.1.rst | 75 +++++
doc/man/polybar.1.rst | 17 +-
doc/man/polybar.5.rst | 12 +-
doc/user/actions.rst | 3 +
doc/user/ipc.rst | 107 ++++++
include/common.hpp | 42 ++-
include/components/bar.hpp | 14 +-
include/components/controller.hpp | 20 +-
include/components/eventloop.hpp | 494 +++++++++++++++++++++-------
include/components/ipc.hpp | 46 ---
include/drawtypes/animation.hpp | 2 +-
include/drawtypes/iconset.hpp | 4 +-
include/drawtypes/label.hpp | 2 +-
include/drawtypes/layouticonset.hpp | 2 +-
include/drawtypes/progressbar.hpp | 2 +-
include/drawtypes/ramp.hpp | 4 +-
include/events/signal.hpp | 11 +-
include/ipc/decoder.hpp | 61 ++++
include/ipc/encoder.hpp | 14 +
include/ipc/ipc.hpp | 98 ++++++
include/ipc/msg.hpp | 83 +++++
include/ipc/util.hpp | 16 +
include/utils/actions.hpp | 1 +
include/utils/mixins.hpp | 18 +-
src/CMakeLists.txt | 397 +++++++++++-----------
src/components/bar.cpp | 24 +-
src/components/controller.cpp | 72 ++--
src/components/eventloop.cpp | 422 +++++++++++-------------
src/components/ipc.cpp | 87 -----
src/ipc.cpp | 135 --------
src/ipc/decoder.cpp | 135 ++++++++
src/ipc/encoder.cpp | 33 ++
src/ipc/ipc.cpp | 218 ++++++++++++
src/ipc/util.cpp | 61 ++++
src/main.cpp | 11 +-
src/modules/ipc.cpp | 3 +-
src/polybar-msg.cpp | 294 +++++++++++++++++
src/utils/actions.cpp | 6 +-
tests/CMakeLists.txt | 3 +
tests/unit_tests/ipc/decoder.cpp | 96 ++++++
tests/unit_tests/ipc/encoder.cpp | 58 ++++
tests/unit_tests/ipc/util.cpp | 22 ++
tests/unit_tests/utils/actions.cpp | 53 ++-
49 files changed, 2333 insertions(+), 978 deletions(-)
create mode 100644 doc/man/polybar-msg.1.rst
create mode 100644 doc/user/ipc.rst
delete mode 100644 include/components/ipc.hpp
create mode 100644 include/ipc/decoder.hpp
create mode 100644 include/ipc/encoder.hpp
create mode 100644 include/ipc/ipc.hpp
create mode 100644 include/ipc/msg.hpp
create mode 100644 include/ipc/util.hpp
delete mode 100644 src/components/ipc.cpp
delete mode 100644 src/ipc.cpp
create mode 100644 src/ipc/decoder.cpp
create mode 100644 src/ipc/encoder.cpp
create mode 100644 src/ipc/ipc.cpp
create mode 100644 src/ipc/util.cpp
create mode 100644 src/polybar-msg.cpp
create mode 100644 tests/unit_tests/ipc/decoder.cpp
create mode 100644 tests/unit_tests/ipc/encoder.cpp
create mode 100644 tests/unit_tests/ipc/util.cpp
diff --git a/.clang-tidy b/.clang-tidy
index a12c09fe..b558598b 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -32,8 +32,6 @@ Checks: '
CheckOptions:
- key: modernize-loop-convert.NamingStyle
value: lower_case
- - key: readability-identifier-naming.GlobalConstantPrefix
- value: 'g_'
- key: readability-identifier-naming.ClassCase
value: lower_case
- key: readability-identifier-naming.ClassConstantCase
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 449e0921..9ec6ae5c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,8 +33,24 @@ jobs:
env:
COLOR: "ON"
steps:
+ - name: Install Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ libxcb-composite0-dev \
+ libxcb-ewmh-dev \
+ libxcb-icccm4-dev \
+ libxcb-image0-dev \
+ libxcb-randr0-dev \
+ libxcb-util0-dev \
+ libxcb1-dev \
+ libcairo2-dev \
+ python3-xcbgen \
+ libuv1-dev \
+ xcb-proto
- uses: actions/checkout@v2
with:
+ submodules: true
ref: ${{ github.event.inputs.ref }}
- name: Build polybar-msg
run: |
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0f3527c6..3b12f450 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
and others will now start producing errors.
This does not affect you unless you are producing your own formatting tags
(for example in a script) and you are using one of these invalid tags.
+- For security reasons, the named pipe at `/tmp/polybar_mqueue.` had its
+ permission bits changed from `666` to `600` to prevent sending ipc messages
+ to polybar processes running under a different user.
### Build
- New dependency: [libuv](https://github.com/libuv/libuv). At least version 1.3
@@ -68,6 +71,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
bar update.
- When not specifying the config file with `--config`, naming your config file
`config` is deprecated. Rename your config file to `config.ini`.
+- Directly writing ipc messages to `/tmp/polybar_mqueue.` is deprecated,
+ users should always use `polybar-msg`. As a consequence the message format
+ used for IPC is deprecated as well.
### Removed
- `DEBUG_SHADED` cmake variable and its associated functionality.
@@ -142,6 +148,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a new `tray-foreground` setting to give hints to tray icons about what
color they should be.
([#2235](https://github.com/polybar/polybar/issues/2235))
+- `polybar-msg`: For module actions, you can now also specify the module name,
+ action name, and optional data as separate arguments.
### Changed
- Polybar now also reads `config.ini` when searching for config files.
diff --git a/cmake/01-core.cmake b/cmake/01-core.cmake
index fe0032e5..7f8d55ef 100644
--- a/cmake/01-core.cmake
+++ b/cmake/01-core.cmake
@@ -22,13 +22,13 @@ include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(BUILD_DOC_HTML "Build HTML documentation" ON "BUILD_DOC" OFF)
CMAKE_DEPENDENT_OPTION(BUILD_DOC_MAN "Build manpages" ON "BUILD_DOC" OFF)
-if (BUILD_POLYBAR OR BUILD_TESTS)
+if (BUILD_POLYBAR OR BUILD_TESTS OR BUILD_POLYBAR_MSG)
set(BUILD_LIBPOLY ON)
else()
set(BUILD_LIBPOLY OFF)
endif()
-if (BUILD_LIBPOLY OR BUILD_POLYBAR_MSG)
+if (BUILD_POLYBAR OR BUILD_POLYBAR_MSG OR BUILD_TESTS)
set(HAS_CXX_COMPILATION ON)
else()
set(HAS_CXX_COMPILATION OFF)
diff --git a/doc/conf.py b/doc/conf.py
index 656c7291..22c1008e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -182,6 +182,7 @@ latex_documents = [
# (source start file, name, description, authors, manual section).
man_pages = [
('man/polybar.1', 'polybar', 'A fast and easy-to-use tool status bar', [], 1),
+ ('man/polybar-msg.1', 'polybar-msg', 'Send IPC messages to polybar', [], 1),
('man/polybar.5', 'polybar', 'configuration file for polybar(1)', [], 5)
]
diff --git a/doc/index.rst b/doc/index.rst
index 41feece2..b7ca5773 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -13,12 +13,14 @@ Welcome to the official polybar documentation.
:caption: Content:
user/actions
+ user/ipc
.. toctree::
:maxdepth: 1
:caption: Manual Pages:
man/polybar.1
+ man/polybar-msg.1
man/polybar.5
.. toctree::
diff --git a/doc/man/polybar-msg.1.rst b/doc/man/polybar-msg.1.rst
new file mode 100644
index 00000000..0141b7a9
--- /dev/null
+++ b/doc/man/polybar-msg.1.rst
@@ -0,0 +1,75 @@
+polybar-msg(1)
+==============
+
+SYNOPSIS
+--------
+| **polybar-msg** [*OPTIONS*] **action** *action-string*
+| **polybar-msg** [*OPTIONS*] **action** *module* *action* [*data*]
+| **polybar-msg** [*OPTIONS*] **cmd** *command*
+
+DESCRIPTION
+-----------
+Polybar allows external control through *actions* and *commands*.
+Actions control individual modules and commands control the bar itself.
+
+The full IPC documentation is linked at the end of this document.
+
+The available actions depend on the target module.
+For actions, the payload is either a single action string or the module name,
+the action name, and the optional data string specified separately.
+
+In order for **polybar-msg** being able to send a message to a running
+**polybar** process, the bar must have IPC enabled and both **polybar-msg** and
+**polybar** must run under the same user.
+
+OPTIONS
+-------
+
+.. program:: polybar-msg
+
+.. option:: -h, --help
+
+ Display help text and exit
+
+.. option:: -p PID
+
+ Send message only to **polybar** process running under the given process ID.
+ If not specified, the message is sent to all running **polybar** processes.
+
+EXAMPLES
+--------
+
+**polybar-msg** **cmd** *quit*
+ Terminate all running **polybar** instances.
+
+**polybar-msg** **action** *mymodule* *module_hide*
+
+**polybar-msg** **action** "*#mymodule.module_hide*"
+ Hide the module named *mymodule*.
+ The first variant specifies the module and action names separately, the second uses an action string.
+
+AUTHORS
+-------
+| Polybar was created by Michael Carlberg and is currently maintained by Patrick Ziegler.
+| Contributors can be listed on GitHub.
+
+REPORTING BUGS
+--------------
+Report issues on GitHub
+
+SEE ALSO
+--------
+.. only:: man
+
+ :manpage:`polybar`\(1),
+ :manpage:`polybar`\(5)
+
+ | IPC documentation:
+
+
+.. only:: not man
+
+ :doc:`polybar.1`,
+ :doc:`polybar.5`
+
+ :doc:`/user/ipc`
diff --git a/doc/man/polybar.1.rst b/doc/man/polybar.1.rst
index a568c838..6df7eeba 100644
--- a/doc/man/polybar.1.rst
+++ b/doc/man/polybar.1.rst
@@ -66,9 +66,9 @@ OPTIONS
Save png snapshot to *FILE* after running for 3 seconds
-AUTHOR
-------
-| Michael Carlberg
+AUTHORS
+-------
+| Polybar was created by Michael Carlberg and is currently maintained by Patrick Ziegler.
| Contributors can be listed on GitHub.
REPORTING BUGS
@@ -77,13 +77,16 @@ Report issues on GitHub
SEE ALSO
--------
-| Full documentation at:
-| Project wiki:
-
.. only:: man
- :manpage:`polybar(5)`
+ :manpage:`polybar-msg`\(1),
+ :manpage:`polybar`\(5)
+
.. only:: not man
+ :doc:`polybar-msg.1`,
:doc:`polybar.5`
+
+| Full documentation at:
+| Project wiki:
diff --git a/doc/man/polybar.5.rst b/doc/man/polybar.5.rst
index 9d893c85..659c858f 100644
--- a/doc/man/polybar.5.rst
+++ b/doc/man/polybar.5.rst
@@ -164,13 +164,21 @@ not affect polybar's behavior. Comment lines start with either the ``;`` or the
name = value ; comment
+AUTHORS
+-------
+| Polybar was created by Michael Carlberg and is currently maintained by Patrick Ziegler.
+| Contributors can be listed on GitHub.
+
SEE ALSO
--------
.. only:: man
- :manpage:`polybar(1)`
+ :manpage:`polybar`\(1),
+ :manpage:`polybar-msg`\(1)
+
.. only:: not man
- :doc:`polybar.1`
+ :doc:`polybar.1`,
+ :doc:`polybar-msg.1`
diff --git a/doc/user/actions.rst b/doc/user/actions.rst
index 95ae2a78..850a8c4a 100644
--- a/doc/user/actions.rst
+++ b/doc/user/actions.rst
@@ -258,6 +258,9 @@ custom/menu
The data has the form ``N-M`` and the action will execute the command
in ``menu-N-M-exec``.
+
+.. _actions-ipc:
+
custom/ipc
^^^^^^^^^^
diff --git a/doc/user/ipc.rst b/doc/user/ipc.rst
new file mode 100644
index 00000000..b278ce36
--- /dev/null
+++ b/doc/user/ipc.rst
@@ -0,0 +1,107 @@
+Inter-process-messaging
+=======================
+
+Polybar supports controlling parts of the bar and its modules from the outside
+through inter-process-messaging (IPC).
+
+IPC is disabled by default and can be enabled by setting ``enable-ipc = true``
+in the bar section.
+
+By default polybar ships with the ``polybar-msg`` tool that is needed to send
+messages to polybar.
+
+.. note:: Starting with version 3.6.0, the underlying IPC mechanism has been
+ completely changed.
+
+ Writing directly to the named pipe to send IPC messages has been
+ deprecated, ``polybar-msg`` should be used exclusively
+ Everything you could do by directly writing to the named pipe, you
+ can also do using ``polybar-msg``.
+ In addition, hook messages are also deprecated; they are replaced by
+ actions on the :ref:`ipc module `.
+
+ Unless noted otherwise, everything in this guide is still valid for
+ older versions.
+
+Sending Messages
+----------------
+
+``polybar-msg`` can be called on the commandline like this:
+
+.. code-block:: shell
+
+ polybar-msg [-p ]
+
+If the ``-p`` argument is specified, the message is only sent to the running
+polybar instance with the given process ID.
+Otherwise, the message is sent to all running polybar processes that have IPC
+enabled.
+
+.. note:: IPC messages are only sent to polybar instances running under the
+ same user as ``polybar-msg`` is running as.
+
+The ```` argument is either :ref:`action ` or
+:ref:`cmd `.
+The allowed values for ```` depend on the type.
+
+Message Types
+-------------
+
+.. _ipc-commands:
+
+Commands
+^^^^^^^^
+
+Using ``cmd`` for ````, you can control certain aspects of the bar.
+
+Available values for ```` are:
+
+* ``quit``: Terminates the bar
+* ``restart``: Restarts the bar in-place
+* ``hide``: Hides the bar
+* ``show``: Makes the bar visible again, if it was hidden
+* ``toggle``: Toggles between the hidden and visible state.
+
+.. _ipc-actions:
+
+Module Actions
+^^^^^^^^^^^^^^
+
+For the ```` ``action``, ``polybar-msg`` can execute
+:doc:`module actions ` in the bar.
+
+An action consists of the name of the target module, the name of the action and an optional data string:
+
+::
+
+ #.[.]
+
+More information about action strings and available actions can be found in
+:doc:`actions`
+
+For example, if you have a date module named ``date``, you can toggle between
+the regular and alternative label with:
+
+.. code-block:: shell
+
+ polybar-msg action "#date.toggle"
+
+As an example for an action with data, say you have a menu module named
+``powermenu``, you can open the menu level 0 using:
+
+.. code-block:: shell
+
+ polybar-msg action "#powermenu.open.0"
+
+
+.. note::
+
+ For convenience, ``polybar-msg`` also allows you to pass the module name,
+ action name, and data as separate arguments:
+
+ .. code-block:: shell
+
+ polybar-msg action date toggle
+ polybar-msg action powermenu open 0
+
+ .. versionadded:: 3.6.0
diff --git a/include/common.hpp b/include/common.hpp
index 6da08c49..3e776f75 100644
--- a/include/common.hpp
+++ b/include/common.hpp
@@ -1,16 +1,15 @@
#pragma once
+#include
#include
#include
+#include
#include
-#include
#include "settings.hpp"
-#define POLYBAR_NS \
- namespace polybar {
-#define POLYBAR_NS_END \
- }
+#define POLYBAR_NS namespace polybar {
+#define POLYBAR_NS_END }
#ifndef PIPE_READ
#define PIPE_READ 0
@@ -21,20 +20,20 @@
POLYBAR_NS
-using std::string;
-using std::size_t;
-using std::move;
-using std::forward;
-using std::pair;
-using std::function;
-using std::shared_ptr;
-using std::unique_ptr;
-using std::make_unique;
-using std::make_shared;
-using std::make_pair;
using std::array;
-using std::vector;
+using std::forward;
+using std::function;
+using std::make_pair;
+using std::make_shared;
+using std::make_unique;
+using std::move;
+using std::pair;
+using std::shared_ptr;
+using std::size_t;
+using std::string;
using std::to_string;
+using std::unique_ptr;
+using std::vector;
using namespace std::string_literals;
@@ -42,4 +41,13 @@ constexpr size_t operator"" _z(unsigned long long n) {
return n;
}
+/**
+ * Convert an enum to its underlying type.
+ */
+template
+constexpr auto to_integral(E e) {
+ static_assert(std::is_enum::value, "only enums are supported");
+ return static_cast>(e);
+}
+
POLYBAR_NS_END
diff --git a/include/components/bar.hpp b/include/components/bar.hpp
index 9e24f222..81ad1df7 100644
--- a/include/components/bar.hpp
+++ b/include/components/bar.hpp
@@ -63,9 +63,9 @@ class bar : public xpp::event::sink {
public:
using make_type = unique_ptr;
- static make_type make(eventloop&, bool only_initialize_values = false);
+ static make_type make(eventloop::eventloop&, bool only_initialize_values = false);
- explicit bar(connection&, signal_emitter&, const config&, const logger&, eventloop&, unique_ptr&&,
+ explicit bar(connection&, signal_emitter&, const config&, const logger&, eventloop::eventloop&, unique_ptr&&,
unique_ptr&&, unique_ptr&&, unique_ptr&&,
bool only_initialize_values);
~bar();
@@ -111,7 +111,7 @@ class bar : public xpp::event::sink m_screen;
unique_ptr m_tray;
unique_ptr m_renderer;
@@ -127,10 +127,10 @@ class bar : public xpp::event::sink()};
+ eventloop::TimerHandle& m_middleclick_timer{m_loop.handle()};
+ eventloop::TimerHandle& m_rightclick_timer{m_loop.handle()};
+ eventloop::TimerHandle& m_dim_timer{m_loop.handle()};
bool m_visible{true};
};
diff --git a/include/components/controller.hpp b/include/components/controller.hpp
index 080100f8..526b39f0 100644
--- a/include/components/controller.hpp
+++ b/include/components/controller.hpp
@@ -25,7 +25,6 @@ class bar;
class config;
class connection;
class inotify_watch;
-class ipc;
class logger;
class signal_emitter;
namespace modules {
@@ -41,9 +40,9 @@ class controller : public signal_receiver {
public:
using make_type = unique_ptr;
- static make_type make(unique_ptr&& ipc);
+ static make_type make(bool has_ipc, eventloop::eventloop&);
- explicit controller(connection&, signal_emitter&, const logger&, const config&, unique_ptr&&);
+ explicit controller(connection&, signal_emitter&, const logger&, const config&, bool has_ipc, eventloop::eventloop&);
~controller();
bool run(bool writeback, string snapshot_dst, bool confwatch);
@@ -56,8 +55,8 @@ class controller : public signal_receiver m_loop;
+ eventloop::eventloop& m_loop;
unique_ptr m_bar;
- unique_ptr m_ipc;
-
- /**
- * Once this is set to true, 'm_loop' and any uv handles can be used.
- */
- std::atomic_bool m_loop_ready{false};
+ bool m_has_ipc;
/**
* \brief Async handle to notify the eventloop
@@ -114,7 +108,7 @@ class controller : public signal_receiver([this]() { notifier_handler(); })};
/**
* Notification data for the controller.
diff --git a/include/components/eventloop.hpp b/include/components/eventloop.hpp
index 553bdff4..1673910b 100644
--- a/include/components/eventloop.hpp
+++ b/include/components/eventloop.hpp
@@ -6,160 +6,406 @@
#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 "': "s + uv_strerror(res)); \
- } \
+#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);
-void close_callback(uv_handle_t*);
+ using cb_void = function;
-/**
- * \tparam H Type of the handle
- * \tparam I Type of the handle passed to the callback. Often the same as H, but not always (e.g. uv_read_start)
- */
-template
-struct UVHandleGeneric {
- UVHandleGeneric(function fun) {
- handle = new H;
- handle->data = this;
- this->func = fun;
- }
+ template
+ using cb_event = std::function;
- ~UVHandleGeneric() {
- close();
- }
-
- uv_loop_t* loop() const {
- return handle->loop;
- }
-
- void close() {
- if (!is_closing()) {
- uv_close((uv_handle_t*)handle, close_callback);
+ template
+ class Handle : public non_copyable_mixin {
+ public:
+ Handle(uv_loop_t* l) : uv_loop(l) {
+ get()->data = this;
}
- }
- bool is_closing() {
- return !handle || uv_is_closing((uv_handle_t*)handle);
- }
-
- bool is_active() {
- return uv_is_active((uv_handle_t*)handle) != 0;
- }
-
- void cleanup_resources() {
- if (handle) {
- delete handle;
- handle = nullptr;
+ Self& leak(std::unique_ptr h) {
+ lifetime_extender = std::move(h);
+ return *lifetime_extender;
}
- }
- static void callback(I* context, Args... args) {
- const auto unpackedThis = static_cast(context->data);
- return unpackedThis->func(std::forward(args)...);
- }
+ void unleak() {
+ lifetime_extender.reset();
+ }
- H* handle{nullptr};
- function func;
-};
+ H* raw() {
+ return get();
+ }
-template
-struct UVHandle : public UVHandleGeneric {
- UVHandle(function fun) : UVHandleGeneric(fun) {}
-};
+ const H* raw() const {
+ return get();
+ }
-struct SignalHandle : public UVHandle {
- SignalHandle(uv_loop_t* loop, function fun);
- void start(int signum);
-};
+ void close() {
+ if (!is_closing()) {
+ uv_close((uv_handle_t*)get(), [](uv_handle_t* handle) { close_callback(*static_cast(handle->data)); });
+ }
+ }
-struct PollHandle : public UVHandle {
- PollHandle(uv_loop_t* loop, int fd, function fun, function err_cb);
- void start(int events);
- void poll_cb(int status, int events);
+ bool is_closing() const {
+ return uv_is_closing(this->template get());
+ }
- function func;
- function err_cb;
-};
+ bool is_active() {
+ return uv_is_active(this->template get()) != 0;
+ }
-struct FSEventHandle : public UVHandle {
- FSEventHandle(uv_loop_t* loop, function fun, function err_cb);
- void start(const string& path);
- void fs_event_cb(const char* path, int events, int status);
+ 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 Self::*Member, typename... Args>
+ static void event_cb(H* handle, Args... args) {
+ Self& This = *static_cast(handle->data);
+ (This.*Member)(Event{std::forward(args)...});
+ }
- function func;
- function err_cb;
-};
+ /**
+ * 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)();
+ }
-struct PipeHandle : public UVHandleGeneric {
- PipeHandle(uv_loop_t* loop, const string& path, function fun, function eof_cb,
- function err_cb);
- void start();
- void read_cb(ssize_t nread, const uv_buf_t* buf);
+ static Self& cast(H* handle) {
+ return *static_cast(handle->data);
+ }
- function func;
- function eof_cb;
- function err_cb;
- int fd;
- string path;
-};
+ template
+ T* get() {
+ return reinterpret_cast(&uv_handle);
+ }
-struct TimerHandle : public UVHandle {
- TimerHandle(uv_loop_t* loop, function fun);
- void start(uint64_t timeout, uint64_t repeat, function new_cb = function(nullptr));
- void stop();
-};
+ template
+ const T* get() const {
+ return reinterpret_cast(&uv_handle);
+ }
-struct AsyncHandle : public UVHandle {
- AsyncHandle(uv_loop_t* loop, function fun);
- void send();
-};
+ uv_loop_t* loop() const {
+ return uv_loop;
+ }
-using SignalHandle_t = std::unique_ptr;
-using PollHandle_t = std::unique_ptr;
-using FSEventHandle_t = std::unique_ptr;
-using PipeHandle_t = std::unique_ptr;
-// shared_ptr because we also return the pointer in order to call methods on it
-using TimerHandle_t = std::shared_ptr;
-using AsyncHandle_t = std::shared_ptr;
+ static void close_callback(Self& self) {
+ self.unleak();
+ }
-class eventloop {
- public:
- eventloop();
- ~eventloop();
- void run();
- void stop();
- void signal_handle(int signum, function fun);
- void poll_handle(int events, int fd, function fun, function err_cb);
- void fs_event_handle(const string& path, function fun, function err_cb);
- void pipe_handle(
- const string& path, function fun, function eof_cb, function err_cb);
- TimerHandle_t timer_handle(function fun);
- AsyncHandle_t async_handle(function fun);
+ static void alloc_callback(uv_handle_t*, size_t, uv_buf_t* buf) {
+ buf->base = new char[BUFSIZ];
+ buf->len = BUFSIZ;
+ }
- protected:
- uv_loop_t* get() const;
+ private:
+ H uv_handle;
+ uv_loop_t* uv_loop;
- private:
- std::unique_ptr m_loop{nullptr};
+ /**
+ * 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 lifetime_extender;
+ };
- vector m_sig_handles;
- vector m_poll_handles;
- vector m_fs_event_handles;
- vector m_pipe_handles;
- vector m_timer_handles;
- vector m_async_handles;
-};
+ struct ErrorEvent {
+ int status;
+ };
+
+ using cb_error = cb_event;
+
+ 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(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 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 lifetime_extender;
+ };
+
+ struct SignalEvent {
+ int signum;
+ };
+
+ class SignalHandle : public Handle {
+ public:
+ using Handle::Handle;
+ using cb = cb_event;
+
+ void init();
+ void start(int signum, cb user_cb);
+
+ private:
+ cb callback;
+ };
+
+ struct PollEvent {
+ uv_poll_event event;
+ };
+
+ class PollHandle : 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);
+
+ private:
+ cb callback;
+ cb_error err_cb;
+ };
+
+ struct FSEvent {
+ const char* path;
+ uv_fs_event event;
+ };
+
+ class FSEventHandle : 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);
+
+ private:
+ cb callback;
+ cb_error err_cb;
+ };
+
+ class TimerHandle : 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();
+
+ private:
+ cb callback;
+ };
+
+ class AsyncHandle : public Handle {
+ 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
+ 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 = user_cb;
+ this->connection_err_cb = 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 = fun;
+ this->read_eof_cb = eof_cb;
+ this->read_err_cb = 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(user_cb, 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); });
+ }
+
+ 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 {
+ 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
+ H& handle(Args... args) {
+ auto ptr = make_unique(get());
+ ptr->init(std::forward(args)...);
+ return ptr->leak(std::move(ptr));
+ }
+
+ protected:
+ uv_loop_t* get() const;
+
+ private:
+ std::unique_ptr m_loop{nullptr};
+ };
+
+} // namespace eventloop
POLYBAR_NS_END
diff --git a/include/components/ipc.hpp b/include/components/ipc.hpp
deleted file mode 100644
index 1d376952..00000000
--- a/include/components/ipc.hpp
+++ /dev/null
@@ -1,46 +0,0 @@
-#pragma once
-
-#include
-
-#include "common.hpp"
-#include "settings.hpp"
-#include "utils/concurrency.hpp"
-
-POLYBAR_NS
-
-class signal_emitter;
-class logger;
-
-/**
- * Component used for inter-process communication.
- *
- * A unique messaging channel will be setup for each
- * running process which will allow messages and
- * events to be sent to the process externally.
- */
-class ipc {
- public:
- using make_type = unique_ptr;
- static make_type make();
-
- explicit ipc(signal_emitter& emitter, const logger& logger);
- ~ipc();
-
- string get_path() const;
-
- void receive_data(string buf);
- void receive_eof();
-
- private:
- signal_emitter& m_sig;
- const logger& m_log;
-
- string m_path{};
-
- /**
- * Buffer for the currently received IPC message.
- */
- string m_buffer{};
-};
-
-POLYBAR_NS_END
diff --git a/include/drawtypes/animation.hpp b/include/drawtypes/animation.hpp
index 843f9729..5f8066b7 100644
--- a/include/drawtypes/animation.hpp
+++ b/include/drawtypes/animation.hpp
@@ -13,7 +13,7 @@ POLYBAR_NS
namespace chrono = std::chrono;
namespace drawtypes {
- class animation : public non_copyable_mixin {
+ class animation : public non_copyable_mixin {
public:
explicit animation(unsigned int framerate_ms) : m_framerate_ms(framerate_ms) {}
explicit animation(vector&& frames, int framerate_ms)
diff --git a/include/drawtypes/iconset.hpp b/include/drawtypes/iconset.hpp
index 075d4b86..242f2923 100644
--- a/include/drawtypes/iconset.hpp
+++ b/include/drawtypes/iconset.hpp
@@ -9,7 +9,7 @@
POLYBAR_NS
namespace drawtypes {
- class iconset : public non_copyable_mixin {
+ class iconset : public non_copyable_mixin {
public:
void add(string id, label_t&& icon);
bool has(const string& id);
@@ -21,6 +21,6 @@ namespace drawtypes {
};
using iconset_t = shared_ptr;
-}
+} // namespace drawtypes
POLYBAR_NS_END
diff --git a/include/drawtypes/label.hpp b/include/drawtypes/label.hpp
index 144446ae..b20b6bca 100644
--- a/include/drawtypes/label.hpp
+++ b/include/drawtypes/label.hpp
@@ -18,7 +18,7 @@ namespace drawtypes {
bool zpad{false};
};
- class label : public non_copyable_mixin