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
This commit is contained in:
Patrick Ziegler 2022-01-22 20:35:37 +01:00 committed by GitHub
parent 8a9cad2792
commit 3356188056
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 2333 additions and 978 deletions

View File

@ -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

View File

@ -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: |

View File

@ -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.<PID>` 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.<PID>` 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.

View File

@ -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)

View File

@ -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)
]

View File

@ -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::

75
doc/man/polybar-msg.1.rst Normal file
View File

@ -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 <https://github.com/polybar/polybar>
SEE ALSO
--------
.. only:: man
:manpage:`polybar`\(1),
:manpage:`polybar`\(5)
| IPC documentation: <https://polybar.rtfd.org/en/stable/user/ipc.html>
.. only:: not man
:doc:`polybar.1`,
:doc:`polybar.5`
:doc:`/user/ipc`

View File

@ -66,9 +66,9 @@ OPTIONS
Save png snapshot to *FILE* after running for 3 seconds
AUTHOR
------
| Michael Carlberg <c@rlberg.se>
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 <https://github.com/polybar/polybar>
SEE ALSO
--------
| Full documentation at: <https://github.com/polybar/polybar>
| Project wiki: <https://github.com/polybar/polybar/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: <https://github.com/polybar/polybar>
| Project wiki: <https://github.com/polybar/polybar/wiki>

View File

@ -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`

View File

@ -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
^^^^^^^^^^

107
doc/user/ipc.rst Normal file
View File

@ -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 <actions-ipc>`.
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 <pid>] <type> <payload>
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 ``<type>`` argument is either :ref:`action <ipc-actions>` or
:ref:`cmd <ipc-commands>`.
The allowed values for ``<payload>`` depend on the type.
Message Types
-------------
.. _ipc-commands:
Commands
^^^^^^^^
Using ``cmd`` for ``<type>``, you can control certain aspects of the bar.
Available values for ``<payload>`` 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 ``<type>`` ``action``, ``polybar-msg`` can execute
:doc:`module actions <actions>` in the bar.
An action consists of the name of the target module, the name of the action and an optional data string:
::
#<modulename>.<actionname>[.<data>]
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

View File

@ -1,16 +1,15 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
#include <functional>
#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 <typename E>
constexpr auto to_integral(E e) {
static_assert(std::is_enum<E>::value, "only enums are supported");
return static_cast<typename std::underlying_type_t<E>>(e);
}
POLYBAR_NS_END

View File

@ -63,9 +63,9 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
> {
public:
using make_type = unique_ptr<bar>;
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<screen>&&,
explicit bar(connection&, signal_emitter&, const config&, const logger&, eventloop::eventloop&, unique_ptr<screen>&&,
unique_ptr<tray_manager>&&, unique_ptr<tags::dispatch>&&, unique_ptr<tags::action_context>&&,
bool only_initialize_values);
~bar();
@ -111,7 +111,7 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
signal_emitter& m_sig;
const config& m_conf;
const logger& m_log;
eventloop& m_loop;
eventloop::eventloop& m_loop;
unique_ptr<screen> m_screen;
unique_ptr<tray_manager> m_tray;
unique_ptr<renderer> m_renderer;
@ -127,10 +127,10 @@ class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::propert
int m_motion_pos{0};
#endif
TimerHandle_t m_leftclick_timer{m_loop.timer_handle(nullptr)};
TimerHandle_t m_middleclick_timer{m_loop.timer_handle(nullptr)};
TimerHandle_t m_rightclick_timer{m_loop.timer_handle(nullptr)};
TimerHandle_t m_dim_timer{m_loop.timer_handle(nullptr)};
eventloop::TimerHandle& m_leftclick_timer{m_loop.handle<eventloop::TimerHandle>()};
eventloop::TimerHandle& m_middleclick_timer{m_loop.handle<eventloop::TimerHandle>()};
eventloop::TimerHandle& m_rightclick_timer{m_loop.handle<eventloop::TimerHandle>()};
eventloop::TimerHandle& m_dim_timer{m_loop.handle<eventloop::TimerHandle>()};
bool m_visible{true};
};

View File

@ -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<SIGN_PRIORITY_CONTROLLER, signals::eve
signals::ipc::hook, signals::ui::button_press, signals::ui::update_background> {
public:
using make_type = unique_ptr<controller>;
static make_type make(unique_ptr<ipc>&& ipc);
static make_type make(bool has_ipc, eventloop::eventloop&);
explicit controller(connection&, signal_emitter&, const logger&, const config&, unique_ptr<ipc>&&);
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<SIGN_PRIORITY_CONTROLLER, signals::eve
void signal_handler(int signum);
void conn_cb(uv_poll_event events);
void confwatch_handler(const char* fname, uv_fs_event events);
void conn_cb();
void confwatch_handler(const char* fname);
void notifier_handler();
void screenshot_handler();
@ -99,14 +98,9 @@ class controller : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eve
signal_emitter& m_sig;
const logger& m_log;
const config& m_conf;
unique_ptr<eventloop> m_loop;
eventloop::eventloop& m_loop;
unique_ptr<bar> m_bar;
unique_ptr<ipc> 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<SIGN_PRIORITY_CONTROLLER, signals::eve
* This handle is used to notify the eventloop of changes which are not otherwise covered by other handles.
* E.g. click actions.
*/
AsyncHandle_t m_notifier{nullptr};
eventloop::AsyncHandle& m_notifier{m_loop.handle<eventloop::AsyncHandle>([this]() { notifier_handler(); })};
/**
* Notification data for the controller.

View File

@ -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<void(void)>;
/**
* \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 <typename H, typename I, typename... Args>
struct UVHandleGeneric {
UVHandleGeneric(function<void(Args...)> fun) {
handle = new H;
handle->data = this;
this->func = fun;
}
template <typename Event>
using cb_event = std::function<void(const Event&)>;
~UVHandleGeneric() {
close();
}
uv_loop_t* loop() const {
return handle->loop;
}
void close() {
if (!is_closing()) {
uv_close((uv_handle_t*)handle, close_callback);
template <typename Self, typename H>
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<Self> h) {
lifetime_extender = std::move(h);
return *lifetime_extender;
}
}
static void callback(I* context, Args... args) {
const auto unpackedThis = static_cast<const UVHandleGeneric*>(context->data);
return unpackedThis->func(std::forward<Args>(args)...);
}
void unleak() {
lifetime_extender.reset();
}
H* handle{nullptr};
function<void(Args...)> func;
};
H* raw() {
return get();
}
template <typename H, typename... Args>
struct UVHandle : public UVHandleGeneric<H, H, Args...> {
UVHandle(function<void(Args...)> fun) : UVHandleGeneric<H, H, Args...>(fun) {}
};
const H* raw() const {
return get();
}
struct SignalHandle : public UVHandle<uv_signal_t, int> {
SignalHandle(uv_loop_t* loop, function<void(int)> fun);
void start(int signum);
};
void close() {
if (!is_closing()) {
uv_close((uv_handle_t*)get(), [](uv_handle_t* handle) { close_callback(*static_cast<Self*>(handle->data)); });
}
}
struct PollHandle : public UVHandle<uv_poll_t, int, int> {
PollHandle(uv_loop_t* loop, int fd, function<void(uv_poll_event)> fun, function<void(int)> err_cb);
void start(int events);
void poll_cb(int status, int events);
bool is_closing() const {
return uv_is_closing(this->template get<uv_handle_t>());
}
function<void(uv_poll_event)> func;
function<void(int)> err_cb;
};
bool is_active() {
return uv_is_active(this->template get<uv_handle_t>()) != 0;
}
struct FSEventHandle : public UVHandle<uv_fs_event_t, const char*, int, int> {
FSEventHandle(uv_loop_t* loop, function<void(const char*, uv_fs_event)> fun, function<void(int)> 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 <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)...});
}
function<void(const char*, uv_fs_event)> func;
function<void(int)> err_cb;
};
/**
* 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)();
}
struct PipeHandle : public UVHandleGeneric<uv_pipe_t, uv_stream_t, ssize_t, const uv_buf_t*> {
PipeHandle(uv_loop_t* loop, const string& path, function<void(const string)> fun, function<void(void)> eof_cb,
function<void(int)> err_cb);
void start();
void read_cb(ssize_t nread, const uv_buf_t* buf);
static Self& cast(H* handle) {
return *static_cast<Self*>(handle->data);
}
function<void(const string)> func;
function<void(void)> eof_cb;
function<void(int)> err_cb;
int fd;
string path;
};
template <typename T = H>
T* get() {
return reinterpret_cast<T*>(&uv_handle);
}
struct TimerHandle : public UVHandle<uv_timer_t> {
TimerHandle(uv_loop_t* loop, function<void(void)> fun);
void start(uint64_t timeout, uint64_t repeat, function<void(void)> new_cb = function<void(void)>(nullptr));
void stop();
};
template <typename T = H>
const T* get() const {
return reinterpret_cast<const T*>(&uv_handle);
}
struct AsyncHandle : public UVHandle<uv_async_t> {
AsyncHandle(uv_loop_t* loop, function<void(void)> fun);
void send();
};
uv_loop_t* loop() const {
return uv_loop;
}
using SignalHandle_t = std::unique_ptr<SignalHandle>;
using PollHandle_t = std::unique_ptr<PollHandle>;
using FSEventHandle_t = std::unique_ptr<FSEventHandle>;
using PipeHandle_t = std::unique_ptr<PipeHandle>;
// shared_ptr because we also return the pointer in order to call methods on it
using TimerHandle_t = std::shared_ptr<TimerHandle>;
using AsyncHandle_t = std::shared_ptr<AsyncHandle>;
static void close_callback(Self& self) {
self.unleak();
}
class eventloop {
public:
eventloop();
~eventloop();
void run();
void stop();
void signal_handle(int signum, function<void(int)> fun);
void poll_handle(int events, int fd, function<void(uv_poll_event)> fun, function<void(int)> err_cb);
void fs_event_handle(const string& path, function<void(const char*, uv_fs_event)> fun, function<void(int)> err_cb);
void pipe_handle(
const string& path, function<void(const string)> fun, function<void(void)> eof_cb, function<void(int)> err_cb);
TimerHandle_t timer_handle(function<void(void)> fun);
AsyncHandle_t async_handle(function<void(void)> 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<uv_loop_t> 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<Self> lifetime_extender;
};
vector<SignalHandle_t> m_sig_handles;
vector<PollHandle_t> m_poll_handles;
vector<FSEventHandle_t> m_fs_event_handles;
vector<PipeHandle_t> m_pipe_handles;
vector<TimerHandle_t> m_timer_handles;
vector<AsyncHandle_t> m_async_handles;
};
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

View File

@ -1,46 +0,0 @@
#pragma once
#include <uv.h>
#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<ipc>;
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

View File

@ -13,7 +13,7 @@ POLYBAR_NS
namespace chrono = std::chrono;
namespace drawtypes {
class animation : public non_copyable_mixin<animation> {
class animation : public non_copyable_mixin {
public:
explicit animation(unsigned int framerate_ms) : m_framerate_ms(framerate_ms) {}
explicit animation(vector<label_t>&& frames, int framerate_ms)

View File

@ -9,7 +9,7 @@
POLYBAR_NS
namespace drawtypes {
class iconset : public non_copyable_mixin<iconset> {
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<iconset>;
}
} // namespace drawtypes
POLYBAR_NS_END

View File

@ -18,7 +18,7 @@ namespace drawtypes {
bool zpad{false};
};
class label : public non_copyable_mixin<label> {
class label : public non_copyable_mixin {
public:
rgba m_foreground{};
rgba m_background{};

View File

@ -12,7 +12,7 @@ using std::tuple;
POLYBAR_NS
namespace drawtypes {
class layouticonset : public non_copyable_mixin<layouticonset> {
class layouticonset : public non_copyable_mixin {
public:
explicit layouticonset(label_t&& default_icon);

View File

@ -9,7 +9,7 @@
POLYBAR_NS
namespace drawtypes {
class progressbar : public non_copyable_mixin<progressbar> {
class progressbar : public non_copyable_mixin {
public:
explicit progressbar(const bar_settings& bar, int width, string format);

View File

@ -8,7 +8,7 @@
POLYBAR_NS
namespace drawtypes {
class ramp : public non_copyable_mixin<ramp> {
class ramp : public non_copyable_mixin {
public:
explicit ramp() = default;
explicit ramp(vector<label_t>&& icons) : m_icons(forward<decltype(icons)>(icons)) {}
@ -28,6 +28,6 @@ namespace drawtypes {
using ramp_t = shared_ptr<ramp>;
ramp_t load_ramp(const config& conf, const string& section, string name, bool required = true);
}
} // namespace drawtypes
POLYBAR_NS_END

View File

@ -1,7 +1,6 @@
#pragma once
#include "common.hpp"
#include "components/ipc.hpp"
#include "components/types.hpp"
#include "utils/functional.hpp"
@ -35,17 +34,17 @@ namespace signals {
using base_type = value_signal<Derived, ValueType>;
explicit value_signal(void* data) : m_ptr(data) {}
explicit value_signal(ValueType&& data) : m_ptr(&data) {}
explicit value_signal(ValueType& data) : m_ptr(&data) {}
explicit value_signal(const ValueType&& data) : m_ptr(&data) {}
explicit value_signal(const ValueType& data) : m_ptr(&data) {}
virtual ~value_signal() {}
inline ValueType cast() const {
return *static_cast<ValueType*>(m_ptr);
inline const ValueType cast() const {
return *static_cast<const ValueType*>(m_ptr);
}
private:
void* m_ptr;
const void* m_ptr;
};
} // namespace detail

61
include/ipc/decoder.hpp Normal file
View File

@ -0,0 +1,61 @@
#pragma once
#include "common.hpp"
#include "components/logger.hpp"
#include "errors.hpp"
#include "ipc/msg.hpp"
POLYBAR_NS
namespace ipc {
/**
* Decoder for the IPC message format.
*/
class decoder {
public:
DEFINE_ERROR(error);
/**
* Callback is called whenever a full message is received.
* The message version, message type, and the data is passed.
*/
using cb = std::function<void(uint8_t, type_t, const std::vector<uint8_t>&)>;
decoder(const logger&, cb callback);
/**
* Call this function whenever new data arrives.
*
* Will throw deocder::error in case of error.
* If an error is thrown, this instance is closed and this function may not be called again.
*/
void on_read(const uint8_t* buf, size_t size);
void close() noexcept;
bool closed() const;
protected:
void process_data(const uint8_t*, size_t size);
ssize_t process_header_data(const uint8_t*, size_t size);
ssize_t process_msg_data(const uint8_t*, size_t size);
ipc::header header;
size_t to_read_header{ipc::HEADER_SIZE};
std::vector<uint8_t> buf;
size_t to_read_buf{0};
cb callback;
private:
enum class state {
// Waiting for header data (header does not contain full header)
HEADER,
// Waiting for message data (header contains valid header)
PAYLOAD,
CLOSED,
} state{state::HEADER};
const logger& m_log;
};
} // namespace ipc
POLYBAR_NS_END

14
include/ipc/encoder.hpp Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "common.hpp"
#include "errors.hpp"
#include "ipc/msg.hpp"
POLYBAR_NS
namespace ipc {
vector<uint8_t> encode(const type_t type, const vector<uint8_t>& data = {});
vector<uint8_t> encode(const type_t type, const string& data);
} // namespace ipc
POLYBAR_NS_END

98
include/ipc/ipc.hpp Normal file
View File

@ -0,0 +1,98 @@
#pragma once
#include <uv.h>
#include <set>
#include "common.hpp"
#include "components/eventloop.hpp"
#include "ipc/decoder.hpp"
#include "settings.hpp"
#include "utils/concurrency.hpp"
POLYBAR_NS
class signal_emitter;
class logger;
namespace ipc {
/**
* 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 : non_copyable_mixin, non_movable_mixin {
public:
using make_type = unique_ptr<ipc>;
static make_type make(eventloop::eventloop& loop);
explicit ipc(signal_emitter& emitter, const logger& logger, eventloop::eventloop& loop);
~ipc();
static string get_socket_path(int pid);
protected:
bool trigger_ipc(v0::ipc_type type, const string& msg);
void trigger_legacy_ipc(const string& msg);
void on_connection();
private:
signal_emitter& m_sig;
const logger& m_log;
eventloop::eventloop& m_loop;
eventloop::PipeHandle& socket;
class connection : public non_movable_mixin {
public:
using cb = std::function<void(connection&, uint8_t, type_t, const std::vector<uint8_t>&)>;
connection(eventloop::eventloop& loop, cb msg_callback);
eventloop::PipeHandle& client_pipe;
decoder dec;
};
void remove_client(connection& conn);
/**
* Custom transparent comparator so that we can lookup and erase connections from their reference.
*/
struct connection_cmp {
using is_transparent = std::true_type;
bool operator()(const unique_ptr<connection>& a, const unique_ptr<connection>& b) const {
return a.get() < b.get();
}
bool operator()(const connection& a, const unique_ptr<connection>& b) const {
return &a < b.get();
}
bool operator()(const unique_ptr<connection>& a, const connection& b) const {
return a.get() < &b;
}
};
std::set<unique_ptr<connection>, connection_cmp> connections;
// Named pipe properties (deprecated)
struct fifo {
fifo(eventloop::eventloop& loop, ipc& ipc, const string& path);
~fifo();
eventloop::PipeHandle& pipe_handle;
};
unique_ptr<fifo> ipc_pipe;
string m_pipe_path{};
/**
* Buffer for the currently received IPC message over the named pipe
*/
string m_pipe_buffer{};
void receive_data(string buf);
void receive_eof();
};
} // namespace ipc
POLYBAR_NS_END

83
include/ipc/msg.hpp Normal file
View File

@ -0,0 +1,83 @@
#pragma once
#include "common.hpp"
POLYBAR_NS
/**
* Defines the binary message format for IPC communications over the IPC socket.
*
* This is an internal API, do not connect to the socket using 3rd party programs, always use `polybar-msg`.
*/
namespace ipc {
/**
* Magic string prefixed to every ipc message.
*
* THIS MUST NEVER CHANGE.
*/
static constexpr std::array<uint8_t, 7> MAGIC = {'p', 'o', 'l', 'y', 'i', 'p', 'c'};
static const string MAGIC_STR = string(reinterpret_cast<const char*>(MAGIC.data()), MAGIC.size());
static constexpr uint8_t VERSION = 0;
using type_t = uint8_t;
/**
* Message type indicating success.
*/
static constexpr type_t TYPE_OK = 0;
static constexpr type_t TYPE_ERR = 255;
union header {
struct header_data {
uint8_t magic[MAGIC.size()];
/**
* Version number of the message format.
*/
uint8_t version;
/**
* Size of the following message in bytes
*/
uint32_t size;
/**
* Type of the message that follows.
*
* Meaning of the values depend on version.
* Only TYPE_OK(0) indicate success and TYPE_ERR(255) always indicates an error, in which case the entire message
* is a string.
*/
type_t type;
} __attribute__((packed)) s;
uint8_t d[sizeof(header_data)];
};
/**
* Size of the standard header shared by all versions.
*
* THIS MUST NEVER CHANGE.
*/
static constexpr size_t HEADER_SIZE = 13;
static_assert(sizeof(header) == HEADER_SIZE, "");
static_assert(sizeof(header::header_data) == HEADER_SIZE, "");
/**
* Definitions for version 0 of the IPC message format.
*
* The format is very simple. The header defines the type (cmd or action) and the payload is the message for that type
* as a string.
*/
namespace v0 {
enum class ipc_type : type_t {
/**
* Message type for ipc commands
*/
CMD = 1,
/**
* Message type for ipc module actions
*/
ACTION = 2,
};
}
} // namespace ipc
POLYBAR_NS_END

16
include/ipc/util.hpp Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "common.hpp"
POLYBAR_NS
namespace ipc {
string get_runtime_path();
string ensure_runtime_path();
string get_socket_path(const string& pid_string);
string get_socket_path(int pid);
string get_glob_socket_path();
int get_pid_from_socket(const string& path);
} // namespace ipc
POLYBAR_NS_END

View File

@ -13,6 +13,7 @@ namespace actions_util {
using action = std::tuple<string, string, string>;
string get_action_string(const modules::module_interface& module, string action, string data);
string get_action_string(const string& module_name, string action, string data);
/**
* Parses an action string of the form "#name.action[.data]".

View File

@ -7,29 +7,27 @@ POLYBAR_NS
/**
* Base class for non copyable objects
*/
template <class T>
class non_copyable_mixin {
public:
non_copyable_mixin(const non_copyable_mixin&) = delete;
non_copyable_mixin& operator=(const non_copyable_mixin&) = delete;
protected:
non_copyable_mixin() {}
~non_copyable_mixin() {}
private:
non_copyable_mixin(const non_copyable_mixin&);
non_copyable_mixin& operator=(const non_copyable_mixin&);
};
/**
* Base class for non movable objects
*/
template <class T>
class non_movable_mixin {
public:
non_movable_mixin(non_movable_mixin&&) = delete;
non_movable_mixin& operator=(non_movable_mixin&&) = delete;
protected:
non_movable_mixin() {}
~non_movable_mixin() {}
private:
non_movable_mixin(non_movable_mixin&&);
non_movable_mixin& operator=(non_movable_mixin&&);
};
POLYBAR_NS_END

View File

@ -5,234 +5,233 @@
get_include_dirs(includes_dir)
get_sources_dirs(src_dir)
if(BUILD_LIBPOLY)
# Source tree {{{
# Source tree {{{
set(ALSA_SOURCES
${src_dir}/adapters/alsa/control.cpp
${src_dir}/adapters/alsa/mixer.cpp
${src_dir}/modules/alsa.cpp
)
set(ALSA_SOURCES
${src_dir}/adapters/alsa/control.cpp
${src_dir}/adapters/alsa/mixer.cpp
${src_dir}/modules/alsa.cpp
)
set(GITHUB_SOURCES ${src_dir}/modules/github.cpp ${src_dir}/utils/http.cpp)
set(GITHUB_SOURCES ${src_dir}/modules/github.cpp ${src_dir}/utils/http.cpp)
set(I3_SOURCES
${src_dir}/modules/i3.cpp
${src_dir}/utils/i3.cpp
)
set(I3_SOURCES
${src_dir}/modules/i3.cpp
${src_dir}/utils/i3.cpp
)
set(MPD_SOURCES
${src_dir}/adapters/mpd.cpp
${src_dir}/modules/mpd.cpp
)
set(MPD_SOURCES
${src_dir}/adapters/mpd.cpp
${src_dir}/modules/mpd.cpp
)
set(NETWORK_SOURCES
${src_dir}/adapters/net.cpp
${src_dir}/modules/network.cpp
$<IF:$<BOOL:${WITH_LIBNL}>,${src_dir}/adapters/net_nl.cpp,${src_dir}/adapters/net_iw.cpp>
)
set(NETWORK_SOURCES
${src_dir}/adapters/net.cpp
${src_dir}/modules/network.cpp
$<IF:$<BOOL:${WITH_LIBNL}>,${src_dir}/adapters/net_nl.cpp,${src_dir}/adapters/net_iw.cpp>
)
set(PULSEAUDIO_SOURCES
${src_dir}/adapters/pulseaudio.cpp
${src_dir}/modules/pulseaudio.cpp
)
set(PULSEAUDIO_SOURCES
${src_dir}/adapters/pulseaudio.cpp
${src_dir}/modules/pulseaudio.cpp
)
set(XCURSOR_SOURCES ${src_dir}/x11/cursor.cpp)
set(XCURSOR_SOURCES ${src_dir}/x11/cursor.cpp)
set(XKB_SOURCES
${src_dir}/modules/xkeyboard.cpp
${src_dir}/x11/extensions/xkb.cpp
)
set(XKB_SOURCES
${src_dir}/modules/xkeyboard.cpp
${src_dir}/x11/extensions/xkb.cpp
)
set(XRM_SOURCES ${src_dir}/x11/xresources.cpp)
set(XRM_SOURCES ${src_dir}/x11/xresources.cpp)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/settings.cpp.cmake
${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
ESCAPE_QUOTES)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/settings.cpp.cmake
${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
ESCAPE_QUOTES)
set(POLY_SOURCES
${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
set(POLY_SOURCES
${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
${src_dir}/adapters/script_runner.cpp
${src_dir}/adapters/script_runner.cpp
${src_dir}/cairo/utils.cpp
${src_dir}/cairo/utils.cpp
${src_dir}/components/bar.cpp
${src_dir}/components/builder.cpp
${src_dir}/components/command_line.cpp
${src_dir}/components/config.cpp
${src_dir}/components/config_parser.cpp
${src_dir}/components/controller.cpp
${src_dir}/components/ipc.cpp
${src_dir}/components/logger.cpp
${src_dir}/components/renderer.cpp
${src_dir}/components/screen.cpp
${src_dir}/components/eventloop.cpp
${src_dir}/components/bar.cpp
${src_dir}/components/builder.cpp
${src_dir}/components/command_line.cpp
${src_dir}/components/config.cpp
${src_dir}/components/config_parser.cpp
${src_dir}/components/controller.cpp
${src_dir}/components/logger.cpp
${src_dir}/components/renderer.cpp
${src_dir}/components/screen.cpp
${src_dir}/components/eventloop.cpp
${src_dir}/drawtypes/animation.cpp
${src_dir}/drawtypes/iconset.cpp
${src_dir}/drawtypes/layouticonset.cpp
${src_dir}/drawtypes/label.cpp
${src_dir}/drawtypes/progressbar.cpp
${src_dir}/drawtypes/ramp.cpp
${src_dir}/drawtypes/animation.cpp
${src_dir}/drawtypes/iconset.cpp
${src_dir}/drawtypes/layouticonset.cpp
${src_dir}/drawtypes/label.cpp
${src_dir}/drawtypes/progressbar.cpp
${src_dir}/drawtypes/ramp.cpp
${src_dir}/events/signal_emitter.cpp
${src_dir}/events/signal_receiver.cpp
${src_dir}/events/signal_emitter.cpp
${src_dir}/events/signal_receiver.cpp
${src_dir}/modules/backlight.cpp
${src_dir}/modules/battery.cpp
${src_dir}/modules/bspwm.cpp
${src_dir}/modules/counter.cpp
${src_dir}/modules/cpu.cpp
${src_dir}/modules/date.cpp
${src_dir}/modules/fs.cpp
${src_dir}/modules/ipc.cpp
${src_dir}/modules/memory.cpp
${src_dir}/modules/menu.cpp
${src_dir}/modules/meta/base.cpp
${src_dir}/modules/script.cpp
${src_dir}/modules/systray.cpp
${src_dir}/modules/temperature.cpp
${src_dir}/modules/text.cpp
${src_dir}/modules/xbacklight.cpp
${src_dir}/modules/xwindow.cpp
${src_dir}/modules/xworkspaces.cpp
${src_dir}/ipc/ipc.cpp
${src_dir}/ipc/decoder.cpp
${src_dir}/ipc/encoder.cpp
${src_dir}/ipc/util.cpp
${src_dir}/tags/action_context.cpp
${src_dir}/tags/context.cpp
${src_dir}/tags/dispatch.cpp
${src_dir}/tags/parser.cpp
${src_dir}/modules/backlight.cpp
${src_dir}/modules/battery.cpp
${src_dir}/modules/bspwm.cpp
${src_dir}/modules/counter.cpp
${src_dir}/modules/cpu.cpp
${src_dir}/modules/date.cpp
${src_dir}/modules/fs.cpp
${src_dir}/modules/ipc.cpp
${src_dir}/modules/memory.cpp
${src_dir}/modules/menu.cpp
${src_dir}/modules/meta/base.cpp
${src_dir}/modules/script.cpp
${src_dir}/modules/systray.cpp
${src_dir}/modules/temperature.cpp
${src_dir}/modules/text.cpp
${src_dir}/modules/xbacklight.cpp
${src_dir}/modules/xwindow.cpp
${src_dir}/modules/xworkspaces.cpp
${src_dir}/utils/actions.cpp
${src_dir}/utils/action_router.cpp
${src_dir}/utils/bspwm.cpp
${src_dir}/utils/color.cpp
${src_dir}/utils/command.cpp
${src_dir}/utils/concurrency.cpp
${src_dir}/utils/env.cpp
${src_dir}/utils/factory.cpp
${src_dir}/utils/file.cpp
${src_dir}/utils/inotify.cpp
${src_dir}/utils/io.cpp
${src_dir}/utils/process.cpp
${src_dir}/utils/socket.cpp
${src_dir}/utils/string.cpp
${src_dir}/tags/action_context.cpp
${src_dir}/tags/context.cpp
${src_dir}/tags/dispatch.cpp
${src_dir}/tags/parser.cpp
${src_dir}/x11/atoms.cpp
${src_dir}/x11/background_manager.cpp
${src_dir}/x11/connection.cpp
${src_dir}/x11/ewmh.cpp
${src_dir}/x11/extensions/composite.cpp
${src_dir}/x11/extensions/randr.cpp
${src_dir}/x11/icccm.cpp
${src_dir}/x11/registry.cpp
${src_dir}/x11/tray_client.cpp
${src_dir}/x11/tray_manager.cpp
${src_dir}/x11/window.cpp
${src_dir}/x11/winspec.cpp
${src_dir}/x11/xembed.cpp
${src_dir}/utils/actions.cpp
${src_dir}/utils/action_router.cpp
${src_dir}/utils/bspwm.cpp
${src_dir}/utils/color.cpp
${src_dir}/utils/command.cpp
${src_dir}/utils/concurrency.cpp
${src_dir}/utils/env.cpp
${src_dir}/utils/factory.cpp
${src_dir}/utils/file.cpp
${src_dir}/utils/inotify.cpp
${src_dir}/utils/io.cpp
${src_dir}/utils/process.cpp
${src_dir}/utils/socket.cpp
${src_dir}/utils/string.cpp
$<$<BOOL:${ENABLE_ALSA}>:${ALSA_SOURCES}>
$<$<BOOL:${ENABLE_CURL}>:${GITHUB_SOURCES}>
$<$<BOOL:${ENABLE_I3}>:${I3_SOURCES}>
$<$<BOOL:${ENABLE_MPD}>:${MPD_SOURCES}>
$<$<BOOL:${ENABLE_NETWORK}>:${NETWORK_SOURCES}>
$<$<BOOL:${ENABLE_PULSEAUDIO}>:${PULSEAUDIO_SOURCES}>
$<$<BOOL:${WITH_XCURSOR}>:${XCURSOR_SOURCES}>
$<$<BOOL:${WITH_XKB}>:${XKB_SOURCES}>
$<$<BOOL:${WITH_XRM}>:${XRM_SOURCES}>
)
${src_dir}/x11/atoms.cpp
${src_dir}/x11/background_manager.cpp
${src_dir}/x11/connection.cpp
${src_dir}/x11/ewmh.cpp
${src_dir}/x11/extensions/composite.cpp
${src_dir}/x11/extensions/randr.cpp
${src_dir}/x11/icccm.cpp
${src_dir}/x11/registry.cpp
${src_dir}/x11/tray_client.cpp
${src_dir}/x11/tray_manager.cpp
${src_dir}/x11/window.cpp
${src_dir}/x11/winspec.cpp
${src_dir}/x11/xembed.cpp
# }}}
$<$<BOOL:${ENABLE_ALSA}>:${ALSA_SOURCES}>
$<$<BOOL:${ENABLE_CURL}>:${GITHUB_SOURCES}>
$<$<BOOL:${ENABLE_I3}>:${I3_SOURCES}>
$<$<BOOL:${ENABLE_MPD}>:${MPD_SOURCES}>
$<$<BOOL:${ENABLE_NETWORK}>:${NETWORK_SOURCES}>
$<$<BOOL:${ENABLE_PULSEAUDIO}>:${PULSEAUDIO_SOURCES}>
$<$<BOOL:${WITH_XCURSOR}>:${XCURSOR_SOURCES}>
$<$<BOOL:${WITH_XKB}>:${XKB_SOURCES}>
$<$<BOOL:${WITH_XRM}>:${XRM_SOURCES}>
)
# Target poly {{{
add_library(poly STATIC EXCLUDE_FROM_ALL ${POLY_SOURCES})
target_include_directories(poly PUBLIC ${includes_dir})
target_link_libraries(poly PUBLIC
Threads::Threads
Cairo::CairoFC
xpp
LibUV::LibUV
)
# }}}
if (TARGET i3ipc++)
target_link_libraries(poly PUBLIC i3ipc++)
endif()
# Target poly {{{
add_library(poly STATIC EXCLUDE_FROM_ALL ${POLY_SOURCES})
target_include_directories(poly PUBLIC ${includes_dir})
target_link_libraries(poly PUBLIC
Threads::Threads
Cairo::CairoFC
xpp
LibUV::LibUV
)
if (TARGET ALSA::ALSA)
target_link_libraries(poly PUBLIC ALSA::ALSA)
endif()
if (TARGET CURL::libcurl)
target_link_libraries(poly PUBLIC CURL::libcurl)
endif()
if (TARGET LibMPDClient::LibMPDClient)
target_link_libraries(poly PUBLIC LibMPDClient::LibMPDClient)
endif()
if (TARGET LibNlGenl3::LibNlGenl3)
target_link_libraries(poly PUBLIC LibNlGenl3::LibNlGenl3)
endif()
if (TARGET Libiw::Libiw)
target_link_libraries(poly PUBLIC Libiw::Libiw)
endif()
if (TARGET LibPulse::LibPulse)
target_link_libraries(poly PUBLIC LibPulse::LibPulse)
endif()
if (TARGET Xcb::RANDR)
target_link_libraries(poly PUBLIC Xcb::RANDR)
endif()
if (TARGET Xcb::COMPOSITE)
target_link_libraries(poly PUBLIC Xcb::COMPOSITE)
endif()
if (TARGET Xcb::XKB)
target_link_libraries(poly PUBLIC Xcb::XKB)
endif()
if (TARGET Xcb::CURSOR)
target_link_libraries(poly PUBLIC Xcb::CURSOR)
endif()
if (TARGET Xcb::XRM)
target_link_libraries(poly PUBLIC Xcb::XRM)
endif()
if (TARGET LibInotify::LibInotify)
target_link_libraries(poly PUBLIC LibInotify::LibInotify)
endif()
target_compile_options(poly PUBLIC ${cxx_flags})
set_target_properties(poly PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/libs)
# }}}
# Target: polybar {{{
if(BUILD_POLYBAR)
add_executable(polybar main.cpp)
target_link_libraries(polybar poly)
target_compile_options(polybar PUBLIC ${cxx_flags})
set_target_properties(polybar PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
install(TARGETS polybar
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT runtime)
endif()
# }}}
if (TARGET i3ipc++)
target_link_libraries(poly PUBLIC i3ipc++)
endif()
if (TARGET ALSA::ALSA)
target_link_libraries(poly PUBLIC ALSA::ALSA)
endif()
if (TARGET CURL::libcurl)
target_link_libraries(poly PUBLIC CURL::libcurl)
endif()
if (TARGET LibMPDClient::LibMPDClient)
target_link_libraries(poly PUBLIC LibMPDClient::LibMPDClient)
endif()
if (TARGET LibNlGenl3::LibNlGenl3)
target_link_libraries(poly PUBLIC LibNlGenl3::LibNlGenl3)
endif()
if (TARGET Libiw::Libiw)
target_link_libraries(poly PUBLIC Libiw::Libiw)
endif()
if (TARGET LibPulse::LibPulse)
target_link_libraries(poly PUBLIC LibPulse::LibPulse)
endif()
if (TARGET Xcb::RANDR)
target_link_libraries(poly PUBLIC Xcb::RANDR)
endif()
if (TARGET Xcb::COMPOSITE)
target_link_libraries(poly PUBLIC Xcb::COMPOSITE)
endif()
if (TARGET Xcb::XKB)
target_link_libraries(poly PUBLIC Xcb::XKB)
endif()
if (TARGET Xcb::CURSOR)
target_link_libraries(poly PUBLIC Xcb::CURSOR)
endif()
if (TARGET Xcb::XRM)
target_link_libraries(poly PUBLIC Xcb::XRM)
endif()
if (TARGET LibInotify::LibInotify)
target_link_libraries(poly PUBLIC LibInotify::LibInotify)
endif()
target_compile_options(poly PUBLIC ${cxx_flags})
set_target_properties(poly PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/libs)
# }}}
# Target: polybar {{{
if(BUILD_POLYBAR)
add_executable(polybar main.cpp)
target_link_libraries(polybar poly)
target_compile_options(polybar PUBLIC ${cxx_flags})
set_target_properties(polybar PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
install(TARGETS polybar
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT runtime)
endif()
# }}}
# Target: polybar-msg {{{
if(BUILD_POLYBAR_MSG)
add_executable(polybar-msg
ipc.cpp
utils/env.cpp
utils/file.cpp
utils/string.cpp)
add_executable(polybar-msg polybar-msg.cpp)
target_link_libraries(polybar-msg poly)
target_include_directories(polybar-msg PRIVATE ${includes_dir})
target_compile_options(polybar-msg PUBLIC ${cxx_flags})

View File

@ -36,7 +36,7 @@ using namespace signals::ui;
/**
* Create instance
*/
bar::make_type bar::make(eventloop& loop, bool only_initialize_values) {
bar::make_type bar::make(eventloop::eventloop& loop, bool only_initialize_values) {
auto action_ctxt = make_unique<tags::action_context>();
// clang-format off
@ -59,9 +59,9 @@ bar::make_type bar::make(eventloop& loop, bool only_initialize_values) {
*
* TODO: Break out all tray handling
*/
bar::bar(connection& conn, signal_emitter& emitter, const config& config, const logger& logger, eventloop& loop,
unique_ptr<screen>&& screen, unique_ptr<tray_manager>&& tray_manager, unique_ptr<tags::dispatch>&& dispatch,
unique_ptr<tags::action_context>&& action_ctxt, bool only_initialize_values)
bar::bar(connection& conn, signal_emitter& emitter, const config& config, const logger& logger,
eventloop::eventloop& loop, unique_ptr<screen>&& screen, unique_ptr<tray_manager>&& tray_manager,
unique_ptr<tags::dispatch>&& dispatch, unique_ptr<tags::action_context>&& action_ctxt, bool only_initialize_values)
: m_connection(conn)
, m_sig(emitter)
, m_conf(config)
@ -613,12 +613,12 @@ void bar::handle(const evt::destroy_notify& evt) {
*/
void bar::handle(const evt::enter_notify&) {
if (m_opts.dimmed) {
m_dim_timer->start(25, 0, [this]() {
m_dim_timer.start(25, 0, [this]() {
m_opts.dimmed = false;
m_sig.emit(dim_window{1.0});
});
} else if (m_dim_timer->is_active()) {
m_dim_timer->stop();
} else if (m_dim_timer.is_active()) {
m_dim_timer.stop();
}
}
@ -632,7 +632,7 @@ void bar::handle(const evt::leave_notify&) {
// Only trigger dimming, if the dim-value is not fully opaque.
if (m_opts.dimvalue < 1.0) {
if (!m_opts.dimmed) {
m_dim_timer->start(3000, 0, [this]() {
m_dim_timer.start(3000, 0, [this]() {
m_opts.dimmed = true;
m_sig.emit(dim_window{double(m_opts.dimvalue)});
});
@ -737,11 +737,11 @@ void bar::handle(const evt::button_press& evt) {
* the configured interval and if in that time another click arrives, we
* need to trigger a double click.
*/
const auto check_double = [this](TimerHandle_t handle, mousebtn btn, int pos) {
if (!handle->is_active()) {
handle->start(m_opts.double_click_interval, 0, [=]() { trigger_click(btn, pos); });
const auto check_double = [this](eventloop::TimerHandle& handle, mousebtn btn, int pos) {
if (!handle.is_active()) {
handle.start(m_opts.double_click_interval, 0, [=]() { trigger_click(btn, pos); });
} else {
handle->stop();
handle.stop();
trigger_click(mousebtn_get_double(btn), pos);
}
};

View File

@ -9,7 +9,6 @@
#include "components/builder.hpp"
#include "components/config.hpp"
#include "components/eventloop.hpp"
#include "components/ipc.hpp"
#include "components/logger.hpp"
#include "components/types.hpp"
#include "events/signal.hpp"
@ -30,23 +29,23 @@ POLYBAR_NS
/**
* Build controller instance
*/
controller::make_type controller::make(unique_ptr<ipc>&& ipc) {
controller::make_type controller::make(bool has_ipc, eventloop::eventloop& loop) {
return std::make_unique<controller>(
connection::make(), signal_emitter::make(), logger::make(), config::make(), forward<decltype(ipc)>(ipc));
connection::make(), signal_emitter::make(), logger::make(), config::make(), has_ipc, loop);
}
/**
* Construct controller
*/
controller::controller(
connection& conn, signal_emitter& emitter, const logger& logger, const config& config, unique_ptr<ipc>&& ipc)
controller::controller(connection& conn, signal_emitter& emitter, const logger& logger, const config& config,
bool has_ipc, eventloop::eventloop& loop)
: m_connection(conn)
, m_sig(emitter)
, m_log(logger)
, m_conf(config)
, m_loop(make_unique<eventloop>())
, m_bar(bar::make(*m_loop))
, m_ipc(forward<decltype(ipc)>(ipc)) {
, m_loop(loop)
, m_bar(bar::make(m_loop))
, m_has_ipc(has_ipc) {
m_conf.ignore_key("settings", "throttle-input-for");
m_conf.ignore_key("settings", "throttle-output");
m_conf.ignore_key("settings", "throttle-output-for");
@ -153,17 +152,15 @@ void controller::trigger_update(bool force) {
}
void controller::trigger_notification() {
if (m_loop_ready) {
m_notifier->send();
}
m_notifier.send();
}
void controller::stop(bool reload) {
update_reload(reload);
m_loop->stop();
m_loop.stop();
}
void controller::conn_cb(uv_poll_event) {
void controller::conn_cb() {
int xcb_error = m_connection.connection_has_error();
if ((xcb_error = m_connection.connection_has_error()) != 0) {
m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(xcb_error));
@ -196,7 +193,7 @@ void controller::signal_handler(int signum) {
stop(signum == SIGUSR1);
}
void controller::confwatch_handler(const char* filename, uv_fs_event) {
void controller::confwatch_handler(const char* filename) {
m_log.notice("Watched config file changed %s", filename);
stop(true);
}
@ -210,8 +207,7 @@ void controller::notifier_handler() {
}
if (data.quit) {
update_reload(data.reload);
m_loop->stop();
stop(data.reload);
return;
}
@ -239,36 +235,33 @@ void controller::read_events(bool confwatch) {
m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id());
try {
m_loop->poll_handle(
UV_READABLE, m_connection.get_file_descriptor(), [this](uv_poll_event events) { conn_cb(events); },
[](int status) { throw runtime_error("libuv error while polling X connection: "s + uv_strerror(status)); });
auto& poll_handle = m_loop.handle<eventloop::PollHandle>(m_connection.get_file_descriptor());
poll_handle.start(
UV_READABLE, [this](const auto&) { conn_cb(); },
[](const auto& e) {
throw runtime_error("libuv error while polling X connection: "s + uv_strerror(e.status));
});
for (auto s : {SIGINT, SIGQUIT, SIGTERM, SIGUSR1, SIGALRM}) {
m_loop->signal_handle(s, [this](int signum) { signal_handler(signum); });
auto& signal_handle = m_loop.handle<eventloop::SignalHandle>();
signal_handle.start(s, [this](const auto& e) { signal_handler(e.signum); });
}
if (confwatch) {
m_loop->fs_event_handle(
m_conf.filepath(), [this](const char* path, uv_fs_event events) { confwatch_handler(path, events); },
[this](int err) { m_log.err("libuv error while watching config file for changes: %s", uv_strerror(err)); });
}
if (m_ipc) {
m_loop->pipe_handle(
m_ipc->get_path(), [this](const string payload) { m_ipc->receive_data(payload); },
[this]() { m_ipc->receive_eof(); },
[this](int err) { m_log.err("libuv error while listening to IPC channel: %s", uv_strerror(err)); });
auto& fs_event_handle = m_loop.handle<eventloop::FSEventHandle>();
fs_event_handle.start(
m_conf.filepath(), 0, [this](const auto& e) { confwatch_handler(e.path); },
[this](const auto& e) {
m_log.err("libuv error while watching config file for changes: %s", uv_strerror(e.status));
});
}
if (!m_snapshot_dst.empty()) {
// Trigger a single screenshot after 3 seconds
m_loop->timer_handle([this]() { screenshot_handler(); })->start(3000, 0);
auto& timer_handle = m_loop.handle<eventloop::TimerHandle>();
timer_handle.start(3000, 0, [this]() { screenshot_handler(); });
}
m_notifier = m_loop->async_handle([this]() { notifier_handler(); });
m_loop_ready = true;
if (!m_writeback) {
m_bar->start();
}
@ -278,16 +271,13 @@ void controller::read_events(bool confwatch) {
*/
trigger_update(true);
m_loop->run();
m_loop.run();
} catch (const exception& err) {
m_log.err("Fatal Error in eventloop: %s", err.what());
stop(false);
}
m_log.info("Eventloop finished");
m_loop_ready = false;
m_loop.reset();
}
/**
@ -464,6 +454,7 @@ void controller::process_inputdata(string&& cmd) {
m_log.err("controller: Error while forwarding input to shell -> %s", err.what());
}
}
/**
* Process eventqueue update event
*/
@ -598,7 +589,7 @@ size_t controller::setup_modules(alignment align) {
try {
auto type = m_conf.get("module/" + module_name, "type");
if (type == ipc_module::TYPE && !m_ipc) {
if (type == ipc_module::TYPE && !m_has_ipc) {
throw application_error("Inter-process messaging needs to be enabled");
}
@ -707,6 +698,7 @@ bool controller::on(const signals::ipc::command& evt) {
m_bar->toggle();
} else {
m_log.warn("\"%s\" is not a valid ipc command", command);
return false;
}
return true;

View File

@ -10,256 +10,204 @@
POLYBAR_NS
/**
* Closes the given wrapper.
*
* We have to have distinct cases for all types because we can't just cast to `UVHandleGeneric` without template
* arguments.
*/
void close_callback(uv_handle_t* handle) {
switch (handle->type) {
case UV_ASYNC:
static_cast<AsyncHandle*>(handle->data)->cleanup_resources();
break;
case UV_FS_EVENT:
static_cast<FSEventHandle*>(handle->data)->cleanup_resources();
break;
case UV_POLL:
static_cast<PollHandle*>(handle->data)->cleanup_resources();
break;
case UV_TIMER:
static_cast<TimerHandle*>(handle->data)->cleanup_resources();
break;
case UV_SIGNAL:
static_cast<SignalHandle*>(handle->data)->cleanup_resources();
break;
case UV_NAMED_PIPE:
static_cast<PipeHandle*>(handle->data)->cleanup_resources();
break;
default:
assert(false);
}
}
namespace eventloop {
static void alloc_cb(uv_handle_t*, size_t, uv_buf_t* buf) {
buf->base = new char[BUFSIZ];
buf->len = BUFSIZ;
}
// SignalHandle {{{
SignalHandle::SignalHandle(uv_loop_t* loop, function<void(int)> fun) : UVHandle(fun) {
UV(uv_signal_init, loop, handle);
}
void SignalHandle::start(int signum) {
UV(uv_signal_start, handle, callback, signum);
}
// }}}
// PollHandle {{{
PollHandle::PollHandle(uv_loop_t* loop, int fd, function<void(uv_poll_event)> fun, function<void(int)> err_cb)
: UVHandle([this](int status, int events) { poll_cb(status, events); }), func(fun), err_cb(err_cb) {
UV(uv_poll_init, loop, handle, fd);
}
void PollHandle::start(int events) {
UV(uv_poll_start, handle, events, callback);
}
void PollHandle::poll_cb(int status, int events) {
if (status < 0) {
close();
err_cb(status);
return;
}
func((uv_poll_event)events);
}
// }}}
// FSEventHandle {{{
FSEventHandle::FSEventHandle(uv_loop_t* loop, function<void(const char*, uv_fs_event)> fun, function<void(int)> err_cb)
: UVHandle([this](const char* path, int events, int status) { fs_event_cb(path, events, status); })
, func(fun)
, err_cb(err_cb) {
UV(uv_fs_event_init, loop, handle);
}
void FSEventHandle::start(const string& path) {
UV(uv_fs_event_start, handle, callback, path.c_str(), 0);
}
void FSEventHandle::fs_event_cb(const char* path, int events, int status) {
if (status < 0) {
close();
err_cb(status);
return;
}
func(path, (uv_fs_event)events);
}
// }}}
// PipeHandle {{{
PipeHandle::PipeHandle(uv_loop_t* loop, const string& path, function<void(const string)> fun,
function<void(void)> eof_cb, function<void(int)> err_cb)
: UVHandleGeneric([&](ssize_t nread, const uv_buf_t* buf) { read_cb(nread, buf); })
, func(fun)
, eof_cb(eof_cb)
, err_cb(err_cb)
, path(path) {
UV(uv_pipe_init, loop, handle, false);
}
void PipeHandle::start() {
if ((fd = open(path.c_str(), O_RDONLY | O_NONBLOCK)) == -1) {
throw system_error("Failed to open pipe '" + path + "'");
}
UV(uv_pipe_open, handle, fd);
UV(uv_read_start, (uv_stream_t*)handle, alloc_cb, callback);
}
void PipeHandle::read_cb(ssize_t nread, const uv_buf_t* buf) {
/*
* Wrap pointer so that it gets automatically freed once the function returns (even with exceptions)
/**
* Closes the given wrapper.
*
* We have to have distinct cases for all types because we can't just cast to `Handle` without template
* arguments.
*/
auto buf_ptr = unique_ptr<char>(buf->base);
if (nread > 0) {
func(string(buf_ptr.get(), nread));
} else if (nread < 0) {
if (nread != UV_EOF) {
close();
err_cb(nread);
void close_handle(uv_handle_t* handle) {
switch (handle->type) {
case UV_ASYNC:
static_cast<AsyncHandle*>(handle->data)->close();
break;
case UV_FS_EVENT:
static_cast<FSEventHandle*>(handle->data)->close();
break;
case UV_POLL:
static_cast<PollHandle*>(handle->data)->close();
break;
case UV_TIMER:
static_cast<TimerHandle*>(handle->data)->close();
break;
case UV_SIGNAL:
static_cast<SignalHandle*>(handle->data)->close();
break;
case UV_NAMED_PIPE:
static_cast<PipeHandle*>(handle->data)->close();
break;
default:
assert(false);
}
}
// SignalHandle {{{
void SignalHandle::init() {
UV(uv_signal_init, loop(), get());
}
void SignalHandle::start(int signum, cb user_cb) {
this->callback = user_cb;
UV(uv_signal_start, get(), event_cb<SignalEvent, &SignalHandle::callback>, signum);
}
// }}}
// PollHandle {{{
void PollHandle::init(int fd) {
UV(uv_poll_init, loop(), get(), fd);
}
void PollHandle::start(int events, cb user_cb, cb_error err_cb) {
this->callback = user_cb;
this->err_cb = err_cb;
UV(uv_poll_start, get(), events, &poll_callback);
}
void PollHandle::poll_callback(uv_poll_t* handle, int status, int events) {
auto& self = cast(handle);
if (status < 0) {
self.close();
self.err_cb(ErrorEvent{status});
return;
}
self.callback(PollEvent{(uv_poll_event)events});
}
// }}}
// FSEventHandle {{{
void FSEventHandle::init() {
UV(uv_fs_event_init, loop(), get());
}
void FSEventHandle::start(const string& path, int flags, cb user_cb, cb_error err_cb) {
this->callback = user_cb;
this->err_cb = err_cb;
UV(uv_fs_event_start, get(), fs_event_callback, path.c_str(), flags);
}
void FSEventHandle::fs_event_callback(uv_fs_event_t* handle, const char* path, int events, int status) {
auto& self = cast(handle);
if (status < 0) {
self.close();
self.err_cb(ErrorEvent{status});
return;
}
self.callback(FSEvent{path, (uv_fs_event)events});
}
// }}}
// PipeHandle {{{
void PipeHandle::init(bool ipc) {
UV(uv_pipe_init, loop(), get(), ipc);
}
void PipeHandle::open(int fd) {
UV(uv_pipe_open, get(), fd);
}
void PipeHandle::bind(const string& path) {
UV(uv_pipe_bind, get(), path.c_str());
}
void PipeHandle::connect(const string& name, cb_connect user_cb, cb_error err_cb) {
this->connect_callback = user_cb;
this->connect_err_cb = err_cb;
uv_pipe_connect(new uv_connect_t(), get(), name.c_str(), connect_cb);
}
void PipeHandle::connect_cb(uv_connect_t* req, int status) {
auto& self = PipeHandle::cast((uv_pipe_t*)req->handle);
if (status < 0) {
self.connect_err_cb(ErrorEvent{status});
} else {
eof_cb();
self.connect_callback();
}
/*
* This is a special case.
*
* Once we read EOF, we no longer receive events for the fd, so we close the entire handle and restart it with a
* new fd.
*
* We reuse the memory for the underlying uv handle
*/
if (!is_closing()) {
uv_close((uv_handle_t*)handle, [](uv_handle_t* handle) {
PipeHandle* This = static_cast<PipeHandle*>(handle->data);
UV(uv_pipe_init, This->loop(), This->handle, false);
This->start();
});
delete req;
}
// }}}
// TimerHandle {{{
void TimerHandle::init() {
UV(uv_timer_init, loop(), get());
}
void TimerHandle::start(uint64_t timeout, uint64_t repeat, cb user_cb) {
this->callback = user_cb;
UV(uv_timer_start, get(), void_event_cb<&TimerHandle::callback>, timeout, repeat);
}
void TimerHandle::stop() {
UV(uv_timer_stop, get());
}
// }}}
// AsyncHandle {{{
void AsyncHandle::init(cb user_cb) {
this->callback = user_cb;
UV(uv_async_init, loop(), get(), void_event_cb<&AsyncHandle::callback>);
}
void AsyncHandle::send() {
UV(uv_async_send, get());
}
// }}}
// eventloop {{{
static void close_walk_cb(uv_handle_t* handle, void*) {
if (!uv_is_closing(handle)) {
close_handle(handle);
}
}
/**
* Completely closes everything in the loop.
*
* After this function returns, uv_loop_close can be called.
*/
static void close_loop(uv_loop_t* loop) {
uv_walk(loop, close_walk_cb, nullptr);
UV(uv_run, loop, UV_RUN_DEFAULT);
}
eventloop::eventloop() {
m_loop = std::make_unique<uv_loop_t>();
UV(uv_loop_init, m_loop.get());
m_loop->data = this;
}
eventloop::~eventloop() {
if (m_loop) {
try {
close_loop(m_loop.get());
UV(uv_loop_close, m_loop.get());
} catch (const std::exception& e) {
logger::make().err("%s", e.what());
}
m_loop.reset();
}
}
}
// }}}
// TimerHandle {{{
TimerHandle::TimerHandle(uv_loop_t* loop, function<void(void)> fun) : UVHandle(fun) {
UV(uv_timer_init, loop, handle);
}
void TimerHandle::start(uint64_t timeout, uint64_t repeat, function<void(void)> new_cb) {
if (new_cb) {
this->func = new_cb;
void eventloop::run() {
UV(uv_run, m_loop.get(), UV_RUN_DEFAULT);
}
UV(uv_timer_start, handle, callback, timeout, repeat);
}
void TimerHandle::stop() {
UV(uv_timer_stop, handle);
}
// }}}
// AsyncHandle {{{
AsyncHandle::AsyncHandle(uv_loop_t* loop, function<void(void)> fun) : UVHandle(fun) {
UV(uv_async_init, loop, handle, callback);
}
void AsyncHandle::send() {
UV(uv_async_send, handle);
}
// }}}
// eventloop {{{
static void close_walk_cb(uv_handle_t* handle, void*) {
if (!uv_is_closing(handle)) {
uv_close(handle, close_callback);
void eventloop::stop() {
uv_stop(m_loop.get());
}
}
/**
* Completely closes everything in the loop.
*
* After this function returns, uv_loop_close can be called.
*/
static void close_loop(uv_loop_t* loop) {
uv_walk(loop, close_walk_cb, nullptr);
UV(uv_run, loop, UV_RUN_DEFAULT);
}
eventloop::eventloop() {
m_loop = std::make_unique<uv_loop_t>();
UV(uv_loop_init, m_loop.get());
m_loop->data = this;
}
eventloop::~eventloop() {
if (m_loop) {
try {
close_loop(m_loop.get());
UV(uv_loop_close, m_loop.get());
} catch (const std::exception& e) {
logger::make().err("%s", e.what());
}
m_loop.reset();
uv_loop_t* eventloop::get() const {
return m_loop.get();
}
}
// }}}
void eventloop::run() {
UV(uv_run, m_loop.get(), UV_RUN_DEFAULT);
}
void eventloop::stop() {
uv_stop(m_loop.get());
}
uv_loop_t* eventloop::get() const {
return m_loop.get();
}
void eventloop::signal_handle(int signum, function<void(int)> fun) {
m_sig_handles.emplace_back(std::make_unique<SignalHandle>(get(), fun));
m_sig_handles.back()->start(signum);
}
void eventloop::poll_handle(int events, int fd, function<void(uv_poll_event)> fun, function<void(int)> err_cb) {
m_poll_handles.emplace_back(std::make_unique<PollHandle>(get(), fd, fun, err_cb));
m_poll_handles.back()->start(events);
}
void eventloop::fs_event_handle(
const string& path, function<void(const char*, uv_fs_event)> fun, function<void(int)> err_cb) {
m_fs_event_handles.emplace_back(std::make_unique<FSEventHandle>(get(), fun, err_cb));
m_fs_event_handles.back()->start(path);
}
void eventloop::pipe_handle(
const string& path, function<void(const string)> fun, function<void(void)> eof_cb, function<void(int)> err_cb) {
m_pipe_handles.emplace_back(std::make_unique<PipeHandle>(get(), path, fun, eof_cb, err_cb));
m_pipe_handles.back()->start();
}
TimerHandle_t eventloop::timer_handle(function<void(void)> fun) {
m_timer_handles.emplace_back(std::make_shared<TimerHandle>(get(), fun));
return m_timer_handles.back();
}
AsyncHandle_t eventloop::async_handle(function<void(void)> fun) {
m_async_handles.emplace_back(std::make_shared<AsyncHandle>(get(), fun));
return m_async_handles.back();
}
// }}}
} // namespace eventloop
POLYBAR_NS_END

View File

@ -1,87 +0,0 @@
#include "components/ipc.hpp"
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include "components/logger.hpp"
#include "errors.hpp"
#include "events/signal.hpp"
#include "events/signal_emitter.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
POLYBAR_NS
/**
* Message types
*/
static constexpr const char* ipc_command_prefix{"cmd:"};
static constexpr const char* ipc_hook_prefix{"hook:"};
static constexpr const char* ipc_action_prefix{"action:"};
/**
* Create instance
*/
ipc::make_type ipc::make() {
return std::make_unique<ipc>(signal_emitter::make(), logger::make());
}
/**
* Construct ipc handler
*/
ipc::ipc(signal_emitter& emitter, const logger& logger) : m_sig(emitter), m_log(logger) {
m_path = string_util::replace(PATH_MESSAGING_FIFO, "%pid%", to_string(getpid()));
if (file_util::exists(m_path) && unlink(m_path.c_str()) == -1) {
throw system_error("Failed to remove ipc channel");
}
if (mkfifo(m_path.c_str(), 0666) == -1) {
throw system_error("Failed to create ipc channel");
}
m_log.info("Created ipc channel at: %s", m_path);
}
/**
* Deconstruct ipc handler
*/
ipc::~ipc() {
m_log.trace("ipc: Removing file handle at: %s", m_path);
unlink(m_path.c_str());
}
string ipc::get_path() const {
return m_path;
}
/**
* Receive parts of an IPC message
*/
void ipc::receive_data(string buf) {
m_buffer += buf;
}
/**
* Called once the end of the message arrives.
*/
void ipc::receive_eof() {
if (m_buffer.empty()) {
return;
}
string payload{string_util::trim(std::move(m_buffer), '\n')};
m_buffer = std::string();
if (payload.find(ipc_command_prefix) == 0) {
m_sig.emit(signals::ipc::command{payload.substr(strlen(ipc_command_prefix))});
} else if (payload.find(ipc_hook_prefix) == 0) {
m_sig.emit(signals::ipc::hook{payload.substr(strlen(ipc_hook_prefix))});
} else if (payload.find(ipc_action_prefix) == 0) {
m_sig.emit(signals::ipc::action{payload.substr(strlen(ipc_action_prefix))});
} else {
m_log.warn("Received unknown ipc message: (payload=%s)", payload);
}
}
POLYBAR_NS_END

View File

@ -1,135 +0,0 @@
#include <fcntl.h>
#include <unistd.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <vector>
#include "common.hpp"
#include "utils/file.hpp"
using namespace polybar;
using namespace std;
#ifndef IPC_CHANNEL_PREFIX
#define IPC_CHANNEL_PREFIX "/tmp/polybar_mqueue."
#endif
void display(const string& msg) {
fprintf(stdout, "%s\n", msg.c_str());
}
void log(int exit_code, const string& msg) {
fprintf(stderr, "polybar-msg: %s\n", msg.c_str());
exit(exit_code);
}
void usage(const string& parameters) {
fprintf(stderr, "Usage: polybar-msg [-p pid] %s\n", parameters.c_str());
exit(127);
}
void remove_pipe(const string& handle) {
if (unlink(handle.c_str()) == -1) {
log(1, "Could not remove stale ipc channel: "s + strerror(errno));
} else {
display("Removed stale ipc channel: " + handle);
}
}
bool validate_type(const string& type) {
return (type == "action" || type == "cmd" || type == "hook");
}
int main(int argc, char** argv) {
const int E_NO_CHANNELS{2};
const int E_MESSAGE_TYPE{3};
const int E_INVALID_PID{4};
const int E_INVALID_CHANNEL{5};
const int E_WRITE{6};
vector<string> args{argv + 1, argv + argc};
string::size_type p;
int pid{0};
// If -p <pid> is passed, check if the process is running and that
// a valid channel pipe is available
if (args.size() >= 2 && args[0].compare(0, 2, "-p") == 0) {
if (!file_util::exists("/proc/" + args[1])) {
log(E_INVALID_PID, "No process with pid " + args[1]);
} else if (!file_util::exists(IPC_CHANNEL_PREFIX + args[1])) {
log(E_INVALID_CHANNEL, "No channel available for pid " + args[1]);
}
pid = strtol(args[1].c_str(), nullptr, 10);
args.erase(args.begin());
args.erase(args.begin());
}
// Validate args
auto help = find_if(args.begin(), args.end(), [](string a) { return a == "-h" || a == "--help"; }) != args.end();
if (help || args.size() < 2) {
usage("<command=(action|cmd|hook)> <payload> [...]");
} else if (!validate_type(args[0])) {
log(E_MESSAGE_TYPE, "\"" + args[0] + "\" is not a valid type.");
}
string ipc_type{args[0]};
args.erase(args.begin());
string ipc_payload{args[0]};
args.erase(args.begin());
// Check hook specific args
if (ipc_type == "hook") {
if (args.size() != 1) {
usage("hook <module-name> <hook-index>");
} else if ((p = ipc_payload.find("module/")) != 0) {
ipc_payload = "module/" + ipc_payload + args[0];
args.erase(args.begin());
} else {
ipc_payload += args[0];
args.erase(args.begin());
}
}
// Get availble channel pipes
auto pipes = file_util::glob(IPC_CHANNEL_PREFIX + "*"s);
// Remove stale channel files without a running parent process
for (auto it = pipes.rbegin(); it != pipes.rend(); it++) {
if ((p = it->rfind('.')) == string::npos) {
continue;
} else if (!file_util::exists("/proc/" + it->substr(p + 1))) {
remove_pipe(*it);
pipes.erase(remove(pipes.begin(), pipes.end(), *it), pipes.end());
} else if (pid && to_string(pid) != it->substr(p + 1)) {
pipes.erase(remove(pipes.begin(), pipes.end(), *it), pipes.end());
}
}
if (pipes.empty()) {
log(E_NO_CHANNELS, "No active ipc channels");
}
int exit_status = 127;
// Write message to each available channel or match
// against pid if one was defined
for (auto&& channel : pipes) {
try {
file_descriptor fd(channel, O_WRONLY | O_NONBLOCK, true);
string payload{ipc_type + ':' + ipc_payload};
if (write(fd, payload.c_str(), payload.size()) != -1) {
display("Successfully wrote \"" + payload + "\" to \"" + channel + "\"");
exit_status = 0;
} else {
log(E_WRITE, "Failed to write \"" + payload + "\" to \"" + channel + "\" (err: " + strerror(errno) + ")");
}
} catch (const exception& err) {
remove_pipe(channel);
}
}
return exit_status;
}

135
src/ipc/decoder.cpp Normal file
View File

@ -0,0 +1,135 @@
#include "ipc/decoder.hpp"
#include <cassert>
#include <cstring>
POLYBAR_NS
namespace ipc {
decoder::decoder(const logger& logger, cb callback) : callback(callback), m_log(logger) {}
void decoder::on_read(const uint8_t* data, size_t size) {
if (state == state::CLOSED) {
throw error("Decoder is closed");
}
try {
process_data(data, size);
} catch (const error& e) {
close();
throw;
}
}
void decoder::process_data(const uint8_t* data, size_t size) {
m_log.trace("ipc: Received %zd bytes", size);
size_t buf_pos = 0;
size_t remain = size;
while (remain > 0) {
if (state == state::HEADER) {
ssize_t num_read = process_header_data(data + buf_pos, remain);
assert(num_read > 0);
assert(remain >= (size_t)num_read);
buf_pos += num_read;
remain -= num_read;
/*
* If an empty message arrives, we need to explicitly trigger this because there is no further data that would
* call process_msg_data.
*/
if (remain == 0 && state == state::PAYLOAD && to_read_buf == 0) {
ssize_t num_read_data = process_msg_data(data + buf_pos, remain);
assert(num_read_data == 0);
(void)num_read_data;
}
} else {
assert(to_read_header == 0);
ssize_t num_read = process_msg_data(data + buf_pos, remain);
assert(num_read > 0);
assert(remain >= (size_t)num_read);
buf_pos += num_read;
remain -= num_read;
}
}
}
void decoder::close() noexcept {
state = state::CLOSED;
}
bool decoder::closed() const {
return state == state::CLOSED;
}
/**
* If we are waiting for header data, read as many bytes as possible from the given buffer.
*
* \return Number of bytes processed.
* \throws decoder::error on message errors
*/
ssize_t decoder::process_header_data(const uint8_t* data, size_t size) {
assert(state == state::HEADER);
assert(to_read_header > 0);
size_t num_read = std::min(size, to_read_header);
std::copy(data, data + num_read, header.d + HEADER_SIZE - to_read_header);
to_read_header -= num_read;
if (to_read_header == 0) {
uint8_t version = header.s.version;
uint32_t msg_size = header.s.size;
m_log.trace(
"Received full ipc header (magic=%.*s version=%d size=%zd)", MAGIC.size(), header.s.magic, version, msg_size);
if (memcmp(header.s.magic, MAGIC.data(), MAGIC.size()) != 0) {
throw error("Invalid magic header, expected '" + MAGIC_STR + "', got '" +
string(reinterpret_cast<const char*>(header.s.magic), MAGIC.size()) + "'");
}
if (version != VERSION) {
throw error("Unsupported message format version " + to_string(version));
}
assert(buf.empty());
state = state::PAYLOAD;
to_read_buf = msg_size;
}
return num_read;
}
/**
* If we are waiting for message data, read as many bytes as possible from the given buffer.
*
* \return Number of bytes processed.
* \throws decoder::error on message errors
*/
ssize_t decoder::process_msg_data(const uint8_t* data, size_t size) {
assert(state == state::PAYLOAD);
size_t num_read = std::min(size, to_read_buf);
buf.reserve(buf.size() + num_read);
for (size_t i = 0; i < num_read; i++) {
buf.push_back(data[i]);
}
to_read_buf -= num_read;
if (to_read_buf == 0) {
callback(header.s.version, header.s.type, buf);
state = state::HEADER;
to_read_header = HEADER_SIZE;
buf.clear();
}
return num_read;
}
} // namespace ipc
POLYBAR_NS_END

33
src/ipc/encoder.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "ipc/encoder.hpp"
#include <cassert>
#include <cstring>
POLYBAR_NS
namespace ipc {
template <typename V>
vector<uint8_t> encode(const type_t type, const V& payload) {
size_t total_size = HEADER_SIZE + payload.size();
std::vector<uint8_t> data(total_size);
auto* header = reinterpret_cast<ipc::header*>(data.data());
std::copy(ipc::MAGIC.begin(), ipc::MAGIC.end(), header->s.magic);
header->s.version = ipc::VERSION;
header->s.size = payload.size();
header->s.type = type;
std::copy(payload.begin(), payload.end(), data.begin() + HEADER_SIZE);
return data;
}
vector<uint8_t> encode(const type_t type, const vector<uint8_t>& payload) {
return encode<vector<uint8_t>>(type, payload);
}
vector<uint8_t> encode(const type_t type, const string& payload) {
return encode<string>(type, payload);
}
} // namespace ipc
POLYBAR_NS_END

218
src/ipc/ipc.cpp Normal file
View File

@ -0,0 +1,218 @@
#include "ipc/ipc.hpp"
#include <sys/stat.h>
#include <unistd.h>
#include <cassert>
#include "components/eventloop.hpp"
#include "components/logger.hpp"
#include "errors.hpp"
#include "events/signal.hpp"
#include "events/signal_emitter.hpp"
#include "ipc/encoder.hpp"
#include "ipc/util.hpp"
#include "utils/env.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
POLYBAR_NS
namespace ipc {
/**
* Message types
*/
static constexpr const char* ipc_command_prefix{"cmd:"};
static constexpr const char* ipc_hook_prefix{"hook:"};
static constexpr const char* ipc_action_prefix{"action:"};
/**
* Create instance
*/
ipc::make_type ipc::make(eventloop::eventloop& loop) {
return std::make_unique<ipc>(signal_emitter::make(), logger::make(), loop);
}
/**
* Construct ipc handler
*/
ipc::ipc(signal_emitter& emitter, const logger& logger, eventloop::eventloop& loop)
: m_sig(emitter), m_log(logger), m_loop(loop), socket(loop.handle<eventloop::PipeHandle>()) {
m_pipe_path = string_util::replace(PATH_MESSAGING_FIFO, "%pid%", to_string(getpid()));
if (file_util::exists(m_pipe_path) && unlink(m_pipe_path.c_str()) == -1) {
throw system_error("Failed to remove ipc channel");
}
if (mkfifo(m_pipe_path.c_str(), 0600) == -1) {
throw system_error("Failed to create ipc channel");
}
m_log.info("Created legacy ipc fifo at '%s'", m_pipe_path);
ipc_pipe = make_unique<fifo>(m_loop, *this, m_pipe_path);
string sock_path = get_socket_path(getpid());
m_log.info("Opening ipc socket at '%s'", sock_path);
m_log.notice("Listening for IPC messages (PID: %d)", getpid());
socket.bind(sock_path);
socket.listen(
4, [this]() { on_connection(); },
[this](const auto& e) {
m_log.err("libuv error while listening to IPC socket: %s", uv_strerror(e.status));
socket.close();
});
}
/**
* Deconstruct ipc handler
*/
ipc::~ipc() {
m_log.trace("ipc: Removing named pipe at: %s", m_pipe_path);
if (unlink(m_pipe_path.c_str()) == -1) {
m_log.err("Failed to delete ipc named pipe: %s", strerror(errno));
}
socket.close();
}
string ipc::get_socket_path(int pid) {
return ensure_runtime_path() + "/ipc." + to_string(pid) + ".sock";
}
bool ipc::trigger_ipc(v0::ipc_type type, const string& msg) {
switch (type) {
case v0::ipc_type::CMD:
m_log.info("Received ipc command: '%s'", msg);
return m_sig.emit(signals::ipc::command{msg});
case v0::ipc_type::ACTION:
m_log.info("Received ipc action: '%s'", msg);
return m_sig.emit(signals::ipc::action{msg});
}
assert(false);
return false;
}
void ipc::trigger_legacy_ipc(const string& msg) {
m_log.info("Received ipc message: '%s'", msg);
if (msg.find(ipc_command_prefix) == 0) {
m_sig.emit(signals::ipc::command{msg.substr(strlen(ipc_command_prefix))});
} else if (msg.find(ipc_hook_prefix) == 0) {
m_sig.emit(signals::ipc::hook{msg.substr(strlen(ipc_hook_prefix))});
} else if (msg.find(ipc_action_prefix) == 0) {
m_sig.emit(signals::ipc::action{msg.substr(strlen(ipc_action_prefix))});
} else {
m_log.warn("Received unknown ipc message: (payload=%s)", msg);
}
}
void ipc::on_connection() {
auto connection = make_unique<ipc::connection>(
m_loop, [this](ipc::connection& c, uint8_t, type_t type, const vector<uint8_t>& msg) {
vector<uint8_t> response;
if (type == to_integral(v0::ipc_type::ACTION) || type == to_integral(v0::ipc_type::CMD)) {
auto ipc_type = static_cast<v0::ipc_type>(type);
string str;
str.insert(str.end(), msg.begin(), msg.end());
if (trigger_ipc(ipc_type, str)) {
response = encode(TYPE_OK);
} else {
response = encode(TYPE_ERR, "Error while executing ipc message, see polybar log for details.");
}
} else {
response = encode(TYPE_ERR, "Unrecognized IPC message type " + to_string(type));
}
c.client_pipe.write(
response, [this, &c]() { remove_client(c); },
[this, &c](const auto& e) {
m_log.err("ipc: libuv error while writing to IPC socket: %s", uv_strerror(e.status));
remove_client(c);
});
});
auto& c = *connection;
socket.accept(c.client_pipe);
c.client_pipe.read_start(
[this, &c](const auto& e) {
try {
c.dec.on_read((const uint8_t*)e.data, e.len);
} catch (const decoder::error& e) {
m_log.err("ipc: Failed to decode IPC message (reason: %s)", e.what());
c.client_pipe.write(
encode(TYPE_ERR, "Invalid binary message format: "s + e.what()), [this, &c]() { remove_client(c); },
[this, &c](const auto& e) {
m_log.err("ipc: libuv error while writing to IPC socket: %s", uv_strerror(e.status));
remove_client(c);
});
}
},
[this, &c]() { remove_client(c); },
[this, &c](const auto& e) {
m_log.err("ipc: libuv error while listening to IPC socket: %s", uv_strerror(e.status));
remove_client(c);
});
connections.emplace(std::move(connection));
m_log.info("ipc: New connection (%d clients)", connections.size());
}
void ipc::remove_client(connection& conn) {
conn.client_pipe.close();
connections.erase(connections.find(conn));
}
ipc::connection::connection(eventloop::eventloop& loop, cb msg_callback)
: client_pipe(loop.handle<eventloop::PipeHandle>())
, dec(logger::make(), [this, msg_callback](uint8_t version, auto type, const auto& msg) {
msg_callback(*this, version, type, msg);
}) {}
ipc::fifo::fifo(eventloop::eventloop& loop, ipc& ipc, const string& path)
: pipe_handle(loop.handle<eventloop::PipeHandle>()) {
int fd;
if ((fd = open(path.c_str(), O_RDONLY | O_NONBLOCK)) == -1) {
throw system_error("Failed to open pipe '" + path + "'");
}
pipe_handle.open(fd);
pipe_handle.read_start([&ipc](const auto& e) mutable { ipc.receive_data(string(e.data, e.len)); },
[&ipc]() { ipc.receive_eof(); },
[this, &ipc](const auto& e) mutable {
ipc.m_log.err("libuv error while listening to IPC channel: %s", uv_strerror(e.status));
pipe_handle.close();
});
}
ipc::fifo::~fifo() {
pipe_handle.close();
}
/**
* Receive parts of an IPC message
*/
void ipc::receive_data(string buf) {
m_pipe_buffer += buf;
m_log.warn("Using the named pipe at '%s' for ipc is deprecated, always use 'polybar-msg'", m_pipe_path);
}
/**
* Called once the end of the message arrives.
*/
void ipc::receive_eof() {
ipc_pipe = make_unique<fifo>(m_loop, *this, m_pipe_path);
if (m_pipe_buffer.empty()) {
return;
}
trigger_legacy_ipc(string_util::trim(std::move(m_pipe_buffer), '\n'));
m_pipe_buffer.clear();
}
} // namespace ipc
POLYBAR_NS_END

61
src/ipc/util.cpp Normal file
View File

@ -0,0 +1,61 @@
#include "ipc/util.hpp"
#include <sys/stat.h>
#include "errors.hpp"
#include "utils/env.hpp"
#include "utils/file.hpp"
#include "utils/string.hpp"
POLYBAR_NS
namespace ipc {
static constexpr auto SUFFIX = ".sock";
string get_runtime_path() {
return env_util::get("XDG_RUNTIME_DIR", "/tmp") + "/polybar";
}
string ensure_runtime_path() {
string runtime_path = get_runtime_path();
if (!file_util::exists(runtime_path) && mkdir(runtime_path.c_str(), 0700) == -1) {
throw system_error("Failed to create ipc socket folders");
}
return runtime_path;
}
string get_socket_path(const string& pid_string) {
return get_runtime_path() + "/ipc." + pid_string + SUFFIX;
}
string get_socket_path(int pid) {
return get_socket_path(to_string(pid));
}
string get_glob_socket_path() {
return get_socket_path("*");
}
int get_pid_from_socket(const string& path) {
if (!string_util::ends_with(path, SUFFIX)) {
return -1;
}
auto stripped = path.substr(0, path.size() - strlen(SUFFIX));
auto p = stripped.rfind('.');
if (p == string::npos) {
return -1;
}
try {
return std::stoi(stripped.substr(p + 1));
} catch (...) {
return -1;
}
}
} // namespace ipc
POLYBAR_NS_END

View File

@ -3,7 +3,7 @@
#include "components/config.hpp"
#include "components/config_parser.hpp"
#include "components/controller.hpp"
#include "components/ipc.hpp"
#include "ipc/ipc.hpp"
#include "utils/env.hpp"
#include "utils/inotify.hpp"
#include "utils/process.hpp"
@ -58,6 +58,8 @@ int main(int argc, char** argv) {
return EXIT_SUCCESS;
}
eventloop::eventloop loop{};
//==================================================
// Connect to X server
//==================================================
@ -135,7 +137,6 @@ int main(int argc, char** argv) {
return EXIT_SUCCESS;
}
if (cli->has("print-wmname")) {
eventloop loop{};
printf("%s\n", bar::make(loop, true)->settings().wmname.c_str());
return EXIT_SUCCESS;
}
@ -143,13 +144,13 @@ int main(int argc, char** argv) {
//==================================================
// Create controller and run application
//==================================================
unique_ptr<ipc> ipc{};
unique_ptr<ipc::ipc> ipc{};
if (conf.get(conf.section(), "enable-ipc", false)) {
ipc = ipc::make();
ipc = ipc::ipc::make(loop);
}
auto ctrl = controller::make(move(ipc));
auto ctrl = controller::make((bool)ipc, loop);
if (!ctrl->run(cli->has("stdout"), cli->get("png"), cli->has("reload"))) {
reload = true;

View File

@ -2,7 +2,6 @@
#include <unistd.h>
#include "components/ipc.hpp"
#include "modules/meta/base.inl"
POLYBAR_NS
@ -205,7 +204,7 @@ namespace modules {
}
void ipc_module::set_hook(int h) {
assert(h >= 0 && (size_t) h < m_hooks.size());
assert(h >= 0 && (size_t)h < m_hooks.size());
m_current_hook = h;
exec_hook();
}

294
src/polybar-msg.cpp Normal file
View File

@ -0,0 +1,294 @@
#include <fcntl.h>
#include <unistd.h>
#include <algorithm>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <deque>
#include "common.hpp"
#include "components/eventloop.hpp"
#include "ipc/decoder.hpp"
#include "ipc/encoder.hpp"
#include "ipc/msg.hpp"
#include "ipc/util.hpp"
#include "modules/ipc.hpp"
#include "utils/actions.hpp"
#include "utils/file.hpp"
using namespace polybar;
using namespace std;
static const char* exec = nullptr;
static constexpr auto USAGE = "<command=(action|cmd)> <payload> [...]";
static constexpr auto USAGE_HOOK = "hook <module-name> <hook-index>";
void display(const string& msg) {
fprintf(stdout, "%s\n", msg.c_str());
}
void error(const string& msg) {
throw std::runtime_error(msg);
}
void uv_error(int status, const string& msg) {
throw std::runtime_error(msg + "(" + uv_strerror(status) + ")");
}
void usage(FILE* f, const string& parameters) {
fprintf(f, "Usage: %s [-p pid] %s\n", exec, parameters.c_str());
}
void remove_socket(const string& handle) {
if (unlink(handle.c_str()) == -1) {
error("Could not remove stale ipc channel: "s + strerror(errno));
} else {
display("Removed stale ipc channel: " + handle);
}
}
bool validate_type(const string& type) {
return (type == "action" || type == "cmd" || type == "hook");
}
static vector<string> get_sockets() {
auto sockets = file_util::glob(ipc::get_glob_socket_path());
auto new_end = std::remove_if(sockets.begin(), sockets.end(), [](const auto& path) {
int pid = ipc::get_pid_from_socket(path);
if (pid <= 0) {
return true;
}
if (!file_util::exists("/proc/" + to_string(pid))) {
remove_socket(path);
return true;
}
return false;
});
sockets.erase(new_end, sockets.end());
return sockets;
}
static void on_write(eventloop::PipeHandle& conn, ipc::decoder& dec) {
conn.read_start(
[&](const auto& e) {
try {
if (!dec.closed()) {
dec.on_read(reinterpret_cast<const uint8_t*>(e.data), e.len);
}
} catch (const ipc::decoder::error& e) {
conn.close();
}
},
[&]() { conn.close(); },
[&](const auto& e) {
conn.close();
uv_error(e.status, "There was an error while reading polybar's response");
});
}
static void on_connection(
eventloop::PipeHandle& conn, ipc::decoder& dec, const ipc::type_t type, const string& payload) {
const auto data = ipc::encode(type, payload);
conn.write(
data, [&]() { on_write(conn, dec); },
[&](const auto& e) {
conn.close();
uv_error(e.status, "There was an error while sending the IPC message.");
});
}
static std::pair<ipc::type_t, string> parse_message(deque<string> args) {
// Validate args
const string ipc_type{args.front()};
args.pop_front();
string ipc_payload{args.front()};
args.pop_front();
if (!validate_type(ipc_type)) {
error("\"" + ipc_type + "\" is not a valid message type.");
}
ipc::type_t type = ipc::TYPE_ERR;
/*
* Check hook specific args
*
* The hook type is deprecated. Its contents are translated into a hook action.
*/
if (ipc_type == "hook") {
if (args.size() != 1) {
usage(stderr, USAGE_HOOK);
throw std::runtime_error("Mismatched number of arguments for hook, expected 1, got "s + to_string(args.size()));
} else {
if (ipc_payload.find("module/") == 0) {
ipc_payload.erase(0, strlen("module/"));
}
// Hook commands use 1-indexed hooks but actions use 0-indexed ones
int hook_index = std::stoi(args.front()) - 1;
args.pop_front();
type = to_integral(ipc::v0::ipc_type::ACTION);
ipc_payload =
actions_util::get_action_string(ipc_payload, polybar::modules::ipc_module::EVENT_HOOK, to_string(hook_index));
fprintf(stderr,
"Warning: Using IPC hook commands is deprecated, use the hook action on the ipc module: %s %s \"%s\"\n", exec,
ipc_type.c_str(), ipc_payload.c_str());
}
}
if (ipc_type == "action") {
type = to_integral(ipc::v0::ipc_type::ACTION);
/**
* Alternatively polybar-msg action <module name> <action> <data>
* is also accepted
*/
if (!args.empty()) {
string name = ipc_payload;
string action = args.front();
args.pop_front();
string data{};
if (!args.empty()) {
data = args.front();
args.pop_front();
}
ipc_payload = actions_util::get_action_string(name, action, data);
}
}
if (ipc_type == "cmd") {
type = to_integral(ipc::v0::ipc_type::CMD);
}
if (!args.empty()) {
error("Too many arguments");
}
assert(type != ipc::TYPE_ERR);
return {type, ipc_payload};
}
int run(int argc, char** argv) {
deque<string> args{argv + 1, argv + argc};
string socket_path;
auto help_pos = find_if(args.begin(), args.end(), [](const string& a) { return a == "-h" || a == "--help"; });
if (help_pos != args.end()) {
usage(stdout, USAGE);
return EXIT_SUCCESS;
}
/* If -p <pid> is passed, check if the process is running and that
* a valid channel socket is available
*/
if (args.size() >= 2 && args[0].compare(0, 2, "-p") == 0) {
auto& pid_string = args[1];
socket_path = ipc::get_socket_path(pid_string);
if (!file_util::exists("/proc/" + pid_string)) {
error("No process with pid " + pid_string);
} else if (!file_util::exists(socket_path)) {
error("No channel available for pid " + pid_string);
}
args.pop_front();
args.pop_front();
}
// If no pid was given, search for all open sockets.
auto sockets = socket_path.empty() ? get_sockets() : vector<string>{socket_path};
// Get availble channel sockets
if (sockets.empty()) {
error("No active ipc channels");
}
if (args.size() < 2) {
usage(stderr, USAGE);
return EXIT_FAILURE;
}
string payload;
ipc::type_t type;
std::tie(type, payload) = parse_message(args);
const string type_str = type == to_integral(ipc::v0::ipc_type::ACTION) ? "action" : "command";
bool success = true;
eventloop::eventloop loop;
logger null_logger{loglevel::NONE};
/*
* Store all decoreds in vector so that they're alive for the whole eventloop.
*/
vector<ipc::decoder> decoders;
for (auto&& channel : sockets) {
auto& conn = loop.handle<eventloop::PipeHandle>();
int pid = ipc::get_pid_from_socket(channel);
assert(pid > 0);
decoders.emplace_back(
null_logger, [pid, channel, &type_str, &payload, &success](uint8_t, ipc::type_t type, const auto& response) {
switch (type) {
case ipc::TYPE_OK:
printf("Successfully wrote %s '%s' to PID %d\n", type_str.c_str(), payload.c_str(), pid);
break;
case ipc::TYPE_ERR: {
string err_str{response.begin(), response.end()};
fprintf(stderr, "%s: Failed to write %s '%s' to PID %d (reason: %s)\n", exec, type_str.c_str(),
payload.c_str(), pid, err_str.c_str());
success = false;
break;
}
default:
fprintf(stderr, "%s: Got back unrecognized message type %d from PID %d\n", exec, type, pid);
success = false;
break;
}
});
/*
* Index to decoder is captured because reference can be invalidated due to the vector being modified.
*/
int idx = decoders.size() - 1;
conn.connect(
channel,
[&conn, &decoders, type, payload, channel, idx]() { on_connection(conn, decoders[idx], type, payload); },
[&](const auto& e) {
fprintf(stderr, "%s: Failed to connect to '%s' (err: '%s')\n", exec, channel.c_str(), uv_strerror(e.status));
success = false;
});
}
try {
loop.run();
} catch (const exception& e) {
throw std::runtime_error("Uncaught exception in eventloop: "s + e.what());
}
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(int argc, char** argv) {
exec = argv[0];
try {
return run(argc, argv);
} catch (const std::exception& e) {
fprintf(stderr, "%s: %s\n", exec, e.what());
return EXIT_FAILURE;
}
}

View File

@ -10,7 +10,11 @@ POLYBAR_NS
namespace actions_util {
string get_action_string(const modules::module_interface& module, string action, string data) {
string str = "#" + module.name_raw() + "." + action;
return get_action_string(module.name_raw(), action, data);
}
string get_action_string(const string& module_name, string action, string data) {
string str = "#" + module_name + "." + action;
if (!data.empty()) {
str += "." + data;
}

View File

@ -64,6 +64,9 @@ add_unit_test(drawtypes/label)
add_unit_test(drawtypes/ramp)
add_unit_test(drawtypes/iconset)
add_unit_test(drawtypes/layouticonset)
add_unit_test(ipc/decoder)
add_unit_test(ipc/encoder)
add_unit_test(ipc/util)
add_unit_test(tags/parser)
add_unit_test(tags/dispatch)
add_unit_test(tags/action_context)

View File

@ -0,0 +1,96 @@
#include "ipc/decoder.hpp"
#include <cstring>
#include <string>
#include <vector>
#include "common/test.hpp"
#include "components/logger.hpp"
#include "gmock/gmock.h"
#include "ipc/msg.hpp"
using namespace polybar;
using namespace ipc;
using ::testing::InSequence;
static std::vector<uint8_t> get_msg(decltype(MAGIC) magic, uint32_t version, type_t type, const std::string& msg) {
std::vector<uint8_t> data(HEADER_SIZE);
header* header = reinterpret_cast<ipc::header*>(data.data());
std::copy(magic.begin(), magic.end(), header->s.magic);
header->s.version = version;
header->s.size = msg.size();
header->s.type = type;
data.insert(data.end(), msg.begin(), msg.end());
return data;
}
static decltype(MAGIC) MAGIC_WRONG = {'0', '0', '0', '0', '0', '0', '0'};
static type_t TYPE_ACTION = to_integral(v0::ipc_type::ACTION);
static auto MSG1 = get_msg(MAGIC, VERSION, TYPE_ACTION, "foobar");
static auto MSG2 = get_msg(MAGIC, VERSION, TYPE_ACTION, "");
static auto MSG_WRONG1 = get_msg(MAGIC_WRONG, VERSION, TYPE_ACTION, "");
static auto MSG_WRONG2 = get_msg(MAGIC, 120, TYPE_ACTION, "");
static auto MSG_WRONG3 = get_msg(MAGIC_WRONG, 120, TYPE_ACTION, "");
class MockCallback {
public:
MOCK_METHOD(void, cb, (uint8_t version, type_t, const vector<uint8_t>&));
};
static logger null_logger(loglevel::NONE);
class DecoderTest : public ::testing::Test {
protected:
MockCallback cb;
decoder dec{null_logger, [this](uint8_t version, auto type, const auto& data) { cb.cb(version, type, data); }};
};
TEST_F(DecoderTest, single_msg1) {
EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG1.begin() + HEADER_SIZE, MSG1.end()))).Times(1);
EXPECT_NO_THROW(dec.on_read(MSG1.data(), MSG1.size()));
}
TEST_F(DecoderTest, single_msg2) {
EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG2.begin() + HEADER_SIZE, MSG2.end()))).Times(1);
EXPECT_NO_THROW(dec.on_read(MSG2.data(), MSG2.size()));
}
TEST_F(DecoderTest, single_msg_wrong1) {
EXPECT_THROW(dec.on_read(MSG_WRONG1.data(), MSG_WRONG1.size()), decoder::error);
// After an error, any further read fails
EXPECT_THROW(dec.on_read(MSG1.data(), MSG1.size()), decoder::error);
}
TEST_F(DecoderTest, single_msg_wrong2) {
EXPECT_THROW(dec.on_read(MSG_WRONG2.data(), MSG_WRONG2.size()), decoder::error);
// After an error, any further read fails
EXPECT_THROW(dec.on_read(MSG1.data(), MSG1.size()), decoder::error);
}
TEST_F(DecoderTest, single_msg_wrong3) {
EXPECT_THROW(dec.on_read(MSG_WRONG3.data(), MSG_WRONG3.size()), decoder::error);
// After an error, any further read fails
EXPECT_THROW(dec.on_read(MSG1.data(), MSG1.size()), decoder::error);
}
TEST_F(DecoderTest, byte_by_byte) {
EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG1.begin() + HEADER_SIZE, MSG1.end()))).Times(1);
for (const uint8_t c : MSG1) {
EXPECT_NO_THROW(dec.on_read(&c, 1));
}
}
TEST_F(DecoderTest, multiple) {
static constexpr int NUM_ITER = 10;
{
InSequence seq;
EXPECT_CALL(cb, cb(0, TYPE_ACTION, vector<uint8_t>(MSG1.begin() + HEADER_SIZE, MSG1.end()))).Times(NUM_ITER);
}
for (int i = 0; i < NUM_ITER; i++) {
EXPECT_NO_THROW(dec.on_read(MSG1.data(), MSG1.size()));
}
}

View File

@ -0,0 +1,58 @@
#include "ipc/encoder.hpp"
#include <cstring>
#include <string>
#include <vector>
#include "common/test.hpp"
#include "gmock/gmock.h"
#include "ipc/decoder.hpp"
#include "ipc/msg.hpp"
using namespace polybar;
using namespace ipc;
using ::testing::InSequence;
static logger null_logger(loglevel::TRACE);
class MockCallback {
public:
MOCK_METHOD(void, cb, (uint8_t version, type_t, const vector<uint8_t>&));
};
class EncoderTest : public ::testing::Test, public testing::WithParamInterface<string> {
protected:
MockCallback cb;
decoder dec{null_logger, [this](uint8_t version, auto type, const auto& data) { cb.cb(version, type, data); }};
};
vector<string> encoder_list = {
""s,
"foo"s,
"\0\1"s,
};
INSTANTIATE_TEST_SUITE_P(Inst, EncoderTest, ::testing::ValuesIn(encoder_list));
TEST_P(EncoderTest, simple) {
auto param = GetParam();
const auto encoded = encode(TYPE_ERR, param);
const header* h = reinterpret_cast<const header*>(encoded.data());
EXPECT_EQ(std::memcmp(h->s.magic, MAGIC.data(), MAGIC.size()), 0);
EXPECT_EQ(h->s.version, 0);
EXPECT_EQ(h->s.size, param.size());
EXPECT_EQ(h->s.type, TYPE_ERR);
if (!param.empty()) {
EXPECT_EQ(std::memcmp(encoded.data() + HEADER_SIZE, param.data(), param.size()), 0);
}
}
TEST_P(EncoderTest, roundtrip) {
auto param = GetParam();
auto payload = vector<uint8_t>(param.begin(), param.end());
const auto encoded = encode(TYPE_ERR, param);
EXPECT_CALL(cb, cb(0, TYPE_ERR, payload)).Times(1);
EXPECT_NO_THROW(dec.on_read(encoded.data(), encoded.size()));
}

View File

@ -0,0 +1,22 @@
#include "ipc/util.hpp"
#include "common/test.hpp"
#include "ipc/msg.hpp"
using namespace polybar;
using namespace ipc;
TEST(GetSocketPath, RoundTrip) {
EXPECT_EQ(123, get_pid_from_socket(get_socket_path(123)));
EXPECT_EQ(1, get_pid_from_socket(get_socket_path(1)));
EXPECT_EQ(-1, get_pid_from_socket(get_glob_socket_path()));
}
TEST(PidFromSocket, EdgeCases) {
EXPECT_EQ(-1, get_pid_from_socket(""));
EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo.txt"));
EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo.sock"));
EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo..sock"));
EXPECT_EQ(-1, get_pid_from_socket("/tmp/foo.bar.sock"));
}

View File

@ -5,19 +5,40 @@
using namespace polybar;
using namespace actions_util;
template<typename T1, typename T2, typename T3>
template <typename T1, typename T2, typename T3>
using triple = std::tuple<T1, T2, T3>;
class GetActionStringTest : public ::testing::TestWithParam<pair<triple<string, string, string>, string>> {};
vector<pair<triple<string, string, string>, string>> get_action_string_list = {
{{"foo", "bar", ""}, "#foo.bar"},
{{"foo", "bar", "data"}, "#foo.bar.data"},
{{"foo", "bar", "data.data2"}, "#foo.bar.data.data2"},
{{"a", "b", "c"}, "#a.b.c"},
{{"a", "b", ""}, "#a.b"},
};
TEST_P(GetActionStringTest, correctness) {
auto action = GetParam().first;
auto exp = GetParam().second;
auto res = get_action_string(std::get<0>(action), std::get<1>(action), std::get<2>(action));
EXPECT_EQ(res, exp);
}
INSTANTIATE_TEST_SUITE_P(Inst, GetActionStringTest, ::testing::ValuesIn(get_action_string_list));
class ParseActionStringTest : public ::testing::TestWithParam<pair<string, triple<string, string, string>>> {};
vector<pair<string, triple<string, string, string>>> parse_action_string_list = {
{"#foo.bar", {"foo", "bar", ""}},
{"#foo.bar.", {"foo", "bar", ""}},
{"#foo.bar.data", {"foo", "bar", "data"}},
{"#foo.bar.data.data2", {"foo", "bar", "data.data2"}},
{"#a.b.c", {"a", "b", "c"}},
{"#a.b.", {"a", "b", ""}},
{"#a.b", {"a", "b", ""}},
{"#foo.bar", {"foo", "bar", ""}},
{"#foo.bar.", {"foo", "bar", ""}},
{"#foo.bar.data", {"foo", "bar", "data"}},
{"#foo.bar.data.data2", {"foo", "bar", "data.data2"}},
{"#a.b.c", {"a", "b", "c"}},
{"#a.b.", {"a", "b", ""}},
{"#a.b", {"a", "b", ""}},
};
TEST_P(ParseActionStringTest, correctness) {
@ -34,14 +55,14 @@ INSTANTIATE_TEST_SUITE_P(Inst, ParseActionStringTest, ::testing::ValuesIn(parse_
class ParseActionStringThrowTest : public ::testing::TestWithParam<string> {};
vector<string> parse_action_string_throw_list = {
"#",
"#.",
"#..",
"#handler..",
"#.action.",
"#.action.data",
"#..data",
"#.data",
"#",
"#.",
"#..",
"#handler..",
"#.action.",
"#.action.data",
"#..data",
"#.data",
};
INSTANTIATE_TEST_SUITE_P(Inst, ParseActionStringThrowTest, ::testing::ValuesIn(parse_action_string_throw_list));