Import polybar_3.5.5.orig.tar.gz
authorUtkarsh Gupta <utkarsh@debian.org>
Mon, 1 Mar 2021 21:22:57 +0000 (21:22 +0000)
committerUtkarsh Gupta <utkarsh@debian.org>
Mon, 1 Mar 2021 21:22:57 +0000 (21:22 +0000)
[dgit import orig polybar_3.5.5.orig.tar.gz]

369 files changed:
.clang-format [new file with mode: 0644]
.clang-tidy [new file with mode: 0644]
.codecov.yml [new file with mode: 0644]
.editorconfig [new file with mode: 0644]
.github/ISSUE_TEMPLATE/bug_report.md [new file with mode: 0644]
.github/ISSUE_TEMPLATE/build.md [new file with mode: 0644]
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/feature_request.md [new file with mode: 0644]
.github/PULL_REQUEST_TEMPLATE.md [new file with mode: 0644]
.github/workflows/ci.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
.gitmodules [new file with mode: 0644]
.valgrind-suppressions [new file with mode: 0644]
.ycm_extra_conf.py [new file with mode: 0644]
CHANGELOG.md [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
CONTRIBUTING.md [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
SUPPORT.md [new file with mode: 0644]
banner.png [new file with mode: 0644]
build.sh [new file with mode: 0755]
cmake/01-core.cmake [new file with mode: 0644]
cmake/02-opts.cmake [new file with mode: 0644]
cmake/03-libs.cmake [new file with mode: 0644]
cmake/04-targets.cmake [new file with mode: 0644]
cmake/05-summary.cmake [new file with mode: 0644]
cmake/common/utils.cmake [new file with mode: 0644]
cmake/modules/FindLibiw.cmake [new file with mode: 0644]
cmake/templates/uninstall.cmake.in [new file with mode: 0644]
cmake/templates/userconfig.cmake.in [new file with mode: 0644]
common/ci/configure.sh [new file with mode: 0755]
common/ci/summary.sh [new file with mode: 0755]
common/clang-format.sh [new file with mode: 0755]
common/clang-tidy.sh [new file with mode: 0755]
common/release-archive.sh [new file with mode: 0755]
config.cmake [new file with mode: 0644]
contrib/bash/CMakeLists.txt [new file with mode: 0644]
contrib/bash/polybar [new file with mode: 0644]
contrib/polybar-git.aur/PKGBUILD [new file with mode: 0644]
contrib/polybar-git.aur/polybar.install [new file with mode: 0644]
contrib/polybar.aur/PKGBUILD [new file with mode: 0644]
contrib/polybar.aur/polybar.install [new file with mode: 0644]
contrib/rpm/polybar.spec [new file with mode: 0644]
contrib/vim/autoload/ft/cpphpp.vim [new file with mode: 0644]
contrib/vim/ftplugin/cpp.vim [new file with mode: 0644]
contrib/vim/ftplugin/dosini.vim [new file with mode: 0644]
contrib/zsh/CMakeLists.txt [new file with mode: 0644]
contrib/zsh/_polybar [new file with mode: 0644]
contrib/zsh/_polybar_msg [new file with mode: 0644]
doc/.gitignore [new file with mode: 0644]
doc/CMakeLists.txt [new file with mode: 0644]
doc/README.md [new file with mode: 0644]
doc/_static/.gitignore [new file with mode: 0644]
doc/conf.py [new file with mode: 0644]
doc/dev/packaging.rst [new file with mode: 0644]
doc/dev/release-workflow.rst [new file with mode: 0644]
doc/index.rst [new file with mode: 0644]
doc/man/polybar.1.rst [new file with mode: 0644]
doc/man/polybar.5.rst [new file with mode: 0644]
doc/user/actions.rst [new file with mode: 0644]
include/CMakeLists.txt [new file with mode: 0644]
include/adapters/alsa/control.hpp [new file with mode: 0644]
include/adapters/alsa/generic.hpp [new file with mode: 0644]
include/adapters/alsa/mixer.hpp [new file with mode: 0644]
include/adapters/mpd.hpp [new file with mode: 0644]
include/adapters/net.hpp [new file with mode: 0644]
include/adapters/pulseaudio.hpp [new file with mode: 0644]
include/cairo/context.hpp [new file with mode: 0644]
include/cairo/font.hpp [new file with mode: 0644]
include/cairo/fwd.hpp [new file with mode: 0644]
include/cairo/surface.hpp [new file with mode: 0644]
include/cairo/types.hpp [new file with mode: 0644]
include/cairo/utils.hpp [new file with mode: 0644]
include/common.hpp [new file with mode: 0644]
include/components/bar.hpp [new file with mode: 0644]
include/components/builder.hpp [new file with mode: 0644]
include/components/command_line.hpp [new file with mode: 0644]
include/components/config.hpp [new file with mode: 0644]
include/components/config_parser.hpp [new file with mode: 0644]
include/components/controller.hpp [new file with mode: 0644]
include/components/ipc.hpp [new file with mode: 0644]
include/components/logger.hpp [new file with mode: 0644]
include/components/parser.hpp [new file with mode: 0644]
include/components/renderer.hpp [new file with mode: 0644]
include/components/screen.hpp [new file with mode: 0644]
include/components/taskqueue.hpp [new file with mode: 0644]
include/components/types.hpp [new file with mode: 0644]
include/debug.hpp [new file with mode: 0644]
include/drawtypes/animation.hpp [new file with mode: 0644]
include/drawtypes/iconset.hpp [new file with mode: 0644]
include/drawtypes/label.hpp [new file with mode: 0644]
include/drawtypes/progressbar.hpp [new file with mode: 0644]
include/drawtypes/ramp.hpp [new file with mode: 0644]
include/errors.hpp [new file with mode: 0644]
include/events/signal.hpp [new file with mode: 0644]
include/events/signal_emitter.hpp [new file with mode: 0644]
include/events/signal_fwd.hpp [new file with mode: 0644]
include/events/signal_receiver.hpp [new file with mode: 0644]
include/events/types.hpp [new file with mode: 0644]
include/modules/alsa.hpp [new file with mode: 0644]
include/modules/backlight.hpp [new file with mode: 0644]
include/modules/battery.hpp [new file with mode: 0644]
include/modules/bspwm.hpp [new file with mode: 0644]
include/modules/counter.hpp [new file with mode: 0644]
include/modules/cpu.hpp [new file with mode: 0644]
include/modules/date.hpp [new file with mode: 0644]
include/modules/fs.hpp [new file with mode: 0644]
include/modules/github.hpp [new file with mode: 0644]
include/modules/i3.hpp [new file with mode: 0644]
include/modules/ipc.hpp [new file with mode: 0644]
include/modules/memory.hpp [new file with mode: 0644]
include/modules/menu.hpp [new file with mode: 0644]
include/modules/meta/base.hpp [new file with mode: 0644]
include/modules/meta/base.inl [new file with mode: 0644]
include/modules/meta/event_handler.hpp [new file with mode: 0644]
include/modules/meta/event_module.hpp [new file with mode: 0644]
include/modules/meta/factory.hpp [new file with mode: 0644]
include/modules/meta/inotify_module.hpp [new file with mode: 0644]
include/modules/meta/static_module.hpp [new file with mode: 0644]
include/modules/meta/timer_module.hpp [new file with mode: 0644]
include/modules/mpd.hpp [new file with mode: 0644]
include/modules/network.hpp [new file with mode: 0644]
include/modules/pulseaudio.hpp [new file with mode: 0644]
include/modules/script.hpp [new file with mode: 0644]
include/modules/systray.hpp [new file with mode: 0644]
include/modules/temperature.hpp [new file with mode: 0644]
include/modules/text.hpp [new file with mode: 0644]
include/modules/unsupported.hpp [new file with mode: 0644]
include/modules/xbacklight.hpp [new file with mode: 0644]
include/modules/xkeyboard.hpp [new file with mode: 0644]
include/modules/xwindow.hpp [new file with mode: 0644]
include/modules/xworkspaces.hpp [new file with mode: 0644]
include/settings.hpp.cmake [new file with mode: 0644]
include/utils/actions.hpp [new file with mode: 0644]
include/utils/bspwm.hpp [new file with mode: 0644]
include/utils/color.hpp [new file with mode: 0644]
include/utils/command.hpp [new file with mode: 0644]
include/utils/concurrency.hpp [new file with mode: 0644]
include/utils/env.hpp [new file with mode: 0644]
include/utils/factory.hpp [new file with mode: 0644]
include/utils/file.hpp [new file with mode: 0644]
include/utils/functional.hpp [new file with mode: 0644]
include/utils/http.hpp [new file with mode: 0644]
include/utils/i3.hpp [new file with mode: 0644]
include/utils/inotify.hpp [new file with mode: 0644]
include/utils/io.hpp [new file with mode: 0644]
include/utils/math.hpp [new file with mode: 0644]
include/utils/memory.hpp [new file with mode: 0644]
include/utils/mixins.hpp [new file with mode: 0644]
include/utils/process.hpp [new file with mode: 0644]
include/utils/scope.hpp [new file with mode: 0644]
include/utils/socket.hpp [new file with mode: 0644]
include/utils/string.hpp [new file with mode: 0644]
include/utils/throttle.hpp [new file with mode: 0644]
include/utils/time.hpp [new file with mode: 0644]
include/x11/atoms.hpp [new file with mode: 0644]
include/x11/background_manager.hpp [new file with mode: 0644]
include/x11/connection.hpp [new file with mode: 0644]
include/x11/cursor.hpp [new file with mode: 0644]
include/x11/ewmh.hpp [new file with mode: 0644]
include/x11/extensions/all.hpp [new file with mode: 0644]
include/x11/extensions/composite.hpp [new file with mode: 0644]
include/x11/extensions/fwd.hpp [new file with mode: 0644]
include/x11/extensions/randr.hpp [new file with mode: 0644]
include/x11/extensions/xkb.hpp [new file with mode: 0644]
include/x11/icccm.hpp [new file with mode: 0644]
include/x11/registry.hpp [new file with mode: 0644]
include/x11/tray_client.hpp [new file with mode: 0644]
include/x11/tray_manager.hpp [new file with mode: 0644]
include/x11/types.hpp [new file with mode: 0644]
include/x11/window.hpp [new file with mode: 0644]
include/x11/winspec.hpp [new file with mode: 0644]
include/x11/xembed.hpp [new file with mode: 0644]
include/x11/xresources.hpp [new file with mode: 0644]
lib/CMakeLists.txt [new file with mode: 0644]
lib/concurrentqueue/include/moodycamel/blockingconcurrentqueue.h [new file with mode: 0644]
lib/concurrentqueue/include/moodycamel/concurrentqueue.h [new file with mode: 0644]
lib/i3ipcpp/.gitignore [new file with mode: 0644]
lib/i3ipcpp/3rd/auss/LICENSE [new file with mode: 0644]
lib/i3ipcpp/3rd/auss/README.md [new file with mode: 0644]
lib/i3ipcpp/3rd/auss/include/auss.hpp [new file with mode: 0644]
lib/i3ipcpp/CHANGELOG [new file with mode: 0644]
lib/i3ipcpp/CMakeLists.txt [new file with mode: 0644]
lib/i3ipcpp/Doxyfile [new file with mode: 0644]
lib/i3ipcpp/LICENSE [new file with mode: 0644]
lib/i3ipcpp/README.md [new file with mode: 0644]
lib/i3ipcpp/examples/CMakeLists.txt [new file with mode: 0644]
lib/i3ipcpp/examples/bar-configs.cpp [new file with mode: 0644]
lib/i3ipcpp/examples/events.cpp [new file with mode: 0644]
lib/i3ipcpp/examples/workspaces.cpp [new file with mode: 0644]
lib/i3ipcpp/include/i3ipc++/ipc-util.hpp [new file with mode: 0644]
lib/i3ipcpp/include/i3ipc++/ipc.hpp [new file with mode: 0644]
lib/i3ipcpp/include/i3ipc++/log.hpp [new file with mode: 0644]
lib/i3ipcpp/src/ipc-util.cpp [new file with mode: 0644]
lib/i3ipcpp/src/ipc.cpp [new file with mode: 0644]
lib/i3ipcpp/test/test_ipc.hpp [new file with mode: 0644]
lib/xpp/.gitignore [new file with mode: 0644]
lib/xpp/CMakeLists.txt [new file with mode: 0644]
lib/xpp/LICENSE [new file with mode: 0644]
lib/xpp/README.md [new file with mode: 0644]
lib/xpp/cmake/FindX11_XCB.cmake [new file with mode: 0644]
lib/xpp/cmake/FindXCB.cmake [new file with mode: 0644]
lib/xpp/generators/TODO [new file with mode: 0644]
lib/xpp/generators/accessor.py [new file with mode: 0644]
lib/xpp/generators/cpp_client.py [new file with mode: 0644]
lib/xpp/generators/cppcookie.py [new file with mode: 0644]
lib/xpp/generators/cpperror.py [new file with mode: 0644]
lib/xpp/generators/cppevent.py [new file with mode: 0644]
lib/xpp/generators/cppreply.py [new file with mode: 0644]
lib/xpp/generators/cpprequest.py [new file with mode: 0644]
lib/xpp/generators/extensionclass.py [new file with mode: 0644]
lib/xpp/generators/interfaceclass.py [new file with mode: 0644]
lib/xpp/generators/objectclass.py [new file with mode: 0644]
lib/xpp/generators/parameter.py [new file with mode: 0644]
lib/xpp/generators/resource_classes.py [new file with mode: 0644]
lib/xpp/generators/utils.py [new file with mode: 0644]
lib/xpp/include/xpp/atom.hpp [new file with mode: 0644]
lib/xpp/include/xpp/colormap.hpp [new file with mode: 0644]
lib/xpp/include/xpp/connection.hpp [new file with mode: 0644]
lib/xpp/include/xpp/core.hpp [new file with mode: 0644]
lib/xpp/include/xpp/cursor.hpp [new file with mode: 0644]
lib/xpp/include/xpp/drawable.hpp [new file with mode: 0644]
lib/xpp/include/xpp/event.hpp [new file with mode: 0644]
lib/xpp/include/xpp/flags.makefile [new file with mode: 0644]
lib/xpp/include/xpp/font.hpp [new file with mode: 0644]
lib/xpp/include/xpp/fontable.hpp [new file with mode: 0644]
lib/xpp/include/xpp/gcontext.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/error.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/event.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/extension.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/factory.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/input_iterator_adapter.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/iterator_traits.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/reply_iterator.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/request.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/resource.hpp [new file with mode: 0644]
lib/xpp/include/xpp/generic/signature.hpp [new file with mode: 0644]
lib/xpp/include/xpp/pixmap.hpp [new file with mode: 0644]
lib/xpp/include/xpp/valueparam.hpp [new file with mode: 0644]
lib/xpp/include/xpp/window.hpp [new file with mode: 0644]
lib/xpp/include/xpp/xpp.hpp [new file with mode: 0644]
lib/xpp/src/examples/Makefile [new file with mode: 0644]
lib/xpp/src/examples/demo_01.cpp [new file with mode: 0644]
lib/xpp/src/examples/demo_02.cpp [new file with mode: 0644]
lib/xpp/src/tests/.gitignore [new file with mode: 0644]
lib/xpp/src/tests/Makefile [new file with mode: 0644]
lib/xpp/src/tests/README.md [new file with mode: 0644]
lib/xpp/src/tests/callable.cpp [new file with mode: 0644]
lib/xpp/src/tests/error_test.cpp [new file with mode: 0644]
lib/xpp/src/tests/event.cpp [new file with mode: 0644]
lib/xpp/src/tests/iterator.cpp [new file with mode: 0644]
lib/xpp/src/tests/requests.cpp [new file with mode: 0644]
lib/xpp/src/tests/resource.cpp [new file with mode: 0644]
lib/xpp/src/tests/sizeof.cpp [new file with mode: 0644]
lib/xpp/src/tests/template.cpp [new file with mode: 0644]
lib/xpp/src/tests/test.cpp [new file with mode: 0644]
lib/xpp/src/tests/xlib-test.cpp [new file with mode: 0644]
lib/xpp/src/tests/xlib.cpp [new file with mode: 0644]
lib/xpp/src/tests/xproto.cpp [new file with mode: 0644]
lib/xpp/src/xpp.cpp [new file with mode: 0644]
src/CMakeLists.txt [new file with mode: 0644]
src/adapters/alsa/control.cpp [new file with mode: 0644]
src/adapters/alsa/mixer.cpp [new file with mode: 0644]
src/adapters/mpd.cpp [new file with mode: 0644]
src/adapters/net.cpp [new file with mode: 0644]
src/adapters/net_iw.cpp [new file with mode: 0644]
src/adapters/net_nl.cpp [new file with mode: 0644]
src/adapters/pulseaudio.cpp [new file with mode: 0644]
src/cairo/utils.cpp [new file with mode: 0644]
src/components/bar.cpp [new file with mode: 0644]
src/components/builder.cpp [new file with mode: 0644]
src/components/command_line.cpp [new file with mode: 0644]
src/components/config.cpp [new file with mode: 0644]
src/components/config_parser.cpp [new file with mode: 0644]
src/components/controller.cpp [new file with mode: 0644]
src/components/ipc.cpp [new file with mode: 0644]
src/components/logger.cpp [new file with mode: 0644]
src/components/parser.cpp [new file with mode: 0644]
src/components/renderer.cpp [new file with mode: 0644]
src/components/screen.cpp [new file with mode: 0644]
src/components/taskqueue.cpp [new file with mode: 0644]
src/drawtypes/animation.cpp [new file with mode: 0644]
src/drawtypes/iconset.cpp [new file with mode: 0644]
src/drawtypes/label.cpp [new file with mode: 0644]
src/drawtypes/progressbar.cpp [new file with mode: 0644]
src/drawtypes/ramp.cpp [new file with mode: 0644]
src/events/signal_emitter.cpp [new file with mode: 0644]
src/events/signal_receiver.cpp [new file with mode: 0644]
src/ipc.cpp [new file with mode: 0644]
src/main.cpp [new file with mode: 0644]
src/modules/alsa.cpp [new file with mode: 0644]
src/modules/backlight.cpp [new file with mode: 0644]
src/modules/battery.cpp [new file with mode: 0644]
src/modules/bspwm.cpp [new file with mode: 0644]
src/modules/counter.cpp [new file with mode: 0644]
src/modules/cpu.cpp [new file with mode: 0644]
src/modules/date.cpp [new file with mode: 0644]
src/modules/fs.cpp [new file with mode: 0644]
src/modules/github.cpp [new file with mode: 0644]
src/modules/i3.cpp [new file with mode: 0644]
src/modules/ipc.cpp [new file with mode: 0644]
src/modules/memory.cpp [new file with mode: 0644]
src/modules/menu.cpp [new file with mode: 0644]
src/modules/meta/base.cpp [new file with mode: 0644]
src/modules/mpd.cpp [new file with mode: 0644]
src/modules/network.cpp [new file with mode: 0644]
src/modules/pulseaudio.cpp [new file with mode: 0644]
src/modules/script.cpp [new file with mode: 0644]
src/modules/systray.cpp [new file with mode: 0644]
src/modules/temperature.cpp [new file with mode: 0644]
src/modules/text.cpp [new file with mode: 0644]
src/modules/xbacklight.cpp [new file with mode: 0644]
src/modules/xkeyboard.cpp [new file with mode: 0644]
src/modules/xwindow.cpp [new file with mode: 0644]
src/modules/xworkspaces.cpp [new file with mode: 0644]
src/settings.cpp.cmake [new file with mode: 0644]
src/utils/actions.cpp [new file with mode: 0644]
src/utils/bspwm.cpp [new file with mode: 0644]
src/utils/color.cpp [new file with mode: 0644]
src/utils/command.cpp [new file with mode: 0644]
src/utils/concurrency.cpp [new file with mode: 0644]
src/utils/env.cpp [new file with mode: 0644]
src/utils/factory.cpp [new file with mode: 0644]
src/utils/file.cpp [new file with mode: 0644]
src/utils/http.cpp [new file with mode: 0644]
src/utils/i3.cpp [new file with mode: 0644]
src/utils/inotify.cpp [new file with mode: 0644]
src/utils/io.cpp [new file with mode: 0644]
src/utils/process.cpp [new file with mode: 0644]
src/utils/socket.cpp [new file with mode: 0644]
src/utils/string.cpp [new file with mode: 0644]
src/utils/throttle.cpp [new file with mode: 0644]
src/x11/atoms.cpp [new file with mode: 0644]
src/x11/background_manager.cpp [new file with mode: 0644]
src/x11/connection.cpp [new file with mode: 0644]
src/x11/cursor.cpp [new file with mode: 0644]
src/x11/ewmh.cpp [new file with mode: 0644]
src/x11/extensions/composite.cpp [new file with mode: 0644]
src/x11/extensions/randr.cpp [new file with mode: 0644]
src/x11/extensions/xkb.cpp [new file with mode: 0644]
src/x11/icccm.cpp [new file with mode: 0644]
src/x11/registry.cpp [new file with mode: 0644]
src/x11/tray_client.cpp [new file with mode: 0644]
src/x11/tray_manager.cpp [new file with mode: 0644]
src/x11/window.cpp [new file with mode: 0644]
src/x11/winspec.cpp [new file with mode: 0644]
src/x11/xembed.cpp [new file with mode: 0644]
src/x11/xresources.cpp [new file with mode: 0644]
tests/CMakeLists.txt [new file with mode: 0644]
tests/CMakeLists.txt.in [new file with mode: 0644]
tests/common/test.hpp [new file with mode: 0644]
tests/unit_tests/components/bar.cpp [new file with mode: 0644]
tests/unit_tests/components/command_line.cpp [new file with mode: 0644]
tests/unit_tests/components/config_parser.cpp [new file with mode: 0644]
tests/unit_tests/components/parser.cpp [new file with mode: 0644]
tests/unit_tests/drawtypes/iconset.cpp [new file with mode: 0644]
tests/unit_tests/drawtypes/label.cpp [new file with mode: 0644]
tests/unit_tests/utils/actions.cpp [new file with mode: 0644]
tests/unit_tests/utils/color.cpp [new file with mode: 0644]
tests/unit_tests/utils/command.cpp [new file with mode: 0644]
tests/unit_tests/utils/file.cpp [new file with mode: 0644]
tests/unit_tests/utils/math.cpp [new file with mode: 0644]
tests/unit_tests/utils/memory.cpp [new file with mode: 0644]
tests/unit_tests/utils/process.cpp [new file with mode: 0644]
tests/unit_tests/utils/scope.cpp [new file with mode: 0644]
tests/unit_tests/utils/string.cpp [new file with mode: 0644]
version.txt [new file with mode: 0644]

diff --git a/.clang-format b/.clang-format
new file mode 100644 (file)
index 0000000..3caddce
--- /dev/null
@@ -0,0 +1,13 @@
+---
+Language: Cpp
+Standard: Cpp11
+BasedOnStyle: Google
+ColumnLimit: 120
+NamespaceIndentation: All
+AlignAfterOpenBracket: DontAlign
+AllowShortFunctionsOnASingleLine: Empty
+AllowShortIfStatementsOnASingleLine: false
+BreakConstructorInitializersBeforeComma: true
+DerivePointerAlignment: false
+PointerAlignment: Left
+---
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644 (file)
index 0000000..a12c09f
--- /dev/null
@@ -0,0 +1,52 @@
+---
+Checks: '
+        -*,
+        performance-*,
+        readability-*,
+        clang-analyzer-alpha.core*,
+        clang-analyzer-alpha.security*,
+        clang-analyzer-alpha.unix.cstring*,
+        clang-analyzer-core.uninitialized*,
+        clang-analyzer-cplusplus.*,
+        clang-analyzer-nullability*,
+        clang-analyzer-unix*,
+        cppcoreguidelines*,
+        modernize-use-*,
+        modernize-*,
+        -modernize-raw-string-literal,
+        -modernize-use-bool-literals,
+        -modernize-use-trailing-return-type,
+        -readability-implicit-bool-cast,
+        -readability-else-after-return,
+        -readability-named-parameter,
+        -readability-implicit-bool-conversion,
+        -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+        -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+        -cppcoreguidelines-pro-type-vararg,
+        -cppcoreguidelines-pro-type-reinterpret-cast,
+        -cppcoreguidelines-pro-type-union-access,
+        -cppcoreguidelines-pro-type-cstyle-cast,
+        -cppcoreguidelines-pro-bounds-constant-array-index
+        '
+
+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
+    value:           UPPER_CASE
+  - key:             readability-identifier-naming.ClassMethodCase
+    value:           lower_case
+  - key:             readability-identifier-naming.MemberCase
+    value:           lower_case
+  - key:             readability-identifier-naming.ProtectedMemberPrefix
+    value:           'm_'
+  - key:             readability-identifier-naming.PrivateMemberPrefix
+    value:           'm_'
+HeaderFilterRegex: ''
+WarningsAsErrors: ''
+AnalyzeTemporaryDtors: false
+...
diff --git a/.codecov.yml b/.codecov.yml
new file mode 100644 (file)
index 0000000..cb44c86
--- /dev/null
@@ -0,0 +1,18 @@
+coverage:
+  status:
+    project:
+      default:
+        # Coverage can drop by 0.1% without failing the status
+        threshold: 0.1
+    patch:
+      default:
+        # Patches don't need test coverage
+        # TODO remove once we have proper testing infrastructure and documentation
+        target: 0
+
+ignore:
+  - "tests/*"
+  - "lib/*"
+
+comment:
+  require_changes: true
diff --git a/.editorconfig b/.editorconfig
new file mode 100644 (file)
index 0000000..57c08e6
--- /dev/null
@@ -0,0 +1,12 @@
+root = true
+
+[*]
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+
+[Makefile]
+indent_style = tab
+indent_size = 2
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644 (file)
index 0000000..2442129
--- /dev/null
@@ -0,0 +1,61 @@
+---
+name: Bug Report
+about: Create a report for something that misbehaves
+
+---
+
+## Checklist
+
+<!-- Please carefully go through this checklist and put an 'x' inside the brackets: '[x]'  -->
+
+* [ ] I have read the appropriate section in the [contributing
+  guidelines](https://github.com/polybar/polybar/blob/master/CONTRIBUTING.md)
+* [ ] I believe this issue is a problem with polybar itself and not a misconfiguration on my part.
+* [ ] I have searched for other open and closed [issues](https://github.com/polybar/polybar/issues?q=is%3Aissue) that
+  may have already reported this problem.
+* [ ] I have checked the [known issues](https://github.com/polybar/polybar/wiki/Known-Issues) page for this problem.
+
+## Describe the bug
+<!-- A clear and concise description of what the bug is: -->
+
+**Expected behavior:**
+<!-- A clear and concise description of what you expected to happen: -->
+
+
+**Actual behavior:**
+<!-- What actually happens: -->
+
+
+**Was it working before?**
+* Did you also experience this bug in an earlier version of polybar (yes/no/don't know)?
+* If no, what was the last version where this worked correctly?
+
+## To Reproduce
+
+<!-- A minimal but complete config with which the problem occurs: -->
+
+```dosini
+
+```
+<!-- List any other steps needed to reproduce the issue, besides starting polybar with the config you posted above. -->
+
+## Polybar Log
+<!-- Post everything polybar outputs to the terminal when you run it and the issue occurs below -->
+```
+
+```
+
+## Screenshots
+<!-- If applicable, add screenshots to help explain your problem. -->
+
+## Environment:
+* WM:
+* Distro:
+* Output of `polybar -vvv`:
+
+```
+
+```
+
+## Additional context
+<!-- Add any other context that you think is necessary about the problem here. -->
diff --git a/.github/ISSUE_TEMPLATE/build.md b/.github/ISSUE_TEMPLATE/build.md
new file mode 100644 (file)
index 0000000..9e0c86a
--- /dev/null
@@ -0,0 +1,46 @@
+---
+name: Build Issues
+about: Report issues while building polybar from source
+
+---
+
+## Checklist
+
+<!-- Please carefully go through this checklist and put an 'x' inside the brackets: '[x]'  -->
+
+* [ ] I have followed every step on the [compiling wiki page](https://github.com/polybar/polybar/wiki/Compiling) and
+  installed all necessary dependencies.
+* [ ] My problem is not described on the [known issues page](https://github.com/polybar/polybar/wiki/Known-Issues)
+* [ ] I have searched for other open and closed [issues](https://github.com/polybar/polybar/issues?q=is%3Aissue) that
+  may have already reported this problem.
+* [ ] I was able to reproduce this build issue in a clean build
+
+## Build Process
+
+**How did you build polybar?**
+
+<!--
+Put an 'x' inside the brackets ([x]) of the entry that applies and respond to the question inside the parenthesis
+-->
+
+* [ ] From a release archive (Which version?)
+* [ ] By cloning this repository (What is the of `git describe --tags`?)
+* [ ] Some other way (How?)
+
+<!-- List the exact commands you are using to build polybar: -->
+
+
+## Build Log
+<!--
+Copy-paste all the terminal output produced while building polybar.
+This MUST include the output of the `cmake`, `make`, and/or `build.sh` commands, if you used them.
+-->
+```
+
+```
+
+## Environment:
+* Distro (with Version):
+
+## Additional context
+<!-- Add any other context that you think is necessary about the problem here. -->
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644 (file)
index 0000000..6a1549a
--- /dev/null
@@ -0,0 +1,8 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Polybar Gitter Room
+    url: https://gitter.im/polybar/polybar
+    about: Please ask and answer questions here.
+  - name: Polybar subreddit
+    url: https://www.reddit.com/r/polybar
+    about: Please ask and answer questions here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644 (file)
index 0000000..0092b9f
--- /dev/null
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+## Is your feature request related to a problem? Please describe.
+<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
+
+## Why does polybar need this feature?
+<!-- Describe why this feature would be useful to a large percentage of users (You need to convince us). -->
+
+## Describe the solution you'd like
+<!-- A clear and concise description of how your solution would work. This includes possible config options that should be added. -->
+
+## Describe alternatives you've considered
+<!-- A clear and concise description of any alternative solutions or features you've considered, if any. -->
+
+## Additional context
+<!-- Add any other context or screenshots about the feature request here. -->
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644 (file)
index 0000000..442cf05
--- /dev/null
@@ -0,0 +1,26 @@
+<!-- Please read our contributing guide before opening a PR: https://github.com/polybar/polybar/blob/master/CONTRIBUTING.md -->
+
+## What type of PR is this? (check all applicable)
+
+* [ ] Refactor
+* [ ] Feature
+* [ ] Bug Fix
+* [ ] Optimization
+* [ ] Documentation Update
+* [ ] Other: *Replace this with a description of the type of this PR*
+
+## Description
+<!--
+  Document user-facing changes in this PR (for example: new config options, changed behavior).
+
+  You can also motivate design decisions here.
+-->
+
+## Related Issues & Documents
+<!-- For example: Fixes #1234, Closes #6789 -->
+
+## Documentation (check all applicable)
+
+* [ ] This PR requires changes to the Wiki documentation (describe the changes)
+* [ ] This PR requires changes to the documentation inside the git repo (please add them to the PR).
+* [ ] Does not require documentation changes
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644 (file)
index 0000000..9974676
--- /dev/null
@@ -0,0 +1,98 @@
+name: CI
+on:
+  workflow_dispatch:
+    inputs:
+      ref:
+        description: 'ref'
+        required: false
+  push:
+  pull_request:
+
+jobs:
+  docs:
+    runs-on: ubuntu-20.04
+    env:
+      COLOR: "ON"
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          ref: ${{ github.event.inputs.ref }}
+      - name: Install Dependencies
+        run: sudo apt-get install -y python3-sphinx
+      - name: Build Documentation
+        run: |
+          mkdir -p doc/build
+          cd doc/build
+          cmake ..
+          make doc
+
+  build:
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        cxx: [g++, clang++]
+        polybar_build_type: ["full"]
+        build_type: ["Release"]
+        include:
+          - cxx: g++
+            polybar_build_type: "tests"
+            build_type: "Coverage"
+          - cxx: g++
+            polybar_build_type: "minimal"
+            build_type: "Release"
+    env:
+      CXX: ${{ matrix.cxx }}
+      BUILD_TYPE: ${{ matrix.build_type }}
+      POLYBAR_BUILD_TYPE: ${{ matrix.polybar_build_type }}
+      POLYBAR_DIR: ${{ github.workspace }}
+      BUILD_DIR: "${{ github.workspace}}/build"
+      MAKEFLAGS: "-j4"
+      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 \
+            xcb-proto
+
+          if [ "$POLYBAR_BUILD_TYPE" != "minimal" ]; then
+            sudo apt-get install -y \
+              libxcb-xkb-dev \
+              libxcb-cursor-dev \
+              libxcb-xrm-dev \
+              i3-wm \
+              libcurl4-openssl-dev \
+              libjsoncpp-dev \
+              libasound2-dev \
+              libpulse-dev \
+              libiw-dev \
+              libmpdclient-dev
+          fi
+      - uses: actions/checkout@v2
+        with:
+          submodules: true
+          ref: ${{ github.event.inputs.ref }}
+      - name: Summary
+        run: ./common/ci/summary.sh
+      - name: Configure
+        run: ./common/ci/configure.sh
+      - name: Build
+        run: |
+          cd $BUILD_DIR
+          make
+      - name: Tests
+        if: ${{ matrix.polybar_build_type == 'tests' }}
+        run: |
+          cd $BUILD_DIR
+          make check
+          cd $POLYBAR_DIR
+          bash <(curl -s https://codecov.io/bash) -F unittests -a "-ap" -Z
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..f6db60c
--- /dev/null
@@ -0,0 +1,11 @@
+/build
+/tags
+/compile_commands.json
+/config
+*.bak
+*.pyc
+*.swp
+*.tmp
+.tags
+
+polybar-*.tar
diff --git a/.gitmodules b/.gitmodules
new file mode 100644 (file)
index 0000000..4b6f7f5
--- /dev/null
@@ -0,0 +1,8 @@
+[submodule "lib/i3ipcpp"]
+       path = lib/i3ipcpp
+       url = https://github.com/polybar/i3ipcpp
+       branch = master
+[submodule "lib/xpp"]
+       path = lib/xpp
+       url = https://github.com/polybar/xpp
+       branch = master
diff --git a/.valgrind-suppressions b/.valgrind-suppressions
new file mode 100644 (file)
index 0000000..6d33fd4
--- /dev/null
@@ -0,0 +1,101 @@
+{
+   xft/fontconfig
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:FcPatternCreate
+   fun:FcFontRenderPrepare
+   fun:FcFontMatch
+   fun:XftFontMatch
+   fun:XftFontOpenName
+   ...
+   ...
+   ...
+   ...
+   ...
+   ...
+}
+{
+   xft/fontconfig
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   obj:/usr/lib/libfontconfig.so.*
+   obj:/usr/lib/libfontconfig.so.*
+   fun:FcPatternAddDouble
+   obj:/usr/lib/libXft.so.*
+   obj:/usr/lib/libXft.so.*
+   obj:/usr/lib/libXft.so.*
+   obj:/usr/lib/libXft.so.*
+   fun:XftDefaultHasRender
+   fun:XftDefaultSubstitute
+   fun:XftFontMatch
+   fun:XftFontOpenName
+}
+{
+   xft/fontconfig
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:malloc
+   fun:XftFontCheckGlyph
+   fun:XftGlyphRender
+   fun:XftDrawGlyphs
+   fun:XftDrawString16
+   ...
+   ...
+   ...
+   ...
+   ...
+   ...
+   ...
+}
+{
+   xft/fontconfig
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:realloc
+   obj:/usr/lib/libfontconfig.so.*
+   obj:/usr/lib/libfontconfig.so.*
+   fun:FcFontRenderPrepare
+   fun:FcFontMatch
+   fun:XftFontMatch
+   fun:XftFontOpenName
+   ...
+   ...
+   ...
+   ...
+   ...
+}
+{
+   xresource manager
+   Memcheck:Leak
+   match-leak-kinds: definite
+   fun:realloc
+   obj:/usr/lib/libX11.so.*
+   obj:/usr/lib/libX11.so.*
+   obj:/usr/lib/libX11.so.*
+   fun:_XlcCreateLC
+   fun:_XlcDefaultLoader
+   fun:_XOpenLC
+   fun:_XrmInitParseInfo
+   obj:/usr/lib/libX11.so.*
+   fun:XrmGetStringDatabase
+   ...
+   ...
+}
+{
+   xft conditional jump
+   Memcheck:Cond
+   obj:/usr/lib/libfreetype.so.*
+   obj:/usr/lib/libfreetype.so.*
+   fun:FT_Outline_Decompose
+   obj:/usr/lib/libfreetype.so.*
+   obj:/usr/lib/libfreetype.so.*
+   obj:/usr/lib/libfreetype.so.*
+   obj:/usr/lib/libfreetype.so.*
+   obj:/usr/lib/libfreetype.so.*
+   fun:XftFontLoadGlyphs
+   fun:XftGlyphExtents
+   ...
+   ...
+}
diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py
new file mode 100644 (file)
index 0000000..4942991
--- /dev/null
@@ -0,0 +1,126 @@
+import os
+import ycm_core
+
+# Default flags
+flags = [
+    '-std=c++14',
+    '-Wall',
+    '-Wextra',
+
+    # Relative paths are corrected by MakeRelativePathsInFlagsAbsolute
+    '-Isrc',
+    '-Iinclude',
+    '-Ilib/concurrentqueue/include',
+    '-Ilib/i3ipcpp/include',
+    '-Ilib/xpp/include',
+    '-Itests',
+
+    '-I/usr/include',
+    '-I/usr/include/freetype2',
+    ]
+
+# Base directory of the project, parent directory of all source files
+project_dir = os.path.dirname(os.path.abspath(__file__))
+
+# This assumes that everyone coding for this project builds inside the 'build'
+# directory
+compilation_database_folder = project_dir + "/build"
+
+if os.path.exists(compilation_database_folder):
+  database = ycm_core.CompilationDatabase(compilation_database_folder)
+else:
+  database = None
+
+SOURCE_EXTENSIONS = ['.cpp', '.cxx', '.cc', '.c', '.m', '.mm']
+
+# Converts all relative paths in the given flag list to absolute paths with
+# working_directory as the base directory
+def MakeRelativePathsInFlagsAbsolute(flags, working_directory):
+  if not working_directory:
+    return list(flags)
+  new_flags = []
+  make_next_absolute = False
+  path_flags = ['-isystem', '-I', '-iquote', '--sysroot=']
+  for flag in flags:
+    new_flag = flag
+
+    if make_next_absolute:
+      make_next_absolute = False
+      if not flag.startswith('/'):
+        new_flag = os.path.join(working_directory, flag)
+
+    for path_flag in path_flags:
+      if flag == path_flag:
+        make_next_absolute = True
+        break
+
+      if flag.startswith(path_flag):
+        path = flag[len(path_flag):]
+        new_flag = path_flag + os.path.join(working_directory, path)
+        break
+
+    if new_flag:
+      new_flags.append(new_flag)
+  return new_flags
+
+def IsHeaderFile(filename):
+  extension = os.path.splitext(filename)[1]
+  return extension in ['.h', '.hxx', '.hpp', '.hh', ".inl"]
+
+# Tries to query the compilation database for flags
+# For header files it tries to use the flags for a corresponding source file
+def GetCompilationInfoForFile(filename):
+  if not database:
+    return None
+
+  # The compilation_commands.json file generated by CMake does not have entries
+  # for header files. We try to use the compile flags used for the corresponding
+  # source file.
+  #
+  # For this try to replace the file extension with an extension that
+  # corresponds to a source and we also try to replace the 'include' folder in
+  # the path with 'src'
+  if IsHeaderFile(filename) :
+    basename = os.path.splitext(filename)[0]
+
+    # Absolute path of the include and source directories
+    include_dir = project_dir + "/include"
+    src_dir = project_dir + "/src"
+
+    # Absolute path without file extension, with the 'include' folder replaced
+    # with 'src' in the path
+    src_basename = None
+    # If the header file is inside the include dir, try to search in the src dir
+    if basename.startswith(include_dir):
+      # file path relative to include dir
+      rel_path_include = os.path.relpath(basename, include_dir)
+      src_basename = os.path.join(src_dir, rel_path_include)
+
+    for extension in SOURCE_EXTENSIONS:
+      # A list of all possible replacement files to be searched
+      replacement_files = [basename + extension]
+
+      if src_basename:
+        replacement_files.append(src_basename + extension)
+
+      for replacement_file in replacement_files:
+        if os.path.exists(replacement_file):
+          comp_info = database.GetCompilationInfoForFile(replacement_file)
+          if comp_info.compiler_flags_:
+            return comp_info
+  return database.GetCompilationInfoForFile(filename)
+
+def FlagsForFile(filename, **kwargs):
+  compilation_info = GetCompilationInfoForFile(filename)
+  if compilation_info and compilation_info.compiler_flags_:
+    # Bear in mind that compilation_info.compiler_flags_ does NOT return a
+    # python list, but a "list-like" StringVec object
+    final_flags = MakeRelativePathsInFlagsAbsolute(
+        [x for x in compilation_info.compiler_flags_ if x != "-Werror"],
+        compilation_info.compiler_working_dir_)
+  else:
+    # We use default flags if GetCompilationInfoForFile can't find any flags
+    relative_to = project_dir
+    final_flags = MakeRelativePathsInFlagsAbsolute(flags, relative_to)
+
+  return {'flags': final_flags, 'do_cache': True}
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644 (file)
index 0000000..44988bd
--- /dev/null
@@ -0,0 +1,29 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+Each release should have the following subsections, if entries exist, in the
+given order: `Breaking`, `Build`, `Deprecated`, `Removed`, `Added`, `Changed`,
+`Fixed`, `Security`.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+## [3.5.5] - 2021-03-01
+### Build
+- Support older python sphinx versions again ([`#2356`](https://github.com/polybar/polybar/issues/2356))
+
+## [3.5.4] - 2021-01-07
+### Fixed
+- Wrong text displayed if module text ends with `}` ([`#2331`](https://github.com/polybar/polybar/issues/2331))
+
+## [3.5.3] - 2020-12-23
+### Build
+- Don't use `git` when building documentation ([`#2309`](https://github.com/polybar/polybar/issues/2309))
+### Fixed
+- Empty color values are no longer treated as invalid and no longer produce an error.
+
+[Unreleased]: https://github.com/polybar/polybar/compare/3.5.5...HEAD
+[3.5.5]: https://github.com/polybar/polybar/releases/tag/3.5.5
+[3.5.4]: https://github.com/polybar/polybar/releases/tag/3.5.4
+[3.5.3]: https://github.com/polybar/polybar/releases/tag/3.5.3
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d687cdd
--- /dev/null
@@ -0,0 +1,167 @@
+#
+# Build configuration
+#
+cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
+
+# Enable ccache by default and as early as possible because project() performs
+# checks on the compiler
+option(ENABLE_CCACHE "Enable ccache support" ON)
+if(ENABLE_CCACHE)
+  message(STATUS "Trying to enable ccache")
+  find_program(BIN_CCACHE ccache)
+
+  string(ASCII 27 esc)
+  if(NOT BIN_CCACHE)
+    message(STATUS "${esc}[33mCouldn't locate ccache, disabling ccache...${esc}[0m")
+  else()
+    # Enable only if the binary is found
+    message(STATUS "${esc}[32mUsing compiler cache ${BIN_CCACHE}${esc}[0m")
+    set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ${BIN_CCACHE})
+  endif()
+endif()
+
+project(polybar CXX)
+
+# Extract version information from version.txt. The first line that looks like
+# a version string is used, so the file supports comments
+file(STRINGS version.txt version_txt REGEX "^[0-9]+\\.[0-9]+\\.[0-9]+.*$" LIMIT_COUNT 1)
+
+# If we are in a git repo we can get the version information from git describe
+execute_process(COMMAND git describe --tags --dirty=-dev
+  WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
+  RESULT_VARIABLE git_result
+  OUTPUT_VARIABLE git_describe
+  OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
+
+if(git_result EQUAL "0")
+  set(APP_VERSION "${git_describe}")
+else()
+  message(STATUS "Could not detect version with git, falling back to built-in version information.")
+  set(APP_VERSION "${version_txt}")
+endif()
+
+set(CMAKE_MODULE_PATH
+  ${CMAKE_MODULE_PATH}
+  ${PROJECT_SOURCE_DIR}/cmake
+  ${PROJECT_SOURCE_DIR}/cmake/common
+  ${PROJECT_SOURCE_DIR}/cmake/modules)
+
+include(GNUInstallDirs)
+include(utils)
+include(01-core)
+include(02-opts)
+include(03-libs)
+include(04-targets)
+include(05-summary)
+
+if(BUILD_DOC)
+  add_subdirectory(doc)
+endif()
+add_subdirectory(contrib/bash)
+add_subdirectory(contrib/zsh)
+add_subdirectory(include)
+add_subdirectory(lib)
+add_subdirectory(src bin)
+
+# We need to enable testing in the root folder so that 'ctest' and 'make test'
+# can be run in the build directory
+if(BUILD_TESTS)
+  enable_testing()
+  add_subdirectory(tests)
+endif()
+
+
+#
+# Generate configuration file
+#
+
+set(MODULES_LEFT "bspwm i3")
+set(MODULES_CENTER "mpd")
+set(MODULES_RIGHT "filesystem xbacklight alsa pulseaudio xkeyboard memory cpu wlan eth battery temperature date powermenu")
+
+set(FONT_FIXED "fixed:pixelsize=10")
+set(FONT_UNIFONT "unifont:fontformat=truetype")
+set(FONT_SIJI "siji:pixelsize=10")
+
+queryfont(FONT_FIXED ${FONT_FIXED} FIELDS family pixelsize)
+queryfont(FONT_UNIFONT ${FONT_UNIFONT} FIELDS family fontformat)
+queryfont(FONT_SIJI ${FONT_SIJI} FIELDS family pixelsize)
+
+# Strip disabled modules {{{
+
+if(NOT ENABLE_PULSEAUDIO)
+  string(REPLACE " pulseaudio" "" MODULES_RIGHT ${MODULES_RIGHT})
+endif()
+if(NOT ENABLE_ALSA)
+  string(REPLACE " alsa" "" MODULES_RIGHT ${MODULES_RIGHT})
+endif()
+if(NOT ENABLE_I3)
+  string(REPLACE " i3" "" MODULES_LEFT ${MODULES_LEFT})
+endif()
+if(NOT ENABLE_MPD)
+  string(REPLACE "mpd" "" MODULES_CENTER ${MODULES_CENTER})
+endif()
+if(NOT ENABLE_NETWORK)
+  string(REPLACE " wlan eth" "" MODULES_RIGHT ${MODULES_RIGHT})
+endif()
+if(NOT WITH_XRANDR)
+  string(REPLACE "xbacklight " "backlight-acpi " MODULES_RIGHT ${MODULES_RIGHT})
+endif()
+if(NOT WITH_XKB)
+  string(REPLACE "xkeyboard " "" MODULES_RIGHT ${MODULES_RIGHT})
+endif()
+
+# }}}
+# Get battery/adapter name {{{
+
+string(REGEX REPLACE /%battery%.* "" PATH_BAT ${SETTING_PATH_BATTERY})
+string(REGEX REPLACE /%adapter%.* "" PATH_ADP ${SETTING_PATH_ADAPTER})
+file(GLOB BAT_LIST RELATIVE ${PATH_BAT} ${PATH_ADP}/B*)
+file(GLOB ADP_LIST RELATIVE ${PATH_ADP} ${PATH_ADP}/A*)
+if(BAT_LIST)
+  list(GET BAT_LIST 0 BATTERY)
+else()
+  set(BATTERY BAT0)
+endif()
+if(ADP_LIST)
+  list(GET ADP_LIST 0 ADAPTER)
+else()
+  set(ADAPTER ADP1)
+endif()
+
+# }}}
+# Get network interfaces {{{
+
+if(ENABLE_NETWORK)
+  file(GLOB IFLIST RELATIVE /sys/class/net /sys/class/net/*)
+  foreach(INTERFACE ${IFLIST})
+    if(NOT ${INTERFACE} STREQUAL "lo")
+      file(GLOB IS_WIRELESS /sys/class/net/${INTERFACE}/wireless)
+      if(IS_WIRELESS)
+        set(INTERFACE_WLAN ${INTERFACE})
+      else()
+        set(INTERFACE_ETH ${INTERFACE})
+      endif()
+    endif()
+  endforeach()
+  if(NOT INTERFACE_ETH)
+    set(INTERFACE_ETH net0)
+  endif()
+  if(NOT INTERFACE_WLAN)
+    set(INTERFACE_WLAN net1)
+  endif()
+endif()
+
+# }}}
+# Configure and install {{{
+
+configure_file(
+  ${CMAKE_CURRENT_LIST_DIR}/config.cmake
+  ${CMAKE_CURRENT_LIST_DIR}/config
+  ESCAPE_QUOTES @ONLY)
+
+install(FILES config
+  DESTINATION ${CMAKE_INSTALL_DOCDIR}
+  COMPONENT config)
+
+# }}}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..90c8672
--- /dev/null
@@ -0,0 +1,123 @@
+# Contributing
+
+First of all, thank you very much for considering contributing to polybar. You
+are awesome! :tada:
+
+**Table of Contents:**
+* [Bug Reports](#bug-reports)
+* [Pull Requests](#pull-requests)
+  + [Testing](#testing)
+  + [Documentation](#documentation)
+  + [Style](#style)
+
+## Bug Reports
+
+Bugs should be reported at the polybar issue tracker, using the [bug report
+template](https://github.com/polybar/polybar/issues/new?template=bug_report.md).
+Make sure you fill out all the required sections.
+
+Before opening a bug report, please search our [issue
+tracker](https://github.com/polybar/polybar/issues?q=is%3Aissue) and [known
+issues page](https://github.com/polybar/polybar/wiki/Known-Issues) for your
+problem to avoid duplicates.
+
+If your issue has already been reported but is already marked as fixed and the
+version of polybar you are using includes this supposed fix, feel free to open a
+new issue.
+
+You should also go through our [debugging
+guide](https://github.com/polybar/polybar/wiki/Debugging-your-Config) to confirm
+what you are experiencing is indeed a polybar bug and not an issue with your
+configuration.
+This will also help you narrow down the issue which, in turn, will help us
+resolve it, if it turns out to be a bug in polybar.
+
+If this bug was not present in a previous version of polybar and you know how
+to, doing a `git bisect` and providing us with the commit ID that introduced the
+issue would be immensely helpful.
+
+## Pull Requests
+
+If you want to start contributing to polybar, a good place to start are issues
+labeled with
+[help wanted](https://github.com/polybar/polybar/labels/help%20wanted)
+or
+[good first issue](https://github.com/polybar/polybar/labels/good%20first%20issue).
+
+Except for small changes, PRs should always address an already open and accepted
+issue.
+Otherwise you run the risk of spending time implementing something and then the
+PR being rejected because the feature you implemented was not actually something
+we want in polybar.
+
+Issues with any of the following labels are generally safe to start working on,
+unless someone else has already claimed them:
+
+* [bug](https://github.com/polybar/polybar/labels/bug)
+* [confirmed](https://github.com/polybar/polybar/labels/confirmed)
+* [good first issue](https://github.com/polybar/polybar/labels/good%20first%20issue)
+* [help wanted](https://github.com/polybar/polybar/labels/help%20wanted)
+
+For anything else, it's a good idea to first comment under the issue to ask
+whether it is something that can/should be worked on right now.
+This is especially true for issues labeled with `feature` (and none of the
+labels listed above), here a feature may depend on some other things being
+implemented first or it may need to be split into many smaller features, because
+it is too big otherwise.
+In particular, this means that you should not open a feature request and
+immediately start working on that feature, unless you are very sure it will be
+accepted or accept the risk of it being rejected.
+
+Things like documentation changes or refactorings, don't necessarily need an
+issue associated with them.
+These changes are less likely to be rejected since they don't change the
+behavior of polybar.
+Nevertheless, for bigger changes or when in doubt, open an issue and ask whether
+such changes would be desirable.
+
+To claim an issue, comment under it to let others know that you are working on
+it.
+
+Feel free to ask for feedback about your changes at any time.
+Especially when implementing features, this can be very useful because it allows
+us to make sure you are going in the direction we had envisioned for that
+feature and you don't lose time on something that ultimately has to be
+rewritten.
+In that case, a [draft PR](https://github.blog/2019-02-14-introducing-draft-pull-requests/)
+is a useful tool.
+
+When creating a PR, please fill out the PR template.
+
+### Testing
+
+Your PR must pass all existing tests.
+If possible, you should also add tests for the things you write.
+However, this is not always possible, for example when working on modules.
+But at least isolated components should be tested.
+
+See the [testing page](https://github.com/polybar/polybar/wiki/Testing) on the
+wiki for more information.
+Also don't hesitate to ask for help, testing isn't that mature in polybar yet
+and some things may be harder/impossible to test right now.
+
+### Documentation
+
+Right now, documentation for polybar lives in two places: The GitHub wiki and
+the git repo itself.
+
+Ultimately, most of the documentation is supposed to live in the repo itself.
+
+For now, if your PR requires documentation changes in the repo, those changes
+need to be in the PR as well.
+
+Changes on the wiki should not be made right away because the wiki should
+reflect the currently released version and not the development version.
+In that case, outline the documentation changes that need to be made (for
+example, which new config options are available).
+If your PR would introduce a lot of new documentation on the wiki, let us know
+and we can decide if we want to put some of the documentation directly into the
+repo.
+
+### Style
+
+Please read our [style guide](https://github.com/polybar/polybar/wiki/Style-Guide).
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..13e205a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+The MIT License (MIT)
+Copyright (c) 2016 Michael Carlberg
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..a1a67b7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,159 @@
+<p align="center">
+  <img src="banner.png" alt="Polybar">
+</p>
+
+<p align="center">
+A fast and easy-to-use tool for creating status bars.
+</p>
+
+<p align="center">
+<a href="https://github.com/polybar/polybar/releases"><img src="https://img.shields.io/github/release/polybar/polybar.svg"></a>
+<a href="https://github.com/polybar/polybar/actions?query=workflow%3ACI"><img src="https://github.com/polybar/polybar/workflows/CI/badge.svg"></a>
+<a href="https://polybar.readthedocs.io"><img src="https://readthedocs.org/projects/polybar/badge/?version=latest"></a>
+<a href="https://gitter.im/polybar/polybar"><img src="https://badges.gitter.im/polybar/polybar.svg"></a>
+<a href="https://codecov.io/gh/polybar/polybar/branch/master"><img src="https://codecov.io/gh/polybar/polybar/branch/master/graph/badge.svg"></a>
+<a href="https://github.com/polybar/polybar/blob/master/LICENSE"><img src="https://img.shields.io/github/license/polybar/polybar.svg"></a>
+<a href="https://www.codetriage.com/polybar/polybar"><img src="https://www.codetriage.com/polybar/polybar/badges/users.svg"></a>
+</p>
+
+**Polybar** aims to help users build beautiful and highly customizable status bars
+for their desktop environment, without the need of having a black belt in shell scripting.
+Here are a few screenshots showing you what it can look like:
+
+[![sample screenshot](https://i.imgur.com/xvlw9iHt.png)](https://i.imgur.com/xvlw9iH.png)
+[![sample screenshot](https://i.imgur.com/cYQOuRrt.png)](https://i.imgur.com/cYQOuRr.png)
+[![sample screenshot](https://i.imgur.com/A6spiZZt.png)](https://i.imgur.com/A6spiZZ.png)
+[![sample screenshot](https://i.imgur.com/TY5a5r9t.png)](https://i.imgur.com/TY5a5r9.png)
+
+You can find polybar configs for these example images (and other configs) [here](https://github.com/jaagr/dots/tree/master/.local/etc/themer/themes).
+
+## Table of Contents
+
+* [Introduction](#introduction)
+* [Getting Help](#getting-help)
+* [Contributing](#contributing)
+* [Getting started](#getting-started)
+  * [Installation](#installation)
+  * [Configuration](#configuration)
+  * [Running](#running)
+* [Community](#community)
+* [Contributors](#contributors)
+* [License](#license)
+
+
+## Introduction
+
+The main purpose of **Polybar** is to help users create awesome status bars.
+It has built-in functionality to display information about the most commonly used services.
+Some of the services included so far:
+
+- Systray icons
+- Window title
+- Playback controls and status display for [MPD](https://www.musicpd.org/) using [libmpdclient](https://www.musicpd.org/libs/libmpdclient/)
+- [ALSA](https://www.alsa-project.org/main/index.php/Main_Page) and [PulseAudio](https://www.freedesktop.org/wiki/Software/PulseAudio/) volume controls
+- Workspace and desktop panel for [bspwm](https://github.com/baskerville/bspwm) and [i3](https://github.com/i3/i3)
+- Workspace module for [EWMH compliant](https://specifications.freedesktop.org/wm-spec/wm-spec-1.3.html#idm140130320786080) window managers
+- Keyboard layout and indicator status
+- CPU and memory load indicator
+- Battery display
+- Network connection details
+- Backlight level
+- Date and time label
+- Time-based shell script execution
+- Command output tailing
+- User-defined menu tree
+- Inter-process messaging
+- And more...
+
+[See the wiki for more details](https://github.com/polybar/polybar/wiki).
+
+## Getting Help
+
+If you find yourself stuck, have a look at our [Support](SUPPORT.md) page for resources where you can find help.
+
+## Contributing
+
+Read our [contributing guidelines](CONTRIBUTING.md) for how to get started with contributing to polybar.
+
+## Getting started
+
+<a href="https://repology.org/metapackage/polybar">
+    <img src="https://repology.org/badge/vertical-allrepos/polybar.svg" alt="Packaging status" align="right">
+</a>
+
+Polybar was already packaged for the distros listed below.
+If you can't find your distro here, you will have to [build from source](https://github.com/polybar/polybar/wiki/Compiling).
+
+If you are using **Debian** (unstable or testing), you can install [polybar](https://tracker.debian.org/pkg/polybar) using `sudo apt install polybar`.
+If you are using **Debian** (buster/stable), you need to enable [backports](https://wiki.debian.org/Backports) and then install using `sudo apt -t buster-backports install polybar`.
+
+If you are using **Arch Linux**, you can install the AUR package [polybar-git](https://aur.archlinux.org/packages/polybar-git/) to get the latest version, or
+[polybar](https://aur.archlinux.org/packages/polybar/) for the latest stable release.
+
+If you are using **Void Linux**, you can install [polybar](https://github.com/void-linux/void-packages/blob/master/srcpkgs/polybar/template) using `xbps-install -S polybar`.
+
+If you are using **NixOS**, polybar is available in both the stable and unstable channels and can be installed with the command `nix-env -iA nixos.polybar`.
+
+If you are using **Slackware**, polybar is available from the [SlackBuilds](https://slackbuilds.org/repository/14.2/desktop/polybar/) repository.
+
+If you are using **Source Mage GNU/Linux**, polybar spell is available in test grimoire and can be installed via `cast polybar`.
+
+If you are using **openSUSE**, polybar is available from [OBS](https://build.opensuse.org/package/show/X11:Utilities/polybar/) repository. Package is available for openSUSE Leap 15.1, openSUSE Leap 15.2 and Tumbleweed.
+
+If you are using **FreeBSD**, [polybar](https://svnweb.freebsd.org/ports/head/x11/polybar/) can be installed using `pkg install polybar`. Make sure you are using the `latest` package branch.
+
+If you are using **Gentoo**, both release and git-master versions are available in the [main](https://packages.gentoo.org/packages/x11-misc/polybar) repository.
+
+If you are using **Fedora**, you can install [polybar](https://src.fedoraproject.org/rpms/polybar) using `sudo dnf install polybar`.
+
+### Installation
+
+The [compiling page](https://github.com/polybar/polybar/wiki/Compiling) on the
+wiki describes all steps necessary to build and install polybar.
+
+### Configuration
+
+Details on how to setup and configure the bar and each module have been moved to [the wiki](https://github.com/polybar/polybar/wiki/Configuration).
+
+#### Install the example configuration
+Run the following inside the build directory:
+~~~ sh
+$ make userconfig
+~~~
+Or you can copy the example config from `/usr/share/doc/polybar/config` or ` /usr/local/share/doc/polybar/config` (depending on your install parameters)
+
+#### Launch the example bar
+  ~~~ sh
+  $ polybar example
+  ~~~
+
+### Running
+
+[See the wiki for details on how to run polybar](https://github.com/polybar/polybar/wiki).
+
+## Community
+Want to get in touch?
+
+* Join our Gitter room at [gitter.im/polybar/polybar](https://gitter.im/polybar/polybar)
+* We have our own subreddit at [r/polybar](https://www.reddit.com/r/polybar).
+* Chat with us in the `#polybar` IRC channel on the `chat.freenode.net` server.
+
+## Contributors
+
+### Owner
+* Michael Carlberg [**@jaagr**](https://github.com/jaagr/)
+
+### Maintainers
+* [**@NBonaparte**](https://github.com/NBonaparte)
+* Chase Geigle [**@skystrife**](https://github.com/skystrife)
+* Patrick Ziegler [**@patrick96**](https://github.com/patrick96)
+
+### Logo Design by
+* [**@Tobaloidee**](https://github.com/Tobaloidee)
+
+
+### [All Contributors](https://github.com/polybar/polybar/graphs/contributors)
+
+## License
+
+Polybar is licensed under the MIT license. [See LICENSE for more information](https://github.com/polybar/polybar/blob/master/LICENSE).
diff --git a/SUPPORT.md b/SUPPORT.md
new file mode 100644 (file)
index 0000000..c4e7c0c
--- /dev/null
@@ -0,0 +1,14 @@
+Getting Help
+============
+
+If you need help or troubleshooting tips or just have a question:
+
+* If applicable, go through our [debugging guide](https://github.com/polybar/polybar/wiki/Debugging-your-Config).
+* Read the [Known Issues page](https://github.com/polybar/polybar/wiki/Known-Issues), maybe others had the same issue before.
+* Read the [Wiki page](https://github.com/polybar/polybar/wiki) for the thing you have problems with.
+* Join our Gitter room at [gitter.im/polybar/polybar](https://gitter.im/polybar/polybar)
+* Ask in our reddit community at [r/polybar](https://www.reddit.com/r/polybar)
+* Join the official IRC channel `#polybar` on the `chat.freenode.net` network. If you don't get an answer try asking on [Gitter](https://gitter.im/polybar/polybar).
+* Ask on [Unix & Linux StackExchange](https://unix.stackexchange.com/). Though not all questions may be suited over there, make sure you're [on topic](https://unix.stackexchange.com/help/on-topic).
+
+Please **do not** use the github issue tracker to ask for help or if you have a question, it is meant for bug reports and feature requests. Issues will be closed and you will be referred to the above resources.
diff --git a/banner.png b/banner.png
new file mode 100644 (file)
index 0000000..6fb984f
Binary files /dev/null and b/banner.png differ
diff --git a/build.sh b/build.sh
new file mode 100755 (executable)
index 0000000..50d1c0e
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,273 @@
+#!/usr/bin/env bash
+
+readonly SELF=${0##*/}
+declare -rA COLORS=(
+  [RED]=$'\033[0;31m'
+  [GREEN]=$'\033[0;32m'
+  [BLUE]=$'\033[0;34m'
+  [PURPLE]=$'\033[0;35m'
+  [CYAN]=$'\033[0;36m'
+  [WHITE]=$'\033[0;37m'
+  [YELLOW]=$'\033[0;33m'
+  [BOLD]=$'\033[1m'
+  [OFF]=$'\033[0m'
+)
+
+usage() {
+  echo "
+  Builds and installs polybar.
+
+  ${COLORS[GREEN]}${COLORS[BOLD]}Usage:${COLORS[OFF]}
+      ${COLORS[CYAN]}${SELF}${COLORS[OFF]} [options]
+
+  ${COLORS[GREEN]}${COLORS[BOLD]}Options:${COLORS[OFF]}
+      ${COLORS[GREEN]}-3, --i3${COLORS[OFF]}
+          Include support for internal/i3 (requires i3); disabled by default.
+      ${COLORS[GREEN]}-a, --alsa${COLORS[OFF]}
+          Include support for internal/alsa (requires alsalib); disabled by default.
+      ${COLORS[GREEN]}-p, --pulseaudio${COLORS[OFF]}
+          Include support for internal/pulseaudio (requires libpulse); disabled by default.
+      ${COLORS[GREEN]}-n, --network${COLORS[OFF]}
+          Include support for internal/network (requires libnl/libiw); disabled by default.
+      ${COLORS[GREEN]}-m, --mpd${COLORS[OFF]}
+          Include support for internal/mpd (requires libmpdclient); disabled by default.
+      ${COLORS[GREEN]}-c, --curl${COLORS[OFF]}
+          Include support for internal/github (requires libcurl); disabled by default.
+      ${COLORS[GREEN]}-i, --ipc${COLORS[OFF]}
+          Build polybar-msg used to send ipc messages; disabled by default.
+      ${COLORS[GREEN]}--all-features${COLORS[OFF]}
+          Enable all abovementioned features;
+          equal to -3 -a -p -n -m -c -i
+      ${COLORS[GREEN]}-g, --gcc${COLORS[OFF]}
+          Use GCC even if Clang is installed; disabled by default.
+      ${COLORS[GREEN]}-j, --jobs${COLORS[OFF]}
+          Use make -j to use make jobs with $(nproc) jobs; disabled by default.
+      ${COLORS[GREEN]}-f${COLORS[OFF]}
+          Remove existing build dir; disabled by default.
+      ${COLORS[GREEN]}-I, --no-install${COLORS[OFF]}
+          Do not execute 'sudo make install'; enabled by default.
+      ${COLORS[GREEN]}-C, --install-config${COLORS[OFF]}
+          Install example configuration; disabled by default.
+      ${COLORS[GREEN]}-A, --auto${COLORS[OFF]}
+          Automatic, non-interactive installation; disabled by default.
+          When set, script defaults options not explicitly set.
+      ${COLORS[GREEN]}-h, --help${COLORS[OFF]}
+          Displays this help.
+"
+}
+
+msg_err() {
+  echo -e "${COLORS[RED]}${COLORS[BOLD]}** ${COLORS[OFF]}$*\n"
+  exit 1
+}
+
+msg() {
+  echo -e "${COLORS[GREEN]}${COLORS[BOLD]}** ${COLORS[OFF]}$*\n"
+}
+
+install() {
+  local p
+
+  if [[ "$AUTO" == ON ]]; then
+    [[ -z "$INSTALL" ]] && INSTALL="ON"
+    [[ -z "$INSTALL_CONF" ]] && INSTALL_CONF="OFF"
+  fi
+
+  if [[ -z "$INSTALL" ]]; then
+    read -r -p "$(msg "Execute 'sudo make install'? [Y/n] ")" -n 1 p && echo
+    [[ "${p^^}" != "N" ]] && INSTALL="ON" || INSTALL="OFF"
+  fi
+
+  if [[ -z "$INSTALL_CONF" ]]; then
+    read -r -p "$(msg "Install example configuration? [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && INSTALL_CONF="OFF" || INSTALL_CONF="ON"
+  fi
+
+
+  if [[ "$INSTALL" == ON ]]; then
+    sudo make install || msg_err "Failed to install executables..."
+  fi
+
+  if [[ "$INSTALL_CONF" == ON ]]; then
+    make userconfig || msg_err "Failed to install user configuration..."
+  fi
+}
+
+set_build_opts() {
+  local p
+
+  msg "Setting build options"
+
+  if [[ "$AUTO" == ON ]]; then
+    [[ -z "$USE_GCC" ]] && USE_GCC="OFF"
+    [[ -z "$ENABLE_I3" ]] && ENABLE_I3="OFF"
+    [[ -z "$ENABLE_ALSA" ]] && ENABLE_ALSA="OFF"
+    [[ -z "$ENABLE_PULSEAUDIO" ]] && ENABLE_PULSEAUDIO="OFF"
+    [[ -z "$ENABLE_NETWORK" ]] && ENABLE_NETWORK="OFF"
+    [[ -z "$ENABLE_MPD" ]] && ENABLE_MPD="OFF"
+    [[ -z "$ENABLE_CURL" ]] && ENABLE_CURL="OFF"
+    [[ -z "$ENABLE_IPC_MSG" ]] && ENABLE_IPC_MSG="OFF"
+    [[ -z "$JOB_COUNT" ]] && JOB_COUNT=1
+  fi
+
+  if [[ -z "$USE_GCC" ]]; then
+    read -r -p "$(msg "Use GCC even if Clang is installed ----------------------------- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && USE_GCC="OFF" || USE_GCC="ON"
+  fi
+
+  if [[ -z "$ENABLE_I3" ]]; then
+    read -r -p "$(msg "Include support for \"internal/i3\" (requires i3) ---------------- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_I3="OFF" || ENABLE_I3="ON"
+  fi
+
+  if [[ -z "$ENABLE_ALSA" ]]; then
+    read -r -p "$(msg "Include support for \"internal/alsa\" (requires alsalib) --------- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_ALSA="OFF" || ENABLE_ALSA="ON"
+  fi
+
+  if [[ -z "$ENABLE_PULSEAUDIO" ]]; then
+    read -r -p "$(msg "Include support for \"internal/pulseaudio\" (requires libpulse) -- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_PULSEAUDIO="OFF" || ENABLE_PULSEAUDIO="ON"
+  fi
+
+  if [[ -z "$ENABLE_NETWORK" ]]; then
+    read -r -p "$(msg "Include support for \"internal/network\" (requires libnl/libiw) -- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_NETWORK="OFF" || ENABLE_NETWORK="ON"
+  fi
+
+  if [[ -z "$ENABLE_MPD" ]]; then
+    read -r -p "$(msg "Include support for \"internal/mpd\" (requires libmpdclient) ----- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_MPD="OFF" || ENABLE_MPD="ON"
+  fi
+
+  if [[ -z "$ENABLE_CURL" ]]; then
+    read -r -p "$(msg "Include support for \"internal/github\" (requires libcurl) ------- [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_CURL="OFF" || ENABLE_CURL="ON"
+  fi
+
+  if [[ -z "$ENABLE_IPC_MSG" ]]; then
+    read -r -p "$(msg "Build \"polybar-msg\" used to send ipc messages ------------------ [y/N]: ")" -n 1 p && echo
+    [[ "${p^^}" != "Y" ]] && ENABLE_IPC_MSG="OFF" || ENABLE_IPC_MSG="ON"
+  fi
+  
+  if [[ -z "$JOB_COUNT" ]]; then
+       read -r -p "$(msg "Parallelize the build using make -j$(nproc) --------------------------- [y/N]: ")" -n 1 p && echo
+       [[ "${p^^}" != "Y" ]] && JOB_COUNT=1 || JOB_COUNT=$(nproc)
+  fi
+
+
+  CXX="c++"
+
+  if [[ "$USE_GCC" == OFF ]]; then
+    if command -v clang++ >/dev/null; then
+      msg "Using compiler: clang++/clang"
+      CXX="clang++"
+    elif command -v g++ >/dev/null; then
+      msg "Using compiler: g++/gcc"
+      CXX="g++"
+    fi
+  else
+    CXX="g++"
+  fi
+}
+
+main() {
+  [[ -d ./.git ]] && {
+    msg "Fetching submodules"
+    git submodule update --init --recursive || msg_err "Failed to clone submodules"
+  }
+
+  [[ -d ./build ]] && {
+    if [[ "$REMOVE_BUILD_DIR" == ON ]]; then
+      msg "Removing existing build dir (-f)"
+      rm -rf ./build >/dev/null || msg_err "Failed to remove existing build dir"
+    else
+      msg "A build dir already exists (pass -f to replace)"
+    fi
+  }
+
+  mkdir -p ./build || msg_err "Failed to create build dir"
+  cd ./build || msg_err "Failed to enter build dir"
+
+  set_build_opts
+
+  msg "Executing cmake command"
+  cmake                                       \
+    -DCMAKE_CXX_COMPILER="${CXX}"             \
+    -DENABLE_ALSA:BOOL="${ENABLE_ALSA}"       \
+    -DENABLE_PULSEAUDIO:BOOL="${ENABLE_PULSEAUDIO}"\
+    -DENABLE_I3:BOOL="${ENABLE_I3}"           \
+    -DENABLE_MPD:BOOL="${ENABLE_MPD}"         \
+    -DENABLE_NETWORK:BOOL="${ENABLE_NETWORK}" \
+    -DENABLE_CURL:BOOL="${ENABLE_CURL}"       \
+    -DBUILD_IPC_MSG:BOOL="${ENABLE_IPC_MSG}"   \
+    .. || msg_err "Failed to generate build... read output to get a hint of what went wrong"
+
+  msg "Building project"
+  if [ -z ${JOB_COUNT} ]; then
+       make || msg_err "Failed to build project"
+  else
+       make -j$JOB_COUNT || msg_err "Failed to build project"
+  fi
+  install
+  msg "Build complete!"
+
+  exit 0
+}
+
+
+#################
+###### Entry
+#################
+while [[ "$1" == -* ]]; do
+  case "$1" in
+    -3|--i3)
+      ENABLE_I3=ON; shift ;;
+    -a|--alsa)
+      ENABLE_ALSA=ON; shift ;;
+    -p|--pulseaudio)
+      ENABLE_PULSEAUDIO=ON; shift ;;
+    -n|--network)
+      ENABLE_NETWORK=ON; shift ;;
+    -m|--mpd)
+      ENABLE_MPD=ON; shift ;;
+    -c|--curl)
+      ENABLE_CURL=ON; shift ;;
+    -i|--ipc)
+      ENABLE_IPC_MSG=ON; shift ;;
+    --all-features)
+      ENABLE_I3=ON
+      ENABLE_ALSA=ON
+      ENABLE_PULSEAUDIO=ON
+      ENABLE_NETWORK=ON
+      ENABLE_MPD=ON
+      ENABLE_CURL=ON
+      ENABLE_IPC_MSG=ON
+      shift ;;
+
+    -g|--gcc)
+      USE_GCC=ON; shift ;;
+    -j|--jobs)
+         JOB_COUNT=$(nproc); shift ;;
+    -f)
+      REMOVE_BUILD_DIR=ON; shift ;;
+    -I|--no-install)
+      INSTALL=OFF; shift ;;
+    -C|--install-config)
+      INSTALL_CONF=ON; shift ;;
+    -A|--auto)
+      AUTO=ON; shift ;;
+    -h|--help)
+      usage
+      exit 0
+      ;;
+    --) shift; break ;;
+    *)
+      usage
+      [[ "$1" =~ ^-[0-9a-zA-Z]{2,}$ ]] && msg_err "don't combine options: ie do [-c -i] instead of [-ci]" || msg_err "unknown option [$1]"
+      ;;
+  esac
+done
+
+main
+
diff --git a/cmake/01-core.cmake b/cmake/01-core.cmake
new file mode 100644 (file)
index 0000000..282a559
--- /dev/null
@@ -0,0 +1,93 @@
+#
+# Core setup
+#
+
+set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+
+set(THREADS_PREFER_PTHREAD_FLAG ON)
+
+# Export compile commands used for custom targets
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+# Set default build type if not specified
+if(NOT CMAKE_BUILD_TYPE)
+  set(CMAKE_BUILD_TYPE "Release")
+  message_colored(STATUS "No build type specified; using ${CMAKE_BUILD_TYPE}" 33)
+endif()
+string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPER)
+
+# Compiler flags
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic")
+
+if (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+  # Need dprintf() for FreeBSD 11.1 and older
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_WITH_DPRINTF")
+  # libinotify uses c99 extension, so suppress this error
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-c99-extensions")
+  # Ensures that libraries from dependencies in LOCALBASE are used
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib")
+endif()
+
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-error=parentheses-equality")
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-zero-length-array")
+endif()
+
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g2")
+
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
+  set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} -fdata-sections -ffunction-sections")
+  set(CMAKE_EXE_LINKER_FLAGS_MINSIZEREL "${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL} -Wl,--gc-sections,--icf=safe")
+endif()
+
+# Check compiler
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL Clang)
+  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "3.4.0")
+    message_colored(FATAL_ERROR "Compiler not supported (Requires clang-3.4+ or gcc-5.1+)" 31)
+  else()
+    message_colored(STATUS "Using supported compiler ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" 32)
+  endif()
+elseif(${CMAKE_CXX_COMPILER_ID} STREQUAL GNU)
+  if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.1.0")
+    message_colored(FATAL_ERROR "Compiler not supported (Requires clang-3.4+ or gcc-5.1+)" 31)
+  else()
+    message_colored(STATUS "Using supported compiler ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION}" 32)
+  endif()
+else()
+  message_colored(WARNING "Using unsupported compiler ${CMAKE_CXX_COMPILER_ID}-${CMAKE_CXX_COMPILER_VERSION} !" 31)
+endif()
+
+# Set compiler and linker flags for preferred C++ library
+if(CXXLIB_CLANG)
+  message_colored(STATUS "Linking against libc++" 32)
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lc++ -lc++abi")
+elseif(CXXLIB_GCC)
+  message_colored(STATUS "Linking against libstdc++" 32)
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++")
+endif()
+
+# Custom build type ; SANITIZE
+SET(CMAKE_CXX_FLAGS_SANITIZE "-O0 -g -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls"
+  CACHE STRING "Flags used by the C++ compiler during sanitize builds." FORCE)
+SET(CMAKE_EXE_LINKER_FLAGS_SANITIZE ""
+  CACHE STRING "Flags used for linking binaries during sanitize builds." FORCE)
+SET(CMAKE_SHARED_LINKER_FLAGS_SANITIZE ""
+  CACHE STRING "Flags used by the shared libraries linker during sanitize builds." FORCE)
+MARK_AS_ADVANCED(
+  CMAKE_CXX_FLAGS_SANITIZE
+  CMAKE_EXE_LINKER_FLAGS_SANITIZE
+  CMAKE_SHARED_LINKER_FLAGS_SANITIZE)
+
+# Custom build type ; Coverage
+SET(CMAKE_CXX_FLAGS_COVERAGE
+  "${CMAKE_CXX_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_COVERAGE} --coverage")
+SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE
+  "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${CMAKE_EXE_LINKER_FLAGS_COVERAGE}")
+SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE
+  "${CMAKE_SHARED_LINKER_FLAGS_DEBUG} ${CMAKE_SHARED_LINKER_FLAGS_COVERAGE}")
diff --git a/cmake/02-opts.cmake b/cmake/02-opts.cmake
new file mode 100644 (file)
index 0000000..6aa7b7b
--- /dev/null
@@ -0,0 +1,82 @@
+#
+# Build options
+#
+set(SPHINX_BUILD "sphinx-build" CACHE STRING "Name/Path of the sphinx-build executable to use.")
+checklib(BUILD_DOC "binary" "${SPHINX_BUILD}")
+
+checklib(ENABLE_ALSA "pkg-config" alsa)
+checklib(ENABLE_CURL "pkg-config" libcurl)
+checklib(ENABLE_I3 "binary" i3)
+checklib(ENABLE_MPD "pkg-config" libmpdclient)
+checklib(WITH_LIBNL "pkg-config" libnl-genl-3.0)
+if(WITH_LIBNL)
+  checklib(ENABLE_NETWORK "pkg-config" libnl-genl-3.0)
+  set(WIRELESS_LIB "libnl")
+else()
+  checklib(ENABLE_NETWORK "cmake" Libiw)
+  set(WIRELESS_LIB "wireless-tools")
+endif()
+checklib(ENABLE_PULSEAUDIO "pkg-config" libpulse)
+checklib(WITH_XKB "pkg-config" xcb-xkb)
+checklib(WITH_XRM "pkg-config" xcb-xrm)
+checklib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12")
+checklib(WITH_XCURSOR "pkg-config" "xcb-cursor")
+
+if(NOT DEFINED ENABLE_CCACHE AND CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG)
+  set(ENABLE_CCACHE ON)
+endif()
+
+option(CXXLIB_CLANG "Link against libc++" OFF)
+option(CXXLIB_GCC "Link against stdlibc++" OFF)
+
+option(BUILD_IPC_MSG "Build ipc messager" ON)
+option(BUILD_TESTS "Build testsuite" OFF)
+option(BUILD_DOC "Build documentation" ON)
+
+option(ENABLE_ALSA "Enable alsa support" ON)
+option(ENABLE_CURL "Enable curl support" ON)
+option(ENABLE_I3 "Enable i3 support" ON)
+option(ENABLE_MPD "Enable mpd support" ON)
+option(WITH_LIBNL "Use netlink interface for wireless" ON)
+option(ENABLE_NETWORK "Enable network support" ON)
+option(ENABLE_XKEYBOARD "Enable xkeyboard support" ON)
+option(ENABLE_PULSEAUDIO "Enable PulseAudio support" ON)
+
+option(WITH_XRANDR "xcb-randr support" ON)
+option(WITH_XRANDR_MONITORS "xcb-randr monitor support" ON)
+option(WITH_XCOMPOSITE "xcb-composite support" ON)
+option(WITH_XKB "xcb-xkb support" ON)
+option(WITH_XRM "xcb-xrm support" ON)
+option(WITH_XCURSOR "xcb-cursor support" ON)
+
+option(DEBUG_LOGGER "Trace logging" ON)
+
+if(CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG)
+  option(DEBUG_LOGGER_VERBOSE "Trace logging (verbose)" OFF)
+  option(DEBUG_HINTS "Debug clickable areas" OFF)
+  option(DEBUG_WHITESPACE "Debug whitespace" OFF)
+  option(DEBUG_FONTCONFIG "Debug fontconfig" OFF)
+endif()
+
+set(SETTING_ALSA_SOUNDCARD "default"
+  CACHE STRING "Name of the ALSA soundcard driver")
+set(SETTING_BSPWM_SOCKET_PATH "/tmp/bspwm_0_0-socket"
+  CACHE STRING "Path to bspwm socket")
+set(SETTING_BSPWM_STATUS_PREFIX "W"
+  CACHE STRING "Prefix prepended to the bspwm status line")
+set(SETTING_CONNECTION_TEST_IP "8.8.8.8"
+  CACHE STRING "Address to ping when testing network connection")
+set(SETTING_PATH_ADAPTER "/sys/class/power_supply/%adapter%"
+  CACHE STRING "Path to adapter")
+set(SETTING_PATH_BACKLIGHT "/sys/class/backlight/%card%"
+  CACHE STRING "Path to backlight sysfs folder")
+set(SETTING_PATH_BATTERY "/sys/class/power_supply/%battery%"
+  CACHE STRING "Path to battery")
+set(SETTING_PATH_CPU_INFO "/proc/stat"
+  CACHE STRING "Path to file containing cpu info")
+set(SETTING_PATH_MEMORY_INFO "/proc/meminfo"
+  CACHE STRING "Path to file containing memory info")
+set(SETTING_PATH_MESSAGING_FIFO "/tmp/polybar_mqueue.%pid%"
+  CACHE STRING "Path to file containing the current temperature")
+set(SETTING_PATH_TEMPERATURE_INFO "/sys/class/thermal/thermal_zone%zone%/temp"
+  CACHE STRING "Path to file containing the current temperature")
diff --git a/cmake/03-libs.cmake b/cmake/03-libs.cmake
new file mode 100644 (file)
index 0000000..1d620e9
--- /dev/null
@@ -0,0 +1,30 @@
+#
+# Check libraries
+#
+
+find_package(Threads REQUIRED)
+list(APPEND libs ${CMAKE_THREAD_LIBS_INIT})
+
+querylib(TRUE "pkg-config" cairo-fc libs dirs)
+
+querylib(ENABLE_ALSA "pkg-config" alsa libs dirs)
+querylib(ENABLE_CURL "pkg-config" libcurl libs dirs)
+querylib(ENABLE_MPD "pkg-config" libmpdclient libs dirs)
+if(WITH_LIBNL)
+  querylib(ENABLE_NETWORK "pkg-config" libnl-genl-3.0 libs dirs)
+else()
+  querylib(ENABLE_NETWORK "cmake" Libiw libs dirs)
+endif()
+querylib(ENABLE_PULSEAUDIO "pkg-config" libpulse libs dirs)
+
+querylib(WITH_XCOMPOSITE "pkg-config" xcb-composite libs dirs)
+querylib(WITH_XKB "pkg-config" xcb-xkb libs dirs)
+querylib(WITH_XRANDR "pkg-config" xcb-randr libs dirs)
+querylib(WITH_XRANDR_MONITORS "pkg-config" "xcb-randr>=1.12" libs dirs)
+querylib(WITH_XRM "pkg-config" xcb-xrm libs dirs)
+querylib(WITH_XCURSOR "pkg-config" xcb-cursor libs dirs)
+
+# FreeBSD Support
+if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
+  querylib(TRUE "pkg-config" libinotify libs dirs)
+endif()
diff --git a/cmake/04-targets.cmake b/cmake/04-targets.cmake
new file mode 100644 (file)
index 0000000..b1d6c3b
--- /dev/null
@@ -0,0 +1,76 @@
+#
+# Custom targets
+#
+
+# Target: userconfig {{{
+
+configure_file(
+  ${PROJECT_SOURCE_DIR}/cmake/templates/userconfig.cmake.in
+  ${PROJECT_BINARY_DIR}/cmake/userconfig.cmake
+  ESCAPE_QUOTES @ONLY)
+
+add_custom_target(userconfig
+  DEPENDS ${PROJECT_NAME}
+  COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/cmake/userconfig.cmake)
+
+# }}}
+# Target: uninstall {{{
+
+configure_file(
+  ${PROJECT_SOURCE_DIR}/cmake/templates/uninstall.cmake.in
+  ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake
+  ESCAPE_QUOTES @ONLY)
+
+add_custom_target(uninstall
+  COMMAND ${CMAKE_COMMAND} -P ${PROJECT_BINARY_DIR}/cmake/uninstall.cmake)
+
+# }}}
+
+# folders where the clang tools should operate
+set(CLANG_SEARCH_PATHS ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/tests)
+
+# Target: codeformat (clang-format) {{{
+
+add_custom_target(codeformat)
+add_custom_command(TARGET codeformat
+  COMMAND ${PROJECT_SOURCE_DIR}/common/clang-format.sh ${CLANG_SEARCH_PATHS})
+
+# }}}
+# Target: codecheck (clang-tidy) {{{
+
+add_custom_target(codecheck)
+add_custom_command(TARGET codecheck
+  COMMAND ${PROJECT_SOURCE_DIR}/common/clang-tidy.sh
+  ${PROJECT_BINARY_DIR} ${CLANG_SEARCH_PATHS})
+
+# }}}
+# Target: codecheck-fix (clang-tidy + clang-format) {{{
+
+add_custom_target(codecheck-fix)
+add_custom_command(TARGET codecheck-fix
+  COMMAND ${PROJECT_SOURCE_DIR}/common/clang-tidy.sh
+  ${PROJECT_BINARY_DIR} -fix ${CLANG_SEARCH_PATHS})
+
+# }}}
+
+# Target: memcheck (valgrind) {{{
+
+add_custom_target(memcheck)
+add_custom_command(TARGET memcheck
+  COMMAND valgrind
+  --leak-check=summary
+  --suppressions=${PROJECT_SOURCE_DIR}/.valgrind-suppressions
+  ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}
+  example --config=${PROJECT_SOURCE_DIR}/doc/config)
+
+add_custom_target(memcheck-full)
+add_custom_command(TARGET memcheck-full
+  COMMAND valgrind
+  --leak-check=full
+  --track-origins=yes
+  --track-fds=yes
+  --suppressions=${PROJECT_SOURCE_DIR}/.valgrind-suppressions
+  ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}/${PROJECT_NAME}
+  example --config=${PROJECT_SOURCE_DIR}/doc/config)
+
+# }}}
diff --git a/cmake/05-summary.cmake b/cmake/05-summary.cmake
new file mode 100644 (file)
index 0000000..ddcb2b0
--- /dev/null
@@ -0,0 +1,49 @@
+#
+# Output build summary
+#
+
+message(STATUS " Build:")
+message_colored(STATUS "   Version: ${APP_VERSION}" "32;1")
+message_colored(STATUS "   Type: ${CMAKE_BUILD_TYPE}" "37;2")
+message_colored(STATUS "   CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}" "37;2")
+message_colored(STATUS "   LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS} ${CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}" "37;2")
+
+message(STATUS " Install Paths:")
+message_colored(STATUS "   PREFIX:  ${CMAKE_INSTALL_PREFIX}" "32")
+message_colored(STATUS "   BINDIR:  ${CMAKE_INSTALL_FULL_BINDIR}" "32")
+message_colored(STATUS "   DATADIR: ${CMAKE_INSTALL_FULL_DATADIR}" "32")
+message_colored(STATUS "   DOCDIR:  ${CMAKE_INSTALL_FULL_DOCDIR}" "32")
+message_colored(STATUS "   MANDIR:  ${CMAKE_INSTALL_FULL_MANDIR}" "32")
+
+message(STATUS " Targets:")
+colored_option("   polybar-msg" BUILD_IPC_MSG)
+colored_option("   testsuite" BUILD_TESTS)
+colored_option("   documentation" BUILD_DOC)
+
+message(STATUS " Module support:")
+colored_option("   alsa" ENABLE_ALSA)
+colored_option("   curl" ENABLE_CURL)
+colored_option("   i3" ENABLE_I3)
+colored_option("   mpd" ENABLE_MPD)
+colored_option("   network (${WIRELESS_LIB})" ENABLE_NETWORK)
+colored_option("   pulseaudio" ENABLE_PULSEAUDIO)
+colored_option("   xkeyboard" WITH_XKB)
+
+message(STATUS " X extensions:")
+colored_option("   xcb-randr" WITH_XRANDR)
+colored_option("   xcb-randr (monitor support)" WITH_XRANDR_MONITORS)
+colored_option("   xcb-composite" WITH_XCOMPOSITE)
+colored_option("   xcb-xkb" WITH_XKB)
+colored_option("   xcb-xrm" WITH_XRM)
+colored_option("   xcb-cursor" WITH_XCURSOR)
+
+message(STATUS " Log options:")
+colored_option("   Trace logging" DEBUG_LOGGER)
+
+if(CMAKE_BUILD_TYPE_UPPER MATCHES DEBUG)
+  message(STATUS " Debug options:")
+  colored_option("   Trace logging (verbose)" DEBUG_LOGGER_VERBOSE)
+  colored_option("   Draw clickable areas" DEBUG_HINTS)
+  colored_option("   Print fc-match details" DEBUG_FONTCONFIG)
+  colored_option("   Enable window shading" DEBUG_SHADED)
+endif()
diff --git a/cmake/common/utils.cmake b/cmake/common/utils.cmake
new file mode 100644 (file)
index 0000000..bc3cb7a
--- /dev/null
@@ -0,0 +1,120 @@
+#
+# Collection of cmake utility functions
+#
+
+# message_colored {{{
+
+function(message_colored message_level text color)
+  string(ASCII 27 esc)
+  message(${message_level} "${esc}[${color}m${text}${esc}[0m")
+endfunction()
+
+# }}}
+# colored_option {{{
+
+function(colored_option text flag)
+  # Append version of option, if ${flag}_VERSION is set
+  set(version ${${flag}_VERSION})
+
+  if(NOT "${version}" STREQUAL "")
+    set(text "${text} (${version})")
+  endif()
+
+  if(${flag})
+    message_colored(STATUS "[X]${text}" "32;1")
+  else()
+    message_colored(STATUS "[ ]${text}" "37;2")
+  endif()
+endfunction()
+
+# }}}
+
+# queryfont {{{
+
+function(queryfont output_variable fontname)
+  set(multi_value_args FIELDS)
+  cmake_parse_arguments(ARG "" "" "${multi_value_args}" ${ARGN})
+
+  find_program(BIN_FCLIST fc-list)
+  if(NOT BIN_FCLIST)
+    message_colored(WARNING "Failed to locate `fc-list`" "33;1")
+    return()
+  endif()
+
+  string(REPLACE ";" " " FIELDS "${ARG_FIELDS}")
+  if(NOT FIELDS)
+    set(FIELDS family)
+  endif()
+
+  execute_process(
+    COMMAND sh -c "${BIN_FCLIST} : ${FIELDS}"
+    RESULT_VARIABLE status
+    OUTPUT_VARIABLE output
+    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
+  STRING(REGEX REPLACE ";" "\\\\;" output "${output}")
+  STRING(REGEX REPLACE "\n" ";" output "${output}")
+  STRING(TOLOWER "${output}" output)
+
+  foreach(match LISTS ${output})
+    if(${match} MATCHES ".*${fontname}.*$")
+      list(APPEND matches ${match})
+    endif()
+  endforeach()
+
+  if(matches)
+    list(GET matches 0 fst_match)
+    set(${output_variable} "${fst_match}" PARENT_SCOPE)
+    message(STATUS "Found font: ${fst_match}")
+  else()
+    message_colored(STATUS "Font not found: ${fontname}" "33;1")
+  endif()
+endfunction()
+
+# }}}
+# querylib {{{
+
+function(querylib flag type pkg out_library out_include_dirs)
+  if(${flag})
+    if(${type} STREQUAL "cmake")
+      find_package(${pkg} REQUIRED)
+      string(TOUPPER ${pkg} pkg_upper)
+      list(APPEND ${out_library} ${${pkg_upper}_LIBRARY})
+      list(APPEND ${out_include_dirs} ${${pkg_upper}_INCLUDE_DIR})
+    elseif(${type} STREQUAL "pkg-config")
+      find_package(PkgConfig REQUIRED)
+      pkg_check_modules(PKG_${flag} REQUIRED ${pkg})
+
+      # Set packet version so that it can be used in the summary
+      set(${flag}_VERSION ${PKG_${flag}_VERSION} PARENT_SCOPE)
+      list(APPEND ${out_library} ${PKG_${flag}_LIBRARIES})
+      list(APPEND ${out_include_dirs} ${PKG_${flag}_INCLUDE_DIRS})
+    else()
+      message(FATAL_ERROR "Invalid lookup type '${type}'")
+    endif()
+    set(${out_library} ${${out_library}} PARENT_SCOPE)
+    set(${out_include_dirs} ${${out_include_dirs}} PARENT_SCOPE)
+  endif()
+endfunction()
+
+# }}}
+# checklib {{{
+
+function(checklib flag type pkg)
+  if(NOT DEFINED ${flag})
+    if(${type} STREQUAL "cmake")
+      find_package(${pkg} QUIET)
+      set(${flag} ${${pkg}_FOUND} CACHE BOOL "")
+    elseif(${type} STREQUAL "pkg-config")
+      find_package(PkgConfig REQUIRED)
+      pkg_check_modules(PKG_${flag} QUIET ${pkg})
+      set(${flag} ${PKG_${flag}_FOUND} CACHE BOOL "")
+    elseif(${type} STREQUAL "binary")
+      find_program(BIN_${flag} ${pkg})
+      set(${flag} ${BIN_${flag}} CACHE BOOL "")
+    else()
+      message(FATAL_ERROR "Invalid lookup type '${type}'")
+    endif()
+  endif()
+endfunction()
+
+# }}}
diff --git a/cmake/modules/FindLibiw.cmake b/cmake/modules/FindLibiw.cmake
new file mode 100644 (file)
index 0000000..c69b941
--- /dev/null
@@ -0,0 +1,17 @@
+# This module defines
+#  LIBIW_FOUND - whether the libiw library was found
+#  LIBIW_LIBRARIES - the libiw library
+#  LIBIW_INCLUDE_DIR - the include path of the libiw library
+
+find_library(LIBIW_LIBRARY iw)
+
+if(LIBIW_LIBRARY)
+    set(LIBIW_LIBRARIES ${LIBIW_LIBRARY})
+endif(LIBIW_LIBRARY)
+
+find_path(LIBIW_INCLUDE_DIR NAMES iwlib.h)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Libiw DEFAULT_MSG LIBIW_LIBRARY LIBIW_INCLUDE_DIR)
+
+mark_as_advanced(LIBIW_INCLUDE_DIR LIBIW_LIBRARY)
diff --git a/cmake/templates/uninstall.cmake.in b/cmake/templates/uninstall.cmake.in
new file mode 100644 (file)
index 0000000..79e6e89
--- /dev/null
@@ -0,0 +1,23 @@
+set(INSTALL_MANIFEST "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
+if(NOT EXISTS ${INSTALL_MANIFEST})
+  message(FATAL_ERROR "Cannot find install manifest: ${INSTALL_MANIFEST}")
+endif()
+
+file(READ ${INSTALL_MANIFEST} files)
+string(REGEX REPLACE "\n" ";" files "${files}")
+list(REVERSE files)
+
+foreach(file ${files})
+  message(STATUS "Uninstalling $ENV{DESTDIR}${file}")
+  if(EXISTS "$ENV{DESTDIR}${file}")
+    execute_process(COMMAND "@CMAKE_COMMAND@"
+      -E remove "$ENV{DESTDIR}${file}"
+      OUTPUT_VARIABLE rm_out
+      RESULT_VARIABLE rm_retval)
+    if(NOT ${rm_retval} EQUAL 0)
+      message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}")
+    endif()
+  else()
+    message(STATUS "File $ENV{DESTDIR}${file} does not exist")
+  endif()
+endforeach()
diff --git a/cmake/templates/userconfig.cmake.in b/cmake/templates/userconfig.cmake.in
new file mode 100644 (file)
index 0000000..9d05c60
--- /dev/null
@@ -0,0 +1,8 @@
+set(USER_CONFIG_HOME "$ENV{XDG_CONFIG_HOME}")
+if(NOT USER_CONFIG_HOME)
+  set(USER_CONFIG_HOME "$ENV{HOME}/.config")
+endif()
+set(USER_CONFIG_HOME "${USER_CONFIG_HOME}/polybar")
+
+file(INSTALL "@CMAKE_SOURCE_DIR@/config"
+  DESTINATION "${USER_CONFIG_HOME}")
diff --git a/common/ci/configure.sh b/common/ci/configure.sh
new file mode 100755 (executable)
index 0000000..9071874
--- /dev/null
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+if [ -d "$BUILD_DIR" ]; then
+  rm -Rf "$BUILD_DIR"
+fi
+
+mkdir -p "${BUILD_DIR}"
+cd "${BUILD_DIR}"
+
+if [ "$POLYBAR_BUILD_TYPE" != "minimal" ]; then
+  ENABLE_PULSEAUDIO=ON
+  ENABLE_NETWORK=ON
+  ENABLE_MPD=ON
+  ENABLE_CURL=ON
+  ENABLE_ALSA=ON
+  ENABLE_I3=ON
+  WITH_XRM=ON
+  WITH_XKB=ON
+  WITH_XRANDR_MONITORS=ON
+  WITH_XCURSOR=ON
+fi
+
+if [ "$POLYBAR_BUILD_TYPE" = "tests" ]; then
+  BUILD_TESTS=ON
+fi
+
+cmake \
+  -DCMAKE_CXX_COMPILER="${CXX}" \
+  -DCMAKE_CXX_FLAGS="${CXXFLAGS} -Werror" \
+  -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
+  -DBUILD_TESTS:BOOL="${BUILD_TESTS:-OFF}" \
+  -DBUILD_DOC:BOOL="${BUILD_DOC:-OFF}" \
+  -DWITH_XRANDR=ON \
+  -DENABLE_PULSEAUDIO="${ENABLE_PULSEAUDIO:-OFF}" \
+  -DENABLE_NETWORK="${ENABLE_NETWORK:-OFF}" \
+  -DENABLE_MPD="${ENABLE_MPD:-OFF}" \
+  -DENABLE_CURL="${ENABLE_CURL:-OFF}" \
+  -DENABLE_ALSA="${ENABLE_ALSA:-OFF}" \
+  -DENABLE_I3="${ENABLE_I3:-OFF}" \
+  -DWITH_XRM="${WITH_XRM:-OFF}" \
+  -DWITH_XKB="${WITH_XKB:-OFF}" \
+  -DWITH_XRANDR_MONITORS="${WITH_XRANDR_MONITORS:-OFF}" \
+  -DWITH_XCURSOR="${WITH_XCURSOR:-OFF}" \
+  ..
diff --git a/common/ci/summary.sh b/common/ci/summary.sh
new file mode 100755 (executable)
index 0000000..a6bfcf8
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+set -x
+
+"${CXX}" --version
+cmake --version
+
+set +x
+
+echo "PATH=${PATH}"
+echo "CXX=${CXX}"
+echo "CXXFLAGS=${CXXFLAGS}"
+echo "LDFLAGS=${LDFLAGS}"
+echo "LD_LIBRARY_PATH=${LD_LIBRARY_PATH}"
+echo "MAKEFLAGS=${MAKEFLAGS}"
+echo "POLYBAR_BUILD_TYPE=${POLYBAR_BUILD_TYPE}"
+echo "CMAKE_BUILD_TYPE=${BUILD_TYPE}"
diff --git a/common/clang-format.sh b/common/clang-format.sh
new file mode 100755 (executable)
index 0000000..0a912c8
--- /dev/null
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+main() {
+  if [ $# -lt 1 ]; then
+    echo "$0 DIR..." 1>&2
+    exit 1
+  fi
+
+  # Search paths
+  search="${*:-.}"
+
+  echo "$0 in $search"
+
+  # shellcheck disable=2086
+  find $search -regex ".*.[c|h]pp"                           \
+    -exec printf "\\033[32;1m** \\033[0mFormatting %s\\n" {} \; \
+    -exec clang-format -style=file -i {} \;
+}
+
+main "$@"
diff --git a/common/clang-tidy.sh b/common/clang-tidy.sh
new file mode 100755 (executable)
index 0000000..f0de51f
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+main() {
+  if [ $# -lt 2 ]; then
+    echo "$0 build_path [-fix] DIR..." 1>&2
+    exit 1
+  fi
+
+  args="-p $1"; shift
+
+  if [ "$1" = "-fix" ]; then
+    args="${args} -fix"; shift
+  fi
+
+  # Search paths
+  search="${*:-.}"
+
+  echo "$0 in $search"
+
+  # shellcheck disable=2086
+  find $search -iname "*.cpp"                                          \
+    -exec printf "\\033[32;1m** \\033[0mProcessing %s\\n" {} \; \
+    -exec clang-tidy $args {} \;
+}
+
+main "$@"
diff --git a/common/release-archive.sh b/common/release-archive.sh
new file mode 100755 (executable)
index 0000000..222dc57
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+git_url="https://github.com/polybar/polybar.git"
+wd="$(realpath .)"
+
+usage() {
+  cat <<EOF
+Usage: $0 [-h] TAG
+
+Creates a polybar release archive for the given git tag.
+
+-h           Print this help message
+EOF
+}
+
+
+cleanup() {
+  if [ -d "$tmp_dir" ]; then
+    rm -rf "$tmp_dir"
+  fi
+}
+
+if [ $# -ne 1 ] ; then
+  usage
+  exit 1
+fi
+
+if [ "$1" = "-h" ]; then
+  usage
+  exit 0
+fi
+
+version="$1"
+tmp_dir="$(mktemp -d)"
+archive="$wd/polybar-${version}.tar"
+
+trap cleanup EXIT
+
+git clone "$git_url" "$tmp_dir/polybar"
+
+cd "$tmp_dir/polybar"
+
+echo "Looking for tag '$version'"
+
+if [ "$(git tag -l "$version" | wc -l)" != "1" ]; then
+  echo "Tag '$version' not found"
+  exit 1
+fi
+
+git checkout "$version"
+git submodule update --init --recursive >/dev/null
+
+find . -type d -name ".git" -exec rm -rf {} \+
+
+cd "$tmp_dir"
+tar cf "$archive" "polybar"
+sha256sum "$archive"
diff --git a/config.cmake b/config.cmake
new file mode 100644 (file)
index 0000000..004bbdb
--- /dev/null
@@ -0,0 +1,423 @@
+;==========================================================
+;
+;
+;   ██████╗  ██████╗ ██╗  ██╗   ██╗██████╗  █████╗ ██████╗
+;   ██╔══██╗██╔═══██╗██║  ╚██╗ ██╔╝██╔══██╗██╔══██╗██╔══██╗
+;   ██████╔╝██║   ██║██║   ╚████╔╝ ██████╔╝███████║██████╔╝
+;   ██╔═══╝ ██║   ██║██║    ╚██╔╝  ██╔══██╗██╔══██║██╔══██╗
+;   ██║     ╚██████╔╝███████╗██║   ██████╔╝██║  ██║██║  ██║
+;   ╚═╝      ╚═════╝ ╚══════╝╚═╝   ╚═════╝ ╚═╝  ╚═╝╚═╝  ╚═╝
+;
+;
+;   To learn more about how to configure Polybar
+;   go to https://github.com/polybar/polybar
+;
+;   The README contains a lot of information
+;
+;==========================================================
+
+[colors]
+;background = ${xrdb:color0:#222}
+background = #222
+background-alt = #444
+;foreground = ${xrdb:color7:#222}
+foreground = #dfdfdf
+foreground-alt = #555
+primary = #ffb52a
+secondary = #e60053
+alert = #bd2c40
+
+[bar/example]
+;monitor = ${env:MONITOR:HDMI-1}
+width = 100%
+height = 27
+;offset-x = 1%
+;offset-y = 1%
+radius = 6.0
+fixed-center = false
+
+background = ${colors.background}
+foreground = ${colors.foreground}
+
+line-size = 3
+line-color = #f00
+
+border-size = 4
+border-color = #00000000
+
+padding-left = 0
+padding-right = 2
+
+module-margin-left = 1
+module-margin-right = 2
+
+font-0 = @FONT_FIXED@;1
+font-1 = @FONT_UNIFONT@:size=8:antialias=false;0
+font-2 = @FONT_SIJI@;1
+
+modules-left = @MODULES_LEFT@
+modules-center = @MODULES_CENTER@
+modules-right = @MODULES_RIGHT@
+
+tray-position = right
+tray-padding = 2
+;tray-background = #0063ff
+
+;wm-restack = bspwm
+;wm-restack = i3
+
+;override-redirect = true
+
+;scroll-up = bspwm-desknext
+;scroll-down = bspwm-deskprev
+
+;scroll-up = i3wm-wsnext
+;scroll-down = i3wm-wsprev
+
+cursor-click = pointer
+cursor-scroll = ns-resize
+
+[module/xwindow]
+type = internal/xwindow
+label = %title:0:30:...%
+
+[module/xkeyboard]
+type = internal/xkeyboard
+blacklist-0 = num lock
+
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-prefix-underline = ${colors.secondary}
+
+label-layout = %layout%
+label-layout-underline = ${colors.secondary}
+
+label-indicator-padding = 2
+label-indicator-margin = 1
+label-indicator-background = ${colors.secondary}
+label-indicator-underline = ${colors.secondary}
+
+[module/filesystem]
+type = internal/fs
+interval = 25
+
+mount-0 = /
+
+label-mounted = %{F#0a81f5}%mountpoint%%{F-}: %percentage_used%%
+label-unmounted = %mountpoint% not mounted
+label-unmounted-foreground = ${colors.foreground-alt}
+
+[module/bspwm]
+type = internal/bspwm
+
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+label-occupied = %index%
+label-occupied-padding = 2
+
+label-urgent = %index%!
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+label-empty = %index%
+label-empty-foreground = ${colors.foreground-alt}
+label-empty-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+[module/i3]
+type = internal/i3
+format = <label-state> <label-mode>
+index-sort = true
+wrapping-scroll = false
+
+; Only show workspaces on the same output as the bar
+;pin-workspaces = true
+
+label-mode-padding = 2
+label-mode-foreground = #000
+label-mode-background = ${colors.primary}
+
+; focused = Active workspace on focused monitor
+label-focused = %index%
+label-focused-background = ${colors.background-alt}
+label-focused-underline= ${colors.primary}
+label-focused-padding = 2
+
+; unfocused = Inactive workspace on any monitor
+label-unfocused = %index%
+label-unfocused-padding = 2
+
+; visible = Active workspace on unfocused monitor
+label-visible = %index%
+label-visible-background = ${self.label-focused-background}
+label-visible-underline = ${self.label-focused-underline}
+label-visible-padding = ${self.label-focused-padding}
+
+; urgent = Workspace with urgency hint set
+label-urgent = %index%
+label-urgent-background = ${colors.alert}
+label-urgent-padding = 2
+
+; Separator in between workspaces
+; label-separator = |
+
+
+[module/mpd]
+type = internal/mpd
+format-online = <label-song>  <icon-prev> <icon-stop> <toggle> <icon-next>
+
+icon-prev = 
+icon-stop = 
+icon-play = 
+icon-pause = 
+icon-next = 
+
+label-song-maxlen = 25
+label-song-ellipsis = true
+
+[module/xbacklight]
+type = internal/xbacklight
+
+format = <label> <bar>
+label = BL
+
+bar-width = 10
+bar-indicator = |
+bar-indicator-foreground = #fff
+bar-indicator-font = 2
+bar-fill = ─
+bar-fill-font = 2
+bar-fill-foreground = #9f78e1
+bar-empty = ─
+bar-empty-font = 2
+bar-empty-foreground = ${colors.foreground-alt}
+
+[module/backlight-acpi]
+inherit = module/xbacklight
+type = internal/backlight
+card = intel_backlight
+
+[module/cpu]
+type = internal/cpu
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #f90000
+label = %percentage:2%%
+
+[module/memory]
+type = internal/memory
+interval = 2
+format-prefix = " "
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #4bffdc
+label = %percentage_used%%
+
+[module/wlan]
+type = internal/network
+interface = @INTERFACE_WLAN@
+interval = 3.0
+
+format-connected = <ramp-signal> <label-connected>
+format-connected-underline = #9f78e1
+label-connected = %essid%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+ramp-signal-0 = 
+ramp-signal-1 = 
+ramp-signal-2 = 
+ramp-signal-3 = 
+ramp-signal-4 = 
+ramp-signal-foreground = ${colors.foreground-alt}
+
+[module/eth]
+type = internal/network
+interface = @INTERFACE_ETH@
+interval = 3.0
+
+format-connected-underline = #55aa55
+format-connected-prefix = " "
+format-connected-prefix-foreground = ${colors.foreground-alt}
+label-connected = %local_ip%
+
+format-disconnected =
+;format-disconnected = <label-disconnected>
+;format-disconnected-underline = ${self.format-connected-underline}
+;label-disconnected = %ifname% disconnected
+;label-disconnected-foreground = ${colors.foreground-alt}
+
+[module/date]
+type = internal/date
+interval = 5
+
+date =
+date-alt = " %Y-%m-%d"
+
+time = %H:%M
+time-alt = %H:%M:%S
+
+format-prefix = 
+format-prefix-foreground = ${colors.foreground-alt}
+format-underline = #0a6cf5
+
+label = %date% %time%
+
+[module/pulseaudio]
+type = internal/pulseaudio
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL %percentage%%
+label-volume-foreground = ${root.foreground}
+
+label-muted = 🔇 muted
+label-muted-foreground = #666
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/alsa]
+type = internal/alsa
+
+format-volume = <label-volume> <bar-volume>
+label-volume = VOL
+label-volume-foreground = ${root.foreground}
+
+format-muted-prefix = " "
+format-muted-foreground = ${colors.foreground-alt}
+label-muted = sound muted
+
+bar-volume-width = 10
+bar-volume-foreground-0 = #55aa55
+bar-volume-foreground-1 = #55aa55
+bar-volume-foreground-2 = #55aa55
+bar-volume-foreground-3 = #55aa55
+bar-volume-foreground-4 = #55aa55
+bar-volume-foreground-5 = #f5a70a
+bar-volume-foreground-6 = #ff5555
+bar-volume-gradient = false
+bar-volume-indicator = |
+bar-volume-indicator-font = 2
+bar-volume-fill = ─
+bar-volume-fill-font = 2
+bar-volume-empty = ─
+bar-volume-empty-font = 2
+bar-volume-empty-foreground = ${colors.foreground-alt}
+
+[module/battery]
+type = internal/battery
+battery = @BATTERY@
+adapter = @ADAPTER@
+full-at = 98
+
+format-charging = <animation-charging> <label-charging>
+format-charging-underline = #ffb52a
+
+format-discharging = <animation-discharging> <label-discharging>
+format-discharging-underline = ${self.format-charging-underline}
+
+format-full-prefix = " "
+format-full-prefix-foreground = ${colors.foreground-alt}
+format-full-underline = ${self.format-charging-underline}
+
+ramp-capacity-0 = 
+ramp-capacity-1 = 
+ramp-capacity-2 = 
+ramp-capacity-foreground = ${colors.foreground-alt}
+
+animation-charging-0 = 
+animation-charging-1 = 
+animation-charging-2 = 
+animation-charging-foreground = ${colors.foreground-alt}
+animation-charging-framerate = 750
+
+animation-discharging-0 = 
+animation-discharging-1 = 
+animation-discharging-2 = 
+animation-discharging-foreground = ${colors.foreground-alt}
+animation-discharging-framerate = 750
+
+[module/temperature]
+type = internal/temperature
+thermal-zone = 0
+warn-temperature = 60
+
+format = <ramp> <label>
+format-underline = #f50a4d
+format-warn = <ramp> <label-warn>
+format-warn-underline = ${self.format-underline}
+
+label = %temperature-c%
+label-warn = %temperature-c%
+label-warn-foreground = ${colors.secondary}
+
+ramp-0 = 
+ramp-1 = 
+ramp-2 = 
+ramp-foreground = ${colors.foreground-alt}
+
+[module/powermenu]
+type = custom/menu
+
+expand-right = true
+
+format-spacing = 1
+
+label-open = 
+label-open-foreground = ${colors.secondary}
+label-close =  cancel
+label-close-foreground = ${colors.secondary}
+label-separator = |
+label-separator-foreground = ${colors.foreground-alt}
+
+menu-0-0 = reboot
+menu-0-0-exec = menu-open-1
+menu-0-1 = power off
+menu-0-1-exec = menu-open-2
+
+menu-1-0 = cancel
+menu-1-0-exec = menu-open-0
+menu-1-1 = reboot
+menu-1-1-exec = sudo reboot
+
+menu-2-0 = power off
+menu-2-0-exec = sudo poweroff
+menu-2-1 = cancel
+menu-2-1-exec = menu-open-0
+
+[settings]
+screenchange-reload = true
+;compositing-background = xor
+;compositing-background = screen
+;compositing-foreground = source
+;compositing-border = over
+;pseudo-transparency = false
+
+[global/wm]
+margin-top = 5
+margin-bottom = 5
+
+; vim:ft=dosini
diff --git a/contrib/bash/CMakeLists.txt b/contrib/bash/CMakeLists.txt
new file mode 100644 (file)
index 0000000..7b18a0d
--- /dev/null
@@ -0,0 +1,6 @@
+#
+# Bash completion template
+#
+install(FILES polybar
+  DESTINATION ${CMAKE_INSTALL_DATADIR}/bash-completion/completions
+  COMPONENT tools)
diff --git a/contrib/bash/polybar b/contrib/bash/polybar
new file mode 100644 (file)
index 0000000..88e7b7c
--- /dev/null
@@ -0,0 +1,95 @@
+_polybar_config_file() {
+  local config_path=${XDG_CONFIG_HOME:-$HOME/.config}/polybar/config
+
+  for ((i = 0; i < COMP_CWORD; i++)); do
+    case ${COMP_WORDS[i]} in
+    --config)
+      config_path=${COMP_WORDS[i + 2]}
+      break
+      ;;
+    -c)
+      config_path=${COMP_WORDS[i + 1]}
+      break
+      ;;
+    esac
+  done
+
+  # Use eval + cd for to get bash's parameter/tilde expansion etc
+  (eval cd $(dirname "$config_path"); echo $PWD/$(basename "$config_path"))
+}
+
+_polybar_bars() {
+  local config_file=$(_polybar_config_file)
+
+  if [ -r "$config_file" ]; then
+    grep -Po '\[bar/\K(.*)(?=\])' "$config_file"
+  fi
+}
+
+_polybar() {
+  local options='-h --help
+                 -v --version
+                 -l --log=
+                 -q --quiet
+                 -c --config=
+                 -r --reload
+                 -d --dump=
+                 -m --list-monitors
+                 -M --list-all-monitors
+                 -w --print-wmname
+                 -s --stdout
+                 -p --png='
+
+  local log_levels='error
+                    warning
+                    notice
+                    info
+                    trace'
+
+  COMPREPLY=()
+
+  local cur=${COMP_WORDS[COMP_CWORD]}
+  case "$cur" in
+  -*)
+    COMPREPLY=( $(compgen -W "$options" -- "$cur") )
+    ;;
+  *)
+    local prev=${COMP_WORDS[COMP_CWORD - 1]}
+    if [ "$prev" = "=" ]; then
+      prev=${COMP_WORDS[COMP_CWORD - 2]}
+    fi
+
+    case "$prev" in
+    -l|--log)
+      COMPREPLY=( $(compgen -W "$log_levels" -- "$cur") )
+      return 0
+      ;;
+    -c|--config)
+      COMPREPLY=( $(compgen -f "$cur") )
+      return 0
+      ;;
+    -p|--png)
+      COMPREPLY=( $(compgen -f -X "!*.png" "$cur") )
+      return 0
+      ;;
+    # TODO: read properties of the selected bar from config
+    -d|--dump)
+      return 0
+      ;;
+    *)
+      COMPREPLY=( $(compgen -W "$options $(_polybar_bars)" -- "$cur") )
+      ;;
+    esac
+  esac
+
+  for ((i = 0; i < ${#COMPREPLY[@]}; i++)); do
+    case ${COMPREPLY[i]} in
+    --*=) ;;
+    -*) COMPREPLY[i]+=" "
+    esac
+  done
+
+  return 0
+}
+
+complete -o filenames -o noquote -o nospace -F _polybar polybar
diff --git a/contrib/polybar-git.aur/PKGBUILD b/contrib/polybar-git.aur/PKGBUILD
new file mode 100644 (file)
index 0000000..415ea4d
--- /dev/null
@@ -0,0 +1,42 @@
+# Maintainer: Patrick Ziegler <p.ziegler96@gmail.com>
+_pkgname=polybar
+pkgname="${_pkgname}-git"
+pkgver=3.4.0
+pkgrel=1
+pkgdesc="A fast and easy-to-use status bar"
+arch=("i686" "x86_64")
+url="https://github.com/polybar/polybar"
+license=("MIT")
+depends=("cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm" "xcb-util-cursor"
+         "alsa-lib" "libpulse" "libmpdclient" "libnl" "jsoncpp" "curl")
+optdepends=("i3-wm: i3 module support"
+            "ttf-unifont: Font used in example config"
+            "siji-git: Font used in example config"
+            "xorg-fonts-misc: Font used in example config")
+makedepends=("cmake" "git" "python" "pkg-config" "python-sphinx" "i3-wm")
+provides=("polybar")
+conflicts=("polybar")
+install="${_pkgname}.install"
+source=("${_pkgname}::git+${url}.git")
+md5sums=("SKIP")
+
+pkgver() {
+  git -C "${_pkgname}" describe --long --tags | sed "s/-/.r/;s/-/./g"
+}
+
+prepare() {
+  git -C "${_pkgname}" submodule update --init --recursive
+  mkdir -p "${_pkgname}/build"
+}
+
+build() {
+  cd "${_pkgname}/build" || exit 1
+  # Force cmake to use system python (to detect xcbgen)
+  cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ..
+  cmake --build .
+}
+
+package() {
+  cmake --build "${_pkgname}/build" --target install -- DESTDIR="${pkgdir}"
+  install -Dm644 "${_pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${_pkgname}/LICENSE"
+}
diff --git a/contrib/polybar-git.aur/polybar.install b/contrib/polybar-git.aur/polybar.install
new file mode 100644 (file)
index 0000000..b579183
--- /dev/null
@@ -0,0 +1,12 @@
+post_install() {
+  cat << EOF
+
+  Get started with the example configuration:
+
+    $ install -Dm644 /usr/share/doc/polybar/config \$HOME/.config/polybar/config
+    $ polybar example
+
+  For more information, see https://github.com/polybar/polybar/wiki
+
+EOF
+}
diff --git a/contrib/polybar.aur/PKGBUILD b/contrib/polybar.aur/PKGBUILD
new file mode 100644 (file)
index 0000000..3fff912
--- /dev/null
@@ -0,0 +1,34 @@
+# Maintainer: Patrick Ziegler <p.ziegler96@gmail.com>
+pkgname=polybar
+pkgver=3.4.3
+pkgrel=1
+pkgdesc="A fast and easy-to-use status bar"
+arch=("i686" "x86_64")
+url="https://github.com/polybar/polybar"
+license=("MIT")
+depends=("cairo" "xcb-util-image" "xcb-util-wm" "xcb-util-xrm" "xcb-util-cursor"
+         "alsa-lib" "libpulse" "libmpdclient" "libnl" "jsoncpp" "curl")
+optdepends=("i3-wm: i3 module support"
+            "ttf-unifont: Font used in example config"
+            "siji-git: Font used in example config"
+            "xorg-fonts-misc: Font used in example config")
+makedepends=("cmake" "git" "python" "pkg-config" "python-sphinx" "i3-wm")
+conflicts=("polybar-git")
+install="${pkgname}.install"
+source=(${url}/releases/download/${pkgver}/polybar-${pkgver}.tar)
+sha256sums=('d4ed121c1d3960493f8268f966d65a94d94c4646a4abb131687e37b63616822f')
+
+prepare() {
+  mkdir -p "${pkgname}/build"
+}
+
+build() {
+  cd "${pkgname}/build" || exit 1
+  cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release ..
+  cmake --build .
+}
+
+package() {
+  cmake --build "${pkgname}/build" --target install -- DESTDIR="${pkgdir}"
+  install -Dm644 "${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
+}
diff --git a/contrib/polybar.aur/polybar.install b/contrib/polybar.aur/polybar.install
new file mode 100644 (file)
index 0000000..25ac794
--- /dev/null
@@ -0,0 +1,26 @@
+post_install() {
+  cat << EOF
+
+  Get started with the example configuration:
+
+    $ install -Dm644 /usr/share/doc/polybar/config \$HOME/.config/polybar/config
+    $ polybar example
+
+  For more information, see https://github.com/polybar/polybar/wiki
+
+EOF
+}
+
+post_upgrade() {
+  [ "$(vercmp "$2" "2.5.1-1")" = "-1" ] || exit 0
+  cat << EOF
+
+  The % suffix has been removed from percentage tokens.
+  The suffix is instead added by the user, for example:
+
+  format-charging = Capacity %percentage%%
+
+                                              -- jaagr
+
+EOF
+}
diff --git a/contrib/rpm/polybar.spec b/contrib/rpm/polybar.spec
new file mode 100644 (file)
index 0000000..f52e7bd
--- /dev/null
@@ -0,0 +1,71 @@
+#
+# spec file for package polybar
+# Initially created for openSUSE
+#
+
+Name:           polybar
+Version:        3.4.1
+Release:        0
+Summary:        A fast and easy-to-use status bar
+License:        MIT
+Group:          System/GUI/Other
+URL:            https://github.com/polybar/polybar
+Source:         https://github.com/polybar/polybar/archive/%{version}.tar.gz
+BuildRequires:  clang >= 3.4
+BuildRequires:  cmake >= 3.1
+BuildRequires:  pkgconfig
+BuildRequires:  python-Sphinx
+BuildRequires:  python-xml
+BuildRequires:  xcb-util-image-devel
+BuildRequires:  xcb-util-wm-devel
+# optional dependency
+BuildRequires:  pkgconfig(alsa)
+# main dependency
+BuildRequires:  pkgconfig(cairo)
+BuildRequires:  pkgconfig(jsoncpp)
+BuildRequires:  pkgconfig(libcurl)
+BuildRequires:  pkgconfig(libpulse)
+BuildRequires:  pkgconfig(python3)
+BuildRequires:  pkgconfig(xcb)
+BuildRequires:  pkgconfig(xcb-proto)
+BuildRequires:  pkgconfig(xcb-util)
+%if 0%{?suse_version} <= 1315
+BuildRequires:  i3-devel
+%else
+BuildRequires:  i3-gaps-devel
+%endif
+%if 0%{?suse_version}
+BuildRequires:  libiw-devel
+%else
+BuildRequires:  wireless-tools-devel
+%endif
+
+%description
+A fast and easy-to-use status bar for tilling WM
+
+%prep
+%setup -q
+
+%build
+%cmake
+make
+
+%install
+%cmake_install
+
+%files
+%dir %{_datadir}/bash-completion/
+%dir %{_datadir}/bash-completion/completions
+%dir %{_datadir}/doc/%{name}
+%dir %{_datadir}/zsh/
+%dir %{_datadir}/zsh/site-functions
+%{_bindir}/%{name}
+%{_bindir}/%{name}-msg
+%{_datadir}/doc/%{name}/config
+%{_mandir}/man1/%{name}.1%{?ext_man}
+%{_datadir}/bash-completion/completions/%{name}
+%{_datadir}/zsh/site-functions/_%{name}
+%{_datadir}/zsh/site-functions/_%{name}_msg
+
+%changelog
+
diff --git a/contrib/vim/autoload/ft/cpphpp.vim b/contrib/vim/autoload/ft/cpphpp.vim
new file mode 100644 (file)
index 0000000..2176abb
--- /dev/null
@@ -0,0 +1,19 @@
+"
+" Get the filename of the swap file
+"
+func! ft#cpphpp#GetFilename()
+  let ext = expand('%:e')
+  let root = expand('%:p:r')
+  if (ext == 'cpp')
+    return fnameescape(substitute(root, '\(src/.*/\)\?src/', '\1include/', '') . '.hpp')
+  elseif (ext == 'hpp')
+    return fnameescape(substitute(root, '\(include/.*/\)\?include/', '\1src/', '') . '.cpp')
+  endif
+endfunc
+
+"
+" Swap between source/header using given cmd
+"
+func! ft#cpphpp#Swap(cmd)
+  execute a:cmd . ' ' . ft#cpphpp#GetFilename()
+endfunc
diff --git a/contrib/vim/ftplugin/cpp.vim b/contrib/vim/ftplugin/cpp.vim
new file mode 100644 (file)
index 0000000..66eebfd
--- /dev/null
@@ -0,0 +1,8 @@
+" Swap between source/header
+nnoremap <silent> <leader>af :call ft#cpphpp#Swap('edit')<cr>
+nnoremap <silent> <leader>as :call ft#cpphpp#Swap('new')<cr>
+nnoremap <silent> <leader>av :call ft#cpphpp#Swap('vnew')<cr>
+
+" Code formatting using clang-format
+set formatprg=/usr/bin/clang-format
+nmap <f1> :ClangFormat<cr>
diff --git a/contrib/vim/ftplugin/dosini.vim b/contrib/vim/ftplugin/dosini.vim
new file mode 100644 (file)
index 0000000..c36e41e
--- /dev/null
@@ -0,0 +1,11 @@
+"
+" Enables syntax folding for the configuration file.
+" Removes the need to clutter the file with fold markers.
+"
+" Put the file in $VIM/after/syntax/dosini.vim
+"
+syn region dosiniSection start="^\[" end="\(\n\+\[\)\@=" contains=dosiniLabel,dosiniHeader,dosiniComment keepend fold
+setlocal foldmethod=syntax
+
+" Uncomment to start with folds open
+"setlocal foldlevel=20
diff --git a/contrib/zsh/CMakeLists.txt b/contrib/zsh/CMakeLists.txt
new file mode 100644 (file)
index 0000000..93e010e
--- /dev/null
@@ -0,0 +1,6 @@
+#
+# Zsh completion template
+#
+install(FILES _polybar _polybar_msg
+  DESTINATION ${CMAKE_INSTALL_DATADIR}/zsh/site-functions
+  COMPONENT tools)
diff --git a/contrib/zsh/_polybar b/contrib/zsh/_polybar
new file mode 100644 (file)
index 0000000..09f99a6
--- /dev/null
@@ -0,0 +1,45 @@
+#compdef polybar
+#
+# Completion for polybar (https://github.com/polybar/polybar)
+#   jaagr <c@rlberg.se>
+#
+_polybar() {
+  local L='-l --log'
+  local Q='-q --quiet'
+  local C='-c --config'
+  local R='-r --reload'
+  local D='-d --dump'
+  local M='-m --list-monitors'
+  local MM='-M --list-all-monitors'
+  local W='-w --print-wmname'
+  local S='-s --stdout'
+
+  _arguments -n : \
+    '(-)'{-h,--help}'[Display help text and exit]' \
+    '(-)'{-v,--version}'[Display build details and exit]' \
+    "($L $Q)"{-l,--log=}'[Set the logging verbosity (default: notice)]:verbosity level:(error warning notice info trace)' \
+    "($L $Q)"{-q,--quiet}'[Be quiet (will override -l)]' \
+    "($C)"{-c,--config=}'[Path to the configuration file]:configuration file:_files' \
+    "($R)"{-r,--reload}'[Reload when the configuration has been modified]' \
+    "($D $R $M $W $S)"{-d,--dump=}'[Print parameter value in bar section and exit]:parameter name' \
+    "($MM $M $D $R $W $S)"{-m,--list-monitors}'[Print list of available monitors (Excluding cloned monitors) and exit]' \
+    "($MM $M $D $R $W $S)"{-M,--list-all-monitors}'[Print list of all available monitors (Including cloned monitors) and exit]' \
+    "($W $R $D $M $S)"{-w,--print-wmname}'[Print the generated WM_NAME and exit]' \
+    "($S)"{-s,--stdout}'[Output data to stdout instead of drawing the X window]' \
+    '::bar name:_polybar_list_names'
+}
+
+(( $+functions[_polybar_list_names] )) || _polybar_list_names() {
+  local conf
+  if (( $+opt_args[-c] )); then
+    conf=${(e)opt_args[-c]}
+  elif (( $+opt_args[--config] )); then
+    conf=${(e)opt_args[--config]}
+  else
+    conf=${XDG_CONFIG_HOME:-$HOME/.config}/polybar/config
+  fi
+  local names; names=(${(f)"$(sed -nr 's|\[bar/([^\]+)\]$|\1|p' ${conf} 2>/dev/null)"})
+  _describe -t names 'configuration name' names
+}
+
+_polybar "$@"
diff --git a/contrib/zsh/_polybar_msg b/contrib/zsh/_polybar_msg
new file mode 100644 (file)
index 0000000..946c464
--- /dev/null
@@ -0,0 +1,32 @@
+#compdef polybar-msg
+#
+# Completion for polybar-msg (https://github.com/polybar/polybar)
+#   jaagr <c@rlberg.se>
+#
+_polybar_msg() {
+  integer ret=1
+
+  _arguments -n : \
+    '-p[Process id of target instance]:process id:_polybar_msg_pids' \
+    '(-p)1:message type:(action cmd hook)' \
+    '*:: :->args'
+
+  case $state in
+    args)
+      case $words[1] in
+        hook) _arguments ':module name:' ':hook index:'; ret=0 ;;
+        action) _arguments ':action payload:'; ret=0 ;;
+        cmd) _arguments ':command payload:(show hide toggle restart quit)'; ret=0 ;;
+      esac
+      ;;
+  esac
+
+  return $ret
+}
+
+(( $+functions[_polybar_msg_pids] )) || _polybar_msg_pids() {
+  local pids; pids=(${(f)"$(ls -1 /tmp/polybar_mqueue.* | egrep -o '[0-9]+$')"})
+  _describe -t pids 'process id of target instance' pids
+}
+
+_polybar_msg "$@"
diff --git a/doc/.gitignore b/doc/.gitignore
new file mode 100644 (file)
index 0000000..378eac2
--- /dev/null
@@ -0,0 +1 @@
+build
diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt
new file mode 100644 (file)
index 0000000..fb8ad29
--- /dev/null
@@ -0,0 +1,69 @@
+cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
+
+# Only used if documentation is built on its own
+project(polybar-doc NONE)
+
+if(NOT SPHINX_BUILD)
+  set(SPHINX_BUILD "sphinx-build")
+endif()
+
+set(SPHINX_FLAGS "" CACHE STRING "Flags to pass to sphinx-build")
+separate_arguments(sphinx_flags UNIX_COMMAND "${SPHINX_FLAGS}")
+
+set(doc_path "${CMAKE_CURRENT_SOURCE_DIR}")
+
+# Configures conf.py in the current folder and puts it in the build folder
+configure_file(conf.py conf.py @ONLY)
+
+# We want to run `sphinx-build` with the following builders
+set(doc_builders "html" "man")
+
+# Name of all documentation targets
+set(doc_targets "")
+
+foreach(builder ${doc_builders})
+  set(doc_target "doc_${builder}")
+  set(builder_log "builder-${builder}.log")
+  add_custom_target(${doc_target}
+    COMMAND ${SPHINX_BUILD}
+            -b ${builder}
+            # conf.py dir
+            -c "${CMAKE_CURRENT_BINARY_DIR}"
+            -d "${CMAKE_CURRENT_BINARY_DIR}/doctrees"
+            ${sphinx_flags}
+            # Documentation source file dir
+            "${CMAKE_CURRENT_SOURCE_DIR}"
+            # Output dir
+            "${CMAKE_CURRENT_BINARY_DIR}/${builder}" > ${builder_log}
+    COMMENT "sphinx-build ${builder}: see doc/${builder_log}")
+
+  list(APPEND doc_targets ${doc_target})
+endforeach()
+
+# Dummy target that depends on all documentation targets
+add_custom_target(doc ALL DEPENDS ${doc_targets})
+
+# This is needed for the case where only the doc target is built
+# CMAKE_INSTALL_DOCDIR uses PROJECT_NAME which is now polybar-doc, to be
+# consistent with a regular install we temporarily override it with "polybar"
+# before including GNUInstallDirs
+# Also since no language is set and GNUInstallDirs cannot set
+# CMAKE_INSTALL_LIBDIR, so we set it to a dummy value to suppress a warning
+if(${CMAKE_PROJECT_NAME} STREQUAL "polybar-doc")
+  set(PROJECT_NAME "polybar")
+  set(CMAKE_INSTALL_LIBDIR "")
+  include(GNUInstallDirs)
+  set(PROJECT_NAME "polybar-doc")
+endif()
+
+install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/
+  DESTINATION ${CMAKE_INSTALL_DOCDIR}
+  COMPONENT doc)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/polybar.1
+  DESTINATION ${CMAKE_INSTALL_MANDIR}/man1
+  COMPONENT doc)
+
+install(FILES ${CMAKE_CURRENT_BINARY_DIR}/man/polybar.5
+  DESTINATION ${CMAKE_INSTALL_MANDIR}/man5
+  COMPONENT doc)
diff --git a/doc/README.md b/doc/README.md
new file mode 100644 (file)
index 0000000..46c0b32
--- /dev/null
@@ -0,0 +1,22 @@
+Polybar Manual
+==============
+
+The official polybar documentation lives here.
+
+The html documentation and man pages are built automatically when you build with cmake (cmake creates the custom
+target `doc`).
+
+## Preview Locally
+The documentation uses [Sphinx](https://www.sphinx-doc.org/en/stable/) to generate the documentation, so you will need to
+have that installed.
+
+If you build polybar normally while having Sphinx installed during configuration, the documentation will be enabled and
+built as well. Building the documentation can be disabled by passing `-DBUILD_DOC=OFF` to `cmake`.
+
+Alternatively the documentation can be built without the rest of polybar, for that run `cmake` only on the `doc`
+directory. For example, create a `build` directory in `doc` and then run `cmake ..` in there.
+
+Once configured, all of the documentation can be generated with `make doc` or use `make doc_html` or `make doc_man` to
+only generate the html documentation or the man pages respectively.
+
+The HTML documentation is in `doc/html/index.html` in your build directory and the man pages are in `doc/man`.
diff --git a/doc/_static/.gitignore b/doc/_static/.gitignore
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/doc/conf.py b/doc/conf.py
new file mode 100644 (file)
index 0000000..9bd1212
--- /dev/null
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+#
+# Configuration file for the Sphinx documentation builder.
+#
+# This file does only contain a selection of the most common options. For a
+# full list see the documentation:
+# https://www.sphinx-doc.org/en/master/config
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+from pathlib import Path
+import datetime
+import sphinx
+import packaging.version
+
+def get_version(root_path):
+  """
+  Reads the polybar version from the version.txt at the root of the repo.
+  """
+  path = Path(root_path) / "version.txt"
+  with open(path, "r") as f:
+    for line in f.readlines():
+      if not line.startswith("#"):
+        return packaging.version.parse(line)
+
+  raise RuntimeError("No version found in {}".format(path))
+
+# -- Project information -----------------------------------------------------
+
+project = 'Polybar User Manual'
+copyright = '2016-{}, Michael Carlberg & contributors'.format(
+    datetime.datetime.now().year
+  )
+author = 'Polybar Team'
+
+# is whether we are on readthedocs.io
+on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
+
+if on_rtd:
+  # On readthedocs, cmake isn't run so the version string isn't available
+  version = os.environ.get('READTHEDOCS_VERSION', None)
+else:
+  # The short X.Y version
+  version = '@APP_VERSION@'
+
+# The full version, including alpha/beta/rc tags
+release = version
+
+# Set path to documentation
+if on_rtd:
+  # On readthedocs conf.py is already in the doc folder
+  doc_path = '.'
+else:
+  # In all other builds conf.py is configured with cmake and put into the
+  # build folder.
+  doc_path = '@doc_path@'
+
+# The version from the version.txt file. Since we are not always first
+# configured by cmake, we don't necessarily have access to the current version
+# number
+version_txt = get_version(Path(doc_path).absolute().parent)
+
+# -- General configuration ---------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = [doc_path + '/_templates']
+
+# The suffix(es) of source filenames.
+# You can specify multiple suffix as a list of string:
+#
+# source_suffix = ['.rst', '.md']
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#
+# This is also used if you do content translation via gettext catalogs.
+# Usually you set "language" from the command line for these cases.
+language = None
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = None
+
+highlight_language = 'none'
+
+smartquotes = False
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+if on_rtd or os.environ.get('USE_RTD_THEME', '0') == '1':
+  html_theme = 'sphinx_rtd_theme'
+else:
+  html_theme = 'alabaster'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#
+# html_theme_options = {}
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = [doc_path + '/_static']
+
+# Custom sidebar templates, must be a dictionary that maps document names
+# to template names.
+#
+# The default sidebars (for documents that don't match any pattern) are
+# defined by theme itself.  Builtin themes are using these templates by
+# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
+# 'searchbox.html']``.
+#
+# html_sidebars = {}
+
+
+# -- Options for HTMLHelp output ---------------------------------------------
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'polybardoc'
+
+
+# -- Options for LaTeX output ------------------------------------------------
+
+latex_elements = {
+    # The paper size ('letterpaper' or 'a4paper').
+    #
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    #
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    #
+    # 'preamble': '',
+
+    # Latex figure (float) alignment
+    #
+    # 'figure_align': 'htbp',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+#  author, documentclass [howto, manual, or own class]).
+latex_documents = [
+    (master_doc, 'polybar.tex', 'polybar Documentation',
+     'Polybar Team', 'manual'),
+]
+
+
+# -- Options for manual page output ------------------------------------------
+
+# One entry per manual page. List of tuples
+# (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.5', 'polybar', 'configuration file for polybar(1)', [], 5)
+]
+
+# -- Options for Texinfo output ----------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+    (master_doc, 'polybar', 'polybar Documentation',
+     author, 'polybar', 'One line description of project.',
+     'Miscellaneous'),
+]
+
+
+# -- Options for Epub output -------------------------------------------------
+
+# Bibliographic Dublin Core info.
+epub_title = project
+
+# The unique identifier of the text. This can be a ISBN number
+# or the project homepage.
+#
+# epub_identifier = ''
+
+# A unique identification for the text.
+#
+# epub_uid = ''
+
+# A list of files that should not be packed into the epub file.
+epub_exclude_files = ['search.html']
+
+# The 'versionadded' and 'versionchanged' directives are overridden.
+suppress_warnings = ['app.add_directive']
+
+# It is not exactly clear in which version the VersionChange class was
+# introduced, but we know it is available in at least 1.8.5.
+# This feature is mainly needed for the online docs on readthedocs for the docs
+# built from master, documentation built for proper releases should not even
+# mention unreleased changes. Because of that it's not that important that this
+# is added to local builds.
+if packaging.version.parse(sphinx.__version__) >= packaging.version.parse("1.8.5"):
+
+  from typing import List
+  from docutils.nodes import Node
+  from sphinx.domains.changeset import VersionChange
+
+  def setup(app):
+    app.add_directive('deprecated', VersionDirective)
+    app.add_directive('versionadded', VersionDirective)
+    app.add_directive('versionchanged', VersionDirective)
+
+  class VersionDirective(VersionChange):
+    """
+    Overwrites the Sphinx directive for versionchanged, versionadded, and
+    deprecated and adds an unreleased tag to versions that are not yet released
+    """
+    def run(self) -> List[Node]:
+      directive_version = packaging.version.parse(self.arguments[0])
+
+      if directive_version > version_txt:
+        self.arguments[0] += " (unreleased)"
+
+      return super().run()
diff --git a/doc/dev/packaging.rst b/doc/dev/packaging.rst
new file mode 100644 (file)
index 0000000..c0436b1
--- /dev/null
@@ -0,0 +1,88 @@
+Packaging Polybar
+=================
+
+Do you want to package polybar for a distro? Great! Read this page to get
+started.
+
+First Steps
+-----------
+
+Before you get started, have a look at the `Packaging Label
+<https://github.com/polybar/polybar/issues?q=label%3APackaging>`_ on our GitHub
+repo and `Repology <https://repology.org/project/polybar/versions>`_ to see if
+polybar is already packaged for that distro or if there are efforts to do so.
+
+Even if a package already exists, it might still make sense for you to package
+polybar in some cases. Some of these cases are:
+
+- The existing package is out-of-date and the packager is no longer able/willing
+  to continue maintaining the package (or they are simply not reachable
+  anymore).
+- The existing package exist in some non-official repository and you are able to
+  introduce the package into the official package repository for the
+  distro/package manager. For example if there is a PPA providing polybar for
+  Ubuntu and you can add polybar to the official Ubuntu repositories, please do
+  :)
+
+The list above is not exhaustive, if you are unsure, feel free to ask in a new
+GitHub issue or on `Gitter <https://gitter.im/polybar>`_. Please also ask if you
+run into any polybar related issues while packaging.
+
+Packaging
+---------
+
+If you haven't already, carefully read the `Compiling
+<https://github.com/polybar/polybar/wiki/Compiling>`_ wiki page to make sure you
+fully understand all the dependencies involved and how to build polybar
+manually.
+
+We can't really tell you how to create a package for your distro, you need to
+figure that out yourself. But we can give you some guidance on building polybar
+for a package
+
+Gathering the Source Code
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Unless you are creating a package that tracks the ``master`` branch, don't clone
+the git repository. We provide a tarball with all the required source code on
+our `Release Page <https://github.com/polybar/polybar/releases>`_, use that in
+your build.
+
+Configuring and Compiling
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. note::
+
+  Do not use the ``build.sh`` script for building polybar for your package. The
+  usage and flags of the script may change without notice and we don't consider
+  that a breaking change.
+
+You can mostly follow the instructions on the `wiki
+<https://github.com/polybar/polybar/wiki/Compiling#compiling>`_ for how to
+compile polybar, but there are some additional ``cmake`` arguments you might
+want to use:
+
+- ``-DCMAKE_BUILD_TYPE=Release``: As of writing this is already the default, but
+  use it just to be on the safe side.
+- ``-DCMAKE_INSTALL_PREFIX=/usr``: Without this all the polybar files will be
+  installed under ``/usr/local``. However, for packages it is often recommended
+  they directly install to ``/usr``. So this flag will install polybar to
+  ``/usr/bin/polybar`` instead of ``/usr/local/bin/polybar``. The packaging
+  guidelines for your distro may disagree with this, in that case be sure to
+  follow your distro's guidelines.
+
+Instead of ``sudo make install``, you will most likely want to use
+``DESTDIR=<dir> make install``. That way the files will be installed into
+``<dir>`` instead of your filesystem root.
+
+Finishing Up
+------------
+
+Finally, subscribe to our `GitHub thread for package maintainers
+<https://github.com/polybar/polybar/issues/1971>`_ to get notified about new
+releases and changes to how polybar is built.
+If you want to, you can also open a PR to add your package to the `Getting
+Started <https://github.com/polybar/polybar#getting-started>`_ section of our
+README.
+
+Thank you very much for maintaining a polybar package! 🎉
diff --git a/doc/dev/release-workflow.rst b/doc/dev/release-workflow.rst
new file mode 100644 (file)
index 0000000..faeb1d2
--- /dev/null
@@ -0,0 +1,193 @@
+Release Workflow
+================
+
+We try to follow `Semantic Versioning <https://semver.org/>`_ in this project.
+Patch releases (e.g. ``3.3.X``) contain only bug fixes. Minor releases (e.g.
+``3.X.0``) can have backwards-compatible features. And major releases (
+``X.0.0``) can introduce incompatible changes.
+
+.. note::
+
+  This document replaces the "`Release Guidelines
+  <https://github.com/polybar/polybar/wiki/Release-Guidelines>`_" on the wiki
+  that we used between 3.2.0 and 3.4.3. Starting with 3.5.0, we will follow
+  the workflow described here to publish releases.
+
+Polybar uses the `OneFlow
+<https://www.endoflineblog.com/oneflow-a-git-branching-model-and-workflow>`_
+branching model for publishing new releases and introducing hotfixes.
+
+The way we accept code from contributors does not change: Contributors fork
+polybar, commit their changes to a new branch and open a PR to get that branch
+merged.
+After reviewing and approving the changes, a maintainer "merges" the PR.
+"Merging" is done in the GitHub UI by either rebasing or squashing the
+changes.
+Regular merging is disabled because we do not want merge a merge commit for
+every PR.
+
+This document is mainly concerned with how to properly release a new version of
+polybar.
+For that reason this might not be of interest to you, if you are not a
+maintainer, but feel free to read on anyway.
+
+Drafting a new Release
+----------------------
+
+There a two processes for how to draft a new release. The process for major and
+minor versions is the same as they both are "regular" releases.
+Patch releases are triggered by bugfixes that cannot wait until the next regular
+release and have a slightly different workflow.
+
+Regular Releases (Major, Minor)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Regular releases are created once we find that ``master`` is in a stable state
+and that there are enough new features to justify a new release.
+A release branch ``release/X.Y.0`` is branched off of a commit on ``master``
+that contains all the features we want in the release, this branch is pushed to
+the official repository.
+For example for version ``3.5.0`` the branch ``release/3.5.0`` would be created:
+
+.. code-block:: shell
+
+  git checkout -b release/3.5.0 <commit>
+
+The release branch should typically only exist for at most a few days.
+
+Hotfix Releases (Patch)
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A hotfix release is created whenever we receive a fix for a bug that we believe
+should be released immediately instead of it only being part of the next regular
+release.
+Generally any bugfix qualifies, but it is up to the maintainers to decide
+whether a hotfix release should be created.
+
+The hotfix release branch ``hotfix/X.Y.Z`` is created by branching off at the
+previous release tag (``X.Y.Z-1``).
+For example, if the latest version is ``3.5.2``, the next hotfix will be on
+branch ``hotfix/3.5.3``:
+
+.. code-block:: shell
+
+  git checkout -b hotfix/3.5.3 3.5.2
+
+Since the PRs for such bugfixes are often not created by maintainers, they will
+often not be based on the latest release tag, but just be branched off
+``master`` because contributors don't necessarily know about this branching
+model and also may well not know whether a hotfix will be created for a certain
+bugfix.
+
+.. TODO create contributor page that describes where to branch off. And link to
+   that page.
+
+In case a PR containing a bugfix that is destined for a patch release is not
+branched off the previous release, a maintainer creates the proper release
+branch and cherry-picks the bugfix commits.
+
+.. note::
+
+  Alternatively, the contributor can also ``git rebase --onto`` to base the
+  branch off the previous release tag. However, in most cases it makes sense for
+  a maintainer to create the release branch since they will also need to add a
+  `Release Commit`_ to it.
+
+Once the release branch is created and contains the right commits, the
+maintainer should follow `Publishing a new Release`_ to finish this patch
+release.
+
+If multiple bugfixes are submitted in close succession, they can all be
+cherry-picked onto the same patch release branch to not create many individual
+release with only a single fix.
+The maintainer can also decide to leave the release branch for this patch
+release open for a week in order to possibly combine multiple bugfixes into a
+single release.
+
+Publishing a new Release
+------------------------
+
+The process for publishing a release is the same for all release types. It goes
+as follows:
+
+* A `Release commit`_ is added to the tip of the release branch.
+* A draft PR is opened for the release branch. This PR MUST NOT be merged in
+  GitHub's interface, it is only here for review, merging happens at the
+  commandline.
+* After approval, the GitHub release publishing tool is used to publish the
+  release and tag the tip of the release branch (the release commit).
+* After the tag is created, the release branch is manually merged into
+  ``master``.
+  Here it is vitally important that the history of the release branch does not
+  change and so we use ``git merge``. We do it manually because using ``git
+  merge`` is disabled on PRs.
+
+.. code-block:: shell
+
+  git checkout master
+  git merge <release-branch>
+  git push origin
+
+* After the tag is created, the release branch can be deleted with ``git push
+  origin :<release-branch>``.
+* Work through the `After-Release Checklist`_.
+
+Here ``<release-branch>`` is either a ``release/X.Y.0`` branch or a
+``hotfix/X.Y.Z`` branch.
+
+Release Commit
+~~~~~~~~~~~~~~
+
+When merging, a release commit must be at the tip of the release branch.
+
+The release commit needs to update the version number in:
+
+* ``version.txt``
+
+The commit message contains the `Changelog`_ for this release.
+
+Changelog
+~~~~~~~~~
+
+Each release should come with a changelog briefly explaining what has changed
+for the user. It should generally be separated into 'Deprecations', 'Features',
+and 'Fixes', with 'Breaking Changes' listed separately at the top.
+
+See `old releases <https://github.com/polybar/polybar/releases>`_ for how to
+format the changelog.
+
+Since major releases generally break backwards compatibility in some way, their
+changelog should also prominently feature precisely what breaking changes were
+introduced. If suitable, maybe even separate documentation dedicated to the
+migration should be written.
+
+After-Release Checklist
+~~~~~~~~~~~~~~~~~~~~~~~
+
+* Make sure all the new functionality is documented on the wiki
+* Mark deprecated features appropriately (see `Deprecations`_)
+* Remove all unreleased notes from the wiki (not for patch releases)
+* Inform packagers of new release in `#1971
+  <https://github.com/polybar/polybar/issues/1971>`_. Mention any dependency
+  changes and any changes to the build workflow. Also mention any new files are
+  created by the installation.
+* Create a source archive named ``polybar-<version>.tar``.
+  The repository contains a script that automates this:
+
+.. code-block:: shell
+
+  ./common/release-archive.sh <version>
+
+* Update the github release with a download section that contains a link to
+  ``polybar-<version>.tar`` and its sha256.
+* Create a PR that updates the AUR ``PKGBUILD`` files for the ``polybar`` and
+  ``polybar-git`` packages (push after the ``.tar`` file was created).
+
+
+Deprecations
+~~~~~~~~~~~~
+
+If any publicly facing part of polybar is being deprecated, it should be marked
+as such in the code, through warnings/errors in the log, and by comments in the
+wiki. Every deprecated functionality is kept until the next major release and
+removed there, unless it has not been deprecated in a minor release before.
diff --git a/doc/index.rst b/doc/index.rst
new file mode 100644 (file)
index 0000000..2e92668
--- /dev/null
@@ -0,0 +1,43 @@
+Polybar Documentation
+=====================
+
+.. note:: This is still very much a work-in-progress. Most information is still
+          to be found on our `GitHub Wiki <https://github.com/polybar/polybar/wiki>`_.
+          We will migrate the wiki content step-by-step.
+
+
+Welcome to the official polybar documentation.
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Content:
+
+   user/actions
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Manual Pages:
+
+   man/polybar.1
+   man/polybar.5
+
+.. toctree::
+   :maxdepth: 1
+   :caption: For Contributors:
+
+   dev/packaging
+
+.. toctree::
+   :maxdepth: 1
+   :caption: Developer Documentation:
+
+   dev/release-workflow
+
+Getting Help
+============
+
+* `Polybar Wiki <https://github.com/polybar/polybar/wiki>`_
+* `Gitter <https://gitter.im/polybar/polybar>`_
+* `/r/polybar <https://reddit.com/r/polybar>`_ on reddit
+* ``#polybar`` on ``chat.freenode.net``
+
diff --git a/doc/man/polybar.1.rst b/doc/man/polybar.1.rst
new file mode 100644 (file)
index 0000000..a1f2ec7
--- /dev/null
@@ -0,0 +1,84 @@
+polybar(1)
+==========
+
+SYNOPSIS
+--------
+**polybar** [*OPTIONS*]... *BAR*
+
+DESCRIPTION
+-----------
+Polybar aims to help users build beautiful and highly customizable status bars for their desktop environment, without the need of having a black belt in shell scripting.
+
+OPTIONS
+-------
+
+.. program:: polybar
+
+.. option:: -h, --help
+
+   Display help text and exit
+
+.. option:: -v, --version
+
+   Display build details and exit
+.. option:: -l, --log=LEVEL
+
+   | Set the logging verbosity (default: **notice**)
+   | *LEVEL* is one of: error, warning, notice, info, trace
+.. option:: -q, --quiet
+
+   Be quiet (will override -l)
+.. option:: -c, --config=FILE
+
+   Specify the path to the configuration file. By default, the configuration file is loaded from:
+
+   |
+   | **$XDG_CONFIG_HOME/polybar/config**
+   | **$HOME/.config/polybar/config**
+.. option:: -r, --reload
+
+   Reload the application when the config file has been modified
+.. option:: -d, --dump=PARAM
+
+   Print the value of the specified parameter *PARAM* in bar section and exit
+.. option:: -m, --list-monitors
+
+   Print list of available monitors and exit
+
+   If some monitors are cloned, this will exclude all but one of them
+.. option:: -M, --list-all-monitors
+
+   Print list of available monitors and exit
+
+   This will also include all cloned monitors.
+.. option:: -w, --print-wmname
+
+   Print the generated *WM_NAME* and exit
+.. option:: -s, --stdout
+
+   Output the data to stdout instead of drawing it to the X window
+.. option:: -p, --png=FILE
+
+   Save png snapshot to *FILE* after running for 3 seconds
+
+AUTHOR
+------
+| Michael Carlberg <c@rlberg.se>
+| Contributors can be listed on GitHub.
+
+REPORTING BUGS
+--------------
+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)`
+
+.. only:: not man
+
+  :doc:`polybar.5`
diff --git a/doc/man/polybar.5.rst b/doc/man/polybar.5.rst
new file mode 100644 (file)
index 0000000..a02a134
--- /dev/null
@@ -0,0 +1,156 @@
+.. highlight:: ini
+
+polybar(5)
+==========
+
+Description
+-----------
+
+The polybar configuration file defines the behavior and look of polybar. It uses
+a variant of the `INI <https://en.wikipedia.org/wiki/INI_file>`_ file format.
+The exact syntax is described below but first a small snippet to get familiar
+with the syntax:
+
+::
+
+  [section_name]
+  ; A comment
+  # Another comment
+
+  background = #ff992a
+  width = 90%
+  monitor = HDMI-0
+
+  screenchange-reload = false
+
+  ; Use double quotes if you want to keep the surrounding space.
+  text = " Some text "
+
+When started ``polybar`` will search for the config file in one of several
+places in the following order:
+
+* If the ``-c`` or ``--config`` command line argument is specified, it will use
+  the path given there.
+* ``$XDG_CONFIG_HOME/polybar/config``
+* ``$HOME/.config/polybar/config``
+
+Syntax
+------
+
+The entire config is line-based so everything is constrained to a single line.
+This means there are no multiline values or other multiline constructs (except
+for sections).
+Each line has one of four types:
+
+* Empty
+* Comment
+* Section Header
+* Key
+
+Spaces at the beginning and end of each line will be ignored.
+
+.. note::
+
+  In this context "spaces" include the regular space character as well as the
+  tab character and any other character for which :manpage:`isspace(3)` returns
+  ``true`` (e.g. ``\r``).
+
+Any line that doesn't fit into one of these four types is a syntax error.
+
+.. note::
+
+  It is recommended that `section header` names and `key` names only use
+  alphanumeric characters as well as dashes (``-``), underscores (``_``) and
+  forward slashes (``/``).
+
+  In practice all characters are allowed except for spaces and any of these:
+  ``"'=;#[](){}:.$\%``
+
+Section Headers
+^^^^^^^^^^^^^^^
+
+Sections are used to group config options together. For example each module is
+defined in its own section.
+
+A section is defined by placing the name of the section in square brackets
+(``[`` and ``]``). For example:
+
+::
+
+  [module/wm]
+
+This declares a section with the name ``module/wm`` and all keys defined after
+this line will belong to that section until a new section is declared.
+
+.. warning::
+  The first non-empty and non-comment line in the main config file must be a
+  section header. It cannot be a key because that key would not belong to any
+  section.
+
+.. note::
+  The following section names are reserved and cannot be used inside the config:
+  ``self``, ``root``, and ``BAR``.
+
+Keys
+^^^^
+
+Keys are defined by assigning a value to a name like this:
+
+
+::
+
+  name = value
+
+This assigns ``value`` to the key ``name`` in whatever section this line is in.
+Key names need to be unique per section.
+If the value is enclosed by double-quotes (``"``), the quotes will be ignored.
+So the following still assigns ``value`` to ``name``:
+
+::
+
+  name = "value"
+
+Spaces around the equal sign are ignored, the following are all equivalent:
+
+::
+
+  name=value
+  name = value
+  name =      value
+
+Because spaces at the beginning and end of the line are also ignored, if you
+want your value to begin and/or end with a space, the value needs to be enclosed
+in double-quotes:
+
+::
+
+  name = " value "
+
+Here ``name`` has a leading and trailing whitespace.
+
+Empty Lines & Comments
+^^^^^^^^^^^^^^^^^^^^^^
+
+Empty lines and comment lines are ignored when reading the config file, they do
+not affect polybar's behavior. Comment lines start with either the ``;`` or the
+``#`` character.
+
+.. note::
+
+  Inline comments are not supported. For example the following line does not end
+  with a comment, they value of ``name`` is actually set to ``value ; comment``:
+
+  ::
+
+    name = value ; comment
+
+SEE ALSO
+--------
+
+.. only:: man
+
+  :manpage:`polybar(1)`
+
+.. only:: not man
+
+  :doc:`polybar.1`
diff --git a/doc/user/actions.rst b/doc/user/actions.rst
new file mode 100644 (file)
index 0000000..cb3581d
--- /dev/null
@@ -0,0 +1,419 @@
+Actions
+=======
+
+.. versionadded:: 3.5.0
+
+.. contents:: Table of Contents
+   :local:
+
+"Actions" are used to trigger certain behavior in modules.
+For example, when you click on your volume module (pulseaudio or alsa), polybar
+internally sends an action to that module that tells it to mute/unmute the
+audio.
+
+These actions are not only used internally, but users can also send these
+actions to polybar through `Inter Process Communication
+<https://github.com/polybar/polybar/wiki/Inter-process-messaging>`_ (IPC) to
+trigger certain behavior in polybar modules.
+
+.. _action-string-format:
+
+Action String Format
+--------------------
+
+An action string follows the following format:
+
+::
+
+  #NAME.ACTION[.DATA]
+
+Where ``NAME`` is the name of the target module (not the type!) and ``ACTION``
+is the name of the action in that module. ``DATA`` is optional data attached to
+an action (for example to say which menu level should be opened).
+
+For example the
+`date module <https://github.com/polybar/polybar/wiki/Module:-date>`_ supports
+the ``toggle`` action to toggle between the regular and the alternative time and
+date format.
+If you have the following date module:
+
+.. code-block:: ini
+
+  [module/mydate]
+  type = internal/date
+  ...
+
+The action string for toggling between the date formats would look like this:
+
+::
+
+  #mydate.toggle
+
+Note that we use the name of the module (``mydate``) and not the type.
+
+As an example for an action string with additional data, take the menu module:
+
+.. code-block:: ini
+
+  [module/powermenu]
+  type = custom/menu
+  menu-0-0 = Poweroff
+  menu-0-0-exec = poweroff
+  menu-0-1 = Suspend
+  menu-0-1-exec = systemctl suspend
+
+The action name to open a certain menu level is ``open``, so to open level 0
+(`menu-0`), the action string additionally has the level attached to it:
+
+::
+
+  #powermenu.open.0
+
+Triggering Actions
+------------------
+
+Most modules already use action strings to trigger actions when you click on or
+scroll over a module.
+But in some cases you may want or need to manually send action strings to
+polybar to trigger a certain behavior.
+
+Everywhere where you can specify a command to run on click or scroll, you can
+also specify an action string.
+For example, in the bar section, you can specify a command that is triggered
+when you click anywhere on the bar (where there isn't another click action):
+
+.. code-block:: ini
+
+  [bar/mybar]
+  ...
+  click-left = #mydate.toggle
+  ...
+
+This will then trigger the ``toggle`` action on the ``mydate`` module when you
+click anywhere on the bar.
+
+Similarly, we can use action strings in ``%{A}``
+`formatting tags <https://github.com/polybar/polybar/wiki/Formatting#action-a>`_
+just as we would regular commands:
+
+::
+
+  %{A1:firefox:}%{A3:#mydate.toggle:}Opens firefox on left-click and toggles the
+  date on right-click %{A}%{A}
+
+Finally, polybar's `Inter Process Communication
+<https://github.com/polybar/polybar/wiki/Inter-process-messaging>`_ (IPC) can
+also be used to trigger actions:
+
+.. code-block:: bash
+
+  polybar-msg action "#mydate.toggle"
+
+.. note::
+
+  The quotes around the action string are necessary, otherwise your shell may
+  interpret the ``#`` as the beginning of the comment and ignore the rest of the
+  line.
+
+Available Actions
+-----------------
+
+The following modules have actions available. Most of them are already used by
+the module by default for click and scroll events.
+
+internal/date
+^^^^^^^^^^^^^
+
+:``toggle``:
+  Toggles the date/time format between ``date``/``time`` and
+  ``date-alt``/``time-alt``
+
+internal/alsa
+^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+  Increases/Decreases the volume by ``interval`` percentage points, where
+  ``interval`` is the config setting in the module. Volume changed like this
+  will never go above 100%.
+
+:``toggle``:
+  Toggles between muted and unmuted.
+
+internal/pulseaudio
+^^^^^^^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+  Increases/Decreases the volume by ``interval`` percentage points, where
+  ``interval`` is the config setting in the module. Volume changed like this
+  will never go above ~153% (if ``use-ui-max`` is set to ``true``) or 100% (if
+  not).
+
+:``toggle``:
+  Toggles between muted and unmuted.
+
+internal/xbacklight
+^^^^^^^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+  Increases/Decreases screen brightness 5 percentage points.
+
+internal/backlight
+^^^^^^^^^^^^^^^^^^
+
+:``inc``, ``dec``:
+  Increases/Decreases screen brightness 5 percentage points.
+
+internal/xkeyboard
+^^^^^^^^^^^^^^^^^^
+
+:``switch``:
+  Cycles through configured keyboard layouts.
+
+internal/mpd
+^^^^^^^^^^^^
+
+:``play``: Starts playing the current song.
+:``pause``: Pauses the current song.
+:``stop``: Stops playing.
+:``prev``: Starts playing the previous song.
+:``next``: Starts playing the next song.
+:``repeat``: Toggles repeat mode.
+:``single``: Toggles single mode.
+:``random``: Toggles random mode.
+:``consume``: Toggles consume mode.
+:``seek``: *(Has Data)* Seeks inside the current song.
+
+           The data must be of the form ``[+-]N``, where ``N`` is a number
+           between 0 and 100.
+
+           If either ``+`` or ``-`` is used, it will seek forward or backward
+           from the current position by ``N%`` (relative to the length of the
+           song).
+           Otherwise it will seek to ``N%`` of the current song.
+
+internal/xworkspaces
+^^^^^^^^^^^^^^^^^^^^
+
+:``focus``: *(Has Data)* Switches to the given workspace.
+
+            The data is the index of the workspace that should be selected.
+:``next``: Switches to the next workspace. The behavior of this action is
+           affected by the ``pin-workspaces`` setting.
+:``prev``: Switches to the previous workspace. The behavior of this action is
+           affected by the ``pin-workspaces`` setting.
+
+internal/bspwm
+^^^^^^^^^^^^^^
+
+:``focus``: *(Has Data)* Switches to the given workspace.
+
+            The data has the form ``N+M``, where ``N`` is the index of the
+            monitor and ``M`` the index of the workspace on that monitor.
+            Both indices are 0-based and correspond to the position the monitor
+            and workspace appear in the output of ``bspc subscribe report``.
+:``next``: Switches to the next workspace. The behavior of this action is
+           affected by the ``pin-workspaces`` setting.
+:``prev``: Switches to the previous workspace. The behavior of this action is
+           affected by the ``pin-workspaces`` setting.
+
+
+internal/i3
+^^^^^^^^^^^
+
+:``focus``: *(Has Data)* Switches to the given workspace.
+
+            The data is the name of the workspace defined in the i3 config.
+:``next``: Switches to the next workspace. The behavior of this action is
+           affected by the ``pin-workspaces`` setting.
+:``prev``: Switches to the previous workspace. The behavior of this action is
+           affected by the ``pin-workspaces`` setting.
+
+custom/menu
+^^^^^^^^^^^
+
+:``open``: *(Has Data)* Opens the given menu level
+
+           The data is a single number specifying which menu level should be
+           opened.
+:``close``: Closes the menu
+:``exec``: *(Has Data)* Executes the command at the given menu element.
+
+           The data has the form ``N-M`` and the action will execute the command
+           in ``menu-N-M-exec``.
+
+Deprecated Action Names
+-----------------------
+
+.. deprecated:: 3.5.0
+
+In earlier versions (< 3.5.0) action strings only included information about the
+module type.
+This meant in bars that contained multiple different modules of the same type,
+actions for these modules were sometimes processed by the wrong module with the
+same type.
+
+Since version 3.5.0, this no longer happens. However, this also means we had to
+change what actions are recognized by polybar modules.
+
+If you explicitly use any polybar action names in your config or any of your
+scripts, you are advised to change them, as they may stop working at some point
+in the future.
+For now polybar still supports the old action names, will convert them to the
+appropriate new action name, and will print a warning to help you find old
+action names in your config.
+
+If you use the `menu module
+<https://github.com/polybar/polybar/wiki/Module:-menu>`_, you most likely use
+old action names to open and close the menu (for example ``menu-open-1`` or
+``menu-close``).
+The ``i3wm-wsnext``, ``i3wm-wsprev``, ``bspwm-desknext``, and ``bspwm-deskprev``
+actions, to switch workspaces in i3 and bspwm, may also appear in your config.
+
+Migration
+^^^^^^^^^
+
+Updating your config to use the new action names is quite straightforward.
+
+For each action name, consult the table below to find the new action name.
+Afterwards build the complete action string as described in
+:ref:`action-string-format`.
+
+Please see :ref:`below <menu-example>` for an example of migrating a typical menu module.
+
++-------------------------+-----------------------+---------------+
+|Module Type              |Deprecated Action Name |New Action Name|
++=========================+=======================+===============+
+|``internal/date``        |``datetoggle``         |``toggle``     |
++-------------------------+-----------------------+---------------+
+|``internal/alsa``        |``volup``              |``inc``        |
+|                         +-----------------------+---------------+
+|                         |``voldown``            |``dec``        |
+|                         +-----------------------+---------------+
+|                         |``volmute``            |``toggle``     |
++-------------------------+-----------------------+---------------+
+|``internal/pulseaudio``  |``pa_volup``           |``inc``        |
+|                         +-----------------------+---------------+
+|                         |``pa_voldown``         |``dec``        |
+|                         +-----------------------+---------------+
+|                         |``pa_volmute``         |``toggle``     |
++-------------------------+-----------------------+---------------+
+|``internal/xbacklight``  |``xbacklight+``        |``inc``        |
+|                         +-----------------------+---------------+
+|                         |``xbacklight-``        |``dec``        |
++-------------------------+-----------------------+---------------+
+|``internal/backlight``   |``backlight+``         |``inc``        |
+|                         +-----------------------+---------------+
+|                         |``backlight-``         |``dec``        |
++-------------------------+-----------------------+---------------+
+|``internal/xkeyboard``   |``xkeyboard/switch``   |``switch``     |
++-------------------------+-----------------------+---------------+
+|``internal/mpd``         |``mpdplay``            |``play``       |
+|                         +-----------------------+---------------+
+|                         |``mpdpause``           |``pause``      |
+|                         +-----------------------+---------------+
+|                         |``mpdstop``            |``stop``       |
+|                         +-----------------------+---------------+
+|                         |``mpdprev``            |``prev``       |
+|                         +-----------------------+---------------+
+|                         |``mpdnext``            |``next``       |
+|                         +-----------------------+---------------+
+|                         |``mpdrepeat``          |``repeat``     |
+|                         +-----------------------+---------------+
+|                         |``mpdsingle``          |``single``     |
+|                         +-----------------------+---------------+
+|                         |``mpdrandom``          |``random``     |
+|                         +-----------------------+---------------+
+|                         |``mpdconsume``         |``consume``    |
+|                         +-----------------------+---------------+
+|                         |``mpdseekN``           |``seek.N``     |
++-------------------------+-----------------------+---------------+
+|``internal/xworkspaces`` |``xworkspaces-focus=N``|``focus.N``    |
+|                         +-----------------------+---------------+
+|                         |``xworkspaces-next``   |``next``       |
+|                         +-----------------------+---------------+
+|                         |``xworkspaces-prev``   |``prev``       |
++-------------------------+-----------------------+---------------+
+|``internal/bspwm``       |``bspwm-deskfocusN``   |``focus.N``    |
+|                         +-----------------------+---------------+
+|                         |``bspwm-desknext``     |``next``       |
+|                         +-----------------------+---------------+
+|                         |``bspwm-deskprev``     |``prev``       |
++-------------------------+-----------------------+---------------+
+|``internal/i3``          |``i3wm-wsfocus-N``     |``focus.N``    |
+|                         +-----------------------+---------------+
+|                         |``i3-wsnext``          |``next``       |
+|                         +-----------------------+---------------+
+|                         |``i3-wsprev``          |``prev``       |
++-------------------------+-----------------------+---------------+
+|``custom/menu``          |``menu-open-N``        |``open.N``     |
+|                         +-----------------------+---------------+
+|                         |``menu-close``         |``close``      |
++-------------------------+-----------------------+---------------+
+
+.. note::
+
+   Some deprecated action names are suffixed with ``N``, this means that that
+   action has some additional data (represented by that ``N``), in the new
+   action names this data will appear in exactly the same way, after a period.
+
+.. _menu-example:
+
+Menu Module Example
+"""""""""""""""""""
+
+The menu module is the only module where we have to explicitly use actions for
+it to work. Because of this, almost everyone will need to update their menu
+module to use the new action format.
+
+Below you can see an example of a menu module:
+
+.. code-block:: ini
+
+  [module/apps]
+  type = custom/menu
+
+  menu-0-0 = Browsers
+  menu-0-0-exec = menu-open-1
+  menu-0-1 = Multimedia
+  menu-0-1-exec = menu-open-2
+
+  menu-1-0 = Firefox
+  menu-1-0-exec = firefox &
+  menu-1-1 = Chromium
+  menu-1-1-exec = chromium &
+
+  menu-2-0 = Gimp
+  menu-2-0-exec = gimp &
+  menu-2-1 = Scrot
+  menu-2-1-exec = scrot &
+
+This module uses two actions: ``menu-open-1`` and ``menu-open-2``.
+These are actions with data, the data specifies which level of the menu should
+be opened.
+
+Looking at the table, we see that the new action name for ``menu-open-N`` is
+``open.N``, where ``.N`` is the data attached to the action.
+Putting this together with the name of the module gives us ``#apps.open.1`` and
+``#apps.open.2`` as action strings.
+Since your menu module likely has a different name, your action strings will
+likely not use ``apps``, but the name of your module.
+
+.. code-block:: ini
+
+  [module/apps]
+  type = custom/menu
+
+  menu-0-0 = Browsers
+  menu-0-0-exec = #apps.open.1
+  menu-0-1 = Multimedia
+  menu-0-1-exec = #apps.open.2
+
+  menu-1-0 = Firefox
+  menu-1-0-exec = firefox &
+  menu-1-1 = Chromium
+  menu-1-1-exec = chromium &
+
+  menu-2-0 = Gimp
+  menu-2-0-exec = gimp &
+  menu-2-1 = Scrot
+  menu-2-1-exec = scrot &
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
new file mode 100644 (file)
index 0000000..cde7973
--- /dev/null
@@ -0,0 +1,25 @@
+#
+# Generate settings.hpp
+#
+
+list(APPEND dirs ${CMAKE_CURRENT_LIST_DIR})
+
+if(WITH_XRANDR)
+  list(APPEND XPP_EXTENSION_LIST xpp::randr::extension)
+endif()
+if(WITH_XCOMPOSITE)
+  list(APPEND XPP_EXTENSION_LIST xpp::composite::extension)
+endif()
+if(WITH_XKB)
+  list(APPEND XPP_EXTENSION_LIST xpp::xkb::extension)
+endif()
+string(REPLACE ";" ", " XPP_EXTENSION_LIST "${XPP_EXTENSION_LIST}")
+
+configure_file(
+  ${CMAKE_CURRENT_LIST_DIR}/settings.hpp.cmake
+  ${CMAKE_BINARY_DIR}/generated-sources/settings.hpp
+  ESCAPE_QUOTES)
+
+list(APPEND dirs ${CMAKE_BINARY_DIR}/generated-sources/)
+set(APP_VERSION ${APP_VERSION} PARENT_SCOPE)
+set(dirs ${dirs} PARENT_SCOPE)
diff --git a/include/adapters/alsa/control.hpp b/include/adapters/alsa/control.hpp
new file mode 100644 (file)
index 0000000..5641b13
--- /dev/null
@@ -0,0 +1,41 @@
+#pragma once
+
+#include <mutex>
+
+#include "common.hpp"
+#include "settings.hpp"
+
+// fwd
+struct _snd_ctl;
+struct _snd_hctl_elem;
+struct _snd_hctl;
+typedef struct _snd_ctl snd_ctl_t;
+typedef struct _snd_hctl_elem snd_hctl_elem_t;
+typedef struct _snd_hctl snd_hctl_t;
+
+POLYBAR_NS
+
+namespace alsa {
+  class control {
+   public:
+    explicit control(int numid);
+    ~control();
+
+    control(const control& o) = delete;
+    control& operator=(const control& o) = delete;
+
+    int get_numid();
+    bool wait(int timeout = -1);
+    bool test_device_plugged();
+    void process_events();
+
+   private:
+    int m_numid{0};
+
+    snd_ctl_t* m_ctl{nullptr};
+    snd_hctl_t* m_hctl{nullptr};
+    snd_hctl_elem_t* m_elem{nullptr};
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/adapters/alsa/generic.hpp b/include/adapters/alsa/generic.hpp
new file mode 100644 (file)
index 0000000..2415946
--- /dev/null
@@ -0,0 +1,65 @@
+#pragma once
+
+#ifdef USE_ALSALIB_H
+#include <alsa/asoundlib.h>
+#else
+#include <assert.h>
+
+#ifndef __FreeBSD__
+#include <endian.h>
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <poll.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#ifndef __GNUC__
+#define __inline__ inline
+#endif
+
+#include <alsa/asoundef.h>
+#include <alsa/version.h>
+#include <alsa/global.h>
+#include <alsa/input.h>
+#include <alsa/output.h>
+#include <alsa/error.h>
+#include <alsa/conf.h>
+#include <alsa/pcm.h>
+#include <alsa/rawmidi.h>
+#include <alsa/timer.h>
+#include <alsa/hwdep.h>
+#include <alsa/control.h>
+#include <alsa/mixer.h>
+#include <alsa/seq_event.h>
+#include <alsa/seq.h>
+#include <alsa/seqmid.h>
+#include <alsa/seq_midi_event.h>
+#endif
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+namespace alsa {
+  DEFINE_ERROR(alsa_exception);
+  DEFINE_CHILD_ERROR(mixer_error, alsa_exception);
+  DEFINE_CHILD_ERROR(control_error, alsa_exception);
+
+  template <typename T>
+  void throw_exception(string&& message, int error_code) {
+    const char* snd_error = snd_strerror(error_code);
+    if (snd_error != nullptr)
+      message += ": " + string{snd_error};
+    throw T(message.c_str());
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/adapters/alsa/mixer.hpp b/include/adapters/alsa/mixer.hpp
new file mode 100644 (file)
index 0000000..b8e17f5
--- /dev/null
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <mutex>
+
+#include "common.hpp"
+#include "settings.hpp"
+
+// fwd
+struct _snd_mixer;
+struct _snd_mixer_elem;
+struct _snd_mixer_selem_id;
+typedef struct _snd_mixer snd_mixer_t;
+typedef struct _snd_mixer_elem snd_mixer_elem_t;
+typedef struct _snd_mixer_selem_id snd_mixer_selem_id_t;
+
+POLYBAR_NS
+
+namespace alsa {
+  class mixer {
+   public:
+    explicit mixer(string&& mixer_selem_name, string&& soundcard_name);
+    ~mixer();
+
+    mixer(const mixer& o) = delete;
+    mixer& operator=(const mixer& o) = delete;
+
+    const string& get_name();
+    const string& get_sound_card();
+
+    bool wait(int timeout = -1);
+    int process_events();
+
+    int get_volume();
+    int get_normalized_volume();
+    void set_volume(float percentage);
+    void set_normalized_volume(float percentage);
+    void set_mute(bool mode);
+    void toggle_mute();
+    bool is_muted();
+
+   private:
+    snd_mixer_t* m_mixer{nullptr};
+    snd_mixer_elem_t* m_elem{nullptr};
+
+    string m_name;
+    string s_name;
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/adapters/mpd.hpp b/include/adapters/mpd.hpp
new file mode 100644 (file)
index 0000000..2a152b0
--- /dev/null
@@ -0,0 +1,183 @@
+#pragma once
+
+#include <mpd/client.h>
+#include <stdlib.h>
+#include <chrono>
+#include <csignal>
+
+#include "common.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+// fwd
+class logger;
+
+namespace chrono = std::chrono;
+
+namespace mpd {
+  extern sig_atomic_t g_connection_closed;
+
+  DEFINE_ERROR(mpd_exception);
+  DEFINE_CHILD_ERROR(client_error, mpd_exception);
+  DEFINE_CHILD_ERROR(server_error, mpd_exception);
+
+  void check_connection(mpd_connection* conn);
+  void check_errors(mpd_connection* conn);
+
+  // types details {{{
+
+  enum class connection_state { NONE = 0, CONNECTED, DISCONNECTED };
+
+  enum class mpdstate {
+    UNKNOWN = 1 << 0,
+    STOPPED = 1 << 1,
+    PLAYING = 1 << 2,
+    PAUSED = 1 << 4,
+  };
+
+  namespace details {
+    struct mpd_connection_deleter {
+      void operator()(mpd_connection* conn);
+    };
+
+    struct mpd_status_deleter {
+      void operator()(mpd_status* status);
+    };
+
+    struct mpd_song_deleter {
+      void operator()(mpd_song* song);
+    };
+  }
+
+  using mpd_connection_t = unique_ptr<mpd_connection, details::mpd_connection_deleter>;
+  using mpd_status_t = unique_ptr<mpd_status, details::mpd_status_deleter>;
+  using mpd_song_t = unique_ptr<mpd_song, details::mpd_song_deleter>;
+
+  // }}}
+  // class : mpdsong {{{
+
+  class mpdsong {
+   public:
+    explicit mpdsong(mpd_song_t&& song) : m_song(forward<decltype(song)>(song)) {}
+
+    operator bool();
+
+    string get_artist();
+    string get_album_artist();
+    string get_album();
+    string get_title();
+    string get_date();
+    unsigned get_duration();
+
+   private:
+    mpd_song_t m_song;
+  };
+
+  // }}}
+  // class : mpdconnection {{{
+
+  class mpdstatus;
+  class mpdconnection {
+   public:
+    explicit mpdconnection(
+        const logger& logger, string host, unsigned int port = 6600, string password = "", unsigned int timeout = 15);
+    ~mpdconnection();
+
+    void connect();
+    void disconnect();
+    bool connected();
+    bool retry_connection(int interval = 1);
+
+    int get_fd();
+    void idle();
+    int noidle();
+
+    unique_ptr<mpdstatus> get_status();
+    unique_ptr<mpdstatus> get_status_safe();
+    unique_ptr<mpdsong> get_song();
+
+    void play();
+    void pause(bool state);
+    void toggle();
+    void stop();
+    void prev();
+    void next();
+    void seek(int songid, int pos);
+
+    void set_repeat(bool mode);
+    void set_random(bool mode);
+    void set_single(bool mode);
+    void set_consume(bool mode);
+
+    operator mpd_connection_t::element_type*();
+
+   protected:
+    void check_prerequisites();
+    void check_prerequisites_commands_list();
+
+   private:
+    const logger& m_log;
+    mpd_connection_t m_connection{};
+
+    struct sigaction m_signal_action {};
+
+    bool m_listactive = false;
+    bool m_idle = false;
+    int m_fd = -1;
+
+    string m_host;
+    unsigned int m_port;
+    string m_password;
+    unsigned int m_timeout;
+  };
+
+  // }}}
+  // class : mpdstatus {{{
+
+  class mpdstatus {
+   public:
+    explicit mpdstatus(mpdconnection* conn, bool autoupdate = true);
+
+    void fetch_data(mpdconnection* conn);
+    void update(int event, mpdconnection* connection);
+
+    bool random() const;
+    bool repeat() const;
+    bool single() const;
+    bool consume() const;
+
+    bool match_state(mpdstate state) const;
+
+    int get_songid() const;
+    int get_queuelen() const;
+    unsigned get_total_time() const;
+    unsigned get_elapsed_time() const;
+    unsigned get_elapsed_percentage();
+    string get_formatted_elapsed();
+    string get_formatted_total();
+    int get_seek_position(int percentage);
+
+   private:
+    mpd_status_t m_status{};
+    unique_ptr<mpdsong> m_song{};
+    mpdstate m_state{mpdstate::UNKNOWN};
+    chrono::system_clock::time_point m_updated_at{};
+
+    bool m_random{false};
+    bool m_repeat{false};
+    bool m_single{false};
+    bool m_consume{false};
+
+    int m_songid{0};
+    int m_queuelen{0};
+
+    unsigned long m_total_time{0UL};
+    unsigned long m_elapsed_time{0UL};
+    unsigned long m_elapsed_time_ms{0UL};
+  };
+
+  // }}}
+}
+
+POLYBAR_NS_END
diff --git a/include/adapters/net.hpp b/include/adapters/net.hpp
new file mode 100644 (file)
index 0000000..97d6d39
--- /dev/null
@@ -0,0 +1,184 @@
+#pragma once
+
+#include <chrono>
+#include <cstdlib>
+
+#include <arpa/inet.h>
+#include <ifaddrs.h>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "errors.hpp"
+#include "components/logger.hpp"
+#include "utils/math.hpp"
+
+#if WITH_LIBNL
+#include <net/if.h>
+
+struct nl_msg;
+struct nlattr;
+#else
+#include <iwlib.h>
+
+/*
+ * wirless_tools 29 (and possibly earlier) redefines 'inline' in iwlib.h
+ * With clang this leads to a conflict in the POLYBAR_NS macro
+ * wirless_tools 30 doesn't have that issue anymore
+ */
+#ifdef inline
+#undef inline
+#endif
+#endif
+
+POLYBAR_NS
+
+class file_descriptor;
+
+namespace net {
+  DEFINE_ERROR(network_error);
+
+  bool is_wireless_interface(const string& ifname);
+
+  // types {{{
+
+  struct quality_range {
+    int val{0};
+    int max{0};
+
+    int percentage() const {
+      if (val < 0) {
+        return std::max(std::min(std::abs(math_util::percentage(val, max, -20)), 100), 0);
+      }
+      return std::max(std::min(math_util::percentage(val, 0, max), 100), 0);
+    }
+  };
+
+  using bytes_t = unsigned int;
+
+  struct link_activity {
+    bytes_t transmitted{0};
+    bytes_t received{0};
+    std::chrono::system_clock::time_point time;
+  };
+
+  struct link_status {
+    string ip;
+    string ip6;
+    link_activity previous{};
+    link_activity current{};
+  };
+
+  // }}}
+  // class : network {{{
+
+  class network {
+   public:
+    explicit network(string interface);
+    virtual ~network() {}
+
+    virtual bool query(bool accumulate = false);
+    virtual bool connected() const = 0;
+    virtual bool ping() const;
+
+    string ip() const;
+    string ip6() const;
+    string downspeed(int minwidth = 3) const;
+    string upspeed(int minwidth = 3) const;
+    void set_unknown_up(bool unknown = true);
+
+   protected:
+    void check_tuntap_or_bridge();
+    bool test_interface() const;
+    string format_speedrate(float bytes_diff, int minwidth) const;
+    void query_ip6();
+
+    const logger& m_log;
+    unique_ptr<file_descriptor> m_socketfd;
+    link_status m_status{};
+    string m_interface;
+    bool m_tuntap{false};
+    bool m_bridge{false};
+    bool m_unknown_up{false};
+  };
+
+  // }}}
+  // class : wired_network {{{
+
+  class wired_network : public network {
+   public:
+    explicit wired_network(string interface) : network(interface) {}
+
+    bool query(bool accumulate = false) override;
+    bool connected() const override;
+    string linkspeed() const;
+
+   private:
+    int m_linkspeed{0};
+  };
+
+  // }}}
+
+#if WITH_LIBNL
+  // class : wireless_network {{{
+
+  class wireless_network : public network {
+   public:
+    wireless_network(string interface) : network(interface), m_ifid(if_nametoindex(interface.c_str())){};
+
+    bool query(bool accumulate = false) override;
+    bool connected() const override;
+    string essid() const;
+    int signal() const;
+    int quality() const;
+
+   protected:
+    static int scan_cb(struct nl_msg* msg, void* instance);
+
+    bool associated_or_joined(struct nlattr** bss);
+    void parse_essid(struct nlattr** bss);
+    void parse_frequency(struct nlattr** bss);
+    void parse_quality(struct nlattr** bss);
+    void parse_signal(struct nlattr** bss);
+
+   private:
+    unsigned int m_ifid{};
+    string m_essid{};
+    int m_frequency{};
+    quality_range m_signalstrength{};
+    quality_range m_linkquality{};
+  };
+
+  // }}}
+#else
+  // class : wireless_network {{{
+
+  class wireless_network : public network {
+   public:
+    wireless_network(string interface) : network(interface) {}
+
+    bool query(bool accumulate = false) override;
+    bool connected() const override;
+
+    string essid() const;
+    int signal() const;
+    int quality() const;
+
+   protected:
+    void query_essid(const int& socket_fd);
+    void query_quality(const int& socket_fd);
+
+   private:
+    shared_ptr<wireless_info> m_info{};
+    string m_essid{};
+    quality_range m_signalstrength{};
+    quality_range m_linkquality{};
+  };
+
+  // }}}
+#endif
+
+  using wireless_t = unique_ptr<wireless_network>;
+  using wired_t = unique_ptr<wired_network>;
+}  // namespace net
+
+POLYBAR_NS_END
diff --git a/include/adapters/pulseaudio.hpp b/include/adapters/pulseaudio.hpp
new file mode 100644 (file)
index 0000000..09398ac
--- /dev/null
@@ -0,0 +1,81 @@
+#pragma once
+
+#include <pulse/pulseaudio.h>
+#include <queue>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "errors.hpp"
+
+#include "utils/math.hpp"
+// fwd
+struct pa_context;
+struct pa_threaded_mainloop;
+struct pa_cvolume;
+typedef struct pa_context pa_context;
+typedef struct pa_threaded_mainloop pa_threaded_mainloop;
+
+POLYBAR_NS
+class logger;
+
+DEFINE_ERROR(pulseaudio_error);
+
+class pulseaudio {
+  // events to add to our queue
+  enum class evtype { NEW = 0, CHANGE, REMOVE, SERVER };
+  using queue = std::queue<evtype>;
+
+  public:
+    explicit pulseaudio(const logger& logger, string&& sink_name, bool m_max_volume);
+    ~pulseaudio();
+
+    pulseaudio(const pulseaudio& o) = delete;
+    pulseaudio& operator=(const pulseaudio& o) = delete;
+
+    const string& get_name();
+
+    bool wait();
+    int process_events();
+
+    int get_volume();
+    double get_decibels();
+    void set_volume(float percentage);
+    void inc_volume(int delta_perc);
+    void set_mute(bool mode);
+    void toggle_mute();
+    bool is_muted();
+
+  private:
+    void update_volume(pa_operation *o);
+    static void check_mute_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata);
+    static void get_sink_volume_callback(pa_context *context, const pa_sink_info *info, int is_last, void *userdata);
+    static void subscribe_callback(pa_context* context, pa_subscription_event_type_t t, uint32_t idx, void* userdata);
+    static void simple_callback(pa_context *context, int success, void *userdata);
+    static void sink_info_callback(pa_context *context, const pa_sink_info *info, int eol, void *userdata);
+    static void context_state_callback(pa_context *context, void *userdata);
+
+    inline void wait_loop(pa_operation *op, pa_threaded_mainloop *loop);
+
+    const logger& m_log;
+
+    // used for temporary callback results
+    int success{0};
+    pa_cvolume cv;
+    bool muted{false};
+    // default sink name
+    static constexpr auto DEFAULT_SINK = "@DEFAULT_SINK@";
+
+    pa_context* m_context{nullptr};
+    pa_threaded_mainloop* m_mainloop{nullptr};
+
+    queue m_events;
+
+    // specified sink name
+    string spec_s_name;
+    string s_name;
+    uint32_t m_index{0};
+
+    pa_volume_t m_max_volume{PA_VOLUME_UI_MAX};
+};
+
+POLYBAR_NS_END
diff --git a/include/cairo/context.hpp b/include/cairo/context.hpp
new file mode 100644 (file)
index 0000000..be881c5
--- /dev/null
@@ -0,0 +1,344 @@
+#pragma once
+
+#include <cairo/cairo-xcb.h>
+
+#include <algorithm>
+#include <cmath>
+#include <deque>
+
+#include "cairo/font.hpp"
+#include "cairo/surface.hpp"
+#include "cairo/types.hpp"
+#include "cairo/utils.hpp"
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/color.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+  /**
+   * \brief Cairo context
+   */
+  class context {
+   public:
+    explicit context(const surface& surface, const logger& log) : m_c(cairo_create(surface)), m_log(log) {
+      auto status = cairo_status(m_c);
+      if (status != CAIRO_STATUS_SUCCESS) {
+        throw application_error(sstream() << "cairo_status(): " << cairo_status_to_string(status));
+      }
+      cairo_set_antialias(m_c, CAIRO_ANTIALIAS_GOOD);
+    }
+
+    virtual ~context() {
+      cairo_destroy(m_c);
+    }
+
+    operator cairo_t*() const {
+      return m_c;
+    }
+
+    context& operator<<(const surface& s) {
+      cairo_set_source_surface(m_c, s, 0.0, 0.0);
+      return *this;
+    }
+
+    context& operator<<(cairo_operator_t o) {
+      cairo_set_operator(m_c, o);
+      return *this;
+    }
+
+    context& operator<<(cairo_pattern_t* s) {
+      cairo_set_source(m_c, s);
+      return *this;
+    }
+
+    context& operator<<(const abspos& p) {
+      if (p.clear) {
+        cairo_new_path(m_c);
+      }
+      cairo_move_to(m_c, p.x, p.y);
+      return *this;
+    }
+
+    context& operator<<(const relpos& p) {
+      cairo_rel_move_to(m_c, p.x, p.y);
+      return *this;
+    }
+
+    context& operator<<(const rgba& f) {
+      cairo_set_source_rgba(m_c, f.red_d(), f.green_d(), f.blue_d(), f.alpha_d());
+      return *this;
+    }
+
+    context& operator<<(const rect& f) {
+      cairo_rectangle(m_c, f.x, f.y, f.w, f.h);
+      return *this;
+    }
+
+    context& operator<<(const line& l) {
+      struct line p {
+        l.x1, l.y1, l.x2, l.y2, l.w
+      };
+      snap(&p.x1, &p.y1);
+      snap(&p.x2, &p.y2);
+      cairo_move_to(m_c, p.x1, p.y1);
+      cairo_line_to(m_c, p.x2, p.y2);
+      cairo_set_line_width(m_c, p.w);
+      cairo_stroke(m_c);
+      return *this;
+    }
+
+    context& operator<<(const translate& d) {
+      cairo_translate(m_c, d.x, d.y);
+      return *this;
+    }
+
+    context& operator<<(const linear_gradient& l) {
+      auto stops = l.steps.size();
+      if (stops >= 2) {
+        auto pattern = cairo_pattern_create_linear(l.x1, l.y1, l.x2, l.y2);
+        auto step = 1.0 / (stops - 1);
+        auto offset = 0.0;
+        for (auto&& color : l.steps) {
+          // clang-format off
+          cairo_pattern_add_color_stop_rgba(pattern, offset, color.red_d(), color.green_d(), color.blue_d(), color.alpha_d());
+          // clang-format on
+          offset += step;
+        }
+        *this << pattern;
+        cairo_pattern_destroy(pattern);
+      }
+      return *this;
+    }
+
+    context& operator<<(const rounded_corners& c) {
+      double d = M_PI / 180.0;
+      cairo_new_sub_path(m_c);
+      cairo_arc(m_c, c.x + c.w - c.radius.top, c.y + c.radius.top, c.radius.top, -90 * d, 0 * d);
+      cairo_arc(m_c, c.x + c.w - c.radius.bottom, c.y + c.h - c.radius.bottom, c.radius.bottom, 0 * d, 90 * d);
+      cairo_arc(m_c, c.x + c.radius.bottom, c.y + c.h - c.radius.bottom, c.radius.bottom, 90 * d, 180 * d);
+      cairo_arc(m_c, c.x + c.radius.top, c.y + c.radius.top, c.radius.top, 180 * d, 270 * d);
+      cairo_close_path(m_c);
+      return *this;
+    }
+
+    context& operator<<(const textblock& t) {
+      double x, y;
+      position(&x, &y);
+
+      // Prioritize the preferred font
+      vector<shared_ptr<font>> fns(m_fonts.begin(), m_fonts.end());
+
+      if (t.font > 0 && t.font <= std::distance(fns.begin(), fns.end())) {
+        std::iter_swap(fns.begin(), fns.begin() + t.font - 1);
+      }
+
+      string utf8 = string(t.contents);
+      utils::unicode_charlist chars;
+      utils::utf8_to_ucs4((const unsigned char*)utf8.c_str(), chars);
+
+      while (!chars.empty()) {
+        auto remaining = chars.size();
+        for (auto&& f : fns) {
+          unsigned int matches = 0;
+
+          // Match as many glyphs as possible if the default/preferred font
+          // is being tested. Otherwise test one glyph at a time against
+          // the remaining fonts. Roll back to the top of the font list
+          // when a glyph has been found.
+          if (f == fns.front() && (matches = f->match(chars)) == 0) {
+            continue;
+          } else if (f != fns.front() && (matches = f->match(chars.front())) == 0) {
+            continue;
+          }
+
+          string subset;
+          auto end = chars.begin();
+          while (matches-- && end != chars.end()) {
+            subset += utf8.substr(end->offset, end->length);
+            end++;
+          }
+
+          // Use the font
+          f->use();
+
+          // Get subset extents
+          cairo_text_extents_t extents;
+          f->textwidth(subset, &extents);
+
+          // Draw the background
+          if (t.bg_rect.h != 0.0) {
+            save();
+            cairo_set_operator(m_c, t.bg_operator);
+            *this << t.bg;
+            cairo_rectangle(m_c, t.bg_rect.x + *t.x_advance, t.bg_rect.y + *t.y_advance,
+                t.bg_rect.w + extents.x_advance, t.bg_rect.h);
+            cairo_fill(m_c);
+            restore();
+          }
+
+          // Render subset
+          auto fontextents = f->extents();
+          f->render(subset, x, y - (fontextents.descent / 2 - fontextents.height / 4) + f->offset());
+
+          // Get updated position
+          position(&x, nullptr);
+
+          // Increase position
+          *t.x_advance += extents.x_advance;
+          *t.y_advance += extents.y_advance;
+
+          chars.erase(chars.begin(), end);
+          break;
+        }
+
+        if (chars.empty()) {
+          break;
+        } else if (remaining != chars.size()) {
+          continue;
+        }
+
+        char unicode[6]{'\0'};
+        utils::ucs4_to_utf8(unicode, chars.begin()->codepoint);
+        m_log.warn("Dropping unmatched character %s (U+%04x) in '%s'", unicode, chars.begin()->codepoint, t.contents);
+        utf8.erase(chars.begin()->offset, chars.begin()->length);
+        for (auto&& c : chars) {
+          c.offset -= chars.begin()->length;
+        }
+        chars.erase(chars.begin(), ++chars.begin());
+      }
+
+      return *this;
+    }
+
+    context& operator<<(shared_ptr<font>&& f) {
+      m_fonts.emplace_back(forward<decltype(f)>(f));
+      return *this;
+    }
+
+    context& save(bool save_point = false) {
+      if (save_point) {
+        m_points.emplace_front(make_pair<double, double>(0.0, 0.0));
+        position(&m_points.front().first, &m_points.front().second);
+      }
+      m_activegroups++;
+      cairo_save(m_c);
+      return *this;
+    }
+
+    context& restore(bool restore_point = false) {
+      if (!m_activegroups) {
+        throw application_error("Unmatched calls to save/restore");
+      }
+      m_activegroups--;
+      cairo_restore(m_c);
+      if (restore_point && !m_points.empty()) {
+        *this << abspos{m_points.front().first, m_points.front().first};
+        m_points.pop_front();
+      }
+      return *this;
+    }
+
+    context& paint() {
+      cairo_paint(m_c);
+      return *this;
+    }
+
+    context& paint(double alpha) {
+      cairo_paint_with_alpha(m_c, alpha);
+      return *this;
+    }
+
+    context& fill(bool preserve = false) {
+      if (preserve) {
+        cairo_fill_preserve(m_c);
+      } else {
+        cairo_fill(m_c);
+      }
+      return *this;
+    }
+
+    context& mask(cairo_pattern_t* pattern) {
+      cairo_mask(m_c, pattern);
+      return *this;
+    }
+
+    context& pop(cairo_pattern_t** pattern) {
+      *pattern = cairo_pop_group(m_c);
+      return *this;
+    }
+
+    context& push() {
+      cairo_push_group(m_c);
+      return *this;
+    }
+
+    context& destroy(cairo_pattern_t** pattern) {
+      cairo_pattern_destroy(*pattern);
+      *pattern = nullptr;
+      return *this;
+    }
+
+    context& clear(bool paint = true) {
+      cairo_save(m_c);
+      cairo_set_operator(m_c, CAIRO_OPERATOR_CLEAR);
+      if (paint) {
+        cairo_paint(m_c);
+      } else {
+        cairo_fill_preserve(m_c);
+      }
+      cairo_restore(m_c);
+      return *this;
+    }
+
+    context& clip(bool preserve = false) {
+      if (preserve) {
+        cairo_clip_preserve(m_c);
+      } else {
+        cairo_clip(m_c);
+        cairo_new_path(m_c);
+      }
+      return *this;
+    }
+
+    context& clip(const rect& r) {
+      *this << r;
+      return clip();
+    }
+
+    context& reset_clip() {
+      cairo_reset_clip(m_c);
+      return *this;
+    }
+
+    context& position(double* x, double* y = nullptr) {
+      if (cairo_has_current_point(m_c)) {
+        double x_, y_;
+        x = x != nullptr ? x : &x_;
+        y = y != nullptr ? y : &y_;
+        cairo_get_current_point(m_c, x, y);
+      }
+      return *this;
+    }
+
+    context& snap(double* x, double* y) {
+      cairo_user_to_device(m_c, x, y);
+      *x = static_cast<int>(*x + 0.5);
+      *y = static_cast<int>(*y + 0.5);
+      return *this;
+    }
+
+   protected:
+    cairo_t* m_c;
+    const logger& m_log;
+    vector<shared_ptr<font>> m_fonts;
+    std::deque<pair<double, double>> m_points;
+    int m_activegroups{0};
+  };
+}  // namespace cairo
+
+POLYBAR_NS_END
diff --git a/include/cairo/font.hpp b/include/cairo/font.hpp
new file mode 100644 (file)
index 0000000..1681a1b
--- /dev/null
@@ -0,0 +1,341 @@
+#pragma once
+
+#include <cairo/cairo-ft.h>
+
+#include "cairo/types.hpp"
+#include "cairo/utils.hpp"
+#include "common.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/math.hpp"
+#include "utils/scope.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+  /**
+   * \brief Global pointer to the Freetype library handler
+   */
+  static FT_Library g_ftlib;
+
+  /**
+   * \brief Abstract font face
+   */
+  class font {
+   public:
+    explicit font(cairo_t* cairo, double offset) : m_cairo(cairo), m_offset(offset) {}
+    virtual ~font(){};
+
+    virtual string name() const = 0;
+    virtual string file() const = 0;
+    virtual double offset() const = 0;
+    virtual double size(double dpi) const = 0;
+
+    virtual cairo_font_extents_t extents() = 0;
+
+    virtual void use() {
+      cairo_set_font_face(m_cairo, cairo_font_face_reference(m_font_face));
+    }
+
+    virtual size_t match(utils::unicode_character& character) = 0;
+    virtual size_t match(utils::unicode_charlist& charlist) = 0;
+    virtual size_t render(const string& text, double x = 0.0, double y = 0.0) = 0;
+    virtual void textwidth(const string& text, cairo_text_extents_t* extents) = 0;
+
+   protected:
+    cairo_t* m_cairo;
+    cairo_font_face_t* m_font_face{nullptr};
+    cairo_font_extents_t m_extents{};
+    double m_offset{0.0};
+  };
+
+  /**
+   * \brief Font based on fontconfig/freetype
+   */
+  class font_fc : public font {
+   public:
+    explicit font_fc(cairo_t* cairo, FcPattern* pattern, double offset, double dpi_x, double dpi_y) : font(cairo, offset), m_pattern(pattern) {
+      cairo_matrix_t fm;
+      cairo_matrix_t ctm;
+      cairo_matrix_init_scale(&fm, size(dpi_x), size(dpi_y));
+      cairo_get_matrix(m_cairo, &ctm);
+
+      auto fontface = cairo_ft_font_face_create_for_pattern(m_pattern);
+      auto opts = cairo_font_options_create();
+      m_scaled = cairo_scaled_font_create(fontface, &fm, &ctm, opts);
+      cairo_font_options_destroy(opts);
+      cairo_font_face_destroy(fontface);
+
+      auto status = cairo_scaled_font_status(m_scaled);
+      if (status != CAIRO_STATUS_SUCCESS) {
+        throw application_error(sstream() << "cairo_scaled_font_create(): " << cairo_status_to_string(status));
+      }
+
+      auto lock = make_unique<utils::ft_face_lock>(m_scaled);
+      auto face = static_cast<FT_Face>(*lock);
+
+      if (FT_Select_Charmap(face, FT_ENCODING_UNICODE) == FT_Err_Ok) {
+        return;
+      } else if (FT_Select_Charmap(face, FT_ENCODING_BIG5) == FT_Err_Ok) {
+        return;
+      } else if (FT_Select_Charmap(face, FT_ENCODING_SJIS) == FT_Err_Ok) {
+        return;
+      }
+
+      lock.reset();
+    }
+
+    ~font_fc() override {
+      if (m_scaled != nullptr) {
+        cairo_scaled_font_destroy(m_scaled);
+      }
+      if (m_pattern != nullptr) {
+        FcPatternDestroy(m_pattern);
+      }
+    }
+
+    cairo_font_extents_t extents() override {
+      cairo_scaled_font_extents(m_scaled, &m_extents);
+      return m_extents;
+    }
+
+    string name() const override {
+      return property("family");
+    }
+
+    string file() const override {
+      return property("file");
+    }
+
+    double offset() const override {
+      return m_offset;
+    }
+
+    /**
+     * Calculates the font size in pixels for the given dpi
+     *
+     * We use the two font properties size and pixelsize. size is in points and
+     * needs to be scaled with the given dpi. pixelsize is not scaled.
+     *
+     * If both size properties are 0, we fall back to a default value of 10
+     * points for scalable fonts or 10 pixel for non-scalable ones. This should
+     * only happen if both properties are purposefully set to 0
+     *
+     * For scalable fonts we try to use the size property scaled according to
+     * the dpi.
+     * For non-scalable fonts we try to use the pixelsize property as-is
+     */
+    double size(double dpi) const override {
+      bool scalable;
+      double fc_pixelsize = 0, fc_size = 0;
+
+      property(FC_SCALABLE, &scalable);
+
+      // Size in points
+      property(FC_SIZE, &fc_size);
+
+      // Size in pixels
+      property(FC_PIXEL_SIZE, &fc_pixelsize);
+
+      // Fall back to a default value if the size is 0
+      double pixelsize = fc_pixelsize == 0? 10 : fc_pixelsize;
+      double size = fc_size == 0? 10 : fc_size;
+
+      // Font size in pixels if we use the pixelsize property
+      int px_pixelsize = pixelsize + 0.5;
+
+      /*
+       * Font size in pixels if we use the size property. Since the size
+       * specifies the font size in points, this is converted to pixels
+       * according to the dpi given.
+       * One point is 1/72 inches, thus this gives us the number of 'dots'
+       * (or pixels) for this font
+       */
+      int px_size = size / 72.0 * dpi + 0.5;
+
+      if (fc_size == 0 && fc_pixelsize == 0) {
+        return scalable? px_size : px_pixelsize;
+      }
+
+      if (scalable) {
+        /*
+         * Use the point size if it's not 0. The pixelsize is only used if the
+         * size property is 0 and pixelsize is not
+         */
+        if (fc_size != 0) {
+          return px_size;
+        }
+        else {
+          return px_pixelsize;
+        }
+      } else {
+        /*
+         * Non-scalable fonts do it the other way around, here the size
+         * property is only used if pixelsize is 0 and size is not
+         */
+        if (fc_pixelsize != 0) {
+          return px_pixelsize;
+        }
+        else {
+          return px_size;
+        }
+      }
+    }
+
+    void use() override {
+      cairo_set_scaled_font(m_cairo, m_scaled);
+    }
+
+    size_t match(utils::unicode_character& character) override {
+      auto lock = make_unique<utils::ft_face_lock>(m_scaled);
+      auto face = static_cast<FT_Face>(*lock);
+      return FT_Get_Char_Index(face, character.codepoint) ? 1 : 0;
+    }
+
+    size_t match(utils::unicode_charlist& charlist) override {
+      auto lock = make_unique<utils::ft_face_lock>(m_scaled);
+      auto face = static_cast<FT_Face>(*lock);
+      size_t available_chars = 0;
+      for (auto&& c : charlist) {
+        if (FT_Get_Char_Index(face, c.codepoint)) {
+          available_chars++;
+        } else {
+          break;
+        }
+      }
+
+      return available_chars;
+    }
+
+    size_t render(const string& text, double x = 0.0, double y = 0.0) override {
+      cairo_glyph_t* glyphs{nullptr};
+      cairo_text_cluster_t* clusters{nullptr};
+      cairo_text_cluster_flags_t cf{};
+      int nglyphs = 0, nclusters = 0;
+
+      string utf8 = string(text);
+      auto status = cairo_scaled_font_text_to_glyphs(
+          m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf);
+
+      if (status != CAIRO_STATUS_SUCCESS) {
+        throw application_error(sstream() << "cairo_scaled_font_text_to_glyphs()" << cairo_status_to_string(status));
+      }
+
+      size_t bytes = 0;
+      for (int g = 0; g < nglyphs; g++) {
+        if (glyphs[g].index) {
+          bytes += clusters[g].num_bytes;
+        } else {
+          break;
+        }
+      }
+
+      if (bytes && bytes < text.size()) {
+        cairo_glyph_free(glyphs);
+        cairo_text_cluster_free(clusters);
+
+        utf8 = text.substr(0, bytes);
+        auto status = cairo_scaled_font_text_to_glyphs(
+            m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf);
+
+        if (status != CAIRO_STATUS_SUCCESS) {
+          throw application_error(sstream() << "cairo_scaled_font_text_to_glyphs()" << cairo_status_to_string(status));
+        }
+      }
+
+      if (bytes) {
+        // auto lock = make_unique<utils::device_lock>(cairo_surface_get_device(cairo_get_target(m_cairo)));
+        // if (lock.get()) {
+        //   cairo_glyph_path(m_cairo, glyphs, nglyphs);
+        // }
+
+        cairo_text_extents_t extents{};
+        cairo_scaled_font_glyph_extents(m_scaled, glyphs, nglyphs, &extents);
+        cairo_show_text_glyphs(m_cairo, utf8.c_str(), utf8.size(), glyphs, nglyphs, clusters, nclusters, cf);
+        cairo_fill(m_cairo);
+        cairo_move_to(m_cairo, x + extents.x_advance, 0.0);
+      }
+
+      cairo_glyph_free(glyphs);
+      cairo_text_cluster_free(clusters);
+
+      return bytes;
+    }
+
+    void textwidth(const string& text, cairo_text_extents_t* extents) override {
+      cairo_scaled_font_text_extents(m_scaled, text.c_str(), extents);
+    }
+
+   protected:
+    string property(string&& property) const {
+      FcChar8* file;
+      if (FcPatternGetString(m_pattern, property.c_str(), 0, &file) == FcResultMatch) {
+        return string(reinterpret_cast<char*>(file));
+      } else {
+        return "";
+      }
+    }
+
+    void property(string&& property, bool* dst) const {
+      FcBool b;
+      FcPatternGetBool(m_pattern, property.c_str(), 0, &b);
+      *dst = b;
+    }
+
+    void property(string&& property, double* dst) const {
+      FcPatternGetDouble(m_pattern, property.c_str(), 0, dst);
+    }
+
+    void property(string&& property, int* dst) const {
+      FcPatternGetInteger(m_pattern, property.c_str(), 0, dst);
+    }
+
+   private:
+    cairo_scaled_font_t* m_scaled{nullptr};
+    FcPattern* m_pattern{nullptr};
+  };
+
+  /**
+   * Match and create font from given fontconfig pattern
+   */
+  inline decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset, double dpi_x, double dpi_y) {
+    static bool fc_init{false};
+    if (!fc_init && !(fc_init = FcInit())) {
+      throw application_error("Could not load fontconfig");
+    } else if (FT_Init_FreeType(&g_ftlib) != FT_Err_Ok) {
+      throw application_error("Could not load FreeType");
+    }
+
+    static auto fc_cleanup = scope_util::make_exit_handler([] {
+      FT_Done_FreeType(g_ftlib);
+      FcFini();
+    });
+
+    auto pattern = FcNameParse((FcChar8*)fontname.c_str());
+
+    if(!pattern) {
+      logger::make().err("Could not parse font \"%s\"", fontname);
+      throw application_error("Could not parse font \"" + fontname + "\"");
+    }
+
+    FcDefaultSubstitute(pattern);
+    FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
+
+    FcResult result;
+    FcPattern* match = FcFontMatch(nullptr, pattern, &result);
+    FcPatternDestroy(pattern);
+
+    if (match == nullptr) {
+      throw application_error("Could not load font \"" + fontname + "\"");
+    }
+
+#ifdef DEBUG_FONTCONFIG
+    FcPatternPrint(match);
+#endif
+
+    return make_shared<font_fc>(cairo, match, offset, dpi_x, dpi_y);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/cairo/fwd.hpp b/include/cairo/fwd.hpp
new file mode 100644 (file)
index 0000000..1730f9d
--- /dev/null
@@ -0,0 +1,15 @@
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+  class context;
+  class surface;
+  class xcb_surface;
+  class font;
+  class font_fc;
+}
+
+POLYBAR_NS_END
diff --git a/include/cairo/surface.hpp b/include/cairo/surface.hpp
new file mode 100644 (file)
index 0000000..0c49bc7
--- /dev/null
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <cairo/cairo-xcb.h>
+
+#include "cairo/types.hpp"
+#include "common.hpp"
+#include "errors.hpp"
+#include "utils/color.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+  /**
+   * \brief Base surface
+   */
+  class surface {
+   public:
+    explicit surface(cairo_surface_t* s) : m_s(s) {}
+    virtual ~surface() {
+      cairo_surface_destroy(m_s);
+    }
+
+    operator cairo_surface_t*() const {
+      return m_s;
+    }
+
+    void flush() {
+      cairo_surface_flush(m_s);
+    }
+
+    void show(bool clear = true) {
+      if (clear) {
+        cairo_surface_show_page(m_s);
+      } else {
+        cairo_surface_copy_page(m_s);
+      }
+    }
+
+    void dirty() {
+      cairo_surface_mark_dirty(m_s);
+    }
+
+    void dirty(const rect& r) {
+      cairo_surface_mark_dirty_rectangle(m_s, r.x, r.y, r.w, r.h);
+    }
+
+    void write_png(const string& dst) {
+      auto status = cairo_surface_write_to_png(m_s, dst.c_str());
+      if (status != CAIRO_STATUS_SUCCESS) {
+        throw application_error(sstream() << "cairo_surface_write_to_png(): " << cairo_status_to_string(status));
+      }
+    }
+
+   protected:
+    cairo_surface_t* m_s;
+  };
+
+  /**
+   * \brief Surface for xcb
+   */
+  class xcb_surface : public surface {
+   public:
+    explicit xcb_surface(xcb_connection_t* c, xcb_pixmap_t p, xcb_visualtype_t* v, int w, int h)
+        : surface(cairo_xcb_surface_create(c, p, v, w, h)) {}
+
+    ~xcb_surface() override {}
+
+    void set_drawable(xcb_drawable_t d, int w, int h) {
+      cairo_surface_flush(m_s);
+      cairo_xcb_surface_set_drawable(m_s, d, w, h);
+    }
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/cairo/types.hpp b/include/cairo/types.hpp
new file mode 100644 (file)
index 0000000..d0d8197
--- /dev/null
@@ -0,0 +1,69 @@
+#pragma once
+
+#include <cairo/cairo.h>
+
+#include "common.hpp"
+#include "components/types.hpp"
+
+POLYBAR_NS
+
+enum class alignment;
+
+namespace cairo {
+  struct point {
+    double x;
+    double y;
+  };
+  struct abspos {
+    double x;
+    double y;
+    bool clear{true};
+  };
+  struct relpos {
+    double x;
+    double y;
+  };
+  struct rect {
+    double x;
+    double y;
+    double w;
+    double h;
+  };
+  struct line {
+    double x1;
+    double y1;
+    double x2;
+    double y2;
+    double w;
+  };
+  struct translate {
+    double x;
+    double y;
+  };
+  struct linear_gradient {
+    double x1;
+    double y1;
+    double x2;
+    double y2;
+    vector<rgba> steps;
+  };
+  struct rounded_corners {
+    double x;
+    double y;
+    double w;
+    double h;
+    struct radius radius;
+  };
+  struct textblock {
+    alignment align;
+    string contents;
+    int font;
+    rgba bg{};
+    cairo_operator_t bg_operator;
+    rect bg_rect;
+    double* x_advance;
+    double* y_advance;
+  };
+}  // namespace cairo
+
+POLYBAR_NS_END
diff --git a/include/cairo/utils.hpp b/include/cairo/utils.hpp
new file mode 100644 (file)
index 0000000..5ed6249
--- /dev/null
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <cairo/cairo-ft.h>
+#include <list>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+  namespace utils {
+    /**
+     * \brief RAII wrapper used acquire cairo_device_t
+     */
+    class device_lock {
+     public:
+      explicit device_lock(cairo_device_t* device);
+      ~device_lock();
+      operator bool() const;
+      operator cairo_device_t*() const;
+
+     private:
+      cairo_device_t* m_device{nullptr};
+    };
+
+    /**
+     * \brief RAII wrapper used to access the underlying
+     * FT_Face of a scaled font face
+     */
+    class ft_face_lock {
+     public:
+      explicit ft_face_lock(cairo_scaled_font_t* font);
+      ~ft_face_lock();
+      operator FT_Face() const;
+
+     private:
+      cairo_scaled_font_t* m_font;
+      FT_Face m_face;
+    };
+
+    /**
+     * \brief Unicode character containing converted codepoint
+     * and details on where its position in the source string
+     */
+    struct unicode_character {
+      explicit unicode_character();
+      unsigned long codepoint;
+      int offset;
+      int length;
+    };
+    using unicode_charlist = std::list<unicode_character>;
+
+    /**
+     * \see <cairo/cairo.h>
+     */
+    cairo_operator_t str2operator(const string& mode, cairo_operator_t fallback);
+
+    /**
+     * \brief Create a UCS-4 codepoint from a utf-8 encoded string
+     */
+    bool utf8_to_ucs4(const unsigned char* src, unicode_charlist& result_list);
+
+    /**
+     * \brief Convert a UCS-4 codepoint to a utf-8 encoded string
+     */
+    size_t ucs4_to_utf8(char* utf8, unsigned int ucs);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/common.hpp b/include/common.hpp
new file mode 100644 (file)
index 0000000..6da08c4
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+#include <functional>
+
+#include "settings.hpp"
+
+#define POLYBAR_NS    \
+  namespace polybar {
+#define POLYBAR_NS_END \
+  }
+
+#ifndef PIPE_READ
+#define PIPE_READ 0
+#endif
+#ifndef PIPE_WRITE
+#define PIPE_WRITE 1
+#endif
+
+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::to_string;
+
+using namespace std::string_literals;
+
+constexpr size_t operator"" _z(unsigned long long n) {
+  return n;
+}
+
+POLYBAR_NS_END
diff --git a/include/components/bar.hpp b/include/components/bar.hpp
new file mode 100644 (file)
index 0000000..1851bde
--- /dev/null
@@ -0,0 +1,134 @@
+#pragma once
+
+#include <cstdlib>
+#include <atomic>
+#include <mutex>
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/math.hpp"
+#include "settings.hpp"
+#include "x11/types.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+// fwd {{{
+class config;
+class connection;
+class logger;
+class parser;
+class renderer;
+class screen;
+class taskqueue;
+class tray_manager;
+// }}}
+
+/**
+ * Allows a new format for pixel sizes (like width in the bar section)
+ *
+ * The new format is X%:Z, where X is in [0, 100], and Z is any real value
+ * describing a pixel offset. The actual value is calculated by X% * max + Z
+ */
+inline double geom_format_to_pixels(std::string str, double max) {
+  size_t i;
+  if ((i = str.find(':')) != std::string::npos) {
+    std::string a = str.substr(0, i - 1);
+    std::string b = str.substr(i + 1);
+    return math_util::max<double>(0,math_util::percentage_to_value<double>(strtod(a.c_str(), nullptr), max) + strtod(b.c_str(), nullptr));
+  } else {
+    if (str.find('%') != std::string::npos) {
+      return math_util::percentage_to_value<double>(strtod(str.c_str(), nullptr), max);
+    } else {
+      return strtod(str.c_str(), nullptr);
+    }
+  }
+}
+
+class bar : public xpp::event::sink<evt::button_press, evt::expose, evt::property_notify, evt::enter_notify,
+                evt::leave_notify, evt::motion_notify, evt::destroy_notify, evt::client_message, evt::configure_notify>,
+            public signal_receiver<SIGN_PRIORITY_BAR, signals::eventqueue::start, signals::ui::tick,
+                signals::ui::shade_window, signals::ui::unshade_window, signals::ui::dim_window
+#if WITH_XCURSOR
+                , signals::ui::cursor_change
+#endif
+               > {
+ public:
+  using make_type = unique_ptr<bar>;
+  static make_type make(bool only_initialize_values = false);
+
+  explicit bar(connection&, signal_emitter&, const config&, const logger&, unique_ptr<screen>&&,
+      unique_ptr<tray_manager>&&, unique_ptr<parser>&&, unique_ptr<taskqueue>&&, bool only_initialize_values);
+  ~bar();
+
+  const bar_settings settings() const;
+
+  void parse(string&& data, bool force = false);
+
+  void hide();
+  void show();
+  void toggle();
+
+ protected:
+  void restack_window();
+  void reconfigue_window();
+  void reconfigure_geom();
+  void reconfigure_pos();
+  void reconfigure_struts();
+  void reconfigure_wm_hints();
+  void broadcast_visibility();
+
+  void handle(const evt::client_message& evt);
+  void handle(const evt::destroy_notify& evt);
+  void handle(const evt::enter_notify& evt);
+  void handle(const evt::leave_notify& evt);
+  void handle(const evt::motion_notify& evt);
+  void handle(const evt::button_press& evt);
+  void handle(const evt::expose& evt);
+  void handle(const evt::property_notify& evt);
+  void handle(const evt::configure_notify& evt);
+
+  bool on(const signals::eventqueue::start&);
+  bool on(const signals::ui::unshade_window&);
+  bool on(const signals::ui::shade_window&);
+  bool on(const signals::ui::tick&);
+  bool on(const signals::ui::dim_window&);
+#if WITH_XCURSOR
+  bool on(const signals::ui::cursor_change&);
+#endif
+
+ private:
+  connection& m_connection;
+  signal_emitter& m_sig;
+  const config& m_conf;
+  const logger& m_log;
+  unique_ptr<screen> m_screen;
+  unique_ptr<tray_manager> m_tray;
+  unique_ptr<renderer> m_renderer;
+  unique_ptr<parser> m_parser;
+  unique_ptr<taskqueue> m_taskqueue;
+
+  bar_settings m_opts{};
+
+  string m_lastinput{};
+  std::mutex m_mutex{};
+  std::atomic<bool> m_dblclicks{false};
+
+  mousebtn m_buttonpress_btn{mousebtn::NONE};
+  int m_buttonpress_pos{0};
+#if WITH_XCURSOR
+  int m_motion_pos{0};
+#endif
+
+  event_timer m_buttonpress{0L, 5L};
+  event_timer m_doubleclick{0L, 150L};
+
+  double m_anim_step{0.0};
+
+  bool m_visible{true};
+};
+
+POLYBAR_NS_END
diff --git a/include/components/builder.hpp b/include/components/builder.hpp
new file mode 100644 (file)
index 0000000..8aeeafa
--- /dev/null
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <map>
+
+#include "common.hpp"
+#include "components/types.hpp"
+POLYBAR_NS
+
+using std::map;
+
+// fwd decl
+using namespace drawtypes;
+namespace modules {
+  struct module_interface;
+}
+
+class builder {
+ public:
+  explicit builder(const bar_settings& bar);
+
+  void reset();
+  string flush();
+  void append(string text);
+  void node(string str);
+  void node(string str, int font_index);
+  void node(const label_t& label);
+  void node_repeat(const string& str, size_t n);
+  void node_repeat(const label_t& label, size_t n);
+  void offset(int pixels);
+  void space(size_t width);
+  void space();
+  void remove_trailing_space(size_t len);
+  void remove_trailing_space();
+  void font(int index);
+  void font_close();
+  void background(rgba color);
+  void background_close();
+  void color(rgba color);
+  void color_close();
+  void line_color(const rgba& color);
+  void line_color_close();
+  void overline_color(rgba color);
+  void overline_color_close();
+  void underline_color(rgba color);
+  void underline_color_close();
+  void overline(const rgba& color = rgba{});
+  void overline_close();
+  void underline(const rgba& color = rgba{});
+  void underline_close();
+  void control(controltag tag);
+  void action(mousebtn index, string action);
+  void action(mousebtn btn, const modules::module_interface& module, string action, string data);
+  void action(mousebtn index, string action, const label_t& label);
+  void action(mousebtn btn, const modules::module_interface& module, string action, string data, const label_t& label);
+  void action_close();
+
+ protected:
+
+  void tag_open(syntaxtag tag, const string& value);
+  void tag_open(attribute attr);
+  void tag_close(syntaxtag tag);
+  void tag_close(attribute attr);
+
+ private:
+  const bar_settings m_bar;
+  string m_output;
+
+  map<syntaxtag, int> m_tags{};
+  map<syntaxtag, string> m_colors{};
+  map<attribute, bool> m_attrs{};
+
+  int m_fontindex{0};
+};
+
+POLYBAR_NS_END
diff --git a/include/components/command_line.hpp b/include/components/command_line.hpp
new file mode 100644 (file)
index 0000000..a572a0f
--- /dev/null
@@ -0,0 +1,78 @@
+#pragma once
+
+#include <map>
+
+#include "common.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+namespace command_line {
+  DEFINE_ERROR(argument_error);
+  DEFINE_ERROR(value_error);
+
+  class option;
+  using choices = vector<string>;
+  using options = vector<option>;
+  using values = std::map<string, string>;
+  using posargs = vector<string>;
+
+  // class definition : option {{{
+
+  class option {
+   public:
+    string flag;
+    string flag_long;
+    string desc;
+    string token;
+    const choices values;
+
+    explicit option(string&& flag, string&& flag_long, string&& desc, string&& token = "", const choices&& c = {})
+        : flag(forward<string>(flag))
+        , flag_long(forward<string>(flag_long))
+        , desc(forward<string>(desc))
+        , token(forward<string>(token))
+        , values(forward<const choices>(c)) {}
+  };
+
+  // }}}
+  // class definition : parser {{{
+
+  class parser {
+   public:
+    using make_type = unique_ptr<parser>;
+    static make_type make(string&& scriptname, const options&& opts);
+
+    explicit parser(string&& synopsis, const options&& opts);
+
+    void usage() const;
+
+    void process_input(const vector<string>& values);
+
+    bool has(const string& option) const;
+    bool has(size_t index) const;
+    string get(string opt) const;
+    string get(size_t index) const;
+    bool compare(string opt, const string& val) const;
+    bool compare(size_t index, const string& val) const;
+
+   protected:
+    auto is_short(const string& option, const string& opt_short) const;
+    auto is_long(const string& option, const string& opt_long) const;
+    auto is(const string& option, string opt_short, string opt_long) const;
+
+    auto parse_value(string input, const string& input_next, choices values) const;
+    void parse(const string& input, const string& input_next = "");
+
+   private:
+    string m_synopsis{};
+    const options m_opts;
+    values m_optvalues{};
+    posargs m_posargs{};
+    bool m_skipnext{false};
+  };
+
+  // }}}
+}
+
+POLYBAR_NS_END
diff --git a/include/components/config.hpp b/include/components/config.hpp
new file mode 100644 (file)
index 0000000..77447b4
--- /dev/null
@@ -0,0 +1,399 @@
+#pragma once
+
+#include <unordered_map>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/env.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+#if WITH_XRM
+#include "x11/xresources.hpp"
+#endif
+
+POLYBAR_NS
+
+DEFINE_ERROR(value_error);
+DEFINE_ERROR(key_error);
+
+using valuemap_t = std::unordered_map<string, string>;
+using sectionmap_t = std::map<string, valuemap_t>;
+using file_list = vector<string>;
+
+class config {
+ public:
+  using make_type = const config&;
+  static make_type make(string path = "", string bar = "");
+
+  explicit config(const logger& logger, string&& path = "", string&& bar = "")
+      : m_log(logger), m_file(move(path)), m_barname(move(bar)){};
+
+  const string& filepath() const;
+  string section() const;
+
+  /**
+   * \brief Instruct the config to connect to the xresource manager
+   */
+  void use_xrm();
+
+  void set_sections(sectionmap_t sections);
+
+  void set_included(file_list included);
+
+  void warn_deprecated(const string& section, const string& key, string replacement) const;
+
+  /**
+   * Returns true if a given parameter exists
+   */
+  bool has(const string& section, const string& key) const {
+    auto it = m_sections.find(section);
+    return it != m_sections.end() && it->second.find(key) != it->second.end();
+  }
+
+  /**
+   * Set parameter value
+   */
+  void set(const string& section, const string& key, string&& value) {
+    auto it = m_sections.find(section);
+    if (it == m_sections.end()) {
+      valuemap_t values;
+      values[key] = value;
+      m_sections[section] = move(values);
+    }
+    auto it2 = it->second.find(key);
+    if ((it2 = it->second.find(key)) == it->second.end()) {
+      it2 = it->second.emplace_hint(it2, key, value);
+    } else {
+      it2->second = value;
+    }
+  }
+
+  /**
+   * Get parameter for the current bar by name
+   */
+  template <typename T = string>
+  T get(const string& key) const {
+    return get<T>(section(), key);
+  }
+
+  /**
+   * Get value of a variable by section and parameter name
+   */
+  template <typename T = string>
+  T get(const string& section, const string& key) const {
+    auto it = m_sections.find(section);
+    if (it == m_sections.end()) {
+      throw key_error("Missing section \"" + section + "\"");
+    }
+    if (it->second.find(key) == it->second.end()) {
+      throw key_error("Missing parameter \"" + section + "." + key + "\"");
+    }
+    return dereference<T>(section, key, it->second.at(key), convert<T>(string{it->second.at(key)}));
+  }
+
+  /**
+   * Get value of a variable by section and parameter name
+   * with a default value in case the parameter isn't defined
+   */
+  template <typename T = string>
+  T get(const string& section, const string& key, const T& default_value) const {
+    try {
+      string string_value{get<string>(section, key)};
+      T result{convert<T>(string{string_value})};
+      return dereference<T>(move(section), move(key), move(string_value), move(result));
+    } catch (const key_error& err) {
+      return default_value;
+    } catch (const value_error& err) {
+      m_log.err("Invalid value for \"%s.%s\", using default value (reason: %s)", section, key, err.what());
+      return default_value;
+    }
+  }
+
+  /**
+   * Get list of values for the current bar by name
+   */
+  template <typename T = string>
+  vector<T> get_list(const string& key) const {
+    return get_list<T>(section(), key);
+  }
+
+  /**
+   * Get list of values by section and parameter name
+   */
+  template <typename T = string>
+  vector<T> get_list(const string& section, const string& key) const {
+    vector<T> results;
+
+    while (true) {
+      try {
+        string string_value{get<string>(section, key + "-" + to_string(results.size()))};
+        T value{convert<T>(string{string_value})};
+
+        if (!string_value.empty()) {
+          results.emplace_back(dereference<T>(section, key, move(string_value), move(value)));
+        } else {
+          results.emplace_back(move(value));
+        }
+      } catch (const key_error& err) {
+        break;
+      }
+    }
+
+    if (results.empty()) {
+      throw key_error("Missing parameter \"" + section + "." + key + "-0\"");
+    }
+
+    return results;
+  }
+
+  /**
+   * Get list of values by section and parameter name
+   * with a default list in case the list isn't defined
+   */
+  template <typename T = string>
+  vector<T> get_list(const string& section, const string& key, const vector<T>& default_value) const {
+    vector<T> results;
+
+    while (true) {
+      try {
+        string string_value{get<string>(section, key + "-" + to_string(results.size()))};
+        T value{convert<T>(string{string_value})};
+
+        if (!string_value.empty()) {
+          results.emplace_back(dereference<T>(section, key, move(string_value), move(value)));
+        } else {
+          results.emplace_back(move(value));
+        }
+      } catch (const key_error& err) {
+        break;
+      } catch (const value_error& err) {
+        m_log.err("Invalid value in list \"%s.%s\", using list as-is (reason: %s)", section, key, err.what());
+        return default_value;
+      }
+    }
+
+    if (!results.empty()) {
+      return results;
+      ;
+    }
+
+    return default_value;
+  }
+
+  /**
+   * Attempt to load value using the deprecated key name. If successful show a
+   * warning message. If it fails load the value using the new key and given
+   * fallback value
+   */
+  template <typename T = string>
+  T deprecated(const string& section, const string& old, const string& newkey, const T& fallback) const {
+    try {
+      T value{get<T>(section, old)};
+      warn_deprecated(section, old, newkey);
+      return value;
+    } catch (const key_error& err) {
+      return get<T>(section, newkey, fallback);
+    }
+  }
+
+  /**
+   * \see deprecated<T>
+   */
+  template <typename T = string>
+  T deprecated_list(const string& section, const string& old, const string& newkey, const vector<T>& fallback) const {
+    try {
+      vector<T> value{get_list<T>(section, old)};
+      warn_deprecated(section, old, newkey);
+      return value;
+    } catch (const key_error& err) {
+      return get_list<T>(section, newkey, fallback);
+    }
+  }
+
+ protected:
+  void copy_inherited();
+
+  template <typename T>
+  T convert(string&& value) const;
+
+  /**
+   * Dereference value reference
+   */
+  template <typename T>
+  T dereference(const string& section, const string& key, const string& var, const T& fallback) const {
+    if (var.substr(0, 2) != "${" || var.substr(var.length() - 1) != "}") {
+      return fallback;
+    }
+
+    auto path = var.substr(2, var.length() - 3);
+    size_t pos;
+
+    if (path.compare(0, 4, "env:") == 0) {
+      return dereference_env<T>(path.substr(4));
+    } else if (path.compare(0, 5, "xrdb:") == 0) {
+      return dereference_xrdb<T>(path.substr(5));
+    } else if (path.compare(0, 5, "file:") == 0) {
+      return dereference_file<T>(path.substr(5));
+    } else if ((pos = path.find(".")) != string::npos) {
+      return dereference_local<T>(path.substr(0, pos), path.substr(pos + 1), section);
+    } else {
+      throw value_error("Invalid reference defined at \"" + section + "." + key + "\"");
+    }
+  }
+
+  /**
+   * Dereference local value reference defined using:
+   *  ${root.key}
+   *  ${root.key:fallback}
+   *  ${self.key}
+   *  ${self.key:fallback}
+   *  ${section.key}
+   *  ${section.key:fallback}
+   */
+  template <typename T>
+  T dereference_local(string section, const string& key, const string& current_section) const {
+    if (section == "BAR") {
+      m_log.warn("${BAR.key} is deprecated. Use ${root.key} instead");
+    }
+
+    section = string_util::replace(section, "BAR", this->section(), 0, 3);
+    section = string_util::replace(section, "root", this->section(), 0, 4);
+    section = string_util::replace(section, "self", current_section, 0, 4);
+
+    try {
+      string string_value{get<string>(section, key)};
+      T result{convert<T>(string{string_value})};
+      return dereference<T>(string(section), move(key), move(string_value), move(result));
+    } catch (const key_error& err) {
+      size_t pos;
+      if ((pos = key.find(':')) != string::npos) {
+        string fallback = key.substr(pos + 1);
+        m_log.info("The reference ${%s.%s} does not exist, using defined fallback value \"%s\"", section,
+            key.substr(0, pos), fallback);
+        return convert<T>(move(fallback));
+      }
+      throw value_error("The reference ${" + section + "." + key + "} does not exist (no fallback set)");
+    }
+  }
+
+  /**
+   * Dereference environment variable reference defined using:
+   *  ${env:key}
+   *  ${env:key:fallback value}
+   */
+  template <typename T>
+  T dereference_env(string var) const {
+    size_t pos;
+    string env_default;
+    /*
+     * This is needed because with only the string we cannot distinguish
+     * between an empty string as default and not default
+     */
+    bool has_default = false;
+
+    if ((pos = var.find(':')) != string::npos) {
+      env_default = var.substr(pos + 1);
+      has_default = true;
+      var.erase(pos);
+    }
+
+    if (env_util::has(var)) {
+      string env_value{env_util::get(var)};
+      m_log.info("Environment var reference ${%s} found (value=%s)", var, env_value);
+      return convert<T>(move(env_value));
+    } else if (has_default) {
+      m_log.info("Environment var ${%s} is undefined, using defined fallback value \"%s\"", var, env_default);
+      return convert<T>(move(env_default));
+    } else {
+      throw value_error(sstream() << "Environment var ${" << var << "} does not exist (no fallback set)");
+    }
+  }
+
+  /**
+   * Dereference X resource db value defined using:
+   *  ${xrdb:key}
+   *  ${xrdb:key:fallback value}
+   */
+  template <typename T>
+  T dereference_xrdb(string var) const {
+    size_t pos;
+#if not WITH_XRM
+    m_log.warn("No built-in support to dereference ${xrdb:%s} references (requires `xcb-util-xrm`)", var);
+    if ((pos = var.find(':')) != string::npos) {
+      return convert<T>(var.substr(pos + 1));
+    }
+    return convert<T>("");
+#else
+    if (!m_xrm) {
+      throw application_error("xrm is not initialized");
+    }
+
+    string fallback;
+    bool has_fallback = false;
+    if ((pos = var.find(':')) != string::npos) {
+      fallback = var.substr(pos + 1);
+      has_fallback = true;
+      var.erase(pos);
+    }
+
+    try {
+      auto value = m_xrm->require<string>(var.c_str());
+      m_log.info("Found matching X resource \"%s\" (value=%s)", var, value);
+      return convert<T>(move(value));
+    } catch (const xresource_error& err) {
+      if (has_fallback) {
+        m_log.info("%s, using defined fallback value \"%s\"", err.what(), fallback);
+        return convert<T>(move(fallback));
+      }
+      throw value_error(sstream() << err.what() << " (no fallback set)");
+    }
+#endif
+  }
+
+  /**
+   * Dereference file reference by reading its contents
+   *  ${file:/absolute/file/path}
+   *  ${file:/absolute/file/path:fallback value}
+   */
+  template <typename T>
+  T dereference_file(string var) const {
+    size_t pos;
+    string fallback;
+    bool has_fallback = false;
+    if ((pos = var.find(':')) != string::npos) {
+      fallback = var.substr(pos + 1);
+      has_fallback = true;
+      var.erase(pos);
+    }
+    var = file_util::expand(var);
+
+    if (file_util::exists(var)) {
+      m_log.info("File reference \"%s\" found", var);
+      return convert<T>(string_util::trim(file_util::contents(var), '\n'));
+    } else if (has_fallback) {
+      m_log.info("File reference \"%s\" not found, using defined fallback value \"%s\"", var, fallback);
+      return convert<T>(move(fallback));
+    } else {
+      throw value_error(sstream() << "The file \"" << var << "\" does not exist (no fallback set)");
+    }
+  }
+
+ private:
+  const logger& m_log;
+  string m_file;
+  string m_barname;
+  sectionmap_t m_sections{};
+
+  /**
+   * Absolute path of all files that were parsed in the process of parsing the
+   * config (Path of the main config file also included)
+   */
+  file_list m_included;
+#if WITH_XRM
+  unique_ptr<xresource_manager> m_xrm;
+#endif
+};
+
+POLYBAR_NS_END
diff --git a/include/components/config_parser.hpp b/include/components/config_parser.hpp
new file mode 100644 (file)
index 0000000..68f343a
--- /dev/null
@@ -0,0 +1,247 @@
+#pragma once
+
+#include <set>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "errors.hpp"
+
+POLYBAR_NS
+
+DEFINE_ERROR(parser_error);
+
+/**
+ * \brief Exception object for syntax errors
+ *
+ * Contains filepath and line number where syntax error was found
+ */
+class syntax_error : public parser_error {
+ public:
+  /**
+   * Default values are used when the thrower doesn't know the position.
+   * parse_line has to catch, set the proper values and rethrow
+   */
+  explicit syntax_error(string msg, const string& file = "", int line_no = -1)
+      : parser_error(file + ":" + to_string(line_no) + ": " + msg), msg(move(msg)) {}
+
+  const string& get_msg() {
+    return msg;
+  };
+
+ private:
+  string msg;
+};
+
+class invalid_name_error : public syntax_error {
+ public:
+  /**
+   * type is either Header or Key
+   */
+  invalid_name_error(const string& type, const string& name)
+      : syntax_error(type + " name '" + name + "' is empty or contains forbidden characters.") {}
+};
+
+/**
+ * \brief All different types a line in a config can be
+ */
+enum class line_type { KEY, HEADER, COMMENT, EMPTY, UNKNOWN };
+
+/**
+ * \brief Storage for a single config line
+ *
+ * More sanitized than the actual string of the comment line, with information
+ * about line type and structure
+ */
+struct line_t {
+  /**
+   * Whether or not this struct represents a "useful" line, a line that has
+   * any semantic significance (key-value or header line)
+   * If false all other fields are not set.
+   * Set this to false, if you want to return a line that has no effect
+   * (for example when you parse a comment line)
+   */
+  bool useful;
+
+  /**
+   * Index of the config_parser::files vector where this line is from
+   */
+  int file_index;
+  int line_no;
+
+  /**
+   * We access header, if is_header == true otherwise we access key, value
+   */
+  bool is_header;
+
+  /**
+   * Only set for header lines
+   */
+  string header;
+
+  /**
+   * Only set for key-value lines
+   */
+  string key, value;
+};
+
+class config_parser {
+ public:
+  config_parser(const logger& logger, string&& file, string&& bar);
+
+  /**
+   * \brief Performs the parsing of the main config file m_file
+   *
+   * \returns config class instance populated with the parsed config
+   *
+   * \throws syntax_error If there was any kind of syntax error
+   * \throws parser_error If aynthing else went wrong
+   */
+  config::make_type parse();
+
+ protected:
+  /**
+   * \brief Converts the `lines` vector to a proper sectionmap
+   */
+  sectionmap_t create_sectionmap();
+
+  /**
+   * \brief Parses the given file, extracts key-value pairs and section
+   *        headers and adds them onto the `lines` vector
+   *
+   * This method directly resolves `include-file` directives and checks for
+   * cyclic dependencies
+   *
+   * `file` is expected to be an already resolved absolute path
+   */
+  void parse_file(const string& file, file_list path);
+
+  /**
+   * \brief Parses the given line string to create a line_t struct
+   *
+   * We use the INI file syntax (https://en.wikipedia.org/wiki/INI_file)
+   * Whitespaces (tested with isspace()) at the beginning and end of a line are ignored
+   * Keys and section names can contain any character except for the following:
+   * - spaces
+   * - equal sign (=)
+   * - semicolon (;)
+   * - pound sign (#)
+   * - Any kind of parentheses ([](){})
+   * - colon (:)
+   * - period (.)
+   * - dollar sign ($)
+   * - backslash (\)
+   * - percent sign (%)
+   * - single and double quotes ('")
+   * So basically any character that has any kind of special meaning is prohibited.
+   *
+   * Comment lines have to start with a semicolon (;) or a pound sign (#),
+   * you cannot put a comment after another type of line.
+   *
+   * key and section names are case-sensitive.
+   *
+   * Keys are specified as `key = value`, spaces around the equal sign, as
+   * well as double quotes around the value are ignored
+   *
+   * sections are defined as [section], everything inside the square brackets is part of the name
+   *
+   * \throws syntax_error if the line isn't well formed. The syntax error
+   *         does not contain the filename or line numbers because parse_line
+   *         doesn't know about those. Whoever calls parse_line needs to
+   *         catch those exceptions and set the file path and line number
+   */
+  line_t parse_line(const string& line);
+
+  /**
+   * \brief Determines the type of a line read from a config file
+   *
+   * Expects that line is trimmed
+   * This mainly looks at the first character and doesn't check if the line is
+   * actually syntactically correct.
+   * HEADER ('['), COMMENT (';' or '#') and EMPTY (None) are uniquely
+   * identified by their first character (or lack thereof). Any line that
+   * is none of the above and contains an equal sign, is treated as KEY.
+   * All others are UNKNOWN
+   */
+  static line_type get_line_type(const string& line);
+
+  /**
+   * \brief Parse a line containing a section header and returns the header name
+   *
+   * Only assumes that the line starts with '[' and is trimmed
+   *
+   * \throws syntax_error if the line doesn't end with ']' or the header name
+   *         contains forbidden characters
+   */
+  string parse_header(const string& line);
+
+  /**
+   * \brief Parses a line containing a key-value pair and returns the key name
+   *        and the value string inside an std::pair
+   *
+   * Only assumes that the line contains '=' at least once and is trimmed
+   *
+   * \throws syntax_error if the key contains forbidden characters
+   */
+  std::pair<string, string> parse_key(const string& line);
+
+  /**
+   * \brief Name of all the files the config includes values from
+   *
+   * The line_t struct uses indices to this vector to map lines to their
+   * original files. This allows us to point the user to the exact location
+   * of errors
+   */
+  file_list m_files;
+
+ private:
+  /**
+   * \brief Checks if the given name doesn't contain any spaces or characters
+   *        in config_parser::m_forbidden_chars
+   */
+  bool is_valid_name(const string& name);
+
+  /**
+   * \brief Whether or not an xresource manager should be used
+   *
+   * Is set to true if any ${xrdb...} references are found
+   */
+  bool use_xrm{false};
+
+  const logger& m_log;
+
+  /**
+   * \brief Absolute path to the main config file
+   */
+  string m_config;
+
+  /**
+   * Is used to resolve ${root...} references
+   */
+  string m_barname;
+
+  /**
+   * \brief List of all the lines in the config (with included files)
+   *
+   * The order here matters, as we have not yet associated key-value pairs
+   * with sections
+   */
+  vector<line_t> m_lines;
+
+  /**
+   * \brief None of these characters can be used in the key and section names
+   */
+  const string m_forbidden_chars{"\"'=;#[](){}:.$\\%"};
+
+  /**
+   * \brief List of names that cannot be used as section names
+   *
+   * These strings have a special meaning inside references and so the
+   * section [self] could never be referenced.
+   *
+   * Note: BAR is deprecated
+   */
+  const std::set<string> m_reserved_section_names = {"self", "BAR", "root"};
+};
+
+POLYBAR_NS_END
diff --git a/include/components/controller.hpp b/include/components/controller.hpp
new file mode 100644 (file)
index 0000000..f70156b
--- /dev/null
@@ -0,0 +1,145 @@
+#pragma once
+
+#include <moodycamel/blockingconcurrentqueue.h>
+
+#include <thread>
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "events/types.hpp"
+#include "settings.hpp"
+#include "utils/actions.hpp"
+#include "utils/file.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+// fwd decl {{{
+
+enum class alignment;
+class bar;
+class config;
+class connection;
+class inotify_watch;
+class ipc;
+class logger;
+class signal_emitter;
+namespace modules {
+  struct module_interface;
+}  // namespace modules
+using module_t = shared_ptr<modules::module_interface>;
+using modulemap_t = std::map<alignment, vector<module_t>>;
+
+// }}}
+
+class controller
+    : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eventqueue::exit_terminate,
+          signals::eventqueue::exit_reload, signals::eventqueue::notify_change, signals::eventqueue::notify_forcechange,
+          signals::eventqueue::check_state, signals::ipc::action, signals::ipc::command, signals::ipc::hook,
+          signals::ui::ready, signals::ui::button_press, signals::ui::update_background> {
+ public:
+  using make_type = unique_ptr<controller>;
+  static make_type make(unique_ptr<ipc>&& ipc, unique_ptr<inotify_watch>&& config_watch);
+
+  explicit controller(connection&, signal_emitter&, const logger&, const config&, unique_ptr<bar>&&, unique_ptr<ipc>&&,
+      unique_ptr<inotify_watch>&&);
+  ~controller();
+
+  bool run(bool writeback, string snapshot_dst);
+
+  bool enqueue(event&& evt);
+  bool enqueue(string&& input_data);
+
+ protected:
+  void read_events();
+  void process_eventqueue();
+  void process_inputdata();
+  bool process_update(bool force);
+
+  bool on(const signals::eventqueue::notify_change& evt);
+  bool on(const signals::eventqueue::notify_forcechange& evt);
+  bool on(const signals::eventqueue::exit_terminate& evt);
+  bool on(const signals::eventqueue::exit_reload& evt);
+  bool on(const signals::eventqueue::check_state& evt);
+  bool on(const signals::ui::ready& evt);
+  bool on(const signals::ui::button_press& evt);
+  bool on(const signals::ipc::action& evt);
+  bool on(const signals::ipc::command& evt);
+  bool on(const signals::ipc::hook& evt);
+  bool on(const signals::ui::update_background& evt);
+
+ private:
+  size_t setup_modules(alignment align);
+
+  bool forward_action(const actions_util::action& cmd);
+  bool try_forward_legacy_action(const string& cmd);
+
+  connection& m_connection;
+  signal_emitter& m_sig;
+  const logger& m_log;
+  const config& m_conf;
+  unique_ptr<bar> m_bar;
+  unique_ptr<ipc> m_ipc;
+  unique_ptr<inotify_watch> m_confwatch;
+
+  array<unique_ptr<file_descriptor>, 2> m_queuefd{};
+
+  /**
+   * \brief State flag
+   */
+  std::atomic<bool> m_process_events{false};
+
+  /**
+   * \brief Destination path of generated snapshot
+   */
+  string m_snapshot_dst;
+
+  /**
+   * \brief Controls weather the output gets printed to stdout
+   */
+  bool m_writeback{false};
+
+  /**
+   * \brief Internal event queue
+   */
+  moodycamel::BlockingConcurrentQueue<event> m_queue;
+
+  /**
+   * \brief Loaded modules
+   */
+  vector<module_t> m_modules;
+
+  /**
+   * \brief Loaded modules grouped by block
+   */
+  modulemap_t m_blocks;
+
+  /**
+   * \brief Maximum number of subsequent events to swallow
+   */
+  size_t m_swallow_limit{5U};
+
+  /**
+   * \brief Time to wait for subsequent events
+   */
+  std::chrono::milliseconds m_swallow_update{10};
+
+  /**
+   * \brief Input data
+   */
+  string m_inputdata;
+
+  /**
+   * \brief Thread for the eventqueue loop
+   */
+  std::thread m_event_thread;
+
+  /**
+   * \brief Misc threads
+   */
+  vector<std::thread> m_threads;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/ipc.hpp b/include/components/ipc.hpp
new file mode 100644 (file)
index 0000000..6b09882
--- /dev/null
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "utils/concurrency.hpp"
+
+POLYBAR_NS
+
+class file_descriptor;
+class logger;
+class signal_emitter;
+
+/**
+ * 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:"};
+
+/**
+ * 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();
+
+  void receive_message();
+  int get_file_descriptor() const;
+
+ private:
+  signal_emitter& m_sig;
+  const logger& m_log;
+
+  string m_path{};
+  unique_ptr<file_descriptor> m_fd;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/logger.hpp b/include/components/logger.hpp
new file mode 100644 (file)
index 0000000..6d99507
--- /dev/null
@@ -0,0 +1,160 @@
+#pragma once
+
+#include <cstdio>
+#include <map>
+#include <string>
+#include <thread>
+#include <utility>
+
+#include "common.hpp"
+#include "settings.hpp"
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+POLYBAR_NS
+
+enum class loglevel {
+  NONE = 0,
+  ERROR,
+  WARNING,
+  NOTICE,
+  INFO,
+  TRACE,
+};
+
+class logger {
+ public:
+  using make_type = const logger&;
+  static make_type make(loglevel level = loglevel::NONE);
+
+  explicit logger(loglevel level);
+
+  static loglevel parse_verbosity(const string& name, loglevel fallback = loglevel::NONE);
+
+  void verbosity(loglevel level);
+
+#ifdef DEBUG_LOGGER  // {{{
+  template <typename... Args>
+  void trace(const string& message, Args&&... args) const {
+    output(loglevel::TRACE, message, std::forward<Args>(args)...);
+  }
+#ifdef DEBUG_LOGGER_VERBOSE
+  template <typename... Args>
+  void trace_x(const string& message, Args&&... args) const {
+    output(loglevel::TRACE, message, std::forward<Args>(args)...);
+  }
+#else
+  template <typename... Args>
+  void trace_x(Args&&...) const {}
+#endif
+#else
+  template <typename... Args>
+  void trace(Args&&...) const {}
+  template <typename... Args>
+  void trace_x(Args&&...) const {}
+#endif  // }}}
+
+  /**
+   * Output an info message
+   */
+  template <typename... Args>
+  void info(const string& message, Args&&... args) const {
+    output(loglevel::INFO, message, std::forward<Args>(args)...);
+  }
+
+  /**
+   * Output a notice
+   */
+  template <typename... Args>
+  void notice(const string& message, Args&&... args) const {
+    output(loglevel::NOTICE, message, std::forward<Args>(args)...);
+  }
+
+  /**
+   * Output a warning message
+   */
+  template <typename... Args>
+  void warn(const string& message, Args&&... args) const {
+    output(loglevel::WARNING, message, std::forward<Args>(args)...);
+  }
+
+  /**
+   * Output an error message
+   */
+  template <typename... Args>
+  void err(const string& message, Args&&... args) const {
+    output(loglevel::ERROR, message, std::forward<Args>(args)...);
+  }
+
+ protected:
+  template <typename T>
+  decltype(auto) convert(T&& arg) const {
+    return forward<T>(arg);
+  }
+
+  /**
+   * Convert string
+   */
+  const char* convert(string& arg) const;
+  const char* convert(const string& arg) const;
+
+  /**
+   * Convert thread id
+   */
+  size_t convert(std::thread::id arg) const;
+
+  /**
+   * Write the log message to the output channel
+   * if the defined verbosity level allows it
+   */
+  template <typename... Args>
+  void output(loglevel level, const string& format, Args&&... values) const {
+    if (level > m_level) {
+      return;
+    }
+
+#if defined(__clang__)  // {{{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wformat-security"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-security"
+#endif  // }}}
+
+    dprintf(m_fd, (m_prefixes.at(level) + format + m_suffixes.at(level) + "\n").c_str(), convert(values)...);
+
+#if defined(__clang__)  // {{{
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif  // }}}
+  }
+
+ private:
+  /**
+   * Logger verbosity level
+   */
+  loglevel m_level{loglevel::TRACE};
+
+  /**
+   * File descriptor used when writing the log messages
+   */
+  int m_fd{STDERR_FILENO};
+
+  /**
+   * Loglevel specific prefixes
+   */
+  std::map<loglevel, string> m_prefixes;
+
+  /**
+   * Loglevel specific suffixes
+   */
+  std::map<loglevel, string> m_suffixes;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/parser.hpp b/include/components/parser.hpp
new file mode 100644 (file)
index 0000000..c2b9ec9
--- /dev/null
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "common.hpp"
+#include "errors.hpp"
+#include "utils/color.hpp"
+
+POLYBAR_NS
+
+class signal_emitter;
+enum class attribute;
+enum class controltag;
+enum class mousebtn;
+struct bar_settings;
+
+DEFINE_ERROR(parser_error);
+DEFINE_CHILD_ERROR(unrecognized_token, parser_error);
+DEFINE_CHILD_ERROR(unrecognized_attribute, parser_error);
+DEFINE_CHILD_ERROR(unclosed_actionblocks, parser_error);
+
+class parser {
+ public:
+  using make_type = unique_ptr<parser>;
+  static make_type make();
+
+ public:
+  explicit parser(signal_emitter& emitter);
+  void parse(const bar_settings& bar, string data);
+
+ protected:
+  void codeblock(string&& data, const bar_settings& bar);
+  size_t text(string&& data);
+
+  static rgba parse_color(const string& s, rgba fallback = rgba{0});
+  static int parse_fontindex(const string& s);
+  static attribute parse_attr(const char attr);
+  mousebtn parse_action_btn(const string& data);
+  static string parse_action_cmd(string&& data);
+  static controltag parse_control(const string& data);
+
+ private:
+  signal_emitter& m_sig;
+  vector<int> m_actions;
+  unique_ptr<parser> m_parser;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/renderer.hpp b/include/components/renderer.hpp
new file mode 100644 (file)
index 0000000..8f494d9
--- /dev/null
@@ -0,0 +1,142 @@
+#pragma once
+
+#include <cairo/cairo.h>
+
+#include <bitset>
+#include <memory>
+
+#include "cairo/fwd.hpp"
+#include "common.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "x11/extensions/fwd.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+// fwd {{{
+class connection;
+class config;
+class logger;
+class background_manager;
+class bg_slice;
+// }}}
+
+using std::map;
+
+struct alignment_block {
+  cairo_pattern_t* pattern;
+  double x;
+  double y;
+};
+
+class renderer
+    : public signal_receiver<SIGN_PRIORITY_RENDERER, signals::ui::request_snapshot, signals::parser::change_background,
+          signals::parser::change_foreground, signals::parser::change_underline, signals::parser::change_overline,
+          signals::parser::change_font, signals::parser::change_alignment, signals::parser::reverse_colors,
+          signals::parser::offset_pixel, signals::parser::attribute_set, signals::parser::attribute_unset,
+          signals::parser::attribute_toggle, signals::parser::action_begin, signals::parser::action_end,
+          signals::parser::text, signals::parser::control> {
+ public:
+  using make_type = unique_ptr<renderer>;
+  static make_type make(const bar_settings& bar);
+
+  explicit renderer(connection& conn, signal_emitter& sig, const config&, const logger& logger, const bar_settings& bar,
+      background_manager& background_manager);
+  ~renderer();
+
+  xcb_window_t window() const;
+  const vector<action_block> actions() const;
+
+  void begin(xcb_rectangle_t rect);
+  void end();
+  void flush();
+
+#if 0
+  void reserve_space(edge side, unsigned int w);
+#endif
+  void fill_background();
+  void fill_overline(double x, double w);
+  void fill_underline(double x, double w);
+  void fill_borders();
+  void draw_text(const string& contents);
+
+ protected:
+  double block_x(alignment a) const;
+  double block_y(alignment a) const;
+  double block_w(alignment a) const;
+  double block_h(alignment a) const;
+
+  void flush(alignment a);
+  void highlight_clickable_areas();
+
+  bool on(const signals::ui::request_snapshot& evt);
+  bool on(const signals::parser::change_background& evt);
+  bool on(const signals::parser::change_foreground& evt);
+  bool on(const signals::parser::change_underline& evt);
+  bool on(const signals::parser::change_overline& evt);
+  bool on(const signals::parser::change_font& evt);
+  bool on(const signals::parser::change_alignment& evt);
+  bool on(const signals::parser::reverse_colors&);
+  bool on(const signals::parser::offset_pixel& evt);
+  bool on(const signals::parser::attribute_set& evt);
+  bool on(const signals::parser::attribute_unset& evt);
+  bool on(const signals::parser::attribute_toggle& evt);
+  bool on(const signals::parser::action_begin& evt);
+  bool on(const signals::parser::action_end& evt);
+  bool on(const signals::parser::text& evt);
+  bool on(const signals::parser::control& evt);
+
+ protected:
+  struct reserve_area {
+    edge side{edge::NONE};
+    unsigned int size{0U};
+  };
+
+ private:
+  connection& m_connection;
+  signal_emitter& m_sig;
+  const config& m_conf;
+  const logger& m_log;
+  const bar_settings& m_bar;
+  std::shared_ptr<bg_slice> m_background;
+
+  int m_depth{32};
+  xcb_window_t m_window;
+  xcb_colormap_t m_colormap;
+  xcb_visualtype_t* m_visual;
+  xcb_gcontext_t m_gcontext;
+  xcb_pixmap_t m_pixmap;
+
+  xcb_rectangle_t m_rect{0, 0, 0U, 0U};
+  reserve_area m_cleararea{};
+
+  // bool m_autosize{false};
+
+  unique_ptr<cairo::context> m_context;
+  unique_ptr<cairo::xcb_surface> m_surface;
+  map<alignment, alignment_block> m_blocks;
+  cairo_pattern_t* m_cornermask{};
+
+  cairo_operator_t m_comp_bg{CAIRO_OPERATOR_SOURCE};
+  cairo_operator_t m_comp_fg{CAIRO_OPERATOR_OVER};
+  cairo_operator_t m_comp_ol{CAIRO_OPERATOR_OVER};
+  cairo_operator_t m_comp_ul{CAIRO_OPERATOR_OVER};
+  cairo_operator_t m_comp_border{CAIRO_OPERATOR_OVER};
+  bool m_pseudo_transparency{false};
+
+  alignment m_align;
+  std::bitset<3> m_attr;
+  int m_font{0};
+  rgba m_bg{};
+  rgba m_fg{};
+  rgba m_ol{};
+  rgba m_ul{};
+  vector<action_block> m_actions;
+
+  bool m_fixedcenter;
+  string m_snapshot_dst;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/screen.hpp b/include/components/screen.hpp
new file mode 100644 (file)
index 0000000..bdcf30a
--- /dev/null
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "events/signal_emitter.hpp"
+#include "events/signal_fwd.hpp"
+#include "x11/extensions/randr.hpp"
+#include "x11/types.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+// fwd
+class config;
+class logger;
+class connection;
+class signal_emitter;
+
+class screen : public xpp::event::sink<evt::randr_screen_change_notify> {
+ public:
+  using make_type = unique_ptr<screen>;
+  static make_type make();
+
+  explicit screen(connection& conn, signal_emitter& emitter, const logger& logger, const config& conf);
+  ~screen();
+
+  struct size size() const {
+    return m_size;
+  }
+
+  xcb_window_t root() const {
+    return m_root;
+  }
+
+ protected:
+  void handle(const evt::randr_screen_change_notify& evt);
+
+ private:
+  connection& m_connection;
+  signal_emitter& m_sig;
+  const logger& m_log;
+  const config& m_conf;
+
+  xcb_window_t m_root;
+  xcb_window_t m_proxy{XCB_NONE};
+
+  vector<monitor_t> m_monitors;
+  struct size m_size {0U, 0U};
+  bool m_sigraised{false};
+
+  bool have_monitors_changed() const;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/taskqueue.hpp b/include/components/taskqueue.hpp
new file mode 100644 (file)
index 0000000..d693b63
--- /dev/null
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+#include <thread>
+
+#include "common.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+
+class taskqueue : non_copyable_mixin<taskqueue> {
+ public:
+  struct deferred {
+    using clock = chrono::high_resolution_clock;
+    using duration = chrono::milliseconds;
+    using timepoint = chrono::time_point<clock, duration>;
+    using callback = function<void(size_t remaining)>;
+
+    explicit deferred(string id, timepoint now, duration wait, callback fn, size_t count)
+        : id(move(id)), func(move(fn)), now(move(now)), wait(move(wait)), count(move(count)) {}
+
+    const string id;
+    const callback func;
+    timepoint now;
+    duration wait;
+    size_t count;
+  };
+
+ public:
+  using make_type = unique_ptr<taskqueue>;
+  static make_type make();
+
+  explicit taskqueue();
+  ~taskqueue();
+
+  void defer(
+      string id, deferred::duration ms, deferred::callback fn, deferred::duration offset = 0ms, size_t count = 1);
+  void defer_unique(
+      string id, deferred::duration ms, deferred::callback fn, deferred::duration offset = 0ms, size_t count = 1);
+
+  bool exist(const string& id);
+  bool purge(const string& id);
+
+ protected:
+  void tick();
+
+ private:
+  std::thread m_thread;
+  std::mutex m_lock{};
+  std::condition_variable m_hold;
+  std::atomic_bool m_active{true};
+
+  vector<unique_ptr<deferred>> m_deferred;
+};
+
+POLYBAR_NS_END
diff --git a/include/components/types.hpp b/include/components/types.hpp
new file mode 100644 (file)
index 0000000..c1baefa
--- /dev/null
@@ -0,0 +1,245 @@
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include <string>
+#include <unordered_map>
+
+#include "common.hpp"
+#include "utils/color.hpp"
+
+POLYBAR_NS
+
+// fwd {{{
+struct randr_output;
+using monitor_t = shared_ptr<randr_output>;
+
+namespace drawtypes {
+  class label;
+}
+
+using label_t = shared_ptr<drawtypes::label>;
+// }}}
+
+struct enum_hash {
+  template <typename T>
+  inline typename std::enable_if<std::is_enum<T>::value, size_t>::type operator()(T const value) const {
+    return static_cast<size_t>(value);
+  }
+};
+
+enum class edge { NONE = 0, TOP, BOTTOM, LEFT, RIGHT, ALL };
+
+enum class alignment { NONE = 0, LEFT, CENTER, RIGHT };
+
+enum class attribute { NONE = 0, UNDERLINE, OVERLINE };
+
+enum class syntaxtag {
+  NONE = 0,
+  A,  // mouse action
+  B,  // background color
+  F,  // foreground color
+  T,  // font index
+  O,  // pixel offset
+  R,  // flip colors
+  o,  // overline color
+  u,  // underline color
+  P,  // Polybar control tag
+};
+
+/**
+ * Values for polybar control tags
+ *
+ * %{P...} tags are tags for internal polybar control commands, they are not
+ * part of the public interface
+ */
+enum class controltag {
+  NONE = 0,
+  R,  // Reset all open tags (B, F, T, o, u). Used at module edges
+};
+
+enum class mousebtn { NONE = 0, LEFT, MIDDLE, RIGHT, SCROLL_UP, SCROLL_DOWN, DOUBLE_LEFT, DOUBLE_MIDDLE, DOUBLE_RIGHT };
+
+enum class strut {
+  LEFT = 0,
+  RIGHT,
+  TOP,
+  BOTTOM,
+  LEFT_START_Y,
+  LEFT_END_Y,
+  RIGHT_START_Y,
+  RIGHT_END_Y,
+  TOP_START_X,
+  TOP_END_X,
+  BOTTOM_START_X,
+  BOTTOM_END_X,
+};
+
+struct position {
+  int x{0};
+  int y{0};
+};
+
+struct size {
+  unsigned int w{1U};
+  unsigned int h{1U};
+};
+
+struct side_values {
+  unsigned int left{0U};
+  unsigned int right{0U};
+};
+
+struct edge_values {
+  unsigned int left{0U};
+  unsigned int right{0U};
+  unsigned int top{0U};
+  unsigned int bottom{0U};
+};
+
+struct radius {
+  double top{0.0};
+  double bottom{0.0};
+
+  operator bool() const {
+    return top != 0.0 || bottom != 0.0;
+  }
+};
+
+struct border_settings {
+  rgba color{0xFF000000};
+  unsigned int size{0U};
+};
+
+struct line_settings {
+  rgba color{0xFF000000};
+  unsigned int size{0U};
+};
+
+struct action {
+  mousebtn button{mousebtn::NONE};
+  string command{};
+};
+
+struct action_block : public action {
+  alignment align{alignment::NONE};
+  double start_x{0.0};
+  double end_x{0.0};
+  bool active{true};
+
+  unsigned int width() const {
+    return static_cast<unsigned int>(end_x - start_x + 0.5);
+  }
+
+  bool test(int point) const {
+    return static_cast<int>(start_x) <= point && static_cast<int>(end_x) > point;
+  }
+};
+
+struct bar_settings {
+  explicit bar_settings() = default;
+  bar_settings(const bar_settings& other) = default;
+
+  xcb_window_t window{XCB_NONE};
+  monitor_t monitor{};
+  bool monitor_strict{false};
+  bool monitor_exact{true};
+  edge origin{edge::TOP};
+  struct size size {
+    1U, 1U
+  };
+  position pos{0, 0};
+  position offset{0, 0};
+  side_values padding{0U, 0U};
+  side_values margin{0U, 0U};
+  side_values module_margin{0U, 0U};
+  edge_values strut{0U, 0U, 0U, 0U};
+
+  rgba background{0xFF000000};
+  rgba foreground{0xFFFFFFFF};
+  vector<rgba> background_steps;
+
+  line_settings underline{};
+  line_settings overline{};
+
+  std::unordered_map<edge, border_settings, enum_hash> borders{};
+
+  struct radius radius {};
+  int spacing{0};
+  label_t separator{};
+
+  string wmname{};
+  string locale{};
+
+  bool override_redirect{false};
+
+  string cursor{};
+  string cursor_click{};
+  string cursor_scroll{};
+
+  vector<action> actions{};
+
+  bool dimmed{false};
+  double dimvalue{1.0};
+
+  bool shaded{false};
+  struct size shade_size {
+    1U, 1U
+  };
+  position shade_pos{1U, 1U};
+
+  const xcb_rectangle_t inner_area(bool abspos = false) const {
+    xcb_rectangle_t rect = this->outer_area(abspos);
+
+    if (borders.find(edge::TOP) != borders.end()) {
+      rect.y += borders.at(edge::TOP).size;
+      rect.height -= borders.at(edge::TOP).size;
+    }
+    if (borders.find(edge::BOTTOM) != borders.end()) {
+      rect.height -= borders.at(edge::BOTTOM).size;
+    }
+    if (borders.find(edge::LEFT) != borders.end()) {
+      rect.x += borders.at(edge::LEFT).size;
+      rect.width -= borders.at(edge::LEFT).size;
+    }
+    if (borders.find(edge::RIGHT) != borders.end()) {
+      rect.width -= borders.at(edge::RIGHT).size;
+    }
+    return rect;
+  }
+
+  const xcb_rectangle_t outer_area(bool abspos = false) const {
+    xcb_rectangle_t rect{0, 0, 0, 0};
+    rect.width += size.w;
+    rect.height += size.h;
+
+    if (abspos) {
+      rect.x = pos.x;
+      rect.y = pos.y;
+    }
+
+    return rect;
+  }
+};
+
+struct event_timer {
+  xcb_timestamp_t event{0L};
+  xcb_timestamp_t offset{1L};
+
+  bool allow(xcb_timestamp_t time) {
+    bool pass = time >= event + offset;
+    event = time;
+    return pass;
+  };
+
+  bool deny(xcb_timestamp_t time) {
+    return !allow(time);
+  };
+};
+
+enum class output_policy {
+  REDIRECTED,
+  IGNORED,
+};
+
+POLYBAR_NS_END
diff --git a/include/debug.hpp b/include/debug.hpp
new file mode 100644 (file)
index 0000000..2ccd925
--- /dev/null
@@ -0,0 +1,51 @@
+#pragma once
+
+#ifndef DEBUG
+#error "Not a debug build..."
+#endif
+
+#include <chrono>
+#include <cstdio>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace debug_util {
+  /**
+   * Wrapper that starts tracking the time when created
+   * and reports the  duration when it goes out of scope
+   */
+  class scope_timer {
+   public:
+    using clock_t = std::chrono::high_resolution_clock;
+    using duration_t = std::chrono::milliseconds;
+
+    explicit scope_timer() : m_start(clock_t::now()) {}
+    ~scope_timer() {
+      printf("%lums\n", std::chrono::duration_cast<duration_t>(clock_t::now() - m_start).count());
+    }
+
+   private:
+    clock_t::time_point m_start;
+  };
+
+  inline unique_ptr<scope_timer> make_scope_timer() {
+    return make_unique<scope_timer>();
+  }
+
+  template <class T>
+  void execution_speed(const T& expr) noexcept {
+    auto start = std::chrono::high_resolution_clock::now();
+    expr();
+    auto finish = std::chrono::high_resolution_clock::now();
+    printf("execution speed: %lums\n", std::chrono::duration_cast<std::chrono::milliseconds>(finish - start).count());
+  }
+
+  template <class T>
+  void memory_usage(const T& object) noexcept {
+    printf("memory usage: %lub\n", sizeof(object));
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/drawtypes/animation.hpp b/include/drawtypes/animation.hpp
new file mode 100644 (file)
index 0000000..843f972
--- /dev/null
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <atomic>
+#include <chrono>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+namespace drawtypes {
+  class animation : public non_copyable_mixin<animation> {
+   public:
+    explicit animation(unsigned int framerate_ms) : m_framerate_ms(framerate_ms) {}
+    explicit animation(vector<label_t>&& frames, int framerate_ms)
+        : m_frames(move(frames))
+        , m_framerate_ms(framerate_ms)
+        , m_framecount(m_frames.size())
+        , m_frame(m_frames.size() - 1) {}
+
+    void add(label_t&& frame);
+    void increment();
+
+    label_t get() const;
+    unsigned int framerate() const;
+
+    explicit operator bool() const;
+
+   protected:
+    vector<label_t> m_frames;
+
+    unsigned int m_framerate_ms = 1000;
+    size_t m_framecount = 0;
+    std::atomic_size_t m_frame{0_z};
+  };
+
+  using animation_t = shared_ptr<animation>;
+
+  animation_t load_animation(
+      const config& conf, const string& section, string name = "animation", bool required = true);
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/include/drawtypes/iconset.hpp b/include/drawtypes/iconset.hpp
new file mode 100644 (file)
index 0000000..075d4b8
--- /dev/null
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <map>
+
+#include "common.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  class iconset : public non_copyable_mixin<iconset> {
+   public:
+    void add(string id, label_t&& icon);
+    bool has(const string& id);
+    label_t get(const string& id, const string& fallback_id = "", bool fuzzy_match = false);
+    operator bool();
+
+   protected:
+    std::map<string, label_t> m_icons;
+  };
+
+  using iconset_t = shared_ptr<iconset>;
+}
+
+POLYBAR_NS_END
diff --git a/include/drawtypes/label.hpp b/include/drawtypes/label.hpp
new file mode 100644 (file)
index 0000000..144446a
--- /dev/null
@@ -0,0 +1,86 @@
+#pragma once
+
+#include <cassert>
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  struct token {
+    string token;
+    size_t min{0_z};
+    size_t max{0_z};
+    string suffix{""s};
+    bool zpad{false};
+  };
+
+  class label : public non_copyable_mixin<label> {
+   public:
+    rgba m_foreground{};
+    rgba m_background{};
+    rgba m_underline{};
+    rgba m_overline{};
+    int m_font{0};
+    side_values m_padding{0U, 0U};
+    side_values m_margin{0U, 0U};
+
+    size_t m_minlen{0};
+    /*
+     * If m_ellipsis is true, m_maxlen MUST be larger or equal to the length of
+     * the ellipsis (3), everything else is a programming error
+     *
+     * load_label should take care of this, but be aware, if you are creating
+     * labels in a different way.
+     */
+    size_t m_maxlen{0_z};
+    alignment m_alignment{alignment::LEFT};
+    bool m_ellipsis{true};
+
+    explicit label(string text, int font) : m_font(font), m_text(text), m_tokenized(m_text) {}
+    explicit label(string text, rgba foreground = rgba{}, rgba background = rgba{}, rgba underline = rgba{},
+        rgba overline = rgba{}, int font = 0, struct side_values padding = {0U, 0U},
+        struct side_values margin = {0U, 0U}, int minlen = 0, size_t maxlen = 0_z,
+        alignment label_alignment = alignment::LEFT, bool ellipsis = true, vector<token>&& tokens = {})
+        : m_foreground(foreground)
+        , m_background(background)
+        , m_underline(underline)
+        , m_overline(overline)
+        , m_font(font)
+        , m_padding(padding)
+        , m_margin(margin)
+        , m_minlen(minlen)
+        , m_maxlen(maxlen)
+        , m_alignment(label_alignment)
+        , m_ellipsis(ellipsis)
+        , m_text(text)
+        , m_tokenized(m_text)
+        , m_tokens(forward<vector<token>>(tokens)) {
+      assert(!m_ellipsis || (m_maxlen == 0 || m_maxlen >= 3));
+    }
+
+    string get() const;
+    operator bool();
+    label_t clone();
+    void clear();
+    void reset_tokens();
+    void reset_tokens(const string& tokenized);
+    bool has_token(const string& token) const;
+    void replace_token(const string& token, string replacement);
+    void replace_defined_values(const label_t& label);
+    void copy_undefined(const label_t& label);
+
+   private:
+    string m_text{};
+    string m_tokenized{};
+    const vector<token> m_tokens{};
+  };
+
+  label_t load_label(const config& conf, const string& section, string name, bool required = true, string def = ""s);
+  label_t load_optional_label(const config& conf, string section, string name, string def = ""s);
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/include/drawtypes/progressbar.hpp b/include/drawtypes/progressbar.hpp
new file mode 100644 (file)
index 0000000..0d10c9b
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "common.hpp"
+#include "components/builder.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  class progressbar : public non_copyable_mixin<progressbar> {
+   public:
+    explicit progressbar(const bar_settings& bar, int width, string format);
+
+    void set_fill(label_t&& fill);
+    void set_empty(label_t&& empty);
+    void set_indicator(label_t&& indicator);
+    void set_gradient(bool mode);
+    void set_colors(vector<rgba>&& colors);
+
+    string output(float percentage);
+
+   protected:
+    void fill(unsigned int perc, unsigned int fill_width);
+
+   private:
+    unique_ptr<builder> m_builder;
+    vector<rgba> m_colors;
+    string m_format;
+    unsigned int m_width;
+    unsigned int m_colorstep = 1;
+    bool m_gradient = false;
+
+    label_t m_fill;
+    label_t m_empty;
+    label_t m_indicator;
+  };
+
+  using progressbar_t = shared_ptr<progressbar>;
+
+  progressbar_t load_progressbar(const bar_settings& bar, const config& conf, const string& section, string name);
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/include/drawtypes/ramp.hpp b/include/drawtypes/ramp.hpp
new file mode 100644 (file)
index 0000000..fb94c19
--- /dev/null
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  class ramp : public non_copyable_mixin<ramp> {
+   public:
+    explicit ramp() = default;
+    explicit ramp(vector<label_t>&& icons) : m_icons(forward<decltype(icons)>(icons)) {}
+
+    void add(label_t&& icon);
+    label_t get(size_t index);
+    label_t get_by_percentage(float percentage);
+    label_t get_by_percentage_with_borders(float percentage);
+    operator bool();
+
+   protected:
+    vector<label_t> m_icons;
+  };
+
+  using ramp_t = shared_ptr<ramp>;
+
+  ramp_t load_ramp(const config& conf, const string& section, string name, bool required = true);
+}
+
+POLYBAR_NS_END
diff --git a/include/errors.hpp b/include/errors.hpp
new file mode 100644 (file)
index 0000000..2ca554b
--- /dev/null
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <cerrno>
+#include <cstring>
+#include <stdexcept>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+using std::strerror;
+using std::exception;
+using std::runtime_error;
+
+class application_error : public runtime_error {
+ public:
+  explicit application_error(const string& message, int code = 0) : runtime_error(message), code(code) {}
+  virtual ~application_error() {}
+  int code{0};
+};
+
+class system_error : public application_error {
+ public:
+  explicit system_error() : application_error(strerror(errno), errno) {}
+  explicit system_error(const string& message)
+      : application_error(message + " (reason: " + strerror(errno) + ")", errno) {}
+  virtual ~system_error() {}
+};
+
+#define DEFINE_CHILD_ERROR(error, parent) \
+  class error : public parent {           \
+    using parent::parent;                 \
+  }
+#define DEFINE_ERROR(error) DEFINE_CHILD_ERROR(error, application_error)
+
+POLYBAR_NS_END
diff --git a/include/events/signal.hpp b/include/events/signal.hpp
new file mode 100644 (file)
index 0000000..dde362b
--- /dev/null
@@ -0,0 +1,181 @@
+#pragma once
+
+#include "common.hpp"
+#include "components/ipc.hpp"
+#include "components/parser.hpp"
+#include "components/types.hpp"
+#include "utils/functional.hpp"
+
+POLYBAR_NS
+
+namespace signals {
+  namespace detail {
+    class signal {
+     public:
+      explicit signal() = default;
+      virtual ~signal() {}
+      virtual size_t size() const = 0;
+    };
+
+    template <typename Derived>
+    class base_signal : public signal {
+     public:
+      using base_type = base_signal<Derived>;
+
+      explicit base_signal() = default;
+      virtual ~base_signal() {}
+
+      virtual size_t size() const override {
+        return sizeof(Derived);
+      };
+    };
+
+    template <typename Derived, typename ValueType>
+    class value_signal : public base_signal<Derived> {
+     public:
+      using base_type = value_signal<Derived, ValueType>;
+
+      explicit value_signal(void* data) : m_ptr(data) {}
+      explicit value_signal(ValueType&& data) : m_ptr(&data) {}
+
+      virtual ~value_signal() {}
+
+      inline ValueType cast() const {
+        return *static_cast<ValueType*>(m_ptr);
+      }
+
+     private:
+      void* m_ptr;
+    };
+  }  // namespace detail
+
+  namespace eventqueue {
+    struct start : public detail::base_signal<start> {
+      using base_type::base_type;
+    };
+    struct exit_terminate : public detail::base_signal<exit_terminate> {
+      using base_type::base_type;
+    };
+    struct exit_reload : public detail::base_signal<exit_reload> {
+      using base_type::base_type;
+    };
+    struct notify_change : public detail::base_signal<notify_change> {
+      using base_type::base_type;
+    };
+    struct notify_forcechange : public detail::base_signal<notify_forcechange> {
+      using base_type::base_type;
+    };
+    struct check_state : public detail::base_signal<check_state> {
+      using base_type::base_type;
+    };
+  }  // namespace eventqueue
+
+  namespace ipc {
+    struct command : public detail::value_signal<command, string> {
+      using base_type::base_type;
+    };
+    struct hook : public detail::value_signal<hook, string> {
+      using base_type::base_type;
+    };
+    struct action : public detail::value_signal<action, string> {
+      using base_type::base_type;
+    };
+  }  // namespace ipc
+
+  namespace ui {
+    struct ready : public detail::base_signal<ready> {
+      using base_type::base_type;
+    };
+    struct changed : public detail::base_signal<changed> {
+      using base_type::base_type;
+    };
+    struct tick : public detail::base_signal<tick> {
+      using base_type::base_type;
+    };
+    struct button_press : public detail::value_signal<button_press, string> {
+      using base_type::base_type;
+    };
+    struct cursor_change : public detail::value_signal<cursor_change, string> {
+      using base_type::base_type;
+    };
+    struct visibility_change : public detail::value_signal<visibility_change, bool> {
+      using base_type::base_type;
+    };
+    struct dim_window : public detail::value_signal<dim_window, double> {
+      using base_type::base_type;
+    };
+    struct shade_window : public detail::base_signal<shade_window> {
+      using base_type::base_type;
+    };
+    struct unshade_window : public detail::base_signal<unshade_window> {
+      using base_type::base_type;
+    };
+    struct request_snapshot : public detail::value_signal<request_snapshot, string> {
+      using base_type::base_type;
+    };
+    /// emitted whenever the desktop background slice changes
+    struct update_background : public detail::base_signal<update_background> {
+      using base_type::base_type;
+    };
+    /// emitted when the bar geometry changes (such as position of the bar on the screen)
+    struct update_geometry : public detail::base_signal<update_geometry> {
+      using base_type::base_type;
+    };
+  }  // namespace ui
+
+  namespace ui_tray {
+    struct mapped_clients : public detail::value_signal<mapped_clients, unsigned int> {
+      using base_type::base_type;
+    };
+  }  // namespace ui_tray
+
+  namespace parser {
+    struct change_background : public detail::value_signal<change_background, rgba> {
+      using base_type::base_type;
+    };
+    struct change_foreground : public detail::value_signal<change_foreground, rgba> {
+      using base_type::base_type;
+    };
+    struct change_underline : public detail::value_signal<change_underline, rgba> {
+      using base_type::base_type;
+    };
+    struct change_overline : public detail::value_signal<change_overline, rgba> {
+      using base_type::base_type;
+    };
+    struct change_font : public detail::value_signal<change_font, int> {
+      using base_type::base_type;
+    };
+    struct change_alignment : public detail::value_signal<change_alignment, alignment> {
+      using base_type::base_type;
+    };
+    struct reverse_colors : public detail::base_signal<reverse_colors> {
+      using base_type::base_type;
+    };
+    struct offset_pixel : public detail::value_signal<offset_pixel, int> {
+      using base_type::base_type;
+    };
+    struct attribute_set : public detail::value_signal<attribute_set, attribute> {
+      using base_type::base_type;
+    };
+    struct attribute_unset : public detail::value_signal<attribute_unset, attribute> {
+      using base_type::base_type;
+    };
+    struct attribute_toggle : public detail::value_signal<attribute_toggle, attribute> {
+      using base_type::base_type;
+    };
+    struct action_begin : public detail::value_signal<action_begin, action> {
+      using base_type::base_type;
+    };
+    struct action_end : public detail::value_signal<action_end, mousebtn> {
+      using base_type::base_type;
+    };
+    struct text : public detail::value_signal<text, string> {
+      using base_type::base_type;
+    };
+    struct control : public detail::value_signal<control, controltag> {
+      using base_type::base_type;
+    };
+  }  // namespace parser
+}  // namespace signals
+
+POLYBAR_NS_END
diff --git a/include/events/signal_emitter.hpp b/include/events/signal_emitter.hpp
new file mode 100644 (file)
index 0000000..10c03ce
--- /dev/null
@@ -0,0 +1,107 @@
+#pragma once
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "events/signal_receiver.hpp"
+
+POLYBAR_NS
+
+/**
+ * \brief Holds all signal receivers attached to the emitter
+ */
+extern signal_receivers_t g_signal_receivers;
+
+/**
+ * Wrapper used to delegate emitted signals
+ * to attached signal receivers
+ */
+class signal_emitter {
+ public:
+  using make_type = signal_emitter&;
+  static make_type make();
+
+  explicit signal_emitter() = default;
+  virtual ~signal_emitter() {}
+
+  template <typename Signal>
+  bool emit(const Signal& sig) {
+    try {
+      if (g_signal_receivers.find(id<Signal>()) != g_signal_receivers.end()) {
+        for (auto&& item : g_signal_receivers.at(id<Signal>())) {
+          if (item.second->on(sig)) {
+            return true;
+          }
+        }
+      }
+    } catch (const std::exception& e) {
+      logger::make().err("Signal receiver raised an exception: %s", e.what());
+    }
+
+    return false;
+  }
+
+  template <typename Signal, typename Next, typename... Signals>
+  bool emit(const Signal& sig) const {
+    return emit<Signal>(sig) || emit<Next, Signals...>(sig);
+  }
+
+  template <int Priority, typename Signal, typename... Signals>
+  void attach(signal_receiver<Priority, Signal, Signals...>* s) {
+    attach<signal_receiver<Priority, Signal, Signals...>, Signal, Signals...>(s);
+  }
+
+  template <int Priority, typename Signal, typename... Signals>
+  void detach(signal_receiver<Priority, Signal, Signals...>* s) {
+    detach<signal_receiver<Priority, Signal, Signals...>, Signal, Signals...>(s);
+  }
+
+ protected:
+  template <typename Signal>
+  std::type_index id() const {
+    return typeid(Signal);
+  }
+
+  template <typename Receiver, typename Signal>
+  void attach(Receiver* s) {
+    attach(s, id<Signal>());
+  }
+
+  template <typename Receiver, typename Signal, typename Next, typename... Signals>
+  void attach(Receiver* s) {
+    attach(s, id<Signal>());
+    attach<Receiver, Next, Signals...>(s);
+  }
+
+  void attach(signal_receiver_interface* s, std::type_index id) {
+    g_signal_receivers[id].emplace(s->priority(), s);
+  }
+
+  template <typename Receiver, typename Signal>
+  void detach(Receiver* s) {
+    detach(s, id<Signal>());
+  }
+
+  template <typename Receiver, typename Signal, typename Next, typename... Signals>
+  void detach(Receiver* s) {
+    detach(s, id<Signal>());
+    detach<Receiver, Next, Signals...>(s);
+  }
+
+  void detach(signal_receiver_interface* d, std::type_index id) {
+    try {
+      auto& prio_map = g_signal_receivers.at(id);
+      const auto& prio_sink_pair = prio_map.equal_range(d->priority());
+
+      for (auto it = prio_sink_pair.first; it != prio_sink_pair.second;) {
+        if (d == it->second) {
+          it = prio_map.erase(it);
+        } else {
+          ++it;
+        }
+      }
+    } catch (...) {
+    }
+  }
+};
+
+POLYBAR_NS_END
diff --git a/include/events/signal_fwd.hpp b/include/events/signal_fwd.hpp
new file mode 100644 (file)
index 0000000..f2efba9
--- /dev/null
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class signal_emitter;
+class signal_receiver_interface;
+template <int Priority, typename Signal, typename... Signals>
+class signal_receiver;
+
+namespace signals {
+  namespace detail {
+    class signal;
+  }
+
+  namespace eventqueue {
+    struct start;
+    struct exit_terminate;
+    struct exit_reload;
+    struct notify_change;
+    struct notify_forcechange;
+    struct check_state;
+  }  // namespace eventqueue
+  namespace ipc {
+    struct command;
+    struct hook;
+    struct action;
+  }  // namespace ipc
+  namespace ui {
+    struct ready;
+    struct changed;
+    struct tick;
+    struct button_press;
+    struct cursor_change;
+    struct visibility_change;
+    struct dim_window;
+    struct shade_window;
+    struct unshade_window;
+    struct request_snapshot;
+    struct update_background;
+    struct update_geometry;
+  }  // namespace ui
+  namespace ui_tray {
+    struct mapped_clients;
+  }
+  namespace parser {
+    struct change_background;
+    struct change_foreground;
+    struct change_underline;
+    struct change_overline;
+    struct change_font;
+    struct change_alignment;
+    struct reverse_colors;
+    struct offset_pixel;
+    struct attribute_set;
+    struct attribute_unset;
+    struct attribute_toggle;
+    struct action_begin;
+    struct action_end;
+    struct text;
+    struct control;
+  }  // namespace parser
+}  // namespace signals
+
+POLYBAR_NS_END
diff --git a/include/events/signal_receiver.hpp b/include/events/signal_receiver.hpp
new file mode 100644 (file)
index 0000000..9f2a805
--- /dev/null
@@ -0,0 +1,52 @@
+#pragma once
+
+#include <map>
+#include <unordered_map>
+#include <typeindex>
+#include <typeinfo>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class signal_receiver_interface {
+ public:
+  using prio = int;
+  using prio_map = std::multimap<prio, signal_receiver_interface*>;
+  virtual ~signal_receiver_interface() {}
+  virtual prio priority() const = 0;
+  template <typename Signal>
+  bool on(const Signal& signal);
+};
+
+template <typename Signal>
+class signal_receiver_impl {
+ public:
+  virtual ~signal_receiver_impl() {}
+  virtual bool on(const Signal&) = 0;
+};
+
+template <typename Signal>
+bool signal_receiver_interface::on(const Signal& s) {
+  auto event_sink = dynamic_cast<signal_receiver_impl<Signal>*>(this);
+
+  if (event_sink != nullptr && event_sink->on(s)) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+template <int Priority, typename Signal, typename... Signals>
+class signal_receiver : public signal_receiver_interface,
+                        public signal_receiver_impl<Signal>,
+                        public signal_receiver_impl<Signals>... {
+ public:
+  prio priority() const {
+    return Priority;
+  }
+};
+
+using signal_receivers_t = std::unordered_map<std::type_index, signal_receiver_interface::prio_map>;
+
+POLYBAR_NS_END
diff --git a/include/events/types.hpp b/include/events/types.hpp
new file mode 100644 (file)
index 0000000..22e4fe1
--- /dev/null
@@ -0,0 +1,59 @@
+#pragma once
+
+#include <cstdint>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+enum class event_type {
+  NONE = 0,
+  UPDATE,
+  CHECK,
+  INPUT,
+  QUIT,
+};
+
+struct event {
+  int type{0};
+  bool flag{false};
+};
+
+namespace {
+  inline bool operator==(int id, event_type type) {
+    return id == static_cast<int>(type);
+  }
+  inline bool operator!=(int id, event_type type) {
+    return !(id == static_cast<int>(type));
+  }
+
+  /**
+   * Create QUIT event
+   */
+  inline event make_quit_evt(bool reload = false) {
+    return event{static_cast<int>(event_type::QUIT), reload};
+  }
+
+  /**
+   * Create UPDATE event
+   */
+  inline event make_update_evt(bool force = false) {
+    return event{static_cast<int>(event_type::UPDATE), force};
+  }
+
+  /**
+   * Create INPUT event
+   */
+  inline event make_input_evt() {
+    return event{static_cast<int>(event_type::INPUT)};
+  }
+
+  /**
+   * Create CHECK event
+   */
+  inline event make_check_evt() {
+    return event{static_cast<int>(event_type::CHECK)};
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/modules/alsa.hpp b/include/modules/alsa.hpp
new file mode 100644 (file)
index 0000000..2190338
--- /dev/null
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "modules/meta/event_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+// fwd
+namespace alsa {
+  class mixer;
+  class control;
+}  // namespace alsa
+
+namespace modules {
+  enum class mixer { NONE = 0, MASTER, SPEAKER, HEADPHONE };
+  enum class control { NONE = 0, HEADPHONE };
+
+  using mixer_t = shared_ptr<alsa::mixer>;
+  using control_t = shared_ptr<alsa::control>;
+
+  class alsa_module : public event_module<alsa_module> {
+   public:
+    explicit alsa_module(const bar_settings&, string);
+
+    void teardown();
+    bool has_event();
+    bool update();
+    string get_format() const;
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/alsa";
+
+    static constexpr auto EVENT_INC = "inc";
+    static constexpr auto EVENT_DEC = "dec";
+    static constexpr auto EVENT_TOGGLE = "toggle";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr auto FORMAT_VOLUME = "format-volume";
+    static constexpr auto FORMAT_MUTED = "format-muted";
+
+    static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
+    static constexpr auto TAG_RAMP_HEADPHONES = "<ramp-headphones>";
+    static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
+    static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
+    static constexpr auto TAG_LABEL_MUTED = "<label-muted>";
+
+    progressbar_t m_bar_volume;
+    ramp_t m_ramp_volume;
+    ramp_t m_ramp_headphones;
+    label_t m_label_volume;
+    label_t m_label_muted;
+
+    map<mixer, mixer_t> m_mixer;
+    map<control, control_t> m_ctrl;
+    int m_headphoneid{0};
+    bool m_mapped{false};
+    int m_interval{5};
+    atomic<bool> m_muted{false};
+    atomic<bool> m_headphones{false};
+    atomic<int> m_volume{0};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp
new file mode 100644 (file)
index 0000000..779821b
--- /dev/null
@@ -0,0 +1,56 @@
+#pragma once
+
+#include "components/config.hpp"
+#include "modules/meta/inotify_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class backlight_module : public inotify_module<backlight_module> {
+   public:
+    struct brightness_handle {
+      void filepath(const string& path);
+      float read() const;
+
+     private:
+      string m_path;
+    };
+
+    string get_output();
+
+   public:
+    explicit backlight_module(const bar_settings&, string);
+
+    void idle();
+    bool on_event(inotify_event* event);
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/backlight";
+
+    static constexpr const char* EVENT_INC = "inc";
+    static constexpr const char* EVENT_DEC = "dec";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr auto TAG_LABEL = "<label>";
+    static constexpr auto TAG_BAR = "<bar>";
+    static constexpr auto TAG_RAMP = "<ramp>";
+
+    ramp_t m_ramp;
+    label_t m_label;
+    progressbar_t m_progressbar;
+    string m_path_backlight;
+    float m_max_brightness;
+    bool m_scroll{false};
+
+    brightness_handle m_val;
+    brightness_handle m_max;
+
+    int m_percentage = 0;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp
new file mode 100644 (file)
index 0000000..07fba08
--- /dev/null
@@ -0,0 +1,113 @@
+#pragma once
+
+#include "common.hpp"
+#include "modules/meta/inotify_module.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class battery_module : public inotify_module<battery_module> {
+   public:
+    enum class state {
+      NONE = 0,
+      CHARGING,
+      DISCHARGING,
+      FULL,
+    };
+
+    enum class value {
+      NONE = 0,
+      ADAPTER,
+      CAPACITY,
+      CAPACITY_MAX,
+      VOLTAGE,
+      RATE,
+    };
+
+    template <typename ReturnType>
+    struct value_reader {
+      using return_type = ReturnType;
+
+      explicit value_reader() = default;
+      explicit value_reader(function<ReturnType()>&& fn) : m_fn(forward<decltype(fn)>(fn)) {}
+
+      ReturnType read() const {
+        return m_fn();
+      }
+
+     private:
+      const function<ReturnType()> m_fn;
+    };
+
+    using state_reader = mutex_wrapper<value_reader<bool /* is_charging */>>;
+    using capacity_reader = mutex_wrapper<value_reader<int /* percentage */>>;
+    using rate_reader = mutex_wrapper<value_reader<unsigned long /* seconds */>>;
+    using consumption_reader = mutex_wrapper<value_reader<string /* watts */>>;
+
+   public:
+    explicit battery_module(const bar_settings&, string);
+
+    void start();
+    void teardown();
+    void idle();
+    bool on_event(inotify_event* event);
+    string get_format() const;
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/battery";
+
+   protected:
+    state current_state();
+    int current_percentage();
+    int clamp_percentage(int percentage, state state) const;
+    string current_time();
+    string current_consumption();
+    void subthread();
+
+   private:
+    static constexpr const char* FORMAT_CHARGING{"format-charging"};
+    static constexpr const char* FORMAT_DISCHARGING{"format-discharging"};
+    static constexpr const char* FORMAT_FULL{"format-full"};
+
+    static constexpr const char* TAG_ANIMATION_CHARGING{"<animation-charging>"};
+    static constexpr const char* TAG_ANIMATION_DISCHARGING{"<animation-discharging>"};
+    static constexpr const char* TAG_BAR_CAPACITY{"<bar-capacity>"};
+    static constexpr const char* TAG_RAMP_CAPACITY{"<ramp-capacity>"};
+    static constexpr const char* TAG_LABEL_CHARGING{"<label-charging>"};
+    static constexpr const char* TAG_LABEL_DISCHARGING{"<label-discharging>"};
+    static constexpr const char* TAG_LABEL_FULL{"<label-full>"};
+
+    static const size_t SKIP_N_UNCHANGED{3_z};
+
+    unique_ptr<state_reader> m_state_reader;
+    unique_ptr<capacity_reader> m_capacity_reader;
+    unique_ptr<rate_reader> m_rate_reader;
+    unique_ptr<consumption_reader> m_consumption_reader;
+
+    label_t m_label_charging;
+    label_t m_label_discharging;
+    label_t m_label_full;
+    animation_t m_animation_charging;
+    animation_t m_animation_discharging;
+    progressbar_t m_bar_capacity;
+    ramp_t m_ramp_capacity;
+
+    string m_fstate;
+    string m_fcapnow;
+    string m_fcapfull;
+    string m_frate;
+    string m_fvoltage;
+
+    state m_state{state::DISCHARGING};
+    int m_percentage{0};
+
+    int m_fullat{100};
+    string m_timeformat;
+    size_t m_unchanged{SKIP_N_UNCHANGED};
+    chrono::duration<double> m_interval{};
+    chrono::system_clock::time_point m_lastpoll;
+    thread m_subthread;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/bspwm.hpp b/include/modules/bspwm.hpp
new file mode 100644 (file)
index 0000000..7bf1a46
--- /dev/null
@@ -0,0 +1,97 @@
+#pragma once
+
+#include "modules/meta/event_module.hpp"
+#include "utils/bspwm.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class bspwm_module : public event_module<bspwm_module> {
+   public:
+    enum class state {
+      NONE = 0U,
+      EMPTY,
+      OCCUPIED,
+      FOCUSED,
+      URGENT,
+      DIMMED,  // used when the monitor is out of focus
+    };
+
+    enum class mode {
+      NONE = 0U,
+      LAYOUT_MONOCLE,
+      LAYOUT_TILED,
+      STATE_FULLSCREEN,
+      STATE_FLOATING,
+      STATE_PSEUDOTILED,
+      NODE_LOCKED,
+      NODE_STICKY,
+      NODE_PRIVATE,
+      NODE_MARKED
+    };
+
+    struct bspwm_monitor {
+      vector<pair<unsigned int, label_t>> workspaces;
+      vector<label_t> modes;
+      label_t label;
+      string name;
+      bool focused{false};
+    };
+
+   public:
+    explicit bspwm_module(const bar_settings&, string);
+
+    void stop();
+    bool has_event();
+    bool update();
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/bspwm";
+
+    static constexpr auto EVENT_FOCUS = "focus";
+    static constexpr auto EVENT_NEXT = "next";
+    static constexpr auto EVENT_PREV = "prev";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    bool handle_status(string& data);
+
+    static constexpr auto DEFAULT_ICON = "ws-icon-default";
+    static constexpr auto DEFAULT_LABEL = "%icon% %name%";
+    static constexpr auto DEFAULT_MONITOR_LABEL = "%name%";
+
+    static constexpr auto TAG_LABEL_MONITOR = "<label-monitor>";
+    static constexpr auto TAG_LABEL_STATE = "<label-state>";
+    static constexpr auto TAG_LABEL_MODE = "<label-mode>";
+
+    bspwm_util::connection_t m_subscriber;
+
+    vector<unique_ptr<bspwm_monitor>> m_monitors;
+
+    map<mode, label_t> m_modelabels;
+    map<unsigned int, label_t> m_statelabels;
+    label_t m_monitorlabel;
+    iconset_t m_icons;
+
+    /**
+     * Separator that is inserted in between workspaces
+     */
+    label_t m_labelseparator;
+
+    bool m_click{true};
+    bool m_scroll{true};
+    bool m_revscroll{true};
+    bool m_pinworkspaces{true};
+    bool m_inlinemode{false};
+    string_util::hash_type m_hash{0U};
+    bool m_fuzzy_match{false};
+
+    // used while formatting output
+    size_t m_index{0U};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/counter.hpp b/include/modules/counter.hpp
new file mode 100644 (file)
index 0000000..a386185
--- /dev/null
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class counter_module : public timer_module<counter_module> {
+   public:
+    explicit counter_module(const bar_settings&, string);
+
+    bool update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/counter";
+
+   private:
+    static constexpr auto TAG_COUNTER = "<counter>";
+
+    int m_counter{0};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/cpu.hpp b/include/modules/cpu.hpp
new file mode 100644 (file)
index 0000000..003c9fb
--- /dev/null
@@ -0,0 +1,53 @@
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  struct cpu_time {
+    unsigned long long user;
+    unsigned long long nice;
+    unsigned long long system;
+    unsigned long long idle;
+    unsigned long long steal;
+    unsigned long long total;
+  };
+
+  using cpu_time_t = unique_ptr<cpu_time>;
+
+  class cpu_module : public timer_module<cpu_module> {
+   public:
+    explicit cpu_module(const bar_settings&, string);
+
+    bool update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/cpu";
+
+   protected:
+    bool read_values();
+    float get_load(size_t core) const;
+
+   private:
+    static constexpr auto TAG_LABEL = "<label>";
+    static constexpr auto TAG_BAR_LOAD = "<bar-load>";
+    static constexpr auto TAG_RAMP_LOAD = "<ramp-load>";
+    static constexpr auto TAG_RAMP_LOAD_PER_CORE = "<ramp-coreload>";
+
+    progressbar_t m_barload;
+    ramp_t m_rampload;
+    ramp_t m_rampload_core;
+    label_t m_label;
+    int m_ramp_padding;
+
+    vector<cpu_time_t> m_cputimes;
+    vector<cpu_time_t> m_cputimes_prev;
+
+    float m_total = 0;
+    vector<float> m_load;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/date.hpp b/include/modules/date.hpp
new file mode 100644 (file)
index 0000000..4899f37
--- /dev/null
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <ctime>
+#include <iomanip>
+#include <iostream>
+
+#include "modules/meta/timer_module.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class date_module : public timer_module<date_module> {
+   public:
+    explicit date_module(const bar_settings&, string);
+
+    bool update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/date";
+
+    static constexpr auto EVENT_TOGGLE = "toggle";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr auto TAG_LABEL = "<label>";
+
+    // \deprecated: Use <label>
+    static constexpr auto TAG_DATE = "<date>";
+
+    label_t m_label;
+
+    string m_dateformat;
+    string m_dateformat_alt;
+    string m_timeformat;
+    string m_timeformat_alt;
+
+    string m_date;
+    string m_time;
+
+    // Single stringstream to be used to gather the results of std::put_time
+    std::stringstream datetime_stream;
+
+    std::atomic<bool> m_toggled{false};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/fs.hpp b/include/modules/fs.hpp
new file mode 100644 (file)
index 0000000..115dddc
--- /dev/null
@@ -0,0 +1,73 @@
+#pragma once
+
+#include "components/config.hpp"
+#include "modules/meta/timer_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  /**
+   * Filesystem structure
+   */
+  struct fs_mount {
+    string mountpoint;
+    bool mounted = false;
+
+    string type;
+    string fsname;
+
+    uint64_t bytes_free{0ULL};
+    uint64_t bytes_used{0ULL};
+    uint64_t bytes_avail{0ULL};
+    uint64_t bytes_total{0ULL};
+
+    int percentage_free{0};
+    int percentage_used{0};
+
+    explicit fs_mount(const string& mountpoint, bool mounted = false) : mountpoint(mountpoint), mounted(mounted) {}
+  };
+
+  using fs_mount_t = unique_ptr<fs_mount>;
+
+  /**
+   * Module used to display filesystem stats.
+   */
+  class fs_module : public timer_module<fs_module> {
+   public:
+    explicit fs_module(const bar_settings&, string);
+
+    bool update();
+    string get_format() const;
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/fs";
+
+   private:
+    static constexpr auto FORMAT_MOUNTED = "format-mounted";
+    static constexpr auto FORMAT_UNMOUNTED = "format-unmounted";
+    static constexpr auto TAG_LABEL_MOUNTED = "<label-mounted>";
+    static constexpr auto TAG_LABEL_UNMOUNTED = "<label-unmounted>";
+    static constexpr auto TAG_BAR_USED = "<bar-used>";
+    static constexpr auto TAG_BAR_FREE = "<bar-free>";
+    static constexpr auto TAG_RAMP_CAPACITY = "<ramp-capacity>";
+
+    label_t m_labelmounted;
+    label_t m_labelunmounted;
+    progressbar_t m_barused;
+    progressbar_t m_barfree;
+    ramp_t m_rampcapacity;
+
+    vector<string> m_mountpoints;
+    vector<fs_mount_t> m_mounts;
+    bool m_fixed{false};
+    bool m_remove_unmounted{false};
+    int m_spacing{2};
+
+    // used while formatting output
+    size_t m_index{0_z};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/github.hpp b/include/modules/github.hpp
new file mode 100644 (file)
index 0000000..a45701f
--- /dev/null
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+#include "settings.hpp"
+#include "utils/http.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  /**
+   * Module used to query the GitHub API for notification count
+   */
+  class github_module : public timer_module<github_module> {
+   public:
+    explicit github_module(const bar_settings&, string);
+
+    bool update();
+    bool build(builder* builder, const string& tag) const;
+    string get_format() const;
+
+    static constexpr auto TYPE = "internal/github";
+
+   private:
+    void update_label(int);
+    int get_number_of_notification();
+    string request();
+    static constexpr auto TAG_LABEL = "<label>";
+    static constexpr auto TAG_LABEL_OFFLINE = "<label-offline>";
+    static constexpr auto FORMAT_OFFLINE = "format-offline";
+
+    label_t m_label{};
+    label_t m_label_offline{};
+    string m_api_url;
+    string m_user;
+    string m_accesstoken{};
+    unique_ptr<http_downloader> m_http{};
+    bool m_empty_notifications{false};
+    std::atomic<bool> m_offline{false};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/i3.hpp b/include/modules/i3.hpp
new file mode 100644 (file)
index 0000000..6cd604c
--- /dev/null
@@ -0,0 +1,99 @@
+#pragma once
+
+#include <i3ipc++/ipc.hpp>
+
+#include "components/config.hpp"
+#include "modules/meta/event_module.hpp"
+#include "utils/i3.hpp"
+#include "utils/io.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class i3_module : public event_module<i3_module> {
+   public:
+    enum class state {
+      NONE,
+      /**
+       * \brief Active workspace on focused monitor
+       */
+      FOCUSED,
+      /**
+       * \brief Inactive workspace on any monitor
+       */
+      UNFOCUSED,
+      /**
+       * \brief Active workspace on unfocused monitor
+       */
+      VISIBLE,
+      /**
+       * \brief Workspace with urgency hint set
+       */
+      URGENT,
+    };
+
+    struct workspace {
+      explicit workspace(string name, enum state state_, label_t&& label)
+          : name(name), state(state_), label(forward<label_t>(label)) {}
+
+      operator bool();
+
+      string name;
+      enum state state;
+      label_t label;
+    };
+
+   public:
+    explicit i3_module(const bar_settings&, string);
+
+    void stop();
+    bool has_event();
+    bool update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/i3";
+
+    static constexpr auto EVENT_FOCUS = "focus";
+    static constexpr auto EVENT_NEXT = "next";
+    static constexpr auto EVENT_PREV = "prev";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static string make_workspace_command(const string& workspace);
+
+    static constexpr const char* DEFAULT_TAGS{"<label-state> <label-mode>"};
+    static constexpr const char* DEFAULT_MODE{"default"};
+    static constexpr const char* DEFAULT_WS_ICON{"ws-icon-default"};
+    static constexpr const char* DEFAULT_WS_LABEL{"%icon% %name%"};
+
+    static constexpr const char* TAG_LABEL_STATE{"<label-state>"};
+    static constexpr const char* TAG_LABEL_MODE{"<label-mode>"};
+
+    map<state, label_t> m_statelabels;
+    vector<unique_ptr<workspace>> m_workspaces;
+    iconset_t m_icons;
+
+    label_t m_modelabel;
+    bool m_modeactive{false};
+
+    /**
+     * Separator that is inserted in between workspaces
+     */
+    label_t m_labelseparator;
+
+    bool m_click{true};
+    bool m_scroll{true};
+    bool m_revscroll{true};
+    bool m_wrap{true};
+    bool m_indexsort{false};
+    bool m_pinworkspaces{false};
+    bool m_strip_wsnumbers{false};
+    bool m_fuzzy_match{false};
+
+    unique_ptr<i3_util::connection_t> m_ipc;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/ipc.hpp b/include/modules/ipc.hpp
new file mode 100644 (file)
index 0000000..63e4fe7
--- /dev/null
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+#include "utils/command.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  /**
+   * Module that allow users to configure hooks on
+   * received ipc messages. The hook will execute the defined
+   * shell script and the resulting output will be used
+   * as the module content.
+   */
+  class ipc_module : public static_module<ipc_module> {
+   public:
+    /**
+     * Hook structure that will be fired
+     * when receiving a message with specified id
+     */
+    struct hook {
+      string payload;
+      string command;
+    };
+
+   public:
+    explicit ipc_module(const bar_settings&, string);
+
+    void start();
+    void update() {}
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+    void on_message(const string& message);
+
+    static constexpr auto TYPE = "custom/ipc";
+
+   private:
+    static constexpr const char* TAG_OUTPUT{"<output>"};
+    vector<unique_ptr<hook>> m_hooks;
+    map<mousebtn, string> m_actions;
+    string m_output;
+    size_t m_initial;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp
new file mode 100644 (file)
index 0000000..e85624a
--- /dev/null
@@ -0,0 +1,47 @@
+#pragma once
+
+#include "modules/meta/timer_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  enum class memtype { NONE = 0, TOTAL, USED, FREE, SHARED, BUFFERS, CACHE, AVAILABLE };
+
+  class memory_module : public timer_module<memory_module> {
+   public:
+    explicit memory_module(const bar_settings&, string);
+
+    bool update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/memory";
+
+   private:
+    static constexpr const char* TAG_LABEL{"<label>"};
+    static constexpr const char* TAG_BAR_USED{"<bar-used>"};
+    static constexpr const char* TAG_BAR_FREE{"<bar-free>"};
+    static constexpr const char* TAG_RAMP_USED{"<ramp-used>"};
+    static constexpr const char* TAG_RAMP_FREE{"<ramp-free>"};
+    static constexpr const char* TAG_BAR_SWAP_USED{"<bar-swap-used>"};
+    static constexpr const char* TAG_BAR_SWAP_FREE{"<bar-swap-free>"};
+    static constexpr const char* TAG_RAMP_SWAP_USED{"<ramp-swap-used>"};
+    static constexpr const char* TAG_RAMP_SWAP_FREE{"<ramp-swap-free>"};
+
+    label_t m_label;
+    progressbar_t m_bar_memused;
+    progressbar_t m_bar_memfree;
+    int m_perc_memused{0};
+    int m_perc_memfree{0};
+    ramp_t m_ramp_memused;
+    ramp_t m_ramp_memfree;
+    progressbar_t m_bar_swapused;
+    progressbar_t m_bar_swapfree;
+    int m_perc_swap_used{0};
+    int m_perc_swap_free{0};
+    ramp_t m_ramp_swapused;
+    ramp_t m_ramp_swapfree;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/menu.hpp b/include/modules/menu.hpp
new file mode 100644 (file)
index 0000000..e07df7a
--- /dev/null
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class menu_module : public static_module<menu_module> {
+   public:
+    struct menu_tree_item {
+      string exec;
+      label_t label;
+    };
+
+    struct menu_tree {
+      vector<unique_ptr<menu_tree_item>> items;
+    };
+
+   public:
+    explicit menu_module(const bar_settings&, string);
+
+    bool build(builder* builder, const string& tag) const;
+    void update() {}
+
+    static constexpr auto TYPE = "custom/menu";
+
+    static constexpr auto EVENT_OPEN = "open";
+    static constexpr auto EVENT_CLOSE = "close";
+    static constexpr auto EVENT_EXEC = "exec";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr auto TAG_LABEL_TOGGLE = "<label-toggle>";
+    static constexpr auto TAG_MENU = "<menu>";
+
+    bool m_expand_right{true};
+
+    label_t m_labelopen;
+    label_t m_labelclose;
+    label_t m_labelseparator;
+
+    vector<unique_ptr<menu_tree>> m_levels;
+
+    std::atomic<int> m_level{-1};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/base.hpp b/include/modules/meta/base.hpp
new file mode 100644 (file)
index 0000000..420ff52
--- /dev/null
@@ -0,0 +1,190 @@
+#pragma once
+
+#include <algorithm>
+#include <chrono>
+#include <condition_variable>
+#include <map>
+#include <mutex>
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/concurrency.hpp"
+#include "utils/functional.hpp"
+#include "utils/inotify.hpp"
+#include "utils/string.hpp"
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+using std::map;
+
+#define DEFAULT_FORMAT "format"
+
+#define CONST_MOD(name) static_cast<name const&>(*this)
+#define CAST_MOD(name) static_cast<name*>(this)
+
+// fwd decl {{{
+
+namespace drawtypes {
+  class ramp;
+  using ramp_t = shared_ptr<ramp>;
+  class progressbar;
+  using progressbar_t = shared_ptr<progressbar>;
+  class animation;
+  using animation_t = shared_ptr<animation>;
+  class iconset;
+  using iconset_t = shared_ptr<iconset>;
+}  // namespace drawtypes
+
+class builder;
+class config;
+class logger;
+class signal_emitter;
+
+// }}}
+
+namespace modules {
+  using namespace drawtypes;
+
+  DEFINE_ERROR(module_error);
+  DEFINE_CHILD_ERROR(undefined_format, module_error);
+  DEFINE_CHILD_ERROR(undefined_format_tag, module_error);
+
+  // class definition : module_format {{{
+
+  struct module_format {
+    string value{};
+    vector<string> tags{};
+    label_t prefix{};
+    label_t suffix{};
+    rgba fg{};
+    rgba bg{};
+    rgba ul{};
+    rgba ol{};
+    size_t ulsize{0};
+    size_t olsize{0};
+    size_t spacing{0};
+    size_t padding{0};
+    size_t margin{0};
+    int offset{0};
+    int font{0};
+
+    string decorate(builder* builder, string output);
+  };
+
+  // }}}
+  // class definition : module_formatter {{{
+
+  class module_formatter {
+   public:
+    explicit module_formatter(const config& conf, string modname) : m_conf(conf), m_modname(modname) {}
+
+    void add(string name, string fallback, vector<string>&& tags, vector<string>&& whitelist = {});
+    bool has(const string& tag, const string& format_name);
+    bool has(const string& tag);
+    shared_ptr<module_format> get(const string& format_name);
+
+   protected:
+    const config& m_conf;
+    string m_modname;
+    map<string, shared_ptr<module_format>> m_formats;
+  };
+
+  // }}}
+
+  // class definition : module_interface {{{
+
+  struct module_interface {
+   public:
+    virtual ~module_interface() {}
+
+    /**
+     * The type users have to specify in the module section `type` key
+     */
+    virtual string type() const = 0;
+
+    /**
+     * Module name w/o 'module/' prefix
+     */
+    virtual string name_raw() const = 0;
+    virtual string name() const = 0;
+    virtual bool running() const = 0;
+
+    /**
+     * Handle action, possibly with data attached
+     *
+     * Any implementation is free to ignore the data, if the action does not
+     * require additional data.
+     *
+     * \returns true if the action is supported and false otherwise
+     */
+    virtual bool input(const string& action, const string& data) = 0;
+
+    virtual void start() = 0;
+    virtual void stop() = 0;
+    virtual void halt(string error_message) = 0;
+    virtual string contents() = 0;
+  };
+
+  // }}}
+  // class definition : module {{{
+
+  template <class Impl>
+  class module : public module_interface {
+   public:
+    module(const bar_settings bar, string name);
+    ~module() noexcept;
+
+    string type() const;
+
+    string name_raw() const;
+    string name() const;
+    bool running() const;
+    void stop();
+    void halt(string error_message);
+    void teardown();
+    string contents();
+
+    bool input(const string& action, const string& data);
+
+   protected:
+    void broadcast();
+    void idle();
+    void sleep(chrono::duration<double> duration);
+    template <class Clock, class Duration>
+    void sleep_until(chrono::time_point<Clock, Duration> point);
+    void wakeup();
+    string get_format() const;
+    string get_output();
+
+   protected:
+    signal_emitter& m_sig;
+    const bar_settings m_bar;
+    const logger& m_log;
+    const config& m_conf;
+
+    mutex m_buildlock;
+    mutex m_updatelock;
+    mutex m_sleeplock;
+    std::condition_variable m_sleephandler;
+
+    const string m_name;
+    const string m_name_raw;
+    unique_ptr<builder> m_builder;
+    unique_ptr<module_formatter> m_formatter;
+    vector<thread> m_threads;
+    thread m_mainthread;
+
+    bool m_handle_events{true};
+
+   private:
+    atomic<bool> m_enabled{true};
+    atomic<bool> m_changed{true};
+    string m_cache;
+  };
+
+  // }}}
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/base.inl b/include/modules/meta/base.inl
new file mode 100644 (file)
index 0000000..c6f0fdb
--- /dev/null
@@ -0,0 +1,211 @@
+#include "components/builder.hpp"
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  // module<Impl> public {{{
+
+  template <typename Impl>
+  module<Impl>::module(const bar_settings bar, string name)
+      : m_sig(signal_emitter::make())
+      , m_bar(bar)
+      , m_log(logger::make())
+      , m_conf(config::make())
+      , m_name("module/" + name)
+      , m_name_raw(name)
+      , m_builder(make_unique<builder>(bar))
+      , m_formatter(make_unique<module_formatter>(m_conf, m_name))
+      , m_handle_events(m_conf.get(m_name, "handle-events", true)) {}
+
+  template <typename Impl>
+  module<Impl>::~module() noexcept {
+    m_log.trace("%s: Deconstructing", name());
+
+    for (auto&& thread_ : m_threads) {
+      if (thread_.joinable()) {
+        thread_.join();
+      }
+    }
+    if (m_mainthread.joinable()) {
+      m_mainthread.join();
+    }
+  }
+
+  template <typename Impl>
+  string module<Impl>::name() const {
+    return m_name;
+  }
+
+  template <typename Impl>
+  string module<Impl>::name_raw() const {
+    return m_name_raw;
+  }
+
+  template <typename Impl>
+  string module<Impl>::type() const {
+    return string(Impl::TYPE);
+  }
+
+  template <typename Impl>
+  bool module<Impl>::running() const {
+    return static_cast<bool>(m_enabled);
+  }
+
+  template <typename Impl>
+  void module<Impl>::stop() {
+    if (!static_cast<bool>(m_enabled)) {
+      return;
+    }
+
+    m_log.info("%s: Stopping", name());
+    m_enabled = false;
+
+    std::lock(m_buildlock, m_updatelock);
+    std::lock_guard<std::mutex> guard_a(m_buildlock, std::adopt_lock);
+    std::lock_guard<std::mutex> guard_b(m_updatelock, std::adopt_lock);
+    {
+      CAST_MOD(Impl)->wakeup();
+      CAST_MOD(Impl)->teardown();
+
+      m_sig.emit(signals::eventqueue::check_state{});
+    }
+  }
+
+  template <typename Impl>
+  void module<Impl>::halt(string error_message) {
+    m_log.err("%s: %s", name(), error_message);
+    m_log.notice("Stopping '%s'...", name());
+    stop();
+  }
+
+  template <typename Impl>
+  void module<Impl>::teardown() {}
+
+  template <typename Impl>
+  string module<Impl>::contents() {
+    if (m_changed) {
+      m_log.info("%s: Rebuilding cache", name());
+      m_cache = CAST_MOD(Impl)->get_output();
+      // Make sure builder is really empty
+      m_builder->flush();
+      if (!m_cache.empty()) {
+        // Add a reset tag after the module
+        m_builder->control(controltag::R);
+        m_cache += m_builder->flush();
+      }
+      m_changed = false;
+    }
+    return m_cache;
+  }
+
+  template <typename Impl>
+  bool module<Impl>::input(const string&, const string&) {
+    // By default a module doesn't support inputs
+    return false;
+  }
+
+  // }}}
+  // module<Impl> protected {{{
+
+  template <typename Impl>
+  void module<Impl>::broadcast() {
+    m_changed = true;
+    m_sig.emit(signals::eventqueue::notify_change{});
+  }
+
+  template <typename Impl>
+  void module<Impl>::idle() {
+    if (running()) {
+      CAST_MOD(Impl)->sleep(25ms);
+    }
+  }
+
+  template <typename Impl>
+  void module<Impl>::sleep(chrono::duration<double> sleep_duration) {
+    if (running()) {
+      std::unique_lock<std::mutex> lck(m_sleeplock);
+      m_sleephandler.wait_for(lck, sleep_duration);
+    }
+  }
+
+  template <typename Impl>
+  template <class Clock, class Duration>
+  void module<Impl>::sleep_until(chrono::time_point<Clock, Duration> point) {
+    if (running()) {
+      std::unique_lock<std::mutex> lck(m_sleeplock);
+      m_sleephandler.wait_until(lck, point);
+    }
+  }
+
+  template <typename Impl>
+  void module<Impl>::wakeup() {
+    m_log.trace("%s: Release sleep lock", name());
+    m_sleephandler.notify_all();
+  }
+
+  template <typename Impl>
+  string module<Impl>::get_format() const {
+    return DEFAULT_FORMAT;
+  }
+
+  template <typename Impl>
+  string module<Impl>::get_output() {
+    std::lock_guard<std::mutex> guard(m_buildlock);
+    auto format_name = CONST_MOD(Impl).get_format();
+    auto format = m_formatter->get(format_name);
+    bool no_tag_built{true};
+    bool fake_no_tag_built{false};
+    bool tag_built{false};
+    auto mingap = std::max(1_z, format->spacing);
+    size_t start, end;
+    string value{format->value};
+    while ((start = value.find('<')) != string::npos && (end = value.find('>', start)) != string::npos) {
+      if (start > 0) {
+        if (no_tag_built) {
+          // If no module tag has been built we do not want to add
+          // whitespace defined between the format tags, but we do still
+          // want to output other non-tag content
+          auto trimmed = string_util::ltrim(value.substr(0, start), ' ');
+          if (!trimmed.empty()) {
+            fake_no_tag_built = false;
+            m_builder->node(move(trimmed));
+          }
+        } else {
+          m_builder->node(value.substr(0, start));
+        }
+        value.erase(0, start);
+        end -= start;
+        start = 0;
+      }
+      string tag{value.substr(start, end + 1)};
+      if (tag.empty()) {
+        continue;
+      } else if (tag[0] == '<' && tag[tag.size() - 1] == '>') {
+        if (!no_tag_built)
+          m_builder->space(format->spacing);
+        else if (fake_no_tag_built)
+          no_tag_built = false;
+        if (!(tag_built = CONST_MOD(Impl).build(m_builder.get(), tag)) && !no_tag_built)
+          m_builder->remove_trailing_space(mingap);
+        if (tag_built)
+          no_tag_built = false;
+      }
+      value.erase(0, tag.size());
+    }
+
+    if (!value.empty()) {
+      m_builder->append(value);
+    }
+
+    return format->decorate(&*m_builder, m_builder->flush());
+  }
+
+  // }}}
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/event_handler.hpp b/include/modules/meta/event_handler.hpp
new file mode 100644 (file)
index 0000000..c1c786a
--- /dev/null
@@ -0,0 +1,33 @@
+#pragma once
+
+#include "common.hpp"
+#include "utils/concurrency.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+  struct event_handler_interface {
+    virtual ~event_handler_interface() {}
+    virtual void connect(connection&) {}
+    virtual void disconnect(connection&) {}
+  };
+
+  template <typename Event, typename... Events>
+  class event_handler : public event_handler_interface, public xpp::event::sink<Event, Events...> {
+   public:
+    virtual ~event_handler() {}
+
+    virtual void connect(connection& conn) override {
+      conn.attach_sink(this, SINK_PRIORITY_MODULE);
+    }
+
+    virtual void disconnect(connection& conn) override {
+      conn.detach_sink(this, SINK_PRIORITY_MODULE);
+    }
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/event_module.hpp b/include/modules/meta/event_module.hpp
new file mode 100644 (file)
index 0000000..417ca3a
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template <class Impl>
+  class event_module : public module<Impl> {
+   public:
+    using module<Impl>::module;
+
+    void start() {
+      this->m_mainthread = thread(&event_module::runner, this);
+    }
+
+   protected:
+    void runner() {
+      this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+      try {
+        // warm up module output before entering the loop
+        std::unique_lock<std::mutex> guard(this->m_updatelock);
+        CAST_MOD(Impl)->update();
+        CAST_MOD(Impl)->broadcast();
+        guard.unlock();
+
+        const auto check = [&]() -> bool {
+          std::lock_guard<std::mutex> guard(this->m_updatelock);
+          return CAST_MOD(Impl)->has_event() && CAST_MOD(Impl)->update();
+        };
+
+        while (this->running()) {
+          if (check()) {
+            CAST_MOD(Impl)->broadcast();
+          }
+          CAST_MOD(Impl)->idle();
+        }
+      } catch (const exception& err) {
+        CAST_MOD(Impl)->halt(err.what());
+      }
+    }
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/factory.hpp b/include/modules/meta/factory.hpp
new file mode 100644 (file)
index 0000000..33deb88
--- /dev/null
@@ -0,0 +1,112 @@
+#pragma once
+
+#include "common.hpp"
+#include "modules/backlight.hpp"
+#include "modules/battery.hpp"
+#include "modules/bspwm.hpp"
+#include "modules/counter.hpp"
+#include "modules/cpu.hpp"
+#include "modules/date.hpp"
+#include "modules/fs.hpp"
+#include "modules/ipc.hpp"
+#include "modules/memory.hpp"
+#include "modules/menu.hpp"
+#include "modules/meta/base.hpp"
+#include "modules/script.hpp"
+#if DEBUG
+#include "modules/systray.hpp"
+#endif
+#include "modules/temperature.hpp"
+#include "modules/text.hpp"
+#include "modules/xbacklight.hpp"
+#include "modules/xwindow.hpp"
+#include "modules/xworkspaces.hpp"
+#if ENABLE_I3
+#include "modules/i3.hpp"
+#endif
+#if ENABLE_MPD
+#include "modules/mpd.hpp"
+#endif
+#if ENABLE_NETWORK
+#include "modules/network.hpp"
+#endif
+#if ENABLE_ALSA
+#include "modules/alsa.hpp"
+#endif
+#if ENABLE_PULSEAUDIO
+#include "modules/pulseaudio.hpp"
+#endif
+#if ENABLE_CURL
+#include "modules/github.hpp"
+#endif
+#if ENABLE_XKEYBOARD
+#include "modules/xkeyboard.hpp"
+#endif
+#include "modules/unsupported.hpp"
+
+POLYBAR_NS
+
+using namespace modules;
+
+namespace {
+  module_interface* make_module(string&& name, const bar_settings& bar, string module_name, const logger& m_log) {
+    if (name == counter_module::TYPE) {
+      return new counter_module(bar, move(module_name));
+    } else if (name == backlight_module::TYPE) {
+      return new backlight_module(bar, move(module_name));
+    } else if (name == battery_module::TYPE) {
+      return new battery_module(bar, move(module_name));
+    } else if (name == bspwm_module::TYPE) {
+      return new bspwm_module(bar, move(module_name));
+    } else if (name == cpu_module::TYPE) {
+      return new cpu_module(bar, move(module_name));
+    } else if (name == date_module::TYPE) {
+      return new date_module(bar, move(module_name));
+    } else if (name == github_module::TYPE) {
+      return new github_module(bar, move(module_name));
+    } else if (name == fs_module::TYPE) {
+      return new fs_module(bar, move(module_name));
+    } else if (name == memory_module::TYPE) {
+      return new memory_module(bar, move(module_name));
+    } else if (name == i3_module::TYPE) {
+      return new i3_module(bar, move(module_name));
+    } else if (name == mpd_module::TYPE) {
+      return new mpd_module(bar, move(module_name));
+    } else if (name == "internal/volume") {
+      m_log.warn("internal/volume is deprecated, use %s instead", string(alsa_module::TYPE));
+      return new alsa_module(bar, move(module_name));
+    } else if (name == alsa_module::TYPE) {
+      return new alsa_module(bar, move(module_name));
+    } else if (name == pulseaudio_module::TYPE) {
+      return new pulseaudio_module(bar, move(module_name));
+    } else if (name == network_module::TYPE) {
+      return new network_module(bar, move(module_name));
+#if DEBUG
+    } else if (name == systray_module::TYPE) {
+      return new systray_module(bar, move(module_name));
+#endif
+    } else if (name == temperature_module::TYPE) {
+      return new temperature_module(bar, move(module_name));
+    } else if (name == xbacklight_module::TYPE) {
+      return new xbacklight_module(bar, move(module_name));
+    } else if (name == xkeyboard_module::TYPE) {
+      return new xkeyboard_module(bar, move(module_name));
+    } else if (name == xwindow_module::TYPE) {
+      return new xwindow_module(bar, move(module_name));
+    } else if (name == xworkspaces_module::TYPE) {
+      return new xworkspaces_module(bar, move(module_name));
+    } else if (name == text_module::TYPE) {
+      return new text_module(bar, move(module_name));
+    } else if (name == script_module::TYPE) {
+      return new script_module(bar, move(module_name));
+    } else if (name == menu_module::TYPE) {
+      return new menu_module(bar, move(module_name));
+    } else if (name == ipc_module::TYPE) {
+      return new ipc_module(bar, move(module_name));
+    } else {
+      throw application_error("Unknown module: " + name);
+    }
+  }
+}  // namespace
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/inotify_module.hpp b/include/modules/meta/inotify_module.hpp
new file mode 100644 (file)
index 0000000..0b174d3
--- /dev/null
@@ -0,0 +1,93 @@
+#pragma once
+
+#include "components/builder.hpp"
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template <class Impl>
+  class inotify_module : public module<Impl> {
+   public:
+    using module<Impl>::module;
+
+    void start() {
+      this->m_mainthread = thread(&inotify_module::runner, this);
+    }
+
+   protected:
+    void runner() {
+      this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+      try {
+        // Warm up module output before entering the loop
+        std::unique_lock<std::mutex> guard(this->m_updatelock);
+        CAST_MOD(Impl)->on_event(nullptr);
+        CAST_MOD(Impl)->broadcast();
+        guard.unlock();
+
+        while (this->running()) {
+          std::lock_guard<std::mutex> guard(this->m_updatelock);
+          CAST_MOD(Impl)->poll_events();
+        }
+      } catch (const module_error& err) {
+        CAST_MOD(Impl)->halt(err.what());
+      } catch (const std::exception& err) {
+        CAST_MOD(Impl)->halt(err.what());
+      }
+    }
+
+    void watch(string path, int mask = IN_ALL_EVENTS) {
+      this->m_log.trace("%s: Attach inotify at %s", this->name(), path);
+      m_watchlist.insert(make_pair(path, mask));
+    }
+
+    void idle() {
+      this->sleep(200ms);
+    }
+
+    void poll_events() {
+      vector<unique_ptr<inotify_watch>> watches;
+
+      try {
+        for (auto&& w : m_watchlist) {
+          watches.emplace_back(inotify_util::make_watch(w.first));
+          watches.back()->attach(w.second);
+        }
+      } catch (const system_error& e) {
+        watches.clear();
+        this->m_log.err("%s: Error while creating inotify watch (what: %s)", this->name(), e.what());
+        CAST_MOD(Impl)->sleep(0.1s);
+        return;
+      }
+
+      while (this->running()) {
+        for (auto&& w : watches) {
+          this->m_log.trace_x("%s: Poll inotify watch %s", this->name(), w->path());
+
+          if (w->poll(1000 / watches.size())) {
+            auto event = w->get_event();
+
+            for (auto&& w : watches) {
+              w->remove(true);
+            }
+
+            if (CAST_MOD(Impl)->on_event(event.get())) {
+              CAST_MOD(Impl)->broadcast();
+            }
+            CAST_MOD(Impl)->idle();
+            return;
+          }
+
+          if (!this->running())
+            break;
+        }
+        CAST_MOD(Impl)->idle();
+      }
+    }
+
+   private:
+    map<string, int> m_watchlist;
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/static_module.hpp b/include/modules/meta/static_module.hpp
new file mode 100644 (file)
index 0000000..cc30e8f
--- /dev/null
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template <class Impl>
+  class static_module : public module<Impl> {
+   public:
+    using module<Impl>::module;
+
+    void start() {
+      this->m_mainthread = thread([&] {
+        this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+        CAST_MOD(Impl)->update();
+        CAST_MOD(Impl)->broadcast();
+      });
+    }
+
+    bool build(builder*, string) const {
+      return true;
+    }
+  };
+}
+
+POLYBAR_NS_END
diff --git a/include/modules/meta/timer_module.hpp b/include/modules/meta/timer_module.hpp
new file mode 100644 (file)
index 0000000..2cd5f3b
--- /dev/null
@@ -0,0 +1,77 @@
+#pragma once
+
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  using interval_t = chrono::duration<double>;
+
+  template <class Impl>
+  class timer_module : public module<Impl> {
+   public:
+    using module<Impl>::module;
+
+    void start() {
+      this->m_mainthread = thread(&timer_module::runner, this);
+    }
+
+   protected:
+    /**
+     * Loads and sets the interval for this module.
+     *
+     * Will throw an exception if a non-positive (<= 0) number is given.
+     */
+    void set_interval(interval_t def) {
+      m_interval = this->m_conf.template get<decltype(m_interval)>(this->name(), "interval", def);
+
+      if (m_interval <= 0s) {
+        throw module_error(
+            this->name() + ": 'interval' must be larger than 0 (got '" + to_string(m_interval.count()) + "s')");
+      }
+    }
+
+    void runner() {
+      this->m_log.trace("%s: Thread id = %i", this->name(), concurrency_util::thread_id(this_thread::get_id()));
+
+      const auto check = [&]() -> bool {
+        std::unique_lock<std::mutex> guard(this->m_updatelock);
+        return CAST_MOD(Impl)->update();
+      };
+
+      try {
+        // warm up module output before entering the loop
+        check();
+        CAST_MOD(Impl)->broadcast();
+
+        while (this->running()) {
+          if (check()) {
+            CAST_MOD(Impl)->broadcast();
+          }
+          // wait until next full interval to avoid drifting clocks
+          using clock = chrono::system_clock;
+          using sys_duration_t = clock::time_point::duration;
+
+          auto sys_interval = chrono::duration_cast<sys_duration_t>(m_interval);
+          clock::time_point now = clock::now();
+          sys_duration_t adjusted = sys_interval - (now.time_since_epoch() % sys_interval);
+
+          // The seemingly arbitrary addition of 500ms is due
+          // to the fact that if we wait the exact time our
+          // thread will be woken just a tiny bit prematurely
+          // and therefore the wrong time will be displayed.
+          // It is currently unknown why exactly the thread gets
+          // woken prematurely.
+          CAST_MOD(Impl)->sleep_until(now + adjusted + 500ms);
+        }
+      } catch (const exception& err) {
+        CAST_MOD(Impl)->halt(err.what());
+      }
+    }
+
+   protected:
+    interval_t m_interval{1.0};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/mpd.hpp b/include/modules/mpd.hpp
new file mode 100644 (file)
index 0000000..04afc97
--- /dev/null
@@ -0,0 +1,110 @@
+#pragma once
+
+#include <chrono>
+
+#include "adapters/mpd.hpp"
+#include "modules/meta/event_module.hpp"
+#include "utils/env.hpp"
+
+POLYBAR_NS
+
+using namespace mpd;
+
+namespace modules {
+  class mpd_module : public event_module<mpd_module> {
+   public:
+    explicit mpd_module(const bar_settings&, string);
+
+    void teardown();
+    inline bool connected() const;
+    void idle();
+    bool has_event();
+    bool update();
+    string get_format() const;
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/mpd";
+
+    static constexpr const char* EVENT_PLAY = "play";
+    static constexpr const char* EVENT_PAUSE = "pause";
+    static constexpr const char* EVENT_STOP = "stop";
+    static constexpr const char* EVENT_PREV = "prev";
+    static constexpr const char* EVENT_NEXT = "next";
+    static constexpr const char* EVENT_REPEAT = "repeat";
+    static constexpr const char* EVENT_SINGLE = "single";
+    static constexpr const char* EVENT_RANDOM = "random";
+    static constexpr const char* EVENT_CONSUME = "consume";
+    static constexpr const char* EVENT_SEEK = "seek";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr const char* FORMAT_ONLINE{"format-online"};
+    static constexpr const char* FORMAT_PLAYING{"format-playing"};
+    static constexpr const char* FORMAT_PAUSED{"format-paused"};
+    static constexpr const char* FORMAT_STOPPED{"format-stopped"};
+    static constexpr const char* TAG_BAR_PROGRESS{"<bar-progress>"};
+    static constexpr const char* TAG_TOGGLE{"<toggle>"};
+    static constexpr const char* TAG_TOGGLE_STOP{"<toggle-stop>"};
+    static constexpr const char* TAG_LABEL_SONG{"<label-song>"};
+    static constexpr const char* TAG_LABEL_TIME{"<label-time>"};
+    static constexpr const char* TAG_ICON_RANDOM{"<icon-random>"};
+    static constexpr const char* TAG_ICON_REPEAT{"<icon-repeat>"};
+    /*
+     * Deprecated
+     */
+    static constexpr const char* TAG_ICON_REPEAT_ONE{"<icon-repeatone>"};
+    /*
+     * Replaces icon-repeatone
+     *
+     * repeatone is misleading, since it doesn't actually affect the repeating behaviour
+     */
+    static constexpr const char* TAG_ICON_SINGLE{"<icon-single>"};
+    static constexpr const char* TAG_ICON_CONSUME{"<icon-consume>"};
+    static constexpr const char* TAG_ICON_PREV{"<icon-prev>"};
+    static constexpr const char* TAG_ICON_STOP{"<icon-stop>"};
+    static constexpr const char* TAG_ICON_PLAY{"<icon-play>"};
+    static constexpr const char* TAG_ICON_PAUSE{"<icon-pause>"};
+    static constexpr const char* TAG_ICON_NEXT{"<icon-next>"};
+    static constexpr const char* TAG_ICON_SEEKB{"<icon-seekb>"};
+    static constexpr const char* TAG_ICON_SEEKF{"<icon-seekf>"};
+
+    static constexpr const char* FORMAT_OFFLINE{"format-offline"};
+    static constexpr const char* TAG_LABEL_OFFLINE{"<label-offline>"};
+
+    unique_ptr<mpdconnection> m_mpd;
+
+    /*
+     * Stores the mpdstatus instance for the current connection
+     * m_status is not initialized if mpd is not connect, you always have to
+     * make sure that m_status is not NULL before dereferencing it
+     */
+    unique_ptr<mpdstatus> m_status;
+
+    string m_host{env_util::get("MPD_HOST", "127.0.0.1")};
+    string m_pass;
+    unsigned int m_port{6600U};
+
+    chrono::system_clock::time_point m_lastsync{};
+    float m_synctime{1.0f};
+
+    int m_quick_attempts{0};
+
+    // This flag is used to let thru a broadcast once every time
+    // the connection state changes
+    connection_state m_statebroadcasted{connection_state::NONE};
+
+    progressbar_t m_bar_progress;
+    iconset_t m_icons;
+    label_t m_label_song;
+    label_t m_label_time;
+    label_t m_label_offline;
+
+    rgba m_toggle_on_color;
+    rgba m_toggle_off_color;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/network.hpp b/include/modules/network.hpp
new file mode 100644 (file)
index 0000000..62ba08f
--- /dev/null
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "adapters/net.hpp"
+#include "components/config.hpp"
+#include "modules/meta/timer_module.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  enum class connection_state { NONE = 0, CONNECTED, DISCONNECTED, PACKETLOSS };
+
+  class network_module : public timer_module<network_module> {
+   public:
+    explicit network_module(const bar_settings&, string);
+
+    void teardown();
+    bool update();
+    string get_format() const;
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/network";
+
+   protected:
+    void subthread_routine();
+
+   private:
+    static constexpr auto FORMAT_CONNECTED = "format-connected";
+    static constexpr auto FORMAT_PACKETLOSS = "format-packetloss";
+    static constexpr auto FORMAT_DISCONNECTED = "format-disconnected";
+    static constexpr auto TAG_RAMP_SIGNAL = "<ramp-signal>";
+    static constexpr auto TAG_RAMP_QUALITY = "<ramp-quality>";
+    static constexpr auto TAG_LABEL_CONNECTED = "<label-connected>";
+    static constexpr auto TAG_LABEL_DISCONNECTED = "<label-disconnected>";
+    static constexpr auto TAG_LABEL_PACKETLOSS = "<label-packetloss>";
+    static constexpr auto TAG_ANIMATION_PACKETLOSS = "<animation-packetloss>";
+
+    net::wired_t m_wired;
+    net::wireless_t m_wireless;
+
+    ramp_t m_ramp_signal;
+    ramp_t m_ramp_quality;
+    animation_t m_animation_packetloss;
+    map<connection_state, label_t> m_label;
+
+    atomic<bool> m_connected{false};
+    atomic<bool> m_packetloss{false};
+
+    int m_signal{0};
+    int m_quality{0};
+    int m_counter{-1};  // -1 to ignore the first run
+
+    string m_interface;
+    int m_ping_nth_update{0};
+    int m_udspeed_minwidth{0};
+    bool m_accumulate{false};
+    bool m_unknown_up{false};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp
new file mode 100644 (file)
index 0000000..78a7251
--- /dev/null
@@ -0,0 +1,57 @@
+#pragma once
+
+#include "modules/meta/event_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+// fwd
+class pulseaudio;
+
+namespace modules {
+  using pulseaudio_t = shared_ptr<pulseaudio>;
+
+  class pulseaudio_module : public event_module<pulseaudio_module> {
+   public:
+    explicit pulseaudio_module(const bar_settings&, string);
+
+    void teardown();
+    bool has_event();
+    bool update();
+    string get_format() const;
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/pulseaudio";
+
+    static constexpr auto EVENT_INC = "inc";
+    static constexpr auto EVENT_DEC = "dec";
+    static constexpr auto EVENT_TOGGLE = "toggle";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr auto FORMAT_VOLUME = "format-volume";
+    static constexpr auto FORMAT_MUTED = "format-muted";
+
+    static constexpr auto TAG_RAMP_VOLUME = "<ramp-volume>";
+    static constexpr auto TAG_BAR_VOLUME = "<bar-volume>";
+    static constexpr auto TAG_LABEL_VOLUME = "<label-volume>";
+    static constexpr auto TAG_LABEL_MUTED = "<label-muted>";
+
+    progressbar_t m_bar_volume;
+    ramp_t m_ramp_volume;
+    label_t m_label_volume;
+    label_t m_label_muted;
+
+    pulseaudio_t m_pulseaudio;
+
+    int m_interval{5};
+    atomic<bool> m_muted{false};
+    atomic<int> m_volume{0};
+    atomic<double> m_decibels{0};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/script.hpp b/include/modules/script.hpp
new file mode 100644 (file)
index 0000000..2a62d3a
--- /dev/null
@@ -0,0 +1,51 @@
+#pragma once
+
+#include "modules/meta/base.hpp"
+#include "utils/command.hpp"
+#include "utils/io.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class script_module : public module<script_module> {
+   public:
+    explicit script_module(const bar_settings&, string);
+    ~script_module() {}
+
+    void start();
+    void stop();
+
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "custom/script";
+
+   protected:
+    chrono::duration<double> process(const mutex_wrapper<function<chrono::duration<double>()>>& handler) const;
+    bool check_condition();
+
+   private:
+    static constexpr const char* TAG_LABEL{"<label>"};
+
+    mutex_wrapper<function<chrono::duration<double>()>> m_handler;
+
+    unique_ptr<command<output_policy::REDIRECTED>> m_command;
+
+    bool m_tail;
+
+    string m_exec;
+    string m_exec_if;
+
+    chrono::duration<double> m_interval{0};
+    map<mousebtn, string> m_actions;
+
+    label_t m_label;
+    string m_output;
+    string m_prev;
+    int m_counter{0};
+
+    bool m_stopping{false};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/systray.hpp b/include/modules/systray.hpp
new file mode 100644 (file)
index 0000000..d59a4c6
--- /dev/null
@@ -0,0 +1,42 @@
+#if DEBUG
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+  /**
+   * Module used to display information about the
+   * currently active X window.
+   */
+  class systray_module : public static_module<systray_module> {
+   public:
+    explicit systray_module(const bar_settings&, string);
+
+    void update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/systray";
+
+    static constexpr auto EVENT_TOGGLE = "toggle";
+
+   protected:
+    bool input(const string& action, const string& data);
+
+   private:
+
+    static constexpr const char* TAG_LABEL_TOGGLE{"<label-toggle>"};
+    static constexpr const char* TAG_TRAY_CLIENTS{"<tray-clients>"};
+
+    connection& m_connection;
+    label_t m_label;
+
+    bool m_hidden{false};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
+#endif
diff --git a/include/modules/temperature.hpp b/include/modules/temperature.hpp
new file mode 100644 (file)
index 0000000..8bc0098
--- /dev/null
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <istream>
+
+#include "modules/meta/timer_module.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  enum class temp_state { NORMAL = 0, WARN };
+
+  class temperature_module : public timer_module<temperature_module> {
+   public:
+    explicit temperature_module(const bar_settings&, string);
+
+    bool update();
+    string get_format() const;
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/temperature";
+
+   private:
+    static constexpr auto TAG_LABEL = "<label>";
+    static constexpr auto TAG_LABEL_WARN = "<label-warn>";
+    static constexpr auto TAG_RAMP = "<ramp>";
+    static constexpr auto FORMAT_WARN = "format-warn";
+
+    map<temp_state, label_t> m_label;
+    ramp_t m_ramp;
+
+    string m_path;
+    int m_zone = 0;
+    // Base temperature used for where to start the ramp
+    int m_tempbase = 0;
+    int m_tempwarn = 0;
+    int m_temp = 0;
+    // Percentage used in the ramp
+    int m_perc = 0;
+
+    // Whether or not to show units with the %temperature-X% tokens
+    bool m_units{true};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/text.hpp b/include/modules/text.hpp
new file mode 100644 (file)
index 0000000..305185e
--- /dev/null
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "modules/meta/static_module.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  class text_module : public static_module<text_module> {
+   public:
+    explicit text_module(const bar_settings&, string);
+
+    void update() {}
+    string get_format() const;
+    string get_output();
+
+    static constexpr auto TYPE = "custom/text";
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/unsupported.hpp b/include/modules/unsupported.hpp
new file mode 100644 (file)
index 0000000..4694cfc
--- /dev/null
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "modules/meta/base.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  struct module_interface;
+
+#define DEFINE_UNSUPPORTED_MODULE(MODULE_NAME, MODULE_TYPE)                             \
+  class MODULE_NAME : public module_interface {                                         \
+   public:                                                                              \
+    MODULE_NAME(const bar_settings, string) {                                           \
+      throw application_error("No built-in support for '" + string{MODULE_TYPE} + "'"); \
+    }                                                                                   \
+    static constexpr auto TYPE = MODULE_TYPE;                                           \
+    string type() const {                                                               \
+      return "";                                                                        \
+    }                                                                                   \
+    string name_raw() const {                                                           \
+      return "";                                                                        \
+    }                                                                                   \
+    string name() const {                                                               \
+      return "";                                                                        \
+    }                                                                                   \
+    bool running() const {                                                              \
+      return false;                                                                     \
+    }                                                                                   \
+    void start() {}                                                                     \
+    void stop() {}                                                                      \
+    void halt(string) {}                                                                \
+    string contents() {                                                                 \
+      return "";                                                                        \
+    }                                                                                   \
+    bool input(const string&, const string&) {                                          \
+      return false;                                                                     \
+    }                                                                                   \
+  }
+
+#if not ENABLE_I3
+  DEFINE_UNSUPPORTED_MODULE(i3_module, "internal/i3");
+#endif
+#if not ENABLE_MPD
+  DEFINE_UNSUPPORTED_MODULE(mpd_module, "internal/mpd");
+#endif
+#if not ENABLE_NETWORK
+  DEFINE_UNSUPPORTED_MODULE(network_module, "internal/network");
+#endif
+#if not ENABLE_ALSA
+  DEFINE_UNSUPPORTED_MODULE(alsa_module, "internal/alsa");
+#endif
+#if not ENABLE_PULSEAUDIO
+  DEFINE_UNSUPPORTED_MODULE(pulseaudio_module, "internal/pulseaudio");
+#endif
+#if not ENABLE_CURL
+  DEFINE_UNSUPPORTED_MODULE(github_module, "internal/github");
+#endif
+#if not ENABLE_XKEYBOARD
+  DEFINE_UNSUPPORTED_MODULE(xkeyboard_module, "internal/xkeyboard");
+#endif
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/xbacklight.hpp b/include/modules/xbacklight.hpp
new file mode 100644 (file)
index 0000000..6fedbf9
--- /dev/null
@@ -0,0 +1,60 @@
+#pragma once
+
+#include "components/config.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "settings.hpp"
+#include "x11/extensions/randr.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+  /**
+   * Backlight module built using the RandR X extension.
+   *
+   * This is built as a replacement for the old backlight
+   * module that was set up using with inotify watches listening
+   * for changes to the raw file handle.
+   *
+   * This module is a lot faster, it's more responsive and it will
+   * be dormant until new values are reported. Inotify watches
+   * are a bit random when it comes to proc-/sysfs.
+   */
+  class xbacklight_module : public static_module<xbacklight_module>, public event_handler<evt::randr_notify> {
+   public:
+    explicit xbacklight_module(const bar_settings& bar, string name_);
+
+    void update();
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/xbacklight";
+
+    static constexpr const char* EVENT_INC = "inc";
+    static constexpr const char* EVENT_DEC = "dec";
+
+   protected:
+    void handle(const evt::randr_notify& evt);
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr const char* TAG_LABEL{"<label>"};
+    static constexpr const char* TAG_BAR{"<bar>"};
+    static constexpr const char* TAG_RAMP{"<ramp>"};
+
+    connection& m_connection;
+    monitor_t m_output;
+    xcb_window_t m_proxy{};
+
+    ramp_t m_ramp;
+    label_t m_label;
+    progressbar_t m_progressbar;
+
+    bool m_scroll{true};
+    std::atomic<int> m_percentage{0};
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/xkeyboard.hpp b/include/modules/xkeyboard.hpp
new file mode 100644 (file)
index 0000000..98da1ee
--- /dev/null
@@ -0,0 +1,70 @@
+#pragma once
+
+#include "common.hpp"
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "x11/extensions/xkb.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+  /**
+   * Keyboard module using the X keyboard extension
+   */
+  class xkeyboard_module
+      : public static_module<xkeyboard_module>,
+        public event_handler<evt::xkb_new_keyboard_notify, evt::xkb_state_notify, evt::xkb_indicator_state_notify> {
+   public:
+    explicit xkeyboard_module(const bar_settings& bar, string name_);
+
+    string get_output();
+    void update();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/xkeyboard";
+
+    static constexpr const char* EVENT_SWITCH = "switch";
+
+   protected:
+    bool query_keyboard();
+    bool blacklisted(const string& indicator_name);
+
+    void handle(const evt::xkb_new_keyboard_notify& evt);
+    void handle(const evt::xkb_state_notify& evt);
+    void handle(const evt::xkb_indicator_state_notify& evt);
+
+    bool input(const string& action, const string& data);
+
+   private:
+    static constexpr const char* TAG_LABEL_LAYOUT{"<label-layout>"};
+    static constexpr const char* TAG_LABEL_INDICATOR{"<label-indicator>"};
+    static constexpr const char* FORMAT_DEFAULT{"<label-layout> <label-indicator>"};
+    static constexpr const char* DEFAULT_LAYOUT_ICON{"layout-icon-default"};
+    static constexpr const char* DEFAULT_INDICATOR_ICON{"indicator-icon-default"};
+
+    connection& m_connection;
+    event_timer m_xkb_newkb_notify{};
+    event_timer m_xkb_state_notify{};
+    event_timer m_xkb_indicator_notify{};
+    unique_ptr<keyboard> m_keyboard;
+
+    label_t m_layout;
+    label_t m_indicator_state_on;
+    label_t m_indicator_state_off;
+    map<keyboard::indicator::type, label_t> m_indicators;
+    map<keyboard::indicator::type, label_t> m_indicator_on_labels;
+    map<keyboard::indicator::type, label_t> m_indicator_off_labels;
+
+    vector<string> m_blacklist;
+    iconset_t m_layout_icons;
+    iconset_t m_indicator_icons_on;
+    iconset_t m_indicator_icons_off;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/xwindow.hpp b/include/modules/xwindow.hpp
new file mode 100644 (file)
index 0000000..506504e
--- /dev/null
@@ -0,0 +1,54 @@
+#pragma once
+
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+  class active_window {
+   public:
+    explicit active_window(xcb_connection_t* conn, xcb_window_t win);
+    ~active_window();
+
+    bool match(const xcb_window_t win) const;
+    string title() const;
+
+   private:
+    xcb_connection_t* m_connection{nullptr};
+    xcb_window_t m_window{XCB_NONE};
+  };
+
+  /**
+   * Module used to display information about the
+   * currently active X window.
+   */
+  class xwindow_module : public static_module<xwindow_module>, public event_handler<evt::property_notify> {
+   public:
+    enum class state { NONE, ACTIVE, EMPTY };
+    explicit xwindow_module(const bar_settings&, string);
+
+    void update(bool force = false);
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/xwindow";
+
+   protected:
+    void handle(const evt::property_notify& evt);
+
+   private:
+    static constexpr const char* TAG_LABEL{"<label>"};
+
+    connection& m_connection;
+    unique_ptr<active_window> m_active;
+    map<state, label_t> m_statelabels;
+    label_t m_label;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/modules/xworkspaces.hpp b/include/modules/xworkspaces.hpp
new file mode 100644 (file)
index 0000000..d58cf45
--- /dev/null
@@ -0,0 +1,116 @@
+#pragma once
+
+#include <bitset>
+#include <mutex>
+#include <set>
+
+#include "components/config.hpp"
+#include "components/types.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/static_module.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace modules {
+  enum class desktop_state {
+    NONE,
+    ACTIVE,
+    URGENT,
+    EMPTY,
+    OCCUPIED,
+  };
+
+  enum class viewport_state {
+    NONE,
+    FOCUSED,
+    UNFOCUSED,
+  };
+
+  struct desktop {
+    explicit desktop(unsigned int index, desktop_state state, label_t&& label)
+        : index(index), state(state), label(label) {}
+    unsigned int index;
+    desktop_state state;
+    label_t label;
+  };
+
+  struct viewport {
+    position pos;
+    string name;
+    vector<unique_ptr<desktop>> desktops;
+    viewport_state state;
+    label_t label;
+  };
+
+  /**
+   * Module used to display EWMH desktops
+   */
+  class xworkspaces_module : public static_module<xworkspaces_module>, public event_handler<evt::property_notify> {
+   public:
+    explicit xworkspaces_module(const bar_settings& bar, string name_);
+
+    void update();
+    string get_output();
+    bool build(builder* builder, const string& tag) const;
+
+    static constexpr auto TYPE = "internal/xworkspaces";
+
+    static constexpr auto EVENT_FOCUS = "focus";
+    static constexpr auto EVENT_NEXT = "next";
+    static constexpr auto EVENT_PREV = "prev";
+
+   protected:
+    void handle(const evt::property_notify& evt);
+
+    void rebuild_clientlist();
+    void rebuild_desktops();
+    void rebuild_desktop_states();
+    void set_desktop_urgent(xcb_window_t window);
+
+    bool input(const string& action, const string& data);
+
+   private:
+    static vector<string> get_desktop_names();
+
+    static constexpr const char* DEFAULT_ICON{"icon-default"};
+    static constexpr const char* DEFAULT_LABEL_STATE{"%icon% %name%"};
+    static constexpr const char* DEFAULT_LABEL_MONITOR{"%name%"};
+
+    static constexpr const char* TAG_LABEL_MONITOR{"<label-monitor>"};
+    static constexpr const char* TAG_LABEL_STATE{"<label-state>"};
+
+    connection& m_connection;
+    ewmh_connection_t m_ewmh;
+
+    vector<monitor_t> m_monitors;
+    bool m_monitorsupport{true};
+
+    vector<string> m_desktop_names;
+    unsigned int m_current_desktop;
+    string m_current_desktop_name;
+
+    /**
+     * Maps an xcb window to its desktop number
+     */
+    map<xcb_window_t, unsigned int> m_clients;
+    vector<unique_ptr<viewport>> m_viewports;
+    map<desktop_state, label_t> m_labels;
+    label_t m_monitorlabel;
+    iconset_t m_icons;
+    bool m_pinworkspaces{false};
+    bool m_click{true};
+    bool m_scroll{true};
+    size_t m_index{0};
+
+    // The following mutex is here to protect the data of this modules.
+    // This can't be achieved using m_buildlock since we "CRTP override" get_output().
+    mutable mutex m_workspace_mutex;
+  };
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/include/settings.hpp.cmake b/include/settings.hpp.cmake
new file mode 100644 (file)
index 0000000..2099ad5
--- /dev/null
@@ -0,0 +1,76 @@
+#pragma once
+
+#include <cstdio>
+#include <string>
+#include <vector>
+
+extern const char* const APP_NAME;
+extern const char* const APP_VERSION;
+
+#cmakedefine01 ENABLE_ALSA
+#cmakedefine01 ENABLE_MPD
+#cmakedefine01 ENABLE_NETWORK
+#cmakedefine01 WITH_LIBNL
+#cmakedefine01 ENABLE_I3
+#cmakedefine01 ENABLE_CURL
+#cmakedefine01 ENABLE_PULSEAUDIO
+
+#cmakedefine01 WITH_XRANDR
+#cmakedefine01 WITH_XCOMPOSITE
+#cmakedefine01 WITH_XKB
+#cmakedefine01 WITH_XRM
+#cmakedefine01 WITH_XCURSOR
+
+#if WITH_XRANDR
+#cmakedefine01 WITH_XRANDR_MONITORS
+#else
+#define WITH_XRANDR_MONITORS 0
+#endif
+
+#if WITH_XKB
+#cmakedefine01 ENABLE_XKEYBOARD
+#else
+#define ENABLE_XKEYBOARD 0
+#endif
+
+#cmakedefine XPP_EXTENSION_LIST @XPP_EXTENSION_LIST@
+
+#cmakedefine DEBUG_LOGGER
+
+#if DEBUG
+#cmakedefine DEBUG_LOGGER_VERBOSE
+#cmakedefine DEBUG_HINTS
+#cmakedefine DEBUG_WHITESPACE
+#cmakedefine DEBUG_SHADED
+#cmakedefine DEBUG_FONTCONFIG
+#endif
+
+static const int SIGN_PRIORITY_CONTROLLER{1};
+static const int SIGN_PRIORITY_SCREEN{2};
+static const int SIGN_PRIORITY_BAR{3};
+static const int SIGN_PRIORITY_RENDERER{4};
+static const int SIGN_PRIORITY_TRAY{5};
+
+extern const int SINK_PRIORITY_BAR;
+extern const int SINK_PRIORITY_SCREEN;
+extern const int SINK_PRIORITY_TRAY;
+extern const int SINK_PRIORITY_MODULE;
+
+extern const char* const ALSA_SOUNDCARD;
+extern const char* const BSPWM_SOCKET_PATH;
+extern const char* const BSPWM_STATUS_PREFIX;
+extern const char* const CONNECTION_TEST_IP;
+extern const char* const PATH_ADAPTER;
+extern const char* const PATH_BACKLIGHT;
+extern const char* const PATH_BATTERY;
+extern const char* const PATH_CPU_INFO;
+extern const char* const PATH_MEMORY_INFO;
+extern const char* const PATH_MESSAGING_FIFO;
+extern const char* const PATH_TEMPERATURE_INFO;
+extern const char* const WIRELESS_LIB;
+
+bool version_details(const std::vector<std::string>& args);
+
+void print_build_info(bool extended = false);
+
+// vim:ft=cpp
diff --git a/include/utils/actions.hpp b/include/utils/actions.hpp
new file mode 100644 (file)
index 0000000..1aa7453
--- /dev/null
@@ -0,0 +1,31 @@
+#pragma once
+
+#include "common.hpp"
+
+namespace modules {
+  struct module_interface;
+}  // namespace modules
+
+POLYBAR_NS
+
+namespace actions_util {
+
+  using action = std::tuple<string, string, string>;
+
+  string get_action_string(const modules::module_interface& module, string action, string data);
+
+  /**
+   * Parses an action string of the form "#name.action[.data]".
+   *
+   * Only call this function with an action string that begins with '#'.
+   *
+   * \returns a triple (name, action, data)
+   *          If no data exists, the third string will be empty.
+   *          This means "#name.action." and "#name.action" will be produce the
+   *          same result.
+   * \throws runtime_error If the action string is malformed
+   */
+  action parse_action_string(string action);
+}  // namespace actions_util
+
+POLYBAR_NS_END
diff --git a/include/utils/bspwm.hpp b/include/utils/bspwm.hpp
new file mode 100644 (file)
index 0000000..075e256
--- /dev/null
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_icccm.h>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "utils/socket.hpp"
+#include "utils/string.hpp"
+#include "x11/extensions/randr.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace bspwm_util {
+  struct payload;
+  using connection_t = unique_ptr<socket_util::unix_connection>;
+  using payload_t = unique_ptr<payload>;
+
+  struct payload {
+    char data[BUFSIZ]{'\0'};
+    size_t len = 0;
+  };
+
+  vector<xcb_window_t> root_windows(connection& conn);
+  bool restack_to_root(connection& conn, const monitor_t& mon, const xcb_window_t win);
+
+  string get_socket_path();
+
+  payload_t make_payload(const string& cmd);
+  connection_t make_connection();
+  connection_t make_subscriber();
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/color.hpp b/include/utils/color.hpp
new file mode 100644 (file)
index 0000000..e48e19e
--- /dev/null
@@ -0,0 +1,66 @@
+#pragma once
+
+#include <cstdlib>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+/**
+ * Represents immutable 32-bit color values.
+ */
+class rgba {
+ public:
+  enum color_type { NONE, ARGB, ALPHA_ONLY };
+
+  explicit rgba();
+  explicit rgba(uint32_t value, color_type type = ARGB);
+  explicit rgba(string hex);
+
+  operator string() const;
+  operator uint32_t() const;
+  bool operator==(const rgba& other) const;
+
+  uint32_t value() const;
+  color_type type() const;
+
+  double alpha_d() const;
+  double red_d() const;
+  double green_d() const;
+  double blue_d() const;
+
+  uint8_t alpha_i() const;
+  uint8_t red_i() const;
+  uint8_t green_i() const;
+  uint8_t blue_i() const;
+
+  bool has_color() const;
+  rgba apply_alpha_to(rgba other) const;
+  rgba try_apply_alpha_to(rgba other) const;
+
+ private:
+  /**
+   * Color value in the form ARGB or A000 depending on the type
+   *
+   * Cannot be const because we have to assign to it in the constructor and initializer lists are not possible.
+   */
+  uint32_t m_value;
+
+  /**
+   * NONE marks this instance as invalid. If such a color is encountered, it
+   * should be treated as if no color was set.
+   *
+   * ALPHA_ONLY is used for color strings that only have an alpha channel (#AA)
+   * these kinds should be combined with another color that has RGB channels
+   * before they are used to render anything.
+   *
+   * Cannot be const because we have to assign to it in the constructor and initializer lists are not possible.
+   */
+  color_type m_type{NONE};
+};
+
+namespace color_util {
+  string simplify_hex(string hex);
+}  // namespace color_util
+
+POLYBAR_NS_END
diff --git a/include/utils/command.hpp b/include/utils/command.hpp
new file mode 100644 (file)
index 0000000..bc3f853
--- /dev/null
@@ -0,0 +1,121 @@
+#pragma once
+
+#include <mutex>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "utils/factory.hpp"
+#include "utils/functional.hpp"
+
+POLYBAR_NS
+
+DEFINE_ERROR(command_error);
+
+/**
+ * Wrapper used to execute command in a subprocess.
+ * In-/output streams are opened to enable ipc.
+ * If the command is created using command_util::make_command<output_policy::REDIRECTED>, the child streams are
+ * redirected and you can read the program output or write into the program input.
+ *
+ * If the command is created using command_util::make_command<output_policy::IGNORED>, the output is not redirected and
+ * you can't communicate with the child program.
+ *
+ * Example usage:
+ *
+ * \code cpp
+ *   auto cmd = command_util::make_command<output_policy::REDIRECTED>("cat /etc/rc.local");
+ *   cmd->exec();
+ *   cmd->tail([](string s) { std::cout << s << std::endl; });
+ * \endcode
+ *
+ * \code cpp
+ *   auto cmd = command_util::make_command<output_policy::REDIRECTED>(
+ *    "while read -r line; do echo data from parent process: $line; done");
+ *   cmd->exec(false);
+ *   cmd->writeline("Test");
+ *   cout << cmd->readline();
+ *   cmd->wait();
+ * \endcode
+ *
+ * \code cpp
+ *   auto cmd = command_util::make_command<output_policy::REDIRECTED>("for i in 1 2 3; do echo $i; done");
+ *   cmd->exec();
+ *   cout << cmd->readline(); // 1
+ *   cout << cmd->readline() << cmd->readline(); // 23
+ * \endcode
+ *
+ * \code cpp
+ *   auto cmd = command_util::make_command<output_policy::IGNORED>("ping kernel.org");
+ *   int status = cmd->exec();
+ * \endcode
+ */
+template <output_policy>
+class command;
+
+template <>
+class command<output_policy::IGNORED> {
+ public:
+  explicit command(const logger& logger, string cmd);
+  command(const command&) = delete;
+  ~command();
+
+  command& operator=(const command&) = delete;
+
+  int exec(bool wait_for_completion = true);
+  void terminate();
+  bool is_running();
+  int wait();
+
+  pid_t get_pid();
+  int get_exit_status();
+
+ protected:
+  const logger& m_log;
+
+  string m_cmd;
+
+  pid_t m_forkpid{};
+  int m_forkstatus = - 1;
+};
+
+template <>
+class command<output_policy::REDIRECTED> : private command<output_policy::IGNORED> {
+ public:
+  explicit command(const logger& logger, string cmd);
+  command(const command&) = delete;
+  ~command();
+
+  command& operator=(const command&) = delete;
+
+  int exec(bool wait_for_completion = true);
+  using command<output_policy::IGNORED>::terminate;
+  using command<output_policy::IGNORED>::is_running;
+  using command<output_policy::IGNORED>::wait;
+
+  using command<output_policy::IGNORED>::get_pid;
+  using command<output_policy::IGNORED>::get_exit_status;
+
+  void tail(callback<string> cb);
+  int writeline(string data);
+  string readline();
+
+  int get_stdout(int c);
+  int get_stdin(int c);
+
+ protected:
+  int m_stdout[2]{};
+  int m_stdin[2]{};
+
+  std::mutex m_pipelock{};
+};
+
+namespace command_util {
+  template <output_policy OutputType, typename... Args>
+  unique_ptr<command<OutputType>> make_command(Args&&... args) {
+    return factory_util::unique<command<OutputType>>(logger::make(), forward<Args>(args)...);
+  }
+}  // namespace command_util
+
+POLYBAR_NS_END
diff --git a/include/utils/concurrency.hpp b/include/utils/concurrency.hpp
new file mode 100644 (file)
index 0000000..3fde261
--- /dev/null
@@ -0,0 +1,70 @@
+#pragma once
+
+#include <atomic>
+#include <chrono>
+#include <map>
+#include <mutex>
+#include <thread>
+
+#include "common.hpp"
+#include "utils/mixins.hpp"
+
+POLYBAR_NS
+
+namespace chrono =std::chrono;
+using namespace std::chrono_literals;
+namespace this_thread = std::this_thread;
+
+using std::atomic;
+using std::map;
+using std::mutex;
+using std::thread;
+
+class spin_lock : public non_copyable_mixin<spin_lock> {
+ public:
+  struct no_backoff_strategy {
+    bool operator()();
+  };
+  struct yield_backoff_strategy {
+    bool operator()();
+  };
+
+ public:
+  explicit spin_lock() = default;
+
+  template <typename Backoff>
+  void lock(Backoff backoff) noexcept {
+    while (m_locked.test_and_set(std::memory_order_acquire)) {
+      backoff();
+    }
+  }
+
+  void lock() noexcept;
+  void unlock() noexcept;
+
+ protected:
+  std::atomic_flag m_locked{false};
+};
+
+template <typename T>
+class mutex_wrapper : public T {
+ public:
+  template <typename... Args>
+  explicit mutex_wrapper(Args&&... args) : T(forward<Args>(args)...) {}
+
+  void lock() const noexcept {
+    m_mtx.lock();
+  }
+  void unlock() const noexcept {
+    m_mtx.unlock();
+  };
+
+ private:
+  mutable mutex m_mtx;
+};
+
+namespace concurrency_util {
+  size_t thread_id(const thread::id id);
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/env.hpp b/include/utils/env.hpp
new file mode 100644 (file)
index 0000000..8fcc4da
--- /dev/null
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace env_util {
+  bool has(const string& var);
+  string get(const string& var, string fallback = "");
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/factory.hpp b/include/utils/factory.hpp
new file mode 100644 (file)
index 0000000..6e0bbac
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+#include <unistd.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace factory_util {
+  namespace detail {
+    struct null_deleter {
+      template <typename T>
+      void operator()(T*) const {}
+    };
+
+    struct fd_deleter {
+      void operator()(int* fd) const {
+        if (fd != nullptr && *fd > 0) {
+          close(*fd);
+        }
+      }
+    };
+  }
+
+  extern detail::null_deleter null_deleter;
+  extern detail::fd_deleter fd_deleter;
+
+  template <typename T, typename... Deps>
+  unique_ptr<T> unique(Deps&&... deps) {
+    return make_unique<T>(forward<Deps>(deps)...);
+  }
+
+  template <typename T, typename... Deps>
+  shared_ptr<T> shared(Deps&&... deps) {
+    return make_shared<T>(forward<Deps>(deps)...);
+  }
+
+  template <class T, class... Deps>
+  shared_ptr<T> singleton(Deps&&... deps) {
+    static shared_ptr<T> instance{make_shared<T>(forward<Deps>(deps)...)};
+    return instance;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/file.hpp b/include/utils/file.hpp
new file mode 100644 (file)
index 0000000..ba16595
--- /dev/null
@@ -0,0 +1,121 @@
+#pragma once
+
+#include <streambuf>
+
+#include "common.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+class file_ptr {
+ public:
+  explicit file_ptr(const string& path, const string& mode = "a+");
+  ~file_ptr();
+
+  explicit operator bool();
+  operator bool() const;
+
+  explicit operator FILE*();
+  operator FILE*() const;
+
+  explicit operator int();
+  operator int() const;
+
+ private:
+  FILE* m_ptr = nullptr;
+  string m_path;
+  string m_mode;
+};
+
+class file_descriptor {
+ public:
+  explicit file_descriptor(const string& path, int flags = 0);
+  explicit file_descriptor(int fd, bool autoclose = true);
+  ~file_descriptor();
+
+  file_descriptor& operator=(const int);
+
+  explicit operator bool();
+  operator bool() const;
+
+  explicit operator int();
+  operator int() const;
+
+ protected:
+  void close();
+
+ private:
+  int m_fd{-1};
+  bool m_autoclose{true};
+};
+
+class fd_streambuf : public std::streambuf {
+ public:
+  using traits_type = std::streambuf::traits_type;
+
+  template <typename... Args>
+  explicit fd_streambuf(Args&&... args) : m_fd(forward<Args>(args)...) {
+    setg(m_in, m_in, m_in);
+    setp(m_out, m_out + bufsize - 1);
+  }
+  ~fd_streambuf();
+
+  explicit operator int();
+  operator int() const;
+
+  void open(int fd);
+  void close();
+
+ protected:
+  int sync();
+  int overflow(int c);
+  int underflow();
+
+ private:
+  file_descriptor m_fd;
+  enum { bufsize = 1024 };
+  char m_out[bufsize];
+  char m_in[bufsize - 1];
+};
+
+template <typename StreamType>
+class fd_stream : public StreamType {
+ public:
+  using type = fd_stream<StreamType>;
+
+  template <typename... Args>
+  explicit fd_stream(Args&&... args) : StreamType(nullptr), m_buf(forward<Args>(args)...) {
+    StreamType::rdbuf(&m_buf);
+  }
+
+  explicit operator int() {
+    return static_cast<const type&>(*this);
+  }
+
+  operator int() const {
+    return m_buf;
+  }
+
+ protected:
+  fd_streambuf m_buf;
+};
+
+namespace file_util {
+  bool exists(const string& filename);
+  bool is_file(const string& filename);
+  string pick(const vector<string>& filenames);
+  string contents(const string& filename);
+  void write_contents(const string& filename, const string& contents);
+  bool is_fifo(const string& filename);
+  vector<string> glob(string pattern);
+  const string expand(const string& path);
+  string get_config_path();
+  vector<string> list_files(const string& dirname);
+
+  template <typename... Args>
+  decltype(auto) make_file_descriptor(Args&&... args) {
+    return factory_util::unique<file_descriptor>(forward<Args>(args)...);
+  }
+}  // namespace file_util
+
+POLYBAR_NS_END
diff --git a/include/utils/functional.hpp b/include/utils/functional.hpp
new file mode 100644 (file)
index 0000000..9d25470
--- /dev/null
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <functional>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+template <typename... Args>
+using callback = function<void(Args...)>;
+
+POLYBAR_NS_END
diff --git a/include/utils/http.hpp b/include/utils/http.hpp
new file mode 100644 (file)
index 0000000..e6f15c7
--- /dev/null
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "common.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+class http_downloader {
+ public:
+  http_downloader(int connection_timeout = 5);
+  ~http_downloader();
+
+  string get(const string& url, const string& user = "", const string& password = "");
+  long response_code();
+
+ protected:
+  static size_t write(void* p, size_t size, size_t bytes, void* stream);
+
+ private:
+  void* m_curl;
+};
+
+namespace http_util {
+  template <typename... Args>
+  decltype(auto) make_downloader(Args&&... args) {
+    return factory_util::unique<http_downloader>(forward<Args>(args)...);
+  }
+}  // namespace http_util
+
+POLYBAR_NS_END
diff --git a/include/utils/i3.hpp b/include/utils/i3.hpp
new file mode 100644 (file)
index 0000000..ae9f2e9
--- /dev/null
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <i3ipc++/ipc.hpp>
+
+#include "common.hpp"
+#include "x11/extensions/randr.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+namespace i3_util {
+  using connection_t = i3ipc::connection;
+  using workspace_t = i3ipc::workspace_t;
+
+  const auto ws_numsort = [](shared_ptr<workspace_t> a, shared_ptr<workspace_t> b) { return a->num < b->num; };
+
+  vector<shared_ptr<workspace_t>> workspaces(const connection_t& conn, const string& output = "");
+  shared_ptr<workspace_t> focused_workspace(const connection_t&);
+
+  vector<xcb_window_t> root_windows(connection& conn, const string& output_name = "");
+  bool restack_to_root(connection& conn, const xcb_window_t win);
+}
+
+namespace {
+  inline bool operator==(i3_util::workspace_t& a, i3_util::workspace_t& b) {
+    return a.num == b.num && a.output == b.output;
+  }
+  inline bool operator!=(i3_util::workspace_t& a, i3_util::workspace_t& b) {
+    return !(a == b);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/inotify.hpp b/include/utils/inotify.hpp
new file mode 100644 (file)
index 0000000..207d7b0
--- /dev/null
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <poll.h>
+#include <sys/inotify.h>
+#include <cstdio>
+
+#include "common.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+struct inotify_event {
+  string filename;
+  bool is_dir;
+  int wd = 0;
+  int cookie = 0;
+  int mask = 0;
+};
+
+class inotify_watch {
+ public:
+  explicit inotify_watch(string path);
+  ~inotify_watch();
+
+  void attach(int mask = IN_MODIFY);
+  void remove(bool force = false);
+  bool poll(int wait_ms = 1000) const;
+  unique_ptr<inotify_event> get_event() const;
+  unique_ptr<inotify_event> await_match() const;
+  const string path() const;
+  int get_file_descriptor() const;
+
+ protected:
+  string m_path;
+  int m_fd{-1};
+  int m_wd{-1};
+  int m_mask{0};
+};
+
+namespace inotify_util {
+  template <typename... Args>
+  decltype(auto) make_watch(Args&&... args) {
+    return factory_util::unique<inotify_watch>(forward<Args>(args)...);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/io.hpp b/include/utils/io.hpp
new file mode 100644 (file)
index 0000000..921e9d1
--- /dev/null
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace io_util {
+  string read(int read_fd, size_t bytes_to_read);
+  string readline(int read_fd);
+
+  size_t write(int write_fd, size_t bytes_to_write, const string& data);
+  size_t writeline(int write_fd, const string& data);
+
+  void tail(int read_fd, const function<void(string)>& callback);
+  void tail(int read_fd, int writeback_fd);
+
+  bool poll(int fd, short int events, int timeout_ms = 0);
+  bool poll_read(int fd, int timeout_ms = 0);
+  bool poll_write(int fd, int timeout_ms = 0);
+
+  bool interrupt_read(int write_fd);
+
+  void set_block(int fd);
+  void set_nonblock(int fd);
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/math.hpp b/include/utils/math.hpp
new file mode 100644 (file)
index 0000000..d76b218
--- /dev/null
@@ -0,0 +1,118 @@
+#pragma once
+
+#include <algorithm>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace math_util {
+  /**
+   * Get the min value
+   */
+  template <typename ValueType>
+  ValueType min(ValueType one, ValueType two) {
+    return one < two ? one : two;
+  }
+
+  /**
+   * Get the max value
+   */
+  template <typename ValueType>
+  ValueType max(ValueType one, ValueType two) {
+    return one > two ? one : two;
+  }
+
+  /**
+   * Limit value T by min and max bounds
+   */
+  template <typename ValueType>
+  ValueType cap(ValueType value, ValueType min_value, ValueType max_value) {
+    value = std::min<ValueType>(value, max_value);
+    value = std::max<ValueType>(value, min_value);
+    return value;
+  }
+
+  /**
+   * Calculate the percentage for a value
+   * between min_value and max_value
+   */
+  template<typename ValueType, typename ReturnType = int>
+  ReturnType unbounded_percentage(ValueType value, ValueType min_value, ValueType max_value){
+    auto upper = (max_value - min_value);
+    auto lower = static_cast<float>(value - min_value);
+    ValueType percentage = (lower / upper) * 100.0f;
+    if (std::is_integral<ReturnType>())
+      percentage += 0.5f;
+    return percentage;
+  }
+
+  /**
+   * Calculates percentage for a value and
+   * clamps it to  a percentage between 0 and 100
+   */
+  template <typename ValueType, typename ReturnType = int>
+  ReturnType percentage(ValueType value, ValueType min_value, ValueType max_value) {
+    auto raw_percentage = unbounded_percentage<ValueType, ReturnType>(value, min_value, max_value);
+    return cap<ReturnType>(raw_percentage, 0.0f, 100.0f);
+  }
+
+  /**
+   * Calculates percentage for a value and
+   * clamps it to  a percentage between 0 and 100
+   */
+  template <typename ValueType, typename ReturnType = int>
+  ReturnType percentage(ValueType value, ValueType max_value) {
+    return percentage<ValueType, ReturnType>(value, max_value - max_value, max_value);
+  }
+
+  /**
+   * Get value for signed percentage of `max_value` (cap between -max_value and max_value)
+   */
+  template <typename ValueType, typename ReturnType = int>
+  ReturnType signed_percentage_to_value(ValueType signed_percentage, ValueType max_value) {
+    if (std::is_integral<ReturnType>())
+      return cap<ReturnType>(signed_percentage * max_value / 100.0f + 0.5f, -max_value, max_value);
+    else
+      return cap<ReturnType>(signed_percentage * max_value / 100.0f, -max_value, max_value);
+  }
+
+  /**
+   * Get value for percentage of `max_value` (cap between 0 and max_value)
+   */
+  template <typename ValueType, typename ReturnType = int>
+  ReturnType percentage_to_value(ValueType percentage, ValueType max_value) {
+    if (std::is_integral<ReturnType>())
+      return cap<ReturnType>(percentage * max_value / 100.0f + 0.5f, 0, max_value);
+    else
+      return cap<ReturnType>(percentage * max_value / 100.0f, 0.0f, max_value);
+  }
+
+  /**
+   * Get value for percentage of `min_value` to `max_value`
+   */
+  template <typename ValueType, typename ReturnType = int>
+  ReturnType percentage_to_value(ValueType percentage, ValueType min_value, ValueType max_value) {
+    if (std::is_integral<ReturnType>())
+      return cap<ReturnType>(percentage * (max_value - min_value) / 100.0f + 0.5f, 0, max_value - min_value) +
+             min_value;
+    else
+      return cap<ReturnType>(percentage * (max_value - min_value) / 100.0f, 0.0f, max_value - min_value) + min_value;
+  }
+
+  template <typename ReturnType = int>
+  ReturnType nearest_10(double value) {
+    return static_cast<ReturnType>(static_cast<int>(value / 10.0 + 0.5) * 10.0);
+  }
+
+  template <typename ReturnType = int>
+  ReturnType nearest_5(double value) {
+    return static_cast<ReturnType>(static_cast<int>(value / 5.0 + 0.5) * 5.0);
+  }
+
+  inline int ceil(double value, int step = 1) {
+    return static_cast<int>((value * 10 + step * 10 - 1) / (step * 10)) * step;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/memory.hpp b/include/utils/memory.hpp
new file mode 100644 (file)
index 0000000..d190f45
--- /dev/null
@@ -0,0 +1,39 @@
+#pragma once
+
+#include <cstdlib>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+template <typename T>
+using malloc_ptr_t = shared_ptr<T>;
+
+namespace memory_util {
+  /**
+   * Create a shared pointer using malloc/free
+   *
+   * Generates a noexcept-type warning because the mangled name for
+   * malloc_ptr_t will change in C++17. This doesn't affect use because we have
+   * no public ABI, so we ignore it here.
+   * See also this SO answer: https://stackoverflow.com/a/46857525/5363071
+   */
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wnoexcept-type"
+  template <typename T, size_t Size = sizeof(T), typename Deleter = decltype(std::free)>
+  inline malloc_ptr_t<T> make_malloc_ptr(Deleter deleter = std::free) {
+    return malloc_ptr_t<T>(static_cast<T*>(calloc(1, Size)), deleter);
+  }
+#pragma GCC diagnostic pop
+
+  /**
+   * Get the number of elements in T
+   */
+  template <typename T>
+  inline auto countof(T& p) {
+    return sizeof(p) / sizeof(p[0]);
+  }
+}  // namespace memory_util
+
+POLYBAR_NS_END
diff --git a/include/utils/mixins.hpp b/include/utils/mixins.hpp
new file mode 100644 (file)
index 0000000..bbf1e8c
--- /dev/null
@@ -0,0 +1,35 @@
+#pragma once
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+/**
+ * Base class for non copyable objects
+ */
+template <class T>
+class non_copyable_mixin {
+ 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 {
+ protected:
+  non_movable_mixin() {}
+  ~non_movable_mixin() {}
+
+ private:
+  non_movable_mixin(non_movable_mixin&&);
+  non_movable_mixin& operator=(non_movable_mixin&&);
+};
+
+POLYBAR_NS_END
diff --git a/include/utils/process.hpp b/include/utils/process.hpp
new file mode 100644 (file)
index 0000000..3ab5fd7
--- /dev/null
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <sys/types.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace process_util {
+  bool in_parent_process(pid_t pid);
+  bool in_forked_process(pid_t pid);
+
+  void redirect_stdio_to_dev_null();
+
+  pid_t spawn_async(std::function<void()> const& lambda);
+  void fork_detached(std::function<void()> const& lambda);
+
+  void exec(char* cmd, char** args);
+  void exec_sh(const char* cmd);
+
+  int wait(pid_t pid);
+
+  pid_t wait_for_completion(pid_t process_id, int* status_addr = nullptr, int waitflags = 0);
+  pid_t wait_for_completion(int* status_addr, int waitflags = 0);
+  pid_t wait_for_completion_nohang(pid_t process_id, int* status);
+  pid_t wait_for_completion_nohang(int* status);
+  pid_t wait_for_completion_nohang();
+
+  bool notify_childprocess();
+}  // namespace process_util
+
+POLYBAR_NS_END
diff --git a/include/utils/scope.hpp b/include/utils/scope.hpp
new file mode 100644 (file)
index 0000000..1d0a0eb
--- /dev/null
@@ -0,0 +1,44 @@
+#pragma once
+
+// TODO: move to functional.hpp
+
+#include "common.hpp"
+
+#include "components/logger.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+namespace scope_util {
+  template <typename... Args>
+  class on_exit {
+   public:
+    on_exit(function<void(Args...)>&& fn, Args... args) : m_callback(bind(fn, args...)) {}
+
+    virtual ~on_exit() {
+      m_callback();
+    }
+
+   protected:
+    function<void()> m_callback;
+  };
+
+  /**
+   * Creates a wrapper that will trigger given callback when
+   * leaving the object's scope (i.e, when it gets destroyed)
+   *
+   * Example usage:
+   * \code cpp
+   *   {
+   *     auto handler = scope_util::make_exit_handler([]{ ... })
+   *     ...
+   *   }
+   * \endcode
+   */
+  template <typename Fn = function<void()>, typename... Args>
+  decltype(auto) make_exit_handler(Fn&& fn, Args&&... args) {
+    return factory_util::unique<on_exit<Args...>>(forward<Fn>(fn), forward<Args>(args)...);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/socket.hpp b/include/utils/socket.hpp
new file mode 100644 (file)
index 0000000..96f36a4
--- /dev/null
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <poll.h>
+
+#include "common.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+namespace socket_util {
+  class unix_connection {
+   public:
+    explicit unix_connection(string&& path);
+
+    ~unix_connection() noexcept;
+
+    int disconnect();
+
+    ssize_t send(const void* data, size_t len, int flags = 0);
+    ssize_t send(const string& data, int flags = 0);
+
+    string receive(const ssize_t receive_bytes, int flags = 0);
+    string receive(const ssize_t receive_bytes, ssize_t* bytes_received, int flags = 0);
+
+    bool peek(const size_t peek_bytes);
+    bool poll(short int events = POLLIN, int timeout_ms = -1);
+
+   protected:
+    int m_fd = -1;
+    string m_socketpath;
+  };
+
+  /**
+   * Creates a wrapper for a unix socket connection
+   *
+   * Example usage:
+   * \code cpp
+   *   auto conn = socket_util::make_unix_connection("/tmp/socket");
+   *   conn->send(...);
+   *   conn->receive(...);
+   * \endcode
+   */
+  inline unique_ptr<unix_connection> make_unix_connection(string&& path) {
+    return factory_util::unique<unix_connection>(forward<string>(path));
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/string.hpp b/include/utils/string.hpp
new file mode 100644 (file)
index 0000000..270fc97
--- /dev/null
@@ -0,0 +1,104 @@
+#pragma once
+
+#include <sstream>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace {
+  /**
+   * Overload that allows sub-string removal at the end of given string
+   */
+  inline string& operator-(string& a, const string& b) {
+    if (a.size() >= b.size() && a.substr(a.size() - b.size()) == b) {
+      return a.erase(a.size() - b.size());
+    } else {
+      return a;
+    }
+  }
+
+  /**
+   * Overload that allows sub-string removal at the end of given string
+   */
+  inline void operator-=(string& a, const string& b) {
+    if (a.size() >= b.size() && a.substr(a.size() - b.size()) == b) {
+      a.erase(a.size() - b.size());
+    }
+  }
+}  // namespace
+
+class sstream {
+ public:
+  sstream() : m_stream() {}
+
+  template <typename T>
+  sstream& operator<<(const T& object) {
+    m_stream << object;
+    return *this;
+  }
+
+  sstream& operator<<(const char* cz) {
+    m_stream << cz;
+    return *this;
+  }
+
+  operator string() const {
+    return m_stream.str();
+  }
+
+  const string to_string() const {
+    return m_stream.str();
+  }
+
+ private:
+  std::stringstream m_stream;
+};
+
+namespace string_util {
+  /**
+   * Hash type
+   */
+  using hash_type = unsigned long;
+
+  bool contains(const string& haystack, const string& needle);
+  string upper(const string& s);
+  string lower(const string& s);
+  bool compare(const string& s1, const string& s2);
+
+  string replace(const string& haystack, const string& needle, const string& replacement, size_t start = 0,
+      size_t end = string::npos);
+  string replace_all(const string& haystack, const string& needle, const string& replacement, size_t start = 0,
+      size_t end = string::npos);
+
+  string squeeze(const string& haystack, char needle);
+
+  string strip(const string& haystack, char needle);
+  string strip_trailing_newline(const string& haystack);
+
+  string ltrim(string value, function<bool(char)> pred);
+  string rtrim(string value, function<bool(char)> pred);
+  string trim(string value, function<bool(char)> pred);
+
+  string ltrim(string&& value, const char& needle = ' ');
+  string rtrim(string&& value, const char& needle = ' ');
+  string trim(string&& value, const char& needle = ' ');
+
+  size_t char_len(const string& value);
+  string utf8_truncate(string&& value, size_t len);
+
+  string join(const vector<string>& strs, const string& delim);
+  vector<string> split(const string& s, char delim);
+  std::vector<std::string> tokenize(const string& str, char delimiters);
+
+  size_t find_nth(const string& haystack, size_t pos, const string& needle, size_t nth);
+
+  string floating_point(double value, size_t precision, bool fixed = false, const string& locale = "");
+  string filesize_mib(unsigned long long kibibytes, size_t precision = 0, const string& locale = "");
+  string filesize_gib(unsigned long long kibibytes, size_t precision = 0, const string& locale = "");
+  string filesize(unsigned long long kbytes, size_t precision = 0, bool fixed = false, const string& locale = "");
+
+  hash_type hash(const string& src);
+}  // namespace string_util
+
+POLYBAR_NS_END
diff --git a/include/utils/throttle.hpp b/include/utils/throttle.hpp
new file mode 100644 (file)
index 0000000..be20708
--- /dev/null
@@ -0,0 +1,92 @@
+#pragma once
+
+#include <chrono>
+#include <deque>
+
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+namespace throttle_util {
+  using timewindow = chrono::duration<double, std::milli>;
+  using timepoint_clock = chrono::high_resolution_clock;
+  using timepoint = timepoint_clock::time_point;
+  using queue = std::deque<timepoint>;
+  using limit = size_t;
+
+  namespace strategy {
+    struct try_once_or_leave_yolo {
+      bool operator()(queue& q, limit l, timewindow);
+    };
+    struct wait_patiently_by_the_door {
+      bool operator()(queue& q, limit l, timewindow);
+    };
+  }
+
+  /**
+   * Throttle events within a set window of time
+   *
+   * Example usage:
+   * \code cpp
+   *   auto t = throttle_util::make_throttler(2, 1s);
+   *   if (t->passthrough())
+   *     ...
+   * \endcode
+   */
+  class event_throttler {
+   public:
+    /**
+     * Construct throttler
+     */
+    explicit event_throttler(int limit, timewindow timewindow) : m_limit(limit), m_timewindow(timewindow) {}
+
+    /**
+     * Check if event is allowed to pass
+     * using specified strategy
+     */
+    template <typename Strategy>
+    bool passthrough(Strategy wait_strategy) {
+      expire_timestamps();
+      return wait_strategy(m_queue, m_limit, m_timewindow);
+    }
+
+    /**
+     * Check if event is allowed to pass
+     * using default strategy
+     */
+    bool passthrough() {
+      return passthrough(strategy::try_once_or_leave_yolo{});
+    }
+
+   protected:
+    /**
+     * Expire old timestamps
+     */
+    void expire_timestamps() {
+      auto now = timepoint_clock::now();
+      while (m_queue.size() > 0) {
+        if ((now - m_queue.front()) < m_timewindow)
+          break;
+        m_queue.pop_front();
+      }
+    }
+
+   private:
+    queue m_queue{};
+    limit m_limit{};
+    timewindow m_timewindow{};
+  };
+
+  using throttle_t = unique_ptr<event_throttler>;
+
+  template <typename... Args>
+  throttle_t make_throttler(Args&&... args) {
+    return factory_util::unique<event_throttler>(forward<Args>(args)...);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/utils/time.hpp b/include/utils/time.hpp
new file mode 100644 (file)
index 0000000..3cf5726
--- /dev/null
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <chrono>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+namespace time_util {
+  using clock_t = chrono::high_resolution_clock;
+
+  template <typename Duration = chrono::milliseconds, typename T>
+  auto measure(const T& expr) noexcept {
+    auto start = clock_t::now();
+    expr();
+    auto finish = clock_t::now();
+    return chrono::duration_cast<Duration>(finish - start).count();
+  }
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/atoms.hpp b/include/x11/atoms.hpp
new file mode 100644 (file)
index 0000000..10611d8
--- /dev/null
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <xcb/xcb_atom.h>
+
+struct cached_atom {
+  const char* name;
+  size_t len;
+  xcb_atom_t* atom;
+};
+
+extern cached_atom ATOMS[36];
+
+extern xcb_atom_t _NET_SUPPORTED;
+extern xcb_atom_t _NET_CURRENT_DESKTOP;
+extern xcb_atom_t _NET_ACTIVE_WINDOW;
+extern xcb_atom_t _NET_WM_NAME;
+extern xcb_atom_t _NET_WM_DESKTOP;
+extern xcb_atom_t _NET_WM_VISIBLE_NAME;
+extern xcb_atom_t _NET_WM_WINDOW_TYPE;
+extern xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK;
+extern xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
+extern xcb_atom_t _NET_WM_PID;
+extern xcb_atom_t _NET_WM_STATE;
+extern xcb_atom_t _NET_WM_STATE_STICKY;
+extern xcb_atom_t _NET_WM_STATE_SKIP_TASKBAR;
+extern xcb_atom_t _NET_WM_STATE_ABOVE;
+extern xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
+extern xcb_atom_t _NET_WM_STRUT;
+extern xcb_atom_t _NET_WM_STRUT_PARTIAL;
+extern xcb_atom_t WM_PROTOCOLS;
+extern xcb_atom_t WM_DELETE_WINDOW;
+extern xcb_atom_t _XEMBED;
+extern xcb_atom_t _XEMBED_INFO;
+extern xcb_atom_t MANAGER;
+extern xcb_atom_t WM_STATE;
+extern xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
+extern xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION;
+extern xcb_atom_t _NET_SYSTEM_TRAY_VISUAL;
+extern xcb_atom_t _NET_SYSTEM_TRAY_COLORS;
+extern xcb_atom_t WM_TAKE_FOCUS;
+extern xcb_atom_t Backlight;
+extern xcb_atom_t BACKLIGHT;
+extern xcb_atom_t _XROOTPMAP_ID;
+extern xcb_atom_t _XSETROOT_ID;
+extern xcb_atom_t ESETROOT_PMAP_ID;
+extern xcb_atom_t _COMPTON_SHADOW;
+extern xcb_atom_t _NET_WM_WINDOW_OPACITY;
+extern xcb_atom_t WM_HINTS;
diff --git a/include/x11/background_manager.hpp b/include/x11/background_manager.hpp
new file mode 100644 (file)
index 0000000..b887d59
--- /dev/null
@@ -0,0 +1,125 @@
+#pragma once
+
+#include <memory>
+#include <vector>
+
+#include "common.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "events/types.hpp"
+#include "x11/extensions/fwd.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+class logger;
+
+namespace cairo {
+  class surface;
+  class xcb_surface;
+}
+
+class bg_slice {
+ public:
+  ~bg_slice();
+  // copying bg_slices is not allowed
+  bg_slice(const bg_slice&) = delete;
+  bg_slice& operator=(const bg_slice&) = delete;
+
+  /**
+   * Get the current desktop background at the location of this slice.
+   * The returned pointer is only valid as long as the slice itself is alive.
+   *
+   * This function is fast, since the current desktop background is cached.
+   */
+  cairo::surface* get_surface() const {
+    return m_surface.get();
+  }
+
+ private:
+  bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window, xcb_visualtype_t* visual);
+
+  // standard components
+  connection& m_connection;
+
+  // area covered by this slice
+  xcb_rectangle_t m_rect{0, 0, 0U, 0U};
+  xcb_window_t m_window;
+
+  // cache for the root window background at this slice's position
+  xcb_pixmap_t m_pixmap{XCB_NONE};
+  unique_ptr<cairo::xcb_surface> m_surface;
+  xcb_gcontext_t m_gcontext{XCB_NONE};
+
+  void allocate_resources(const logger& log, xcb_visualtype_t* visual);
+  void free_resources();
+
+  friend class background_manager;
+};
+
+/**
+ * \brief Class to keep track of the desktop background used to support pseudo-transparency
+ *
+ * For pseudo-transparency that bar needs access to the desktop background.
+ * We only need to store the slice of the background image which is covered by the bar window,
+ * so this class takes a rectangle that limits what part of the background is stored.
+ */
+class background_manager : public signal_receiver<SIGN_PRIORITY_SCREEN, signals::ui::update_geometry>,
+                           public xpp::event::sink<evt::property_notify>
+{
+ public:
+  using make_type = background_manager&;
+  static make_type make();
+
+  /**
+   * Initializes a new background_manager that by default does not observe anything.
+   *
+   * To observe a slice of the background you need to call background_manager::activate.
+   */
+  explicit background_manager(connection& conn, signal_emitter& sig, const logger& log);
+  ~background_manager();
+
+  /**
+   * Starts observing a rectangular slice of the desktop background.
+   *
+   * After calling this function, you can obtain the current slice of the desktop background
+   * by calling get_surface on the returned bg_slice object.
+   * Whenever the background slice changes (for example, due to bar position changes or because
+   * the user changed the desktop background) the class emits a signals::ui::update_background event.
+   *
+   * You should only call this function once and then re-use the returned bg_slice because the bg_slice
+   * caches the background. If you don't need the background anymore, destroy the shared_ptr to free up
+   * resources.
+   *
+   * \param rect Slice of the background to observe (coordinates relative to window).
+   * \param window Coordinates are interpreted relative to this window
+   */
+  std::shared_ptr<bg_slice> observe(xcb_rectangle_t rect, xcb_window_t window);
+
+  void handle(const evt::property_notify& evt);
+  bool on(const signals::ui::update_geometry&);
+ private:
+  void activate();
+  void deactivate();
+
+  // references to standard components
+  connection& m_connection;
+  signal_emitter& m_sig;
+  const logger& m_log;
+
+  // list of slices that need to be filled with the desktop background
+  std::vector<std::weak_ptr<bg_slice>> m_slices;
+
+  // required values for fetching the root window's background
+  xcb_visualtype_t* m_visual{nullptr};
+
+  // true if we are currently attached as a listener for desktop background changes
+  bool m_attached{false};
+
+  void allocate_resources();
+  void free_resources();
+  void fetch_root_pixmap();
+
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/connection.hpp b/include/x11/connection.hpp
new file mode 100644 (file)
index 0000000..8931ac8
--- /dev/null
@@ -0,0 +1,173 @@
+#pragma once
+
+#include <xcb/xcb.h>
+#include <cstdlib>
+#include <xpp/core.hpp>
+#include <xpp/generic/factory.hpp>
+#include <xpp/proto/x.hpp>
+
+#include "common.hpp"
+#include "components/screen.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/registry.hpp"
+#include "x11/types.hpp"
+
+POLYBAR_NS
+
+namespace detail {
+  template <typename Connection, typename... Extensions>
+  class interfaces : public xpp::x::extension::interface<interfaces<Connection, Extensions...>, Connection>,
+                     public Extensions::template interface<interfaces<Connection, Extensions...>, Connection>... {
+   public:
+    const Connection& connection() const {
+      return static_cast<const Connection&>(*this);
+    }
+  };
+
+  template <typename Derived, typename... Extensions>
+  class connection_base : public xpp::core,
+                          public xpp::generic::error_dispatcher,
+                          public detail::interfaces<connection_base<Derived, Extensions...>, Extensions...>,
+                          private xpp::x::extension,
+                          private xpp::x::extension::error_dispatcher,
+                          private Extensions...,
+                          private Extensions::error_dispatcher... {
+   public:
+    explicit connection_base(xcb_connection_t* c, int s)
+        : xpp::core(c)
+        , interfaces<connection_base<Derived, Extensions...>, Extensions...>(*this)
+        , Extensions(m_c.get())...
+        , Extensions::error_dispatcher(static_cast<Extensions&>(*this).get())... {
+      core::m_screen = s;
+      m_root_window = screen_of_display(default_screen())->root;
+    }
+
+    virtual ~connection_base() {}
+
+    void operator()(const shared_ptr<xcb_generic_error_t>& error) const {
+      check<xpp::x::extension, Extensions...>(error);
+    }
+
+    template <typename Extension>
+    const Extension& extension() const {
+      return static_cast<const Extension&>(*this);
+    }
+
+    template <typename WindowType = xcb_window_t>
+    WindowType root() const {
+      using make = xpp::generic::factory::make<connection_base, xcb_window_t, WindowType>;
+      return make()(*this, m_root_window);
+    }
+
+    shared_ptr<xcb_generic_event_t> wait_for_event() const {
+      try {
+        return core::wait_for_event();
+      } catch (const shared_ptr<xcb_generic_error_t>& error) {
+        check<xpp::x::extension, Extensions...>(error);
+      }
+      throw;  // re-throw exception
+    }
+
+    shared_ptr<xcb_generic_event_t> wait_for_special_event(xcb_special_event_t* se) const {
+      try {
+        return core::wait_for_special_event(se);
+      } catch (const shared_ptr<xcb_generic_error_t>& error) {
+        check<xpp::x::extension, Extensions...>(error);
+      }
+      throw;  // re-throw exception
+    }
+
+   private:
+    xcb_window_t m_root_window;
+
+    template <typename Extension, typename Next, typename... Rest>
+    void check(const shared_ptr<xcb_generic_error_t>& error) const {
+      check<Extension>(error);
+      check<Next, Rest...>(error);
+    }
+
+    template <typename Extension>
+    void check(const shared_ptr<xcb_generic_error_t>& error) const {
+      using error_dispatcher = typename Extension::error_dispatcher;
+      auto& dispatcher = static_cast<const error_dispatcher&>(*this);
+      dispatcher(error);
+    }
+  };
+}
+
+class connection : public detail::connection_base<connection&, XPP_EXTENSION_LIST> {
+ public:
+  using base_type = detail::connection_base<connection&, XPP_EXTENSION_LIST>;
+
+  using make_type = connection&;
+  static make_type make(xcb_connection_t* conn = nullptr, int default_screen = 0);
+
+  explicit connection(xcb_connection_t* c, int default_screen);
+  ~connection();
+
+  const connection& operator=(const connection& o) {
+    return o;
+  }
+
+  static void pack_values(unsigned int mask, const unsigned int* src, unsigned int* dest);
+  static void pack_values(unsigned int mask, const xcb_params_cw_t* src, unsigned int* dest);
+  static void pack_values(unsigned int mask, const xcb_params_gc_t* src, unsigned int* dest);
+  static void pack_values(unsigned int mask, const xcb_params_configure_window_t* src, unsigned int* dest);
+
+  xcb_screen_t* screen(bool realloc = false);
+
+  string id(xcb_window_t w) const;
+
+  void ensure_event_mask(xcb_window_t win, unsigned int event);
+  void clear_event_mask(xcb_window_t win);
+
+  shared_ptr<xcb_client_message_event_t> make_client_message(xcb_atom_t type, xcb_window_t target) const;
+  void send_client_message(const shared_ptr<xcb_client_message_event_t>& message, xcb_window_t target,
+      unsigned int event_mask = 0xFFFFFF, bool propagate = false) const;
+
+  xcb_visualtype_t* visual_type(xcb_screen_t* screen, int match_depth = 32);
+  xcb_visualtype_t* visual_type_for_id(xcb_screen_t* screen, xcb_visualid_t visual_id);
+
+  bool root_pixmap(xcb_pixmap_t* pixmap, int* depth, xcb_rectangle_t* rect);
+
+  static string error_str(int error_code);
+
+  void dispatch_event(const shared_ptr<xcb_generic_event_t>& evt) const;
+
+  template <typename Event, unsigned int ResponseType>
+  void wait_for_response(function<bool(const Event*)> check_event) {
+    int fd = get_file_descriptor();
+    shared_ptr<xcb_generic_event_t> evt{};
+    while (!connection_has_error()) {
+      fd_set fds;
+      FD_ZERO(&fds);
+      FD_SET(fd, &fds);
+
+      if (!select(fd + 1, &fds, nullptr, nullptr, nullptr)) {
+        continue;
+      } else if ((evt = shared_ptr<xcb_generic_event_t>(xcb_poll_for_event(*this), free)) == nullptr) {
+        continue;
+      } else if (evt->response_type != ResponseType) {
+        continue;
+      } else if (check_event(reinterpret_cast<const Event*>(&*evt))) {
+        break;
+      }
+    }
+  }
+
+  template <typename Sink>
+  void attach_sink(Sink&& sink, registry::priority prio = 0) {
+    m_registry.attach(prio, forward<Sink>(sink));
+  }
+
+  template <typename Sink>
+  void detach_sink(Sink&& sink, registry::priority prio = 0) {
+    m_registry.detach(prio, forward<Sink>(sink));
+  }
+
+ protected:
+  registry m_registry{*this};
+  xcb_screen_t* m_screen{nullptr};
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/cursor.hpp b/include/x11/cursor.hpp
new file mode 100644 (file)
index 0000000..84c3325
--- /dev/null
@@ -0,0 +1,27 @@
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XCURSOR
+#error "Not built with support for xcb-cursor..."
+#endif
+
+#include <xcb/xcb_cursor.h>
+
+#include "common.hpp"
+#include "x11/connection.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace cursor_util {
+  static const std::map<string, vector<string>> cursors = {
+    {"pointer", {"pointing_hand", "pointer", "hand", "hand1", "hand2", "e29285e634086352946a0e7090d73106", "9d800788f1b08800ae810202380a0822"}},
+    {"default", {"left_ptr", "arrow", "dnd-none", "op_left_arrow"}},
+    {"ns-resize", {"size_ver", "sb_v_double_arrow", "v_double_arrow", "n-resize", "s-resize", "col-resize", "top_side", "bottom_side", "base_arrow_up", "base_arrow_down", "based_arrow_down", "based_arrow_up", "00008160000006810000408080010102"}}
+  };
+  bool valid(string name);
+  bool set_cursor(xcb_connection_t *c, xcb_screen_t *screen, xcb_window_t w, string name);
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/ewmh.hpp b/include/x11/ewmh.hpp
new file mode 100644 (file)
index 0000000..e6fb137
--- /dev/null
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <xcb/xcb_ewmh.h>
+
+#include "common.hpp"
+#include "utils/memory.hpp"
+
+POLYBAR_NS
+
+struct position;
+
+using ewmh_connection_t = malloc_ptr_t<xcb_ewmh_connection_t>;
+
+namespace ewmh_util {
+  ewmh_connection_t initialize();
+
+  bool supports(xcb_atom_t atom, int screen = 0);
+
+  string get_wm_name(xcb_window_t win);
+  string get_visible_name(xcb_window_t win);
+  string get_icon_name(xcb_window_t win);
+  string get_reply_string(xcb_ewmh_get_utf8_strings_reply_t* reply);
+
+  vector<position> get_desktop_viewports(int screen = 0);
+  vector<string> get_desktop_names(int screen = 0);
+  unsigned int get_current_desktop(int screen = 0);
+  unsigned int get_number_of_desktops(int screen = 0);
+  xcb_window_t get_active_window(int screen = 0);
+
+  void change_current_desktop(unsigned int desktop);
+  unsigned int get_desktop_from_window(xcb_window_t window);
+
+  void set_wm_window_type(xcb_window_t win, vector<xcb_atom_t> types);
+
+  void set_wm_state(xcb_window_t win, vector<xcb_atom_t> states);
+  vector<xcb_atom_t> get_wm_state(xcb_window_t win);
+
+  void set_wm_pid(xcb_window_t win);
+  void set_wm_pid(xcb_window_t win, unsigned int pid);
+
+  void set_wm_desktop(xcb_window_t win, unsigned int desktop = -1u);
+  void set_wm_window_opacity(xcb_window_t win, unsigned long int values);
+
+  vector<xcb_window_t> get_client_list(int screen = 0);
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/extensions/all.hpp b/include/x11/extensions/all.hpp
new file mode 100644 (file)
index 0000000..501c07e
--- /dev/null
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "settings.hpp"
+
+#if WITH_XRANDR
+#include "x11/extensions/randr.hpp"
+#endif
+#include "x11/extensions/composite.hpp"
+#if WITH_XKB
+#include "x11/extensions/xkb.hpp"
+#endif
diff --git a/include/x11/extensions/composite.hpp b/include/x11/extensions/composite.hpp
new file mode 100644 (file)
index 0000000..6922d5d
--- /dev/null
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XCOMPOSITE
+#error "X Composite extension is disabled..."
+#endif
+
+#include <xcb/composite.h>
+#include <xpp/proto/composite.hpp>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+// fwd
+class connection;
+
+namespace composite_util {
+  void query_extension(connection& conn);
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/extensions/fwd.hpp b/include/x11/extensions/fwd.hpp
new file mode 100644 (file)
index 0000000..71b025f
--- /dev/null
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "settings.hpp"
+
+namespace xpp {
+#if WITH_XRANDR
+  namespace randr {
+    class extension;
+  }
+#endif
+#if WITH_XCOMPOSITE
+  namespace composite {
+    class extension;
+  }
+#endif
+#if WITH_XKB
+  namespace xkb {
+    class extension;
+  }
+#endif
+}
diff --git a/include/x11/extensions/randr.hpp b/include/x11/extensions/randr.hpp
new file mode 100644 (file)
index 0000000..4eeef38
--- /dev/null
@@ -0,0 +1,68 @@
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XRANDR
+#error "X RandR extension is disabled..."
+#endif
+
+#include <xcb/randr.h>
+
+#include <xpp/proto/randr.hpp>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class connection;
+struct position;
+
+namespace evt {
+  using randr_notify = xpp::randr::event::notify<connection&>;
+  using randr_screen_change_notify = xpp::randr::event::screen_change_notify<connection&>;
+}  // namespace evt
+
+struct backlight_values {
+  unsigned int atom{0};
+  double min{0.0};
+  double max{0.0};
+  double val{0.0};
+};
+
+struct randr_output {
+  string name;
+  unsigned short int w{0U};
+  unsigned short int h{0U};
+  short int x{0};
+  short int y{0};
+  xcb_randr_output_t output;
+  backlight_values backlight;
+  bool primary{false};
+
+  bool match(const string& o, bool exact = true) const;
+  bool match(const position& p) const;
+
+  bool contains(const position& p) const;
+  bool contains(const randr_output& output) const;
+
+  bool equals(const randr_output& output) const;
+};
+
+using monitor_t = shared_ptr<randr_output>;
+
+namespace randr_util {
+  void query_extension(connection& conn);
+
+  bool check_monitor_support();
+
+  monitor_t make_monitor(xcb_randr_output_t randr, string name, unsigned short int w, unsigned short int h, short int x,
+      short int y, bool primary);
+  vector<monitor_t> get_monitors(
+      connection& conn, xcb_window_t root, bool connected_only = false, bool purge_clones = true);
+  monitor_t match_monitor(vector<monitor_t> monitors, const string& name, bool exact_match);
+
+  void get_backlight_range(connection& conn, const monitor_t& mon, backlight_values& dst);
+  void get_backlight_value(connection& conn, const monitor_t& mon, backlight_values& dst);
+}  // namespace randr_util
+
+POLYBAR_NS_END
diff --git a/include/x11/extensions/xkb.hpp b/include/x11/extensions/xkb.hpp
new file mode 100644 (file)
index 0000000..06f7f1f
--- /dev/null
@@ -0,0 +1,95 @@
+#pragma once
+
+#include <map>
+
+#include "settings.hpp"
+
+#if not WITH_XKB
+#error "X xkb extension is disabled..."
+#endif
+
+#if defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wreserved-id-macro"
+#pragma clang diagnostic ignored "-Wkeyword-macro"
+#endif
+#define explicit mask_cxx_explicit_keyword
+#include <xcb/xkb.h>
+#undef explicit
+#include <xpp/proto/xkb.hpp>
+#if defined(__clang__)
+#pragma clang diagnostic pop
+#endif
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+using std::map;
+
+// fwd
+class connection;
+
+namespace evt {
+  using xkb_new_keyboard_notify = xpp::xkb::event::new_keyboard_notify<connection&>;
+  using xkb_map_notify = xpp::xkb::event::map_notify<connection&>;
+  using xkb_state_notify = xpp::xkb::event::state_notify<connection&>;
+  using xkb_controls_notify = xpp::xkb::event::controls_notify<connection&>;
+  using xkb_indicator_state_notify = xpp::xkb::event::indicator_state_notify<connection&>;
+  using xkb_indicator_map_notify = xpp::xkb::event::indicator_map_notify<connection&>;
+  using xkb_names_notify = xpp::xkb::event::names_notify<connection&>;
+  using xkb_compat_map_notify = xpp::xkb::event::compat_map_notify<connection&>;
+  using xkb_bell_notify = xpp::xkb::event::bell_notify<connection&>;
+  using xkb_action_message = xpp::xkb::event::action_message<connection&>;
+  using xkb_access_x_notify = xpp::xkb::event::access_x_notify<connection&>;
+  using xkb_extension_device_notify = xpp::xkb::event::extension_device_notify<connection&>;
+}
+
+class keyboard {
+ public:
+  struct indicator {
+    enum class type { NONE = 0U, CAPS_LOCK, NUM_LOCK, SCROLL_LOCK };
+    xcb_atom_t atom{};
+    unsigned char mask{0};
+    string name{};
+    bool enabled{false};
+  };
+
+  struct layout {
+    string group_name{};
+    vector<string> symbols{};
+  };
+
+  explicit keyboard(vector<layout>&& layouts_, map<indicator::type, indicator>&& indicators_, unsigned char group)
+      : layouts(forward<decltype(layouts)>(layouts_)), indicators(forward<decltype(indicators)>(indicators_)), current_group(group) {}
+
+  const indicator& get(const indicator::type& i) const;
+  void set(unsigned int state);
+  bool on(const indicator::type&) const;
+  void current(unsigned char  group);
+  unsigned char current() const;
+  const string group_name(size_t index = 0) const;
+  const string layout_name(size_t index = 0) const;
+  const string indicator_name(const indicator::type&) const;
+  size_t size() const;
+
+ private:
+  vector<layout> layouts;
+  map<indicator::type, indicator> indicators;
+  unsigned char current_group{0};
+};
+
+namespace xkb_util {
+  static constexpr const char* LAYOUT_SYMBOL_BLACKLIST{";group;inet;pc;"};
+
+  void query_extension(connection& conn);
+
+  void switch_layout(connection& conn, xcb_xkb_device_spec_t device, unsigned char index);
+  unsigned char get_current_group(connection& conn, xcb_xkb_device_spec_t device);
+  vector<keyboard::layout> get_layouts(connection& conn, xcb_xkb_device_spec_t device);
+  map<keyboard::indicator::type, keyboard::indicator> get_indicators(connection& conn, xcb_xkb_device_spec_t device);
+  string parse_layout_symbol(string&& name);
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/icccm.hpp b/include/x11/icccm.hpp
new file mode 100644 (file)
index 0000000..d27bbb9
--- /dev/null
@@ -0,0 +1,18 @@
+#pragma once
+
+#include <xcb/xcb_icccm.h>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+namespace icccm_util {
+  string get_wm_name(xcb_connection_t* c, xcb_window_t w);
+  string get_reply_string(xcb_icccm_get_text_property_reply_t* reply);
+
+  void set_wm_name(xcb_connection_t* c, xcb_window_t w, const char* wmname, size_t l, const char* wmclass, size_t l2);
+  void set_wm_protocols(xcb_connection_t* c, xcb_window_t w, vector<xcb_atom_t> flags);
+  bool get_wm_urgency(xcb_connection_t* c, xcb_window_t w);
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/registry.hpp b/include/x11/registry.hpp
new file mode 100644 (file)
index 0000000..1a26277
--- /dev/null
@@ -0,0 +1,25 @@
+#pragma once
+
+#include "common.hpp"
+#include "x11/extensions/fwd.hpp"
+
+// fwd
+namespace xpp {
+  namespace event {
+    template <typename Connection, typename... Extensions>
+    class registry;
+  }
+}
+
+POLYBAR_NS
+
+class connection;
+
+class registry : public xpp::event::registry<connection&, XPP_EXTENSION_LIST> {
+ public:
+  using priority = unsigned int;
+
+  explicit registry(connection& conn);
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/tray_client.hpp b/include/x11/tray_client.hpp
new file mode 100644 (file)
index 0000000..d74c954
--- /dev/null
@@ -0,0 +1,48 @@
+#pragma once
+
+#include <xcb/xcb.h>
+
+#include "common.hpp"
+#include "utils/concurrency.hpp"
+
+POLYBAR_NS
+
+// fwd declarations
+class connection;
+struct xembed_data;
+
+class tray_client {
+ public:
+  explicit tray_client(connection& conn, xcb_window_t win, unsigned int w, unsigned int h);
+  tray_client(const tray_client& c) = delete;
+  tray_client& operator=(tray_client& c) = delete;
+
+  ~tray_client();
+
+  unsigned int width() const;
+  unsigned int height() const;
+  void clear_window() const;
+
+  bool match(const xcb_window_t& win) const;
+  bool mapped() const;
+  void mapped(bool state);
+
+  xcb_window_t window() const;
+  xembed_data* xembed() const;
+
+  void ensure_state() const;
+  void reconfigure(int x, int y) const;
+  void configure_notify(int x, int y) const;
+
+ protected:
+  connection& m_connection;
+  xcb_window_t m_window{0};
+
+  shared_ptr<xembed_data> m_xembed;
+  bool m_mapped{false};
+
+  unsigned int m_width;
+  unsigned int m_height;
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/tray_manager.hpp b/include/x11/tray_manager.hpp
new file mode 100644 (file)
index 0000000..1228c3a
--- /dev/null
@@ -0,0 +1,173 @@
+#pragma once
+
+#include <chrono>
+#include <memory>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/concurrency.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/tray_client.hpp"
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+#define TRAY_WM_NAME "Polybar tray window"
+#define TRAY_WM_CLASS "tray\0Polybar"
+
+#define TRAY_PLACEHOLDER "<placeholder-systray>"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+
+// fwd declarations
+class connection;
+struct xembed_data;
+class background_manager;
+class bg_slice;
+
+struct tray_settings {
+  tray_settings() = default;
+  tray_settings& operator=(const tray_settings& o) = default;
+
+  alignment align{alignment::NONE};
+  bool running{false};
+  int rel_x{0};
+  int rel_y{0};
+  int orig_x{0};
+  int orig_y{0};
+  int configured_x{0};
+  int configured_y{0};
+  unsigned int configured_w{0U};
+  unsigned int configured_h{0U};
+  unsigned int configured_slots{0U};
+  unsigned int width{0U};
+  unsigned int width_max{0U};
+  unsigned int height{0U};
+  unsigned int height_fill{0U};
+  unsigned int spacing{0U};
+  unsigned int sibling{0U};
+  rgba background{};
+  bool transparent{false};
+  bool detached{false};
+};
+
+class tray_manager : public xpp::event::sink<evt::expose, evt::visibility_notify, evt::client_message,
+                         evt::configure_request, evt::resize_request, evt::selection_clear, evt::property_notify,
+                         evt::reparent_notify, evt::destroy_notify, evt::map_notify, evt::unmap_notify>,
+                     public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::visibility_change, signals::ui::dim_window,
+                         signals::ui::update_background> {
+ public:
+  using make_type = unique_ptr<tray_manager>;
+  static make_type make();
+
+  explicit tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back);
+
+  ~tray_manager();
+
+  const tray_settings settings() const;
+
+  void setup(const bar_settings& bar_opts);
+  void activate();
+  void activate_delayed(chrono::duration<double, std::milli> delay = 1s);
+  void deactivate(bool clear_selection = true);
+  void reconfigure();
+
+ protected:
+  void reconfigure_window();
+  void reconfigure_clients();
+  void reconfigure_bg(bool realloc = false);
+  void refresh_window();
+  void redraw_window(bool realloc_bg = false);
+
+  void query_atom();
+  void create_window();
+  void create_bg(bool realloc = false);
+  void restack_window();
+  void set_wm_hints();
+  void set_tray_colors();
+
+  void acquire_selection();
+  void notify_clients();
+  void notify_clients_delayed();
+
+  void track_selection_owner(xcb_window_t owner);
+  void process_docking_request(xcb_window_t win);
+
+  int calculate_x(unsigned width, bool abspos = true) const;
+  int calculate_y(bool abspos = true) const;
+  unsigned short int calculate_w() const;
+  unsigned short int calculate_h() const;
+
+  int calculate_client_x(const xcb_window_t& win);
+  int calculate_client_y();
+
+  bool is_embedded(const xcb_window_t& win) const;
+  shared_ptr<tray_client> find_client(const xcb_window_t& win) const;
+  void remove_client(shared_ptr<tray_client>& client, bool reconfigure = true);
+  void remove_client(xcb_window_t win, bool reconfigure = true);
+  unsigned int mapped_clients() const;
+
+  void handle(const evt::expose& evt);
+  void handle(const evt::visibility_notify& evt);
+  void handle(const evt::client_message& evt);
+  void handle(const evt::configure_request& evt);
+  void handle(const evt::resize_request& evt);
+  void handle(const evt::selection_clear& evt);
+  void handle(const evt::property_notify& evt);
+  void handle(const evt::reparent_notify& evt);
+  void handle(const evt::destroy_notify& evt);
+  void handle(const evt::map_notify& evt);
+  void handle(const evt::unmap_notify& evt);
+
+  bool on(const signals::ui::visibility_change& evt);
+  bool on(const signals::ui::dim_window& evt);
+  bool on(const signals::ui::update_background& evt);
+
+ private:
+  connection& m_connection;
+  signal_emitter& m_sig;
+  const logger& m_log;
+  background_manager& m_background_manager;
+  std::shared_ptr<bg_slice> m_bg_slice;
+  vector<shared_ptr<tray_client>> m_clients;
+
+  tray_settings m_opts{};
+
+  xcb_gcontext_t m_gc{0};
+  xcb_pixmap_t m_pixmap{0};
+  unique_ptr<cairo::surface> m_surface;
+  unique_ptr<cairo::context> m_context;
+
+  unsigned int m_prevwidth{0U};
+  unsigned int m_prevheight{0U};
+
+  xcb_atom_t m_atom{0};
+  xcb_window_t m_tray{0};
+  xcb_window_t m_othermanager{0};
+
+  atomic<bool> m_activated{false};
+  atomic<bool> m_mapped{false};
+  atomic<bool> m_hidden{false};
+  atomic<bool> m_acquired_selection{false};
+
+  thread m_delaythread;
+
+  mutex m_mtx{};
+
+  bool m_firstactivation{true};
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/types.hpp b/include/x11/types.hpp
new file mode 100644 (file)
index 0000000..8de8cdc
--- /dev/null
@@ -0,0 +1,84 @@
+#pragma once
+
+#include <xpp/event.hpp>
+
+#include "common.hpp"
+
+namespace xpp {
+  template <typename Connection, template <typename, typename> class...>
+  class gcontext;
+  template <typename Connection, template <typename, typename> class...>
+  class pixmap;
+  template <typename Connection, template <typename, typename> class...>
+  class drawable;
+  template <typename Connection, template <typename, typename> class...>
+  class colormap;
+  template <typename Connection, template <typename, typename> class...>
+  class atom;
+  template <typename Connection, template <typename, typename> class...>
+  class font;
+  template <typename Connection, template <typename, typename> class...>
+  class cursor;
+  namespace event {
+    template <class Event, class... Events>
+    class sink;
+  }
+}
+
+POLYBAR_NS
+
+class connection;
+class registry;
+
+using gcontext = xpp::gcontext<connection&>;
+using pixmap = xpp::pixmap<connection&>;
+using drawable = xpp::drawable<connection&>;
+using colormap = xpp::colormap<connection&>;
+using atom = xpp::atom<connection&>;
+using font = xpp::font<connection&>;
+using cursor = xpp::cursor<connection&>;
+
+namespace evt {
+  // window focus events
+  using focus_in = xpp::x::event::focus_in<connection&>;
+  using focus_out = xpp::x::event::focus_out<connection&>;
+  // cursor events
+  using enter_notify = xpp::x::event::enter_notify<connection&>;
+  using leave_notify = xpp::x::event::leave_notify<connection&>;
+  using motion_notify = xpp::x::event::motion_notify<connection&>;
+  // keyboard events
+  using button_press = xpp::x::event::button_press<connection&>;
+  using button_release = xpp::x::event::button_release<connection&>;
+  using key_press = xpp::x::event::key_press<connection&>;
+  using key_release = xpp::x::event::key_release<connection&>;
+  using keymap_notify = xpp::x::event::keymap_notify<connection&>;
+  // render events
+  using circulate_notify = xpp::x::event::circulate_notify<connection&>;
+  using circulate_request = xpp::x::event::circulate_request<connection&>;
+  using colormap_notify = xpp::x::event::colormap_notify<connection&>;
+  using configure_notify = xpp::x::event::configure_notify<connection&>;
+  using configure_request = xpp::x::event::configure_request<connection&>;
+  using create_notify = xpp::x::event::create_notify<connection&>;
+  using destroy_notify = xpp::x::event::destroy_notify<connection&>;
+  using expose = xpp::x::event::expose<connection&>;
+  using graphics_exposure = xpp::x::event::graphics_exposure<connection&>;
+  using gravity_notify = xpp::x::event::gravity_notify<connection&>;
+  using map_notify = xpp::x::event::map_notify<connection&>;
+  using map_request = xpp::x::event::map_request<connection&>;
+  using mapping_notify = xpp::x::event::mapping_notify<connection&>;
+  using no_exposure = xpp::x::event::no_exposure<connection&>;
+  using reparent_notify = xpp::x::event::reparent_notify<connection&>;
+  using resize_request = xpp::x::event::resize_request<connection&>;
+  using unmap_notify = xpp::x::event::unmap_notify<connection&>;
+  using visibility_notify = xpp::x::event::visibility_notify<connection&>;
+  // data events
+  using client_message = xpp::x::event::client_message<connection&>;
+  using ge_generic = xpp::x::event::ge_generic<connection&>;
+  using property_notify = xpp::x::event::property_notify<connection&>;
+  // selection events
+  using selection_clear = xpp::x::event::selection_clear<connection&>;
+  using selection_notify = xpp::x::event::selection_notify<connection&>;
+  using selection_request = xpp::x::event::selection_request<connection&>;
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/window.hpp b/include/x11/window.hpp
new file mode 100644 (file)
index 0000000..9ddfe9f
--- /dev/null
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+#include <xpp/window.hpp>
+
+#include "common.hpp"
+
+POLYBAR_NS
+
+class connection;
+
+class window : public xpp::window<connection&> {
+ public:
+  window(const window&) = default;
+  using xpp::window<class connection&>::window;
+
+  window reconfigure_geom(unsigned short int w, unsigned short int h, short int x = 0, short int y = 0);
+  window reconfigure_pos(short int x, short int y);
+  window reconfigure_struts(unsigned short int w, unsigned short int h, short int x, bool bottom = false);
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/winspec.hpp b/include/x11/winspec.hpp
new file mode 100644 (file)
index 0000000..6eea107
--- /dev/null
@@ -0,0 +1,180 @@
+#pragma once
+
+#include <xcb/xcb_aux.h>
+
+#include "common.hpp"
+#include "components/types.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+struct cw_size {
+  explicit cw_size(unsigned short int w, unsigned short int h) : w(w), h(h) {}
+  explicit cw_size(struct size size) : w(size.w), h(size.h) {}
+  unsigned short int w{1};
+  unsigned short int h{1};
+};
+struct cw_pos {
+  explicit cw_pos(short int x, short int y) : x(x), y(y) {}
+  explicit cw_pos(struct position pos) : x(pos.x), y(pos.y) {}
+  short int x{0};
+  short int y{0};
+};
+struct cw_border {
+  explicit cw_border(unsigned short int border_width) : border_width(border_width) {}
+  unsigned short int border_width{0};
+};
+struct cw_class {
+  explicit cw_class(unsigned short int class_) : class_(class_) {}
+  unsigned short int class_{XCB_COPY_FROM_PARENT};
+};
+struct cw_parent {
+  explicit cw_parent(xcb_window_t parent) : parent(parent) {}
+  xcb_window_t parent{XCB_NONE};
+};
+struct cw_depth {
+  explicit cw_depth(unsigned char depth) : depth(depth) {}
+  unsigned char depth{XCB_COPY_FROM_PARENT};
+};
+struct cw_visual {
+  explicit cw_visual(xcb_visualid_t visualid) : visualid(visualid) {}
+  xcb_visualid_t visualid{XCB_COPY_FROM_PARENT};
+};
+struct cw_mask {
+  explicit cw_mask(unsigned int mask) : mask(mask) {}
+  const unsigned int mask{0};
+};
+struct cw_params {
+  explicit cw_params(const xcb_params_cw_t* params) : params(params) {}
+  const xcb_params_cw_t* params{nullptr};
+};
+struct cw_params_back_pixel {
+  explicit cw_params_back_pixel(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_back_pixmap {
+  explicit cw_params_back_pixmap(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_backing_pixel {
+  explicit cw_params_backing_pixel(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_backing_planes {
+  explicit cw_params_backing_planes(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_backing_store {
+  explicit cw_params_backing_store(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_bit_gravity {
+  explicit cw_params_bit_gravity(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_border_pixel {
+  explicit cw_params_border_pixel(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_border_pixmap {
+  explicit cw_params_border_pixmap(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_colormap {
+  explicit cw_params_colormap(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_cursor {
+  explicit cw_params_cursor(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_dont_propagate {
+  explicit cw_params_dont_propagate(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_event_mask {
+  explicit cw_params_event_mask(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_override_redirect {
+  explicit cw_params_override_redirect(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_save_under {
+  explicit cw_params_save_under(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_params_win_gravity {
+  explicit cw_params_win_gravity(unsigned int value) : value(value) {}
+  unsigned int value{0};
+};
+struct cw_flush {
+  explicit cw_flush(bool checked = false) : checked(checked) {}
+  bool checked{false};
+};
+
+/**
+ * Create X window
+ *
+ * Example usage:
+ * \code cpp
+ *   auto win = winspec(m_connection)
+ *     << cw_size(100, 200)
+ *     << cw_pos(10, -20)
+ *     << cw_border(9)
+ *     << cw_class(XCB_WINDOW_CLASS_INPUT_ONLY)
+ *     << cw_parent(0x000110a);
+ *     << cw_flush(false);
+ * \endcode
+ */
+class winspec {
+ public:
+  explicit winspec(connection& conn);
+  explicit winspec(connection& conn, const xcb_window_t& window);
+
+  explicit operator xcb_window_t() const;
+  explicit operator xcb_rectangle_t() const;
+
+  xcb_window_t operator<<(const cw_flush& f);
+
+  winspec& operator<<(const cw_size& size);
+  winspec& operator<<(const cw_pos& p);
+  winspec& operator<<(const cw_border& b);
+  winspec& operator<<(const cw_class& c);
+  winspec& operator<<(const cw_parent& p);
+  winspec& operator<<(const cw_depth& d);
+  winspec& operator<<(const cw_visual& v);
+  winspec& operator<<(const cw_params_back_pixel& p);
+  winspec& operator<<(const cw_params_back_pixmap& p);
+  winspec& operator<<(const cw_params_backing_pixel& p);
+  winspec& operator<<(const cw_params_backing_planes& p);
+  winspec& operator<<(const cw_params_backing_store& p);
+  winspec& operator<<(const cw_params_bit_gravity& p);
+  winspec& operator<<(const cw_params_border_pixel& p);
+  winspec& operator<<(const cw_params_border_pixmap& p);
+  winspec& operator<<(const cw_params_colormap& p);
+  winspec& operator<<(const cw_params_cursor& p);
+  winspec& operator<<(const cw_params_dont_propagate& p);
+  winspec& operator<<(const cw_params_event_mask& p);
+  winspec& operator<<(const cw_params_override_redirect& p);
+  winspec& operator<<(const cw_params_save_under& p);
+  winspec& operator<<(const cw_params_win_gravity& p);
+
+ protected:
+  connection& m_connection;
+
+  xcb_window_t m_window{XCB_NONE};
+  unsigned int m_parent{XCB_NONE};
+  unsigned char m_depth{XCB_COPY_FROM_PARENT};
+  unsigned short int m_class{XCB_COPY_FROM_PARENT};
+  xcb_visualid_t m_visual{XCB_COPY_FROM_PARENT};
+  short int m_x{0};
+  short int m_y{0};
+  unsigned short int m_width{1U};
+  unsigned short int m_height{1U};
+  unsigned short int m_border{0};
+  unsigned int m_mask{0};
+  xcb_params_cw_t m_params{};
+};
+
+POLYBAR_NS_END
diff --git a/include/x11/xembed.hpp b/include/x11/xembed.hpp
new file mode 100644 (file)
index 0000000..b587bbf
--- /dev/null
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "common.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+#define XEMBED_VERSION 0
+#define XEMBED_MAPPED (1 << 0)
+
+#define XEMBED_EMBEDDED_NOTIFY 0
+#define XEMBED_WINDOW_ACTIVATE 1
+#define XEMBED_WINDOW_DEACTIVATE 2
+#define XEMBED_REQUEST_FOCUS 3
+#define XEMBED_FOCUS_IN 3
+#define XEMBED_FOCUS_OUT 4
+#define XEMBED_FOCUS_NEXT 5
+#define XEMBED_FOCUS_PREV 6
+
+#define XEMBED_FOCUS_CURRENT 0
+#define XEMBED_FOCUS_FIRST 1
+#define XEMBED_FOCUS_LAST 1
+
+struct xembed_data {
+  unsigned long version;
+  unsigned long flags;
+  xcb_timestamp_t time;
+  xcb_atom_t xembed;
+  xcb_atom_t xembed_info;
+};
+
+namespace xembed {
+  xembed_data* query(connection& conn, xcb_window_t win, xembed_data* data);
+  void send_message(connection& conn, xcb_window_t target, long message, long d1, long d2, long d3);
+  void send_focus_event(connection& conn, xcb_window_t target);
+  void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, long version);
+  void notify_activated(connection& conn, xcb_window_t win);
+  void notify_deactivated(connection& conn, xcb_window_t win);
+  void notify_focused(connection& conn, xcb_window_t win, long focus_type);
+  void notify_unfocused(connection& conn, xcb_window_t win);
+  void unembed(connection& conn, xcb_window_t win, xcb_window_t root);
+}
+
+POLYBAR_NS_END
diff --git a/include/x11/xresources.hpp b/include/x11/xresources.hpp
new file mode 100644 (file)
index 0000000..763e6b7
--- /dev/null
@@ -0,0 +1,64 @@
+#pragma once
+
+#include "settings.hpp"
+
+#if not WITH_XRM
+#error "Not built with support for xcb-xrm..."
+#endif
+
+#include <xcb/xcb_xrm.h>
+
+#include "common.hpp"
+#include "errors.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+DEFINE_ERROR(xresource_error);
+
+class xresource_manager {
+ public:
+  explicit xresource_manager(xcb_connection_t* conn) : m_xrm(xcb_xrm_database_from_default(conn)) {
+    if (m_xrm == nullptr) {
+      throw application_error("xcb_xrm_database_from_default()");
+    }
+  }
+
+  ~xresource_manager() {
+    xcb_xrm_database_free(m_xrm);
+  }
+
+  operator bool() const {
+    return m_xrm != nullptr;
+  }
+
+  template <typename T>
+  T require(const char* name) const {
+    char* result{nullptr};
+    if (xcb_xrm_resource_get_string(m_xrm, string_util::replace(name, "*", ".").c_str(), nullptr, &result) == -1) {
+      throw xresource_error(sstream() << "X resource \"" << name << "\" not found");
+    } else if (result == nullptr) {
+      throw xresource_error(sstream() << "X resource \"" << name << "\" not found");
+    }
+    return convert<T>(string(result));
+  }
+
+  template <typename T>
+  T get(const char* name, const T& fallback) const {
+    try {
+      return convert<T>(require<T>(name));
+    } catch (const xresource_error) {
+      return fallback;
+    }
+  }
+
+ protected:
+  template <typename T>
+  T convert(string&& value) const;
+
+ private:
+  xcb_xrm_database_t* m_xrm;
+};
+
+POLYBAR_NS_END
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b74af3b
--- /dev/null
@@ -0,0 +1,40 @@
+#
+# Configure libs
+#
+
+# Library: concurrentqueue {{{
+
+add_library(concurrentqueue INTERFACE)
+target_include_directories(concurrentqueue INTERFACE
+  $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/concurrentqueue/include>)
+list(APPEND libs concurrentqueue)
+
+# }}}
+# Library: xpp {{{
+
+set(XCB_PROTOS xproto)
+
+if(WITH_XRANDR)
+  list(APPEND XCB_PROTOS randr)
+endif()
+if(WITH_XCOMPOSITE)
+  list(APPEND XCB_PROTOS composite)
+endif()
+if(WITH_XKB)
+  list(APPEND XCB_PROTOS xkb)
+endif()
+
+add_subdirectory(xpp)
+list(APPEND libs xpp)
+
+# }}}
+# Library: i3ipcpp {{{
+
+if(ENABLE_I3)
+  add_subdirectory(i3ipcpp)
+  list(APPEND libs ${I3IPCPP_LIBRARIES})
+endif()
+
+# }}}
+
+set(libs ${libs} PARENT_SCOPE)
diff --git a/lib/concurrentqueue/include/moodycamel/blockingconcurrentqueue.h b/lib/concurrentqueue/include/moodycamel/blockingconcurrentqueue.h
new file mode 100644 (file)
index 0000000..325a32b
--- /dev/null
@@ -0,0 +1,981 @@
+// Provides an efficient blocking version of moodycamel::ConcurrentQueue.
+// ©2015-2016 Cameron Desrochers. Distributed under the terms of the simplified
+// BSD license, available at the top of concurrentqueue.h.
+// Uses Jeff Preshing's semaphore implementation (under the terms of its
+// separate zlib license, embedded below).
+
+#pragma once
+
+#include "concurrentqueue.h"
+#include <type_traits>
+#include <cerrno>
+#include <memory>
+#include <chrono>
+#include <ctime>
+
+#if defined(_WIN32)
+// Avoid including windows.h in a header; we only need a handful of
+// items, so we'll redeclare them here (this is relatively safe since
+// the API generally has to remain stable between Windows versions).
+// I know this is an ugly hack but it still beats polluting the global
+// namespace with thousands of generic names or adding a .cpp for nothing.
+extern "C" {
+       struct _SECURITY_ATTRIBUTES;
+       __declspec(dllimport) void* __stdcall CreateSemaphoreW(_SECURITY_ATTRIBUTES* lpSemaphoreAttributes, long lInitialCount, long lMaximumCount, const wchar_t* lpName);
+       __declspec(dllimport) int __stdcall CloseHandle(void* hObject);
+       __declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void* hHandle, unsigned long dwMilliseconds);
+       __declspec(dllimport) int __stdcall ReleaseSemaphore(void* hSemaphore, long lReleaseCount, long* lpPreviousCount);
+}
+#elif defined(__MACH__)
+#include <mach/mach.h>
+#elif defined(__unix__)
+#include <semaphore.h>
+#endif
+
+namespace moodycamel
+{
+namespace details
+{
+       // Code in the mpmc_sema namespace below is an adaptation of Jeff Preshing's
+       // portable + lightweight semaphore implementations, originally from
+       // https://github.com/preshing/cpp11-on-multicore/blob/master/common/sema.h
+       // LICENSE:
+       // Copyright (c) 2015 Jeff Preshing
+       //
+       // This software is provided 'as-is', without any express or implied
+       // warranty. In no event will the authors be held liable for any damages
+       // arising from the use of this software.
+       //
+       // Permission is granted to anyone to use this software for any purpose,
+       // including commercial applications, and to alter it and redistribute it
+       // freely, subject to the following restrictions:
+       //
+       // 1. The origin of this software must not be misrepresented; you must not
+       //      claim that you wrote the original software. If you use this software
+       //      in a product, an acknowledgement in the product documentation would be
+       //      appreciated but is not required.
+       // 2. Altered source versions must be plainly marked as such, and must not be
+       //      misrepresented as being the original software.
+       // 3. This notice may not be removed or altered from any source distribution.
+       namespace mpmc_sema
+       {
+#if defined(_WIN32)
+               class Semaphore
+               {
+               private:
+                       void* m_hSema;
+                       
+                       Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
+                       Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
+
+               public:
+                       Semaphore(int initialCount = 0)
+                       {
+                               assert(initialCount >= 0);
+                               const long maxLong = 0x7fffffff;
+                               m_hSema = CreateSemaphoreW(nullptr, initialCount, maxLong, nullptr);
+                       }
+
+                       ~Semaphore()
+                       {
+                               CloseHandle(m_hSema);
+                       }
+
+                       void wait()
+                       {
+                               const unsigned long infinite = 0xffffffff;
+                               WaitForSingleObject(m_hSema, infinite);
+                       }
+                       
+                       bool try_wait()
+                       {
+                               const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
+                               return WaitForSingleObject(m_hSema, 0) != RC_WAIT_TIMEOUT;
+                       }
+                       
+                       bool timed_wait(std::uint64_t usecs)
+                       {
+                               const unsigned long RC_WAIT_TIMEOUT = 0x00000102;
+                               return WaitForSingleObject(m_hSema, (unsigned long)(usecs / 1000)) != RC_WAIT_TIMEOUT;
+                       }
+
+                       void signal(int count = 1)
+                       {
+                               ReleaseSemaphore(m_hSema, count, nullptr);
+                       }
+               };
+#elif defined(__MACH__)
+               //---------------------------------------------------------
+               // Semaphore (Apple iOS and OSX)
+               // Can't use POSIX semaphores due to http://lists.apple.com/archives/darwin-kernel/2009/Apr/msg00010.html
+               //---------------------------------------------------------
+               class Semaphore
+               {
+               private:
+                       semaphore_t m_sema;
+
+                       Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
+                       Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
+
+               public:
+                       Semaphore(int initialCount = 0)
+                       {
+                               assert(initialCount >= 0);
+                               semaphore_create(mach_task_self(), &m_sema, SYNC_POLICY_FIFO, initialCount);
+                       }
+
+                       ~Semaphore()
+                       {
+                               semaphore_destroy(mach_task_self(), m_sema);
+                       }
+
+                       void wait()
+                       {
+                               semaphore_wait(m_sema);
+                       }
+                       
+                       bool try_wait()
+                       {
+                               return timed_wait(0);
+                       }
+                       
+                       bool timed_wait(std::uint64_t timeout_usecs)
+                       {
+                               mach_timespec_t ts;
+                               ts.tv_sec = timeout_usecs / 1000000;
+                               ts.tv_nsec = (timeout_usecs % 1000000) * 1000;
+
+                               // added in OSX 10.10: https://developer.apple.com/library/prerelease/mac/documentation/General/Reference/APIDiffsMacOSX10_10SeedDiff/modules/Darwin.html
+                               kern_return_t rc = semaphore_timedwait(m_sema, ts);
+
+                               return rc != KERN_OPERATION_TIMED_OUT;
+                       }
+
+                       void signal()
+                       {
+                               semaphore_signal(m_sema);
+                       }
+
+                       void signal(int count)
+                       {
+                               while (count-- > 0)
+                               {
+                                       semaphore_signal(m_sema);
+                               }
+                       }
+               };
+#elif defined(__unix__)
+               //---------------------------------------------------------
+               // Semaphore (POSIX, Linux)
+               //---------------------------------------------------------
+               class Semaphore
+               {
+               private:
+                       sem_t m_sema;
+
+                       Semaphore(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
+                       Semaphore& operator=(const Semaphore& other) MOODYCAMEL_DELETE_FUNCTION;
+
+               public:
+                       Semaphore(int initialCount = 0)
+                       {
+                               assert(initialCount >= 0);
+                               sem_init(&m_sema, 0, initialCount);
+                       }
+
+                       ~Semaphore()
+                       {
+                               sem_destroy(&m_sema);
+                       }
+
+                       void wait()
+                       {
+                               // http://stackoverflow.com/questions/2013181/gdb-causes-sem-wait-to-fail-with-eintr-error
+                               int rc;
+                               do {
+                                       rc = sem_wait(&m_sema);
+                               } while (rc == -1 && errno == EINTR);
+                       }
+
+                       bool try_wait()
+                       {
+                               int rc;
+                               do {
+                                       rc = sem_trywait(&m_sema);
+                               } while (rc == -1 && errno == EINTR);
+                               return !(rc == -1 && errno == EAGAIN);
+                       }
+
+                       bool timed_wait(std::uint64_t usecs)
+                       {
+                               struct timespec ts;
+                               const int usecs_in_1_sec = 1000000;
+                               const int nsecs_in_1_sec = 1000000000;
+                               clock_gettime(CLOCK_REALTIME, &ts);
+                               ts.tv_sec += usecs / usecs_in_1_sec;
+                               ts.tv_nsec += (usecs % usecs_in_1_sec) * 1000;
+                               // sem_timedwait bombs if you have more than 1e9 in tv_nsec
+                               // so we have to clean things up before passing it in
+                               if (ts.tv_nsec > nsecs_in_1_sec) {
+                                       ts.tv_nsec -= nsecs_in_1_sec;
+                                       ++ts.tv_sec;
+                               }
+
+                               int rc;
+                               do {
+                                       rc = sem_timedwait(&m_sema, &ts);
+                               } while (rc == -1 && errno == EINTR);
+                               return !(rc == -1 && errno == ETIMEDOUT);
+                       }
+
+                       void signal()
+                       {
+                               sem_post(&m_sema);
+                       }
+
+                       void signal(int count)
+                       {
+                               while (count-- > 0)
+                               {
+                                       sem_post(&m_sema);
+                               }
+                       }
+               };
+#else
+#error Unsupported platform! (No semaphore wrapper available)
+#endif
+
+               //---------------------------------------------------------
+               // LightweightSemaphore
+               //---------------------------------------------------------
+               class LightweightSemaphore
+               {
+               public:
+                       typedef std::make_signed<std::size_t>::type ssize_t;
+
+               private:
+                       std::atomic<ssize_t> m_count;
+                       Semaphore m_sema;
+
+                       bool waitWithPartialSpinning(std::int64_t timeout_usecs = -1)
+                       {
+                               ssize_t oldCount;
+                               // Is there a better way to set the initial spin count?
+                               // If we lower it to 1000, testBenaphore becomes 15x slower on my Core i7-5930K Windows PC,
+                               // as threads start hitting the kernel semaphore.
+                               int spin = 10000;
+                               while (--spin >= 0)
+                               {
+                                       oldCount = m_count.load(std::memory_order_relaxed);
+                                       if ((oldCount > 0) && m_count.compare_exchange_strong(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
+                                               return true;
+                                       std::atomic_signal_fence(std::memory_order_acquire);     // Prevent the compiler from collapsing the loop.
+                               }
+                               oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
+                               if (oldCount > 0)
+                                       return true;
+                               if (timeout_usecs < 0)
+                               {
+                                       m_sema.wait();
+                                       return true;
+                               }
+                               if (m_sema.timed_wait((std::uint64_t)timeout_usecs))
+                                       return true;
+                               // At this point, we've timed out waiting for the semaphore, but the
+                               // count is still decremented indicating we may still be waiting on
+                               // it. So we have to re-adjust the count, but only if the semaphore
+                               // wasn't signaled enough times for us too since then. If it was, we
+                               // need to release the semaphore too.
+                               while (true)
+                               {
+                                       oldCount = m_count.load(std::memory_order_acquire);
+                                       if (oldCount >= 0 && m_sema.try_wait())
+                                               return true;
+                                       if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed))
+                                               return false;
+                               }
+                       }
+
+                       ssize_t waitManyWithPartialSpinning(ssize_t max, std::int64_t timeout_usecs = -1)
+                       {
+                               assert(max > 0);
+                               ssize_t oldCount;
+                               int spin = 10000;
+                               while (--spin >= 0)
+                               {
+                                       oldCount = m_count.load(std::memory_order_relaxed);
+                                       if (oldCount > 0)
+                                       {
+                                               ssize_t newCount = oldCount > max ? oldCount - max : 0;
+                                               if (m_count.compare_exchange_strong(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
+                                                       return oldCount - newCount;
+                                       }
+                                       std::atomic_signal_fence(std::memory_order_acquire);
+                               }
+                               oldCount = m_count.fetch_sub(1, std::memory_order_acquire);
+                               if (oldCount <= 0)
+                               {
+                                       if (timeout_usecs < 0)
+                                               m_sema.wait();
+                                       else if (!m_sema.timed_wait((std::uint64_t)timeout_usecs))
+                                       {
+                                               while (true)
+                                               {
+                                                       oldCount = m_count.load(std::memory_order_acquire);
+                                                       if (oldCount >= 0 && m_sema.try_wait())
+                                                               break;
+                                                       if (oldCount < 0 && m_count.compare_exchange_strong(oldCount, oldCount + 1, std::memory_order_relaxed))
+                                                               return 0;
+                                               }
+                                       }
+                               }
+                               if (max > 1)
+                                       return 1 + tryWaitMany(max - 1);
+                               return 1;
+                       }
+
+               public:
+                       LightweightSemaphore(ssize_t initialCount = 0) : m_count(initialCount)
+                       {
+                               assert(initialCount >= 0);
+                       }
+
+                       bool tryWait()
+                       {
+                               ssize_t oldCount = m_count.load(std::memory_order_relaxed);
+                               while (oldCount > 0)
+                               {
+                                       if (m_count.compare_exchange_weak(oldCount, oldCount - 1, std::memory_order_acquire, std::memory_order_relaxed))
+                                               return true;
+                               }
+                               return false;
+                       }
+
+                       void wait()
+                       {
+                               if (!tryWait())
+                                       waitWithPartialSpinning();
+                       }
+
+                       bool wait(std::int64_t timeout_usecs)
+                       {
+                               return tryWait() || waitWithPartialSpinning(timeout_usecs);
+                       }
+
+                       // Acquires between 0 and (greedily) max, inclusive
+                       ssize_t tryWaitMany(ssize_t max)
+                       {
+                               assert(max >= 0);
+                               ssize_t oldCount = m_count.load(std::memory_order_relaxed);
+                               while (oldCount > 0)
+                               {
+                                       ssize_t newCount = oldCount > max ? oldCount - max : 0;
+                                       if (m_count.compare_exchange_weak(oldCount, newCount, std::memory_order_acquire, std::memory_order_relaxed))
+                                               return oldCount - newCount;
+                               }
+                               return 0;
+                       }
+
+                       // Acquires at least one, and (greedily) at most max
+                       ssize_t waitMany(ssize_t max, std::int64_t timeout_usecs)
+                       {
+                               assert(max >= 0);
+                               ssize_t result = tryWaitMany(max);
+                               if (result == 0 && max > 0)
+                                       result = waitManyWithPartialSpinning(max, timeout_usecs);
+                               return result;
+                       }
+                       
+                       ssize_t waitMany(ssize_t max)
+                       {
+                               ssize_t result = waitMany(max, -1);
+                               assert(result > 0);
+                               return result;
+                       }
+
+                       void signal(ssize_t count = 1)
+                       {
+                               assert(count >= 0);
+                               ssize_t oldCount = m_count.fetch_add(count, std::memory_order_release);
+                               ssize_t toRelease = -oldCount < count ? -oldCount : count;
+                               if (toRelease > 0)
+                               {
+                                       m_sema.signal((int)toRelease);
+                               }
+                       }
+                       
+                       ssize_t availableApprox() const
+                       {
+                               ssize_t count = m_count.load(std::memory_order_relaxed);
+                               return count > 0 ? count : 0;
+                       }
+               };
+       }       // end namespace mpmc_sema
+}      // end namespace details
+
+
+// This is a blocking version of the queue. It has an almost identical interface to
+// the normal non-blocking version, with the addition of various wait_dequeue() methods
+// and the removal of producer-specific dequeue methods.
+template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
+class BlockingConcurrentQueue
+{
+private:
+       typedef ::moodycamel::ConcurrentQueue<T, Traits> ConcurrentQueue;
+       typedef details::mpmc_sema::LightweightSemaphore LightweightSemaphore;
+
+public:
+       typedef typename ConcurrentQueue::producer_token_t producer_token_t;
+       typedef typename ConcurrentQueue::consumer_token_t consumer_token_t;
+       
+       typedef typename ConcurrentQueue::index_t index_t;
+       typedef typename ConcurrentQueue::size_t size_t;
+       typedef typename std::make_signed<size_t>::type ssize_t;
+       
+       static const size_t BLOCK_SIZE = ConcurrentQueue::BLOCK_SIZE;
+       static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = ConcurrentQueue::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD;
+       static const size_t EXPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::EXPLICIT_INITIAL_INDEX_SIZE;
+       static const size_t IMPLICIT_INITIAL_INDEX_SIZE = ConcurrentQueue::IMPLICIT_INITIAL_INDEX_SIZE;
+       static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = ConcurrentQueue::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
+       static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = ConcurrentQueue::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE;
+       static const size_t MAX_SUBQUEUE_SIZE = ConcurrentQueue::MAX_SUBQUEUE_SIZE;
+       
+public:
+       // Creates a queue with at least `capacity` element slots; note that the
+       // actual number of elements that can be inserted without additional memory
+       // allocation depends on the number of producers and the block size (e.g. if
+       // the block size is equal to `capacity`, only a single block will be allocated
+       // up-front, which means only a single producer will be able to enqueue elements
+       // without an extra allocation -- blocks aren't shared between producers).
+       // This method is not thread safe -- it is up to the user to ensure that the
+       // queue is fully constructed before it starts being used by other threads (this
+       // includes making the memory effects of construction visible, possibly with a
+       // memory barrier).
+       explicit BlockingConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
+               : inner(capacity), sema(create<LightweightSemaphore>(), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
+       {
+               assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
+               if (!sema) {
+                       MOODYCAMEL_THROW(std::bad_alloc());
+               }
+       }
+       
+       BlockingConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers)
+               : inner(minCapacity, maxExplicitProducers, maxImplicitProducers), sema(create<LightweightSemaphore>(), &BlockingConcurrentQueue::template destroy<LightweightSemaphore>)
+       {
+               assert(reinterpret_cast<ConcurrentQueue*>((BlockingConcurrentQueue*)1) == &((BlockingConcurrentQueue*)1)->inner && "BlockingConcurrentQueue must have ConcurrentQueue as its first member");
+               if (!sema) {
+                       MOODYCAMEL_THROW(std::bad_alloc());
+               }
+       }
+       
+       // Disable copying and copy assignment
+       BlockingConcurrentQueue(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
+       BlockingConcurrentQueue& operator=(BlockingConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
+       
+       // Moving is supported, but note that it is *not* a thread-safe operation.
+       // Nobody can use the queue while it's being moved, and the memory effects
+       // of that move must be propagated to other threads before they can use it.
+       // Note: When a queue is moved, its tokens are still valid but can only be
+       // used with the destination queue (i.e. semantically they are moved along
+       // with the queue itself).
+       BlockingConcurrentQueue(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
+               : inner(std::move(other.inner)), sema(std::move(other.sema))
+       { }
+       
+       inline BlockingConcurrentQueue& operator=(BlockingConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
+       {
+               return swap_internal(other);
+       }
+       
+       // Swaps this queue's state with the other's. Not thread-safe.
+       // Swapping two queues does not invalidate their tokens, however
+       // the tokens that were created for one queue must be used with
+       // only the swapped queue (i.e. the tokens are tied to the
+       // queue's movable state, not the object itself).
+       inline void swap(BlockingConcurrentQueue& other) MOODYCAMEL_NOEXCEPT
+       {
+               swap_internal(other);
+       }
+       
+private:
+       BlockingConcurrentQueue& swap_internal(BlockingConcurrentQueue& other)
+       {
+               if (this == &other) {
+                       return *this;
+               }
+               
+               inner.swap(other.inner);
+               sema.swap(other.sema);
+               return *this;
+       }
+       
+public:
+       // Enqueues a single item (by copying it).
+       // Allocates memory if required. Only fails if memory allocation fails (or implicit
+       // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
+       // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(T const& item)
+       {
+               if (details::likely(inner.enqueue(item))) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by moving it, if possible).
+       // Allocates memory if required. Only fails if memory allocation fails (or implicit
+       // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
+       // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(T&& item)
+       {
+               if (details::likely(inner.enqueue(std::move(item)))) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by copying it) using an explicit producer token.
+       // Allocates memory if required. Only fails if memory allocation fails (or
+       // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(producer_token_t const& token, T const& item)
+       {
+               if (details::likely(inner.enqueue(token, item))) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by moving it, if possible) using an explicit producer token.
+       // Allocates memory if required. Only fails if memory allocation fails (or
+       // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(producer_token_t const& token, T&& item)
+       {
+               if (details::likely(inner.enqueue(token, std::move(item)))) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues several items.
+       // Allocates memory if required. Only fails if memory allocation fails (or
+       // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
+       // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Note: Use std::make_move_iterator if the elements should be moved instead of copied.
+       // Thread-safe.
+       template<typename It>
+       inline bool enqueue_bulk(It itemFirst, size_t count)
+       {
+               if (details::likely(inner.enqueue_bulk(std::forward<It>(itemFirst), count))) {
+                       sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues several items using an explicit producer token.
+       // Allocates memory if required. Only fails if memory allocation fails
+       // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Note: Use std::make_move_iterator if the elements should be moved
+       // instead of copied.
+       // Thread-safe.
+       template<typename It>
+       inline bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
+       {
+               if (details::likely(inner.enqueue_bulk(token, std::forward<It>(itemFirst), count))) {
+                       sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by copying it).
+       // Does not allocate memory. Fails if not enough room to enqueue (or implicit
+       // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
+       // is 0).
+       // Thread-safe.
+       inline bool try_enqueue(T const& item)
+       {
+               if (inner.try_enqueue(item)) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by moving it, if possible).
+       // Does not allocate memory (except for one-time implicit producer).
+       // Fails if not enough room to enqueue (or implicit production is
+       // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
+       // Thread-safe.
+       inline bool try_enqueue(T&& item)
+       {
+               if (inner.try_enqueue(std::move(item))) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by copying it) using an explicit producer token.
+       // Does not allocate memory. Fails if not enough room to enqueue.
+       // Thread-safe.
+       inline bool try_enqueue(producer_token_t const& token, T const& item)
+       {
+               if (inner.try_enqueue(token, item)) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues a single item (by moving it, if possible) using an explicit producer token.
+       // Does not allocate memory. Fails if not enough room to enqueue.
+       // Thread-safe.
+       inline bool try_enqueue(producer_token_t const& token, T&& item)
+       {
+               if (inner.try_enqueue(token, std::move(item))) {
+                       sema->signal();
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues several items.
+       // Does not allocate memory (except for one-time implicit producer).
+       // Fails if not enough room to enqueue (or implicit production is
+       // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
+       // Note: Use std::make_move_iterator if the elements should be moved
+       // instead of copied.
+       // Thread-safe.
+       template<typename It>
+       inline bool try_enqueue_bulk(It itemFirst, size_t count)
+       {
+               if (inner.try_enqueue_bulk(std::forward<It>(itemFirst), count)) {
+                       sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
+                       return true;
+               }
+               return false;
+       }
+       
+       // Enqueues several items using an explicit producer token.
+       // Does not allocate memory. Fails if not enough room to enqueue.
+       // Note: Use std::make_move_iterator if the elements should be moved
+       // instead of copied.
+       // Thread-safe.
+       template<typename It>
+       inline bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
+       {
+               if (inner.try_enqueue_bulk(token, std::forward<It>(itemFirst), count)) {
+                       sema->signal((LightweightSemaphore::ssize_t)(ssize_t)count);
+                       return true;
+               }
+               return false;
+       }
+       
+       
+       // Attempts to dequeue from the queue.
+       // Returns false if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline bool try_dequeue(U& item)
+       {
+               if (sema->tryWait()) {
+                       while (!inner.try_dequeue(item)) {
+                               continue;
+                       }
+                       return true;
+               }
+               return false;
+       }
+       
+       // Attempts to dequeue from the queue using an explicit consumer token.
+       // Returns false if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline bool try_dequeue(consumer_token_t& token, U& item)
+       {
+               if (sema->tryWait()) {
+                       while (!inner.try_dequeue(token, item)) {
+                               continue;
+                       }
+                       return true;
+               }
+               return false;
+       }
+       
+       // Attempts to dequeue several elements from the queue.
+       // Returns the number of items actually dequeued.
+       // Returns 0 if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t try_dequeue_bulk(It itemFirst, size_t max)
+       {
+               size_t count = 0;
+               max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
+               while (count != max) {
+                       count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
+               }
+               return count;
+       }
+       
+       // Attempts to dequeue several elements from the queue using an explicit consumer token.
+       // Returns the number of items actually dequeued.
+       // Returns 0 if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
+       {
+               size_t count = 0;
+               max = (size_t)sema->tryWaitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
+               while (count != max) {
+                       count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
+               }
+               return count;
+       }
+       
+       
+       
+       // Blocks the current thread until there's something to dequeue, then
+       // dequeues it.
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline void wait_dequeue(U& item)
+       {
+               sema->wait();
+               while (!inner.try_dequeue(item)) {
+                       continue;
+               }
+       }
+
+       // Blocks the current thread until either there's something to dequeue
+       // or the timeout (specified in microseconds) expires. Returns false
+       // without setting `item` if the timeout expires, otherwise assigns
+       // to `item` and returns true.
+       // Using a negative timeout indicates an indefinite timeout,
+       // and is thus functionally equivalent to calling wait_dequeue.
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline bool wait_dequeue_timed(U& item, std::int64_t timeout_usecs)
+       {
+               if (!sema->wait(timeout_usecs)) {
+                       return false;
+               }
+               while (!inner.try_dequeue(item)) {
+                       continue;
+               }
+               return true;
+       }
+    
+    // Blocks the current thread until either there's something to dequeue
+       // or the timeout expires. Returns false without setting `item` if the
+    // timeout expires, otherwise assigns to `item` and returns true.
+       // Never allocates. Thread-safe.
+       template<typename U, typename Rep, typename Period>
+       inline bool wait_dequeue_timed(U& item, std::chrono::duration<Rep, Period> const& timeout)
+    {
+        return wait_dequeue_timed(item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
+    }
+       
+       // Blocks the current thread until there's something to dequeue, then
+       // dequeues it using an explicit consumer token.
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline void wait_dequeue(consumer_token_t& token, U& item)
+       {
+               sema->wait();
+               while (!inner.try_dequeue(token, item)) {
+                       continue;
+               }
+       }
+       
+       // Blocks the current thread until either there's something to dequeue
+       // or the timeout (specified in microseconds) expires. Returns false
+       // without setting `item` if the timeout expires, otherwise assigns
+       // to `item` and returns true.
+       // Using a negative timeout indicates an indefinite timeout,
+       // and is thus functionally equivalent to calling wait_dequeue.
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::int64_t timeout_usecs)
+       {
+               if (!sema->wait(timeout_usecs)) {
+                       return false;
+               }
+               while (!inner.try_dequeue(token, item)) {
+                       continue;
+               }
+               return true;
+       }
+    
+    // Blocks the current thread until either there's something to dequeue
+       // or the timeout expires. Returns false without setting `item` if the
+    // timeout expires, otherwise assigns to `item` and returns true.
+       // Never allocates. Thread-safe.
+       template<typename U, typename Rep, typename Period>
+       inline bool wait_dequeue_timed(consumer_token_t& token, U& item, std::chrono::duration<Rep, Period> const& timeout)
+    {
+        return wait_dequeue_timed(token, item, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
+    }
+       
+       // Attempts to dequeue several elements from the queue.
+       // Returns the number of items actually dequeued, which will
+       // always be at least one (this method blocks until the queue
+       // is non-empty) and at most max.
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t wait_dequeue_bulk(It itemFirst, size_t max)
+       {
+               size_t count = 0;
+               max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
+               while (count != max) {
+                       count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
+               }
+               return count;
+       }
+       
+       // Attempts to dequeue several elements from the queue.
+       // Returns the number of items actually dequeued, which can
+       // be 0 if the timeout expires while waiting for elements,
+       // and at most max.
+       // Using a negative timeout indicates an indefinite timeout,
+       // and is thus functionally equivalent to calling wait_dequeue_bulk.
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::int64_t timeout_usecs)
+       {
+               size_t count = 0;
+               max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
+               while (count != max) {
+                       count += inner.template try_dequeue_bulk<It&>(itemFirst, max - count);
+               }
+               return count;
+       }
+    
+    // Attempts to dequeue several elements from the queue.
+       // Returns the number of items actually dequeued, which can
+       // be 0 if the timeout expires while waiting for elements,
+       // and at most max.
+       // Never allocates. Thread-safe.
+       template<typename It, typename Rep, typename Period>
+       inline size_t wait_dequeue_bulk_timed(It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
+    {
+        return wait_dequeue_bulk_timed<It&>(itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
+    }
+       
+       // Attempts to dequeue several elements from the queue using an explicit consumer token.
+       // Returns the number of items actually dequeued, which will
+       // always be at least one (this method blocks until the queue
+       // is non-empty) and at most max.
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t wait_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
+       {
+               size_t count = 0;
+               max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max);
+               while (count != max) {
+                       count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
+               }
+               return count;
+       }
+       
+       // Attempts to dequeue several elements from the queue using an explicit consumer token.
+       // Returns the number of items actually dequeued, which can
+       // be 0 if the timeout expires while waiting for elements,
+       // and at most max.
+       // Using a negative timeout indicates an indefinite timeout,
+       // and is thus functionally equivalent to calling wait_dequeue_bulk.
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::int64_t timeout_usecs)
+       {
+               size_t count = 0;
+               max = (size_t)sema->waitMany((LightweightSemaphore::ssize_t)(ssize_t)max, timeout_usecs);
+               while (count != max) {
+                       count += inner.template try_dequeue_bulk<It&>(token, itemFirst, max - count);
+               }
+               return count;
+       }
+       
+       // Attempts to dequeue several elements from the queue using an explicit consumer token.
+       // Returns the number of items actually dequeued, which can
+       // be 0 if the timeout expires while waiting for elements,
+       // and at most max.
+       // Never allocates. Thread-safe.
+       template<typename It, typename Rep, typename Period>
+       inline size_t wait_dequeue_bulk_timed(consumer_token_t& token, It itemFirst, size_t max, std::chrono::duration<Rep, Period> const& timeout)
+    {
+        return wait_dequeue_bulk_timed<It&>(token, itemFirst, max, std::chrono::duration_cast<std::chrono::microseconds>(timeout).count());
+    }
+       
+       
+       // Returns an estimate of the total number of elements currently in the queue. This
+       // estimate is only accurate if the queue has completely stabilized before it is called
+       // (i.e. all enqueue and dequeue operations have completed and their memory effects are
+       // visible on the calling thread, and no further operations start while this method is
+       // being called).
+       // Thread-safe.
+       inline size_t size_approx() const
+       {
+               return (size_t)sema->availableApprox();
+       }
+       
+       
+       // Returns true if the underlying atomic variables used by
+       // the queue are lock-free (they should be on most platforms).
+       // Thread-safe.
+       static bool is_lock_free()
+       {
+               return ConcurrentQueue::is_lock_free();
+       }
+       
+
+private:
+       template<typename U>
+       static inline U* create()
+       {
+               auto p = (Traits::malloc)(sizeof(U));
+               return p != nullptr ? new (p) U : nullptr;
+       }
+       
+       template<typename U, typename A1>
+       static inline U* create(A1&& a1)
+       {
+               auto p = (Traits::malloc)(sizeof(U));
+               return p != nullptr ? new (p) U(std::forward<A1>(a1)) : nullptr;
+       }
+       
+       template<typename U>
+       static inline void destroy(U* p)
+       {
+               if (p != nullptr) {
+                       p->~U();
+               }
+               (Traits::free)(p);
+       }
+       
+private:
+       ConcurrentQueue inner;
+       std::unique_ptr<LightweightSemaphore, void (*)(LightweightSemaphore*)> sema;
+};
+
+
+template<typename T, typename Traits>
+inline void swap(BlockingConcurrentQueue<T, Traits>& a, BlockingConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT
+{
+       a.swap(b);
+}
+
+}      // end namespace moodycamel
diff --git a/lib/concurrentqueue/include/moodycamel/concurrentqueue.h b/lib/concurrentqueue/include/moodycamel/concurrentqueue.h
new file mode 100644 (file)
index 0000000..9db83a1
--- /dev/null
@@ -0,0 +1,3623 @@
+// Provides a C++11 implementation of a multi-producer, multi-consumer lock-free queue.
+// An overview, including benchmark results, is provided here:
+//     http://moodycamel.com/blog/2014/a-fast-general-purpose-lock-free-queue-for-c++
+// The full design is also described in excruciating detail at:
+//    http://moodycamel.com/blog/2014/detailed-design-of-a-lock-free-queue
+
+// Simplified BSD license:
+// Copyright (c) 2013-2016, Cameron Desrochers.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification,
+// are permitted provided that the following conditions are met:
+//
+// - Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+// - Redistributions in binary form must reproduce the above copyright notice, this list of
+// conditions and the following disclaimer in the documentation and/or other materials
+// provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+#pragma once
+
+#if defined(__GNUC__)
+// Disable -Wconversion warnings (spuriously triggered when Traits::size_t and
+// Traits::index_t are set to < 32 bits, causing integer promotion, causing warnings
+// upon assigning any computed values)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wconversion"
+
+#ifdef MCDBGQ_USE_RELACY
+#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
+#endif
+#endif
+
+#if defined(__APPLE__)
+#include "TargetConditionals.h"
+#endif
+
+#ifdef MCDBGQ_USE_RELACY
+#include "relacy/relacy_std.hpp"
+#include "relacy_shims.h"
+// We only use malloc/free anyway, and the delete macro messes up `= delete` method declarations.
+// We'll override the default trait malloc ourselves without a macro.
+#undef new
+#undef delete
+#undef malloc
+#undef free
+#else
+#include <atomic>              // Requires C++11. Sorry VS2010.
+#include <cassert>
+#endif
+#include <cstddef>              // for max_align_t
+#include <cstdint>
+#include <cstdlib>
+#include <type_traits>
+#include <algorithm>
+#include <utility>
+#include <limits>
+#include <climits>             // for CHAR_BIT
+#include <array>
+#include <thread>              // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading
+
+// Platform-specific definitions of a numeric thread ID type and an invalid value
+namespace moodycamel { namespace details {
+       template<typename thread_id_t> struct thread_id_converter {
+               typedef thread_id_t thread_id_numeric_size_t;
+               typedef thread_id_t thread_id_hash_t;
+               static thread_id_hash_t prehash(thread_id_t const& x) { return x; }
+       };
+} }
+#if defined(MCDBGQ_USE_RELACY)
+namespace moodycamel { namespace details {
+       typedef std::uint32_t thread_id_t;
+       static const thread_id_t invalid_thread_id  = 0xFFFFFFFFU;
+       static const thread_id_t invalid_thread_id2 = 0xFFFFFFFEU;
+       static inline thread_id_t thread_id() { return rl::thread_index(); }
+} }
+#elif defined(_WIN32) || defined(__WINDOWS__) || defined(__WIN32__)
+// No sense pulling in windows.h in a header, we'll manually declare the function
+// we use and rely on backwards-compatibility for this not to break
+extern "C" __declspec(dllimport) unsigned long __stdcall GetCurrentThreadId(void);
+namespace moodycamel { namespace details {
+       static_assert(sizeof(unsigned long) == sizeof(std::uint32_t), "Expected size of unsigned long to be 32 bits on Windows");
+       typedef std::uint32_t thread_id_t;
+       static const thread_id_t invalid_thread_id  = 0;                        // See http://blogs.msdn.com/b/oldnewthing/archive/2004/02/23/78395.aspx
+       static const thread_id_t invalid_thread_id2 = 0xFFFFFFFFU;      // Not technically guaranteed to be invalid, but is never used in practice. Note that all Win32 thread IDs are presently multiples of 4.
+       static inline thread_id_t thread_id() { return static_cast<thread_id_t>(::GetCurrentThreadId()); }
+} }
+#elif defined(__arm__) || defined(_M_ARM) || defined(__aarch64__) || (defined(__APPLE__) && TARGET_OS_IPHONE)
+namespace moodycamel { namespace details {
+       static_assert(sizeof(std::thread::id) == 4 || sizeof(std::thread::id) == 8, "std::thread::id is expected to be either 4 or 8 bytes");
+       
+       typedef std::thread::id thread_id_t;
+       static const thread_id_t invalid_thread_id;         // Default ctor creates invalid ID
+
+       // Note we don't define a invalid_thread_id2 since std::thread::id doesn't have one; it's
+       // only used if MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is defined anyway, which it won't
+       // be.
+       static inline thread_id_t thread_id() { return std::this_thread::get_id(); }
+
+       template<std::size_t> struct thread_id_size { };
+       template<> struct thread_id_size<4> { typedef std::uint32_t numeric_t; };
+       template<> struct thread_id_size<8> { typedef std::uint64_t numeric_t; };
+
+       template<> struct thread_id_converter<thread_id_t> {
+               typedef thread_id_size<sizeof(thread_id_t)>::numeric_t thread_id_numeric_size_t;
+#ifndef __APPLE__
+               typedef std::size_t thread_id_hash_t;
+#else
+               typedef thread_id_numeric_size_t thread_id_hash_t;
+#endif
+
+               static thread_id_hash_t prehash(thread_id_t const& x)
+               {
+#ifndef __APPLE__
+                       return std::hash<std::thread::id>()(x);
+#else
+                       return *reinterpret_cast<thread_id_hash_t const*>(&x);
+#endif
+               }
+       };
+} }
+#else
+// Use a nice trick from this answer: http://stackoverflow.com/a/8438730/21475
+// In order to get a numeric thread ID in a platform-independent way, we use a thread-local
+// static variable's address as a thread identifier :-)
+#if defined(__GNUC__) || defined(__INTEL_COMPILER)
+#define MOODYCAMEL_THREADLOCAL __thread
+#elif defined(_MSC_VER)
+#define MOODYCAMEL_THREADLOCAL __declspec(thread)
+#else
+// Assume C++11 compliant compiler
+#define MOODYCAMEL_THREADLOCAL thread_local
+#endif
+namespace moodycamel { namespace details {
+       typedef std::uintptr_t thread_id_t;
+       static const thread_id_t invalid_thread_id  = 0;                // Address can't be nullptr
+       static const thread_id_t invalid_thread_id2 = 1;                // Member accesses off a null pointer are also generally invalid. Plus it's not aligned.
+       static inline thread_id_t thread_id() { static MOODYCAMEL_THREADLOCAL int x; return reinterpret_cast<thread_id_t>(&x); }
+} }
+#endif
+
+// Exceptions
+#ifndef MOODYCAMEL_EXCEPTIONS_ENABLED
+#if (defined(_MSC_VER) && defined(_CPPUNWIND)) || (defined(__GNUC__) && defined(__EXCEPTIONS)) || (!defined(_MSC_VER) && !defined(__GNUC__))
+#define MOODYCAMEL_EXCEPTIONS_ENABLED
+#endif
+#endif
+#ifdef MOODYCAMEL_EXCEPTIONS_ENABLED
+#define MOODYCAMEL_TRY try
+#define MOODYCAMEL_CATCH(...) catch(__VA_ARGS__)
+#define MOODYCAMEL_RETHROW throw
+#define MOODYCAMEL_THROW(expr) throw (expr)
+#else
+#define MOODYCAMEL_TRY if (true)
+#define MOODYCAMEL_CATCH(...) else if (false)
+#define MOODYCAMEL_RETHROW
+#define MOODYCAMEL_THROW(expr)
+#endif
+
+#ifndef MOODYCAMEL_NOEXCEPT
+#if !defined(MOODYCAMEL_EXCEPTIONS_ENABLED)
+#define MOODYCAMEL_NOEXCEPT
+#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) true
+#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) true
+#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1800
+// VS2012's std::is_nothrow_[move_]constructible is broken and returns true when it shouldn't :-(
+// We have to assume *all* non-trivial constructors may throw on VS2012!
+#define MOODYCAMEL_NOEXCEPT _NOEXCEPT
+#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference<valueType>::value && std::is_move_constructible<type>::value ? std::is_trivially_move_constructible<type>::value : std::is_trivially_copy_constructible<type>::value)
+#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference<valueType>::value && std::is_move_assignable<type>::value ? std::is_trivially_move_assignable<type>::value || std::is_nothrow_move_assignable<type>::value : std::is_trivially_copy_assignable<type>::value || std::is_nothrow_copy_assignable<type>::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr))
+#elif defined(_MSC_VER) && defined(_NOEXCEPT) && _MSC_VER < 1900
+#define MOODYCAMEL_NOEXCEPT _NOEXCEPT
+#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) (std::is_rvalue_reference<valueType>::value && std::is_move_constructible<type>::value ? std::is_trivially_move_constructible<type>::value || std::is_nothrow_move_constructible<type>::value : std::is_trivially_copy_constructible<type>::value || std::is_nothrow_copy_constructible<type>::value)
+#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) ((std::is_rvalue_reference<valueType>::value && std::is_move_assignable<type>::value ? std::is_trivially_move_assignable<type>::value || std::is_nothrow_move_assignable<type>::value : std::is_trivially_copy_assignable<type>::value || std::is_nothrow_copy_assignable<type>::value) && MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr))
+#else
+#define MOODYCAMEL_NOEXCEPT noexcept
+#define MOODYCAMEL_NOEXCEPT_CTOR(type, valueType, expr) noexcept(expr)
+#define MOODYCAMEL_NOEXCEPT_ASSIGN(type, valueType, expr) noexcept(expr)
+#endif
+#endif
+
+#ifndef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+#ifdef MCDBGQ_USE_RELACY
+#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+#else
+// VS2013 doesn't support `thread_local`, and MinGW-w64 w/ POSIX threading has a crippling bug: http://sourceforge.net/p/mingw-w64/bugs/445
+// g++ <=4.7 doesn't support thread_local either.
+// Finally, iOS/ARM doesn't have support for it either, and g++/ARM allows it to compile but it's unconfirmed to actually work
+#if (!defined(_MSC_VER) || _MSC_VER >= 1900) && (!defined(__MINGW32__) && !defined(__MINGW64__) || !defined(__WINPTHREADS_VERSION)) && (!defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) && (!defined(__APPLE__) || !TARGET_OS_IPHONE) && !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__)
+// Assume `thread_local` is fully supported in all other C++11 compilers/platforms
+//#define MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED    // always disabled for now since several users report having problems with it on
+#endif
+#endif
+#endif
+
+// VS2012 doesn't support deleted functions. 
+// In this case, we declare the function normally but don't define it. A link error will be generated if the function is called.
+#ifndef MOODYCAMEL_DELETE_FUNCTION
+#if defined(_MSC_VER) && _MSC_VER < 1800
+#define MOODYCAMEL_DELETE_FUNCTION
+#else
+#define MOODYCAMEL_DELETE_FUNCTION = delete
+#endif
+#endif
+
+// Compiler-specific likely/unlikely hints
+namespace moodycamel { namespace details {
+#if defined(__GNUC__)
+       inline bool likely(bool x) { return __builtin_expect((x), true); }
+       inline bool unlikely(bool x) { return __builtin_expect((x), false); }
+#else
+       inline bool likely(bool x) { return x; }
+       inline bool unlikely(bool x) { return x; }
+#endif
+} }
+
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+#include "internal/concurrentqueue_internal_debug.h"
+#endif
+
+namespace moodycamel {
+namespace details {
+       template<typename T>
+       struct const_numeric_max {
+               static_assert(std::is_integral<T>::value, "const_numeric_max can only be used with integers");
+               static const T value = std::numeric_limits<T>::is_signed
+                       ? (static_cast<T>(1) << (sizeof(T) * CHAR_BIT - 1)) - static_cast<T>(1)
+                       : static_cast<T>(-1);
+       };
+
+#if defined(__GNUC__) && !defined( __clang__ )
+       typedef ::max_align_t max_align_t;      // GCC forgot to add it to std:: for a while
+#else
+       typedef std::max_align_t max_align_t;   // Others (e.g. MSVC) insist it can *only* be accessed via std::
+#endif
+}
+
+// Default traits for the ConcurrentQueue. To change some of the
+// traits without re-implementing all of them, inherit from this
+// struct and shadow the declarations you wish to be different;
+// since the traits are used as a template type parameter, the
+// shadowed declarations will be used where defined, and the defaults
+// otherwise.
+struct ConcurrentQueueDefaultTraits
+{
+       // General-purpose size type. std::size_t is strongly recommended.
+       typedef std::size_t size_t;
+       
+       // The type used for the enqueue and dequeue indices. Must be at least as
+       // large as size_t. Should be significantly larger than the number of elements
+       // you expect to hold at once, especially if you have a high turnover rate;
+       // for example, on 32-bit x86, if you expect to have over a hundred million
+       // elements or pump several million elements through your queue in a very
+       // short space of time, using a 32-bit type *may* trigger a race condition.
+       // A 64-bit int type is recommended in that case, and in practice will
+       // prevent a race condition no matter the usage of the queue. Note that
+       // whether the queue is lock-free with a 64-int type depends on the whether
+       // std::atomic<std::uint64_t> is lock-free, which is platform-specific.
+       typedef std::size_t index_t;
+       
+       // Internally, all elements are enqueued and dequeued from multi-element
+       // blocks; this is the smallest controllable unit. If you expect few elements
+       // but many producers, a smaller block size should be favoured. For few producers
+       // and/or many elements, a larger block size is preferred. A sane default
+       // is provided. Must be a power of 2.
+       static const size_t BLOCK_SIZE = 32;
+       
+       // For explicit producers (i.e. when using a producer token), the block is
+       // checked for being empty by iterating through a list of flags, one per element.
+       // For large block sizes, this is too inefficient, and switching to an atomic
+       // counter-based approach is faster. The switch is made for block sizes strictly
+       // larger than this threshold.
+       static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = 32;
+       
+       // How many full blocks can be expected for a single explicit producer? This should
+       // reflect that number's maximum for optimal performance. Must be a power of 2.
+       static const size_t EXPLICIT_INITIAL_INDEX_SIZE = 32;
+       
+       // How many full blocks can be expected for a single implicit producer? This should
+       // reflect that number's maximum for optimal performance. Must be a power of 2.
+       static const size_t IMPLICIT_INITIAL_INDEX_SIZE = 32;
+       
+       // The initial size of the hash table mapping thread IDs to implicit producers.
+       // Note that the hash is resized every time it becomes half full.
+       // Must be a power of two, and either 0 or at least 1. If 0, implicit production
+       // (using the enqueue methods without an explicit producer token) is disabled.
+       static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = 32;
+       
+       // Controls the number of items that an explicit consumer (i.e. one with a token)
+       // must consume before it causes all consumers to rotate and move on to the next
+       // internal queue.
+       static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = 256;
+       
+       // The maximum number of elements (inclusive) that can be enqueued to a sub-queue.
+       // Enqueue operations that would cause this limit to be surpassed will fail. Note
+       // that this limit is enforced at the block level (for performance reasons), i.e.
+       // it's rounded up to the nearest block size.
+       static const size_t MAX_SUBQUEUE_SIZE = details::const_numeric_max<size_t>::value;
+       
+       
+#ifndef MCDBGQ_USE_RELACY
+       // Memory allocation can be customized if needed.
+       // malloc should return nullptr on failure, and handle alignment like std::malloc.
+#if defined(malloc) || defined(free)
+       // Gah, this is 2015, stop defining macros that break standard code already!
+       // Work around malloc/free being special macros:
+       static inline void* WORKAROUND_malloc(size_t size) { return malloc(size); }
+       static inline void WORKAROUND_free(void* ptr) { return free(ptr); }
+       static inline void* (malloc)(size_t size) { return WORKAROUND_malloc(size); }
+       static inline void (free)(void* ptr) { return WORKAROUND_free(ptr); }
+#else
+       static inline void* malloc(size_t size) { return std::malloc(size); }
+       static inline void free(void* ptr) { return std::free(ptr); }
+#endif
+#else
+       // Debug versions when running under the Relacy race detector (ignore
+       // these in user code)
+       static inline void* malloc(size_t size) { return rl::rl_malloc(size, $); }
+       static inline void free(void* ptr) { return rl::rl_free(ptr, $); }
+#endif
+};
+
+
+// When producing or consuming many elements, the most efficient way is to:
+//    1) Use one of the bulk-operation methods of the queue with a token
+//    2) Failing that, use the bulk-operation methods without a token
+//    3) Failing that, create a token and use that with the single-item methods
+//    4) Failing that, use the single-parameter methods of the queue
+// Having said that, don't create tokens willy-nilly -- ideally there should be
+// a maximum of one token per thread (of each kind).
+struct ProducerToken;
+struct ConsumerToken;
+
+template<typename T, typename Traits> class ConcurrentQueue;
+template<typename T, typename Traits> class BlockingConcurrentQueue;
+class ConcurrentQueueTests;
+
+
+namespace details
+{
+       struct ConcurrentQueueProducerTypelessBase
+       {
+               ConcurrentQueueProducerTypelessBase* next;
+               std::atomic<bool> inactive;
+               ProducerToken* token;
+               
+               ConcurrentQueueProducerTypelessBase()
+                       : next(nullptr), inactive(false), token(nullptr)
+               {
+               }
+       };
+       
+       template<bool use32> struct _hash_32_or_64 {
+               static inline std::uint32_t hash(std::uint32_t h)
+               {
+                       // MurmurHash3 finalizer -- see https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
+                       // Since the thread ID is already unique, all we really want to do is propagate that
+                       // uniqueness evenly across all the bits, so that we can use a subset of the bits while
+                       // reducing collisions significantly
+                       h ^= h >> 16;
+                       h *= 0x85ebca6b;
+                       h ^= h >> 13;
+                       h *= 0xc2b2ae35;
+                       return h ^ (h >> 16);
+               }
+       };
+       template<> struct _hash_32_or_64<1> {
+               static inline std::uint64_t hash(std::uint64_t h)
+               {
+                       h ^= h >> 33;
+                       h *= 0xff51afd7ed558ccd;
+                       h ^= h >> 33;
+                       h *= 0xc4ceb9fe1a85ec53;
+                       return h ^ (h >> 33);
+               }
+       };
+       template<std::size_t size> struct hash_32_or_64 : public _hash_32_or_64<(size > 4)> {  };
+       
+       static inline size_t hash_thread_id(thread_id_t id)
+       {
+               static_assert(sizeof(thread_id_t) <= 8, "Expected a platform where thread IDs are at most 64-bit values");
+               return static_cast<size_t>(hash_32_or_64<sizeof(thread_id_converter<thread_id_t>::thread_id_hash_t)>::hash(
+                       thread_id_converter<thread_id_t>::prehash(id)));
+       }
+       
+       template<typename T>
+       static inline bool circular_less_than(T a, T b)
+       {
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4554)
+#endif
+               static_assert(std::is_integral<T>::value && !std::numeric_limits<T>::is_signed, "circular_less_than is intended to be used only with unsigned integer types");
+               return static_cast<T>(a - b) > static_cast<T>(static_cast<T>(1) << static_cast<T>(sizeof(T) * CHAR_BIT - 1));
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+       }
+       
+       template<typename U>
+       static inline char* align_for(char* ptr)
+       {
+               const std::size_t alignment = std::alignment_of<U>::value;
+               return ptr + (alignment - (reinterpret_cast<std::uintptr_t>(ptr) % alignment)) % alignment;
+       }
+
+       template<typename T>
+       static inline T ceil_to_pow_2(T x)
+       {
+               static_assert(std::is_integral<T>::value && !std::numeric_limits<T>::is_signed, "ceil_to_pow_2 is intended to be used only with unsigned integer types");
+
+               // Adapted from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
+               --x;
+               x |= x >> 1;
+               x |= x >> 2;
+               x |= x >> 4;
+               for (std::size_t i = 1; i < sizeof(T); i <<= 1) {
+                       x |= x >> (i << 3);
+               }
+               ++x;
+               return x;
+       }
+       
+       template<typename T>
+       static inline void swap_relaxed(std::atomic<T>& left, std::atomic<T>& right)
+       {
+               T temp = std::move(left.load(std::memory_order_relaxed));
+               left.store(std::move(right.load(std::memory_order_relaxed)), std::memory_order_relaxed);
+               right.store(std::move(temp), std::memory_order_relaxed);
+       }
+       
+       template<typename T>
+       static inline T const& nomove(T const& x)
+       {
+               return x;
+       }
+       
+       template<bool Enable>
+       struct nomove_if
+       {
+               template<typename T>
+               static inline T const& eval(T const& x)
+               {
+                       return x;
+               }
+       };
+       
+       template<>
+       struct nomove_if<false>
+       {
+               template<typename U>
+               static inline auto eval(U&& x)
+                       -> decltype(std::forward<U>(x))
+               {
+                       return std::forward<U>(x);
+               }
+       };
+       
+       template<typename It>
+       static inline auto deref_noexcept(It& it) MOODYCAMEL_NOEXCEPT -> decltype(*it)
+       {
+               return *it;
+       }
+       
+#if defined(__clang__) || !defined(__GNUC__) || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)
+       template<typename T> struct is_trivially_destructible : std::is_trivially_destructible<T> { };
+#else
+       template<typename T> struct is_trivially_destructible : std::has_trivial_destructor<T> { };
+#endif
+       
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+#ifdef MCDBGQ_USE_RELACY
+       typedef RelacyThreadExitListener ThreadExitListener;
+       typedef RelacyThreadExitNotifier ThreadExitNotifier;
+#else
+       struct ThreadExitListener
+       {
+               typedef void (*callback_t)(void*);
+               callback_t callback;
+               void* userData;
+               
+               ThreadExitListener* next;               // reserved for use by the ThreadExitNotifier
+       };
+       
+       
+       class ThreadExitNotifier
+       {
+       public:
+               static void subscribe(ThreadExitListener* listener)
+               {
+                       auto& tlsInst = instance();
+                       listener->next = tlsInst.tail;
+                       tlsInst.tail = listener;
+               }
+               
+               static void unsubscribe(ThreadExitListener* listener)
+               {
+                       auto& tlsInst = instance();
+                       ThreadExitListener** prev = &tlsInst.tail;
+                       for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) {
+                               if (ptr == listener) {
+                                       *prev = ptr->next;
+                                       break;
+                               }
+                               prev = &ptr->next;
+                       }
+               }
+               
+       private:
+               ThreadExitNotifier() : tail(nullptr) { }
+               ThreadExitNotifier(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION;
+               ThreadExitNotifier& operator=(ThreadExitNotifier const&) MOODYCAMEL_DELETE_FUNCTION;
+               
+               ~ThreadExitNotifier()
+               {
+                       // This thread is about to exit, let everyone know!
+                       assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined.");
+                       for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) {
+                               ptr->callback(ptr->userData);
+                       }
+               }
+               
+               // Thread-local
+               static inline ThreadExitNotifier& instance()
+               {
+                       static thread_local ThreadExitNotifier notifier;
+                       return notifier;
+               }
+               
+       private:
+               ThreadExitListener* tail;
+       };
+#endif
+#endif
+       
+       template<typename T> struct static_is_lock_free_num { enum { value = 0 }; };
+       template<> struct static_is_lock_free_num<signed char> { enum { value = ATOMIC_CHAR_LOCK_FREE }; };
+       template<> struct static_is_lock_free_num<short> { enum { value = ATOMIC_SHORT_LOCK_FREE }; };
+       template<> struct static_is_lock_free_num<int> { enum { value = ATOMIC_INT_LOCK_FREE }; };
+       template<> struct static_is_lock_free_num<long> { enum { value = ATOMIC_LONG_LOCK_FREE }; };
+       template<> struct static_is_lock_free_num<long long> { enum { value = ATOMIC_LLONG_LOCK_FREE }; };
+       template<typename T> struct static_is_lock_free : static_is_lock_free_num<typename std::make_signed<T>::type> {  };
+       template<> struct static_is_lock_free<bool> { enum { value = ATOMIC_BOOL_LOCK_FREE }; };
+       template<typename U> struct static_is_lock_free<U*> { enum { value = ATOMIC_POINTER_LOCK_FREE }; };
+}
+
+
+struct ProducerToken
+{
+       template<typename T, typename Traits>
+       explicit ProducerToken(ConcurrentQueue<T, Traits>& queue);
+       
+       template<typename T, typename Traits>
+       explicit ProducerToken(BlockingConcurrentQueue<T, Traits>& queue);
+       
+       ProducerToken(ProducerToken&& other) MOODYCAMEL_NOEXCEPT
+               : producer(other.producer)
+       {
+               other.producer = nullptr;
+               if (producer != nullptr) {
+                       producer->token = this;
+               }
+       }
+       
+       inline ProducerToken& operator=(ProducerToken&& other) MOODYCAMEL_NOEXCEPT
+       {
+               swap(other);
+               return *this;
+       }
+       
+       void swap(ProducerToken& other) MOODYCAMEL_NOEXCEPT
+       {
+               std::swap(producer, other.producer);
+               if (producer != nullptr) {
+                       producer->token = this;
+               }
+               if (other.producer != nullptr) {
+                       other.producer->token = &other;
+               }
+       }
+       
+       // A token is always valid unless:
+       //     1) Memory allocation failed during construction
+       //     2) It was moved via the move constructor
+       //        (Note: assignment does a swap, leaving both potentially valid)
+       //     3) The associated queue was destroyed
+       // Note that if valid() returns true, that only indicates
+       // that the token is valid for use with a specific queue,
+       // but not which one; that's up to the user to track.
+       inline bool valid() const { return producer != nullptr; }
+       
+       ~ProducerToken()
+       {
+               if (producer != nullptr) {
+                       producer->token = nullptr;
+                       producer->inactive.store(true, std::memory_order_release);
+               }
+       }
+       
+       // Disable copying and assignment
+       ProducerToken(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION;
+       ProducerToken& operator=(ProducerToken const&) MOODYCAMEL_DELETE_FUNCTION;
+       
+private:
+       template<typename T, typename Traits> friend class ConcurrentQueue;
+       friend class ConcurrentQueueTests;
+       
+protected:
+       details::ConcurrentQueueProducerTypelessBase* producer;
+};
+
+
+struct ConsumerToken
+{
+       template<typename T, typename Traits>
+       explicit ConsumerToken(ConcurrentQueue<T, Traits>& q);
+       
+       template<typename T, typename Traits>
+       explicit ConsumerToken(BlockingConcurrentQueue<T, Traits>& q);
+       
+       ConsumerToken(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT
+               : initialOffset(other.initialOffset), lastKnownGlobalOffset(other.lastKnownGlobalOffset), itemsConsumedFromCurrent(other.itemsConsumedFromCurrent), currentProducer(other.currentProducer), desiredProducer(other.desiredProducer)
+       {
+       }
+       
+       inline ConsumerToken& operator=(ConsumerToken&& other) MOODYCAMEL_NOEXCEPT
+       {
+               swap(other);
+               return *this;
+       }
+       
+       void swap(ConsumerToken& other) MOODYCAMEL_NOEXCEPT
+       {
+               std::swap(initialOffset, other.initialOffset);
+               std::swap(lastKnownGlobalOffset, other.lastKnownGlobalOffset);
+               std::swap(itemsConsumedFromCurrent, other.itemsConsumedFromCurrent);
+               std::swap(currentProducer, other.currentProducer);
+               std::swap(desiredProducer, other.desiredProducer);
+       }
+       
+       // Disable copying and assignment
+       ConsumerToken(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION;
+       ConsumerToken& operator=(ConsumerToken const&) MOODYCAMEL_DELETE_FUNCTION;
+
+private:
+       template<typename T, typename Traits> friend class ConcurrentQueue;
+       friend class ConcurrentQueueTests;
+       
+private: // but shared with ConcurrentQueue
+       std::uint32_t initialOffset;
+       std::uint32_t lastKnownGlobalOffset;
+       std::uint32_t itemsConsumedFromCurrent;
+       details::ConcurrentQueueProducerTypelessBase* currentProducer;
+       details::ConcurrentQueueProducerTypelessBase* desiredProducer;
+};
+
+// Need to forward-declare this swap because it's in a namespace.
+// See http://stackoverflow.com/questions/4492062/why-does-a-c-friend-class-need-a-forward-declaration-only-in-other-namespaces
+template<typename T, typename Traits>
+inline void swap(typename ConcurrentQueue<T, Traits>::ImplicitProducerKVP& a, typename ConcurrentQueue<T, Traits>::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT;
+
+
+template<typename T, typename Traits = ConcurrentQueueDefaultTraits>
+class ConcurrentQueue
+{
+public:
+       typedef ::moodycamel::ProducerToken producer_token_t;
+       typedef ::moodycamel::ConsumerToken consumer_token_t;
+       
+       typedef typename Traits::index_t index_t;
+       typedef typename Traits::size_t size_t;
+       
+       static const size_t BLOCK_SIZE = static_cast<size_t>(Traits::BLOCK_SIZE);
+       static const size_t EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD = static_cast<size_t>(Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD);
+       static const size_t EXPLICIT_INITIAL_INDEX_SIZE = static_cast<size_t>(Traits::EXPLICIT_INITIAL_INDEX_SIZE);
+       static const size_t IMPLICIT_INITIAL_INDEX_SIZE = static_cast<size_t>(Traits::IMPLICIT_INITIAL_INDEX_SIZE);
+       static const size_t INITIAL_IMPLICIT_PRODUCER_HASH_SIZE = static_cast<size_t>(Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE);
+       static const std::uint32_t EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE = static_cast<std::uint32_t>(Traits::EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE);
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4307)         // + integral constant overflow (that's what the ternary expression is for!)
+#pragma warning(disable: 4309)         // static_cast: Truncation of constant value
+#endif
+       static const size_t MAX_SUBQUEUE_SIZE = (details::const_numeric_max<size_t>::value - static_cast<size_t>(Traits::MAX_SUBQUEUE_SIZE) < BLOCK_SIZE) ? details::const_numeric_max<size_t>::value : ((static_cast<size_t>(Traits::MAX_SUBQUEUE_SIZE) + (BLOCK_SIZE - 1)) / BLOCK_SIZE * BLOCK_SIZE);
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+       static_assert(!std::numeric_limits<size_t>::is_signed && std::is_integral<size_t>::value, "Traits::size_t must be an unsigned integral type");
+       static_assert(!std::numeric_limits<index_t>::is_signed && std::is_integral<index_t>::value, "Traits::index_t must be an unsigned integral type");
+       static_assert(sizeof(index_t) >= sizeof(size_t), "Traits::index_t must be at least as wide as Traits::size_t");
+       static_assert((BLOCK_SIZE > 1) && !(BLOCK_SIZE & (BLOCK_SIZE - 1)), "Traits::BLOCK_SIZE must be a power of 2 (and at least 2)");
+       static_assert((EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD > 1) && !(EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD & (EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD - 1)), "Traits::EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD must be a power of 2 (and greater than 1)");
+       static_assert((EXPLICIT_INITIAL_INDEX_SIZE > 1) && !(EXPLICIT_INITIAL_INDEX_SIZE & (EXPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::EXPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)");
+       static_assert((IMPLICIT_INITIAL_INDEX_SIZE > 1) && !(IMPLICIT_INITIAL_INDEX_SIZE & (IMPLICIT_INITIAL_INDEX_SIZE - 1)), "Traits::IMPLICIT_INITIAL_INDEX_SIZE must be a power of 2 (and greater than 1)");
+       static_assert((INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) || !(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE & (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE - 1)), "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be a power of 2");
+       static_assert(INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0 || INITIAL_IMPLICIT_PRODUCER_HASH_SIZE >= 1, "Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE must be at least 1 (or 0 to disable implicit enqueueing)");
+
+public:
+       // Creates a queue with at least `capacity` element slots; note that the
+       // actual number of elements that can be inserted without additional memory
+       // allocation depends on the number of producers and the block size (e.g. if
+       // the block size is equal to `capacity`, only a single block will be allocated
+       // up-front, which means only a single producer will be able to enqueue elements
+       // without an extra allocation -- blocks aren't shared between producers).
+       // This method is not thread safe -- it is up to the user to ensure that the
+       // queue is fully constructed before it starts being used by other threads (this
+       // includes making the memory effects of construction visible, possibly with a
+       // memory barrier).
+       explicit ConcurrentQueue(size_t capacity = 6 * BLOCK_SIZE)
+               : producerListTail(nullptr),
+               producerCount(0),
+               initialBlockPoolIndex(0),
+               nextExplicitConsumerId(0),
+               globalExplicitConsumerOffset(0)
+       {
+               implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed);
+               populate_initial_implicit_producer_hash();
+               populate_initial_block_list(capacity / BLOCK_SIZE + ((capacity & (BLOCK_SIZE - 1)) == 0 ? 0 : 1));
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+               // Track all the producers using a fully-resolved typed list for
+               // each kind; this makes it possible to debug them starting from
+               // the root queue object (otherwise wacky casts are needed that
+               // don't compile in the debugger's expression evaluator).
+               explicitProducers.store(nullptr, std::memory_order_relaxed);
+               implicitProducers.store(nullptr, std::memory_order_relaxed);
+#endif
+       }
+       
+       // Computes the correct amount of pre-allocated blocks for you based
+       // on the minimum number of elements you want available at any given
+       // time, and the maximum concurrent number of each type of producer.
+       ConcurrentQueue(size_t minCapacity, size_t maxExplicitProducers, size_t maxImplicitProducers)
+               : producerListTail(nullptr),
+               producerCount(0),
+               initialBlockPoolIndex(0),
+               nextExplicitConsumerId(0),
+               globalExplicitConsumerOffset(0)
+       {
+               implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed);
+               populate_initial_implicit_producer_hash();
+               size_t blocks = (((minCapacity + BLOCK_SIZE - 1) / BLOCK_SIZE) - 1) * (maxExplicitProducers + 1) + 2 * (maxExplicitProducers + maxImplicitProducers);
+               populate_initial_block_list(blocks);
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+               explicitProducers.store(nullptr, std::memory_order_relaxed);
+               implicitProducers.store(nullptr, std::memory_order_relaxed);
+#endif
+       }
+       
+       // Note: The queue should not be accessed concurrently while it's
+       // being deleted. It's up to the user to synchronize this.
+       // This method is not thread safe.
+       ~ConcurrentQueue()
+       {
+               // Destroy producers
+               auto ptr = producerListTail.load(std::memory_order_relaxed);
+               while (ptr != nullptr) {
+                       auto next = ptr->next_prod();
+                       if (ptr->token != nullptr) {
+                               ptr->token->producer = nullptr;
+                       }
+                       destroy(ptr);
+                       ptr = next;
+               }
+               
+               // Destroy implicit producer hash tables
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE != 0) {
+                       auto hash = implicitProducerHash.load(std::memory_order_relaxed);
+                       while (hash != nullptr) {
+                               auto prev = hash->prev;
+                               if (prev != nullptr) {          // The last hash is part of this object and was not allocated dynamically
+                                       for (size_t i = 0; i != hash->capacity; ++i) {
+                                               hash->entries[i].~ImplicitProducerKVP();
+                                       }
+                                       hash->~ImplicitProducerHash();
+                                       (Traits::free)(hash);
+                               }
+                               hash = prev;
+                       }
+               }
+               
+               // Destroy global free list
+               auto block = freeList.head_unsafe();
+               while (block != nullptr) {
+                       auto next = block->freeListNext.load(std::memory_order_relaxed);
+                       if (block->dynamicallyAllocated) {
+                               destroy(block);
+                       }
+                       block = next;
+               }
+               
+               // Destroy initial free list
+               destroy_array(initialBlockPool, initialBlockPoolSize);
+       }
+
+       // Disable copying and copy assignment
+       ConcurrentQueue(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
+       ConcurrentQueue& operator=(ConcurrentQueue const&) MOODYCAMEL_DELETE_FUNCTION;
+       
+       // Moving is supported, but note that it is *not* a thread-safe operation.
+       // Nobody can use the queue while it's being moved, and the memory effects
+       // of that move must be propagated to other threads before they can use it.
+       // Note: When a queue is moved, its tokens are still valid but can only be
+       // used with the destination queue (i.e. semantically they are moved along
+       // with the queue itself).
+       ConcurrentQueue(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
+               : producerListTail(other.producerListTail.load(std::memory_order_relaxed)),
+               producerCount(other.producerCount.load(std::memory_order_relaxed)),
+               initialBlockPoolIndex(other.initialBlockPoolIndex.load(std::memory_order_relaxed)),
+               initialBlockPool(other.initialBlockPool),
+               initialBlockPoolSize(other.initialBlockPoolSize),
+               freeList(std::move(other.freeList)),
+               nextExplicitConsumerId(other.nextExplicitConsumerId.load(std::memory_order_relaxed)),
+               globalExplicitConsumerOffset(other.globalExplicitConsumerOffset.load(std::memory_order_relaxed))
+       {
+               // Move the other one into this, and leave the other one as an empty queue
+               implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed);
+               populate_initial_implicit_producer_hash();
+               swap_implicit_producer_hashes(other);
+               
+               other.producerListTail.store(nullptr, std::memory_order_relaxed);
+               other.producerCount.store(0, std::memory_order_relaxed);
+               other.nextExplicitConsumerId.store(0, std::memory_order_relaxed);
+               other.globalExplicitConsumerOffset.store(0, std::memory_order_relaxed);
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+               explicitProducers.store(other.explicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed);
+               other.explicitProducers.store(nullptr, std::memory_order_relaxed);
+               implicitProducers.store(other.implicitProducers.load(std::memory_order_relaxed), std::memory_order_relaxed);
+               other.implicitProducers.store(nullptr, std::memory_order_relaxed);
+#endif
+               
+               other.initialBlockPoolIndex.store(0, std::memory_order_relaxed);
+               other.initialBlockPoolSize = 0;
+               other.initialBlockPool = nullptr;
+               
+               reown_producers();
+       }
+       
+       inline ConcurrentQueue& operator=(ConcurrentQueue&& other) MOODYCAMEL_NOEXCEPT
+       {
+               return swap_internal(other);
+       }
+       
+       // Swaps this queue's state with the other's. Not thread-safe.
+       // Swapping two queues does not invalidate their tokens, however
+       // the tokens that were created for one queue must be used with
+       // only the swapped queue (i.e. the tokens are tied to the
+       // queue's movable state, not the object itself).
+       inline void swap(ConcurrentQueue& other) MOODYCAMEL_NOEXCEPT
+       {
+               swap_internal(other);
+       }
+       
+private:
+       ConcurrentQueue& swap_internal(ConcurrentQueue& other)
+       {
+               if (this == &other) {
+                       return *this;
+               }
+               
+               details::swap_relaxed(producerListTail, other.producerListTail);
+               details::swap_relaxed(producerCount, other.producerCount);
+               details::swap_relaxed(initialBlockPoolIndex, other.initialBlockPoolIndex);
+               std::swap(initialBlockPool, other.initialBlockPool);
+               std::swap(initialBlockPoolSize, other.initialBlockPoolSize);
+               freeList.swap(other.freeList);
+               details::swap_relaxed(nextExplicitConsumerId, other.nextExplicitConsumerId);
+               details::swap_relaxed(globalExplicitConsumerOffset, other.globalExplicitConsumerOffset);
+               
+               swap_implicit_producer_hashes(other);
+               
+               reown_producers();
+               other.reown_producers();
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+               details::swap_relaxed(explicitProducers, other.explicitProducers);
+               details::swap_relaxed(implicitProducers, other.implicitProducers);
+#endif
+               
+               return *this;
+       }
+       
+public:
+       // Enqueues a single item (by copying it).
+       // Allocates memory if required. Only fails if memory allocation fails (or implicit
+       // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
+       // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(T const& item)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
+               return inner_enqueue<CanAlloc>(item);
+       }
+       
+       // Enqueues a single item (by moving it, if possible).
+       // Allocates memory if required. Only fails if memory allocation fails (or implicit
+       // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0,
+       // or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(T&& item)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
+               return inner_enqueue<CanAlloc>(std::move(item));
+       }
+       
+       // Enqueues a single item (by copying it) using an explicit producer token.
+       // Allocates memory if required. Only fails if memory allocation fails (or
+       // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(producer_token_t const& token, T const& item)
+       {
+               return inner_enqueue<CanAlloc>(token, item);
+       }
+       
+       // Enqueues a single item (by moving it, if possible) using an explicit producer token.
+       // Allocates memory if required. Only fails if memory allocation fails (or
+       // Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Thread-safe.
+       inline bool enqueue(producer_token_t const& token, T&& item)
+       {
+               return inner_enqueue<CanAlloc>(token, std::move(item));
+       }
+       
+       // Enqueues several items.
+       // Allocates memory if required. Only fails if memory allocation fails (or
+       // implicit production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
+       // is 0, or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Note: Use std::make_move_iterator if the elements should be moved instead of copied.
+       // Thread-safe.
+       template<typename It>
+       bool enqueue_bulk(It itemFirst, size_t count)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
+               return inner_enqueue_bulk<CanAlloc>(itemFirst, count);
+       }
+       
+       // Enqueues several items using an explicit producer token.
+       // Allocates memory if required. Only fails if memory allocation fails
+       // (or Traits::MAX_SUBQUEUE_SIZE has been defined and would be surpassed).
+       // Note: Use std::make_move_iterator if the elements should be moved
+       // instead of copied.
+       // Thread-safe.
+       template<typename It>
+       bool enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
+       {
+               return inner_enqueue_bulk<CanAlloc>(token, itemFirst, count);
+       }
+       
+       // Enqueues a single item (by copying it).
+       // Does not allocate memory. Fails if not enough room to enqueue (or implicit
+       // production is disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE
+       // is 0).
+       // Thread-safe.
+       inline bool try_enqueue(T const& item)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
+               return inner_enqueue<CannotAlloc>(item);
+       }
+       
+       // Enqueues a single item (by moving it, if possible).
+       // Does not allocate memory (except for one-time implicit producer).
+       // Fails if not enough room to enqueue (or implicit production is
+       // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
+       // Thread-safe.
+       inline bool try_enqueue(T&& item)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
+               return inner_enqueue<CannotAlloc>(std::move(item));
+       }
+       
+       // Enqueues a single item (by copying it) using an explicit producer token.
+       // Does not allocate memory. Fails if not enough room to enqueue.
+       // Thread-safe.
+       inline bool try_enqueue(producer_token_t const& token, T const& item)
+       {
+               return inner_enqueue<CannotAlloc>(token, item);
+       }
+       
+       // Enqueues a single item (by moving it, if possible) using an explicit producer token.
+       // Does not allocate memory. Fails if not enough room to enqueue.
+       // Thread-safe.
+       inline bool try_enqueue(producer_token_t const& token, T&& item)
+       {
+               return inner_enqueue<CannotAlloc>(token, std::move(item));
+       }
+       
+       // Enqueues several items.
+       // Does not allocate memory (except for one-time implicit producer).
+       // Fails if not enough room to enqueue (or implicit production is
+       // disabled because Traits::INITIAL_IMPLICIT_PRODUCER_HASH_SIZE is 0).
+       // Note: Use std::make_move_iterator if the elements should be moved
+       // instead of copied.
+       // Thread-safe.
+       template<typename It>
+       bool try_enqueue_bulk(It itemFirst, size_t count)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return false;
+               return inner_enqueue_bulk<CannotAlloc>(itemFirst, count);
+       }
+       
+       // Enqueues several items using an explicit producer token.
+       // Does not allocate memory. Fails if not enough room to enqueue.
+       // Note: Use std::make_move_iterator if the elements should be moved
+       // instead of copied.
+       // Thread-safe.
+       template<typename It>
+       bool try_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
+       {
+               return inner_enqueue_bulk<CannotAlloc>(token, itemFirst, count);
+       }
+       
+       
+       
+       // Attempts to dequeue from the queue.
+       // Returns false if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename U>
+       bool try_dequeue(U& item)
+       {
+               // Instead of simply trying each producer in turn (which could cause needless contention on the first
+               // producer), we score them heuristically.
+               size_t nonEmptyCount = 0;
+               ProducerBase* best = nullptr;
+               size_t bestSize = 0;
+               for (auto ptr = producerListTail.load(std::memory_order_acquire); nonEmptyCount < 3 && ptr != nullptr; ptr = ptr->next_prod()) {
+                       auto size = ptr->size_approx();
+                       if (size > 0) {
+                               if (size > bestSize) {
+                                       bestSize = size;
+                                       best = ptr;
+                               }
+                               ++nonEmptyCount;
+                       }
+               }
+               
+               // If there was at least one non-empty queue but it appears empty at the time
+               // we try to dequeue from it, we need to make sure every queue's been tried
+               if (nonEmptyCount > 0) {
+                       if (details::likely(best->dequeue(item))) {
+                               return true;
+                       }
+                       for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
+                               if (ptr != best && ptr->dequeue(item)) {
+                                       return true;
+                               }
+                       }
+               }
+               return false;
+       }
+       
+       // Attempts to dequeue from the queue.
+       // Returns false if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // This differs from the try_dequeue(item) method in that this one does
+       // not attempt to reduce contention by interleaving the order that producer
+       // streams are dequeued from. So, using this method can reduce overall throughput
+       // under contention, but will give more predictable results in single-threaded
+       // consumer scenarios. This is mostly only useful for internal unit tests.
+       // Never allocates. Thread-safe.
+       template<typename U>
+       bool try_dequeue_non_interleaved(U& item)
+       {
+               for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
+                       if (ptr->dequeue(item)) {
+                               return true;
+                       }
+               }
+               return false;
+       }
+       
+       // Attempts to dequeue from the queue using an explicit consumer token.
+       // Returns false if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename U>
+       bool try_dequeue(consumer_token_t& token, U& item)
+       {
+               // The idea is roughly as follows:
+               // Every 256 items from one producer, make everyone rotate (increase the global offset) -> this means the highest efficiency consumer dictates the rotation speed of everyone else, more or less
+               // If you see that the global offset has changed, you must reset your consumption counter and move to your designated place
+               // If there's no items where you're supposed to be, keep moving until you find a producer with some items
+               // If the global offset has not changed but you've run out of items to consume, move over from your current position until you find an producer with something in it
+               
+               if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) {
+                       if (!update_current_producer_after_rotation(token)) {
+                               return false;
+                       }
+               }
+               
+               // If there was at least one non-empty queue but it appears empty at the time
+               // we try to dequeue from it, we need to make sure every queue's been tried
+               if (static_cast<ProducerBase*>(token.currentProducer)->dequeue(item)) {
+                       if (++token.itemsConsumedFromCurrent == EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) {
+                               globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed);
+                       }
+                       return true;
+               }
+               
+               auto tail = producerListTail.load(std::memory_order_acquire);
+               auto ptr = static_cast<ProducerBase*>(token.currentProducer)->next_prod();
+               if (ptr == nullptr) {
+                       ptr = tail;
+               }
+               while (ptr != static_cast<ProducerBase*>(token.currentProducer)) {
+                       if (ptr->dequeue(item)) {
+                               token.currentProducer = ptr;
+                               token.itemsConsumedFromCurrent = 1;
+                               return true;
+                       }
+                       ptr = ptr->next_prod();
+                       if (ptr == nullptr) {
+                               ptr = tail;
+                       }
+               }
+               return false;
+       }
+       
+       // Attempts to dequeue several elements from the queue.
+       // Returns the number of items actually dequeued.
+       // Returns 0 if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename It>
+       size_t try_dequeue_bulk(It itemFirst, size_t max)
+       {
+               size_t count = 0;
+               for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
+                       count += ptr->dequeue_bulk(itemFirst, max - count);
+                       if (count == max) {
+                               break;
+                       }
+               }
+               return count;
+       }
+       
+       // Attempts to dequeue several elements from the queue using an explicit consumer token.
+       // Returns the number of items actually dequeued.
+       // Returns 0 if all producer streams appeared empty at the time they
+       // were checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename It>
+       size_t try_dequeue_bulk(consumer_token_t& token, It itemFirst, size_t max)
+       {
+               if (token.desiredProducer == nullptr || token.lastKnownGlobalOffset != globalExplicitConsumerOffset.load(std::memory_order_relaxed)) {
+                       if (!update_current_producer_after_rotation(token)) {
+                               return 0;
+                       }
+               }
+               
+               size_t count = static_cast<ProducerBase*>(token.currentProducer)->dequeue_bulk(itemFirst, max);
+               if (count == max) {
+                       if ((token.itemsConsumedFromCurrent += static_cast<std::uint32_t>(max)) >= EXPLICIT_CONSUMER_CONSUMPTION_QUOTA_BEFORE_ROTATE) {
+                               globalExplicitConsumerOffset.fetch_add(1, std::memory_order_relaxed);
+                       }
+                       return max;
+               }
+               token.itemsConsumedFromCurrent += static_cast<std::uint32_t>(count);
+               max -= count;
+               
+               auto tail = producerListTail.load(std::memory_order_acquire);
+               auto ptr = static_cast<ProducerBase*>(token.currentProducer)->next_prod();
+               if (ptr == nullptr) {
+                       ptr = tail;
+               }
+               while (ptr != static_cast<ProducerBase*>(token.currentProducer)) {
+                       auto dequeued = ptr->dequeue_bulk(itemFirst, max);
+                       count += dequeued;
+                       if (dequeued != 0) {
+                               token.currentProducer = ptr;
+                               token.itemsConsumedFromCurrent = static_cast<std::uint32_t>(dequeued);
+                       }
+                       if (dequeued == max) {
+                               break;
+                       }
+                       max -= dequeued;
+                       ptr = ptr->next_prod();
+                       if (ptr == nullptr) {
+                               ptr = tail;
+                       }
+               }
+               return count;
+       }
+       
+       
+       
+       // Attempts to dequeue from a specific producer's inner queue.
+       // If you happen to know which producer you want to dequeue from, this
+       // is significantly faster than using the general-case try_dequeue methods.
+       // Returns false if the producer's queue appeared empty at the time it
+       // was checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename U>
+       inline bool try_dequeue_from_producer(producer_token_t const& producer, U& item)
+       {
+               return static_cast<ExplicitProducer*>(producer.producer)->dequeue(item);
+       }
+       
+       // Attempts to dequeue several elements from a specific producer's inner queue.
+       // Returns the number of items actually dequeued.
+       // If you happen to know which producer you want to dequeue from, this
+       // is significantly faster than using the general-case try_dequeue methods.
+       // Returns 0 if the producer's queue appeared empty at the time it
+       // was checked (so, the queue is likely but not guaranteed to be empty).
+       // Never allocates. Thread-safe.
+       template<typename It>
+       inline size_t try_dequeue_bulk_from_producer(producer_token_t const& producer, It itemFirst, size_t max)
+       {
+               return static_cast<ExplicitProducer*>(producer.producer)->dequeue_bulk(itemFirst, max);
+       }
+       
+       
+       // Returns an estimate of the total number of elements currently in the queue. This
+       // estimate is only accurate if the queue has completely stabilized before it is called
+       // (i.e. all enqueue and dequeue operations have completed and their memory effects are
+       // visible on the calling thread, and no further operations start while this method is
+       // being called).
+       // Thread-safe.
+       size_t size_approx() const
+       {
+               size_t size = 0;
+               for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
+                       size += ptr->size_approx();
+               }
+               return size;
+       }
+       
+       
+       // Returns true if the underlying atomic variables used by
+       // the queue are lock-free (they should be on most platforms).
+       // Thread-safe.
+       static bool is_lock_free()
+       {
+               return
+                       details::static_is_lock_free<bool>::value == 2 &&
+                       details::static_is_lock_free<size_t>::value == 2 &&
+                       details::static_is_lock_free<std::uint32_t>::value == 2 &&
+                       details::static_is_lock_free<index_t>::value == 2 &&
+                       details::static_is_lock_free<void*>::value == 2 &&
+                       details::static_is_lock_free<typename details::thread_id_converter<details::thread_id_t>::thread_id_numeric_size_t>::value == 2;
+       }
+
+
+private:
+       friend struct ProducerToken;
+       friend struct ConsumerToken;
+       friend struct ExplicitProducer;
+       friend class ConcurrentQueueTests;
+               
+       enum AllocationMode { CanAlloc, CannotAlloc };
+       
+       
+       ///////////////////////////////
+       // Queue methods
+       ///////////////////////////////
+       
+       template<AllocationMode canAlloc, typename U>
+       inline bool inner_enqueue(producer_token_t const& token, U&& element)
+       {
+               return static_cast<ExplicitProducer*>(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue<canAlloc>(std::forward<U>(element));
+       }
+       
+       template<AllocationMode canAlloc, typename U>
+       inline bool inner_enqueue(U&& element)
+       {
+               auto producer = get_or_add_implicit_producer();
+               return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue<canAlloc>(std::forward<U>(element));
+       }
+       
+       template<AllocationMode canAlloc, typename It>
+       inline bool inner_enqueue_bulk(producer_token_t const& token, It itemFirst, size_t count)
+       {
+               return static_cast<ExplicitProducer*>(token.producer)->ConcurrentQueue::ExplicitProducer::template enqueue_bulk<canAlloc>(itemFirst, count);
+       }
+       
+       template<AllocationMode canAlloc, typename It>
+       inline bool inner_enqueue_bulk(It itemFirst, size_t count)
+       {
+               auto producer = get_or_add_implicit_producer();
+               return producer == nullptr ? false : producer->ConcurrentQueue::ImplicitProducer::template enqueue_bulk<canAlloc>(itemFirst, count);
+       }
+       
+       inline bool update_current_producer_after_rotation(consumer_token_t& token)
+       {
+               // Ah, there's been a rotation, figure out where we should be!
+               auto tail = producerListTail.load(std::memory_order_acquire);
+               if (token.desiredProducer == nullptr && tail == nullptr) {
+                       return false;
+               }
+               auto prodCount = producerCount.load(std::memory_order_relaxed);
+               auto globalOffset = globalExplicitConsumerOffset.load(std::memory_order_relaxed);
+               if (details::unlikely(token.desiredProducer == nullptr)) {
+                       // Aha, first time we're dequeueing anything.
+                       // Figure out our local position
+                       // Note: offset is from start, not end, but we're traversing from end -- subtract from count first
+                       std::uint32_t offset = prodCount - 1 - (token.initialOffset % prodCount);
+                       token.desiredProducer = tail;
+                       for (std::uint32_t i = 0; i != offset; ++i) {
+                               token.desiredProducer = static_cast<ProducerBase*>(token.desiredProducer)->next_prod();
+                               if (token.desiredProducer == nullptr) {
+                                       token.desiredProducer = tail;
+                               }
+                       }
+               }
+               
+               std::uint32_t delta = globalOffset - token.lastKnownGlobalOffset;
+               if (delta >= prodCount) {
+                       delta = delta % prodCount;
+               }
+               for (std::uint32_t i = 0; i != delta; ++i) {
+                       token.desiredProducer = static_cast<ProducerBase*>(token.desiredProducer)->next_prod();
+                       if (token.desiredProducer == nullptr) {
+                               token.desiredProducer = tail;
+                       }
+               }
+               
+               token.lastKnownGlobalOffset = globalOffset;
+               token.currentProducer = token.desiredProducer;
+               token.itemsConsumedFromCurrent = 0;
+               return true;
+       }
+       
+       
+       ///////////////////////////
+       // Free list
+       ///////////////////////////
+       
+       template <typename N>
+       struct FreeListNode
+       {
+               FreeListNode() : freeListRefs(0), freeListNext(nullptr) { }
+               
+               std::atomic<std::uint32_t> freeListRefs;
+               std::atomic<N*> freeListNext;
+       };
+       
+       // A simple CAS-based lock-free free list. Not the fastest thing in the world under heavy contention, but
+       // simple and correct (assuming nodes are never freed until after the free list is destroyed), and fairly
+       // speedy under low contention.
+       template<typename N>            // N must inherit FreeListNode or have the same fields (and initialization of them)
+       struct FreeList
+       {
+               FreeList() : freeListHead(nullptr) { }
+               FreeList(FreeList&& other) : freeListHead(other.freeListHead.load(std::memory_order_relaxed)) { other.freeListHead.store(nullptr, std::memory_order_relaxed); }
+               void swap(FreeList& other) { details::swap_relaxed(freeListHead, other.freeListHead); }
+               
+               FreeList(FreeList const&) MOODYCAMEL_DELETE_FUNCTION;
+               FreeList& operator=(FreeList const&) MOODYCAMEL_DELETE_FUNCTION;
+               
+               inline void add(N* node)
+               {
+#if MCDBGQ_NOLOCKFREE_FREELIST
+                       debug::DebugLock lock(mutex);
+#endif         
+                       // We know that the should-be-on-freelist bit is 0 at this point, so it's safe to
+                       // set it using a fetch_add
+                       if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST, std::memory_order_acq_rel) == 0) {
+                               // Oh look! We were the last ones referencing this node, and we know
+                               // we want to add it to the free list, so let's do it!
+                               add_knowing_refcount_is_zero(node);
+                       }
+               }
+               
+               inline N* try_get()
+               {
+#if MCDBGQ_NOLOCKFREE_FREELIST
+                       debug::DebugLock lock(mutex);
+#endif         
+                       auto head = freeListHead.load(std::memory_order_acquire);
+                       while (head != nullptr) {
+                               auto prevHead = head;
+                               auto refs = head->freeListRefs.load(std::memory_order_relaxed);
+                               if ((refs & REFS_MASK) == 0 || !head->freeListRefs.compare_exchange_strong(refs, refs + 1, std::memory_order_acquire, std::memory_order_relaxed)) {
+                                       head = freeListHead.load(std::memory_order_acquire);
+                                       continue;
+                               }
+                               
+                               // Good, reference count has been incremented (it wasn't at zero), which means we can read the
+                               // next and not worry about it changing between now and the time we do the CAS
+                               auto next = head->freeListNext.load(std::memory_order_relaxed);
+                               if (freeListHead.compare_exchange_strong(head, next, std::memory_order_acquire, std::memory_order_relaxed)) {
+                                       // Yay, got the node. This means it was on the list, which means shouldBeOnFreeList must be false no
+                                       // matter the refcount (because nobody else knows it's been taken off yet, it can't have been put back on).
+                                       assert((head->freeListRefs.load(std::memory_order_relaxed) & SHOULD_BE_ON_FREELIST) == 0);
+                                       
+                                       // Decrease refcount twice, once for our ref, and once for the list's ref
+                                       head->freeListRefs.fetch_add(-2, std::memory_order_release);
+                                       return head;
+                               }
+                               
+                               // OK, the head must have changed on us, but we still need to decrease the refcount we increased.
+                               // Note that we don't need to release any memory effects, but we do need to ensure that the reference
+                               // count decrement happens-after the CAS on the head.
+                               refs = prevHead->freeListRefs.fetch_add(-1, std::memory_order_acq_rel);
+                               if (refs == SHOULD_BE_ON_FREELIST + 1) {
+                                       add_knowing_refcount_is_zero(prevHead);
+                               }
+                       }
+                       
+                       return nullptr;
+               }
+               
+               // Useful for traversing the list when there's no contention (e.g. to destroy remaining nodes)
+               N* head_unsafe() const { return freeListHead.load(std::memory_order_relaxed); }
+               
+       private:
+               inline void add_knowing_refcount_is_zero(N* node)
+               {
+                       // Since the refcount is zero, and nobody can increase it once it's zero (except us, and we run
+                       // only one copy of this method per node at a time, i.e. the single thread case), then we know
+                       // we can safely change the next pointer of the node; however, once the refcount is back above
+                       // zero, then other threads could increase it (happens under heavy contention, when the refcount
+                       // goes to zero in between a load and a refcount increment of a node in try_get, then back up to
+                       // something non-zero, then the refcount increment is done by the other thread) -- so, if the CAS
+                       // to add the node to the actual list fails, decrease the refcount and leave the add operation to
+                       // the next thread who puts the refcount back at zero (which could be us, hence the loop).
+                       auto head = freeListHead.load(std::memory_order_relaxed);
+                       while (true) {
+                               node->freeListNext.store(head, std::memory_order_relaxed);
+                               node->freeListRefs.store(1, std::memory_order_release);
+                               if (!freeListHead.compare_exchange_strong(head, node, std::memory_order_release, std::memory_order_relaxed)) {
+                                       // Hmm, the add failed, but we can only try again when the refcount goes back to zero
+                                       if (node->freeListRefs.fetch_add(SHOULD_BE_ON_FREELIST - 1, std::memory_order_release) == 1) {
+                                               continue;
+                                       }
+                               }
+                               return;
+                       }
+               }
+               
+       private:
+               // Implemented like a stack, but where node order doesn't matter (nodes are inserted out of order under contention)
+               std::atomic<N*> freeListHead;
+       
+       static const std::uint32_t REFS_MASK = 0x7FFFFFFF;
+       static const std::uint32_t SHOULD_BE_ON_FREELIST = 0x80000000;
+               
+#if MCDBGQ_NOLOCKFREE_FREELIST
+               debug::DebugMutex mutex;
+#endif
+       };
+       
+       
+       ///////////////////////////
+       // Block
+       ///////////////////////////
+       
+       enum InnerQueueContext { implicit_context = 0, explicit_context = 1 };
+       
+       struct Block
+       {
+               Block()
+                       : next(nullptr), elementsCompletelyDequeued(0), freeListRefs(0), freeListNext(nullptr), shouldBeOnFreeList(false), dynamicallyAllocated(true)
+               {
+#if MCDBGQ_TRACKMEM
+                       owner = nullptr;
+#endif
+               }
+               
+               template<InnerQueueContext context>
+               inline bool is_empty() const
+               {
+                       if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
+                               // Check flags
+                               for (size_t i = 0; i < BLOCK_SIZE; ++i) {
+                                       if (!emptyFlags[i].load(std::memory_order_relaxed)) {
+                                               return false;
+                                       }
+                               }
+                               
+                               // Aha, empty; make sure we have all other memory effects that happened before the empty flags were set
+                               std::atomic_thread_fence(std::memory_order_acquire);
+                               return true;
+                       }
+                       else {
+                               // Check counter
+                               if (elementsCompletelyDequeued.load(std::memory_order_relaxed) == BLOCK_SIZE) {
+                                       std::atomic_thread_fence(std::memory_order_acquire);
+                                       return true;
+                               }
+                               assert(elementsCompletelyDequeued.load(std::memory_order_relaxed) <= BLOCK_SIZE);
+                               return false;
+                       }
+               }
+               
+               // Returns true if the block is now empty (does not apply in explicit context)
+               template<InnerQueueContext context>
+               inline bool set_empty(index_t i)
+               {
+                       if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
+                               // Set flag
+                               assert(!emptyFlags[BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1))].load(std::memory_order_relaxed));
+                               emptyFlags[BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1))].store(true, std::memory_order_release);
+                               return false;
+                       }
+                       else {
+                               // Increment counter
+                               auto prevVal = elementsCompletelyDequeued.fetch_add(1, std::memory_order_release);
+                               assert(prevVal < BLOCK_SIZE);
+                               return prevVal == BLOCK_SIZE - 1;
+                       }
+               }
+               
+               // Sets multiple contiguous item statuses to 'empty' (assumes no wrapping and count > 0).
+               // Returns true if the block is now empty (does not apply in explicit context).
+               template<InnerQueueContext context>
+               inline bool set_many_empty(index_t i, size_t count)
+               {
+                       if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
+                               // Set flags
+                               std::atomic_thread_fence(std::memory_order_release);
+                               i = BLOCK_SIZE - 1 - static_cast<size_t>(i & static_cast<index_t>(BLOCK_SIZE - 1)) - count + 1;
+                               for (size_t j = 0; j != count; ++j) {
+                                       assert(!emptyFlags[i + j].load(std::memory_order_relaxed));
+                                       emptyFlags[i + j].store(true, std::memory_order_relaxed);
+                               }
+                               return false;
+                       }
+                       else {
+                               // Increment counter
+                               auto prevVal = elementsCompletelyDequeued.fetch_add(count, std::memory_order_release);
+                               assert(prevVal + count <= BLOCK_SIZE);
+                               return prevVal + count == BLOCK_SIZE;
+                       }
+               }
+               
+               template<InnerQueueContext context>
+               inline void set_all_empty()
+               {
+                       if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
+                               // Set all flags
+                               for (size_t i = 0; i != BLOCK_SIZE; ++i) {
+                                       emptyFlags[i].store(true, std::memory_order_relaxed);
+                               }
+                       }
+                       else {
+                               // Reset counter
+                               elementsCompletelyDequeued.store(BLOCK_SIZE, std::memory_order_relaxed);
+                       }
+               }
+               
+               template<InnerQueueContext context>
+               inline void reset_empty()
+               {
+                       if (context == explicit_context && BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD) {
+                               // Reset flags
+                               for (size_t i = 0; i != BLOCK_SIZE; ++i) {
+                                       emptyFlags[i].store(false, std::memory_order_relaxed);
+                               }
+                       }
+                       else {
+                               // Reset counter
+                               elementsCompletelyDequeued.store(0, std::memory_order_relaxed);
+                       }
+               }
+               
+               inline T* operator[](index_t idx) MOODYCAMEL_NOEXCEPT { return static_cast<T*>(static_cast<void*>(elements)) + static_cast<size_t>(idx & static_cast<index_t>(BLOCK_SIZE - 1)); }
+               inline T const* operator[](index_t idx) const MOODYCAMEL_NOEXCEPT { return static_cast<T const*>(static_cast<void const*>(elements)) + static_cast<size_t>(idx & static_cast<index_t>(BLOCK_SIZE - 1)); }
+               
+       private:
+               // IMPORTANT: This must be the first member in Block, so that if T depends on the alignment of
+               // addresses returned by malloc, that alignment will be preserved. Apparently clang actually
+               // generates code that uses this assumption for AVX instructions in some cases. Ideally, we
+               // should also align Block to the alignment of T in case it's higher than malloc's 16-byte
+               // alignment, but this is hard to do in a cross-platform way. Assert for this case:
+               static_assert(std::alignment_of<T>::value <= std::alignment_of<details::max_align_t>::value, "The queue does not support super-aligned types at this time");
+               // Additionally, we need the alignment of Block itself to be a multiple of max_align_t since
+               // otherwise the appropriate padding will not be added at the end of Block in order to make
+               // arrays of Blocks all be properly aligned (not just the first one). We use a union to force
+               // this.
+               union {
+                       char elements[sizeof(T) * BLOCK_SIZE];
+                       details::max_align_t dummy;
+               };
+       public:
+               Block* next;
+               std::atomic<size_t> elementsCompletelyDequeued;
+               std::atomic<bool> emptyFlags[BLOCK_SIZE <= EXPLICIT_BLOCK_EMPTY_COUNTER_THRESHOLD ? BLOCK_SIZE : 1];
+       public:
+               std::atomic<std::uint32_t> freeListRefs;
+               std::atomic<Block*> freeListNext;
+               std::atomic<bool> shouldBeOnFreeList;
+               bool dynamicallyAllocated;              // Perhaps a better name for this would be 'isNotPartOfInitialBlockPool'
+               
+#if MCDBGQ_TRACKMEM
+               void* owner;
+#endif
+       };
+       static_assert(std::alignment_of<Block>::value >= std::alignment_of<details::max_align_t>::value, "Internal error: Blocks must be at least as aligned as the type they are wrapping");
+
+
+#if MCDBGQ_TRACKMEM
+public:
+       struct MemStats;
+private:
+#endif
+       
+       ///////////////////////////
+       // Producer base
+       ///////////////////////////
+       
+       struct ProducerBase : public details::ConcurrentQueueProducerTypelessBase
+       {
+               ProducerBase(ConcurrentQueue* parent, bool isExplicit) :
+                       tailIndex(0),
+                       headIndex(0),
+                       dequeueOptimisticCount(0),
+                       dequeueOvercommit(0),
+                       tailBlock(nullptr),
+                       isExplicit(isExplicit),
+                       parent(parent)
+               {
+               }
+               
+               virtual ~ProducerBase() { };
+               
+               template<typename U>
+               inline bool dequeue(U& element)
+               {
+                       if (isExplicit) {
+                               return static_cast<ExplicitProducer*>(this)->dequeue(element);
+                       }
+                       else {
+                               return static_cast<ImplicitProducer*>(this)->dequeue(element);
+                       }
+               }
+               
+               template<typename It>
+               inline size_t dequeue_bulk(It& itemFirst, size_t max)
+               {
+                       if (isExplicit) {
+                               return static_cast<ExplicitProducer*>(this)->dequeue_bulk(itemFirst, max);
+                       }
+                       else {
+                               return static_cast<ImplicitProducer*>(this)->dequeue_bulk(itemFirst, max);
+                       }
+               }
+               
+               inline ProducerBase* next_prod() const { return static_cast<ProducerBase*>(next); }
+               
+               inline size_t size_approx() const
+               {
+                       auto tail = tailIndex.load(std::memory_order_relaxed);
+                       auto head = headIndex.load(std::memory_order_relaxed);
+                       return details::circular_less_than(head, tail) ? static_cast<size_t>(tail - head) : 0;
+               }
+               
+               inline index_t getTail() const { return tailIndex.load(std::memory_order_relaxed); }
+       protected:
+               std::atomic<index_t> tailIndex;         // Where to enqueue to next
+               std::atomic<index_t> headIndex;         // Where to dequeue from next
+               
+               std::atomic<index_t> dequeueOptimisticCount;
+               std::atomic<index_t> dequeueOvercommit;
+               
+               Block* tailBlock;
+               
+       public:
+               bool isExplicit;
+               ConcurrentQueue* parent;
+               
+       protected:
+#if MCDBGQ_TRACKMEM
+               friend struct MemStats;
+#endif
+       };
+       
+       
+       ///////////////////////////
+       // Explicit queue
+       ///////////////////////////
+               
+       struct ExplicitProducer : public ProducerBase
+       {
+               explicit ExplicitProducer(ConcurrentQueue* parent) :
+                       ProducerBase(parent, true),
+                       blockIndex(nullptr),
+                       pr_blockIndexSlotsUsed(0),
+                       pr_blockIndexSize(EXPLICIT_INITIAL_INDEX_SIZE >> 1),
+                       pr_blockIndexFront(0),
+                       pr_blockIndexEntries(nullptr),
+                       pr_blockIndexRaw(nullptr)
+               {
+                       size_t poolBasedIndexSize = details::ceil_to_pow_2(parent->initialBlockPoolSize) >> 1;
+                       if (poolBasedIndexSize > pr_blockIndexSize) {
+                               pr_blockIndexSize = poolBasedIndexSize;
+                       }
+                       
+                       new_block_index(0);             // This creates an index with double the number of current entries, i.e. EXPLICIT_INITIAL_INDEX_SIZE
+               }
+               
+               ~ExplicitProducer()
+               {
+                       // Destruct any elements not yet dequeued.
+                       // Since we're in the destructor, we can assume all elements
+                       // are either completely dequeued or completely not (no halfways).
+                       if (this->tailBlock != nullptr) {               // Note this means there must be a block index too
+                               // First find the block that's partially dequeued, if any
+                               Block* halfDequeuedBlock = nullptr;
+                               if ((this->headIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1)) != 0) {
+                                       // The head's not on a block boundary, meaning a block somewhere is partially dequeued
+                                       // (or the head block is the tail block and was fully dequeued, but the head/tail are still not on a boundary)
+                                       size_t i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & (pr_blockIndexSize - 1);
+                                       while (details::circular_less_than<index_t>(pr_blockIndexEntries[i].base + BLOCK_SIZE, this->headIndex.load(std::memory_order_relaxed))) {
+                                               i = (i + 1) & (pr_blockIndexSize - 1);
+                                       }
+                                       assert(details::circular_less_than<index_t>(pr_blockIndexEntries[i].base, this->headIndex.load(std::memory_order_relaxed)));
+                                       halfDequeuedBlock = pr_blockIndexEntries[i].block;
+                               }
+                               
+                               // Start at the head block (note the first line in the loop gives us the head from the tail on the first iteration)
+                               auto block = this->tailBlock;
+                               do {
+                                       block = block->next;
+                                       if (block->ConcurrentQueue::Block::template is_empty<explicit_context>()) {
+                                               continue;
+                                       }
+                                       
+                                       size_t i = 0;   // Offset into block
+                                       if (block == halfDequeuedBlock) {
+                                               i = static_cast<size_t>(this->headIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1));
+                                       }
+                                       
+                                       // Walk through all the items in the block; if this is the tail block, we need to stop when we reach the tail index
+                                       auto lastValidIndex = (this->tailIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1)) == 0 ? BLOCK_SIZE : static_cast<size_t>(this->tailIndex.load(std::memory_order_relaxed) & static_cast<index_t>(BLOCK_SIZE - 1));
+                                       while (i != BLOCK_SIZE && (block != this->tailBlock || i != lastValidIndex)) {
+                                               (*block)[i++]->~T();
+                                       }
+                               } while (block != this->tailBlock);
+                       }
+                       
+                       // Destroy all blocks that we own
+                       if (this->tailBlock != nullptr) {
+                               auto block = this->tailBlock;
+                               do {
+                                       auto nextBlock = block->next;
+                                       if (block->dynamicallyAllocated) {
+                                               destroy(block);
+                                       }
+                                       else {
+                                               this->parent->add_block_to_free_list(block);
+                                       }
+                                       block = nextBlock;
+                               } while (block != this->tailBlock);
+                       }
+                       
+                       // Destroy the block indices
+                       auto header = static_cast<BlockIndexHeader*>(pr_blockIndexRaw);
+                       while (header != nullptr) {
+                               auto prev = static_cast<BlockIndexHeader*>(header->prev);
+                               header->~BlockIndexHeader();
+                               (Traits::free)(header);
+                               header = prev;
+                       }
+               }
+               
+               template<AllocationMode allocMode, typename U>
+               inline bool enqueue(U&& element)
+               {
+                       index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed);
+                       index_t newTailIndex = 1 + currentTailIndex;
+                       if ((currentTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0) {
+                               // We reached the end of a block, start a new one
+                               auto startBlock = this->tailBlock;
+                               auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed;
+                               if (this->tailBlock != nullptr && this->tailBlock->next->ConcurrentQueue::Block::template is_empty<explicit_context>()) {
+                                       // We can re-use the block ahead of us, it's empty!                                     
+                                       this->tailBlock = this->tailBlock->next;
+                                       this->tailBlock->ConcurrentQueue::Block::template reset_empty<explicit_context>();
+                                       
+                                       // We'll put the block on the block index (guaranteed to be room since we're conceptually removing the
+                                       // last block from it first -- except instead of removing then adding, we can just overwrite).
+                                       // Note that there must be a valid block index here, since even if allocation failed in the ctor,
+                                       // it would have been re-attempted when adding the first block to the queue; since there is such
+                                       // a block, a block index must have been successfully allocated.
+                               }
+                               else {
+                                       // Whatever head value we see here is >= the last value we saw here (relatively),
+                                       // and <= its current value. Since we have the most recent tail, the head must be
+                                       // <= to it.
+                                       auto head = this->headIndex.load(std::memory_order_relaxed);
+                                       assert(!details::circular_less_than<index_t>(currentTailIndex, head));
+                                       if (!details::circular_less_than<index_t>(head, currentTailIndex + BLOCK_SIZE)
+                                               || (MAX_SUBQUEUE_SIZE != details::const_numeric_max<size_t>::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) {
+                                               // We can't enqueue in another block because there's not enough leeway -- the
+                                               // tail could surpass the head by the time the block fills up! (Or we'll exceed
+                                               // the size limit, if the second part of the condition was true.)
+                                               return false;
+                                       }
+                                       // We're going to need a new block; check that the block index has room
+                                       if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize) {
+                                               // Hmm, the circular block index is already full -- we'll need
+                                               // to allocate a new index. Note pr_blockIndexRaw can only be nullptr if
+                                               // the initial allocation failed in the constructor.
+                                               
+                                               if (allocMode == CannotAlloc || !new_block_index(pr_blockIndexSlotsUsed)) {
+                                                       return false;
+                                               }
+                                       }
+                                       
+                                       // Insert a new block in the circular linked list
+                                       auto newBlock = this->parent->ConcurrentQueue::template requisition_block<allocMode>();
+                                       if (newBlock == nullptr) {
+                                               return false;
+                                       }
+#if MCDBGQ_TRACKMEM
+                                       newBlock->owner = this;
+#endif
+                                       newBlock->ConcurrentQueue::Block::template reset_empty<explicit_context>();
+                                       if (this->tailBlock == nullptr) {
+                                               newBlock->next = newBlock;
+                                       }
+                                       else {
+                                               newBlock->next = this->tailBlock->next;
+                                               this->tailBlock->next = newBlock;
+                                       }
+                                       this->tailBlock = newBlock;
+                                       ++pr_blockIndexSlotsUsed;
+                               }
+                               
+                               if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
+                                       // The constructor may throw. We want the element not to appear in the queue in
+                                       // that case (without corrupting the queue):
+                                       MOODYCAMEL_TRY {
+                                               new ((*this->tailBlock)[currentTailIndex]) T(std::forward<U>(element));
+                                       }
+                                       MOODYCAMEL_CATCH (...) {
+                                               // Revert change to the current block, but leave the new block available
+                                               // for next time
+                                               pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed;
+                                               this->tailBlock = startBlock == nullptr ? this->tailBlock : startBlock;
+                                               MOODYCAMEL_RETHROW;
+                                       }
+                               }
+                               else {
+                                       (void)startBlock;
+                                       (void)originalBlockIndexSlotsUsed;
+                               }
+                               
+                               // Add block to block index
+                               auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront];
+                               entry.base = currentTailIndex;
+                               entry.block = this->tailBlock;
+                               blockIndex.load(std::memory_order_relaxed)->front.store(pr_blockIndexFront, std::memory_order_release);
+                               pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1);
+                               
+                               if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
+                                       this->tailIndex.store(newTailIndex, std::memory_order_release);
+                                       return true;
+                               }
+                       }
+                       
+                       // Enqueue
+                       new ((*this->tailBlock)[currentTailIndex]) T(std::forward<U>(element));
+                       
+                       this->tailIndex.store(newTailIndex, std::memory_order_release);
+                       return true;
+               }
+               
+               template<typename U>
+               bool dequeue(U& element)
+               {
+                       auto tail = this->tailIndex.load(std::memory_order_relaxed);
+                       auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed);
+                       if (details::circular_less_than<index_t>(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) {
+                               // Might be something to dequeue, let's give it a try
+                               
+                               // Note that this if is purely for performance purposes in the common case when the queue is
+                               // empty and the values are eventually consistent -- we may enter here spuriously.
+                               
+                               // Note that whatever the values of overcommit and tail are, they are not going to change (unless we
+                               // change them) and must be the same value at this point (inside the if) as when the if condition was
+                               // evaluated.
+
+                               // We insert an acquire fence here to synchronize-with the release upon incrementing dequeueOvercommit below.
+                               // This ensures that whatever the value we got loaded into overcommit, the load of dequeueOptisticCount in
+                               // the fetch_add below will result in a value at least as recent as that (and therefore at least as large).
+                               // Note that I believe a compiler (signal) fence here would be sufficient due to the nature of fetch_add (all
+                               // read-modify-write operations are guaranteed to work on the latest value in the modification order), but
+                               // unfortunately that can't be shown to be correct using only the C++11 standard.
+                               // See http://stackoverflow.com/questions/18223161/what-are-the-c11-memory-ordering-guarantees-in-this-corner-case
+                               std::atomic_thread_fence(std::memory_order_acquire);
+                               
+                               // Increment optimistic counter, then check if it went over the boundary
+                               auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed);
+                               
+                               // Note that since dequeueOvercommit must be <= dequeueOptimisticCount (because dequeueOvercommit is only ever
+                               // incremented after dequeueOptimisticCount -- this is enforced in the `else` block below), and since we now
+                               // have a version of dequeueOptimisticCount that is at least as recent as overcommit (due to the release upon
+                               // incrementing dequeueOvercommit and the acquire above that synchronizes with it), overcommit <= myDequeueCount.
+                               assert(overcommit <= myDequeueCount);
+                               
+                               // Note that we reload tail here in case it changed; it will be the same value as before or greater, since
+                               // this load is sequenced after (happens after) the earlier load above. This is supported by read-read
+                               // coherency (as defined in the standard), explained here: http://en.cppreference.com/w/cpp/atomic/memory_order
+                               tail = this->tailIndex.load(std::memory_order_acquire);
+                               if (details::likely(details::circular_less_than<index_t>(myDequeueCount - overcommit, tail))) {
+                                       // Guaranteed to be at least one element to dequeue!
+                                       
+                                       // Get the index. Note that since there's guaranteed to be at least one element, this
+                                       // will never exceed tail. We need to do an acquire-release fence here since it's possible
+                                       // that whatever condition got us to this point was for an earlier enqueued element (that
+                                       // we already see the memory effects for), but that by the time we increment somebody else
+                                       // has incremented it, and we need to see the memory effects for *that* element, which is
+                                       // in such a case is necessarily visible on the thread that incremented it in the first
+                                       // place with the more current condition (they must have acquired a tail that is at least
+                                       // as recent).
+                                       auto index = this->headIndex.fetch_add(1, std::memory_order_acq_rel);
+                                       
+                                       
+                                       // Determine which block the element is in
+                                       
+                                       auto localBlockIndex = blockIndex.load(std::memory_order_acquire);
+                                       auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire);
+                                       
+                                       // We need to be careful here about subtracting and dividing because of index wrap-around.
+                                       // When an index wraps, we need to preserve the sign of the offset when dividing it by the
+                                       // block size (in order to get a correct signed block count offset in all cases):
+                                       auto headBase = localBlockIndex->entries[localBlockIndexHead].base;
+                                       auto blockBaseIndex = index & ~static_cast<index_t>(BLOCK_SIZE - 1);
+                                       auto offset = static_cast<size_t>(static_cast<typename std::make_signed<index_t>::type>(blockBaseIndex - headBase) / BLOCK_SIZE);
+                                       auto block = localBlockIndex->entries[(localBlockIndexHead + offset) & (localBlockIndex->size - 1)].block;
+                                       
+                                       // Dequeue
+                                       auto& el = *((*block)[index]);
+                                       if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) {
+                                               // Make sure the element is still fully dequeued and destroyed even if the assignment
+                                               // throws
+                                               struct Guard {
+                                                       Block* block;
+                                                       index_t index;
+                                                       
+                                                       ~Guard()
+                                                       {
+                                                               (*block)[index]->~T();
+                                                               block->ConcurrentQueue::Block::template set_empty<explicit_context>(index);
+                                                       }
+                                               } guard = { block, index };
+                                               
+                                               element = std::move(el);
+                                       }
+                                       else {
+                                               element = std::move(el);
+                                               el.~T();
+                                               block->ConcurrentQueue::Block::template set_empty<explicit_context>(index);
+                                       }
+                                       
+                                       return true;
+                               }
+                               else {
+                                       // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent
+                                       this->dequeueOvercommit.fetch_add(1, std::memory_order_release);                // Release so that the fetch_add on dequeueOptimisticCount is guaranteed to happen before this write
+                               }
+                       }
+               
+                       return false;
+               }
+               
+               template<AllocationMode allocMode, typename It>
+               bool enqueue_bulk(It itemFirst, size_t count)
+               {
+                       // First, we need to make sure we have enough room to enqueue all of the elements;
+                       // this means pre-allocating blocks and putting them in the block index (but only if
+                       // all the allocations succeeded).
+                       index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed);
+                       auto startBlock = this->tailBlock;
+                       auto originalBlockIndexFront = pr_blockIndexFront;
+                       auto originalBlockIndexSlotsUsed = pr_blockIndexSlotsUsed;
+                       
+                       Block* firstAllocatedBlock = nullptr;
+                       
+                       // Figure out how many blocks we'll need to allocate, and do so
+                       size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1));
+                       index_t currentTailIndex = (startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1);
+                       if (blockBaseDiff > 0) {
+                               // Allocate as many blocks as possible from ahead
+                               while (blockBaseDiff > 0 && this->tailBlock != nullptr && this->tailBlock->next != firstAllocatedBlock && this->tailBlock->next->ConcurrentQueue::Block::template is_empty<explicit_context>()) {
+                                       blockBaseDiff -= static_cast<index_t>(BLOCK_SIZE);
+                                       currentTailIndex += static_cast<index_t>(BLOCK_SIZE);
+                                       
+                                       this->tailBlock = this->tailBlock->next;
+                                       firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock;
+                                       
+                                       auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront];
+                                       entry.base = currentTailIndex;
+                                       entry.block = this->tailBlock;
+                                       pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1);
+                               }
+                               
+                               // Now allocate as many blocks as necessary from the block pool
+                               while (blockBaseDiff > 0) {
+                                       blockBaseDiff -= static_cast<index_t>(BLOCK_SIZE);
+                                       currentTailIndex += static_cast<index_t>(BLOCK_SIZE);
+                                       
+                                       auto head = this->headIndex.load(std::memory_order_relaxed);
+                                       assert(!details::circular_less_than<index_t>(currentTailIndex, head));
+                                       bool full = !details::circular_less_than<index_t>(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max<size_t>::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head));
+                                       if (pr_blockIndexRaw == nullptr || pr_blockIndexSlotsUsed == pr_blockIndexSize || full) {
+                                               if (allocMode == CannotAlloc || full || !new_block_index(originalBlockIndexSlotsUsed)) {
+                                                       // Failed to allocate, undo changes (but keep injected blocks)
+                                                       pr_blockIndexFront = originalBlockIndexFront;
+                                                       pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed;
+                                                       this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock;
+                                                       return false;
+                                               }
+                                               
+                                               // pr_blockIndexFront is updated inside new_block_index, so we need to
+                                               // update our fallback value too (since we keep the new index even if we
+                                               // later fail)
+                                               originalBlockIndexFront = originalBlockIndexSlotsUsed;
+                                       }
+                                       
+                                       // Insert a new block in the circular linked list
+                                       auto newBlock = this->parent->ConcurrentQueue::template requisition_block<allocMode>();
+                                       if (newBlock == nullptr) {
+                                               pr_blockIndexFront = originalBlockIndexFront;
+                                               pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed;
+                                               this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock;
+                                               return false;
+                                       }
+                                       
+#if MCDBGQ_TRACKMEM
+                                       newBlock->owner = this;
+#endif
+                                       newBlock->ConcurrentQueue::Block::template set_all_empty<explicit_context>();
+                                       if (this->tailBlock == nullptr) {
+                                               newBlock->next = newBlock;
+                                       }
+                                       else {
+                                               newBlock->next = this->tailBlock->next;
+                                               this->tailBlock->next = newBlock;
+                                       }
+                                       this->tailBlock = newBlock;
+                                       firstAllocatedBlock = firstAllocatedBlock == nullptr ? this->tailBlock : firstAllocatedBlock;
+                                       
+                                       ++pr_blockIndexSlotsUsed;
+                                       
+                                       auto& entry = blockIndex.load(std::memory_order_relaxed)->entries[pr_blockIndexFront];
+                                       entry.base = currentTailIndex;
+                                       entry.block = this->tailBlock;
+                                       pr_blockIndexFront = (pr_blockIndexFront + 1) & (pr_blockIndexSize - 1);
+                               }
+                               
+                               // Excellent, all allocations succeeded. Reset each block's emptiness before we fill them up, and
+                               // publish the new block index front
+                               auto block = firstAllocatedBlock;
+                               while (true) {
+                                       block->ConcurrentQueue::Block::template reset_empty<explicit_context>();
+                                       if (block == this->tailBlock) {
+                                               break;
+                                       }
+                                       block = block->next;
+                               }
+                               
+                               if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) {
+                                       blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release);
+                               }
+                       }
+                       
+                       // Enqueue, one block at a time
+                       index_t newTailIndex = startTailIndex + static_cast<index_t>(count);
+                       currentTailIndex = startTailIndex;
+                       auto endBlock = this->tailBlock;
+                       this->tailBlock = startBlock;
+                       assert((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0);
+                       if ((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) {
+                               this->tailBlock = firstAllocatedBlock;
+                       }
+                       while (true) {
+                               auto stopIndex = (currentTailIndex & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                               if (details::circular_less_than<index_t>(newTailIndex, stopIndex)) {
+                                       stopIndex = newTailIndex;
+                               }
+                               if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) {
+                                       while (currentTailIndex != stopIndex) {
+                                               new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++);
+                                       }
+                               }
+                               else {
+                                       MOODYCAMEL_TRY {
+                                               while (currentTailIndex != stopIndex) {
+                                                       // Must use copy constructor even if move constructor is available
+                                                       // because we may have to revert if there's an exception.
+                                                       // Sorry about the horrible templated next line, but it was the only way
+                                                       // to disable moving *at compile time*, which is important because a type
+                                                       // may only define a (noexcept) move constructor, and so calls to the
+                                                       // cctor will not compile, even if they are in an if branch that will never
+                                                       // be executed
+                                                       new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst));
+                                                       ++currentTailIndex;
+                                                       ++itemFirst;
+                                               }
+                                       }
+                                       MOODYCAMEL_CATCH (...) {
+                                               // Oh dear, an exception's been thrown -- destroy the elements that
+                                               // were enqueued so far and revert the entire bulk operation (we'll keep
+                                               // any allocated blocks in our linked list for later, though).
+                                               auto constructedStopIndex = currentTailIndex;
+                                               auto lastBlockEnqueued = this->tailBlock;
+                                               
+                                               pr_blockIndexFront = originalBlockIndexFront;
+                                               pr_blockIndexSlotsUsed = originalBlockIndexSlotsUsed;
+                                               this->tailBlock = startBlock == nullptr ? firstAllocatedBlock : startBlock;
+                                               
+                                               if (!details::is_trivially_destructible<T>::value) {
+                                                       auto block = startBlock;
+                                                       if ((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0) {
+                                                               block = firstAllocatedBlock;
+                                                       }
+                                                       currentTailIndex = startTailIndex;
+                                                       while (true) {
+                                                               auto stopIndex = (currentTailIndex & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                                                               if (details::circular_less_than<index_t>(constructedStopIndex, stopIndex)) {
+                                                                       stopIndex = constructedStopIndex;
+                                                               }
+                                                               while (currentTailIndex != stopIndex) {
+                                                                       (*block)[currentTailIndex++]->~T();
+                                                               }
+                                                               if (block == lastBlockEnqueued) {
+                                                                       break;
+                                                               }
+                                                               block = block->next;
+                                                       }
+                                               }
+                                               MOODYCAMEL_RETHROW;
+                                       }
+                               }
+                               
+                               if (this->tailBlock == endBlock) {
+                                       assert(currentTailIndex == newTailIndex);
+                                       break;
+                               }
+                               this->tailBlock = this->tailBlock->next;
+                       }
+                       
+                       if (!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst))) && firstAllocatedBlock != nullptr) {
+                               blockIndex.load(std::memory_order_relaxed)->front.store((pr_blockIndexFront - 1) & (pr_blockIndexSize - 1), std::memory_order_release);
+                       }
+                       
+                       this->tailIndex.store(newTailIndex, std::memory_order_release);
+                       return true;
+               }
+               
+               template<typename It>
+               size_t dequeue_bulk(It& itemFirst, size_t max)
+               {
+                       auto tail = this->tailIndex.load(std::memory_order_relaxed);
+                       auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed);
+                       auto desiredCount = static_cast<size_t>(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit));
+                       if (details::circular_less_than<size_t>(0, desiredCount)) {
+                               desiredCount = desiredCount < max ? desiredCount : max;
+                               std::atomic_thread_fence(std::memory_order_acquire);
+                               
+                               auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed);
+                               assert(overcommit <= myDequeueCount);
+                               
+                               tail = this->tailIndex.load(std::memory_order_acquire);
+                               auto actualCount = static_cast<size_t>(tail - (myDequeueCount - overcommit));
+                               if (details::circular_less_than<size_t>(0, actualCount)) {
+                                       actualCount = desiredCount < actualCount ? desiredCount : actualCount;
+                                       if (actualCount < desiredCount) {
+                                               this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release);
+                                       }
+                                       
+                                       // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this
+                                       // will never exceed tail.
+                                       auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel);
+                                       
+                                       // Determine which block the first element is in
+                                       auto localBlockIndex = blockIndex.load(std::memory_order_acquire);
+                                       auto localBlockIndexHead = localBlockIndex->front.load(std::memory_order_acquire);
+                                       
+                                       auto headBase = localBlockIndex->entries[localBlockIndexHead].base;
+                                       auto firstBlockBaseIndex = firstIndex & ~static_cast<index_t>(BLOCK_SIZE - 1);
+                                       auto offset = static_cast<size_t>(static_cast<typename std::make_signed<index_t>::type>(firstBlockBaseIndex - headBase) / BLOCK_SIZE);
+                                       auto indexIndex = (localBlockIndexHead + offset) & (localBlockIndex->size - 1);
+                                       
+                                       // Iterate the blocks and dequeue
+                                       auto index = firstIndex;
+                                       do {
+                                               auto firstIndexInBlock = index;
+                                               auto endIndex = (index & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                                               endIndex = details::circular_less_than<index_t>(firstIndex + static_cast<index_t>(actualCount), endIndex) ? firstIndex + static_cast<index_t>(actualCount) : endIndex;
+                                               auto block = localBlockIndex->entries[indexIndex].block;
+                                               if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) {
+                                                       while (index != endIndex) {
+                                                               auto& el = *((*block)[index]);
+                                                               *itemFirst++ = std::move(el);
+                                                               el.~T();
+                                                               ++index;
+                                                       }
+                                               }
+                                               else {
+                                                       MOODYCAMEL_TRY {
+                                                               while (index != endIndex) {
+                                                                       auto& el = *((*block)[index]);
+                                                                       *itemFirst = std::move(el);
+                                                                       ++itemFirst;
+                                                                       el.~T();
+                                                                       ++index;
+                                                               }
+                                                       }
+                                                       MOODYCAMEL_CATCH (...) {
+                                                               // It's too late to revert the dequeue, but we can make sure that all
+                                                               // the dequeued objects are properly destroyed and the block index
+                                                               // (and empty count) are properly updated before we propagate the exception
+                                                               do {
+                                                                       block = localBlockIndex->entries[indexIndex].block;
+                                                                       while (index != endIndex) {
+                                                                               (*block)[index++]->~T();
+                                                                       }
+                                                                       block->ConcurrentQueue::Block::template set_many_empty<explicit_context>(firstIndexInBlock, static_cast<size_t>(endIndex - firstIndexInBlock));
+                                                                       indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1);
+                                                                       
+                                                                       firstIndexInBlock = index;
+                                                                       endIndex = (index & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                                                                       endIndex = details::circular_less_than<index_t>(firstIndex + static_cast<index_t>(actualCount), endIndex) ? firstIndex + static_cast<index_t>(actualCount) : endIndex;
+                                                               } while (index != firstIndex + actualCount);
+                                                               
+                                                               MOODYCAMEL_RETHROW;
+                                                       }
+                                               }
+                                               block->ConcurrentQueue::Block::template set_many_empty<explicit_context>(firstIndexInBlock, static_cast<size_t>(endIndex - firstIndexInBlock));
+                                               indexIndex = (indexIndex + 1) & (localBlockIndex->size - 1);
+                                       } while (index != firstIndex + actualCount);
+                                       
+                                       return actualCount;
+                               }
+                               else {
+                                       // Wasn't anything to dequeue after all; make the effective dequeue count eventually consistent
+                                       this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release);
+                               }
+                       }
+                       
+                       return 0;
+               }
+               
+       private:
+               struct BlockIndexEntry
+               {
+                       index_t base;
+                       Block* block;
+               };
+               
+               struct BlockIndexHeader
+               {
+                       size_t size;
+                       std::atomic<size_t> front;              // Current slot (not next, like pr_blockIndexFront)
+                       BlockIndexEntry* entries;
+                       void* prev;
+               };
+               
+               
+               bool new_block_index(size_t numberOfFilledSlotsToExpose)
+               {
+                       auto prevBlockSizeMask = pr_blockIndexSize - 1;
+                       
+                       // Create the new block
+                       pr_blockIndexSize <<= 1;
+                       auto newRawPtr = static_cast<char*>((Traits::malloc)(sizeof(BlockIndexHeader) + std::alignment_of<BlockIndexEntry>::value - 1 + sizeof(BlockIndexEntry) * pr_blockIndexSize));
+                       if (newRawPtr == nullptr) {
+                               pr_blockIndexSize >>= 1;                // Reset to allow graceful retry
+                               return false;
+                       }
+                       
+                       auto newBlockIndexEntries = reinterpret_cast<BlockIndexEntry*>(details::align_for<BlockIndexEntry>(newRawPtr + sizeof(BlockIndexHeader)));
+                       
+                       // Copy in all the old indices, if any
+                       size_t j = 0;
+                       if (pr_blockIndexSlotsUsed != 0) {
+                               auto i = (pr_blockIndexFront - pr_blockIndexSlotsUsed) & prevBlockSizeMask;
+                               do {
+                                       newBlockIndexEntries[j++] = pr_blockIndexEntries[i];
+                                       i = (i + 1) & prevBlockSizeMask;
+                               } while (i != pr_blockIndexFront);
+                       }
+                       
+                       // Update everything
+                       auto header = new (newRawPtr) BlockIndexHeader;
+                       header->size = pr_blockIndexSize;
+                       header->front.store(numberOfFilledSlotsToExpose - 1, std::memory_order_relaxed);
+                       header->entries = newBlockIndexEntries;
+                       header->prev = pr_blockIndexRaw;                // we link the new block to the old one so we can free it later
+                       
+                       pr_blockIndexFront = j;
+                       pr_blockIndexEntries = newBlockIndexEntries;
+                       pr_blockIndexRaw = newRawPtr;
+                       blockIndex.store(header, std::memory_order_release);
+                       
+                       return true;
+               }
+               
+       private:
+               std::atomic<BlockIndexHeader*> blockIndex;
+               
+               // To be used by producer only -- consumer must use the ones in referenced by blockIndex
+               size_t pr_blockIndexSlotsUsed;
+               size_t pr_blockIndexSize;
+               size_t pr_blockIndexFront;              // Next slot (not current)
+               BlockIndexEntry* pr_blockIndexEntries;
+               void* pr_blockIndexRaw;
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+       public:
+               ExplicitProducer* nextExplicitProducer;
+       private:
+#endif
+               
+#if MCDBGQ_TRACKMEM
+               friend struct MemStats;
+#endif
+       };
+       
+       
+       //////////////////////////////////
+       // Implicit queue
+       //////////////////////////////////
+       
+       struct ImplicitProducer : public ProducerBase
+       {                       
+               ImplicitProducer(ConcurrentQueue* parent) :
+                       ProducerBase(parent, false),
+                       nextBlockIndexCapacity(IMPLICIT_INITIAL_INDEX_SIZE),
+                       blockIndex(nullptr)
+               {
+                       new_block_index();
+               }
+               
+               ~ImplicitProducer()
+               {
+                       // Note that since we're in the destructor we can assume that all enqueue/dequeue operations
+                       // completed already; this means that all undequeued elements are placed contiguously across
+                       // contiguous blocks, and that only the first and last remaining blocks can be only partially
+                       // empty (all other remaining blocks must be completely full).
+                       
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+                       // Unregister ourselves for thread termination notification
+                       if (!this->inactive.load(std::memory_order_relaxed)) {
+                               details::ThreadExitNotifier::unsubscribe(&threadExitListener);
+                       }
+#endif
+                       
+                       // Destroy all remaining elements!
+                       auto tail = this->tailIndex.load(std::memory_order_relaxed);
+                       auto index = this->headIndex.load(std::memory_order_relaxed);
+                       Block* block = nullptr;
+                       assert(index == tail || details::circular_less_than(index, tail));
+                       bool forceFreeLastBlock = index != tail;                // If we enter the loop, then the last (tail) block will not be freed
+                       while (index != tail) {
+                               if ((index & static_cast<index_t>(BLOCK_SIZE - 1)) == 0 || block == nullptr) {
+                                       if (block != nullptr) {
+                                               // Free the old block
+                                               this->parent->add_block_to_free_list(block);
+                                       }
+                                       
+                                       block = get_block_index_entry_for_index(index)->value.load(std::memory_order_relaxed);
+                               }
+                               
+                               ((*block)[index])->~T();
+                               ++index;
+                       }
+                       // Even if the queue is empty, there's still one block that's not on the free list
+                       // (unless the head index reached the end of it, in which case the tail will be poised
+                       // to create a new block).
+                       if (this->tailBlock != nullptr && (forceFreeLastBlock || (tail & static_cast<index_t>(BLOCK_SIZE - 1)) != 0)) {
+                               this->parent->add_block_to_free_list(this->tailBlock);
+                       }
+                       
+                       // Destroy block index
+                       auto localBlockIndex = blockIndex.load(std::memory_order_relaxed);
+                       if (localBlockIndex != nullptr) {
+                               for (size_t i = 0; i != localBlockIndex->capacity; ++i) {
+                                       localBlockIndex->index[i]->~BlockIndexEntry();
+                               }
+                               do {
+                                       auto prev = localBlockIndex->prev;
+                                       localBlockIndex->~BlockIndexHeader();
+                                       (Traits::free)(localBlockIndex);
+                                       localBlockIndex = prev;
+                               } while (localBlockIndex != nullptr);
+                       }
+               }
+               
+               template<AllocationMode allocMode, typename U>
+               inline bool enqueue(U&& element)
+               {
+                       index_t currentTailIndex = this->tailIndex.load(std::memory_order_relaxed);
+                       index_t newTailIndex = 1 + currentTailIndex;
+                       if ((currentTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0) {
+                               // We reached the end of a block, start a new one
+                               auto head = this->headIndex.load(std::memory_order_relaxed);
+                               assert(!details::circular_less_than<index_t>(currentTailIndex, head));
+                               if (!details::circular_less_than<index_t>(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max<size_t>::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head))) {
+                                       return false;
+                               }
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                               debug::DebugLock lock(mutex);
+#endif
+                               // Find out where we'll be inserting this block in the block index
+                               BlockIndexEntry* idxEntry;
+                               if (!insert_block_index_entry<allocMode>(idxEntry, currentTailIndex)) {
+                                       return false;
+                               }
+                               
+                               // Get ahold of a new block
+                               auto newBlock = this->parent->ConcurrentQueue::template requisition_block<allocMode>();
+                               if (newBlock == nullptr) {
+                                       rewind_block_index_tail();
+                                       idxEntry->value.store(nullptr, std::memory_order_relaxed);
+                                       return false;
+                               }
+#if MCDBGQ_TRACKMEM
+                               newBlock->owner = this;
+#endif
+                               newBlock->ConcurrentQueue::Block::template reset_empty<implicit_context>();
+                               
+                               if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
+                                       // May throw, try to insert now before we publish the fact that we have this new block
+                                       MOODYCAMEL_TRY {
+                                               new ((*newBlock)[currentTailIndex]) T(std::forward<U>(element));
+                                       }
+                                       MOODYCAMEL_CATCH (...) {
+                                               rewind_block_index_tail();
+                                               idxEntry->value.store(nullptr, std::memory_order_relaxed);
+                                               this->parent->add_block_to_free_list(newBlock);
+                                               MOODYCAMEL_RETHROW;
+                                       }
+                               }
+                               
+                               // Insert the new block into the index
+                               idxEntry->value.store(newBlock, std::memory_order_relaxed);
+                               
+                               this->tailBlock = newBlock;
+                               
+                               if (!MOODYCAMEL_NOEXCEPT_CTOR(T, U, new (nullptr) T(std::forward<U>(element)))) {
+                                       this->tailIndex.store(newTailIndex, std::memory_order_release);
+                                       return true;
+                               }
+                       }
+                       
+                       // Enqueue
+                       new ((*this->tailBlock)[currentTailIndex]) T(std::forward<U>(element));
+                       
+                       this->tailIndex.store(newTailIndex, std::memory_order_release);
+                       return true;
+               }
+               
+               template<typename U>
+               bool dequeue(U& element)
+               {
+                       // See ExplicitProducer::dequeue for rationale and explanation
+                       index_t tail = this->tailIndex.load(std::memory_order_relaxed);
+                       index_t overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed);
+                       if (details::circular_less_than<index_t>(this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit, tail)) {
+                               std::atomic_thread_fence(std::memory_order_acquire);
+                               
+                               index_t myDequeueCount = this->dequeueOptimisticCount.fetch_add(1, std::memory_order_relaxed);
+                               assert(overcommit <= myDequeueCount);
+                               tail = this->tailIndex.load(std::memory_order_acquire);
+                               if (details::likely(details::circular_less_than<index_t>(myDequeueCount - overcommit, tail))) {
+                                       index_t index = this->headIndex.fetch_add(1, std::memory_order_acq_rel);
+                                       
+                                       // Determine which block the element is in
+                                       auto entry = get_block_index_entry_for_index(index);
+                                       
+                                       // Dequeue
+                                       auto block = entry->value.load(std::memory_order_relaxed);
+                                       auto& el = *((*block)[index]);
+                                       
+                                       if (!MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, element = std::move(el))) {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                                               // Note: Acquiring the mutex with every dequeue instead of only when a block
+                                               // is released is very sub-optimal, but it is, after all, purely debug code.
+                                               debug::DebugLock lock(producer->mutex);
+#endif
+                                               struct Guard {
+                                                       Block* block;
+                                                       index_t index;
+                                                       BlockIndexEntry* entry;
+                                                       ConcurrentQueue* parent;
+                                                       
+                                                       ~Guard()
+                                                       {
+                                                               (*block)[index]->~T();
+                                                               if (block->ConcurrentQueue::Block::template set_empty<implicit_context>(index)) {
+                                                                       entry->value.store(nullptr, std::memory_order_relaxed);
+                                                                       parent->add_block_to_free_list(block);
+                                                               }
+                                                       }
+                                               } guard = { block, index, entry, this->parent };
+                                               
+                                               element = std::move(el);
+                                       }
+                                       else {
+                                               element = std::move(el);
+                                               el.~T();
+                                       
+                                               if (block->ConcurrentQueue::Block::template set_empty<implicit_context>(index)) {
+                                                       {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                                                               debug::DebugLock lock(mutex);
+#endif
+                                                               // Add the block back into the global free pool (and remove from block index)
+                                                               entry->value.store(nullptr, std::memory_order_relaxed);
+                                                       }
+                                                       this->parent->add_block_to_free_list(block);            // releases the above store
+                                               }
+                                       }
+                                       
+                                       return true;
+                               }
+                               else {
+                                       this->dequeueOvercommit.fetch_add(1, std::memory_order_release);
+                               }
+                       }
+               
+                       return false;
+               }
+               
+               template<AllocationMode allocMode, typename It>
+               bool enqueue_bulk(It itemFirst, size_t count)
+               {
+                       // First, we need to make sure we have enough room to enqueue all of the elements;
+                       // this means pre-allocating blocks and putting them in the block index (but only if
+                       // all the allocations succeeded).
+                       
+                       // Note that the tailBlock we start off with may not be owned by us any more;
+                       // this happens if it was filled up exactly to the top (setting tailIndex to
+                       // the first index of the next block which is not yet allocated), then dequeued
+                       // completely (putting it on the free list) before we enqueue again.
+                       
+                       index_t startTailIndex = this->tailIndex.load(std::memory_order_relaxed);
+                       auto startBlock = this->tailBlock;
+                       Block* firstAllocatedBlock = nullptr;
+                       auto endBlock = this->tailBlock;
+                       
+                       // Figure out how many blocks we'll need to allocate, and do so
+                       size_t blockBaseDiff = ((startTailIndex + count - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1)) - ((startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1));
+                       index_t currentTailIndex = (startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1);
+                       if (blockBaseDiff > 0) {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                               debug::DebugLock lock(mutex);
+#endif
+                               do {
+                                       blockBaseDiff -= static_cast<index_t>(BLOCK_SIZE);
+                                       currentTailIndex += static_cast<index_t>(BLOCK_SIZE);
+                                       
+                                       // Find out where we'll be inserting this block in the block index
+                                       BlockIndexEntry* idxEntry;
+                                       Block* newBlock;
+                                       bool indexInserted = false;
+                                       auto head = this->headIndex.load(std::memory_order_relaxed);
+                                       assert(!details::circular_less_than<index_t>(currentTailIndex, head));
+                                       bool full = !details::circular_less_than<index_t>(head, currentTailIndex + BLOCK_SIZE) || (MAX_SUBQUEUE_SIZE != details::const_numeric_max<size_t>::value && (MAX_SUBQUEUE_SIZE == 0 || MAX_SUBQUEUE_SIZE - BLOCK_SIZE < currentTailIndex - head));
+                                       if (full || !(indexInserted = insert_block_index_entry<allocMode>(idxEntry, currentTailIndex)) || (newBlock = this->parent->ConcurrentQueue::template requisition_block<allocMode>()) == nullptr) {
+                                               // Index allocation or block allocation failed; revert any other allocations
+                                               // and index insertions done so far for this operation
+                                               if (indexInserted) {
+                                                       rewind_block_index_tail();
+                                                       idxEntry->value.store(nullptr, std::memory_order_relaxed);
+                                               }
+                                               currentTailIndex = (startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1);
+                                               for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) {
+                                                       currentTailIndex += static_cast<index_t>(BLOCK_SIZE);
+                                                       idxEntry = get_block_index_entry_for_index(currentTailIndex);
+                                                       idxEntry->value.store(nullptr, std::memory_order_relaxed);
+                                                       rewind_block_index_tail();
+                                               }
+                                               this->parent->add_blocks_to_free_list(firstAllocatedBlock);
+                                               this->tailBlock = startBlock;
+                                               
+                                               return false;
+                                       }
+                                       
+#if MCDBGQ_TRACKMEM
+                                       newBlock->owner = this;
+#endif
+                                       newBlock->ConcurrentQueue::Block::template reset_empty<implicit_context>();
+                                       newBlock->next = nullptr;
+                                       
+                                       // Insert the new block into the index
+                                       idxEntry->value.store(newBlock, std::memory_order_relaxed);
+                                       
+                                       // Store the chain of blocks so that we can undo if later allocations fail,
+                                       // and so that we can find the blocks when we do the actual enqueueing
+                                       if ((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr) {
+                                               assert(this->tailBlock != nullptr);
+                                               this->tailBlock->next = newBlock;
+                                       }
+                                       this->tailBlock = newBlock;
+                                       endBlock = newBlock;
+                                       firstAllocatedBlock = firstAllocatedBlock == nullptr ? newBlock : firstAllocatedBlock;
+                               } while (blockBaseDiff > 0);
+                       }
+                       
+                       // Enqueue, one block at a time
+                       index_t newTailIndex = startTailIndex + static_cast<index_t>(count);
+                       currentTailIndex = startTailIndex;
+                       this->tailBlock = startBlock;
+                       assert((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) != 0 || firstAllocatedBlock != nullptr || count == 0);
+                       if ((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0 && firstAllocatedBlock != nullptr) {
+                               this->tailBlock = firstAllocatedBlock;
+                       }
+                       while (true) {
+                               auto stopIndex = (currentTailIndex & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                               if (details::circular_less_than<index_t>(newTailIndex, stopIndex)) {
+                                       stopIndex = newTailIndex;
+                               }
+                               if (MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))) {
+                                       while (currentTailIndex != stopIndex) {
+                                               new ((*this->tailBlock)[currentTailIndex++]) T(*itemFirst++);
+                                       }
+                               }
+                               else {
+                                       MOODYCAMEL_TRY {
+                                               while (currentTailIndex != stopIndex) {
+                                                       new ((*this->tailBlock)[currentTailIndex]) T(details::nomove_if<(bool)!MOODYCAMEL_NOEXCEPT_CTOR(T, decltype(*itemFirst), new (nullptr) T(details::deref_noexcept(itemFirst)))>::eval(*itemFirst));
+                                                       ++currentTailIndex;
+                                                       ++itemFirst;
+                                               }
+                                       }
+                                       MOODYCAMEL_CATCH (...) {
+                                               auto constructedStopIndex = currentTailIndex;
+                                               auto lastBlockEnqueued = this->tailBlock;
+                                               
+                                               if (!details::is_trivially_destructible<T>::value) {
+                                                       auto block = startBlock;
+                                                       if ((startTailIndex & static_cast<index_t>(BLOCK_SIZE - 1)) == 0) {
+                                                               block = firstAllocatedBlock;
+                                                       }
+                                                       currentTailIndex = startTailIndex;
+                                                       while (true) {
+                                                               auto stopIndex = (currentTailIndex & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                                                               if (details::circular_less_than<index_t>(constructedStopIndex, stopIndex)) {
+                                                                       stopIndex = constructedStopIndex;
+                                                               }
+                                                               while (currentTailIndex != stopIndex) {
+                                                                       (*block)[currentTailIndex++]->~T();
+                                                               }
+                                                               if (block == lastBlockEnqueued) {
+                                                                       break;
+                                                               }
+                                                               block = block->next;
+                                                       }
+                                               }
+                                               
+                                               currentTailIndex = (startTailIndex - 1) & ~static_cast<index_t>(BLOCK_SIZE - 1);
+                                               for (auto block = firstAllocatedBlock; block != nullptr; block = block->next) {
+                                                       currentTailIndex += static_cast<index_t>(BLOCK_SIZE);
+                                                       auto idxEntry = get_block_index_entry_for_index(currentTailIndex);
+                                                       idxEntry->value.store(nullptr, std::memory_order_relaxed);
+                                                       rewind_block_index_tail();
+                                               }
+                                               this->parent->add_blocks_to_free_list(firstAllocatedBlock);
+                                               this->tailBlock = startBlock;
+                                               MOODYCAMEL_RETHROW;
+                                       }
+                               }
+                               
+                               if (this->tailBlock == endBlock) {
+                                       assert(currentTailIndex == newTailIndex);
+                                       break;
+                               }
+                               this->tailBlock = this->tailBlock->next;
+                       }
+                       this->tailIndex.store(newTailIndex, std::memory_order_release);
+                       return true;
+               }
+               
+               template<typename It>
+               size_t dequeue_bulk(It& itemFirst, size_t max)
+               {
+                       auto tail = this->tailIndex.load(std::memory_order_relaxed);
+                       auto overcommit = this->dequeueOvercommit.load(std::memory_order_relaxed);
+                       auto desiredCount = static_cast<size_t>(tail - (this->dequeueOptimisticCount.load(std::memory_order_relaxed) - overcommit));
+                       if (details::circular_less_than<size_t>(0, desiredCount)) {
+                               desiredCount = desiredCount < max ? desiredCount : max;
+                               std::atomic_thread_fence(std::memory_order_acquire);
+                               
+                               auto myDequeueCount = this->dequeueOptimisticCount.fetch_add(desiredCount, std::memory_order_relaxed);
+                               assert(overcommit <= myDequeueCount);
+                               
+                               tail = this->tailIndex.load(std::memory_order_acquire);
+                               auto actualCount = static_cast<size_t>(tail - (myDequeueCount - overcommit));
+                               if (details::circular_less_than<size_t>(0, actualCount)) {
+                                       actualCount = desiredCount < actualCount ? desiredCount : actualCount;
+                                       if (actualCount < desiredCount) {
+                                               this->dequeueOvercommit.fetch_add(desiredCount - actualCount, std::memory_order_release);
+                                       }
+                                       
+                                       // Get the first index. Note that since there's guaranteed to be at least actualCount elements, this
+                                       // will never exceed tail.
+                                       auto firstIndex = this->headIndex.fetch_add(actualCount, std::memory_order_acq_rel);
+                                       
+                                       // Iterate the blocks and dequeue
+                                       auto index = firstIndex;
+                                       BlockIndexHeader* localBlockIndex;
+                                       auto indexIndex = get_block_index_index_for_index(index, localBlockIndex);
+                                       do {
+                                               auto blockStartIndex = index;
+                                               auto endIndex = (index & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                                               endIndex = details::circular_less_than<index_t>(firstIndex + static_cast<index_t>(actualCount), endIndex) ? firstIndex + static_cast<index_t>(actualCount) : endIndex;
+                                               
+                                               auto entry = localBlockIndex->index[indexIndex];
+                                               auto block = entry->value.load(std::memory_order_relaxed);
+                                               if (MOODYCAMEL_NOEXCEPT_ASSIGN(T, T&&, details::deref_noexcept(itemFirst) = std::move((*(*block)[index])))) {
+                                                       while (index != endIndex) {
+                                                               auto& el = *((*block)[index]);
+                                                               *itemFirst++ = std::move(el);
+                                                               el.~T();
+                                                               ++index;
+                                                       }
+                                               }
+                                               else {
+                                                       MOODYCAMEL_TRY {
+                                                               while (index != endIndex) {
+                                                                       auto& el = *((*block)[index]);
+                                                                       *itemFirst = std::move(el);
+                                                                       ++itemFirst;
+                                                                       el.~T();
+                                                                       ++index;
+                                                               }
+                                                       }
+                                                       MOODYCAMEL_CATCH (...) {
+                                                               do {
+                                                                       entry = localBlockIndex->index[indexIndex];
+                                                                       block = entry->value.load(std::memory_order_relaxed);
+                                                                       while (index != endIndex) {
+                                                                               (*block)[index++]->~T();
+                                                                       }
+                                                                       
+                                                                       if (block->ConcurrentQueue::Block::template set_many_empty<implicit_context>(blockStartIndex, static_cast<size_t>(endIndex - blockStartIndex))) {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                                                                               debug::DebugLock lock(mutex);
+#endif
+                                                                               entry->value.store(nullptr, std::memory_order_relaxed);
+                                                                               this->parent->add_block_to_free_list(block);
+                                                                       }
+                                                                       indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1);
+                                                                       
+                                                                       blockStartIndex = index;
+                                                                       endIndex = (index & ~static_cast<index_t>(BLOCK_SIZE - 1)) + static_cast<index_t>(BLOCK_SIZE);
+                                                                       endIndex = details::circular_less_than<index_t>(firstIndex + static_cast<index_t>(actualCount), endIndex) ? firstIndex + static_cast<index_t>(actualCount) : endIndex;
+                                                               } while (index != firstIndex + actualCount);
+                                                               
+                                                               MOODYCAMEL_RETHROW;
+                                                       }
+                                               }
+                                               if (block->ConcurrentQueue::Block::template set_many_empty<implicit_context>(blockStartIndex, static_cast<size_t>(endIndex - blockStartIndex))) {
+                                                       {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                                                               debug::DebugLock lock(mutex);
+#endif
+                                                               // Note that the set_many_empty above did a release, meaning that anybody who acquires the block
+                                                               // we're about to free can use it safely since our writes (and reads!) will have happened-before then.
+                                                               entry->value.store(nullptr, std::memory_order_relaxed);
+                                                       }
+                                                       this->parent->add_block_to_free_list(block);            // releases the above store
+                                               }
+                                               indexIndex = (indexIndex + 1) & (localBlockIndex->capacity - 1);
+                                       } while (index != firstIndex + actualCount);
+                                       
+                                       return actualCount;
+                               }
+                               else {
+                                       this->dequeueOvercommit.fetch_add(desiredCount, std::memory_order_release);
+                               }
+                       }
+                       
+                       return 0;
+               }
+               
+       private:
+               // The block size must be > 1, so any number with the low bit set is an invalid block base index
+               static const index_t INVALID_BLOCK_BASE = 1;
+               
+               struct BlockIndexEntry
+               {
+                       std::atomic<index_t> key;
+                       std::atomic<Block*> value;
+               };
+               
+               struct BlockIndexHeader
+               {
+                       size_t capacity;
+                       std::atomic<size_t> tail;
+                       BlockIndexEntry* entries;
+                       BlockIndexEntry** index;
+                       BlockIndexHeader* prev;
+               };
+               
+               template<AllocationMode allocMode>
+               inline bool insert_block_index_entry(BlockIndexEntry*& idxEntry, index_t blockStartIndex)
+               {
+                       auto localBlockIndex = blockIndex.load(std::memory_order_relaxed);              // We're the only writer thread, relaxed is OK
+                       auto newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1);
+                       idxEntry = localBlockIndex->index[newTail];
+                       if (idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE ||
+                               idxEntry->value.load(std::memory_order_relaxed) == nullptr) {
+                               
+                               idxEntry->key.store(blockStartIndex, std::memory_order_relaxed);
+                               localBlockIndex->tail.store(newTail, std::memory_order_release);
+                               return true;
+                       }
+                       
+                       // No room in the old block index, try to allocate another one!
+                       if (allocMode == CannotAlloc || !new_block_index()) {
+                               return false;
+                       }
+                       localBlockIndex = blockIndex.load(std::memory_order_relaxed);
+                       newTail = (localBlockIndex->tail.load(std::memory_order_relaxed) + 1) & (localBlockIndex->capacity - 1);
+                       idxEntry = localBlockIndex->index[newTail];
+                       assert(idxEntry->key.load(std::memory_order_relaxed) == INVALID_BLOCK_BASE);
+                       idxEntry->key.store(blockStartIndex, std::memory_order_relaxed);
+                       localBlockIndex->tail.store(newTail, std::memory_order_release);
+                       return true;
+               }
+               
+               inline void rewind_block_index_tail()
+               {
+                       auto localBlockIndex = blockIndex.load(std::memory_order_relaxed);
+                       localBlockIndex->tail.store((localBlockIndex->tail.load(std::memory_order_relaxed) - 1) & (localBlockIndex->capacity - 1), std::memory_order_relaxed);
+               }
+               
+               inline BlockIndexEntry* get_block_index_entry_for_index(index_t index) const
+               {
+                       BlockIndexHeader* localBlockIndex;
+                       auto idx = get_block_index_index_for_index(index, localBlockIndex);
+                       return localBlockIndex->index[idx];
+               }
+               
+               inline size_t get_block_index_index_for_index(index_t index, BlockIndexHeader*& localBlockIndex) const
+               {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+                       debug::DebugLock lock(mutex);
+#endif
+                       index &= ~static_cast<index_t>(BLOCK_SIZE - 1);
+                       localBlockIndex = blockIndex.load(std::memory_order_acquire);
+                       auto tail = localBlockIndex->tail.load(std::memory_order_acquire);
+                       auto tailBase = localBlockIndex->index[tail]->key.load(std::memory_order_relaxed);
+                       assert(tailBase != INVALID_BLOCK_BASE);
+                       // Note: Must use division instead of shift because the index may wrap around, causing a negative
+                       // offset, whose negativity we want to preserve
+                       auto offset = static_cast<size_t>(static_cast<typename std::make_signed<index_t>::type>(index - tailBase) / BLOCK_SIZE);
+                       size_t idx = (tail + offset) & (localBlockIndex->capacity - 1);
+                       assert(localBlockIndex->index[idx]->key.load(std::memory_order_relaxed) == index && localBlockIndex->index[idx]->value.load(std::memory_order_relaxed) != nullptr);
+                       return idx;
+               }
+               
+               bool new_block_index()
+               {
+                       auto prev = blockIndex.load(std::memory_order_relaxed);
+                       size_t prevCapacity = prev == nullptr ? 0 : prev->capacity;
+                       auto entryCount = prev == nullptr ? nextBlockIndexCapacity : prevCapacity;
+                       auto raw = static_cast<char*>((Traits::malloc)(
+                               sizeof(BlockIndexHeader) +
+                               std::alignment_of<BlockIndexEntry>::value - 1 + sizeof(BlockIndexEntry) * entryCount +
+                               std::alignment_of<BlockIndexEntry*>::value - 1 + sizeof(BlockIndexEntry*) * nextBlockIndexCapacity));
+                       if (raw == nullptr) {
+                               return false;
+                       }
+                       
+                       auto header = new (raw) BlockIndexHeader;
+                       auto entries = reinterpret_cast<BlockIndexEntry*>(details::align_for<BlockIndexEntry>(raw + sizeof(BlockIndexHeader)));
+                       auto index = reinterpret_cast<BlockIndexEntry**>(details::align_for<BlockIndexEntry*>(reinterpret_cast<char*>(entries) + sizeof(BlockIndexEntry) * entryCount));
+                       if (prev != nullptr) {
+                               auto prevTail = prev->tail.load(std::memory_order_relaxed);
+                               auto prevPos = prevTail;
+                               size_t i = 0;
+                               do {
+                                       prevPos = (prevPos + 1) & (prev->capacity - 1);
+                                       index[i++] = prev->index[prevPos];
+                               } while (prevPos != prevTail);
+                               assert(i == prevCapacity);
+                       }
+                       for (size_t i = 0; i != entryCount; ++i) {
+                               new (entries + i) BlockIndexEntry;
+                               entries[i].key.store(INVALID_BLOCK_BASE, std::memory_order_relaxed);
+                               index[prevCapacity + i] = entries + i;
+                       }
+                       header->prev = prev;
+                       header->entries = entries;
+                       header->index = index;
+                       header->capacity = nextBlockIndexCapacity;
+                       header->tail.store((prevCapacity - 1) & (nextBlockIndexCapacity - 1), std::memory_order_relaxed);
+                       
+                       blockIndex.store(header, std::memory_order_release);
+                       
+                       nextBlockIndexCapacity <<= 1;
+                       
+                       return true;
+               }
+               
+       private:
+               size_t nextBlockIndexCapacity;
+               std::atomic<BlockIndexHeader*> blockIndex;
+
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+       public:
+               details::ThreadExitListener threadExitListener;
+       private:
+#endif
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+       public:
+               ImplicitProducer* nextImplicitProducer;
+       private:
+#endif
+
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODBLOCKINDEX
+               mutable debug::DebugMutex mutex;
+#endif
+#if MCDBGQ_TRACKMEM
+               friend struct MemStats;
+#endif
+       };
+       
+       
+       //////////////////////////////////
+       // Block pool manipulation
+       //////////////////////////////////
+       
+       void populate_initial_block_list(size_t blockCount)
+       {
+               initialBlockPoolSize = blockCount;
+               if (initialBlockPoolSize == 0) {
+                       initialBlockPool = nullptr;
+                       return;
+               }
+               
+               initialBlockPool = create_array<Block>(blockCount);
+               if (initialBlockPool == nullptr) {
+                       initialBlockPoolSize = 0;
+               }
+               for (size_t i = 0; i < initialBlockPoolSize; ++i) {
+                       initialBlockPool[i].dynamicallyAllocated = false;
+               }
+       }
+       
+       inline Block* try_get_block_from_initial_pool()
+       {
+               if (initialBlockPoolIndex.load(std::memory_order_relaxed) >= initialBlockPoolSize) {
+                       return nullptr;
+               }
+               
+               auto index = initialBlockPoolIndex.fetch_add(1, std::memory_order_relaxed);
+               
+               return index < initialBlockPoolSize ? (initialBlockPool + index) : nullptr;
+       }
+       
+       inline void add_block_to_free_list(Block* block)
+       {
+#if MCDBGQ_TRACKMEM
+               block->owner = nullptr;
+#endif
+               freeList.add(block);
+       }
+       
+       inline void add_blocks_to_free_list(Block* block)
+       {
+               while (block != nullptr) {
+                       auto next = block->next;
+                       add_block_to_free_list(block);
+                       block = next;
+               }
+       }
+       
+       inline Block* try_get_block_from_free_list()
+       {
+               return freeList.try_get();
+       }
+       
+       // Gets a free block from one of the memory pools, or allocates a new one (if applicable)
+       template<AllocationMode canAlloc>
+       Block* requisition_block()
+       {
+               auto block = try_get_block_from_initial_pool();
+               if (block != nullptr) {
+                       return block;
+               }
+               
+               block = try_get_block_from_free_list();
+               if (block != nullptr) {
+                       return block;
+               }
+               
+               if (canAlloc == CanAlloc) {
+                       return create<Block>();
+               }
+               
+               return nullptr;
+       }
+       
+
+#if MCDBGQ_TRACKMEM
+       public:
+               struct MemStats {
+                       size_t allocatedBlocks;
+                       size_t usedBlocks;
+                       size_t freeBlocks;
+                       size_t ownedBlocksExplicit;
+                       size_t ownedBlocksImplicit;
+                       size_t implicitProducers;
+                       size_t explicitProducers;
+                       size_t elementsEnqueued;
+                       size_t blockClassBytes;
+                       size_t queueClassBytes;
+                       size_t implicitBlockIndexBytes;
+                       size_t explicitBlockIndexBytes;
+                       
+                       friend class ConcurrentQueue;
+                       
+               private:
+                       static MemStats getFor(ConcurrentQueue* q)
+                       {
+                               MemStats stats = { 0 };
+                               
+                               stats.elementsEnqueued = q->size_approx();
+                       
+                               auto block = q->freeList.head_unsafe();
+                               while (block != nullptr) {
+                                       ++stats.allocatedBlocks;
+                                       ++stats.freeBlocks;
+                                       block = block->freeListNext.load(std::memory_order_relaxed);
+                               }
+                               
+                               for (auto ptr = q->producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
+                                       bool implicit = dynamic_cast<ImplicitProducer*>(ptr) != nullptr;
+                                       stats.implicitProducers += implicit ? 1 : 0;
+                                       stats.explicitProducers += implicit ? 0 : 1;
+                                       
+                                       if (implicit) {
+                                               auto prod = static_cast<ImplicitProducer*>(ptr);
+                                               stats.queueClassBytes += sizeof(ImplicitProducer);
+                                               auto head = prod->headIndex.load(std::memory_order_relaxed);
+                                               auto tail = prod->tailIndex.load(std::memory_order_relaxed);
+                                               auto hash = prod->blockIndex.load(std::memory_order_relaxed);
+                                               if (hash != nullptr) {
+                                                       for (size_t i = 0; i != hash->capacity; ++i) {
+                                                               if (hash->index[i]->key.load(std::memory_order_relaxed) != ImplicitProducer::INVALID_BLOCK_BASE && hash->index[i]->value.load(std::memory_order_relaxed) != nullptr) {
+                                                                       ++stats.allocatedBlocks;
+                                                                       ++stats.ownedBlocksImplicit;
+                                                               }
+                                                       }
+                                                       stats.implicitBlockIndexBytes += hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry);
+                                                       for (; hash != nullptr; hash = hash->prev) {
+                                                               stats.implicitBlockIndexBytes += sizeof(typename ImplicitProducer::BlockIndexHeader) + hash->capacity * sizeof(typename ImplicitProducer::BlockIndexEntry*);
+                                                       }
+                                               }
+                                               for (; details::circular_less_than<index_t>(head, tail); head += BLOCK_SIZE) {
+                                                       //auto block = prod->get_block_index_entry_for_index(head);
+                                                       ++stats.usedBlocks;
+                                               }
+                                       }
+                                       else {
+                                               auto prod = static_cast<ExplicitProducer*>(ptr);
+                                               stats.queueClassBytes += sizeof(ExplicitProducer);
+                                               auto tailBlock = prod->tailBlock;
+                                               bool wasNonEmpty = false;
+                                               if (tailBlock != nullptr) {
+                                                       auto block = tailBlock;
+                                                       do {
+                                                               ++stats.allocatedBlocks;
+                                                               if (!block->ConcurrentQueue::Block::template is_empty<explicit_context>() || wasNonEmpty) {
+                                                                       ++stats.usedBlocks;
+                                                                       wasNonEmpty = wasNonEmpty || block != tailBlock;
+                                                               }
+                                                               ++stats.ownedBlocksExplicit;
+                                                               block = block->next;
+                                                       } while (block != tailBlock);
+                                               }
+                                               auto index = prod->blockIndex.load(std::memory_order_relaxed);
+                                               while (index != nullptr) {
+                                                       stats.explicitBlockIndexBytes += sizeof(typename ExplicitProducer::BlockIndexHeader) + index->size * sizeof(typename ExplicitProducer::BlockIndexEntry);
+                                                       index = static_cast<typename ExplicitProducer::BlockIndexHeader*>(index->prev);
+                                               }
+                                       }
+                               }
+                               
+                               auto freeOnInitialPool = q->initialBlockPoolIndex.load(std::memory_order_relaxed) >= q->initialBlockPoolSize ? 0 : q->initialBlockPoolSize - q->initialBlockPoolIndex.load(std::memory_order_relaxed);
+                               stats.allocatedBlocks += freeOnInitialPool;
+                               stats.freeBlocks += freeOnInitialPool;
+                               
+                               stats.blockClassBytes = sizeof(Block) * stats.allocatedBlocks;
+                               stats.queueClassBytes += sizeof(ConcurrentQueue);
+                               
+                               return stats;
+                       }
+               };
+               
+               // For debugging only. Not thread-safe.
+               MemStats getMemStats()
+               {
+                       return MemStats::getFor(this);
+               }
+       private:
+               friend struct MemStats;
+#endif
+       
+       
+       //////////////////////////////////
+       // Producer list manipulation
+       //////////////////////////////////      
+       
+       ProducerBase* recycle_or_create_producer(bool isExplicit)
+       {
+               bool recycled;
+               return recycle_or_create_producer(isExplicit, recycled);
+       }
+       
+       ProducerBase* recycle_or_create_producer(bool isExplicit, bool& recycled)
+       {
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
+               debug::DebugLock lock(implicitProdMutex);
+#endif
+               // Try to re-use one first
+               for (auto ptr = producerListTail.load(std::memory_order_acquire); ptr != nullptr; ptr = ptr->next_prod()) {
+                       if (ptr->inactive.load(std::memory_order_relaxed) && ptr->isExplicit == isExplicit) {
+                               bool expected = true;
+                               if (ptr->inactive.compare_exchange_strong(expected, /* desired */ false, std::memory_order_acquire, std::memory_order_relaxed)) {
+                                       // We caught one! It's been marked as activated, the caller can have it
+                                       recycled = true;
+                                       return ptr;
+                               }
+                       }
+               }
+               
+               recycled = false;
+               return add_producer(isExplicit ? static_cast<ProducerBase*>(create<ExplicitProducer>(this)) : create<ImplicitProducer>(this));
+       }
+       
+       ProducerBase* add_producer(ProducerBase* producer)
+       {
+               // Handle failed memory allocation
+               if (producer == nullptr) {
+                       return nullptr;
+               }
+               
+               producerCount.fetch_add(1, std::memory_order_relaxed);
+               
+               // Add it to the lock-free list
+               auto prevTail = producerListTail.load(std::memory_order_relaxed);
+               do {
+                       producer->next = prevTail;
+               } while (!producerListTail.compare_exchange_weak(prevTail, producer, std::memory_order_release, std::memory_order_relaxed));
+               
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+               if (producer->isExplicit) {
+                       auto prevTailExplicit = explicitProducers.load(std::memory_order_relaxed);
+                       do {
+                               static_cast<ExplicitProducer*>(producer)->nextExplicitProducer = prevTailExplicit;
+                       } while (!explicitProducers.compare_exchange_weak(prevTailExplicit, static_cast<ExplicitProducer*>(producer), std::memory_order_release, std::memory_order_relaxed));
+               }
+               else {
+                       auto prevTailImplicit = implicitProducers.load(std::memory_order_relaxed);
+                       do {
+                               static_cast<ImplicitProducer*>(producer)->nextImplicitProducer = prevTailImplicit;
+                       } while (!implicitProducers.compare_exchange_weak(prevTailImplicit, static_cast<ImplicitProducer*>(producer), std::memory_order_release, std::memory_order_relaxed));
+               }
+#endif
+               
+               return producer;
+       }
+       
+       void reown_producers()
+       {
+               // After another instance is moved-into/swapped-with this one, all the
+               // producers we stole still think their parents are the other queue.
+               // So fix them up!
+               for (auto ptr = producerListTail.load(std::memory_order_relaxed); ptr != nullptr; ptr = ptr->next_prod()) {
+                       ptr->parent = this;
+               }
+       }
+       
+       
+       //////////////////////////////////
+       // Implicit producer hash
+       //////////////////////////////////
+       
+       struct ImplicitProducerKVP
+       {
+               std::atomic<details::thread_id_t> key;
+               ImplicitProducer* value;                // No need for atomicity since it's only read by the thread that sets it in the first place
+               
+               ImplicitProducerKVP() : value(nullptr) { }
+               
+               ImplicitProducerKVP(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT
+               {
+                       key.store(other.key.load(std::memory_order_relaxed), std::memory_order_relaxed);
+                       value = other.value;
+               }
+               
+               inline ImplicitProducerKVP& operator=(ImplicitProducerKVP&& other) MOODYCAMEL_NOEXCEPT
+               {
+                       swap(other);
+                       return *this;
+               }
+               
+               inline void swap(ImplicitProducerKVP& other) MOODYCAMEL_NOEXCEPT
+               {
+                       if (this != &other) {
+                               details::swap_relaxed(key, other.key);
+                               std::swap(value, other.value);
+                       }
+               }
+       };
+       
+       template<typename XT, typename XTraits>
+       friend void moodycamel::swap(typename ConcurrentQueue<XT, XTraits>::ImplicitProducerKVP&, typename ConcurrentQueue<XT, XTraits>::ImplicitProducerKVP&) MOODYCAMEL_NOEXCEPT;
+       
+       struct ImplicitProducerHash
+       {
+               size_t capacity;
+               ImplicitProducerKVP* entries;
+               ImplicitProducerHash* prev;
+       };
+       
+       inline void populate_initial_implicit_producer_hash()
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return;
+               
+               implicitProducerHashCount.store(0, std::memory_order_relaxed);
+               auto hash = &initialImplicitProducerHash;
+               hash->capacity = INITIAL_IMPLICIT_PRODUCER_HASH_SIZE;
+               hash->entries = &initialImplicitProducerHashEntries[0];
+               for (size_t i = 0; i != INITIAL_IMPLICIT_PRODUCER_HASH_SIZE; ++i) {
+                       initialImplicitProducerHashEntries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed);
+               }
+               hash->prev = nullptr;
+               implicitProducerHash.store(hash, std::memory_order_relaxed);
+       }
+       
+       void swap_implicit_producer_hashes(ConcurrentQueue& other)
+       {
+               if (INITIAL_IMPLICIT_PRODUCER_HASH_SIZE == 0) return;
+               
+               // Swap (assumes our implicit producer hash is initialized)
+               initialImplicitProducerHashEntries.swap(other.initialImplicitProducerHashEntries);
+               initialImplicitProducerHash.entries = &initialImplicitProducerHashEntries[0];
+               other.initialImplicitProducerHash.entries = &other.initialImplicitProducerHashEntries[0];
+               
+               details::swap_relaxed(implicitProducerHashCount, other.implicitProducerHashCount);
+               
+               details::swap_relaxed(implicitProducerHash, other.implicitProducerHash);
+               if (implicitProducerHash.load(std::memory_order_relaxed) == &other.initialImplicitProducerHash) {
+                       implicitProducerHash.store(&initialImplicitProducerHash, std::memory_order_relaxed);
+               }
+               else {
+                       ImplicitProducerHash* hash;
+                       for (hash = implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &other.initialImplicitProducerHash; hash = hash->prev) {
+                               continue;
+                       }
+                       hash->prev = &initialImplicitProducerHash;
+               }
+               if (other.implicitProducerHash.load(std::memory_order_relaxed) == &initialImplicitProducerHash) {
+                       other.implicitProducerHash.store(&other.initialImplicitProducerHash, std::memory_order_relaxed);
+               }
+               else {
+                       ImplicitProducerHash* hash;
+                       for (hash = other.implicitProducerHash.load(std::memory_order_relaxed); hash->prev != &initialImplicitProducerHash; hash = hash->prev) {
+                               continue;
+                       }
+                       hash->prev = &other.initialImplicitProducerHash;
+               }
+       }
+       
+       // Only fails (returns nullptr) if memory allocation fails
+       ImplicitProducer* get_or_add_implicit_producer()
+       {
+               // Note that since the data is essentially thread-local (key is thread ID),
+               // there's a reduced need for fences (memory ordering is already consistent
+               // for any individual thread), except for the current table itself.
+               
+               // Start by looking for the thread ID in the current and all previous hash tables.
+               // If it's not found, it must not be in there yet, since this same thread would
+               // have added it previously to one of the tables that we traversed.
+               
+               // Code and algorithm adapted from http://preshing.com/20130605/the-worlds-simplest-lock-free-hash-table
+               
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
+               debug::DebugLock lock(implicitProdMutex);
+#endif
+               
+               auto id = details::thread_id();
+               auto hashedId = details::hash_thread_id(id);
+               
+               auto mainHash = implicitProducerHash.load(std::memory_order_acquire);
+               for (auto hash = mainHash; hash != nullptr; hash = hash->prev) {
+                       // Look for the id in this hash
+                       auto index = hashedId;
+                       while (true) {          // Not an infinite loop because at least one slot is free in the hash table
+                               index &= hash->capacity - 1;
+                               
+                               auto probedKey = hash->entries[index].key.load(std::memory_order_relaxed);
+                               if (probedKey == id) {
+                                       // Found it! If we had to search several hashes deep, though, we should lazily add it
+                                       // to the current main hash table to avoid the extended search next time.
+                                       // Note there's guaranteed to be room in the current hash table since every subsequent
+                                       // table implicitly reserves space for all previous tables (there's only one
+                                       // implicitProducerHashCount).
+                                       auto value = hash->entries[index].value;
+                                       if (hash != mainHash) {
+                                               index = hashedId;
+                                               while (true) {
+                                                       index &= mainHash->capacity - 1;
+                                                       probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed);
+                                                       auto empty = details::invalid_thread_id;
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+                                                       auto reusable = details::invalid_thread_id2;
+                                                       if ((probedKey == empty    && mainHash->entries[index].key.compare_exchange_strong(empty,    id, std::memory_order_relaxed)) ||
+                                                               (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) {
+#else
+                                                       if ((probedKey == empty    && mainHash->entries[index].key.compare_exchange_strong(empty,    id, std::memory_order_relaxed))) {
+#endif
+                                                               mainHash->entries[index].value = value;
+                                                               break;
+                                                       }
+                                                       ++index;
+                                               }
+                                       }
+                                       
+                                       return value;
+                               }
+                               if (probedKey == details::invalid_thread_id) {
+                                       break;          // Not in this hash table
+                               }
+                               ++index;
+                       }
+               }
+               
+               // Insert!
+               auto newCount = 1 + implicitProducerHashCount.fetch_add(1, std::memory_order_relaxed);
+               while (true) {
+                       if (newCount >= (mainHash->capacity >> 1) && !implicitProducerHashResizeInProgress.test_and_set(std::memory_order_acquire)) {
+                               // We've acquired the resize lock, try to allocate a bigger hash table.
+                               // Note the acquire fence synchronizes with the release fence at the end of this block, and hence when
+                               // we reload implicitProducerHash it must be the most recent version (it only gets changed within this
+                               // locked block).
+                               mainHash = implicitProducerHash.load(std::memory_order_acquire);
+                               if (newCount >= (mainHash->capacity >> 1)) {
+                                       auto newCapacity = mainHash->capacity << 1;
+                                       while (newCount >= (newCapacity >> 1)) {
+                                               newCapacity <<= 1;
+                                       }
+                                       auto raw = static_cast<char*>((Traits::malloc)(sizeof(ImplicitProducerHash) + std::alignment_of<ImplicitProducerKVP>::value - 1 + sizeof(ImplicitProducerKVP) * newCapacity));
+                                       if (raw == nullptr) {
+                                               // Allocation failed
+                                               implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed);
+                                               implicitProducerHashResizeInProgress.clear(std::memory_order_relaxed);
+                                               return nullptr;
+                                       }
+                                       
+                                       auto newHash = new (raw) ImplicitProducerHash;
+                                       newHash->capacity = newCapacity;
+                                       newHash->entries = reinterpret_cast<ImplicitProducerKVP*>(details::align_for<ImplicitProducerKVP>(raw + sizeof(ImplicitProducerHash)));
+                                       for (size_t i = 0; i != newCapacity; ++i) {
+                                               new (newHash->entries + i) ImplicitProducerKVP;
+                                               newHash->entries[i].key.store(details::invalid_thread_id, std::memory_order_relaxed);
+                                       }
+                                       newHash->prev = mainHash;
+                                       implicitProducerHash.store(newHash, std::memory_order_release);
+                                       implicitProducerHashResizeInProgress.clear(std::memory_order_release);
+                                       mainHash = newHash;
+                               }
+                               else {
+                                       implicitProducerHashResizeInProgress.clear(std::memory_order_release);
+                               }
+                       }
+                       
+                       // If it's < three-quarters full, add to the old one anyway so that we don't have to wait for the next table
+                       // to finish being allocated by another thread (and if we just finished allocating above, the condition will
+                       // always be true)
+                       if (newCount < (mainHash->capacity >> 1) + (mainHash->capacity >> 2)) {
+                               bool recycled;
+                               auto producer = static_cast<ImplicitProducer*>(recycle_or_create_producer(false, recycled));
+                               if (producer == nullptr) {
+                                       implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed);
+                                       return nullptr;
+                               }
+                               if (recycled) {
+                                       implicitProducerHashCount.fetch_add(-1, std::memory_order_relaxed);
+                               }
+                               
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+                               producer->threadExitListener.callback = &ConcurrentQueue::implicit_producer_thread_exited_callback;
+                               producer->threadExitListener.userData = producer;
+                               details::ThreadExitNotifier::subscribe(&producer->threadExitListener);
+#endif
+                               
+                               auto index = hashedId;
+                               while (true) {
+                                       index &= mainHash->capacity - 1;
+                                       auto probedKey = mainHash->entries[index].key.load(std::memory_order_relaxed);
+                                       
+                                       auto empty = details::invalid_thread_id;
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+                                       auto reusable = details::invalid_thread_id2;
+                                       if ((probedKey == empty    && mainHash->entries[index].key.compare_exchange_strong(empty,    id, std::memory_order_relaxed)) ||
+                                               (probedKey == reusable && mainHash->entries[index].key.compare_exchange_strong(reusable, id, std::memory_order_acquire))) {
+#else
+                                       if ((probedKey == empty    && mainHash->entries[index].key.compare_exchange_strong(empty,    id, std::memory_order_relaxed))) {
+#endif
+                                               mainHash->entries[index].value = producer;
+                                               break;
+                                       }
+                                       ++index;
+                               }
+                               return producer;
+                       }
+                       
+                       // Hmm, the old hash is quite full and somebody else is busy allocating a new one.
+                       // We need to wait for the allocating thread to finish (if it succeeds, we add, if not,
+                       // we try to allocate ourselves).
+                       mainHash = implicitProducerHash.load(std::memory_order_acquire);
+               }
+       }
+       
+#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
+       void implicit_producer_thread_exited(ImplicitProducer* producer)
+       {
+               // Remove from thread exit listeners
+               details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener);
+               
+               // Remove from hash
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
+               debug::DebugLock lock(implicitProdMutex);
+#endif
+               auto hash = implicitProducerHash.load(std::memory_order_acquire);
+               assert(hash != nullptr);                // The thread exit listener is only registered if we were added to a hash in the first place
+               auto id = details::thread_id();
+               auto hashedId = details::hash_thread_id(id);
+               details::thread_id_t probedKey;
+               
+               // We need to traverse all the hashes just in case other threads aren't on the current one yet and are
+               // trying to add an entry thinking there's a free slot (because they reused a producer)
+               for (; hash != nullptr; hash = hash->prev) {
+                       auto index = hashedId;
+                       do {
+                               index &= hash->capacity - 1;
+                               probedKey = hash->entries[index].key.load(std::memory_order_relaxed);
+                               if (probedKey == id) {
+                                       hash->entries[index].key.store(details::invalid_thread_id2, std::memory_order_release);
+                                       break;
+                               }
+                               ++index;
+                       } while (probedKey != details::invalid_thread_id);              // Can happen if the hash has changed but we weren't put back in it yet, or if we weren't added to this hash in the first place
+               }
+               
+               // Mark the queue as being recyclable
+               producer->inactive.store(true, std::memory_order_release);
+       }
+       
+       static void implicit_producer_thread_exited_callback(void* userData)
+       {
+               auto producer = static_cast<ImplicitProducer*>(userData);
+               auto queue = producer->parent;
+               queue->implicit_producer_thread_exited(producer);
+       }
+#endif
+       
+       //////////////////////////////////
+       // Utility functions
+       //////////////////////////////////
+       
+       template<typename U>
+       static inline U* create_array(size_t count)
+       {
+               assert(count > 0);
+               auto p = static_cast<U*>((Traits::malloc)(sizeof(U) * count));
+               if (p == nullptr) {
+                       return nullptr;
+               }
+               
+               for (size_t i = 0; i != count; ++i) {
+                       new (p + i) U();
+               }
+               return p;
+       }
+       
+       template<typename U>
+       static inline void destroy_array(U* p, size_t count)
+       {
+               if (p != nullptr) {
+                       assert(count > 0);
+                       for (size_t i = count; i != 0; ) {
+                               (p + --i)->~U();
+                       }
+                       (Traits::free)(p);
+               }
+       }
+       
+       template<typename U>
+       static inline U* create()
+       {
+               auto p = (Traits::malloc)(sizeof(U));
+               return p != nullptr ? new (p) U : nullptr;
+       }
+       
+       template<typename U, typename A1>
+       static inline U* create(A1&& a1)
+       {
+               auto p = (Traits::malloc)(sizeof(U));
+               return p != nullptr ? new (p) U(std::forward<A1>(a1)) : nullptr;
+       }
+       
+       template<typename U>
+       static inline void destroy(U* p)
+       {
+               if (p != nullptr) {
+                       p->~U();
+               }
+               (Traits::free)(p);
+       }
+
+private:
+       std::atomic<ProducerBase*> producerListTail;
+       std::atomic<std::uint32_t> producerCount;
+       
+       std::atomic<size_t> initialBlockPoolIndex;
+       Block* initialBlockPool;
+       size_t initialBlockPoolSize;
+       
+#if !MCDBGQ_USEDEBUGFREELIST
+       FreeList<Block> freeList;
+#else
+       debug::DebugFreeList<Block> freeList;
+#endif
+       
+       std::atomic<ImplicitProducerHash*> implicitProducerHash;
+       std::atomic<size_t> implicitProducerHashCount;          // Number of slots logically used
+       ImplicitProducerHash initialImplicitProducerHash;
+       std::array<ImplicitProducerKVP, INITIAL_IMPLICIT_PRODUCER_HASH_SIZE> initialImplicitProducerHashEntries;
+       std::atomic_flag implicitProducerHashResizeInProgress;
+       
+       std::atomic<std::uint32_t> nextExplicitConsumerId;
+       std::atomic<std::uint32_t> globalExplicitConsumerOffset;
+       
+#if MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
+       debug::DebugMutex implicitProdMutex;
+#endif
+       
+#ifdef MOODYCAMEL_QUEUE_INTERNAL_DEBUG
+       std::atomic<ExplicitProducer*> explicitProducers;
+       std::atomic<ImplicitProducer*> implicitProducers;
+#endif
+};
+
+
+template<typename T, typename Traits>
+ProducerToken::ProducerToken(ConcurrentQueue<T, Traits>& queue)
+       : producer(queue.recycle_or_create_producer(true))
+{
+       if (producer != nullptr) {
+               producer->token = this;
+       }
+}
+
+template<typename T, typename Traits>
+ProducerToken::ProducerToken(BlockingConcurrentQueue<T, Traits>& queue)
+       : producer(reinterpret_cast<ConcurrentQueue<T, Traits>*>(&queue)->recycle_or_create_producer(true))
+{
+       if (producer != nullptr) {
+               producer->token = this;
+       }
+}
+
+template<typename T, typename Traits>
+ConsumerToken::ConsumerToken(ConcurrentQueue<T, Traits>& queue)
+       : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr)
+{
+       initialOffset = queue.nextExplicitConsumerId.fetch_add(1, std::memory_order_release);
+       lastKnownGlobalOffset = -1;
+}
+
+template<typename T, typename Traits>
+ConsumerToken::ConsumerToken(BlockingConcurrentQueue<T, Traits>& queue)
+       : itemsConsumedFromCurrent(0), currentProducer(nullptr), desiredProducer(nullptr)
+{
+       initialOffset = reinterpret_cast<ConcurrentQueue<T, Traits>*>(&queue)->nextExplicitConsumerId.fetch_add(1, std::memory_order_release);
+       lastKnownGlobalOffset = -1;
+}
+
+template<typename T, typename Traits>
+inline void swap(ConcurrentQueue<T, Traits>& a, ConcurrentQueue<T, Traits>& b) MOODYCAMEL_NOEXCEPT
+{
+       a.swap(b);
+}
+
+inline void swap(ProducerToken& a, ProducerToken& b) MOODYCAMEL_NOEXCEPT
+{
+       a.swap(b);
+}
+
+inline void swap(ConsumerToken& a, ConsumerToken& b) MOODYCAMEL_NOEXCEPT
+{
+       a.swap(b);
+}
+
+template<typename T, typename Traits>
+inline void swap(typename ConcurrentQueue<T, Traits>::ImplicitProducerKVP& a, typename ConcurrentQueue<T, Traits>::ImplicitProducerKVP& b) MOODYCAMEL_NOEXCEPT
+{
+       a.swap(b);
+}
+
+}
+
+#if defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
diff --git a/lib/i3ipcpp/.gitignore b/lib/i3ipcpp/.gitignore
new file mode 100644 (file)
index 0000000..da0e228
--- /dev/null
@@ -0,0 +1,4 @@
+build
+doc
+*.sublime-workspace
+*.log
diff --git a/lib/i3ipcpp/3rd/auss/LICENSE b/lib/i3ipcpp/3rd/auss/LICENSE
new file mode 100644 (file)
index 0000000..00d2e13
--- /dev/null
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to <http://unlicense.org/>
\ No newline at end of file
diff --git a/lib/i3ipcpp/3rd/auss/README.md b/lib/i3ipcpp/3rd/auss/README.md
new file mode 100644 (file)
index 0000000..1cfbe43
--- /dev/null
@@ -0,0 +1,27 @@
+# AutoStringStream ![license](https://img.shields.io/npm/l/chas-storage.svg)
+
+Simple header-only wrapper on `std::stringstream` with automatic casting to `std::string`
+
+## Usage
+
+```c++
+#include <auss.hpp>
+```
+```c++
+auss_t() << "Hello, " << user_name
+```
+```c++
+throw std::runtime_error(auss_t() << "Something gone wrong, See " << log_path)
+```
+
+### Own namespace
+If you wouldn't pollute global namespace just define `AUSS_USE_OWN_NAMESPACE`. Either before `#include` or in compiler flags (`-DAUSS_USE_OWN_NAMESPACE` for GCC).
+
+Also you can specifiy the name of namespace with `AUSS_OWN_NAMESPACE_NAME`:
+```
+-DAUSS_OWN_NAMESPACE_NAME="theauss"
+```
+
+## License
+
+Licensed under Unlicense. See `LICENSE` file for more info.
diff --git a/lib/i3ipcpp/3rd/auss/include/auss.hpp b/lib/i3ipcpp/3rd/auss/include/auss.hpp
new file mode 100644 (file)
index 0000000..9f38d3a
--- /dev/null
@@ -0,0 +1,47 @@
+#pragma once
+
+#define AUSS_HPP
+
+#include <sstream>
+
+#ifdef AUSS_USE_OWN_NAMESPACE
+       #ifndef AUSS_OWN_NAMESPACE_NAME
+       #define AUSS_OWN_NAMESPACE_NAME auss
+       #endif
+namespace AUSS_OWN_NAMESPACE_NAME {
+#endif
+
+class AutoStringStream {
+public:
+       AutoStringStream() : m_stream() {}
+       virtual ~AutoStringStream() {}
+
+       template<typename T>
+       AutoStringStream&  operator<<(const T&  arg) {
+               m_stream << arg;
+               return *this;
+       }
+
+       AutoStringStream&  operator<<(const bool arg) {
+               m_stream << (arg ? "true" : "false");
+               return *this;
+       }
+
+       operator std::string() const {
+               return m_stream.str();
+       }
+
+       const std::string  to_string() const {
+               return m_stream.str();
+       }
+private:
+       std::stringstream  m_stream;
+};
+
+#ifndef AUSS_CUSTOM_TYPEDEF
+typedef AutoStringStream auss_t;
+#endif
+
+#ifdef AUSS_USE_OWN_NAMESPACE
+}
+#endif
diff --git a/lib/i3ipcpp/CHANGELOG b/lib/i3ipcpp/CHANGELOG
new file mode 100644 (file)
index 0000000..10658eb
--- /dev/null
@@ -0,0 +1,33 @@
+0.4
+       + Added support of GET_BARCONFIG and barconfig_update event and examples for them
+       + Added getters form main and event sockets
+       + Added i3ipc::get_version()
+
+       ~ Calling i3ipc::connection::prepare_to_event_handling() is no more necessary
+       ~ Using i3ipc::errno_error when calling c-functions instead of std::runtime_error
+       ~ Logging-subsystem became public
+       ~ Refactoring of CMakeLists.txt
+
+       - i3ipc::connection::get_file_descriptor() removed
+
+0.3
+       + Added support of BINDING event
+
+       ~ Removed submodules
+
+       * Bug fixies
+
+0.2.1
+       + Added example of event handling
+       * Fixed SIGSEGV in parse_*_from_json functions
+
+0.2.0
+       + Implemented GET_TREE (i3ipc::connection::get_tree())
+
+       ~ Shipping all available payload with workspace and window events (issue #2)
+       ~ i3ipc::I3Connection renamed to i3ipc::connection
+
+       ~ Internal refreactoring
+
+       * Fixing failed build: Parts of a struct were initialised in wrong order, C99-style designated initialisers did not prevent this from causing an error [mox]
+       * Minor documentation and code fixies
diff --git a/lib/i3ipcpp/CMakeLists.txt b/lib/i3ipcpp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8d9d0bb
--- /dev/null
@@ -0,0 +1,65 @@
+# Project setup
+
+cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
+project(i3ipc++ CXX)
+
+option(WITH_LOGGING "Build with log support" OFF)
+option(WITH_TESTS "Build unit tests executables" OFF)
+option(BUILD_EXAMPLES "Build example executables" OFF)
+
+add_library(${PROJECT_NAME} STATIC
+  ${PROJECT_SOURCE_DIR}/3rd/auss/include/auss.hpp
+  ${PROJECT_SOURCE_DIR}/include/i3ipc++/ipc.hpp
+  ${PROJECT_SOURCE_DIR}/include/i3ipc++/ipc-util.hpp
+  ${PROJECT_SOURCE_DIR}/src/ipc.cpp
+  ${PROJECT_SOURCE_DIR}/src/ipc-util.cpp)
+
+target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/3rd/auss/include)
+target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include)
+
+target_compile_options(${PROJECT_NAME} PRIVATE -std=c++11 -Wall -Wextra -Wno-unused-parameter -Wno-deprecated-declarations)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Debug>:-g3 -DDEBUG>)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Release>:-O2>)
+
+# External library: jsoncpp-1.7.7 {{{
+
+find_package(PkgConfig)
+pkg_check_modules(JSONCPP REQUIRED jsoncpp)
+
+target_link_libraries(${PROJECT_NAME} PUBLIC ${JSONCPP_LIBRARIES})
+target_include_directories(${PROJECT_NAME} PUBLIC ${JSONCPP_INCLUDEDIR})
+
+# }}}
+# Export lists to the parent scope if there are any {{{
+
+get_directory_property(HAS_PARENT PARENT_DIRECTORY)
+if(HAS_PARENT)
+  set(I3IPCPP_LIBRARIES ${PROJECT_NAME} PARENT_SCOPE)
+  set(I3IPCPP_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/include ${JSONCPP_INCLUDEDIR} PARENT_SCOPE)
+endif()
+
+# }}}
+# Build examples if the option was given {{{
+
+if(BUILD_EXAMPLES)
+  add_subdirectory("${PROJECT_SOURCE_DIR}/examples")
+endif()
+
+# }}}
+# Build cpp tests if the option was given {{{
+
+if(WITH_TESTS)
+  find_package(CxxTest)
+  if(CXXTEST_FOUND)
+    include_directories(${CXXTEST_INCLUDE_DIR} "${PROJECT_SOURCE_DIR}/src")
+    add_definitions("-DTEST_SRC_ROOT=${PROJECT_SOURCE_DIR}/test")
+    enable_testing()
+    file(GLOB SRC_TEST "${PROJECT_SOURCE_DIR}/test/*.hpp")
+    CXXTEST_ADD_TEST(i3ipcpp_check test.cpp ${SRC_TEST})
+    target_link_libraries(i3ipcpp_check ${I3IPCPP_LIBRARIES})
+  else()
+    message(WARNING "CxxTest not found. Unable to run unit-tests")
+  endif()
+endif()
+
+# }}}
diff --git a/lib/i3ipcpp/Doxyfile b/lib/i3ipcpp/Doxyfile
new file mode 100644 (file)
index 0000000..0cb53b6
--- /dev/null
@@ -0,0 +1,2353 @@
+# Doxyfile 1.8.8
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a double hash (##) is considered a comment and is placed in
+# front of the TAG it is preceding.
+#
+# All text after a single hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists, items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (\" \").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all text
+# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv
+# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv
+# for the list of possible encodings.
+# The default value is: UTF-8.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by
+# double-quotes, unless you are using Doxywizard) that should identify the
+# project for which the documentation is generated. This name is used in the
+# title of most generated pages and in a few other places.
+# The default value is: My Project.
+
+PROJECT_NAME           = i3ipc++
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
+# could be handy for archiving the generated documentation or if some version
+# control system is used.
+
+PROJECT_NUMBER         = 
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer a
+# quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF          = "An C++ implementaiton of the i3 IPC"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is included in
+# the documentation. The maximum height of the logo should not exceed 55 pixels
+# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo
+# to the output directory.
+
+PROJECT_LOGO           = 
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
+# into which the generated documentation will be written. If a relative path is
+# entered, it will be relative to the location where doxygen was started. If
+# left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = doc
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub-
+# directories (in 2 levels) under the output directory of each output format and
+# will distribute the generated files over these directories. Enabling this
+# option can be useful when feeding doxygen a huge amount of source files, where
+# putting all generated files in the same directory would otherwise causes
+# performance problems for the file system.
+# The default value is: NO.
+
+CREATE_SUBDIRS         = YES
+
+# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
+# characters to appear in the names of generated files. If set to NO, non-ASCII
+# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
+# U+3044.
+# The default value is: NO.
+
+ALLOW_UNICODE_NAMES    = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
+# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
+# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
+# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
+# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
+# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
+# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
+# Ukrainian and Vietnamese.
+# The default value is: English.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member
+# descriptions after the members that are listed in the file and class
+# documentation (similar to Javadoc). Set to NO to disable this.
+# The default value is: YES.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief
+# description of a member or function before the detailed description
+#
+# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+# The default value is: YES.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator that is
+# used to form the text in various listings. Each string in this list, if found
+# as the leading text of the brief description, will be stripped from the text
+# and the result, after processing the whole list, is used as the annotated
+# text. Otherwise, the brief description is used as-is. If left blank, the
+# following values are used ($name is automatically replaced with the name of
+# the entity):The $name class, The $name widget, The $name file, is, provides,
+# specifies, contains, represents, a, an and the.
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# doxygen will generate a detailed section even if there is only a brief
+# description.
+# The default value is: NO.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+# The default value is: NO.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path
+# before files name in the file list and in the header files. If set to NO the
+# shortest path that makes the file name unique will be used
+# The default value is: YES.
+
+FULL_PATH_NAMES        = YES
+
+# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.
+# Stripping is only done if one of the specified strings matches the left-hand
+# part of the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the path to
+# strip.
+#
+# Note that you can specify absolute paths here, but also relative paths, which
+# will be relative from the directory where doxygen is started.
+# This tag requires that the tag FULL_PATH_NAMES is set to YES.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the
+# path mentioned in the documentation of a class, which tells the reader which
+# header file to include in order to use a class. If left blank only the name of
+# the header file containing the class definition is used. Otherwise one should
+# specify the list of include paths that are normally passed to the compiler
+# using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but
+# less readable) file names. This can be useful is your file systems doesn't
+# support long names like on DOS, Mac, or CD-ROM.
+# The default value is: NO.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the
+# first line (until the first dot) of a Javadoc-style comment as the brief
+# description. If set to NO, the Javadoc-style will behave just like regular Qt-
+# style comments (thus requiring an explicit @brief command for a brief
+# description.)
+# The default value is: NO.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first
+# line (until the first dot) of a Qt-style comment as the brief description. If
+# set to NO, the Qt-style will behave just like regular Qt-style comments (thus
+# requiring an explicit \brief command for a brief description.)
+# The default value is: NO.
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a
+# multi-line C++ special comment block (i.e. a block of //! or /// comments) as
+# a brief description. This used to be the default behavior. The new default is
+# to treat a multi-line C++ comment block as a detailed description. Set this
+# tag to YES if you prefer the old behavior instead.
+#
+# Note that setting this tag to YES also means that rational rose comments are
+# not recognized any more.
+# The default value is: NO.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the
+# documentation from any documented member that it re-implements.
+# The default value is: YES.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a
+# new page for each member. If set to NO, the documentation of a member will be
+# part of the file/class/namespace that contains it.
+# The default value is: NO.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen
+# uses this value to replace tabs by spaces in code fragments.
+# Minimum value: 1, maximum value: 16, default value: 4.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that act as commands in
+# the documentation. An alias has the form:
+# name=value
+# For example adding
+# "sideeffect=@par Side Effects:\n"
+# will allow you to put the command \sideeffect (or @sideeffect) in the
+# documentation, which will result in a user-defined paragraph with heading
+# "Side Effects:". You can put \n's in the value part of an alias to insert
+# newlines.
+
+ALIASES                = 
+
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding "class=itcl::class"
+# will allow you to use the command class in the itcl::class meaning.
+
+TCL_SUBST              = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
+# only. Doxygen will then generate output that is more tailored for C. For
+# instance, some of the names that are used will be different. The list of all
+# members will be omitted, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or
+# Python sources only. Doxygen will then generate output that is more tailored
+# for that language. For instance, namespaces will be presented as packages,
+# qualified scopes will look different, etc.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources. Doxygen will then generate output that is tailored for Fortran.
+# The default value is: NO.
+
+OPTIMIZE_FOR_FORTRAN   = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for VHDL.
+# The default value is: NO.
+
+OPTIMIZE_OUTPUT_VHDL   = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension, and
+# language is one of the parsers supported by doxygen: IDL, Java, Javascript,
+# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran:
+# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran:
+# Fortran. In the later case the parser tries to guess whether the code is fixed
+# or free formatted code, this is the default for Fortran type files), VHDL. For
+# instance to make doxygen treat .inc files as Fortran files (default is PHP),
+# and .f files as C (default is Fortran), use: inc=Fortran f=C.
+#
+# Note For files without extension you can use no_extension as a placeholder.
+#
+# Note that for custom extensions you also need to set FILE_PATTERNS otherwise
+# the files are not read by doxygen.
+
+EXTENSION_MAPPING      = 
+
+# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments
+# according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you can
+# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in
+# case of backward compatibilities issues.
+# The default value is: YES.
+
+MARKDOWN_SUPPORT       = YES
+
+# When enabled doxygen tries to link words that correspond to documented
+# classes, or namespaces to their corresponding documentation. Such a link can
+# be prevented in individual cases by by putting a % sign in front of the word
+# or globally by setting AUTOLINK_SUPPORT to NO.
+# The default value is: YES.
+
+AUTOLINK_SUPPORT       = YES
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should set this
+# tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string);
+# versus func(std::string) {}). This also make the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+# The default value is: NO.
+
+BUILTIN_STL_SUPPORT    = YES
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+# The default value is: NO.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:
+# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen
+# will parse them like normal C++ but will assume all classes use public instead
+# of private inheritance when no explicit protection keyword is present.
+# The default value is: NO.
+
+SIP_SUPPORT            = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate
+# getter and setter methods for a property. Setting this option to YES will make
+# doxygen to replace the get and set methods by a property in the documentation.
+# This will only work if the methods are indeed getting or setting a simple
+# type. If this is not the case, or you want to show the methods anyway, you
+# should set this option to NO.
+# The default value is: YES.
+
+IDL_PROPERTY_SUPPORT   = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+# The default value is: NO.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES to allow class member groups of the same type
+# (for instance a group of public functions) to be put as a subgroup of that
+# type (e.g. under the Public Functions section). Set it to NO to prevent
+# subgrouping. Alternatively, this can be done per class using the
+# \nosubgrouping command.
+# The default value is: YES.
+
+SUBGROUPING            = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions
+# are shown inside the group in which they are included (e.g. using \ingroup)
+# instead of on a separate page (for HTML and Man pages) or section (for LaTeX
+# and RTF).
+#
+# Note that this feature does not work in combination with
+# SEPARATE_MEMBER_PAGES.
+# The default value is: NO.
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions
+# with only public data fields or simple typedef fields will be shown inline in
+# the documentation of the scope in which they are defined (i.e. file,
+# namespace, or group documentation), provided this scope is documented. If set
+# to NO, structs, classes, and unions are shown on a separate page (for HTML and
+# Man pages) or section (for LaTeX and RTF).
+# The default value is: NO.
+
+INLINE_SIMPLE_STRUCTS  = NO
+
+# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or
+# enum is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically be
+# useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+# The default value is: NO.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This
+# cache is used to resolve symbols given their name and scope. Since this can be
+# an expensive process and often the same symbol appears multiple times in the
+# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small
+# doxygen will become slower. If the cache is too large, memory is wasted. The
+# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range
+# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536
+# symbols. At the end of a run doxygen will report the cache usage and suggest
+# the optimal cache size from a speed point of view.
+# Minimum value: 0, maximum value: 9, default value: 0.
+
+LOOKUP_CACHE_SIZE      = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available. Private
+# class members and static file members will be hidden unless the
+# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.
+# Note: This will also disable the warnings about undocumented members that are
+# normally produced when WARNINGS is set to YES.
+# The default value is: NO.
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will
+# be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+# The default value is: NO.
+
+EXTRACT_PACKAGE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file will be
+# included in the documentation.
+# The default value is: NO.
+
+EXTRACT_STATIC         = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined
+# locally in source files will be included in the documentation. If set to NO
+# only classes defined in header files are included. Does not have any effect
+# for Java sources.
+# The default value is: YES.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local methods,
+# which are defined in the implementation section but not in the interface are
+# included in the documentation. If set to NO only methods in the interface are
+# included.
+# The default value is: NO.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base name of
+# the file that contains the anonymous namespace. By default anonymous namespace
+# are hidden.
+# The default value is: NO.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all
+# undocumented members inside documented classes or files. If set to NO these
+# members will be included in the various overviews, but no documentation
+# section is generated. This option has no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy. If set
+# to NO these classes will be included in the various overviews. This option has
+# no effect if EXTRACT_ALL is enabled.
+# The default value is: NO.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend
+# (class|struct|union) declarations. If set to NO these declarations will be
+# included in the documentation.
+# The default value is: NO.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any
+# documentation blocks found inside the body of a function. If set to NO these
+# blocks will be appended to the function's detailed documentation block.
+# The default value is: NO.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation that is typed after a
+# \internal command is included. If the tag is set to NO then the documentation
+# will be excluded. Set it to YES to include the internal documentation.
+# The default value is: NO.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file
+# names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+# The default value is: system dependent.
+
+CASE_SENSE_NAMES       = NO 
+
+# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with
+# their full class and namespace scopes in the documentation. If set to YES the
+# scope will be hidden.
+# The default value is: NO.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of
+# the files that are included by a file in the documentation of that file.
+# The default value is: YES.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each
+# grouped member an include statement to the documentation, telling the reader
+# which file to include in order to use the member.
+# The default value is: NO.
+
+SHOW_GROUPED_MEMB_INC  = NO
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include
+# files with double quotes in the documentation rather than with sharp brackets.
+# The default value is: NO.
+
+FORCE_LOCAL_INCLUDES   = NO
+
+# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the
+# documentation for inline members.
+# The default value is: YES.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the
+# (detailed) documentation of file and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order.
+# The default value is: YES.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief
+# descriptions of file, namespace and class members alphabetically by member
+# name. If set to NO the members will appear in declaration order. Note that
+# this will also influence the order of the classes in the class list.
+# The default value is: NO.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the
+# (brief and detailed) documentation of class members so that constructors and
+# destructors are listed first. If set to NO the constructors will appear in the
+# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.
+# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief
+# member documentation.
+# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting
+# detailed member documentation.
+# The default value is: NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy
+# of group names into alphabetical order. If set to NO the group names will
+# appear in their defined order.
+# The default value is: NO.
+
+SORT_GROUP_NAMES       = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by
+# fully-qualified names, including namespaces. If set to NO, the class list will
+# be sorted only by class name, not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the alphabetical
+# list.
+# The default value is: NO.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper
+# type resolution of all parameters of a function it will reject a match between
+# the prototype and the implementation of a member function even if there is
+# only one candidate or it is obvious which candidate to choose by doing a
+# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still
+# accept a match between prototype and implementation in such cases.
+# The default value is: NO.
+
+STRICT_PROTO_MATCHING  = NO
+
+# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the
+# todo list. This list is created by putting \todo commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the
+# test list. This list is created by putting \test commands in the
+# documentation.
+# The default value is: YES.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug
+# list. This list is created by putting \bug commands in the documentation.
+# The default value is: YES.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO)
+# the deprecated list. This list is created by putting \deprecated commands in
+# the documentation.
+# The default value is: YES.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional documentation
+# sections, marked by \if <section_label> ... \endif and \cond <section_label>
+# ... \endcond blocks.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the
+# initial value of a variable or macro / define can have for it to appear in the
+# documentation. If the initializer consists of more lines than specified here
+# it will be hidden. Use a value of 0 to hide initializers completely. The
+# appearance of the value of individual variables and macros / defines can be
+# controlled using \showinitializer or \hideinitializer command in the
+# documentation regardless of this setting.
+# Minimum value: 0, maximum value: 10000, default value: 30.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at
+# the bottom of the documentation of classes and structs. If set to YES the list
+# will mention the files that were used to generate the documentation.
+# The default value is: YES.
+
+SHOW_USED_FILES        = YES
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This
+# will remove the Files entry from the Quick Index and from the Folder Tree View
+# (if specified).
+# The default value is: YES.
+
+SHOW_FILES             = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces
+# page. This will remove the Namespaces entry from the Quick Index and from the
+# Folder Tree View (if specified).
+# The default value is: YES.
+
+SHOW_NAMESPACES        = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command command input-file, where command is the value of the
+# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided
+# by doxygen. Whatever the program writes to standard output is used as the file
+# version. For an example see the documentation.
+
+FILE_VERSION_FILTER    = 
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option. You can
+# optionally specify a file name after the option, if omitted DoxygenLayout.xml
+# will be used as the name of the layout file.
+#
+# Note that if you run doxygen from a directory containing a file called
+# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
+# tag is left empty.
+
+LAYOUT_FILE            = 
+
+# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
+# the reference definitions. This must be a list of .bib files. The .bib
+# extension is automatically appended if omitted. This requires the bibtex tool
+# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info.
+# For LaTeX the style of the bibliography can be controlled using
+# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the
+# search path. See also \cite for info how to create references.
+
+CITE_BIB_FILES         = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated to
+# standard output by doxygen. If QUIET is set to YES this implies that the
+# messages are off.
+# The default value is: NO.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES
+# this implies that the warnings are on.
+#
+# Tip: Turn warnings on while writing the documentation.
+# The default value is: YES.
+
+WARNINGS               = YES
+
+# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate
+# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag
+# will automatically be disabled.
+# The default value is: YES.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some parameters
+# in a documented function, or documenting parameters that don't exist or using
+# markup commands wrongly.
+# The default value is: YES.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that
+# are documented, but have no documentation for their parameters or return
+# value. If set to NO doxygen will only warn about wrong or incomplete parameter
+# documentation, but not about the absence of documentation.
+# The default value is: NO.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that doxygen
+# can produce. The string should contain the $file, $line, and $text tags, which
+# will be replaced by the file and line number from which the warning originated
+# and the warning text. Optionally the format may contain $version, which will
+# be replaced by the version of the file (if it could be obtained via
+# FILE_VERSION_FILTER)
+# The default value is: $file:$line: $text.
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning and error
+# messages should be written. If left blank the output is written to standard
+# error (stderr).
+
+WARN_LOGFILE           = doxygen-warning.log
+
+#---------------------------------------------------------------------------
+# Configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag is used to specify the files and/or directories that contain
+# documented source files. You may enter file names like myfile.cpp or
+# directories like /usr/src/myproject. Separate the files or directories with
+# spaces.
+# Note: If this tag is empty the current directory is searched.
+
+INPUT                  = include
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
+# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
+# documentation (see: http://www.gnu.org/software/libiconv) for the list of
+# possible encodings.
+# The default value is: UTF-8.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank the
+# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii,
+# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp,
+# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown,
+# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf,
+# *.qsf, *.as and *.js.
+
+FILE_PATTERNS          = *.hpp \
+                         *.h
+
+# The RECURSIVE tag can be used to specify whether or not subdirectories should
+# be searched for input files as well.
+# The default value is: NO.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should be
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+#
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+# The default value is: NO.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories.
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = *.cpp
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+#
+# Note that the wildcards are matched against the file with absolute path, so to
+# exclude all test directories use the pattern */test/*
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or directories
+# that contain example code fragments that are included (see the \include
+# command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and
+# *.h) to filter out the source-files in the directories. If left blank all
+# files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude commands
+# irrespective of the value of the RECURSIVE tag.
+# The default value is: NO.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or directories
+# that contain images that are to be included in the documentation (see the
+# \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command:
+#
+# <filter> <input-file>
+#
+# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the
+# name of an input file. Doxygen will then use the output that the filter
+# program writes to standard output. If FILTER_PATTERNS is specified, this tag
+# will be ignored.
+#
+# Note that the filter must not add or remove lines; it is applied before the
+# code is scanned, but not when the output code is generated. If lines are added
+# or removed, the anchors will not be placed correctly.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis. Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match. The filters are a list of the form: pattern=filter
+# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how
+# filters are used. If the FILTER_PATTERNS tag is empty or if none of the
+# patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER ) will also be used to filter the input files that are used for
+# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).
+# The default value is: NO.
+
+FILTER_SOURCE_FILES    = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and
+# it is also possible to disable source filtering for a specific pattern using
+# *.ext= (so without naming a filter).
+# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.
+
+FILTER_SOURCE_PATTERNS = 
+
+# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that
+# is part of the input, its contents will be placed on the main page
+# (index.html). This can be useful if you have a project on for instance GitHub
+# and want to reuse the introduction page also for the doxygen output.
+
+USE_MDFILE_AS_MAINPAGE = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will be
+# generated. Documented entities will be cross-referenced with these sources.
+#
+# Note: To get rid of all source code in the generated output, make sure that
+# also VERBATIM_HEADERS is set to NO.
+# The default value is: NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body of functions,
+# classes and enums directly into the documentation.
+# The default value is: NO.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any
+# special comment blocks from generated source code fragments. Normal C, C++ and
+# Fortran comments will always remain visible.
+# The default value is: YES.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES then for each documented
+# function all documented functions referencing it will be listed.
+# The default value is: NO.
+
+REFERENCED_BY_RELATION = NO
+
+# If the REFERENCES_RELATION tag is set to YES then for each documented function
+# all documented entities called/used by that function will be listed.
+# The default value is: NO.
+
+REFERENCES_RELATION    = NO
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set
+# to YES, then the hyperlinks from functions in REFERENCES_RELATION and
+# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will
+# link to the documentation.
+# The default value is: YES.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the
+# source code will show a tooltip with additional information such as prototype,
+# brief description and links to the definition and documentation. Since this
+# will make the HTML file larger and loading of large files a bit slower, you
+# can opt to disable this feature.
+# The default value is: YES.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+SOURCE_TOOLTIPS        = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code will
+# point to the HTML generated by the htags(1) tool instead of doxygen built-in
+# source browser. The htags tool is part of GNU's global source tagging system
+# (see http://www.gnu.org/software/global/global.html). You will need version
+# 4.8.6 or higher.
+#
+# To use it do the following:
+# - Install the latest version of global
+# - Enable SOURCE_BROWSER and USE_HTAGS in the config file
+# - Make sure the INPUT points to the root of the source tree
+# - Run doxygen as normal
+#
+# Doxygen will invoke htags (and that will in turn invoke gtags), so these
+# tools must be available from the command line (i.e. in the search path).
+#
+# The result: instead of the source browser generated by doxygen, the links to
+# source code will now point to the output of htags.
+# The default value is: NO.
+# This tag requires that the tag SOURCE_BROWSER is set to YES.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a
+# verbatim copy of the header file for each class for which an include is
+# specified. Set to NO to disable this.
+# See also: Section \class.
+# The default value is: YES.
+
+VERBATIM_HEADERS       = YES
+
+# If the CLANG_ASSISTED_PARSING tag is set to YES, then doxygen will use the
+# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the
+# cost of reduced performance. This can be particularly helpful with template
+# rich C++ code for which doxygen's built-in parser lacks the necessary type
+# information.
+# Note: The availability of this option depends on whether or not doxygen was
+# compiled with the --with-libclang option.
+# The default value is: NO.
+
+CLANG_ASSISTED_PARSING = NO
+
+# If clang assisted parsing is enabled you can provide the compiler with command
+# line options that you would normally use when invoking the compiler. Note that
+# the include paths will already be set by doxygen for the files and directories
+# specified with INPUT and INCLUDE_PATH.
+# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
+
+CLANG_OPTIONS          = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all
+# compounds will be generated. Enable this if the project contains a lot of
+# classes, structs, unions or interfaces.
+# The default value is: YES.
+
+ALPHABETICAL_INDEX     = YES
+
+# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in
+# which the alphabetical index list will be split.
+# Minimum value: 1, maximum value: 20, default value: 5.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all classes will
+# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
+# can be used to specify a prefix (or a list of prefixes) that should be ignored
+# while generating the index headers.
+# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output
+# The default value is: YES.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each
+# generated HTML page (for example: .htm, .php, .asp).
+# The default value is: .html.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a user-defined HTML header file for
+# each generated HTML page. If the tag is left blank doxygen will generate a
+# standard header.
+#
+# To get valid HTML the header file that includes any scripts and style sheets
+# that doxygen needs, which is dependent on the configuration options used (e.g.
+# the setting GENERATE_TREEVIEW). It is highly recommended to start with a
+# default header using
+# doxygen -w html new_header.html new_footer.html new_stylesheet.css
+# YourConfigFile
+# and then modify the file new_header.html. See also section "Doxygen usage"
+# for information on how to generate the default header that doxygen normally
+# uses.
+# Note: The header is subject to change so you typically have to regenerate the
+# default header when upgrading to a newer version of doxygen. For a description
+# of the possible markers and block names see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
+# generated HTML page. If the tag is left blank doxygen will generate a standard
+# footer. See HTML_HEADER for more information on how to generate a default
+# footer and what special commands can be used inside the footer. See also
+# section "Doxygen usage" for information on how to generate the default footer
+# that doxygen normally uses.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style
+# sheet that is used by each HTML page. It can be used to fine-tune the look of
+# the HTML output. If left blank doxygen will generate a default style sheet.
+# See also section "Doxygen usage" for information on how to generate the style
+# sheet that doxygen normally uses.
+# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as
+# it is more robust and this tag (HTML_STYLESHEET) will in the future become
+# obsolete.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_STYLESHEET        = 
+
+# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined
+# cascading style sheets that are included after the standard style sheets
+# created by doxygen. Using this option one can overrule certain style aspects.
+# This is preferred over using HTML_STYLESHEET since it does not replace the
+# standard style sheet and is therefor more robust against future updates.
+# Doxygen will copy the style sheet files to the output directory.
+# Note: The order of the extra stylesheet files is of importance (e.g. the last
+# stylesheet in the list overrules the setting of the previous ones in the
+# list). For an example see the documentation.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_STYLESHEET  = 
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that the
+# files will be copied as-is; there are no commands or markers available.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_EXTRA_FILES       = 
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
+# will adjust the colors in the stylesheet and background images according to
+# this color. Hue is specified as an angle on a colorwheel, see
+# http://en.wikipedia.org/wiki/Hue for more information. For instance the value
+# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300
+# purple, and 360 is red again.
+# Minimum value: 0, maximum value: 359, default value: 220.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_HUE    = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
+# in the HTML output. For a value of 0 the output will use grayscales only. A
+# value of 255 will produce the most vivid colors.
+# Minimum value: 0, maximum value: 255, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_SAT    = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the
+# luminance component of the colors in the HTML output. Values below 100
+# gradually make the output lighter, whereas values above 100 make the output
+# darker. The value divided by 100 is the actual gamma applied, so 80 represents
+# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not
+# change the gamma.
+# Minimum value: 40, maximum value: 240, default value: 80.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_COLORSTYLE_GAMMA  = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting this
+# to NO can help when comparing the output of multiple runs.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_TIMESTAMP         = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
+# shown in the various tree structured indices initially; the user can expand
+# and collapse entries dynamically later on. Doxygen will expand the tree to
+# such a level that at most the specified number of entries are visible (unless
+# a fully collapsed tree already exceeds this amount). So setting the number of
+# entries 1 will produce a full collapsed tree by default. 0 is a special value
+# representing an infinite number of entries and will result in a full expanded
+# tree by default.
+# Minimum value: 0, maximum value: 9999, default value: 100.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+HTML_INDEX_NUM_ENTRIES = 100
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files will be
+# generated that can be used as input for Apple's Xcode 3 integrated development
+# environment (see: http://developer.apple.com/tools/xcode/), introduced with
+# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a
+# Makefile in the HTML output directory. Running make will produce the docset in
+# that directory and running make install will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at
+# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_DOCSET        = NO
+
+# This tag determines the name of the docset feed. A documentation feed provides
+# an umbrella under which multiple documentation sets from a single provider
+# (such as a company or product suite) can be grouped.
+# The default value is: Doxygen generated docs.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_FEEDNAME        = "Doxygen generated docs"
+
+# This tag specifies a string that should uniquely identify the documentation
+# set bundle. This should be a reverse domain-name style string, e.g.
+# com.mycompany.MyDocSet. Doxygen will append .docset to the name.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_BUNDLE_ID       = org.doxygen.Project
+
+# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+# The default value is: org.doxygen.Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_ID    = org.doxygen.Publisher
+
+# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.
+# The default value is: Publisher.
+# This tag requires that the tag GENERATE_DOCSET is set to YES.
+
+DOCSET_PUBLISHER_NAME  = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three
+# additional HTML index files: index.hhp, index.hhc, and index.hhk. The
+# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop
+# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on
+# Windows.
+#
+# The HTML Help Workshop contains a compiler that can convert all HTML output
+# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML
+# files are now used as the Windows 98 help format, and will replace the old
+# Windows help format (.hlp) on all Windows platforms in the future. Compressed
+# HTML files also contain an index, a table of contents, and you can search for
+# words in the documentation. The HTML workshop also contains a viewer for
+# compressed HTML files.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_HTMLHELP      = NO
+
+# The CHM_FILE tag can be used to specify the file name of the resulting .chm
+# file. You can add a path in front of the file if the result should not be
+# written to the html output directory.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_FILE               = 
+
+# The HHC_LOCATION tag can be used to specify the location (absolute path
+# including file name) of the HTML help compiler ( hhc.exe). If non-empty
+# doxygen will try to run the HTML help compiler on the generated index.hhp.
+# The file has to be specified with full path.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+HHC_LOCATION           = 
+
+# The GENERATE_CHI flag controls if a separate .chi index file is generated (
+# YES) or that it should be included in the master .chm file ( NO).
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+GENERATE_CHI           = NO
+
+# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc)
+# and project file content.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+CHM_INDEX_ENCODING     = 
+
+# The BINARY_TOC flag controls whether a binary table of contents is generated (
+# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it
+# enables the Previous and Next buttons.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members to
+# the table of contents of the HTML help documentation and to the tree view.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTMLHELP is set to YES.
+
+TOC_EXPAND             = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
+# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
+# (.qch) of the generated HTML documentation.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_QHP           = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify
+# the file name of the resulting .qch file. The path specified is relative to
+# the HTML output folder.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QCH_FILE               = 
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help
+# Project output. For more information please see Qt Help Project / Namespace
+# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace).
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_NAMESPACE          = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt
+# Help Project output. For more information please see Qt Help Project / Virtual
+# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual-
+# folders).
+# The default value is: doc.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_VIRTUAL_FOLDER     = doc
+
+# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom
+# filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_NAME   = 
+
+# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see Qt Help Project / Custom
+# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom-
+# filters).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_CUST_FILTER_ATTRS  = 
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's filter section matches. Qt Help Project / Filter Attributes (see:
+# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes).
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHP_SECT_FILTER_ATTRS  = 
+
+# The QHG_LOCATION tag can be used to specify the location of Qt's
+# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the
+# generated .qhp file.
+# This tag requires that the tag GENERATE_QHP is set to YES.
+
+QHG_LOCATION           = 
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be
+# generated, together with the HTML files, they form an Eclipse help plugin. To
+# install this plugin and make it available under the help contents menu in
+# Eclipse, the contents of the directory containing the HTML and XML files needs
+# to be copied into the plugins directory of eclipse. The name of the directory
+# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.
+# After copying Eclipse needs to be restarted before the help appears.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_ECLIPSEHELP   = NO
+
+# A unique identifier for the Eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have this
+# name. Each documentation set should have its own identifier.
+# The default value is: org.doxygen.Project.
+# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.
+
+ECLIPSE_DOC_ID         = org.doxygen.Project
+
+# If you want full control over the layout of the generated HTML pages it might
+# be necessary to disable the index and replace it with your own. The
+# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top
+# of each HTML page. A value of NO enables the index and the value YES disables
+# it. Since the tabs in the index contain the same information as the navigation
+# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+DISABLE_INDEX          = NO
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information. If the tag
+# value is set to YES, a side panel will be generated containing a tree-like
+# index structure (just like the one that is generated for HTML Help). For this
+# to work a browser that supports JavaScript, DHTML, CSS and frames is required
+# (i.e. any modern browser). Windows users are probably better off using the
+# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can
+# further fine-tune the look of the index. As an example, the default style
+# sheet generated by doxygen has an example that shows how to put an image at
+# the root of the tree instead of the PROJECT_NAME. Since the tree basically has
+# the same information as the tab index, you could consider setting
+# DISABLE_INDEX to YES when enabling this option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+GENERATE_TREEVIEW      = YES
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that
+# doxygen will group on one line in the generated HTML documentation.
+#
+# Note that a value of 0 will completely suppress the enum values from appearing
+# in the overview section.
+# Minimum value: 0, maximum value: 20, default value: 4.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used
+# to set the initial width (in pixels) of the frame in which the tree is shown.
+# Minimum value: 0, maximum value: 1500, default value: 250.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+TREEVIEW_WIDTH         = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to
+# external symbols imported via tag files in a separate window.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+EXT_LINKS_IN_WINDOW    = NO
+
+# Use this tag to change the font size of LaTeX formulas included as images in
+# the HTML documentation. When you change the font size after a successful
+# doxygen run you need to manually remove any form_*.png images from the HTML
+# output directory to force them to be regenerated.
+# Minimum value: 8, maximum value: 50, default value: 10.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_FONTSIZE       = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are not
+# supported properly for IE 6.0, but are supported on all modern browsers.
+#
+# Note that when changing this option you need to delete any form_*.png files in
+# the HTML output directory before the changes have effect.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+FORMULA_TRANSPARENT    = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see
+# http://www.mathjax.org) which uses client side Javascript for the rendering
+# instead of using prerendered bitmaps. Use this if you do not have LaTeX
+# installed or if you want to formulas look prettier in the HTML output. When
+# enabled you may also need to install MathJax separately and configure the path
+# to it using the MATHJAX_RELPATH option.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+USE_MATHJAX            = NO
+
+# When MathJax is enabled you can set the default output format to be used for
+# the MathJax output. See the MathJax site (see:
+# http://docs.mathjax.org/en/latest/output.html) for more details.
+# Possible values are: HTML-CSS (which is slower, but has the best
+# compatibility), NativeMML (i.e. MathML) and SVG.
+# The default value is: HTML-CSS.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_FORMAT         = HTML-CSS
+
+# When MathJax is enabled you need to specify the location relative to the HTML
+# output directory using the MATHJAX_RELPATH option. The destination directory
+# should contain the MathJax.js script. For instance, if the mathjax directory
+# is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax
+# Content Delivery Network so you can quickly see the result without installing
+# MathJax. However, it is strongly recommended to install a local copy of
+# MathJax from http://www.mathjax.org before deployment.
+# The default value is: http://cdn.mathjax.org/mathjax/latest.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax
+# extension names that should be enabled during MathJax rendering. For example
+# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_EXTENSIONS     = 
+
+# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces
+# of code that will be used on startup of the MathJax code. See the MathJax site
+# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an
+# example see the documentation.
+# This tag requires that the tag USE_MATHJAX is set to YES.
+
+MATHJAX_CODEFILE       = 
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box for
+# the HTML output. The underlying search engine uses javascript and DHTML and
+# should work on any modern browser. Note that when using HTML help
+# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)
+# there is already a search function so this one should typically be disabled.
+# For large projects the javascript based search engine can be slow, then
+# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to
+# search using the keyboard; to jump to the search box use <access key> + S
+# (what the <access key> is depends on the OS and browser, but it is typically
+# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down
+# key> to jump into the search results window, the results can be navigated
+# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel
+# the search. The filter options can be selected when the cursor is inside the
+# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>
+# to select a filter and <Enter> or <escape> to activate or cancel the filter
+# option.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_HTML is set to YES.
+
+SEARCHENGINE           = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a web server instead of a web client using Javascript. There
+# are two flavors of web server based searching depending on the EXTERNAL_SEARCH
+# setting. When disabled, doxygen will generate a PHP script for searching and
+# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing
+# and searching needs to be provided by external tools. See the section
+# "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SERVER_BASED_SEARCH    = NO
+
+# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP
+# script for searching. Instead the search results are written to an XML file
+# which needs to be processed by an external indexer. Doxygen will invoke an
+# external search engine pointed to by the SEARCHENGINE_URL option to obtain the
+# search results.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/).
+#
+# See the section "External Indexing and Searching" for details.
+# The default value is: NO.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH        = NO
+
+# The SEARCHENGINE_URL should point to a search engine hosted by a web server
+# which will return the search results when EXTERNAL_SEARCH is enabled.
+#
+# Doxygen ships with an example indexer ( doxyindexer) and search engine
+# (doxysearch.cgi) which are based on the open source search engine library
+# Xapian (see: http://xapian.org/). See the section "External Indexing and
+# Searching" for details.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHENGINE_URL       = 
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed
+# search data is written to a file for indexing by an external tool. With the
+# SEARCHDATA_FILE tag the name of this file can be specified.
+# The default file is: searchdata.xml.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+SEARCHDATA_FILE        = searchdata.xml
+
+# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the
+# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is
+# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple
+# projects and redirect the results back to the right project.
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTERNAL_SEARCH_ID     = 
+
+# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen
+# projects other than the one defined by this configuration file, but that are
+# all added to the same external search index. Each project needs to have a
+# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of
+# to a relative location where the documentation can be found. The format is:
+# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...
+# This tag requires that the tag SEARCHENGINE is set to YES.
+
+EXTRA_SEARCH_MAPPINGS  = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES doxygen will generate LaTeX output.
+# The default value is: YES.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked.
+#
+# Note that when enabling USE_PDFLATEX this option is only used for generating
+# bitmaps for formulas in the HTML output, but not in the Makefile that is
+# written to the output directory.
+# The default file is: latex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate
+# index for LaTeX.
+# The default file is: makeindex.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES doxygen generates more compact LaTeX
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used by the
+# printer.
+# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x
+# 14 inches) and executive (7.25 x 10.5 inches).
+# The default value is: a4.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PAPER_TYPE             = a4
+
+# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names
+# that should be included in the LaTeX output. To get the times font for
+# instance you can specify
+# EXTRA_PACKAGES=times
+# If left blank no extra packages will be included.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the
+# generated LaTeX document. The header should contain everything until the first
+# chapter. If it is left blank doxygen will generate a standard header. See
+# section "Doxygen usage" for information on how to let doxygen write the
+# default header to a separate file.
+#
+# Note: Only use a user-defined header if you know what you are doing! The
+# following commands have a special meaning inside the header: $title,
+# $datetime, $date, $doxygenversion, $projectname, $projectnumber,
+# $projectbrief, $projectlogo. Doxygen will replace $title with the empy string,
+# for the replacement values of the other commands the user is refered to
+# HTML_HEADER.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HEADER           = 
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the
+# generated LaTeX document. The footer should contain everything after the last
+# chapter. If it is left blank doxygen will generate a standard footer. See
+# LATEX_HEADER for more information on how to generate a default footer and what
+# special commands can be used inside the footer.
+#
+# Note: Only use a user-defined footer if you know what you are doing!
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_FOOTER           = 
+
+# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the LATEX_OUTPUT output
+# directory. Note that the files will be copied as-is; there are no commands or
+# markers available.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_EXTRA_FILES      = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is
+# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will
+# contain links (just like the HTML output) instead of page references. This
+# makes the output suitable for online browsing using a PDF viewer.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+PDF_HYPERLINKS         = YES
+
+# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate
+# the PDF file directly from the LaTeX files. Set this option to YES to get a
+# higher quality PDF documentation.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+USE_PDFLATEX           = YES
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
+# command to the generated LaTeX files. This will instruct LaTeX to keep running
+# if errors occur, instead of asking the user for help. This option is also used
+# when generating formulas in HTML.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BATCHMODE        = NO
+
+# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the
+# index chapters (such as File Index, Compound Index, etc.) in the output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_HIDE_INDICES     = NO
+
+# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source
+# code with syntax highlighting in the LaTeX output.
+#
+# Note that which sources are shown also depends on other settings such as
+# SOURCE_BROWSER.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_SOURCE_CODE      = NO
+
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. See
+# http://en.wikipedia.org/wiki/BibTeX and \cite for more info.
+# The default value is: plain.
+# This tag requires that the tag GENERATE_LATEX is set to YES.
+
+LATEX_BIB_STYLE        = plain
+
+#---------------------------------------------------------------------------
+# Configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES doxygen will generate RTF output. The
+# RTF output is optimized for Word 97 and may not look too pretty with other RTF
+# readers/editors.
+# The default value is: NO.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: rtf.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES doxygen generates more compact RTF
+# documents. This may be useful for small projects and may help to save some
+# trees in general.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will
+# contain hyperlink fields. The RTF file will contain links (just like the HTML
+# output) instead of page references. This makes the output suitable for online
+# browsing using Word or some other Word compatible readers that support those
+# fields.
+#
+# Note: WordPad (write) and others do not support links.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's config
+# file, i.e. a series of assignments. You only have to provide replacements,
+# missing definitions are set to their default value.
+#
+# See also section "Doxygen usage" for information on how to generate the
+# default style sheet that doxygen normally uses.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an RTF document. Syntax is
+# similar to doxygen's config file. A template extensions file can be generated
+# using doxygen -e rtf extensionFile.
+# This tag requires that the tag GENERATE_RTF is set to YES.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES doxygen will generate man pages for
+# classes and files.
+# The default value is: NO.
+
+GENERATE_MAN           = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it. A directory man3 will be created inside the directory specified by
+# MAN_OUTPUT.
+# The default directory is: man.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to the generated
+# man pages. In case the manual section does not start with a number, the number
+# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is
+# optional.
+# The default value is: .3.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_EXTENSION          = .3
+
+# The MAN_SUBDIR tag determines the name of the directory created within
+# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by
+# MAN_EXTENSION with the initial . removed.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_SUBDIR             = 
+
+# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it
+# will generate one additional man file for each entity documented in the real
+# man page(s). These additional files only source the real man page, but without
+# them the man command would be unable to find the correct page.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_MAN is set to YES.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES doxygen will generate an XML file that
+# captures the structure of the code including all documentation.
+# The default value is: NO.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a
+# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of
+# it.
+# The default directory is: xml.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_OUTPUT             = xml
+
+# If the XML_PROGRAMLISTING tag is set to YES doxygen will dump the program
+# listings (including syntax highlighting and cross-referencing information) to
+# the XML output. Note that enabling this will significantly increase the size
+# of the XML output.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_XML is set to YES.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to the DOCBOOK output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_DOCBOOK tag is set to YES doxygen will generate Docbook files
+# that can be used to generate PDF.
+# The default value is: NO.
+
+GENERATE_DOCBOOK       = NO
+
+# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in
+# front of it.
+# The default directory is: docbook.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_OUTPUT         = docbook
+
+# If the DOCBOOK_PROGRAMLISTING tag is set to YES doxygen will include the
+# program listings (including syntax highlighting and cross-referencing
+# information) to the DOCBOOK output. Note that enabling this will significantly
+# increase the size of the DOCBOOK output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_DOCBOOK is set to YES.
+
+DOCBOOK_PROGRAMLISTING = NO
+
+#---------------------------------------------------------------------------
+# Configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES doxygen will generate an AutoGen
+# Definitions (see http://autogen.sf.net) file that captures the structure of
+# the code including all documentation. Note that this feature is still
+# experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# Configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES doxygen will generate a Perl module
+# file that captures the structure of the code including all documentation.
+#
+# Note that this feature is still experimental and incomplete at the moment.
+# The default value is: NO.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES doxygen will generate the necessary
+# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI
+# output from the Perl module output.
+# The default value is: NO.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be nicely
+# formatted so it can be parsed by a human reader. This is useful if you want to
+# understand what is going on. On the other hand, if this tag is set to NO the
+# size of the Perl module output will be much smaller and Perl will parse it
+# just the same.
+# The default value is: YES.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file are
+# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful
+# so different doxyrules.make files included by the same Makefile don't
+# overwrite each other's variables.
+# This tag requires that the tag GENERATE_PERLMOD is set to YES.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES doxygen will evaluate all
+# C-preprocessor directives found in the sources and include files.
+# The default value is: YES.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES doxygen will expand all macro names
+# in the source code. If set to NO only conditional compilation will be
+# performed. Macro expansion can be done in a controlled way by setting
+# EXPAND_ONLY_PREDEF to YES.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then
+# the macro expansion is limited to the macros specified with the PREDEFINED and
+# EXPAND_AS_DEFINED tags.
+# The default value is: NO.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES the includes files in the
+# INCLUDE_PATH will be searched if a #include is found.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by the
+# preprocessor.
+# This tag requires that the tag SEARCH_INCLUDES is set to YES.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will be
+# used.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that are
+# defined before the preprocessor is started (similar to the -D option of e.g.
+# gcc). The argument of the tag is a list of macros of the form: name or
+# name=definition (no spaces). If the definition and the "=" are omitted, "=1"
+# is assumed. To prevent a macro definition from being undefined via #undef or
+# recursively expanded use the := operator instead of the = operator.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
+# tag can be used to specify a list of macro names that should be expanded. The
+# macro definition that is found in the sources will be used. Use the PREDEFINED
+# tag if you want to use a different macro definition that overrules the
+# definition found in the source code.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will
+# remove all references to function-like macros that are alone on a line, have
+# an all uppercase name, and do not end with a semicolon. Such function macros
+# are typically used for boiler-plate code, and will confuse the parser if not
+# removed.
+# The default value is: YES.
+# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration options related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES tag can be used to specify one or more tag files. For each tag
+# file the location of the external documentation should be added. The format of
+# a tag file without this location is as follows:
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where loc1 and loc2 can be relative or absolute paths or URLs. See the
+# section "Linking to external documentation" for more information about the use
+# of tag files.
+# Note: Each tag file must have a unique name (where the name does NOT include
+# the path). If a tag file is not located in the directory in which doxygen is
+# run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create a
+# tag file that is based on the input files it reads. See section "Linking to
+# external documentation" for more information about the usage of tag files.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external class will be listed in the
+# class index. If set to NO only the inherited external classes will be listed.
+# The default value is: NO.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed in
+# the modules index. If set to NO, only the current project's groups will be
+# listed.
+# The default value is: YES.
+
+EXTERNAL_GROUPS        = YES
+
+# If the EXTERNAL_PAGES tag is set to YES all external pages will be listed in
+# the related pages index. If set to NO, only the current project's pages will
+# be listed.
+# The default value is: YES.
+
+EXTERNAL_PAGES         = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of 'which perl').
+# The default file (with absolute path) is: /usr/bin/perl.
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES doxygen will generate a class diagram
+# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to
+# NO turns the diagrams off. Note that this option also works with HAVE_DOT
+# disabled, but it is recommended to install and use dot, since it yields more
+# powerful graphs.
+# The default value is: YES.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see:
+# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH            = 
+
+# You can include diagrams made with dia in doxygen documentation. Doxygen will
+# then run dia to produce the diagram and insert it in the documentation. The
+# DIA_PATH tag allows you to specify the directory where the dia binary resides.
+# If left empty dia is assumed to be found in the default search path.
+
+DIA_PATH               = 
+
+# If set to YES, the inheritance and collaboration graphs will hide inheritance
+# and usage relations if the target is undocumented or is not a class.
+# The default value is: YES.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz (see:
+# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
+# Bell Labs. The other options in this section have no effect if this option is
+# set to NO
+# The default value is: YES.
+
+HAVE_DOT               = YES
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed
+# to run in parallel. When set to 0 doxygen will base this on the number of
+# processors available in the system. You can set it explicitly to a value
+# larger than 0 to get control over the balance between CPU load and processing
+# speed.
+# Minimum value: 0, maximum value: 32, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_NUM_THREADS        = 0
+
+# When you want a differently looking font in the dot files that doxygen
+# generates you can specify the font name using DOT_FONTNAME. You need to make
+# sure dot is able to find the font, which can be done by putting it in a
+# standard location or by setting the DOTFONTPATH environment variable or by
+# setting DOT_FONTPATH to the directory containing the font.
+# The default value is: Helvetica.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTNAME           = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
+# dot graphs.
+# Minimum value: 4, maximum value: 24, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTSIZE           = 10
+
+# By default doxygen will tell dot to use the default font as specified with
+# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
+# the path where dot can find it using this tag.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_FONTPATH           = 
+
+# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for
+# each documented class showing the direct and indirect inheritance relations.
+# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
+# graph for each documented class showing the direct and indirect implementation
+# dependencies (inheritance, containment, and class references variables) of the
+# class with other documented classes.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
+# groups, showing the direct groups dependencies.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LOOK               = NO
+
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside the
+# class node. If there are many fields or methods and many nodes the graph may
+# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the
+# number of items for each type to make the size more manageable. Set this to 0
+# for no limit. Note that the threshold may be exceeded by 50% before the limit
+# is enforced. So when you set the threshold to 10, up to 15 fields may appear,
+# but if the number exceeds 15, the total amount of fields shown is limited to
+# 10.
+# Minimum value: 0, maximum value: 100, default value: 10.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+UML_LIMIT_NUM_FIELDS   = 10
+
+# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and
+# collaboration graphs will show the relations between templates and their
+# instances.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
+# YES then doxygen will generate a graph for each documented file showing the
+# direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDE_GRAPH          = YES
+
+# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
+# set to YES then doxygen will generate a graph for each documented file showing
+# the direct and indirect include dependencies of the file with other documented
+# files.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH tag is set to YES then doxygen will generate a call
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable call graphs for selected
+# functions only using the \callgraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller
+# dependency graph for every global function or class method.
+#
+# Note that enabling this option will significantly increase the time of a run.
+# So in most cases it will be better to enable caller graphs for selected
+# functions only using the \callergraph command.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical
+# hierarchy of all classes instead of a textual one.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
+# dependencies a directory has on other directories in a graphical way. The
+# dependency relations are determined by the #include relations between the
+# files in the directories.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot.
+# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
+# to make the SVG files visible in IE 9+ (other browsers do not have this
+# requirement).
+# Possible values are: png, png:cairo, png:cairo:cairo, png:cairo:gd, png:gd,
+# png:gd:gd, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd, gif, gif:cairo,
+# gif:cairo:gd, gif:gd, gif:gd:gd and svg.
+# The default value is: png.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_IMAGE_FORMAT       = png
+
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+#
+# Note that this requires a modern browser other than Internet Explorer. Tested
+# and working are Firefox, Chrome, Safari, and Opera.
+# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make
+# the SVG files visible. Older versions of IE do not have SVG support.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+INTERACTIVE_SVG        = NO
+
+# The DOT_PATH tag can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the \dotfile
+# command).
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOTFILE_DIRS           = 
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the \mscfile
+# command).
+
+MSCFILE_DIRS           = 
+
+# The DIAFILE_DIRS tag can be used to specify one or more directories that
+# contain dia files that are included in the documentation (see the \diafile
+# command).
+
+DIAFILE_DIRS           = 
+
+# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the
+# path where java can find the plantuml.jar file. If left blank, it is assumed
+# PlantUML is not used or called during a preprocessing step. Doxygen will
+# generate a warning when it encounters a \startuml command in this case and
+# will not generate output for the diagram.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+PLANTUML_JAR_PATH      = 
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes
+# that will be shown in the graph. If the number of nodes in a graph becomes
+# larger than this value, doxygen will truncate the graph, which is visualized
+# by representing a node as a red box. Note that doxygen if the number of direct
+# children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that
+# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+# Minimum value: 0, maximum value: 10000, default value: 50.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs
+# generated by dot. A depth value of 3 means that only nodes reachable from the
+# root by following a path via at most 3 edges will be shown. Nodes that lay
+# further from the root node will be omitted. Note that setting this option to 1
+# or 2 may greatly reduce the computation time needed for large code bases. Also
+# note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+# Minimum value: 0, maximum value: 1000, default value: 0.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not seem
+# to support this out of the box.
+#
+# Warning: Depending on the platform used, enabling this option may lead to
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
+# read).
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10) support
+# this, this feature is disabled by default.
+# The default value is: NO.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page
+# explaining the meaning of the various boxes and arrows in the dot generated
+# graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES doxygen will remove the intermediate dot
+# files that are used to generate the various graphs.
+# The default value is: YES.
+# This tag requires that the tag HAVE_DOT is set to YES.
+
+DOT_CLEANUP            = YES
diff --git a/lib/i3ipcpp/LICENSE b/lib/i3ipcpp/LICENSE
new file mode 100644 (file)
index 0000000..069d363
--- /dev/null
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Original work Copyright (c) 2015, Sergey Naumov
+Modified work Copyright 2018, Michael Carlberg (jaagr) and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/i3ipcpp/README.md b/lib/i3ipcpp/README.md
new file mode 100644 (file)
index 0000000..2c5cc90
--- /dev/null
@@ -0,0 +1,129 @@
+[![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org)
+
+i3ipc++
+=======
+An implementation of i3 IPC in C++11.
+
+## Requirements
+
+* cmake (>= 3.0)
+* C++11 compiler
+* sigc++ 2.0
+* jsoncpp
+
+## Using
+Yet the only way of using is to add this repo as a submodule
+
+```bash
+git submodule add https://github.com/drmgc/i3ipcpp.git ./i3ipc++/
+```
+
+Then just type this in your `CMakeLists.txt`:
+
+```cmake
+...
+add_subdirectory(i3ipc++)
+
+include_directories(${I3IPCpp_INCLUDE_DIRS})
+link_directories(${I3IPCpp_LIBRARY_DIRS})
+...
+```
+
+And then just link:
+
+```cmake
+...
+target_link_libraries(someapp ${I3IPCpp_LIBRARIES})
+...
+```
+
+## Usage
+
+See also examples in `examples/` directory.
+
+### Connecting
+
+```c++
+#include <i3ipc++/ipc.hpp>
+
+i3ipc::connection  conn;
+```
+
+The connection will be established automaticly.
+
+### Event handling
+
+First of all you need to declare the events you want to handle. As example we want to handle an binding and workspace events:
+```c++
+conn.subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_BINDING);
+```
+
+Then we need to connect to the signal handlers:
+```c++
+// Handler of WORKSPACE EVENT
+conn.signal_workspace_event.connect([](const i3ipc::workspace_event_t&  ev) {
+       std::cout << "workspace_event: " << (char)ev.type << std::endl;
+       if (ev.current) {
+               std::cout << "\tSwitched to #" << ev.current->num << " - \"" << ev.current->name << '"' << std::endl;
+       }
+});
+
+// Handler of binding event
+conn.signal_binding_event.connect([](const i3ipc::binding_t&  b) {
+       std::cout << "binding_event:" << std::endl
+               << "\tcommand = \"" << b.command << '"' << std::endl
+               << "\tinput_code = " << b.input_code << std::endl
+               << "\tsymbol = " << b.symbol << std::endl
+               << "\tinput_type = " << static_cast<char>(b.input_type) << std::endl
+               << "\tevent_state_mask =" << std::endl;
+       for (const std::string& s : b.event_state_mask) {
+               std::cout << "\t\t\"" << s << '"' << std::endl;
+       }
+});
+```
+
+Then we starting the event-handling loop
+```c++
+while (true) {
+       conn.handle_event();
+}
+```
+
+**Note:** If you want to interract with event_socket or just want to prepare manually you can call `conn.connect_event_socket()` (if you want to reconnect `conn.connect_event_socket(true)`), but if by default `connect_event_socket()` called on first `handle_event()` call.
+
+### Requesting
+
+Also you can request some data from i3, as example barconfigs:
+```c++
+std::vector<std::string>  bar_configs = conn.get_bar_configs_list();
+```
+
+And then do with them something:
+```c++
+for (auto&  name : bar_configs) {
+       std::shared_ptr<i3ipc::bar_config_t>  bc = conn.get_bar_config(name);
+
+       // ... handling
+}
+```
+
+### Sending commands
+
+And, of course, you can command i3:
+```c++
+if (!conn.send_command("exit")) {
+       throw std::string("Failed to exit via command");
+}
+```
+
+## Version i3 support
+It is written according to the *current* specification, so some of new features in IPC can be not-implemented. If there is some of them, please notice at issues page.
+
+## Documentation
+The latest documentation you can find [**here**](http://drmgc.github.io/docs/api-ref/i3ipc++/latest/)
+
+## Licensing
+This library is licensed under under the MIT license, but it also uses [`JsonCpp`](https://github.com/open-source-parsers/jsoncpp) (*only for parsing i3's replies*) and my header-only library [`auss`](https://github.com/drmgc/auss)
+
+## Backward compatibility note
+While version is `0.x` there can be a lack of backward compatibility between minor releases, please see release notes.
diff --git a/lib/i3ipcpp/examples/CMakeLists.txt b/lib/i3ipcpp/examples/CMakeLists.txt
new file mode 100644 (file)
index 0000000..d1a8285
--- /dev/null
@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 3.0)
+project(i3ipc++-examples)
+
+include_directories(
+       ${I3IPCpp_INCLUDE_DIRS}
+)
+
+link_directories(
+       ${I3IPCpp_LIBRARY_DIRS}
+)
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -Wall -Wextra -Wno-unused-parameter")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g3 -DDEBUG")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
+
+add_executable(workspaces workspaces.cpp)
+target_link_libraries(workspaces ${I3IPCpp_LIBRARIES})
+
+add_executable(events events.cpp)
+target_link_libraries(events ${I3IPCpp_LIBRARIES})
+
+add_executable(bar-configs bar-configs.cpp)
+target_link_libraries(bar-configs ${I3IPCpp_LIBRARIES})
diff --git a/lib/i3ipcpp/examples/bar-configs.cpp b/lib/i3ipcpp/examples/bar-configs.cpp
new file mode 100644 (file)
index 0000000..f61ab04
--- /dev/null
@@ -0,0 +1,43 @@
+/**
+ * This program dumps all available barconfigs
+ */
+
+#include <iostream>
+
+#include <i3ipc++/ipc.hpp>
+
+
+static void  dump_bar_config(const i3ipc::bar_config_t&  bc) {
+       std::cout << '"' << bc.id << '"' << std::endl
+               << "\tmode = " << static_cast<char>(bc.mode) << std::endl
+               << "\tposition = " << static_cast<char>(bc.position) << std::endl
+               << "\tstatus_command = \"" << bc.status_command << '"' << std::endl
+               << "\tfont = \"" << bc.font << '"' << std::endl
+               << "\tworkspace_buttons = " << (bc.workspace_buttons ? "true" : "false") << std::endl
+               << "\tbinding_mode_indicator = " << (bc.binding_mode_indicator ? "true" : "false") << std::endl
+               << "\tverbose = " << (bc.verbose ? "true" : "false") << std::endl
+               << "\tcolors:" << std::endl;
+
+       std::cout << std::hex;
+       for (auto  iter = bc.colors.begin(); iter != bc.colors.end(); iter++) {
+               std::cout << "\t\t\"" << iter->first << "\" = #" << iter->second << std::endl;
+       }
+       std::cout << std::dec;
+}
+
+
+int  main() {
+       // First of all needs to create a connection
+       i3ipc::connection  conn;
+
+       // Then request a list of barconfigs
+       std::vector<std::string>  bar_configs = conn.get_bar_configs_list();
+
+       // And dump 'em all!!!!!
+       for (auto&  name : bar_configs) {
+               std::shared_ptr<i3ipc::bar_config_t>  bc = conn.get_bar_config(name);
+               dump_bar_config(*bc);
+       }
+
+       return 0;
+}
diff --git a/lib/i3ipcpp/examples/events.cpp b/lib/i3ipcpp/examples/events.cpp
new file mode 100644 (file)
index 0000000..7509fe4
--- /dev/null
@@ -0,0 +1,46 @@
+/**
+ * This programs handle events and dump them to console
+ */
+
+#include <iostream>
+
+#include <i3ipc++/ipc.hpp>
+
+
+int  main() {
+       // First of all we need to connect to an i3 process
+       i3ipc::connection  conn;
+
+       // Then we subscribing on events (see i3ipc::EVENT_TYPE)
+       conn.subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_WINDOW | i3ipc::ET_BINDING);
+
+       // Handler of workspace_event
+       conn.signal_workspace_event.connect([](const i3ipc::workspace_event_t&  ev) {
+               std::cout << "workspace_event: " << (char)ev.type << std::endl;
+       });
+
+       // Handler of window_event
+       conn.signal_window_event.connect([](const i3ipc::window_event_t&  ev) {
+               std::cout << "window_event: " << (char)ev.type << std::endl;
+       });
+
+       // Handler of binding event
+       conn.signal_binding_event.connect([](const i3ipc::binding_t&  b) {
+               std::cout << "binding_event:" << std::endl
+                       << "\tcommand = \"" << b.command << '"' << std::endl
+                       << "\tinput_code = " << b.input_code << std::endl
+                       << "\tsymbol = " << b.symbol << std::endl
+                       << "\tinput_type = " << static_cast<char>(b.input_type) << std::endl
+                       << "\tevent_state_mask =" << std::endl;
+               for (const std::string& s : b.event_state_mask) {
+                       std::cout << "\t\t\"" << s << '"' << std::endl;
+               }
+       });
+
+       // And starting an event-handling loop
+       while (true) {
+               conn.handle_event();
+       }
+
+       return 0;
+}
diff --git a/lib/i3ipcpp/examples/workspaces.cpp b/lib/i3ipcpp/examples/workspaces.cpp
new file mode 100644 (file)
index 0000000..58ea242
--- /dev/null
@@ -0,0 +1,64 @@
+/**
+ * This program dumps a tree of windows and workspaces to console
+ */
+
+#include <iostream>
+
+#include <i3ipc++/ipc.hpp>
+
+
+/**
+ * Reqursively dump containers of a tree
+ * @param  c      a root container
+ * @param  prefix an alignment
+ */
+void  dump_tree_container(const i3ipc::container_t&  c, std::string&  prefix) {
+       std::cout << prefix << "ID: " << c.id << " (i3's; X11's - " << c.xwindow_id << ")" << std::endl;
+       prefix.push_back('\t');
+       std::cout << prefix << "name = \"" << c.name << "\"" << std::endl;
+       std::cout << prefix << "type = \"" << c.type << "\"" << std::endl;
+       std::cout << prefix << "border = \"" << c.border_raw << "\"" << std::endl;
+       std::cout << prefix << "current_border_width = " << c.current_border_width << std::endl;
+       std::cout << prefix << "layout = \"" << c.layout_raw << "\"" << std::endl;
+       std::cout << prefix << "percent = " << c.percent << std::endl;
+       if (c.urgent) {
+               std::cout << prefix << "urgent" << std::endl;
+       }
+       if (c.focused) {
+               std::cout << prefix << "focused" << std::endl;
+       }
+       prefix.push_back('\t');
+       for (auto&  n : c.nodes) {
+               dump_tree_container(*n, prefix);
+       }
+       prefix.pop_back();
+       prefix.pop_back();
+}
+
+
+int  main() {
+       // First of all needs to create a connection
+       i3ipc::connection  conn;
+
+       // Then we dump workspaces
+       for (auto&  w : conn.get_workspaces()) {
+               std::cout << '#' << std::hex << w->num << std::dec
+                       << "\n\tName: " << w->name
+                       << "\n\tVisible: " << w->visible
+                       << "\n\tFocused: " << w->focused
+                       << "\n\tUrgent: " << w->urgent
+                       << "\n\tRect: "
+                       << "\n\t\tX: " << w->rect.x
+                       << "\n\t\tY: " << w->rect.y
+                       << "\n\t\tWidth: " << w->rect.width
+                       << "\n\t\tHeight: " << w->rect.height
+                       << "\n\tOutput: " << w->output
+                       << std::endl;
+       }
+
+       // Then we dump the tree
+       std::string  prefix_buf;
+       dump_tree_container(*conn.get_tree(), prefix_buf);
+
+       return 0;
+}
diff --git a/lib/i3ipcpp/include/i3ipc++/ipc-util.hpp b/lib/i3ipcpp/include/i3ipc++/ipc-util.hpp
new file mode 100644 (file)
index 0000000..c4894a6
--- /dev/null
@@ -0,0 +1,169 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <memory>
+#include <stdexcept>
+
+// extern "C" {
+// #include <i3/ipc.h>
+// }
+
+namespace i3ipc {
+
+/** @defgroup i3ipc_util i3 IPC internal utilities
+ * Stuff for internal usage in I3Connection 
+ * @{
+ */
+
+// extern "C" {
+
+/**
+ * i3 IPC header
+ */
+struct header_t {
+       /* 6 = strlen(I3_IPC_MAGIC) */
+       char magic[6]; ///< Magic string @see I3_IPC_MAGIC
+       uint32_t size; ///< Size of payload
+       uint32_t type; ///< Message type
+}  __attribute__ ((packed));
+
+
+/**
+ * @brief Base class of i3 IPC errors
+ */
+class ipc_error : public std::runtime_error { using std::runtime_error::runtime_error; };
+
+/**
+ * @brief Something wrong in message header (wrong magic number, message type etc.)
+ */
+class invalid_header_error : public ipc_error { using ipc_error::ipc_error; };
+
+/**
+ * @brief Socket return EOF, but expected a data
+ */
+class eof_error : public ipc_error { using ipc_error::ipc_error; };
+
+/**
+ * @brief If something wrong in a payload of i3's reply
+ */
+class invalid_reply_payload_error : public ipc_error { using ipc_error::ipc_error; };
+
+/**
+ * @brief If any error occured, while using C-functions
+ */
+class errno_error : public ipc_error {
+public:
+       errno_error();
+       errno_error(const std::string&  msg);
+};
+
+
+/**
+ * @brief Messages (requests), that can be sended from the client
+ */
+enum class ClientMessageType : uint32_t {
+       COMMAND = 0,
+       GET_WORKSPACES = 1,
+       SUBSCRIBE = 2,
+       GET_OUTPUTS = 3,
+       GET_TREE = 4,
+       GET_MARKS = 5,
+       GET_BAR_CONFIG = 6,
+       GET_VERSION = 7,
+};
+
+
+/**
+ * @brief Replies, that can be sended from the i3 to the client
+ */
+enum class ReplyType : uint32_t {
+       COMMAND = 0,
+       WORKSPACES = 1,
+       SUBSCRIBE = 2,
+       OUTPUTS = 3,
+       TREE = 4,
+       MARKS = 5,
+       BAR_CONFIG = 6,
+       VERSION = 7,
+};
+
+
+/**
+ * @brief i3 IPC message buffer
+ */
+struct buf_t {
+       uint32_t  size; ///< @brief Size of whole buffer
+       uint8_t*  data; ///< @brief Pointer to the message
+
+       /**
+        * @brief i3 IPC message header
+        *
+        * Pointing on the begining
+        */
+       header_t*  header;
+
+       /**
+        * @brief Message payload
+        *
+        * Pointing on the byte after the header
+        */
+       char*  payload;
+
+       buf_t(uint32_t  payload_size);
+       ~buf_t();
+
+       /**
+        * @brief Resize payload to the payload_size in the header
+        */
+       void  realloc_payload_to_header();
+};
+
+/**
+ * Connect to the i3 socket
+ * @param  socket_path a socket path
+ * @return             socket id
+ */
+int32_t  i3_connect(const std::string&  socket_path);
+
+/**
+ * @brief Close the connection
+ * @param  sockfd socket
+ */
+void  i3_disconnect(const int32_t  sockfd);
+
+/**
+ * @brief Send message to the socket
+ * @param  sockfd a socket
+ * @param  buff   a message
+ */
+void   i3_send(const int32_t  sockfd, const buf_t&  buff);
+
+/**
+ * @brief Recive a message from i3
+ * @param  sockfd  a socket
+ * @return  a buffer of the message
+ */
+std::shared_ptr<buf_t>   i3_recv(const int32_t  sockfd);
+
+/**
+ * @brief Pack a buffer of message
+ */
+std::shared_ptr<buf_t>  i3_pack(const ClientMessageType  type, const std::string&  payload);
+
+/**
+ * @brief Pack, send a message and receiv a reply
+ *
+ * Almost same to:
+ * @code{.cpp}
+ * i3_send(sockfd, i3_pack(type, payload));
+ * auto  reply = i3_recv(sockfd);
+ * @endcode
+ */
+std::shared_ptr<buf_t>  i3_msg(const int32_t  sockfd, const ClientMessageType  type, const std::string&  payload = std::string());
+
+/**
+ * @}
+ */
+
+}
diff --git a/lib/i3ipcpp/include/i3ipc++/ipc.hpp b/lib/i3ipcpp/include/i3ipc++/ipc.hpp
new file mode 100644 (file)
index 0000000..92745d3
--- /dev/null
@@ -0,0 +1,377 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+#include <memory>
+#include <vector>
+#include <map>
+#include <list>
+#include <functional>
+
+extern "C" {
+#include <i3/ipc.h>
+}
+
+/**
+ * @addtogroup i3ipc i3 IPC C++ binding
+ * @{
+ */
+namespace i3ipc {
+
+/**
+ * Get path to the i3 IPC socket
+ * @return Path to a socket
+ */
+std::string  get_socketpath();
+
+/**
+ * Primitive of rectangle
+ */
+struct rect_t {
+       uint32_t  x; ///< Position on X axis
+       uint32_t  y; ///< Position on Y axis
+       uint32_t  width; ///< Width of rectangle
+       uint32_t  height; ///< Height of rectangle
+};
+
+/**
+ * i3's workspace
+ */
+struct workspace_t {
+       int  num; ///< Index of the worksapce
+       std::string  name; ///< Name of the workspace
+       bool  visible; ///< Is the workspace visible
+       bool  focused; ///< Is the workspace is currently focused
+       bool  urgent; ///< Is the workspace is urgent
+       rect_t  rect; ///< A size of the workspace
+       std::string  output; ///< An output of the workspace
+};
+
+/**
+ * i3's output
+ */
+struct output_t {
+       std::string  name; ///< Name of the output
+       bool  active; ///< Is the output currently active
+       std::string  current_workspace; ///< Name of current workspace
+       rect_t  rect; ///< Size of the output
+};
+
+/**
+ * Version of i3
+ */
+struct version_t {
+       std::string  human_readable; ///< Human redable version string
+       std::string  loaded_config_file_name; ///< Path to current config of i3
+       uint32_t  major; ///< Major version of i3
+       uint32_t  minor; ///< Minor version of i3
+       uint32_t  patch; ///< Patch number of i3
+};
+
+
+/**
+ * Types of the events of i3
+ */
+enum EventType {
+       ET_WORKSPACE = (1 << 0), ///< Workspace event
+       ET_OUTPUT = (1 << 1), ///< Output event
+       ET_MODE = (1 << 2), ///< Output mode event
+       ET_WINDOW = (1 << 3), ///< Window event
+       ET_BARCONFIG_UPDATE = (1 << 4), ///< Bar config update event @attention Yet is not implemented as signal in connection
+       ET_BINDING = (1 << 5), ///< Binding event
+};
+
+/**
+ * Types of workspace events
+ */
+enum class WorkspaceEventType : char {
+       FOCUS = 'f', ///< Focused
+       INIT = 'i', ///< Initialized
+       EMPTY = 'e', ///< Became empty
+       URGENT = 'u', ///< Became urgent
+       RENAME = 'r', ///< Renamed
+       RELOAD = 'l', ///< Reloaded
+       RESTORED = 's', ///< Restored
+};
+
+/**
+ * Types of window events
+ */
+enum class WindowEventType : char {
+       NEW = 'n', ///< Window created
+       CLOSE = 'c', ///< Window closed
+       FOCUS = 'f', ///< Window got focus
+       TITLE = 't', ///< Title of window has been changed
+       FULLSCREEN_MODE = 'F', ///< Window toggled to fullscreen mode
+       MOVE = 'M', ///< Window moved
+       FLOATING = '_', ///< Window toggled floating mode
+       URGENT = 'u', ///< Window became urgent
+};
+
+
+/**
+ * A style of a container's border
+ */
+enum class BorderStyle : char {
+       UNKNOWN = '?', //< If got an unknown border style in reply
+       NONE = 'N',
+       NORMAL = 'n',
+       PIXEL = 'P',
+       ONE_PIXEL = '1',
+};
+
+
+/**
+ * A type of a container's layout
+ */
+enum class ContainerLayout : char {
+       UNKNOWN = '?', //< If got an unknown border style in reply
+       SPLIT_H = 'h',
+       SPLIT_V = 'v',
+       STACKED = 's',
+       TABBED = 't',
+       DOCKAREA = 'd',
+       OUTPUT = 'o',
+};
+
+
+/**
+ * A type of the input of bindings
+ */
+enum class InputType : char {
+       UNKNOWN = '?', //< If got an unknown input_type in binding_event
+       KEYBOARD = 'k',
+       MOUSE = 'm',
+};
+
+
+/**
+ * A mode of a bar
+ */
+enum class BarMode : char {
+       UNKNOWN = '?',
+       DOCK = 'd', ///< The bar sets the dock window type
+       HIDE = 'h', ///< The bar does not show unless a specific key is pressed
+};
+
+
+/**
+ * A position (of a bar?)
+ */
+enum class Position : char {
+       UNKNOWN = '?',
+       TOP = 't',
+       BOTTOM = 'b',
+};
+
+
+/**
+ * A node of tree of windows
+ */
+struct container_t {
+       uint64_t  id; ///< The internal ID (actually a C pointer value) of this container. Do not make any assumptions about it. You can use it to (re-)identify and address containers when talking to i3
+       uint64_t  xwindow_id; ///< The X11 window ID of the actual client window inside this container. This field is set to null for split containers or otherwise empty containers. This ID corresponds to what xwininfo(1) and other X11-related tools display (usually in hex)
+       std::string  name; ///< The internal name of this container. For all containers which are part of the tree structure down to the workspace contents, this is set to a nice human-readable name of the container. For containers that have an X11 window, the content is the title (_NET_WM_NAME property) of that window. For all other containers, the content is not defined (yet)
+       std::string  type; ///< Type of this container
+       BorderStyle  border; ///< A style of the container's border
+       std::string  border_raw; ///< A "border" field of TREE reply. NOT empty only if border equals BorderStyle::UNKNOWN
+       uint32_t  current_border_width; ///< Number of pixels of the border width
+       ContainerLayout  layout; ///< A type of the container's layout
+       std::string  layout_raw; ///< A "layout" field of TREE reply. NOT empty only if layout equals ContainerLayout::UNKNOWN
+       float  percent; ///< The percentage which this container takes in its parent. A value of < 0 means that the percent property does not make sense for this container, for example for the root container.
+       rect_t  rect; ///< The absolute display coordinates for this container
+       rect_t  window_rect; ///< The coordinates of the actual client window inside its container. These coordinates are relative to the container and do not include the window decoration (which is actually rendered on the parent container)
+       rect_t  deco_rect; ///< The coordinates of the window decoration inside its container. These coordinates are relative to the container and do not include the actual client window
+       rect_t  geometry; ///< The original geometry the window specified when i3 mapped it. Used when switching a window to floating mode, for example
+       bool  urgent;
+       bool  focused;
+
+       std::list< std::shared_ptr<container_t> >  nodes;
+};
+
+
+/**
+ * A workspace event
+ */
+struct workspace_event_t {
+       WorkspaceEventType  type;
+       std::shared_ptr<workspace_t>  current; ///< Current focused workspace
+       std::shared_ptr<workspace_t>  old; ///< Old (previous) workspace @note With some WindowEventType could be null
+};
+
+
+/**
+ * A window event
+ */
+struct window_event_t {
+       WindowEventType  type;
+       std::shared_ptr<container_t>  container; ///< A container event associated with @note With some WindowEventType could be null
+};
+
+
+/**
+ * A binding
+ */
+struct binding_t {
+       std::string  command; ///< The i3 command that is configured to run for this binding
+       std::vector<std::string>  event_state_mask; ///< The group and modifier keys that were configured with this binding
+       int32_t  input_code; ///< If the binding was configured with bindcode, this will be the key code that was given for the binding. If the binding is a mouse binding, it will be the number of the mouse button that was pressed. Otherwise it will be 0
+       std::string  symbol; ///< If this is a keyboard binding that was configured with bindsym, this field will contain the given symbol. Otherwise it will be null
+       InputType  input_type;
+};
+
+
+/**
+ * A mode
+ */
+struct mode_t {
+       std::string  change; ///< The current mode in use
+       bool pango_markup; ///< Should pango markup be used for displaying this mode
+};
+
+
+/**
+ * A bar configuration
+ */
+struct bar_config_t {
+       std::string  id; ///< The ID for this bar. Included in case you request multiple configurations and want to differentiate the different replies.
+       BarMode  mode;
+       Position  position;
+       std::string  status_command; ///< Command which will be run to generate a statusline. Each line on stdout of this command will be displayed in the bar. At the moment, no formatting is supported
+       std::string  font; ///< The font to use for text on the bar
+       bool  workspace_buttons; ///< Display workspace buttons or not? Defaults to true.
+       bool  binding_mode_indicator; ///< Display the mode indicator or not? Defaults to true.
+       bool  verbose; ///< Should the bar enable verbose output for debugging? Defaults to false.
+       std::map<std::string, uint32_t>  colors; ///< Contains key/value pairs of colors. Each value is a color code in format 0xRRGGBB
+};
+
+
+struct buf_t;
+/**
+ * Connection to the i3
+ */
+class connection {
+public:
+       /**
+        * Connect to the i3
+        * @param  socket_path path to a i3 IPC socket
+        */
+       connection(const std::string&  socket_path = get_socketpath());
+       ~connection();
+
+       /**
+        * Send a command to i3
+        * @param  command command
+        * @return         Is command successfully executed
+        */
+       bool  send_command(const std::string&  command) const;
+
+       /**
+        * Request a list of workspaces
+        * @return List of workspaces
+        */
+       std::vector< std::shared_ptr<workspace_t> >  get_workspaces() const;
+
+       /**
+        * Request a list of outputs
+        * @return List of outputs
+        */
+       std::vector< std::shared_ptr<output_t> >  get_outputs() const;
+
+       /**
+        * Request a version of i3
+        * @return Version of i3
+        */
+       version_t  get_version() const;
+
+       /**
+        * Request a tree of windows
+        * @return A root container
+        */
+       std::shared_ptr<container_t>  get_tree() const;
+
+       /**
+        * Request a list of names of available barconfigs
+        * @return A list of names of barconfigs
+        */
+       std::vector<std::string>  get_bar_configs_list() const;
+
+       /**
+        * Request a barconfig
+        * @param  name  name of barconfig
+        * @return The barconfig
+        */
+       std::shared_ptr<bar_config_t>  get_bar_config(const std::string&  name) const;
+
+       /**
+        * Subscribe on an events of i3
+        *
+        * If connection isn't handling events at the moment, event numer will be added to subscription list.
+        * Else will also send subscripe request to i3
+        *
+        * Example:
+        * @code{.cpp}
+        * connection  conn;
+        * conn.subscribe(i3ipc::ipc::ET_WORKSPACE | i3ipc::ipc::ET_WINDOW);
+        * @endcode
+        *
+        * @param  events event type (EventType enum)
+        * @return        Is successfully subscribed. If connection isn't handling events at the moment, then always true.
+        */
+       bool  subscribe(const int32_t  events);
+
+       /**
+        * Handle an event from i3
+        * @note Used only in main()
+        */
+       bool handle_event();
+
+       /**
+        * Get the fd of the main socket
+        * @return the file descriptor of the main socket.
+        */
+       int32_t get_main_socket_fd();
+
+       /**
+        * Get the fd of the event socket
+        * @return the file descriptor of the event socket.
+        */
+       int32_t get_event_socket_fd();
+
+       /**
+        * Connect the event socket to IPC
+        * @param  reconnect if true the event socket will be disconnected and connected again
+        * @note Automaticly called, when calling handle_event();
+        */
+       void  connect_event_socket(const bool  reconnect = false);
+
+       /**
+        * Disconnect the event socket
+        */
+       void  disconnect_event_socket();
+
+       std::function<void(const workspace_event_t&)> on_workspace_event; ///< Workspace event signal
+       std::function<void()> on_output_event; ///< Output event signal
+       std::function<void(const mode_t&)> on_mode_event; ///< Output mode event signal
+       std::function<void(const window_event_t&)> on_window_event; ///< Window event signal
+       std::function<void(const bar_config_t&)> on_barconfig_update_event; ///< Barconfig update event signal
+       std::function<void(const binding_t&)> on_binding_event; ///< Binding event signal
+       std::function<void(EventType, const std::shared_ptr<const buf_t>&)> on_event; ///< i3 event signal @note Default handler routes event to signal according to type
+private:
+       const int32_t  m_main_socket;
+       int32_t  m_event_socket;
+       int32_t  m_subscriptions;
+       const std::string  m_socket_path;
+};
+
+/**
+ * Get version of i3ipc++
+ * @return the version of i3ipc++
+ */
+const version_t&  get_version();
+
+}
+
+/**
+ * @}
+ */
diff --git a/lib/i3ipcpp/include/i3ipc++/log.hpp b/lib/i3ipcpp/include/i3ipc++/log.hpp
new file mode 100644 (file)
index 0000000..9e0aefc
--- /dev/null
@@ -0,0 +1,97 @@
+#pragma once
+
+#include <ostream>
+#include <vector>
+#include <auss.hpp>
+
+/**
+ * @addtogroup logging Logging
+ * @{
+ */
+
+namespace i3ipc {
+
+/**
+ * @brief Common logging outputs
+ */
+extern std::vector<std::ostream*>  g_logging_outs;
+
+/**
+ * @brief Logging outputs for error messages
+ */
+extern std::vector<std::ostream*>  g_logging_err_outs;
+
+/**
+ * @brief Put to a logging outputs some dtat
+ * @param  data  data, that you want to put to the logging outputs
+ * @param  err  is your information is error report or something that must be putted to the error logging outputs
+ */
+template<typename T>
+inline void  log(const T&  data, const bool  err=false) {
+       for (auto  out : (err ? g_logging_err_outs : g_logging_outs)) {
+               *out << data << std::endl;
+       }
+}
+
+template<>
+inline void  log(const auss_t&  data, const bool  err) {
+       log(data.to_string());
+}
+
+}
+
+#ifdef WITH_LOGGING
+
+/**
+ * Internal macro used in I3IPC_*-logging macros
+ */
+#define I3IPC_LOG(T, ERR) \
+       ::i3ipc::log((T), (ERR));
+
+/**
+ * Put information message to log
+ * @param T message
+ */
+#define I3IPC_INFO(T) I3IPC_LOG(auss_t() << "i: " << T, false)
+
+/**
+ * Put error message to log
+ * @param T message
+ */
+#define I3IPC_ERR(T) I3IPC_LOG(auss_t() << "E: " << T, true)
+
+/**
+ * Put warning message to log
+ * @param T message
+ */
+#define I3IPC_WARN(T) I3IPC_LOG(auss_t() << "W: " << T, true)
+
+#ifdef DEBUG
+
+/**
+ * Put debug message to log
+ * @param T message
+ */
+#define I3IPC_DEBUG(T) I3IPC_LOG(auss_t() << "D: " << T, true)
+
+#else
+
+/**
+ * Put debug message to log
+ * @param T message
+ */
+#define I3IPC_DEBUG(T)
+#endif
+
+#else
+#define I3IPC_LOG(T, ERR)
+#define I3IPC_INFO(T)
+#define I3IPC_ERR(T)
+#define I3IPC_WARN(T)
+#define I3IPC_DEBUG(T)
+#define I3IPC_DEBUG(T)
+#endif
+
+/**
+ * @}
+ */
diff --git a/lib/i3ipcpp/src/ipc-util.cpp b/lib/i3ipcpp/src/ipc-util.cpp
new file mode 100644 (file)
index 0000000..d9851ee
--- /dev/null
@@ -0,0 +1,172 @@
+extern "C" {
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <errno.h>
+}
+
+#include <cstring>
+#include <ios>
+
+#include <auss.hpp>
+
+#include "i3ipc++/ipc-util.hpp"
+
+namespace i3ipc {
+
+static std::string  format_errno(const std::string&  msg = std::string()) {
+       auss_t  a;
+       if (msg.size() > 0)
+               a << msg << ": ";
+       a << "errno " << errno << " (" << strerror(errno) << ')';
+       return a;
+}
+
+errno_error::errno_error() : ipc_error(format_errno()) {}
+errno_error::errno_error(const std::string&  msg) : ipc_error(format_errno(msg)) {}
+
+static const std::string  g_i3_ipc_magic = "i3-ipc";
+
+buf_t::buf_t(uint32_t  payload_size) : size(sizeof(header_t) + payload_size) {
+       data = new uint8_t[size];
+       header = (header_t*)data;
+       payload = (char*)(data + sizeof(header_t));
+       memcpy(header->magic, g_i3_ipc_magic.c_str(), sizeof(header->magic));
+       header->size = payload_size;
+       header->type = 0x0;
+}
+buf_t::~buf_t() {
+       delete[] data;
+}
+
+void  buf_t::realloc_payload_to_header() {
+       uint8_t*  new_data = new uint8_t[sizeof(header_t) + header->size];
+       memcpy(new_data, header, sizeof(header_t));
+       delete[] data;
+       data = new_data;
+       header = (header_t*)data;
+       payload = (char*)(data + sizeof(header_t));
+}
+
+
+int32_t  i3_connect(const std::string&  socket_path) {
+       int32_t  sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
+       if (sockfd == -1) {
+               throw errno_error("Could not create a socket");
+       }
+
+       (void)fcntl(sockfd, F_SETFD, FD_CLOEXEC); // What for?
+
+       struct sockaddr_un addr;
+       memset(&addr, 0, sizeof(struct sockaddr_un));
+       addr.sun_family = AF_LOCAL;
+       strncpy(addr.sun_path, socket_path.c_str(), sizeof(addr.sun_path) - 1);
+       if (connect(sockfd, (const struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) {
+               throw errno_error("Failed to connect to i3");
+       }
+
+       return sockfd;
+}
+
+
+void  i3_disconnect(const int32_t  sockfd) {
+       close(sockfd);
+}
+
+
+std::shared_ptr<buf_t>  i3_pack(const ClientMessageType  type, const std::string&  payload) {
+       buf_t*  buff = new buf_t(payload.length());
+       buff->header->type = static_cast<uint32_t>(type);
+       strncpy(buff->payload, payload.c_str(), buff->header->size);
+       return std::shared_ptr<buf_t>(buff);
+}
+
+ssize_t  writeall(int  fd, const uint8_t*  buf, size_t  count) {
+       size_t written = 0;
+       ssize_t n = 0;
+
+       while (written < count) {
+               n = write(fd, buf + written, count - written);
+               if (n == -1) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+                       return n;
+               }
+               written += (size_t)n;
+       }
+
+       return written;
+}
+
+ssize_t  swrite(int  fd, const uint8_t*  buf, size_t  count) {
+       ssize_t n;
+
+       n = writeall(fd, buf, count);
+       if (n == -1)
+               throw errno_error(auss_t() << "Failed to write " << std::hex << fd);
+       else
+               return n;
+}
+
+void   i3_send(const int32_t  sockfd, const buf_t&  buff) {
+       swrite(sockfd, buff.data, buff.size);
+}
+
+std::shared_ptr<buf_t>   i3_recv(const int32_t  sockfd) {
+       auto buff = std::make_shared<buf_t>(0);
+       const uint32_t  header_size = sizeof(header_t);
+
+       {
+               uint8_t*  header = (uint8_t*)buff->header;
+               uint32_t  readed = 0;
+               while (readed < header_size) {
+                       int  n = read(sockfd, header + readed, header_size - readed);
+                       if (n == -1) {
+                               throw errno_error(auss_t() << "Failed to read header from socket 0x" << std::hex << sockfd);
+                       }
+                       if (n == 0) {
+                               throw eof_error("Unexpected EOF while reading header");
+                       }
+
+                       readed += n;
+               }
+       }
+
+       if (g_i3_ipc_magic != std::string(buff->header->magic, g_i3_ipc_magic.length())) {
+               throw invalid_header_error("Invalid magic in reply");
+       }
+
+       buff->realloc_payload_to_header();
+
+       {
+               uint32_t  readed = 0;
+               int n;
+               while (readed < buff->header->size) {
+                       if ((n = read(sockfd, buff->payload + readed, buff->header->size - readed)) == -1) {
+                               if (errno == EINTR || errno == EAGAIN)
+                                       continue;
+                               throw errno_error(auss_t() << "Failed to read payload from socket 0x" << std::hex << sockfd);
+                       }
+
+                       readed += n;
+               }
+       }
+
+       return buff;
+}
+
+
+std::shared_ptr<buf_t>  i3_msg(const int32_t  sockfd, const ClientMessageType  type, const std::string&  payload) {
+       auto  send_buff = i3_pack(type, payload);
+       i3_send(sockfd, *send_buff);
+       auto  recv_buff = i3_recv(sockfd);
+       if (send_buff->header->type != recv_buff->header->type) {
+               throw invalid_header_error(auss_t() << "Invalid reply type: Expected 0x" << std::hex << send_buff->header->type << ", got 0x" << recv_buff->header->type);
+       }
+       return recv_buff;
+}
+
+}
diff --git a/lib/i3ipcpp/src/ipc.cpp b/lib/i3ipcpp/src/ipc.cpp
new file mode 100644 (file)
index 0000000..c17d3e4
--- /dev/null
@@ -0,0 +1,630 @@
+#include <cstdio>
+#include <cstring>
+#include <stdexcept>
+#include <iostream>
+
+#include <json/json.h>
+#include <auss.hpp>
+
+#include "i3ipc++/ipc-util.hpp"
+#include "i3ipc++/ipc.hpp"
+#include "i3ipc++/log.hpp"
+
+namespace i3ipc {
+
+// For log.hpp
+std::vector<std::ostream*>  g_logging_outs = {
+       &std::cout,
+};
+std::vector<std::ostream*>  g_logging_err_outs = {
+       &std::cerr,
+};
+
+#define IPC_JSON_READ(ROOT) \
+{ \
+       Json::CharReaderBuilder b; \
+       const std::unique_ptr<Json::CharReader> reader(b.newCharReader());      \
+       JSONCPP_STRING error; \
+       if(!reader->parse(buf->payload, buf->payload + buf->header->size, &ROOT, &error)) { \
+               throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " << error); \
+       } \
+}
+
+#define IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, TYPE_CHECK, TYPE_NAME) \
+       {\
+               if (!(OBJ).TYPE_CHECK()) { \
+                       throw invalid_reply_payload_error(auss_t() << "Failed to parse reply on \"" i3IPC_TYPE_STR "\": " OBJ_DESCR " expected to be " TYPE_NAME); \
+               } \
+       }
+#define IPC_JSON_ASSERT_TYPE_OBJECT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isObject, "an object")
+#define IPC_JSON_ASSERT_TYPE_ARRAY(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isArray, "an array")
+#define IPC_JSON_ASSERT_TYPE_BOOL(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isBool, "a bool")
+#define IPC_JSON_ASSERT_TYPE_INT(OBJ, OBJ_DESCR) IPC_JSON_ASSERT_TYPE(OBJ, OBJ_DESCR, isInt, "an integer")
+
+
+inline rect_t  parse_rect_from_json(const Json::Value&  value) {
+       rect_t r{};
+       r.x = value["x"].asUInt();
+       r.y = value["y"].asUInt();
+       r.width = value["width"].asUInt();
+       r.height = value["height"].asUInt();
+       return r;
+}
+
+
+static std::shared_ptr<container_t>  parse_container_from_json(const Json::Value&  o) {
+#define i3IPC_TYPE_STR "PARSE CONTAINER FROM JSON"
+       if (o.isNull())
+               return std::shared_ptr<container_t>();
+       std::shared_ptr<container_t>  container (new container_t());
+       IPC_JSON_ASSERT_TYPE_OBJECT(o, "o")
+
+       container->id = o["id"].asUInt64();
+       container->xwindow_id= o["window"].asUInt64();
+       container->name = o["name"].asString();
+       container->type = o["type"].asString();
+       container->current_border_width = o["current_border_width"].asInt();
+       container->percent = o["percent"].asFloat();
+       container->rect = parse_rect_from_json(o["rect"]);
+       container->window_rect = parse_rect_from_json(o["window_rect"]);
+       container->deco_rect = parse_rect_from_json(o["deco_rect"]);
+       container->geometry = parse_rect_from_json(o["geometry"]);
+       container->urgent = o["urgent"].asBool();
+       container->focused = o["focused"].asBool();
+
+       container->border = BorderStyle::UNKNOWN;
+       std::string  border = o["border"].asString();
+       if (border == "normal") {
+               container->border = BorderStyle::NORMAL;
+       } else if (border == "none") {
+               container->border = BorderStyle::NONE;
+       } else if (border == "pixel") {
+               container->border = BorderStyle::PIXEL;
+       } else if (border == "1pixel") {
+               container->border = BorderStyle::ONE_PIXEL;
+       } else {
+               container->border_raw = border;
+               I3IPC_WARN("Got a unknown \"border\" property: \"" << border << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+       }
+
+       container->layout = ContainerLayout::UNKNOWN;
+       std::string  layout = o["layout"].asString();
+
+       if (layout == "splith") {
+               container->layout = ContainerLayout::SPLIT_H;
+       } else if (layout == "splitv") {
+               container->layout = ContainerLayout::SPLIT_V;
+       } else if (layout == "stacked") {
+               container->layout = ContainerLayout::STACKED;
+       } else if (layout == "tabbed") {
+               container->layout = ContainerLayout::TABBED;
+       } else if (layout == "dockarea") {
+               container->layout = ContainerLayout::DOCKAREA;
+       } else if (layout == "output") {
+               container->layout = ContainerLayout::OUTPUT;
+       } else {
+               container->layout_raw = border;
+               I3IPC_WARN("Got a unknown \"layout\" property: \"" << layout << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+       }
+
+       Json::Value  nodes = o["nodes"];
+       if (!nodes.isNull()) {
+               IPC_JSON_ASSERT_TYPE_ARRAY(nodes, "nodes")
+               for (Json::ArrayIndex  i = 0; i < nodes.size(); i++) {
+                       container->nodes.push_back(parse_container_from_json(nodes[i]));
+               }
+       }
+
+       return container;
+#undef i3IPC_TYPE_STR
+}
+
+static std::shared_ptr<workspace_t>  parse_workspace_from_json(const Json::Value&  value) {
+       if (value.isNull())
+               return std::shared_ptr<workspace_t>();
+       Json::Value  num = value["num"];
+       Json::Value  name = value["name"];
+       Json::Value  visible = value["visible"];
+       Json::Value  focused = value["focused"];
+       Json::Value  urgent = value["urgent"];
+       Json::Value  rect = value["rect"];
+       Json::Value  output = value["output"];
+
+       std::shared_ptr<workspace_t>  p (new workspace_t());
+       p->num = num.asInt();
+       p->name = name.asString();
+       p->visible = visible.asBool();
+       p->focused = focused.asBool();
+       p->urgent = urgent.asBool();
+       p->rect = parse_rect_from_json(rect);
+       p->output = output.asString();
+       return p;
+}
+
+static std::shared_ptr<output_t>  parse_output_from_json(const Json::Value&  value) {
+       if (value.isNull())
+               return std::shared_ptr<output_t>();
+       Json::Value  name = value["name"];
+       Json::Value  active = value["active"];
+       Json::Value  current_workspace = value["current_workspace"];
+       Json::Value  rect = value["rect"];
+
+       std::shared_ptr<output_t>  p (new output_t());
+       p->name = name.asString();
+       p->active = active.asBool();
+       p->current_workspace = (current_workspace.isNull() ? std::string() : current_workspace.asString());
+       p->rect = parse_rect_from_json(rect);
+       return p;
+}
+
+static std::shared_ptr<binding_t>  parse_binding_from_json(const Json::Value&  value) {
+#define i3IPC_TYPE_STR "PARSE BINDING FROM JSON"
+       if (value.isNull())
+               return std::shared_ptr<binding_t>();
+       IPC_JSON_ASSERT_TYPE_OBJECT(value, "binding")
+       std::shared_ptr<binding_t>  b (new binding_t());
+
+       b->command = value["command"].asString();
+       b->symbol = value["symbol"].asString();
+       b->input_code = value["input_code"].asInt();
+
+       Json::Value input_type = value["input_type"].asString();
+       if (input_type == "keyboard") {
+               b->input_type = InputType::KEYBOARD;
+       } else if (input_type == "mouse") {
+               b->input_type = InputType::MOUSE;
+       } else {
+               b->input_type = InputType::UNKNOWN;
+       }
+
+       Json::Value  esm_arr = value["event_state_mask"];
+       IPC_JSON_ASSERT_TYPE_ARRAY(esm_arr, "event_state_mask")
+
+       b->event_state_mask.resize(esm_arr.size());
+
+       for (Json::ArrayIndex  i = 0; i < esm_arr.size(); i++) {
+               b->event_state_mask[i] = esm_arr[i].asString();
+       }
+
+       return b;
+#undef i3IPC_TYPE_STR
+}
+
+static std::shared_ptr<mode_t>  parse_mode_from_json(const Json::Value&  value) {
+       if (value.isNull())
+               return std::shared_ptr<mode_t>();
+       Json::Value  change = value["change"];
+       Json::Value  pango_markup = value["pango_markup"];
+
+       std::shared_ptr<mode_t>  p (new mode_t());
+       p->change = change.asString();
+       p->pango_markup = pango_markup.asBool();
+       return p;
+}
+
+
+static std::shared_ptr<bar_config_t>  parse_bar_config_from_json(const Json::Value&  value) {
+#define i3IPC_TYPE_STR "PARSE BAR CONFIG FROM JSON"
+       if (value.isNull())
+               return std::shared_ptr<bar_config_t>();
+       IPC_JSON_ASSERT_TYPE_OBJECT(value, "(root)")
+       std::shared_ptr<bar_config_t>  bc (new bar_config_t());
+
+       bc->id = value["id"].asString();
+       bc->status_command = value["status_command"].asString();
+       bc->font = value["font"].asString();
+       bc->workspace_buttons = value["workspace_buttons"].asBool();
+       bc->binding_mode_indicator = value["binding_mode_indicator"].asBool();
+       bc->verbose = value["verbose"].asBool();
+
+       std::string  mode = value["mode"].asString();
+       if (mode == "dock") {
+               bc->mode = BarMode::DOCK;
+       } else if (mode == "hide") {
+               bc->mode = BarMode::HIDE;
+       } else {
+               bc->mode = BarMode::UNKNOWN;
+               I3IPC_WARN("Got a unknown \"mode\" property: \"" << mode << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+       }
+
+       std::string  position = value["position"].asString();
+       if (position == "top") {
+               bc->position = Position::TOP;
+       } else if (mode == "bottom") {
+               bc->position = Position::BOTTOM;
+       } else {
+               bc->position = Position::UNKNOWN;
+               I3IPC_WARN("Got a unknown \"position\" property: \"" << position << "\". Perhaps its neccessary to update i3ipc++. If you are using latest, note maintainer about this")
+       }
+
+       Json::Value  colors = value["colors"];
+       IPC_JSON_ASSERT_TYPE_OBJECT(value, "colors")
+       auto  colors_list = colors.getMemberNames();
+       for (auto&  m : colors_list) {
+               bc->colors[m] = std::stoul(colors[m].asString().substr(1), nullptr, 16);
+       }
+
+       return bc;
+#undef i3IPC_TYPE_STR
+}
+
+
+std::string  get_socketpath() {
+       std::string  str;
+       {
+               auss_t  str_buf;
+               FILE*  in;
+               char  buf[512] = {0};
+               if (!(in = popen("i3 --get-socketpath", "r"))) {
+                       throw errno_error("Failed to get socket path");
+               }
+
+               while (fgets(buf, sizeof(buf), in) != nullptr) {
+                       str_buf << buf;
+               }
+               pclose(in);
+               str = str_buf;
+       }
+       if (str.back() == '\n') {
+               str.pop_back();
+       }
+       return str;
+}
+
+
+connection::connection(const std::string&  socket_path) : m_main_socket(i3_connect(socket_path)), m_event_socket(-1), m_subscriptions(0), m_socket_path(socket_path) {
+#define i3IPC_TYPE_STR "i3's event"
+       on_event = [this](EventType  event_type, const std::shared_ptr<const buf_t>&  buf) {
+               switch (event_type) {
+               case ET_WORKSPACE: {
+                       workspace_event_t  ev;
+                       Json::Value  root;
+                       IPC_JSON_READ(root);
+                       std::string  change = root["change"].asString();
+                       if (change == "focus") {
+                               ev.type = WorkspaceEventType::FOCUS;
+                       } else if (change == "init") {
+                               ev.type = WorkspaceEventType::INIT;
+                       } else if (change == "empty") {
+                               ev.type = WorkspaceEventType::EMPTY;
+                       } else if (change == "urgent") {
+                               ev.type = WorkspaceEventType::URGENT;
+                       } else if (change == "rename") {
+                               ev.type = WorkspaceEventType::RENAME;
+                       } else if (change == "reload") {
+                               ev.type = WorkspaceEventType::RELOAD;
+                       } else if (change == "restored") {
+                               ev.type = WorkspaceEventType::RESTORED;
+                       } else {
+                               I3IPC_WARN("Unknown workspace event type " << change)
+                               break;
+                       }
+                       I3IPC_DEBUG("WORKSPACE " << change)
+
+                       Json::Value  current = root["current"];
+                       Json::Value  old = root["old"];
+
+                       if (!current.isNull()) {
+                               ev.current = parse_workspace_from_json(current);
+                       }
+                       if (!old.isNull()) {
+                               ev.old = parse_workspace_from_json(old);
+                       }
+
+                       if (on_workspace_event) {
+                               on_workspace_event(ev);
+                       }
+                       break;
+               }
+               case ET_OUTPUT:
+                       I3IPC_DEBUG("OUTPUT")
+                       if (on_output_event) {
+                               on_output_event();
+                       }
+                       break;
+               case ET_MODE: {
+                       I3IPC_DEBUG("MODE")
+                       Json::Value  root;
+                       IPC_JSON_READ(root);
+                       std::shared_ptr<mode_t>  mode_data = parse_mode_from_json(root);
+                       if (on_mode_event) {
+                               on_mode_event(*mode_data);
+                       }
+                       break;
+               }
+               case ET_WINDOW: {
+                       window_event_t  ev;
+                       Json::Value  root;
+                       IPC_JSON_READ(root);
+                       std::string  change = root["change"].asString();
+                       if (change == "new") {
+                               ev.type = WindowEventType::NEW;
+                       } else if (change == "close") {
+                               ev.type = WindowEventType::CLOSE;
+                       } else if (change == "focus") {
+                               ev.type = WindowEventType::FOCUS;
+                       } else if (change == "title") {
+                               ev.type = WindowEventType::TITLE;
+                       } else if (change == "fullscreen_mode") {
+                               ev.type = WindowEventType::FULLSCREEN_MODE;
+                       } else if (change == "move") {
+                               ev.type = WindowEventType::MOVE;
+                       } else if (change == "floating") {
+                               ev.type = WindowEventType::FLOATING;
+                       } else if (change == "urgent") {
+                               ev.type = WindowEventType::URGENT;
+                       }
+                       I3IPC_DEBUG("WINDOW " << change)
+
+                       Json::Value  container = root["container"];
+                       if (!container.isNull()) {
+                               ev.container = parse_container_from_json(container);
+                       }
+
+                       if (on_window_event) {
+                               on_window_event(ev);
+                       }
+                       break;
+               }
+               case ET_BARCONFIG_UPDATE: {
+                       I3IPC_DEBUG("BARCONFIG_UPDATE")
+                       Json::Value  root;
+                       IPC_JSON_READ(root);
+                       std::shared_ptr<bar_config_t>  barconf = parse_bar_config_from_json(root);
+                       if (on_barconfig_update_event) {
+                               on_barconfig_update_event(*barconf);
+                       }
+                       break;
+               }
+               case ET_BINDING: {
+                       Json::Value  root;
+                       IPC_JSON_READ(root);
+                       std::string  change = root["change"].asString();
+                       if (change != "run") {
+                               I3IPC_WARN("Got \"" << change << "\" in field \"change\" of binding_event. Expected \"run\"")
+                       }
+
+                       Json::Value  binding_json = root["binding"];
+                       std::shared_ptr<binding_t>  bptr;
+                       if (!binding_json.isNull()) {
+                               bptr = parse_binding_from_json(binding_json);
+                       }
+
+                       if (!bptr) {
+                               I3IPC_ERR("Failed to parse field \"binding\" from binding_event")
+                       } else {
+                               I3IPC_DEBUG("BINDING " << bptr->symbol);
+                               if (on_binding_event) {
+                                       on_binding_event(*bptr);
+                               }
+                       }
+                       break;
+               }
+               };
+       };
+#undef i3IPC_TYPE_STR
+}
+connection::~connection() {
+       i3_disconnect(m_main_socket);
+       if (m_event_socket > 0)
+               this->disconnect_event_socket();
+}
+
+
+void  connection::connect_event_socket(const bool  reconnect) {
+       if (m_event_socket > 0) {
+               if (reconnect) {
+                       this->disconnect_event_socket();
+               } else {
+                       I3IPC_ERR("Trying to initialize event socket secondary")
+                       return;
+               }
+       }
+       m_event_socket = i3_connect(m_socket_path);
+       this->subscribe(m_subscriptions);
+}
+
+
+void  connection::disconnect_event_socket() {
+       if (m_event_socket <= 0) {
+               I3IPC_WARN("Trying to disconnect non-connected event socket")
+               return;
+       }
+       i3_disconnect(m_event_socket);
+}
+
+
+bool connection::handle_event() {
+       if (m_event_socket <= 0) {
+               this->connect_event_socket();
+       }
+
+       auto buf = i3_recv(m_event_socket);
+
+       if (buf && this->on_event) {
+               this->on_event(static_cast<EventType>(1 << (buf->header->type & 0x7f)), std::static_pointer_cast<const buf_t>(buf));
+       } else if (buf) {
+               return true;
+       }
+       return false;
+}
+
+
+bool  connection::subscribe(const int32_t  events) {
+#define i3IPC_TYPE_STR "SUBSCRIBE"
+       if (m_event_socket <= 0) {
+               m_subscriptions |= events;
+               return true;
+       }
+       std::string  payload;
+       {
+               auss_t  payload_auss;
+               if (events & static_cast<int32_t>(ET_WORKSPACE)) {
+                       payload_auss << "\"workspace\",";
+               }
+               if (events & static_cast<int32_t>(ET_OUTPUT)) {
+                       payload_auss << "\"output\",";
+               }
+               if (events & static_cast<int32_t>(ET_MODE)) {
+                       payload_auss << "\"mode\",";
+               }
+               if (events & static_cast<int32_t>(ET_WINDOW)) {
+                       payload_auss << "\"window\",";
+               }
+               if (events & static_cast<int32_t>(ET_BARCONFIG_UPDATE)) {
+                       payload_auss << "\"barconfig_update\",";
+               }
+               if (events & static_cast<int32_t>(ET_BINDING)) {
+                       payload_auss << "\"binding\",";
+               }
+               payload = payload_auss;
+               if (payload.empty()) {
+                       return true;
+               }
+               payload.pop_back();
+       }
+       I3IPC_DEBUG("i3 IPC subscriptions: " << payload)
+
+       auto  buf = i3_msg(m_event_socket, ClientMessageType::SUBSCRIBE, auss_t() << '[' << payload << ']');
+       Json::Value  root;
+       IPC_JSON_READ(root)
+
+       m_subscriptions |= events;
+
+       return root["success"].asBool();
+#undef i3IPC_TYPE_STR
+}
+
+
+version_t  connection::get_version() const {
+#define i3IPC_TYPE_STR "GET_VERSION"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::GET_VERSION);
+       Json::Value  root;
+       IPC_JSON_READ(root)
+       IPC_JSON_ASSERT_TYPE_OBJECT(root, "root")
+
+       version_t v{};
+       v.human_readable = root["human_readable"].asString();
+       v.loaded_config_file_name = root["loaded_config_file_name"].asString();
+       v.major = root["major"].asUInt();
+       v.minor = root["minor"].asUInt();
+       v.patch = root["patch"].asUInt();
+       return v;
+#undef i3IPC_TYPE_STR
+}
+
+
+std::shared_ptr<container_t>  connection::get_tree() const {
+#define i3IPC_TYPE_STR "GET_TREE"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::GET_TREE);
+       Json::Value  root;
+       IPC_JSON_READ(root);
+       return parse_container_from_json(root);
+#undef i3IPC_TYPE_STR
+}
+
+
+std::vector< std::shared_ptr<output_t> >  connection::get_outputs() const {
+#define i3IPC_TYPE_STR "GET_OUTPUTS"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::GET_OUTPUTS);
+       Json::Value  root;
+       IPC_JSON_READ(root)
+       IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+
+       std::vector< std::shared_ptr<output_t> >  outputs;
+
+       for (auto w : root) {
+               outputs.push_back(parse_output_from_json(w));
+       }
+
+       return outputs;
+#undef i3IPC_TYPE_STR
+}
+
+
+std::vector< std::shared_ptr<workspace_t> >  connection::get_workspaces() const {
+#define i3IPC_TYPE_STR "GET_WORKSPACES"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::GET_WORKSPACES);
+       Json::Value  root;
+       IPC_JSON_READ(root)
+       IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+
+       std::vector< std::shared_ptr<workspace_t> >  workspaces;
+
+       for (auto w : root) {
+               workspaces.push_back(parse_workspace_from_json(w));
+       }
+
+       return workspaces;
+#undef i3IPC_TYPE_STR
+}
+
+
+std::vector<std::string>  connection::get_bar_configs_list() const {
+#define i3IPC_TYPE_STR "GET_BAR_CONFIG (get_bar_configs_list)"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG);
+       Json::Value  root;
+       IPC_JSON_READ(root)
+       IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+
+       std::vector<std::string>  l;
+
+       for (auto w : root) {
+               l.push_back(w.asString());
+       }
+
+       return l;
+#undef i3IPC_TYPE_STR
+}
+
+
+std::shared_ptr<bar_config_t>  connection::get_bar_config(const std::string&  name) const {
+#define i3IPC_TYPE_STR "GET_BAR_CONFIG"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::GET_BAR_CONFIG, name);
+       Json::Value  root;
+       IPC_JSON_READ(root)
+       return parse_bar_config_from_json(root);
+#undef i3IPC_TYPE_STR
+}
+
+
+bool  connection::send_command(const std::string&  command) const {
+#define i3IPC_TYPE_STR "COMMAND"
+       auto  buf = i3_msg(m_main_socket, ClientMessageType::COMMAND, command);
+       Json::Value  root;
+       IPC_JSON_READ(root)
+       IPC_JSON_ASSERT_TYPE_ARRAY(root, "root")
+       Json::Value  payload = root[0];
+       IPC_JSON_ASSERT_TYPE_OBJECT(payload, " first item of root")
+
+       if (payload["success"].asBool()) {
+               return true;
+       } else {
+               Json::Value  error = payload["error"];
+               if (!error.isNull()) {
+                       I3IPC_ERR("Failed to execute command: " << error.asString())
+               }
+               return false;
+       }
+#undef i3IPC_TYPE_STR
+}
+
+int32_t  connection::get_main_socket_fd() { return m_main_socket; }
+
+int32_t  connection::get_event_socket_fd() { return m_event_socket; }
+
+
+const version_t&  get_version() {
+#define I3IPC_VERSION_MAJOR  0
+#define I3IPC_VERSION_MINOR  4
+#define I3IPC_VERSION_PATCH  0
+       static version_t v{};
+       v.human_readable = auss_t() << I3IPC_VERSION_MAJOR << '.' << I3IPC_VERSION_MINOR << '.' << I3IPC_VERSION_PATCH;
+       v.loaded_config_file_name = std::string();
+       v.major = I3IPC_VERSION_MAJOR;
+       v.minor = I3IPC_VERSION_MINOR;
+       v.patch = I3IPC_VERSION_PATCH;
+       return v;
+}
+
+}
diff --git a/lib/i3ipcpp/test/test_ipc.hpp b/lib/i3ipcpp/test/test_ipc.hpp
new file mode 100644 (file)
index 0000000..1aba72c
--- /dev/null
@@ -0,0 +1,28 @@
+#include <iostream>
+
+#include <auss.hpp>
+
+#include "ipc-util.hpp"
+
+#include <cxxtest/TestSuite.h>
+
+class testsuite_ipc_util : public CxxTest::TestSuite {
+public:
+       void test_pack() {
+               {
+                       using namespace i3ipc;
+                       auto  buff = i3_pack(ClientMessageType::COMMAND, "exit");
+                       auss_t  auss;
+                       auss << std::hex;
+                       for (uint32_t  i = 0; i < buff->size; i++) {
+                               if (buff->data[i] < 0x10) {
+                                       auss << '0';
+                               }
+                               auss << static_cast<uint32_t>(buff->data[i]) << ' ';
+                       }
+                       std::string  str = auss;
+                       str.pop_back();
+                       TS_ASSERT_EQUALS(str, "69 33 2d 69 70 63 04 00 00 00 00 00 00 00 65 78 69 74")
+               }
+       }
+};
diff --git a/lib/xpp/.gitignore b/lib/xpp/.gitignore
new file mode 100644 (file)
index 0000000..95f4599
--- /dev/null
@@ -0,0 +1,7 @@
+build
+.clang_complete
+*.o
+*.swp
+*.gch
+*.d
+*.pyc
diff --git a/lib/xpp/CMakeLists.txt b/lib/xpp/CMakeLists.txt
new file mode 100644 (file)
index 0000000..caee2ae
--- /dev/null
@@ -0,0 +1,200 @@
+cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
+project(xpp)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
+
+#
+# Process dependencies
+#
+find_package(PkgConfig)
+pkg_check_modules(XCBPROTO REQUIRED xcb-proto)
+
+# Store the location of the proto xml files inside XCBPROTO_XCBINCLUDEDIR
+if(${CMAKE_VERSION} VERSION_LESS "3.4.0")
+    # pkg_get_variable was introduced in 3.4.0 if it isn't available, we fall back to ${prefix}/share/xcb
+
+    if(NOT XCBPROTO_PREFIX)
+        set(XCBPROTO_PREFIX /usr)
+    endif()
+
+    set(XCBPROTO_XCBINCLUDEDIR ${XCBPROTO_PREFIX}/share/xcb)
+    message(STATUS "${PROJECT_NAME}: pkg_get_variable not supported, setting XCBPROTO_XCBINCLUDEDIR to ${XCBPROTO_XCBINCLUDEDIR}")
+else()
+    # The xml file that need to be included later are stored in xcbincludedir as defined in xcb-proto.pc
+    pkg_get_variable(XCBPROTO_XCBINCLUDEDIR xcb-proto xcbincludedir)
+endif()
+
+find_package(PythonInterp 3.5 REQUIRED)
+find_package(XCB REQUIRED XCB ICCCM EWMH UTIL IMAGE)
+
+if(NOT PYTHON_EXECUTABLE)
+  message(FATAL_ERROR "Missing PYTHON_EXECUTABLE")
+endif()
+
+set(GEN_SRC "${CMAKE_CURRENT_BINARY_DIR}/generated-sources")
+
+set(XPP_INCLUDE_DIRS
+  ${PROJECT_SOURCE_DIR}/include
+  ${GEN_SRC}/include
+  ${XCB_XCB_INCLUDE_DIR}
+  ${XCB_EWMH_INCLUDE_DIR}
+  ${XCB_ICCCM_INCLUDE_DIR}
+  ${XCB_UTIL_INCLUDE_DIR}
+  ${XCB_IMAGE_INCLUDE_DIR})
+set(XPP_LIBRARIES
+  ${XCB_XCB_LIBRARY}
+  ${XCB_EWMH_LIBRARY}
+  ${XCB_ICCCM_LIBRARY}
+  ${XCB_UTIL_LIBRARY}
+  ${XCB_IMAGE_LIBRARY})
+
+#
+# Loop through a hardcoded list of python executables to locate the python module "xcbgen"
+#
+# TODO drop python2 once ubuntu and debian ship python3-xcbgen in their
+# maintained distros
+foreach(CURRENT_EXECUTABLE ${PYTHON_EXECUTABLE} python3 python python2 python2.7)
+  message(STATUS "Searching for xcbgen with " ${CURRENT_EXECUTABLE})
+
+  execute_process(COMMAND "${CURRENT_EXECUTABLE}" "-c"
+    "import re,xcbgen;print(re.compile('/xcbgen/__init__.py.*').sub('',xcbgen.__file__))"
+    RESULT_VARIABLE _xcbgen_status
+    OUTPUT_VARIABLE _xcbgen_location
+    ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+  # When a shell script returns successfully its return code is 0
+  if(_xcbgen_status EQUAL 0)
+    set(PYTHON_XCBGEN "${_xcbgen_location}" CACHE STRING "Location of python module: xcbgen ")
+    message(STATUS "Found xcbgen in " ${PYTHON_XCBGEN})
+    break()
+  endif()
+
+endforeach(CURRENT_EXECUTABLE)
+
+if(NOT PYTHON_XCBGEN)
+  message(FATAL_ERROR "Missing required python module: xcbgen")
+endif()
+
+#
+# Include XCB libs depending on what protos we build
+#
+if(NOT XCB_PROTOS)
+  set(XCB_PROTOS
+    "bigreq"
+    "composite"
+    "damage"
+    "dpms"
+    "dri2"
+    "dri3"
+    "glx"
+    "present"
+    "randr"
+    "record"
+    "render"
+    "res"
+    "screensaver"
+    "shape"
+    "shm"
+    "sync"
+    "xc_misc"
+    "xevie"
+    "xf86dri"
+    "xfixes"
+    "xinerama"
+    "xinput"
+    "xkb"
+    "xprint"
+    "xproto"
+    "xselinux"
+    "xtest"
+    "xv"
+    "xvmc")
+endif()
+
+if(";${XCB_PROTOS};" MATCHES ";randr;")
+  find_package(XCB REQUIRED RANDR)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_RANDR_INCLUDE_DIR})
+  set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_RANDR_LIBRARY})
+endif()
+if(";${XCB_PROTOS};" MATCHES ";render;")
+  find_package(XCB REQUIRED RENDER)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_RENDER_INCLUDE_DIR})
+  set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_RENDER_LIBRARY})
+endif()
+if(";${XCB_PROTOS};" MATCHES ";damage;")
+  find_package(XCB REQUIRED DAMAGE)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_DAMAGE_INCLUDE_DIR})
+  set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_DAMAGE_LIBRARY})
+endif()
+if(";${XCB_PROTOS};" MATCHES ";sync;")
+  find_package(XCB REQUIRED SYNC)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_SYNC_INCLUDE_DIR})
+  set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_SYNC_LIBRARY})
+endif()
+if(";${XCB_PROTOS};" MATCHES ";composite;")
+  find_package(XCB REQUIRED COMPOSITE)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_COMPOSITE_INCLUDE_DIR})
+  set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_COMPOSITE_LIBRARY})
+endif()
+if(";${XCB_PROTOS};" MATCHES ";xkb;")
+  find_package(XCB REQUIRED XKB)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} ${XCB_XKB_INCLUDE_DIR})
+  set(XPP_LIBRARIES ${XPP_LIBRARIES} ${XCB_XKB_LIBRARY})
+endif()
+
+set(PROTO_LIST)
+
+file(GLOB PROTO_LIST_RAW RELATIVE ${XCBPROTO_XCBINCLUDEDIR} ${XCBPROTO_XCBINCLUDEDIR}/*.xml)
+
+#
+# Filter glob
+#
+foreach(PROTO_RAW ${PROTO_LIST_RAW})
+  string(REGEX REPLACE "(^xf86vidmode.xml|^ge.xml|.xml)\$" "" PROTO ${PROTO_RAW})
+  if(PROTO AND ";${XCB_PROTOS};" MATCHES ";${PROTO};")
+    message(STATUS "${PROJECT_NAME}: including xcb proto ${PROTO_RAW}")
+    set(PROTO_LIST ${PROTO_LIST} ${PROTO})
+  endif()
+endforeach(PROTO_RAW)
+
+#
+# Add commands
+#
+set(PROTO_HEADER_DIR "${GEN_SRC}/include/xpp/proto")
+file(MAKE_DIRECTORY ${PROTO_HEADER_DIR})
+set(PROTO_HEADER_FILES "")
+foreach(PROTO ${PROTO_LIST})
+  string(REGEX REPLACE "proto\$" "" PROTO_OUTPUT ${PROTO})
+  set(OUTPUT_FILE ${PROTO_HEADER_DIR}/${PROTO_OUTPUT}.hpp)
+  add_custom_command(
+    OUTPUT ${OUTPUT_FILE}
+    COMMAND ${PYTHON_EXECUTABLE} ${PROJECT_SOURCE_DIR}/generators/cpp_client.py -p "${PYTHON_XCBGEN}"
+    ${XCBPROTO_XCBINCLUDEDIR}/${PROTO}.xml > ${OUTPUT_FILE})
+  list(APPEND PROTO_HEADER_FILES ${OUTPUT_FILE})
+endforeach(PROTO)
+
+#
+# Create project lib and commands
+#
+file(GLOB_RECURSE HEADER_FILES ${PROJECT_SOURCE_DIR}/include/*.hpp)
+add_library(${PROJECT_NAME} STATIC ${HEADER_FILES} ${PROTO_HEADER_FILES})
+
+target_include_directories(${PROJECT_NAME} PUBLIC ${XPP_INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} PRIVATE ${XPP_LIBRARIES})
+
+target_compile_options(${PROJECT_NAME} PRIVATE -std=c++14 -Wall -Wpedantic)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Debug>:-g3 -DDEBUG>)
+target_compile_options(${PROJECT_NAME} PRIVATE $<$<CONFIG:Release>:-O3 -Wno-unused-variable>)
+target_compile_options(${PROJECT_NAME} PUBLIC ${X11_XCB_DEFINITIONS} ${XCB_DEFINITIONS})
+
+set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
+
+#
+# Export lists to the parent scope if there are any
+#
+get_directory_property(HAS_PARENT PARENT_DIRECTORY)
+
+if(HAS_PARENT)
+  set(XPP_INCLUDE_DIRS ${XPP_INCLUDE_DIRS} PARENT_SCOPE)
+  set(XPP_LIBRARIES ${PROJECT_NAME} PARENT_SCOPE)
+endif()
diff --git a/lib/xpp/LICENSE b/lib/xpp/LICENSE
new file mode 100644 (file)
index 0000000..493c564
--- /dev/null
@@ -0,0 +1,8 @@
+Original work Copyright (c) 2018, Jochen Keil
+Modified work Copyright 2018, Michael Carlberg (jaagr) and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/xpp/README.md b/lib/xpp/README.md
new file mode 100644 (file)
index 0000000..97a4e60
--- /dev/null
@@ -0,0 +1,313 @@
+# xpp - A C++11 RAII wrapper for XCB
+
+## Synopsis
+
+XPP is a header only C++11
+[RAII](https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization)
+wrapper around [X protocol C-language Binding
+(XCB)](http://xcb.freedesktop.org). Pointers to dynamically allocated memory,
+such as events and errors are wrapped in std::shared_ptr.
+
+Furthermore, interfaces for connection and resource types are provided to
+facilitate the creation of custom classes. For convenience, a connection class
+and several basic resource type classes are readily available.
+
+XPP makes widespread use of the
+[Curiously Recurring Template Pattern (CRTP)](https://en.wikibooks.org/wiki/More_C++_Idioms/Curiously_Recurring_Template_Pattern)
+to avoid overhead through dynamic dispatch. Hence, most interfaces are
+implicitly defined.
+
+## Prerequisites
+
+  * Python 3
+  * GCC >= 4.8 (or Clang >= 3.3, untested)
+  * libxcb
+
+## Quick Start
+
+```
+  git clone https://github.com/jrk-/xpp
+  cd xpp
+  make
+  make examples
+  cd src/examples
+  for demo in demo_*; do ./${demo}; done
+```
+
+## Documentation
+
+### General
+
+The bindings can be generated by calling `make` in the top level directory. If
+this fails, check the [`XCBGEN`](include/proto/Makefile#L38) and
+[`PROTODIR`](include/proto/Makefile#L39) variables in
+[include/proto/Makefile](include/proto/Makefile). These need to point to the `xcbgen`
+python package and the xml protocol description respectively.
+
+Once the bindings are generated they can be used by including
+[include/xpp.hpp](include/xpp.hpp). If an extensions is required, it needs to be
+included additionally. For example, the RandR extension is available through
+`proto/randr.hpp`, the Damage extension through `proto/damage.hpp`, etc.
+
+Recent (and working) examples can be found in [src/examples](src/examples).
+To compile them, call `make examples` in the `xpp` directory or just `make` in
+[src/examples](src/examples).
+
+### Requests
+
+Requests obey this naming scheme: `xpp:: ExtensionName :: RequestName`.
+
+##### Examples:
+
+Core X protocol:
+`MapWindow`: `xcb_map_window{,_checked}` -> `xpp::x::map_window{,_checked}`
+`InternAtom`: `xcb_intern_atom{,_checked}` -> `xpp::x::intern_atom{,_unchecked}`
+
+RandR protocol:
+`SelectInput`: `xcb_randr_select_input{,_checked}` -> `xpp::randr::select_input{,_checked}`
+`QueryVersion`: `xcb_randr_query_version{,_unchecked}` -> `xpp::randr::query_version{,_unchecked}`
+
+##### Default Parameter
+
+All `xcb_timestamp_t` parameters are alternatively available with a default
+value of `XCB_TIME_CURRENT_TIME`.
+
+##### Parameter Lists
+
+Requests which take a list of values as parameters can be used with any STL
+container by passing in Iterators. Example:
+
+```
+std::string string_example = "example string";
+// std::list<char> list_example = { 'a', 'b', 'c' };
+// std::map<int, char> map_example = { {0, 'a'}, {1, 'b'}, {2, 'c'} };
+xpp::x::change_property_checked(c, XCB_PROP_MODE_REPLACE, window,
+                                atom, XCB_ATOM_STRING, 8,
+                                string_example.begin(), string_example.end());
+                                // list_example.begin(), list_example.end());
+                                // for associative containers the value (std::pair<..>::second_type) will be used
+                                // map_example.begin(), map_example.end());
+```
+
+### Replies
+
+XCB returns replies only when they are explicitely queried. With XPP this is not
+necessary anymore, because the operators for accessing the reply are overloaded.
+
+For example, getting the reply for the `InternAtom` request is as simple as this:
+
+```
+auto reply = xpp::x::intern_atom(connection, true, "MY_ATOM_NAME");
+// do some other stuff ..
+// latency hiding is still effective, because the call to
+// xcb_intern_atom_reply happens but now in operator->()
+xcb_atom_t atom = reply->atom;
+```
+
+#### Member Accessors
+
+##### Simple Types
+
+Primitive types like `xcb_window_t`, `xcb_atom_t`, etc. can be accessed either
+directly through the overloaded `operator->()` or via a method which has the
+same name as the member. These methods are templated with a default template
+type of the native type. Any type which is default constructible from the native
+type or a connection and the native type can be specified as template argument.
+
+Examples:
+
+```
+xcb_window_t w1 = reply->member;
+xcb_window_t w2 = reply.member(); // default template parameter is xcb_window_t
+xpp::window w3 = reply.member<xpp::window>();
+```
+
+##### List Types
+
+Lists (e.g. the result for `QueryTree`) are accessible through iterators. The
+value type is templated, with the default being the native data type.
+
+Example:
+
+```
+auto tree = xpp::x::query_tree(c, window);
+
+// default template type: xcb_window_t
+for (auto && child : tree.children()) {
+  // child has type xcb_window_t
+}
+
+// xpp::window is constructible with a connection and xcb_window_t
+// other types which are default-constructible with either the value type
+// (e.g.  xcb_window_t) or a connection & the value type are possible, too
+for (auto && child : tree.children<xpp::window>()) {
+  // child has type xpp::window
+}
+```
+
+Caveat: Some requests (in particular `GetProperty`) return an untyped array of
+bytes (`void *`). To access the desired data type, a template type must be
+specified. For constructible types a type trait must be implemented, like so:
+
+```
+struct my_type {
+  my_type(const xcb_window_t &);
+  // ..
+};
+
+namespace xpp { namespace generic {
+struct traits<my_type> {
+  typedef xcb_atom_t type;
+};
+}; }; // namespace xpp::generic
+```
+
+### Errors
+
+XCB offers four different variants of request functions.
+
+##### Requests without a reply:
+
+* Error delivered through event queue: `xcb_void_cookie_t xcb_request(...)`
+
+* Error can be checked immediately with `xcb_request_check(xcb_connection_t *, xcb_void_cookie_t)`: `xcb_void_cookie_t xcb_request_checked(...)`
+
+##### Requests with reply:
+
+* Error can be checked when getting the reply:
+`xcb_request_reply_t * xcb_request_reply(xcb_connection_t *, xcb_request_cookie_t, xcb_generic_error_t **)`:
+`xcb_request_cookie_t xcb_request(...)`
+
+* Error delivered through event queue: `xcb_request_cookie_t xcb_request_unchecked(...)`
+
+For more information on this, refer to [xcb-requests (3)](http://www.x.org/releases/current/doc/man/man3/xcb-requests.3.xhtml).
+
+With xpp errors are either thrown as `std::shared_ptr<xcb_generic_error_t>` or
+typed as `xpp:: extension ::error:: error_type`, e.g. `xpp::x::error::value`.
+
+The latter are based upon `xpp::generic::error` (which inherits from
+`std::runtime_error`) and come with a textual error description which is
+accessible through the `what()` method.
+
+For typed errors it is necessary to use a connection class which implements the
+appropriate error dispatching. The supplied `xpp::connection` class already does
+this. If no error dispatcher are available (e.g. when used with
+`xcb_connection_t *`), then a simply `std::shared_ptr<xcb_generic_error_t>`
+will be thrown.
+
+### Events
+
+Events returned by the event producing methods (`wait_for_event`,
+`poll_for_event`, etc.) from `xpp::core` and `xpp::connection` are encapsulated
+as `std::shared_ptr<xcb_generic_event_t>`.
+
+For additional convenience typed events are available. An event type is based on
+`xpp::generic::event`. The general structure for a typed event is
+
+`xpp::` Extension `::event::` EventName
+
+Examples:
+
+```
+xpp::x::event::key_press
+xpp::randr::event::notify
+xpp::damage::event::notify
+```
+
+Events can be converted from `std::shared_ptr<xcb_generic_event_t>` to a typed
+event by either using an event dispatcher functor (e.g.
+`xpp::x::event::dispatcher`) or by using the event registry described below.
+
+##### Registry
+
+The event registry `xpp::event::registry<Connection, Extensions ...>` can be
+used to connect events and event handlers.
+
+First, a registry object for the desired `Connection` type and `Extensions` is
+necessary.
+
+Then, arbitrary objects, which implement the `xpp::event::sink<..>` interface
+need to be attached for event handling by calling the `attach()` method.
+It takes two parameters. The first one specifies the priority, in case there are
+more than one event handler for this event. Handlers with lower priorities are
+called first. The second one is a pointer to an object which implements the
+`xpp::event::sink<..>` interface.
+
+For a detailed example, take a look at this [demo](src/examples/demo_01.cpp).
+
+### Interfaces
+
+Interfaces for creating custom types are available.
+
+##### <a name="interface-connection"></a>Connection
+
+For every extension a "connection" interface, called
+`xpp:: ExtensionName ::interface<typename Derived, typename Connection>`
+is available.
+
+These encapsulate every request for a particular extension. The `Derived`
+template parameter specifies the class which wants to derive from the interface.
+The `Derived` class must provide a method `Connection connection();`.
+
+Examples:
+
+```
+xpp::x::interface<typename Derived, typename Connection>
+xpp::randr::interface<typename Derived, typename Connection>
+xpp::damage::interface<typename Derived, typename Connection>
+etc.
+```
+
+For a customizable default implementation, take a look at the `xpp::connection`
+class described [here](#default-type-connection).
+
+##### Resources
+
+In addition, interfaces for basic resource types like `xcb_window_t`,
+`xcb_atom_t`, `xcb_gcontext_t`, etc. are available.
+
+Again, the naming scheme follows the format
+`xpp:: ExtensionName :: XidType <typename Derived, typename Connection>`
+
+Despite the `connection()` method described [here](#interface-connection),
+`Derived` needs to implement a `resource()` method which returns a xid which
+will be passed as parameter to the encapsulated requests.
+
+Examples:
+
+```
+xpp::x::window<typename Derived, typename Connection>
+xpp::randr::output<typename Derived, typename Connection>
+xpp::render::glyphset<typename Derived, typename Connection>
+etc.
+```
+
+### Default Types
+
+##### <a name="default-type-connection"></a>Connection
+
+`xpp::connection<Extensions ...>` provides a default
+implementation of the [core connection methods](include/core.hpp), the core
+X protocol and error handling facilities. In addition, it is implicitly
+convertible to `xcb_connection_t *`, hence it can be used seamlessly with XCB
+functions. The connection can be augmented with additional extension methods, by
+specifying the desired extensions as template parameters.
+
+Example:
+
+`typedef xpp::connection<xpp::randr::extension, xpp::damage::extension> my_connection;`
+
+##### Resources
+
+For the basic resource types like `Drawable`, `Window`, `Pixmap`, `Atom`,
+`Colormap`, `Cursor`, `Font`, `Fontable` and `GContext` wrapper types exist.
+They are named `xpp::drawable`, `xpp::window`, etc.
+
+Each is based upon xpp::generic::resource and provides the core X protocol
+interface for the encapsulated resource type. If the resource can be acquired
+from the X server (e.g. with `CreateWindow`) then a named constructor is
+available (e.g. `create_window` for `xpp::window`).
+
+Resources acquired through the named constructors are reference counted. When
+their lifetime expires, the resource handle will automatically be freed on the
+server. No call to destroy or free functions is necessary.
diff --git a/lib/xpp/cmake/FindX11_XCB.cmake b/lib/xpp/cmake/FindX11_XCB.cmake
new file mode 100644 (file)
index 0000000..7611b08
--- /dev/null
@@ -0,0 +1,31 @@
+# - Try to find libX11-xcb
+# Once done this will define
+#
+# X11_XCB_FOUND - system has libX11-xcb
+# X11_XCB_LIBRARIES - Link these to use libX11-xcb
+# X11_XCB_INCLUDE_DIR - the libX11-xcb include dir
+# X11_XCB_DEFINITIONS - compiler switches required for using libX11-xcb
+
+# Copyright (c) 2011 Fredrik Höglund <fredrik@kde.org>
+# Copyright (c) 2008 Helio Chissini de Castro, <helio@kde.org>
+# Copyright (c) 2007 Matthias Kretz, <kretz@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+IF (NOT WIN32)
+  # use pkg-config to get the directories and then use these values
+  # in the FIND_PATH() and FIND_LIBRARY() calls
+  FIND_PACKAGE(PkgConfig)
+  PKG_CHECK_MODULES(PKG_X11_XCB QUIET x11-xcb)
+
+  SET(X11_XCB_DEFINITIONS ${PKG_X11_XCB_CFLAGS})
+
+  FIND_PATH(X11_XCB_INCLUDE_DIR NAMES X11/Xlib-xcb.h HINTS ${PKG_X11_XCB_INCLUDE_DIRS})
+  FIND_LIBRARY(X11_XCB_LIBRARIES NAMES X11-xcb HINTS ${PKG_X11_XCB_LIBRARY_DIRS})
+
+  include(FindPackageHandleStandardArgs)
+  FIND_PACKAGE_HANDLE_STANDARD_ARGS(X11_XCB DEFAULT_MSG X11_XCB_LIBRARIES X11_XCB_INCLUDE_DIR)
+
+  MARK_AS_ADVANCED(X11_XCB_INCLUDE_DIR X11_XCB_LIBRARIES)
+ENDIF (NOT WIN32)
diff --git a/lib/xpp/cmake/FindXCB.cmake b/lib/xpp/cmake/FindXCB.cmake
new file mode 100644 (file)
index 0000000..4a15b31
--- /dev/null
@@ -0,0 +1,254 @@
+# Try to find XCB on a Unix system
+#
+# This will define:
+#
+#   XCB_FOUND        - True if xcb is available
+#   XCB_LIBRARIES    - Link these to use xcb
+#   XCB_INCLUDE_DIRS - Include directory for xcb
+#   XCB_DEFINITIONS  - Compiler flags for using xcb
+#
+# In addition the following more fine grained variables will be defined:
+#
+#   XCB_XCB_FOUND        XCB_XCB_INCLUDE_DIR        XCB_XCB_LIBRARY
+#   XCB_UTIL_FOUND       XCB_UTIL_INCLUDE_DIR       XCB_UTIL_LIBRARY
+#   XCB_COMPOSITE_FOUND  XCB_COMPOSITE_INCLUDE_DIR  XCB_COMPOSITE_LIBRARY
+#   XCB_DAMAGE_FOUND     XCB_DAMAGE_INCLUDE_DIR     XCB_DAMAGE_LIBRARY
+#   XCB_XFIXES_FOUND     XCB_XFIXES_INCLUDE_DIR     XCB_XFIXES_LIBRARY
+#   XCB_RENDER_FOUND     XCB_RENDER_INCLUDE_DIR     XCB_RENDER_LIBRARY
+#   XCB_RANDR_FOUND      XCB_RANDR_INCLUDE_DIR      XCB_RANDR_LIBRARY
+#   XCB_SHAPE_FOUND      XCB_SHAPE_INCLUDE_DIR      XCB_SHAPE_LIBRARY
+#   XCB_DRI2_FOUND       XCB_DRI2_INCLUDE_DIR       XCB_DRI2_LIBRARY
+#   XCB_GLX_FOUND        XCB_GLX_INCLUDE_DIR        XCB_GLX_LIBRARY
+#   XCB_SHM_FOUND        XCB_SHM_INCLUDE_DIR        XCB_SHM_LIBRARY
+#   XCB_XV_FOUND         XCB_XV_INCLUDE_DIR         XCB_XV_LIBRARY
+#   XCB_SYNC_FOUND       XCB_SYNC_INCLUDE_DIR       XCB_SYNC_LIBRARY
+#   XCB_XTEST_FOUND      XCB_XTEST_INCLUDE_DIR      XCB_XTEST_LIBRARY
+#   XCB_ICCCM_FOUND      XCB_ICCCM_INCLUDE_DIR      XCB_ICCCM_LIBRARY
+#   XCB_EWMH_FOUND       XCB_EWMH_INCLUDE_DIR       XCB_EWMH_LIBRARY
+#   XCB_IMAGE_FOUND      XCB_IMAGE_INCLUDE_DIR      XCB_IMAGE_LIBRARY
+#   XCB_RENDERUTIL_FOUND XCB_RENDERUTIL_INCLUDE_DIR XCB_RENDERUTIL_LIBRARY
+#   XCB_KEYSYMS_FOUND    XCB_KEYSYMS_INCLUDE_DIR    XCB_KEYSYMS_LIBRARY
+#
+# Copyright (c) 2011 Fredrik Höglund <fredrik@kde.org>
+# Copyright (c) 2013 Martin Gräßlin <mgraesslin@kde.org>
+#
+# Redistribution and use is allowed according to the terms of the BSD license.
+# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
+
+set(knownComponents XCB
+                    COMPOSITE
+                    DAMAGE
+                    DRI2
+                    EWMH
+                    GLX
+                    ICCCM
+                    IMAGE
+                    KEYSYMS
+                    RANDR
+                    RENDER
+                    RENDERUTIL
+                    SHAPE
+                    SHM
+                    SYNC
+                    UTIL
+                    XFIXES
+                    XTEST
+                    XV
+                    XINERAMA
+                    XKB)
+
+unset(unknownComponents)
+
+set(pkgConfigModules)
+set(requiredComponents)
+
+if (XCB_FIND_COMPONENTS)
+  set(comps ${XCB_FIND_COMPONENTS})
+else()
+  set(comps ${knownComponents})
+endif()
+
+# iterate through the list of requested components, and check that we know them all.
+# If not, fail.
+foreach(comp ${comps})
+    list(FIND knownComponents ${comp} index )
+    if("${index}" STREQUAL "-1")
+        list(APPEND unknownComponents "${comp}")
+    else()
+        if("${comp}" STREQUAL "XCB")
+            list(APPEND pkgConfigModules "xcb")
+        elseif("${comp}" STREQUAL "COMPOSITE")
+            list(APPEND pkgConfigModules "xcb-composite")
+        elseif("${comp}" STREQUAL "DAMAGE")
+            list(APPEND pkgConfigModules "xcb-damage")
+        elseif("${comp}" STREQUAL "DRI2")
+            list(APPEND pkgConfigModules "xcb-dri2")
+        elseif("${comp}" STREQUAL "EWMH")
+            list(APPEND pkgConfigModules "xcb-ewmh")
+        elseif("${comp}" STREQUAL "GLX")
+            list(APPEND pkgConfigModules "xcb-glx")
+        elseif("${comp}" STREQUAL "ICCCM")
+            list(APPEND pkgConfigModules "xcb-icccm")
+        elseif("${comp}" STREQUAL "IMAGE")
+            list(APPEND pkgConfigModules "xcb-image")
+        elseif("${comp}" STREQUAL "KEYSYMS")
+            list(APPEND pkgConfigModules "xcb-keysyms")
+        elseif("${comp}" STREQUAL "RANDR")
+            list(APPEND pkgConfigModules "xcb-randr")
+        elseif("${comp}" STREQUAL "RENDER")
+            list(APPEND pkgConfigModules "xcb-render")
+        elseif("${comp}" STREQUAL "RENDERUTIL")
+            list(APPEND pkgConfigModules "xcb-renderutil")
+        elseif("${comp}" STREQUAL "SHAPE")
+            list(APPEND pkgConfigModules "xcb-shape")
+        elseif("${comp}" STREQUAL "SHM")
+            list(APPEND pkgConfigModules "xcb-shm")
+        elseif("${comp}" STREQUAL "SYNC")
+            list(APPEND pkgConfigModules "xcb-sync")
+        elseif("${comp}" STREQUAL "UTIL")
+            list(APPEND pkgConfigModules "xcb-util")
+        elseif("${comp}" STREQUAL "XFIXES")
+            list(APPEND pkgConfigModules "xcb-xfixes")
+        elseif("${comp}" STREQUAL "XTEST")
+            list(APPEND pkgConfigModules "xcb-xtest")
+        elseif("${comp}" STREQUAL "XV")
+            list(APPEND pkgConfigModules "xcb-xv")
+        elseif("${comp}" STREQUAL "XINERAMA")
+            list(APPEND pkgConfigModules "xcb-xinerama")
+        elseif("${comp}" STREQUAL "XKB")
+            list(APPEND pkgConfigModules "xcb-xkb")
+        endif()
+    endif()
+endforeach()
+
+
+if(DEFINED unknownComponents)
+   set(msgType STATUS)
+   if(XCB_FIND_REQUIRED)
+      set(msgType FATAL_ERROR)
+   endif()
+   if(NOT XCB_FIND_QUIETLY)
+      message(${msgType} "XCB: requested unknown components ${unknownComponents}")
+   endif()
+   return()
+endif()
+
+macro(_xcb_handle_component _comp)
+    set(_header )
+    set(_lib )
+    if("${_comp}" STREQUAL "XCB")
+        set(_header "xcb/xcb.h")
+        set(_lib "xcb")
+    elseif("${_comp}" STREQUAL "COMPOSITE")
+        set(_header "xcb/composite.h")
+        set(_lib "xcb-composite")
+    elseif("${_comp}" STREQUAL "DAMAGE")
+        set(_header "xcb/damage.h")
+        set(_lib "xcb-damage")
+    elseif("${_comp}" STREQUAL "DRI2")
+        set(_header "xcb/dri2.h")
+        set(_lib "xcb-dri2")
+    elseif("${_comp}" STREQUAL "EWMH")
+        set(_header "xcb/xcb_ewmh.h")
+        set(_lib "xcb-ewmh")
+    elseif("${_comp}" STREQUAL "GLX")
+        set(_header "xcb/glx.h")
+        set(_lib "xcb-glx")
+    elseif("${_comp}" STREQUAL "ICCCM")
+        set(_header "xcb/xcb_icccm.h")
+        set(_lib "xcb-icccm")
+    elseif("${_comp}" STREQUAL "IMAGE")
+        set(_header "xcb/xcb_image.h")
+        set(_lib "xcb-image")
+    elseif("${_comp}" STREQUAL "KEYSYMS")
+        set(_header "xcb/xcb_keysyms.h")
+        set(_lib "xcb-keysyms")
+    elseif("${_comp}" STREQUAL "RANDR")
+        set(_header "xcb/randr.h")
+        set(_lib "xcb-randr")
+    elseif("${_comp}" STREQUAL "RENDER")
+        set(_header "xcb/render.h")
+        set(_lib "xcb-render")
+    elseif("${_comp}" STREQUAL "RENDERUTIL")
+        set(_header "xcb/xcb_renderutil.h")
+        set(_lib "xcb-render-util")
+    elseif("${_comp}" STREQUAL "SHAPE")
+        set(_header "xcb/shape.h")
+        set(_lib "xcb-shape")
+    elseif("${_comp}" STREQUAL "SHM")
+        set(_header "xcb/shm.h")
+        set(_lib "xcb-shm")
+    elseif("${_comp}" STREQUAL "SYNC")
+        set(_header "xcb/sync.h")
+        set(_lib "xcb-sync")
+    elseif("${_comp}" STREQUAL "UTIL")
+        set(_header "xcb/xcb_util.h")
+        set(_lib "xcb-util")
+    elseif("${_comp}" STREQUAL "XFIXES")
+        set(_header "xcb/xfixes.h")
+        set(_lib "xcb-xfixes")
+    elseif("${_comp}" STREQUAL "XTEST")
+        set(_header "xcb/xtest.h")
+        set(_lib "xcb-xtest")
+    elseif("${_comp}" STREQUAL "XV")
+        set(_header "xcb/xv.h")
+        set(_lib "xcb-xv")
+    elseif("${_comp}" STREQUAL "XINERAMA")
+        set(_header "xcb/xinerama.h")
+        set(_lib "xcb-xinerama")
+    elseif("${_comp}" STREQUAL "XKB")
+        set(_header "xcb/xkb.h")
+        set(_lib "xcb-xkb")
+    endif()
+
+    find_path(XCB_${_comp}_INCLUDE_DIR NAMES ${_header} HINTS ${PKG_XCB_INCLUDE_DIRS})
+    find_library(XCB_${_comp}_LIBRARY NAMES ${_lib} HINTS ${PKG_XCB_LIBRARY_DIRS})
+
+    if(XCB_${_comp}_INCLUDE_DIR AND XCB_${_comp}_LIBRARY)
+        list(APPEND XCB_INCLUDE_DIRS ${XCB_${_comp}_INCLUDE_DIR})
+        list(APPEND XCB_LIBRARIES ${XCB_${_comp}_LIBRARY})
+        if (NOT XCB_FIND_QUIETLY)
+            message(STATUS "XCB[${_comp}]: Found component ${_comp}")
+        endif()
+    endif()
+
+    if(XCB_FIND_REQUIRED_${_comp})
+        list(APPEND requiredComponents XCB_${_comp}_FOUND)
+    endif()
+
+    # Bypass developer warning that the first argument to find_package_handle_standard_args (XCB_...) does not match
+    # the name of the calling package (XCB)
+    # https://cmake.org/cmake/help/v3.17/module/FindPackageHandleStandardArgs.html
+    set(FPHSA_NAME_MISMATCHED TRUE)
+    find_package_handle_standard_args(XCB_${_comp} REQUIRED_VARS XCB_${_comp}_LIBRARY XCB_${_comp}_INCLUDE_DIR)
+
+    mark_as_advanced(XCB_${_comp}_LIBRARY XCB_${_comp}_INCLUDE_DIR)
+
+    # compatibility for old variable naming
+    set(XCB_${_comp}_INCLUDE_DIRS ${XCB_${_comp}_INCLUDE_DIR})
+    set(XCB_${_comp}_LIBRARIES ${XCB_${_comp}_LIBRARY})
+endmacro()
+
+IF (NOT WIN32)
+    include(FindPackageHandleStandardArgs)
+    # Use pkg-config to get the directories and then use these values
+    # in the FIND_PATH() and FIND_LIBRARY() calls
+    find_package(PkgConfig)
+    pkg_check_modules(PKG_XCB QUIET ${pkgConfigModules})
+
+    set(XCB_DEFINITIONS ${PKG_XCB_CFLAGS})
+
+    foreach(comp ${comps})
+        _xcb_handle_component(${comp})
+    endforeach()
+
+    if(XCB_INCLUDE_DIRS)
+        list(REMOVE_DUPLICATES XCB_INCLUDE_DIRS)
+    endif()
+
+    find_package_handle_standard_args(XCB REQUIRED_VARS XCB_LIBRARIES XCB_INCLUDE_DIRS ${requiredComponents})
+
+    # compatibility for old variable naming
+    set(XCB_INCLUDE_DIR ${XCB_INCLUDE_DIRS})
+
+ENDIF (NOT WIN32)
diff --git a/lib/xpp/generators/TODO b/lib/xpp/generators/TODO
new file mode 100644 (file)
index 0000000..6176683
--- /dev/null
@@ -0,0 +1,53 @@
+TODO:
+
+* Build with python 3
+
+* valueparams
+
+* serialized fields (e.g. xcb_sync_create_alarm_value_list_serialize)
+  (is this necessary?)
+
+* specialize iterator for non-vector data structures:
+  Instead of converting to vector, check if it is possible to send the data
+  directly through the socket (e.g. map { key, value }:
+  for (k,v : map) { socket_send(v); } ...
+
+* XInput Event handling: Am I doing this right? (Multiple switches etc.)
+
+* Adapter classes for drawable, window, pixmap, atom, font, etc.
+
+$ grep xidtype *.xml
+damage.xml:  <xidtype name="DAMAGE" />
+glx.xml:     <xidtype name="PIXMAP" />
+glx.xml:     <xidtype name="CONTEXT" />
+glx.xml:     <xidtype name="PBUFFER" />
+glx.xml:     <xidtype name="WINDOW" />
+glx.xml:     <xidtype name="FBCONFIG" />
+present.xml: <xidtype name="EVENT"/>
+randr.xml:   <xidtype name="MODE" />
+randr.xml:   <xidtype name="CRTC" />
+randr.xml:   <xidtype name="OUTPUT" />
+randr.xml:   <xidtype name="PROVIDER" />
+record.xml:  <xidtype name="CONTEXT" />
+render.xml:  <xidtype name="GLYPHSET" />
+render.xml:  <xidtype name="PICTURE" />
+render.xml:  <xidtype name="PICTFORMAT" />
+shm.xml:     <xidtype name="SEG" />
+sync.xml:    <xidtype name="ALARM" />
+sync.xml:    <xidtype name="COUNTER" />
+sync.xml:    <xidtype name="FENCE" />
+xfixes.xml:  <xidtype name="REGION" />
+xfixes.xml:  <xidtype name="BARRIER" />
+xprint.xml:  <xidtype name="PCONTEXT" />
+xproto.xml:  <xidtype name="WINDOW" />
+xproto.xml:  <xidtype name="PIXMAP" />
+xproto.xml:  <xidtype name="CURSOR" />
+xproto.xml:  <xidtype name="FONT" />
+xproto.xml:  <xidtype name="GCONTEXT" />
+xproto.xml:  <xidtype name="COLORMAP" />
+xproto.xml:  <xidtype name="ATOM" />
+xvmc.xml:    <xidtype name="CONTEXT" />
+xvmc.xml:    <xidtype name="SURFACE" />
+xvmc.xml:    <xidtype name="SUBPICTURE" />
+xv.xml:      <xidtype name="PORT" />
+xv.xml:      <xidtype name="ENCODING" />
diff --git a/lib/xpp/generators/accessor.py b/lib/xpp/generators/accessor.py
new file mode 100644 (file)
index 0000000..4bcc4cb
--- /dev/null
@@ -0,0 +1,126 @@
+from resource_classes import _resource_classes
+
+_templates = {}
+
+_templates['iter_fixed'] = \
+"""\
+xpp::generic::iterator<Connection,
+                       %s,
+                       SIGNATURE(%s_%s),
+                       SIGNATURE(%s_%s_length)>\
+"""
+
+_templates['iter_variable'] = \
+"""\
+xpp::generic::iterator<Connection,
+                       %s,
+                       SIGNATURE(%s_next),
+                       SIGNATURE(%s_sizeof),
+                       SIGNATURE(%s_%s_iterator)>\
+"""
+
+_templates['list'] = \
+"""\
+    xpp::generic::list<Connection,
+                       %s_reply_t,
+                       %s
+                      >
+    %s(void)
+    {
+      return xpp::generic::list<Connection,
+                                %s_reply_t,
+                                %s
+                               >(%s);
+    }\
+"""
+
+_templates['string_accessor'] = \
+'''\
+    std::string
+    %s(void)
+    {
+      return std::string(%s_%s(this->get().get()),
+                         %s_%s_length(this->get().get()));
+    }
+'''
+
+def _string_accessor(member, c_name):
+    return _templates['string_accessor'] % \
+            (member, c_name, member, c_name, member)
+
+class Accessor(object):
+    def __init__(self, is_fixed=False, is_string=False, is_variable=False, \
+                 member="", c_type="", return_type="", iter_name="", c_name=""):
+
+        self.is_fixed = is_fixed
+        self.is_string = is_string
+        self.is_variable = is_variable
+
+        self.member = member
+        self.c_type = c_type
+        self.return_type = return_type
+        self.iter_name = iter_name
+        self.c_name = c_name
+
+        self.object_type = self.c_type.replace("xcb_", "").replace("_t", "").upper()
+
+        if self.c_type == "void":
+            self.return_type = "Type"
+        elif self.object_type in _resource_classes:
+            self.return_type = self.member.capitalize()
+        else:
+            self.return_type = self.c_type
+
+    def __str__(self):
+        if self.is_fixed:
+            return self.list(self.iter_fixed())
+        elif self.is_variable:
+            return self.list(self.iter_variable())
+        elif self.is_string:
+            return self.string()
+        else:
+            return ""
+
+
+    def iter_fixed(self):
+        return_type = self.return_type
+
+        return _templates['iter_fixed'] \
+                % (return_type,
+                   self.c_name, self.member,
+                   self.c_name, self.member)
+
+
+    def iter_variable(self):
+        return _templates['iter_variable'] \
+                % (self.c_type,
+                   self.iter_name,
+                   self.iter_name,
+                   self.c_name, self.member)
+
+
+    def list(self, iterator):
+        template = "    template<typename Type" if self.c_type == "void" else ""
+
+        # template<typename Children = xcb_window_t>
+        if self.object_type in _resource_classes:
+            template += ", " if template != "" else "    template<typename "
+            template += self.member.capitalize() + " = " + self.c_type
+
+        template += ">\n" if template != "" else ""
+
+        c_tor_params = "this->m_c, this->get()"
+
+        fst_iterator = "\n                       ".join(iterator.split('\n'))
+        snd_iterator = "\n                                ".join(iterator.split('\n'))
+
+        return template + _templates['list'] \
+                % (self.c_name,
+                   fst_iterator,
+                   self.member,
+                   self.c_name,
+                   snd_iterator,
+                   c_tor_params)
+
+    def string(self):
+        return _string_accessor(self.member, self.c_name)
diff --git a/lib/xpp/generators/cpp_client.py b/lib/xpp/generators/cpp_client.py
new file mode 100644 (file)
index 0000000..ce9c9e7
--- /dev/null
@@ -0,0 +1,3125 @@
+#!/usr/bin/env python
+# vim: set ts=4 sws=4 sw=4:
+
+from xml.etree.ElementTree import *
+from os.path import basename
+from functools import reduce
+import getopt
+import os
+import sys
+import errno
+import time
+import re
+import collections
+
+from utils import \
+        get_namespace, \
+        get_ext_name, \
+        _n_item, \
+        _ext
+
+from cppevent import CppEvent
+from cpperror import CppError
+from accessor import Accessor
+from parameter import Parameter
+from cpprequest import CppRequest
+from objectclass import ObjectClass
+from interfaceclass import InterfaceClass
+from extensionclass import ExtensionClass
+from resource_classes import _resource_classes
+
+_cpp_request_names = []
+_cpp_request_objects = {}
+
+# see c_open()
+_interface_class = InterfaceClass()
+
+_cpp_events = []
+_cpp_errors = []
+
+_object_classes = {}
+
+for i, v in enumerate(_resource_classes):
+    _object_classes[i] = ObjectClass(v)
+
+        # , "render" : collections.OrderedDict( \
+        #         {
+        #         } )
+        # , "xinerama" : collections.OrderedDict( \
+        #         {
+        #         } )
+
+# Jump to the bottom of this file for the main routine
+
+# Some hacks to make the API more readable, and to keep backwards compability
+_cname_re = re.compile('([A-Z0-9][a-z]+|[A-Z0-9]+(?![a-z])|[a-z]+)')
+_cname_special_cases = {'DECnet':'decnet'}
+
+_extension_special_cases = ['XPrint', 'XCMisc', 'BigRequests']
+
+_xcb_includes = \
+    { "xproto" : "xcb.h"
+    }
+
+
+_cplusplus_annoyances = {'class' : '_class',
+                         'new'   : '_new',
+                         'delete': '_delete',
+                         'explicit': '_explicit'}
+_c_keywords = {'default' : '_default'}
+
+_hlines = []
+_hlevel = 0
+_clines = []
+_clevel = 0
+_ns = None
+
+# global variable to keep track of serializers and
+# switch data types due to weird dependencies
+finished_serializers = []
+finished_sizeof = []
+finished_switch = []
+
+# keeps enum objects so that we can refer to them when generating manpages.
+enums = {}
+
+manpaths = False
+
+def _get_xcb_include(ns):
+    # return _xcb_includes.get(ns, _ns.ext_name.lower()) + ".h"
+    return _xcb_includes.get(ns, _ns.file.replace(".xml", ".h"))
+
+# def get_namespace(_ns):
+#     if _ns.is_ext:
+#         return get_ext_name(_ns.ext_name)
+#     else:
+#         return "x"
+
+def _h(fmt, *args):
+    '''
+    Writes the given line to the header file.
+    '''
+    _hlines[_hlevel].append(fmt % args)
+
+def _c(fmt, *args):
+    '''
+    Writes the given line to the source file.
+    '''
+    _clines[_clevel].append(fmt % args)
+
+def _hc(fmt, *args):
+    '''
+    Writes the given line to both the header and source files.
+    '''
+    _h(fmt, *args)
+    _c(fmt, *args)
+
+# XXX See if this level thing is really necessary.
+def _h_setlevel(idx):
+    '''
+    Changes the array that header lines are written to.
+    Supports writing different sections of the header file.
+    '''
+    global _hlevel
+    while len(_hlines) <= idx:
+        _hlines.append([])
+    _hlevel = idx
+
+def _c_setlevel(idx):
+    '''
+    Changes the array that source lines are written to.
+    Supports writing to different sections of the source file.
+    '''
+    global _clevel
+    while len(_clines) <= idx:
+        _clines.append([])
+    _clevel = idx
+
+
+def _cpp(str):
+    '''
+    Checks for certain C++ reserved words and fixes them.
+    '''
+    if str in _cplusplus_annoyances:
+        return _cplusplus_annoyances[str]
+    elif str in _c_keywords:
+        return  _c_keywords[str]
+    else:
+        return str
+
+
+def _n(list):
+    '''
+    Does C-name conversion on a tuple of strings.
+    Different behavior depending on length of tuple, extension/not extension, etc.
+    Basically C-name converts the individual pieces, then joins with underscores.
+    '''
+    if len(list) == 1:
+        parts = list
+    elif len(list) == 2:
+        parts = [list[0], _n_item(list[1])]
+    elif _ns.is_ext:
+        parts = [list[0], _ext(list[1])] + [_n_item(i) for i in list[2:]]
+    else:
+        parts = [list[0]] + [_n_item(i) for i in list[1:]]
+    return '_'.join(parts).lower()
+
+def _t(list):
+    '''
+    Does C-name conversion on a tuple of strings representing a type.
+    Same as _n but adds a "_t" on the end.
+    '''
+    if len(list) == 1:
+        parts = list
+    elif len(list) == 2:
+        parts = [list[0], _n_item(list[1]), 't']
+    elif _ns.is_ext:
+        parts = [list[0], _ext(list[1])] + [_n_item(i) for i in list[2:]] + ['t']
+    else:
+        parts = [list[0]] + [_n_item(i) for i in list[1:]] + ['t']
+    return '_'.join(parts).lower()
+
+
+def c_open(self):
+    '''
+    Exported function that handles module open.
+    Opens the files and writes out the auto-generated comment,
+    header file includes, etc.
+    '''
+    global _ns
+    _ns = self.namespace
+    # _ns.header = "test"
+    _ns.c_ext_global_name = _n(_ns.prefix + ('id',))
+
+    _interface_class.set_namespace(_ns)
+
+    # Build the type-name collision avoidance table used by c_enum
+    build_collision_table()
+
+    _h_setlevel(0)
+    _c_setlevel(0)
+
+    # _h('#ifndef EXPORT_%s_MIXINS', get_namespace(_ns).upper())
+    _h('#ifndef XPP_%s_HPP', get_namespace(_ns).upper())
+    _h('#define XPP_%s_HPP', get_namespace(_ns).upper())
+    _h('')
+    _h('#include <string>')
+    _h('#include <vector>')
+    _h('')
+
+    _h('#include <xcb/' + _get_xcb_include(_ns.header.lower()) + '>')
+
+    _h('')
+
+    _h('#include "xpp/generic.hpp"')
+
+    # if not _ns.is_ext:
+    #     _h('#include "xproto-stub.hpp"')
+    # _h('#include "../core/generic/resource.hpp"')
+    _h('')
+    _h('namespace xpp { namespace %s {' % get_namespace(_ns))
+    # _h('class window;')
+    # _h('namespace %s {', get_namespace(_ns))
+    # _h('')
+
+def c_close(self):
+    '''
+    Exported function that handles module close.
+    Writes out all the stored content lines, then closes the files.
+    '''
+
+    _h('')
+    _h(ExtensionClass(_ns).make_class())
+
+
+    for cpp_event in _cpp_events:
+        _h(cpp_event.make_class())
+
+    _h('')
+
+    for cpp_error in _cpp_errors:
+        _h(cpp_error.make_class())
+
+    _h('')
+
+    for name in _cpp_request_names:
+        _h("%s", _cpp_request_objects[name].make_class())
+
+    _h('')
+
+    for key in _object_classes:
+        _h(_object_classes[key].make_inline())
+        # sys.stderr.write(_object_classes[key].make_inline())
+
+
+
+    _h('')
+    _h(_interface_class.make_proto())
+    # sys.stderr.write(_interface_class.make_proto())
+
+    _h('')
+    # _h('}; // namespace xpp')
+    _h("} } // namespace xpp::%s" % get_namespace(_ns))
+
+    _h('')
+    _h('#endif // XPP_%s_HPP', get_namespace(_ns).upper())
+
+    # Write header file
+    hfile = sys.stdout
+    for list in _hlines:
+        for line in list:
+            hfile.write(line)
+            hfile.write('\n')
+    # hfile.close()
+
+    # cfile = sys.stderr
+    # for list in _clines:
+    #     for line in list:
+    #         cfile.write(line)
+    #         cfile.write('\n')
+
+def build_collision_table():
+    global namecount
+    namecount = {}
+
+    for v in list(module.types.values()):
+        name = _t(v[0])
+        namecount[name] = (namecount.get(name) or 0) + 1
+
+def c_enum(self, name):
+    '''
+    Exported function that handles enum declarations.
+    '''
+
+    enums[name] = self
+
+    tname = _t(name)
+    if namecount[tname] > 1:
+        tname = _t(name + ('enum',))
+
+    _h_setlevel(0)
+    _h('')
+    _h('typedef enum %s {', tname)
+
+    count = len(self.values)
+
+    for (enam, eval) in self.values:
+        count = count - 1
+        equals = ' = ' if eval != '' else ''
+        comma = ',' if count > 0 else ''
+        doc = ''
+        if hasattr(self, "doc") and self.doc and enam in self.doc.fields:
+            doc = '\n/**< %s */\n' % self.doc.fields[enam]
+        _h('    %s%s%s%s%s', _n(name + (enam,)).upper(), equals, eval, comma, doc)
+
+    _h('} %s;', tname)
+
+def _c_type_setup(self, name, postfix):
+    '''
+    Sets up all the C-related state by adding additional data fields to
+    all Field and Type objects.  Here is where we figure out most of our
+    variable and function names.
+
+    Recurses into child fields and list member types.
+    '''
+    # Do all the various names in advance
+
+    self.c_type = _t(name + postfix)
+    self.c_wiretype = 'char' if self.c_type == 'void' else self.c_type
+
+    self.c_iterator_type = _t(name + ('iterator',))
+    self.c_next_name = _n(name + ('next',))
+    self.c_end_name = _n(name + ('end',))
+
+    self.c_request_name = _n(name)
+    self.c_checked_name = _n(name)
+    self.c_unchecked_name = _n(name)
+    # self.c_checked_name = _n(name + ('checked',))
+    # self.c_unchecked_name = _n(name + ('unchecked',))
+    self.c_reply_name = _n(name + ('reply',))
+    self.c_reply_type = _t(name + ('reply',))
+    self.c_cookie_type = _t(name + ('cookie',))
+    self.c_reply_fds_name = _n(name + ('reply_fds',))
+
+    self.need_aux = False
+    self.need_serialize = False
+    self.need_sizeof = False
+
+    self.c_aux_name = _n(name + ('aux',))
+    self.c_aux_checked_name = _n(name)
+    self.c_aux_unchecked_name = _n(name)
+    # self.c_aux_checked_name = _n(name + ('aux', 'checked'))
+    # self.c_aux_unchecked_name = _n(name + ('aux', 'unchecked'))
+    self.c_serialize_name = _n(name + ('serialize',))
+    self.c_unserialize_name = _n(name + ('unserialize',))
+    self.c_unpack_name = _n(name + ('unpack',))
+    self.c_sizeof_name = _n(name + ('sizeof',))
+
+    # special case: structs where variable size fields are followed by fixed size fields
+    self.var_followed_by_fixed_fields = False
+
+    if self.is_switch:
+        self.need_serialize = True
+        self.c_container = 'struct'
+        for bitcase in self.bitcases:
+            bitcase.c_field_name = _cpp(bitcase.field_name)
+            bitcase_name = bitcase.field_type if bitcase.type.has_name else name
+            _c_type_setup(bitcase.type, bitcase_name, ())
+
+    elif self.is_container:
+
+        self.c_container = 'union' if self.is_union else 'struct'
+        prev_varsized_field = None
+        prev_varsized_offset = 0
+        first_field_after_varsized = None
+
+        for field in self.fields:
+            _c_type_setup(field.type, field.field_type, ())
+            if field.type.is_list:
+                _c_type_setup(field.type.member, field.field_type, ())
+                if (field.type.nmemb is None):
+                    self.need_sizeof = True
+
+            field.c_field_type = _t(field.field_type)
+            field.c_field_const_type = ('' if field.type.nmemb == 1 else 'const ') + field.c_field_type
+            field.c_field_name = _cpp(field.field_name)
+            field.c_subscript = '[%d]' % field.type.nmemb if (field.type.nmemb and field.type.nmemb > 1) else ''
+            field.c_pointer = ' ' if field.type.nmemb == 1 else '*'
+
+            # correct the c_pointer field for variable size non-list types
+            if not field.type.fixed_size() and field.c_pointer == ' ':
+                field.c_pointer = '*'
+            if field.type.is_list and not field.type.member.fixed_size():
+                field.c_pointer = '*'
+
+            if field.type.is_switch:
+                field.c_pointer = '*'
+                field.c_field_const_type = 'const ' + field.c_field_type
+                self.need_aux = True
+            elif not field.type.fixed_size() and not field.type.is_bitcase:
+                self.need_sizeof = True
+
+            field.c_iterator_type = _t(field.field_type + ('iterator',))      # xcb_fieldtype_iterator_t
+            field.c_iterator_name = _n(name + (field.field_name, 'iterator')) # xcb_container_field_iterator
+            field.c_accessor_name = _n(name + (field.field_name,))            # xcb_container_field
+            field.c_length_name = _n(name + (field.field_name, 'length'))     # xcb_container_field_length
+            field.c_end_name = _n(name + (field.field_name, 'end'))           # xcb_container_field_end
+
+            field.prev_varsized_field = prev_varsized_field
+            field.prev_varsized_offset = prev_varsized_offset
+
+            if prev_varsized_offset == 0:
+                first_field_after_varsized = field
+            field.first_field_after_varsized = first_field_after_varsized
+
+            if field.type.fixed_size():
+                prev_varsized_offset += field.type.size
+                # special case: intermixed fixed and variable size fields
+                if prev_varsized_field is not None and not field.type.is_pad and field.wire:
+                    if not self.is_union:
+                        self.need_serialize = True
+                        self.var_followed_by_fixed_fields = True
+            else:
+                self.last_varsized_field = field
+                prev_varsized_field = field
+                prev_varsized_offset = 0
+
+            if self.var_followed_by_fixed_fields:
+                if field.type.fixed_size():
+                    field.prev_varsized_field = None
+
+    if self.need_serialize:
+        # when _unserialize() is wanted, create _sizeof() as well for consistency reasons
+        self.need_sizeof = True
+
+    # as switch does never appear at toplevel,
+    # continue here with type construction
+    if self.is_switch:
+        if self.c_type not in finished_switch:
+            finished_switch.append(self.c_type)
+            # special: switch C structs get pointer fields for variable-sized members
+            _c_complex(self)
+            for bitcase in self.bitcases:
+                bitcase_name = bitcase.type.name if bitcase.type.has_name else name
+                _c_accessors(bitcase.type, bitcase_name, bitcase_name)
+                # no list with switch as element, so no call to
+                # _c_iterator(field.type, field_name) necessary
+
+    if not self.is_bitcase:
+        if self.need_serialize:
+            if self.c_serialize_name not in finished_serializers:
+                finished_serializers.append(self.c_serialize_name)
+                _c_serialize('serialize', self)
+
+                # _unpack() and _unserialize() are only needed for special cases:
+                #   switch -> unpack
+                #   special cases -> unserialize
+                if self.is_switch or self.var_followed_by_fixed_fields:
+                    _c_serialize('unserialize', self)
+
+        # if self.need_sizeof:
+        #     if self.c_sizeof_name not in finished_sizeof:
+        #         if not module.namespace.is_ext or self.name[:2] == module.namespace.prefix:
+        #             finished_sizeof.append(self.c_sizeof_name)
+        #             _c_serialize('sizeof', self)
+# _c_type_setup()
+
+def _c_helper_absolute_name(prefix, field=None):
+    """
+    turn prefix, which is a list of tuples (name, separator, Type obj) into a string
+    representing a valid name in C (based on the context)
+    if field is not None, append the field name as well
+    """
+    prefix_str = ''
+    for name, sep, obj in prefix:
+        prefix_str += name
+        if '' == sep:
+            sep = '->'
+            if ((obj.is_bitcase and obj.has_name) or     # named bitcase
+                (obj.is_switch and len(obj.parents)>1)):
+                sep = '.'
+        prefix_str += sep
+    if field is not None:
+        prefix_str += _cpp(field.field_name)
+    return prefix_str
+# _c_absolute_name
+
+def _c_helper_field_mapping(complex_type, prefix, flat=False):
+    """
+    generate absolute names, based on prefix, for all fields starting from complex_type
+    if flat == True, nested complex types are not taken into account
+    """
+    all_fields = {}
+    if complex_type.is_switch:
+        for b in complex_type.bitcases:
+            if b.type.has_name:
+                switch_name, switch_sep, switch_type = prefix[-1]
+                bitcase_prefix = prefix + [(b.type.name[-1], '.', b.type)]
+            else:
+                bitcase_prefix = prefix
+
+            if (True==flat and not b.type.has_name) or False==flat:
+                all_fields.update(_c_helper_field_mapping(b.type, bitcase_prefix, flat))
+    else:
+        for f in complex_type.fields:
+            fname = _c_helper_absolute_name(prefix, f)
+            if f.field_name in all_fields:
+                raise Exception("field name %s has been registered before" % f.field_name)
+
+            all_fields[f.field_name] = (fname, f)
+            if f.type.is_container and flat==False:
+                if f.type.is_bitcase and not f.type.has_name:
+                    new_prefix = prefix
+                elif f.type.is_switch and len(f.type.parents)>1:
+                    # nested switch gets another separator
+                    new_prefix = prefix+[(f.c_field_name, '.', f.type)]
+                else:
+                    new_prefix = prefix+[(f.c_field_name, '->', f.type)]
+                all_fields.update(_c_helper_field_mapping(f.type, new_prefix, flat))
+
+    return all_fields
+# _c_field_mapping()
+
+def _c_helper_resolve_field_names (prefix):
+    """
+    get field names for all objects in the prefix array
+    """
+    all_fields = {}
+    tmp_prefix = []
+    # look for fields in the remaining containers
+    for idx, p in enumerate(prefix):
+        name, sep, obj = p
+        if ''==sep:
+            # sep can be preset in prefix, if not, make a sensible guess
+            sep = '.' if (obj.is_switch or obj.is_bitcase) else '->'
+            # exception: 'toplevel' object (switch as well!) always have sep '->'
+            sep = '->' if idx<1 else sep
+        if not obj.is_bitcase or (obj.is_bitcase and obj.has_name):
+            tmp_prefix.append((name, sep, obj))
+        all_fields.update(_c_helper_field_mapping(obj, tmp_prefix, flat=True))
+
+    return all_fields
+# _c_helper_resolve_field_names
+
+def get_expr_fields(self):
+    """
+    get the Fields referenced by switch or list expression
+    """
+    def get_expr_field_names(expr):
+        if expr.op is None:
+            if expr.lenfield_name is not None:
+                return [expr.lenfield_name]
+            else:
+                # constant value expr
+                return []
+        else:
+            if expr.op == '~':
+                return get_expr_field_names(expr.rhs)
+            elif expr.op == 'popcount':
+                return get_expr_field_names(expr.rhs)
+            elif expr.op == 'sumof':
+                # sumof expr references another list,
+                # we need that list's length field here
+                field = None
+                for f in expr.lenfield_parent.fields:
+                    if f.field_name == expr.lenfield_name:
+                        field = f
+                        break
+                if field is None:
+                    raise Exception("list field '%s' referenced by sumof not found" % expr.lenfield_name)
+                # referenced list + its length field
+                return [expr.lenfield_name] + get_expr_field_names(field.type.expr)
+            elif expr.op == 'enumref':
+                return []
+            else:
+                return get_expr_field_names(expr.lhs) + get_expr_field_names(expr.rhs)
+    # get_expr_field_names()
+
+    # resolve the field names with the parent structure(s)
+    unresolved_fields_names = get_expr_field_names(self.expr)
+
+    # construct prefix from self
+    prefix = [('', '', p) for p in self.parents]
+    if self.is_container:
+        prefix.append(('', '', self))
+
+    all_fields = _c_helper_resolve_field_names (prefix)
+    resolved_fields_names = [x for x in unresolved_fields_names if x in list(all_fields.keys())]
+    if len(unresolved_fields_names) != len(resolved_fields_names):
+        raise Exception("could not resolve all fields for %s" % self.name)
+
+    resolved_fields = [all_fields[n][1] for n in resolved_fields_names]
+    return resolved_fields
+# get_expr_fields()
+
+def resolve_expr_fields(complex_obj):
+    """
+    find expr fields appearing in complex_obj and descendents that cannot be resolved within complex_obj
+    these are normally fields that need to be given as function parameters
+    """
+    all_fields = []
+    expr_fields = []
+    unresolved = []
+
+    for field in complex_obj.fields:
+        all_fields.append(field)
+        if field.type.is_switch or field.type.is_list:
+            expr_fields += get_expr_fields(field.type)
+        if field.type.is_container:
+            expr_fields += resolve_expr_fields(field.type)
+
+    # try to resolve expr fields
+    for e in expr_fields:
+        if e not in all_fields and e not in unresolved:
+            unresolved.append(e)
+    return unresolved
+# resolve_expr_fields()
+
+def get_serialize_params(context, self, buffer_var='_buffer', aux_var='_aux'):
+    """
+    functions like _serialize(), _unserialize(), and _unpack() sometimes need additional parameters:
+    E.g. in order to unpack switch, extra parameters might be needed to evaluate the switch
+    expression. This function tries to resolve all fields within a structure, and returns the
+    unresolved fields as the list of external parameters.
+    """
+    def add_param(params, param):
+        if param not in params:
+            params.append(param)
+
+    # collect all fields into param_fields
+    param_fields = []
+    wire_fields = []
+
+    for field in self.fields:
+        if field.visible:
+            # the field should appear as a parameter in the function call
+            param_fields.append(field)
+        if field.wire and not field.auto:
+            if field.type.fixed_size() and not self.is_switch:
+                # field in the xcb_out structure
+                wire_fields.append(field)
+        # fields like 'pad0' are skipped!
+
+    # in case of switch, parameters always contain any fields referenced in the switch expr
+    # we do not need any variable size fields here, as the switch data type contains both
+    # fixed and variable size fields
+    if self.is_switch:
+        param_fields = get_expr_fields(self)
+
+    # _serialize()/_unserialize()/_unpack() function parameters
+    # note: don't use set() for params, it is unsorted
+    params = []
+
+    # 1. the parameter for the void * buffer
+    if  'serialize' == context:
+        params.append(('void', '**', buffer_var))
+    elif context in ('unserialize', 'unpack', 'sizeof'):
+        params.append(('const void', '*', buffer_var))
+
+    # 2. any expr fields that cannot be resolved within self and descendants
+    unresolved_fields = resolve_expr_fields(self)
+    for f in unresolved_fields:
+        add_param(params, (f.c_field_type, '', f.c_field_name))
+
+    # 3. param_fields contain the fields necessary to evaluate the switch expr or any other fields
+    #    that do not appear in the data type struct
+    for p in param_fields:
+        if self.is_switch:
+            typespec = p.c_field_const_type
+            pointerspec = p.c_pointer
+            add_param(params, (typespec, pointerspec, p.c_field_name))
+        else:
+            if p.visible and not p.wire and not p.auto:
+                typespec = p.c_field_type
+                pointerspec = ''
+                add_param(params, (typespec, pointerspec, p.c_field_name))
+
+    # 4. aux argument
+    if 'serialize' == context:
+        add_param(params, ('const %s' % self.c_type, '*', aux_var))
+    elif 'unserialize' == context:
+        add_param(params, ('%s' % self.c_type, '**', aux_var))
+    elif 'unpack' == context:
+        add_param(params, ('%s' % self.c_type, '*', aux_var))
+
+    # 5. switch contains all variable size fields as struct members
+    #    for other data types though, these have to be supplied separately
+    #    this is important for the special case of intermixed fixed and
+    #    variable size fields
+    if not self.is_switch and 'serialize' == context:
+        for p in param_fields:
+            if not p.type.fixed_size():
+                add_param(params, (p.c_field_const_type, '*', p.c_field_name))
+
+    return (param_fields, wire_fields, params)
+# get_serialize_params()
+
+def _c_serialize_helper_insert_padding(context, code_lines, space, postpone):
+    code_lines.append('%s    /* insert padding */' % space)
+    code_lines.append('%s    xcb_pad = -xcb_block_len & (xcb_align_to - 1);' % space)
+#    code_lines.append('%s    printf("automatically inserting padding: %%%%d\\n", xcb_pad);' % space)
+    code_lines.append('%s    xcb_buffer_len += xcb_block_len + xcb_pad;' % space)
+
+    if not postpone:
+        code_lines.append('%s    if (0 != xcb_pad) {' % space)
+
+        if 'serialize' == context:
+            code_lines.append('%s        xcb_parts[xcb_parts_idx].iov_base = xcb_pad0;' % space)
+            code_lines.append('%s        xcb_parts[xcb_parts_idx].iov_len = xcb_pad;' % space)
+            code_lines.append('%s        xcb_parts_idx++;' % space)
+        elif context in ('unserialize', 'unpack', 'sizeof'):
+            code_lines.append('%s        xcb_tmp += xcb_pad;' % space)
+
+        code_lines.append('%s        xcb_pad = 0;' % space)
+        code_lines.append('%s    }' % space)
+
+    code_lines.append('%s    xcb_block_len = 0;' % space)
+
+    # keep tracking of xcb_parts entries for serialize
+    return 1
+# _c_serialize_helper_insert_padding()
+
+def _c_serialize_helper_switch(context, self, complex_name,
+                               code_lines, temp_vars,
+                               space, prefix):
+    count = 0
+    switch_expr = _c_accessor_get_expr(self.expr, None)
+
+    for b in self.bitcases:
+        len_expr = len(b.type.expr)
+        for n, expr in enumerate(b.type.expr):
+            bitcase_expr = _c_accessor_get_expr(expr, None)
+            # only one <enumref> in the <bitcase>
+            if len_expr == 1:
+                code_lines.append('    if(%s & %s) {' % (switch_expr, bitcase_expr))
+            # multiple <enumref> in the <bitcase>
+            elif n == 0: # first
+                code_lines.append('    if((%s & %s) ||' % (switch_expr, bitcase_expr))
+            elif len_expr == (n + 1): # last
+                code_lines.append('       (%s & %s)) {' % (switch_expr, bitcase_expr))
+            else: # between first and last
+                code_lines.append('       (%s & %s) ||' % (switch_expr, bitcase_expr))
+
+        b_prefix = prefix
+        if b.type.has_name:
+            b_prefix = prefix + [(b.c_field_name, '.', b.type)]
+
+        count += _c_serialize_helper_fields(context, b.type,
+                                            code_lines, temp_vars,
+                                            "%s    " % space,
+                                            b_prefix,
+                                            is_bitcase = True)
+        code_lines.append('    }')
+
+#    if 'serialize' == context:
+#        count += _c_serialize_helper_insert_padding(context, code_lines, space, False)
+#    elif context in ('unserialize', 'unpack', 'sizeof'):
+#        # padding
+#        code_lines.append('%s    xcb_pad = -xcb_block_len & 3;' % space)
+#        code_lines.append('%s    xcb_buffer_len += xcb_block_len + xcb_pad;' % space)
+
+    return count
+# _c_serialize_helper_switch
+
+def _c_serialize_helper_switch_field(context, self, field, c_switch_variable, prefix):
+    """
+    handle switch by calling _serialize() or _unpack(), depending on context
+    """
+    # switch is handled by this function as a special case
+    param_fields, wire_fields, params = get_serialize_params(context, self)
+    field_mapping = _c_helper_field_mapping(self, prefix)
+    prefix_str = _c_helper_absolute_name(prefix)
+
+    # find the parameters that need to be passed to _serialize()/_unpack():
+    # all switch expr fields must be given as parameters
+    args = get_expr_fields(field.type)
+    # length fields for variable size types in switch, normally only some of need
+    # need to be passed as parameters
+    switch_len_fields = resolve_expr_fields(field.type)
+
+    # a switch field at this point _must_ be a bitcase field
+    # we require that bitcases are "self-contiguous"
+    bitcase_unresolved = resolve_expr_fields(self)
+    if len(bitcase_unresolved) != 0:
+        raise Exception('unresolved fields within bitcase is not supported at this point')
+
+    # get the C names for the parameters
+    c_field_names = ''
+    for a in switch_len_fields:
+        c_field_names += "%s, " % field_mapping[a.c_field_name][0]
+    for a in args:
+        c_field_names += "%s, " % field_mapping[a.c_field_name][0]
+
+    # call _serialize()/_unpack() to determine the actual size
+    if 'serialize' == context:
+        length = "%s(&%s, %s&%s%s)" % (field.type.c_serialize_name, c_switch_variable,
+                                       c_field_names, prefix_str, field.c_field_name)
+    elif context in ('unserialize', 'unpack'):
+        length = "%s(xcb_tmp, %s&%s%s)" % (field.type.c_unpack_name,
+                                           c_field_names, prefix_str, field.c_field_name)
+
+    return length
+# _c_serialize_helper_switch_field()
+
+def _c_serialize_helper_list_field(context, self, field,
+                                   code_lines, temp_vars,
+                                   space, prefix):
+    """
+    helper function to cope with lists of variable length
+    """
+    expr = field.type.expr
+    prefix_str = _c_helper_absolute_name(prefix)
+    param_fields, wire_fields, params = get_serialize_params('sizeof', self)
+    param_names = [p[2] for p in params]
+
+    expr_fields_names = [f.field_name for f in get_expr_fields(field.type)]
+    resolved = [x for x in expr_fields_names if x in param_names]
+    unresolved = [x for x in expr_fields_names if x not in param_names]
+
+    field_mapping = {}
+    for r in resolved:
+        field_mapping[r] = (r, None)
+
+    if len(unresolved)>0:
+        tmp_prefix = prefix
+        if len(tmp_prefix)==0:
+            raise Exception("found an empty prefix while resolving expr field names for list %s",
+                            field.c_field_name)
+
+        field_mapping.update(_c_helper_resolve_field_names(prefix))
+        resolved += [x for x in unresolved if x in field_mapping]
+        unresolved = [x for x in unresolved if x not in field_mapping]
+        if len(unresolved)>0:
+            raise Exception('could not resolve the length fields required for list %s' % field.c_field_name)
+
+    list_length = _c_accessor_get_expr(expr, field_mapping)
+
+    # default: list with fixed size elements
+    length = '%s * sizeof(%s)' % (list_length, field.type.member.c_wiretype)
+
+    # list with variable-sized elements
+    if not field.type.member.fixed_size():
+        length = ''
+        if context in ('unserialize', 'sizeof', 'unpack'):
+            int_i = '    unsigned int i;'
+            xcb_tmp_len = '    unsigned int xcb_tmp_len;'
+            if int_i not in temp_vars:
+                temp_vars.append(int_i)
+            if xcb_tmp_len not in temp_vars:
+                temp_vars.append(xcb_tmp_len)
+            # loop over all list elements and call sizeof repeatedly
+            # this should be a bit faster than using the iterators
+            code_lines.append("%s    for(i=0; i<%s; i++) {" % (space, list_length))
+            code_lines.append("%s        xcb_tmp_len = %s(xcb_tmp);" %
+                              (space, field.type.c_sizeof_name))
+            code_lines.append("%s        xcb_block_len += xcb_tmp_len;" % space)
+            code_lines.append("%s        xcb_tmp += xcb_tmp_len;" % space)
+            code_lines.append("%s    }" % space)
+
+        elif 'serialize' == context:
+            code_lines.append('%s    xcb_parts[xcb_parts_idx].iov_len = 0;' % space)
+            code_lines.append('%s    xcb_tmp = (char *) %s%s;' % (space, prefix_str, field.c_field_name))
+            code_lines.append('%s    for(i=0; i<%s; i++) { ' % (space, list_length))
+            code_lines.append('%s        xcb_block_len = %s(xcb_tmp);' % (space, field.type.c_sizeof_name))
+            code_lines.append('%s        xcb_parts[xcb_parts_idx].iov_len += xcb_block_len;' % space)
+            code_lines.append('%s    }' % space)
+            code_lines.append('%s    xcb_block_len = xcb_parts[xcb_parts_idx].iov_len;' % space)
+
+    return length
+# _c_serialize_helper_list_field()
+
+def _c_serialize_helper_fields_fixed_size(context, self, field,
+                                          code_lines, temp_vars,
+                                          space, prefix):
+    # keep the C code a bit more readable by giving the field name
+    if not self.is_bitcase:
+        code_lines.append('%s    /* %s.%s */' % (space, self.c_type, field.c_field_name))
+    else:
+        scoped_name = [p[2].c_type if idx==0 else p[0] for idx, p in enumerate(prefix)]
+        typename = reduce(lambda x,y: "%s.%s" % (x, y), scoped_name)
+        code_lines.append('%s    /* %s.%s */' % (space, typename, field.c_field_name))
+
+    abs_field_name = _c_helper_absolute_name(prefix, field)
+    # default for simple cases: call sizeof()
+    length = "sizeof(%s)" % field.c_field_type
+
+    if context in ('unserialize', 'unpack', 'sizeof'):
+        # default: simple cast
+        value = '    %s = *(%s *)xcb_tmp;' % (abs_field_name, field.c_field_type)
+
+        # padding - we could probably just ignore it
+        if field.type.is_pad and field.type.nmemb > 1:
+            value = ''
+            for i in range(field.type.nmemb):
+                code_lines.append('%s    %s[%d] = *(%s *)xcb_tmp;' %
+                                  (space, abs_field_name, i, field.c_field_type))
+            # total padding = sizeof(pad0) * nmemb
+            length += " * %d" % field.type.nmemb
+
+        if field.type.is_list:
+            # no such case in the protocol, cannot be tested and therefore ignored for now
+            raise Exception('list with fixed number of elemens unhandled in _unserialize()')
+
+    elif 'serialize' == context:
+        value = '    xcb_parts[xcb_parts_idx].iov_base = (char *) '
+
+        if field.type.is_expr:
+            # need to register a temporary variable for the expression in case we know its type
+            if field.type.c_type is None:
+                raise Exception("type for field '%s' (expression '%s') unkown" %
+                                (field.field_name, _c_accessor_get_expr(field.type.expr, prefix)))
+
+            temp_vars.append('    %s xcb_expr_%s = %s;' % (field.type.c_type, _cpp(field.field_name),
+                                                           _c_accessor_get_expr(field.type.expr, prefix)))
+            value += "&xcb_expr_%s;" % _cpp(field.field_name)
+
+        elif field.type.is_pad:
+            if field.type.nmemb == 1:
+                value += "&xcb_pad;"
+            else:
+                # we could also set it to 0, see definition of xcb_send_request()
+                value = '    xcb_parts[xcb_parts_idx].iov_base = xcb_pad0;'
+                length += "*%d" % field.type.nmemb
+
+        else:
+            # non-list type with fixed size
+            if field.type.nmemb == 1:
+                value += "&%s;" % (abs_field_name)
+
+            # list with nmemb (fixed size) elements
+            else:
+                value += '%s;' % (abs_field_name)
+                length = '%d' % field.type.nmemb
+
+    return (value, length)
+# _c_serialize_helper_fields_fixed_size()
+
+def _c_serialize_helper_fields_variable_size(context, self, field,
+                                             code_lines, temp_vars,
+                                             space, prefix):
+    prefix_str = _c_helper_absolute_name(prefix)
+
+    if context in ('unserialize', 'unpack', 'sizeof'):
+        value = ''
+        var_field_name = 'xcb_tmp'
+
+        # special case: intermixed fixed and variable size fields
+        if self.var_followed_by_fixed_fields and 'unserialize' == context:
+            value = '    %s = (%s *)xcb_tmp;' % (field.c_field_name, field.c_field_type)
+            temp_vars.append('    %s *%s;' % (field.type.c_type, field.c_field_name))
+        # special case: switch
+        if 'unpack' == context:
+            value = '    %s%s = (%s *)xcb_tmp;' % (prefix_str, field.c_field_name, field.c_field_type)
+
+    elif 'serialize' == context:
+        # variable size fields appear as parameters to _serialize() if the
+        # 'toplevel' container is not a switch
+        prefix_string = prefix_str if prefix[0][2].is_switch else ''
+        var_field_name = "%s%s" % (prefix_string, field.c_field_name)
+        value = '    xcb_parts[xcb_parts_idx].iov_base = (char *) %s;' % var_field_name
+
+    length = ''
+
+    code_lines.append('%s    /* %s */' % (space, field.c_field_name))
+
+    if field.type.is_list:
+        if value != '':
+            # in any context, list is already a pointer, so the default assignment is ok
+            code_lines.append("%s%s" % (space, value))
+            value = ''
+        length = _c_serialize_helper_list_field(context, self, field,
+                                                code_lines, temp_vars,
+                                                space, prefix)
+
+    elif field.type.is_switch:
+        value = ''
+        if context == 'serialize':
+            # the _serialize() function allocates the correct amount memory if given a NULL pointer
+            value = '    xcb_parts[xcb_parts_idx].iov_base = (char *)0;'
+        length = _c_serialize_helper_switch_field(context, self, field,
+                                                  'xcb_parts[xcb_parts_idx].iov_base',
+                                                  prefix)
+
+    else:
+        # in all remaining special cases - call _sizeof()
+        length = "%s(%s)" % (field.type.c_sizeof_name, var_field_name)
+
+    return (value, length)
+# _c_serialize_helper_fields_variable_size
+
+def _c_serialize_helper_fields(context, self,
+                               code_lines, temp_vars,
+                               space, prefix, is_bitcase):
+    count = 0
+    need_padding = False
+    prev_field_was_variable = False
+
+    for field in self.fields:
+        if not field.visible:
+            if not ((field.wire and not field.auto) or 'unserialize' == context):
+                continue
+
+        # switch/bitcase: fixed size fields must be considered explicitly
+        if field.type.fixed_size():
+            if self.is_bitcase or self.var_followed_by_fixed_fields:
+                if prev_field_was_variable and need_padding:
+                    # insert padding
+#                    count += _c_serialize_helper_insert_padding(context, code_lines, space,
+#                                                                self.var_followed_by_fixed_fields)
+                    prev_field_was_variable = False
+
+                # prefix for fixed size fields
+                fixed_prefix = prefix
+
+                value, length = _c_serialize_helper_fields_fixed_size(context, self, field,
+                                                                      code_lines, temp_vars,
+                                                                      space, fixed_prefix)
+            else:
+                continue
+
+        # fields with variable size
+        else:
+            # switch/bitcase: always calculate padding before and after variable sized fields
+            if need_padding or is_bitcase:
+                count += _c_serialize_helper_insert_padding(context, code_lines, space,
+                                                            self.var_followed_by_fixed_fields)
+
+            value, length = _c_serialize_helper_fields_variable_size(context, self, field,
+                                                                     code_lines, temp_vars,
+                                                                     space, prefix)
+            prev_field_was_variable = True
+
+        # save (un)serialization C code
+        if '' != value:
+            code_lines.append('%s%s' % (space, value))
+
+        if field.type.fixed_size():
+            if is_bitcase or self.var_followed_by_fixed_fields:
+                # keep track of (un)serialized object's size
+                code_lines.append('%s    xcb_block_len += %s;' % (space, length))
+                if context in ('unserialize', 'unpack', 'sizeof'):
+                    code_lines.append('%s    xcb_tmp += %s;' % (space, length))
+        else:
+            # variable size objects or bitcase:
+            #   value & length might have been inserted earlier for special cases
+            if '' != length:
+                # special case: intermixed fixed and variable size fields
+                if (not field.type.fixed_size() and
+                    self.var_followed_by_fixed_fields and 'unserialize' == context):
+                    temp_vars.append('    int %s_len;' % field.c_field_name)
+                    code_lines.append('%s    %s_len = %s;' % (space, field.c_field_name, length))
+                    code_lines.append('%s    xcb_block_len += %s_len;' % (space, field.c_field_name))
+                    code_lines.append('%s    xcb_tmp += %s_len;' % (space, field.c_field_name))
+                else:
+                    code_lines.append('%s    xcb_block_len += %s;' % (space, length))
+                    # increase pointer into the byte stream accordingly
+                    if context in ('unserialize', 'sizeof', 'unpack'):
+                        code_lines.append('%s    xcb_tmp += xcb_block_len;' % space)
+
+        if 'serialize' == context:
+            if '' != length:
+                code_lines.append('%s    xcb_parts[xcb_parts_idx].iov_len = %s;' % (space, length))
+            code_lines.append('%s    xcb_parts_idx++;' % space)
+            count += 1
+
+        code_lines.append('%s    xcb_align_to = ALIGNOF(%s);' % (space, 'char' if field.c_field_type == 'void' else field.c_field_type))
+
+        need_padding = True
+        if self.var_followed_by_fixed_fields:
+            need_padding = False
+
+    return count
+# _c_serialize_helper_fields()
+
+def _c_serialize_helper(context, complex_type,
+                        code_lines, temp_vars,
+                        space='', prefix=[]):
+    # count tracks the number of fields to serialize
+    count = 0
+
+    if hasattr(complex_type, 'type'):
+        self = complex_type.type
+        complex_name = complex_type.name
+    else:
+        self = complex_type
+        if self.var_followed_by_fixed_fields and 'unserialize' == context:
+            complex_name = 'xcb_out'
+        else:
+            complex_name = '_aux'
+
+    # special case: switch is serialized by evaluating each bitcase separately
+    if self.is_switch:
+        count += _c_serialize_helper_switch(context, self, complex_name,
+                                            code_lines, temp_vars,
+                                            space, prefix)
+
+    # all other data types can be evaluated one field a time
+    else:
+        # unserialize & fixed size fields: simply cast the buffer to the respective xcb_out type
+        if context in ('unserialize', 'unpack', 'sizeof') and not self.var_followed_by_fixed_fields:
+            code_lines.append('%s    xcb_block_len += sizeof(%s);' % (space, self.c_type))
+            code_lines.append('%s    xcb_tmp += xcb_block_len;' % space)
+            code_lines.append('%s    xcb_buffer_len += xcb_block_len;' % space)
+            code_lines.append('%s    xcb_block_len = 0;' % space)
+
+        count += _c_serialize_helper_fields(context, self,
+                                            code_lines, temp_vars,
+                                            space, prefix, False)
+    # "final padding"
+    count += _c_serialize_helper_insert_padding(context, code_lines, space, False)
+
+    return count
+# _c_serialize_helper()
+
+def _c_serialize(context, self):
+    """
+    depending on the context variable, generate _serialize(), _unserialize(), _unpack(), or _sizeof()
+    for the ComplexType variable self
+    """
+    _h_setlevel(1)
+    _c_setlevel(1)
+
+    # _hc('')
+    # # _serialize() returns the buffer size
+    # _hc('int')
+
+    # sys.stderr.write('int')
+
+
+    if self.is_switch and 'unserialize' == context:
+        context = 'unpack'
+
+    cases = { 'serialize'   : self.c_serialize_name,
+              'unserialize' : self.c_unserialize_name,
+              'unpack'      : self.c_unpack_name,
+              'sizeof'      : self.c_sizeof_name }
+    func_name = cases[context]
+
+    param_fields, wire_fields, params = get_serialize_params(context, self)
+    variable_size_fields = 0
+    # maximum space required for type definition of function arguments
+    maxtypelen = 0
+
+    # determine N(variable_fields)
+    for field in param_fields:
+        # if self.is_switch, treat all fields as if they are variable sized
+        if not field.type.fixed_size() or self.is_switch:
+            variable_size_fields += 1
+    # determine maxtypelen
+    for p in params:
+        maxtypelen = max(maxtypelen, len(p[0]) + len(p[1]))
+
+    # write to .c/.h
+    indent = ' '*(len(func_name)+2)
+    param_str = []
+    for p in params:
+        typespec, pointerspec, field_name = p
+        spacing = ' '*(maxtypelen-len(typespec)-len(pointerspec))
+        param_str.append("%s%s%s  %s%s  /**< */" % (indent, typespec, spacing, pointerspec, field_name))
+    # insert function name
+    param_str[0] = "%s (%s" % (func_name, param_str[0].strip())
+    param_str = ["%s," % x for x in param_str]
+
+    # >>> THIS! <<< #
+    # for s in param_str[:-1]:
+    #     sys.stderr.write(s)
+
+    #     _hc(s)
+    # _h("%s);" % param_str[-1].rstrip(','))
+
+    # >>> AND THAT! <<< #
+    # sys.stderr.write("%s);" % param_str[-1].rstrip(','))
+    # >>> AND THAT! <<< #
+    _c("%s)" % param_str[-1].rstrip(','))
+    _c('{')
+
+    code_lines = []
+    temp_vars = []
+    prefix = []
+
+    if 'serialize' == context:
+        if not self.is_switch and not self.var_followed_by_fixed_fields:
+            _c('    %s *xcb_out = *_buffer;', self.c_type)
+            _c('    unsigned int xcb_out_pad = -sizeof(%s) & 3;', self.c_type)
+            _c('    unsigned int xcb_buffer_len = sizeof(%s) + xcb_out_pad;', self.c_type)
+            _c('    unsigned int xcb_align_to = 0;')
+        else:
+            _c('    char *xcb_out = *_buffer;')
+            _c('    unsigned int xcb_buffer_len = 0;')
+            _c('    unsigned int xcb_align_to = 0;')
+        prefix = [('_aux', '->', self)]
+        aux_ptr = 'xcb_out'
+
+    elif context in ('unserialize', 'unpack'):
+        _c('    char *xcb_tmp = (char *)_buffer;')
+        if not self.is_switch:
+            if not self.var_followed_by_fixed_fields:
+                _c('    const %s *_aux = (%s *)_buffer;', self.c_type, self.c_type)
+                prefix = [('_aux', '->', self)]
+            else:
+                _c('    %s xcb_out;', self.c_type)
+                prefix = [('xcb_out', '.', self)]
+        else:
+            aux_var = '_aux' # default for unpack: single pointer
+            # note: unserialize not generated for switch
+            if 'unserialize' == context:
+                aux_var = '(*_aux)' # unserialize: double pointer (!)
+            prefix = [(aux_var, '->', self)]
+        aux_ptr = '*_aux'
+        _c('    unsigned int xcb_buffer_len = 0;')
+        _c('    unsigned int xcb_block_len = 0;')
+        _c('    unsigned int xcb_pad = 0;')
+        _c('    unsigned int xcb_align_to = 0;')
+
+    elif 'sizeof' == context:
+        param_names = [p[2] for p in params]
+        if self.is_switch:
+            # switch: call _unpack()
+            _c('    %s _aux;', self.c_type)
+            _c('    return %s(%s, &_aux);', self.c_unpack_name, reduce(lambda x,y: "%s, %s" % (x, y), param_names))
+            _c('}')
+            return
+        elif self.var_followed_by_fixed_fields:
+            # special case: call _unserialize()
+            _c('    return %s(%s, NULL);', self.c_unserialize_name, reduce(lambda x,y: "%s, %s" % (x, y), param_names))
+            _c('}')
+            return
+        else:
+            _c('    char *xcb_tmp = (char *)_buffer;')
+            prefix = [('_aux', '->', self)]
+
+    count = _c_serialize_helper(context, self, code_lines, temp_vars, prefix=prefix)
+    # update variable size fields (only important for context=='serialize'
+    variable_size_fields = count
+    if 'serialize' == context:
+        temp_vars.append('    unsigned int xcb_pad = 0;')
+        temp_vars.append('    char xcb_pad0[3] = {0, 0, 0};')
+        temp_vars.append('    struct iovec xcb_parts[%d];' % count)
+        temp_vars.append('    unsigned int xcb_parts_idx = 0;')
+        temp_vars.append('    unsigned int xcb_block_len = 0;')
+        temp_vars.append('    unsigned int i;')
+        temp_vars.append('    char *xcb_tmp;')
+    elif 'sizeof' == context:
+        # neither switch nor intermixed fixed and variable size fields:
+        # evaluate parameters directly
+        if not (self.is_switch or self.var_followed_by_fixed_fields):
+
+            # look if we have to declare an '_aux' variable at all
+            if len([x for x in code_lines if x.find('_aux')!=-1])>0:
+                if not self.var_followed_by_fixed_fields:
+                    _c('    const %s *_aux = (%s *)_buffer;', self.c_type, self.c_type)
+                else:
+                    _c('    %s *_aux = malloc(sizeof(%s));', self.c_type, self.c_type)
+
+            _c('    unsigned int xcb_buffer_len = 0;')
+            _c('    unsigned int xcb_block_len = 0;')
+            _c('    unsigned int xcb_pad = 0;')
+            _c('    unsigned int xcb_align_to = 0;')
+
+    _c('')
+    for t in temp_vars:
+        _c(t)
+    _c('')
+    for l in code_lines:
+        _c(l)
+
+    # variable sized fields have been collected, now
+    # allocate memory and copy everything into a continuous memory area
+    # note: this is not necessary in case of unpack
+    if context in ('serialize', 'unserialize'):
+        # unserialize: check for sizeof-only invocation
+        if 'unserialize' == context:
+            _c('')
+            _c('    if (NULL == _aux)')
+            _c('        return xcb_buffer_len;')
+
+        _c('')
+        _c('    if (NULL == %s) {', aux_ptr)
+        _c('        /* allocate memory */')
+        _c('        %s = malloc(xcb_buffer_len);', aux_ptr)
+        if 'serialize' == context:
+            _c('        *_buffer = xcb_out;')
+        _c('    }')
+        _c('')
+
+        # serialize: handle variable size fields in a loop
+        if 'serialize' == context:
+            if not self.is_switch and not self.var_followed_by_fixed_fields:
+                if len(wire_fields)>0:
+                    _c('    *xcb_out = *_aux;')
+            # copy variable size fields into the buffer
+            if variable_size_fields > 0:
+                # xcb_out padding
+                if not self.is_switch and not self.var_followed_by_fixed_fields:
+                    _c('    xcb_tmp = (char*)++xcb_out;')
+                    _c('    xcb_tmp += xcb_out_pad;')
+                else:
+                    _c('    xcb_tmp = xcb_out;')
+
+                # variable sized fields
+                _c('    for(i=0; i<xcb_parts_idx; i++) {')
+                _c('        if (0 != xcb_parts[i].iov_base && 0 != xcb_parts[i].iov_len)')
+                _c('            memcpy(xcb_tmp, xcb_parts[i].iov_base, xcb_parts[i].iov_len);')
+                _c('        if (0 != xcb_parts[i].iov_len)')
+                _c('            xcb_tmp += xcb_parts[i].iov_len;')
+                _c('    }')
+
+        # unserialize: assign variable size fields individually
+        if 'unserialize' == context:
+            _c('    xcb_tmp = ((char *)*_aux)+xcb_buffer_len;')
+            param_fields.reverse()
+            for field in param_fields:
+                if not field.type.fixed_size():
+                    _c('    xcb_tmp -= %s_len;', field.c_field_name)
+                    _c('    memmove(xcb_tmp, %s, %s_len);', field.c_field_name, field.c_field_name)
+            _c('    *%s = xcb_out;', aux_ptr)
+
+    _c('')
+    _c('    return xcb_buffer_len;')
+    _c('}')
+# _c_serialize()
+
+def _c_iterator_get_end(field, accum):
+    '''
+    Figures out what C code is needed to find the end of a variable-length structure field.
+    For nested structures, recurses into its last variable-sized field.
+    For lists, calls the end function
+    '''
+    if field.type.is_container:
+        accum = field.c_accessor_name + '(' + accum + ')'
+        return _c_iterator_get_end(field.type.last_varsized_field, accum)
+    if field.type.is_list:
+        # XXX we can always use the first way
+        if field.type.member.is_simple:
+            return field.c_end_name + '(' + accum + ')'
+        else:
+            return field.type.member.c_end_name + '(' + field.c_iterator_name + '(' + accum + '))'
+
+def _c_iterator(self, name):
+    '''
+    Declares the iterator structure and next/end functions for a given type.
+    '''
+    _h_setlevel(0)
+    _h('')
+    _h('/**')
+    _h(' * @brief %s', self.c_iterator_type)
+    _h(' **/')
+    _h('typedef struct %s {', self.c_iterator_type)
+    _h('    %s *data; /**<  */', self.c_type)
+    _h('    int%s rem; /**<  */', ' ' * (len(self.c_type) - 2))
+    _h('    int%s index; /**<  */', ' ' * (len(self.c_type) - 2))
+    _h('} %s;', self.c_iterator_type)
+
+    _h_setlevel(1)
+    _c_setlevel(1)
+    _h('')
+    _h('/**')
+    _h(' * Get the next element of the iterator')
+    _h(' * @param i Pointer to a %s', self.c_iterator_type)
+    _h(' *')
+    _h(' * Get the next element in the iterator. The member rem is')
+    _h(' * decreased by one. The member data points to the next')
+    _h(' * element. The member index is increased by sizeof(%s)', self.c_type)
+    _h(' */')
+    _c('')
+    _hc('')
+    _hc('/*****************************************************************************')
+    _hc(' **')
+    _hc(' ** void %s', self.c_next_name)
+    _hc(' ** ')
+    _hc(' ** @param %s *i', self.c_iterator_type)
+    _hc(' ** @returns void')
+    _hc(' **')
+    _hc(' *****************************************************************************/')
+    _hc(' ')
+    _hc('void')
+    _h('%s (%s *i  /**< */);', self.c_next_name, self.c_iterator_type)
+    _c('%s (%s *i  /**< */)', self.c_next_name, self.c_iterator_type)
+    _c('{')
+
+    if not self.fixed_size():
+        _c('    %s *R = i->data;', self.c_type)
+
+        if self.is_union:
+            # FIXME - how to determine the size of a variable size union??
+            _c('    /* FIXME - determine the size of the union %s */', self.c_type)
+        else:
+            if self.need_sizeof:
+                _c('    xcb_generic_iterator_t child;')
+                _c('    child.data = (%s *)(((char *)R) + %s(R));',
+                   self.c_type, self.c_sizeof_name)
+                _c('    i->index = (char *) child.data - (char *) i->data;')
+            else:
+                _c('    xcb_generic_iterator_t child = %s;', _c_iterator_get_end(self.last_varsized_field, 'R'))
+                _c('    i->index = child.index;')
+            _c('    --i->rem;')
+            _c('    i->data = (%s *) child.data;', self.c_type)
+
+    else:
+        _c('    --i->rem;')
+        _c('    ++i->data;')
+        _c('    i->index += sizeof(%s);', self.c_type)
+
+    _c('}')
+
+    _h('')
+    _h('/**')
+    _h(' * Return the iterator pointing to the last element')
+    _h(' * @param i An %s', self.c_iterator_type)
+    _h(' * @return  The iterator pointing to the last element')
+    _h(' *')
+    _h(' * Set the current element in the iterator to the last element.')
+    _h(' * The member rem is set to 0. The member data points to the')
+    _h(' * last element.')
+    _h(' */')
+    _c('')
+    _hc('')
+    _hc('/*****************************************************************************')
+    _hc(' **')
+    _hc(' ** xcb_generic_iterator_t %s', self.c_end_name)
+    _hc(' ** ')
+    _hc(' ** @param %s i', self.c_iterator_type)
+    _hc(' ** @returns xcb_generic_iterator_t')
+    _hc(' **')
+    _hc(' *****************************************************************************/')
+    _hc(' ')
+    _hc('xcb_generic_iterator_t')
+    _h('%s (%s i  /**< */);', self.c_end_name, self.c_iterator_type)
+    _c('%s (%s i  /**< */)', self.c_end_name, self.c_iterator_type)
+    _c('{')
+    _c('    xcb_generic_iterator_t ret;')
+
+    if self.fixed_size():
+        _c('    ret.data = i.data + i.rem;')
+        _c('    ret.index = i.index + ((char *) ret.data - (char *) i.data);')
+        _c('    ret.rem = 0;')
+    else:
+        _c('    while(i.rem > 0)')
+        _c('        %s(&i);', self.c_next_name)
+        _c('    ret.data = i.data;')
+        _c('    ret.rem = i.rem;')
+        _c('    ret.index = i.index;')
+
+    _c('    return ret;')
+    _c('}')
+
+def _c_accessor_get_length(expr, field_mapping=None):
+    '''
+    Figures out what C code is needed to get a length field.
+    The field_mapping parameter can be used to change the absolute name of a length field.
+    For fields that follow a variable-length field, use the accessor.
+    Otherwise, just reference the structure field directly.
+    '''
+
+    lenfield_name = expr.lenfield_name
+    if lenfield_name is not None:
+        if field_mapping is not None and lenfield_name in field_mapping:
+            lenfield_name = field_mapping[lenfield_name][0]
+
+    if expr.lenfield is not None and expr.lenfield.prev_varsized_field is not None:
+        # special case: variable and fixed size fields are intermixed
+        # if the lenfield is among the fixed size fields, there is no need
+        # to call a special accessor function like <expr.lenfield.c_accessor_name + '(' + prefix + ')'>
+        return field_mapping(expr.lenfield_name)
+    elif expr.lenfield_name is not None:
+        return lenfield_name
+    else:
+        return str(expr.nmemb)
+
+def _c_accessor_get_expr(expr, field_mapping):
+    '''
+    Figures out what C code is needed to get the length of a list field.
+    The field_mapping parameter can be used to change the absolute name of a length field.
+    Recurses for math operations.
+    Returns bitcount for value-mask fields.
+    Otherwise, uses the value of the length field.
+    '''
+    lenexp = _c_accessor_get_length(expr, field_mapping)
+
+    if expr.op == '~':
+        return '(' + '~' + _c_accessor_get_expr(expr.rhs, field_mapping) + ')'
+    elif expr.op == 'popcount':
+        return 'xcb_popcount(' + _c_accessor_get_expr(expr.rhs, field_mapping) + ')'
+    elif expr.op == 'enumref':
+        enum_name = expr.lenfield_type.name
+        constant_name = expr.lenfield_name
+        c_name = _n(enum_name + (constant_name,)).upper()
+        return c_name
+    elif expr.op == 'sumof':
+        # locate the referenced list object
+        list_obj = expr.lenfield_type
+        field = None
+        for f in expr.lenfield_parent.fields:
+            if f.field_name == expr.lenfield_name:
+                field = f
+                break
+
+        if field is None:
+            raise Exception("list field '%s' referenced by sumof not found" % expr.lenfield_name)
+        list_name = field_mapping[field.c_field_name][0]
+        c_length_func = "%s(%s)" % (field.c_length_name, list_name)
+        # note: xcb_sumof() has only been defined for integers
+        c_length_func = _c_accessor_get_expr(field.type.expr, field_mapping)
+        return 'xcb_sumof(%s, %s)' % (list_name, c_length_func)
+    elif expr.op != None:
+        return ('(' + _c_accessor_get_expr(expr.lhs, field_mapping) +
+                ' ' + expr.op + ' ' +
+                _c_accessor_get_expr(expr.rhs, field_mapping) + ')')
+    elif expr.bitfield:
+        return 'xcb_popcount(' + lenexp + ')'
+    else:
+        return lenexp
+
+def type_pad_type(type):
+    if type == 'void':
+        return 'char'
+    return type
+
+def _c_accessors_field(self, field):
+    '''
+    Declares the accessor functions for a non-list field that follows a variable-length field.
+    '''
+    c_type = self.c_type
+
+    # special case: switch
+    switch_obj = self if self.is_switch else None
+    if self.is_bitcase:
+        switch_obj = self.parents[-1]
+    if switch_obj is not None:
+        c_type = switch_obj.c_type
+
+    if field.type.is_simple:
+        _hc('')
+        _hc('')
+        _hc('/*****************************************************************************')
+        _hc(' ** ')
+        _hc(' ** %s %s', field.c_field_type, field.c_accessor_name)
+        _hc(' ** ')
+        _hc(' ** @param const %s *R', c_type)
+        _hc(' ** @returns %s', field.c_field_type)
+        _hc(' **')
+        _hc(' *****************************************************************************/')
+        _hc(' ')
+        _hc('%s', field.c_field_type)
+        _h('%s (const %s *R  /**< */);', field.c_accessor_name, c_type)
+        _c('%s (const %s *R  /**< */)', field.c_accessor_name, c_type)
+        _c('{')
+        if field.prev_varsized_field is None:
+            _c('    return (%s *) (R + 1);', field.c_field_type)
+        else:
+            _c('    xcb_generic_iterator_t prev = %s;', _c_iterator_get_end(field.prev_varsized_field, 'R'))
+            _c('    return * (%s *) ((char *) prev.data + XCB_TYPE_PAD(%s, prev.index) + %d);',
+               field.c_field_type, type_pad_type(field.first_field_after_varsized.type.c_type), field.prev_varsized_offset)
+        _c('}')
+    else:
+        _hc('')
+        _hc('')
+        _hc('/*****************************************************************************')
+        _hc(' **')
+        _hc(' ** %s * %s', field.c_field_type, field.c_accessor_name)
+        _hc(' ** ')
+        _hc(' ** @param const %s *R', c_type)
+        _hc(' ** @returns %s *', field.c_field_type)
+        _hc(' **')
+        _hc(' *****************************************************************************/')
+        _hc(' ')
+        if field.type.is_switch and switch_obj is None:
+            return_type = 'void *'
+        else:
+            return_type = '%s *' % field.c_field_type
+
+        _hc(return_type)
+        _h('%s (const %s *R  /**< */);', field.c_accessor_name, c_type)
+        _c('%s (const %s *R  /**< */)', field.c_accessor_name, c_type)
+        _c('{')
+        if field.prev_varsized_field is None:
+            _c('    return (%s) (R + 1);', return_type)
+            # note: the special case 'variable fields followed by fixed size fields'
+            #       is not of any consequence here, since the ordering gets
+            #       'corrected' in the reply function
+        else:
+            _c('    xcb_generic_iterator_t prev = %s;', _c_iterator_get_end(field.prev_varsized_field, 'R'))
+            _c('    return (%s) ((char *) prev.data + XCB_TYPE_PAD(%s, prev.index) + %d);',
+               return_type, type_pad_type(field.first_field_after_varsized.type.c_type), field.prev_varsized_offset)
+        _c('}')
+
+
+def _c_accessors_list(self, field):
+    '''
+    Declares the accessor functions for a list field.
+    Declares a direct-accessor function only if the list members are fixed size.
+    Declares length and get-iterator functions always.
+    '''
+    list = field.type
+    c_type = self.c_type
+
+    # special case: switch
+    # in case of switch, 2 params have to be supplied to certain accessor functions:
+    #   1. the anchestor object (request or reply)
+    #   2. the (anchestor) switch object
+    # the reason is that switch is either a child of a request/reply or nested in another switch,
+    # so whenever we need to access a length field, we might need to refer to some anchestor type
+    switch_obj = self if self.is_switch else None
+    if self.is_bitcase:
+        switch_obj = self.parents[-1]
+    if switch_obj is not None:
+        c_type = switch_obj.c_type
+
+    params = []
+    fields = {}
+    parents = self.parents if hasattr(self, 'parents') else [self]
+    # 'R': parents[0] is always the 'toplevel' container type
+    params.append(('const %s *R' % parents[0].c_type, parents[0]))
+    fields.update(_c_helper_field_mapping(parents[0], [('R', '->', parents[0])], flat=True))
+    # auxiliary object for 'R' parameters
+    R_obj = parents[0]
+
+    if switch_obj is not None:
+        # now look where the fields are defined that are needed to evaluate
+        # the switch expr, and store the parent objects in accessor_params and
+        # the fields in switch_fields
+
+        # 'S': name for the 'toplevel' switch
+        toplevel_switch = parents[1]
+        params.append(('const %s *S' % toplevel_switch.c_type, toplevel_switch))
+        fields.update(_c_helper_field_mapping(toplevel_switch, [('S', '->', toplevel_switch)], flat=True))
+
+        # initialize prefix for everything "below" S
+        prefix_str = '/* %s */ S' % toplevel_switch.name[-1]
+        prefix = [(prefix_str, '->', toplevel_switch)]
+
+        # look for fields in the remaining containers
+        for p in parents[2:] + [self]:
+            # the separator between parent and child is always '.' here,
+            # because of nested switch statements
+            if not p.is_bitcase or (p.is_bitcase and p.has_name):
+                prefix.append((p.name[-1], '.', p))
+            fields.update(_c_helper_field_mapping(p, prefix, flat=True))
+
+        # auxiliary object for 'S' parameter
+        S_obj = parents[1]
+
+    _h_setlevel(1)
+
+    if list.member.fixed_size():
+        idx = 1 if switch_obj is not None else 0
+
+    if switch_obj is not None:
+        spacing = ' '*(len(field.c_length_name)+2)
+        length = _c_accessor_get_expr(field.type.expr, fields)
+    else:
+        length = _c_accessor_get_expr(field.type.expr, fields)
+
+    request_name = _ext(_n_item(self.name[-1]))
+
+    if not request_name in _cpp_request_objects:
+        # print >> sys.stderr, 'WARN: Skipping undefined _cpp_request_object %s' % (request_name,)
+        return
+
+    if list.member.fixed_size() and not self.is_bitcase:
+        if field.c_field_type == "char":
+            _cpp_request_objects[request_name].accessors.append( \
+            Accessor(is_string=True,
+                     member=_ext(_n_item(field.field_name)),
+                     c_name=_n(self.name))
+            )
+
+        else:
+            _cpp_request_objects[request_name].accessors.append( \
+            Accessor(is_fixed=True,
+                     member=_ext(_n_item(field.field_name)),
+                     c_type=field.c_field_type,
+                     return_type="", # 'Type' if field.c_field_type == 'void' else field.c_field_type,
+                     iter_name="",
+                     c_name=_n(self.name))
+            )
+
+    else:
+
+        # sys.stderr.write('request_name: %s\n' % request_name)
+        # sys.stderr.write('c_iterator_name:\n%s;\nc_end_name:\n%s\n' \
+        #         % (field.c_iterator_name, field.c_end_name))
+        # sys.stderr.write('field: %s\n' % (field))
+        # sys.stderr.write('\n\n')
+
+        if not self.is_bitcase:
+            _cpp_request_objects[request_name].accessors.append( \
+            Accessor(is_variable=True,
+                     member=_ext(_n_item(field.field_name)),
+                     c_type=field.c_field_type,
+                     return_type='Type' if field.c_field_type == 'void' else field.c_field_type,
+                     iter_name=_n(field.type.name),
+                     c_name=_n(self.name))
+            )
+
+    # sys.stderr.write('c_iterator_name:\n%s;\nc_end_name:\n%s\n' % (field.c_iterator_name,
+    #     field.c_end_name))
+    # sys.stderr.write('field: %s\n' % (field))
+    # sys.stderr.write('\n\n')
+
+def _c_accessors(self, name, base):
+    '''
+    Declares the accessor functions for the fields of a structure.
+    '''
+    # no accessors for switch itself -
+    # switch always needs to be unpacked explicitly
+
+    for field in self.fields:
+        if field.type.is_list and not field.type.fixed_size():
+            _c_accessors_list(self, field)
+        elif field.prev_varsized_field is not None or not field.type.fixed_size():
+            pass
+            # _c_accessors_field(self, field)
+            # sys.stderr.write("c_accessors_field(%s, %s)\n" % (self, field))
+
+def c_simple(self, name):
+    '''
+    Exported function that handles cardinal type declarations.
+    These are types which are typedef'd to one of the CARDx's, char, float, etc.
+    '''
+    _c_type_setup(self, name, ())
+
+    if (self.name != name):
+        # Typedef
+        _h_setlevel(0)
+        my_name = _t(name)
+        # _h('')
+        # _h('typedef %s %s;', _t(self.name), my_name)
+        # if field.type.is_simple:
+        #     if len(name) == 2:
+        # _h('NS_HEAD(type)')
+        # _h("TYPE_CLASS(%s)", _ext(_n_item(name[-1])))
+        # _h('NS_TAIL(type)')
+        # sys.stderr.write('type: %s, name: %s\n' % (field.field_type, field.field_name))
+        # sys.stderr.write('simple: %s\n' % (field))
+
+        # Iterator
+        # _c_iterator(self, name)
+
+def _c_complex(self):
+    '''
+    Helper function for handling all structure types.
+    Called for all structs, requests, replies, events, errors.
+    '''
+
+    '''
+    _h_setlevel(0)
+    _h('')
+    _h('/**')
+    _h(' * @brief %s', self.c_type)
+    _h(' **/')
+    _h('typedef %s %s {', self.c_container, self.c_type)
+    '''
+
+    struct_fields = []
+    maxtypelen = 0
+
+    varfield = None
+    for field in self.fields:
+        if not field.type.fixed_size() and not self.is_switch and not self.is_union:
+            varfield = field.c_field_name
+            continue
+        if field.wire:
+            struct_fields.append(field)
+
+    for field in struct_fields:
+        length = len(field.c_field_type)
+        # account for '*' pointer_spec
+        if not field.type.fixed_size() and not self.is_union:
+            length += 1
+        maxtypelen = max(maxtypelen, length)
+
+    ### TODO: serialization(?)
+    def _c_complex_field(self, field, space=''):
+        if (field.type.fixed_size() or self.is_union or
+            # in case of switch with switch children, don't make the field a pointer
+            # necessary for unserialize to work
+            (self.is_switch and field.type.is_switch)):
+            spacing = ' ' * (maxtypelen - len(field.c_field_type))
+            # _h('%s    %s%s %s%s; /**<  */', space, field.c_field_type, spacing, field.c_field_name, field.c_subscript)
+            # _h("%s %s" % (field.field_type, field.field_name))
+            # sys.stderr.write("serialize: %s, %s\n" % (field.field_type, field.field_name))
+        else:
+            spacing = ' ' * (maxtypelen - (len(field.c_field_type) + 1))
+            # _h('%s    %s%s *%s%s; /**<  */', space, field.c_field_type, spacing, field.c_field_name, field.c_subscript)
+
+    if not self.is_switch:
+        for field in struct_fields:
+            _c_complex_field(self, field)
+    else:
+        for b in self.bitcases:
+            space = ''
+            if b.type.has_name:
+                # _h('    struct _%s {', b.c_field_name)
+                space = '    '
+            for field in b.type.fields:
+                _c_complex_field(self, field, space)
+            # if b.type.has_name:
+                # _h('    } %s;', b.c_field_name)
+
+    # _h('} %s;', self.c_type)
+
+def c_struct(self, name):
+    '''
+    Exported function that handles structure declarations.
+    '''
+    _c_type_setup(self, name, ())
+    _c_complex(self)
+    _c_accessors(self, name, name)
+    _c_iterator(self, name)
+
+def c_union(self, name):
+    '''
+    Exported function that handles union declarations.
+    '''
+    _c_type_setup(self, name, ())
+    _c_complex(self)
+    _c_iterator(self, name)
+
+def _c_request_helper(self, name, cookie_type, void, regular, aux=False, reply_fds=False):
+    '''
+    Declares a request function.
+    '''
+
+    # Four stunningly confusing possibilities here:
+    #
+    #   Void            Non-void
+    # ------------------------------
+    # "req"            "req"
+    # 0 flag           CHECKED flag   Normal Mode
+    # void_cookie      req_cookie
+    # ------------------------------
+    # "req_checked"    "req_unchecked"
+    # CHECKED flag     0 flag         Abnormal Mode
+    # void_cookie      req_cookie
+    # ------------------------------
+
+
+    # Whether we are _checked or _unchecked
+    checked = void and not regular
+    unchecked = not void and not regular
+
+    # What kind of cookie we return
+    func_cookie = 'xcb_void_cookie_t' if void else self.c_cookie_type
+
+    # What flag is passed to xcb_request
+    func_flags = '0' if (void and regular) or (not void and not regular) else 'XCB_REQUEST_CHECKED'
+
+    if reply_fds:
+        if func_flags == '0':
+            func_flags = 'XCB_REQUEST_REPLY_FDS'
+        else:
+            func_flags = func_flags + '|XCB_REQUEST_REPLY_FDS'
+
+    # Global extension id variable or NULL for xproto
+    func_ext_global = '&' + _ns.c_ext_global_name if _ns.is_ext else '0'
+
+    # What our function name is
+    func_name = self.c_request_name if not aux else self.c_aux_name
+    if checked:
+        func_name = self.c_checked_name if not aux else self.c_aux_checked_name
+    if unchecked:
+        func_name = self.c_unchecked_name if not aux else self.c_aux_unchecked_name
+
+    param_fields = []
+    wire_fields = []
+    maxtypelen = len('xcb_connection_t')
+    serial_fields = []
+    # special case: list with variable size elements
+    list_with_var_size_elems = False
+
+    for field in self.fields:
+        if field.visible:
+            # The field should appear as a call parameter
+            param_fields.append(field)
+        if field.wire and not field.auto:
+            # We need to set the field up in the structure
+            wire_fields.append(field)
+        if field.type.need_serialize or field.type.need_sizeof:
+            serial_fields.append(field)
+
+    for field in param_fields:
+        c_field_const_type = field.c_field_const_type
+        if field.type.need_serialize and not aux:
+            c_field_const_type = "const void"
+        if len(c_field_const_type) > maxtypelen:
+            maxtypelen = len(c_field_const_type)
+        if field.type.is_list and not field.type.member.fixed_size():
+            list_with_var_size_elems = True
+
+    _h_setlevel(1)
+    _c_setlevel(1)
+    # _h('')
+    # _h('/**')
+    # if hasattr(self, "doc") and self.doc:
+    #     if self.doc.brief:
+    #         _h(' * @brief ' + self.doc.brief)
+    #     else:
+    #         _h(' * No brief doc yet')
+
+    # _h(' *')
+    # _h(' * @param c The connection')
+    param_names = [f.c_field_name for f in param_fields]
+    if hasattr(self, "doc") and self.doc:
+        for field in param_fields:
+            # XXX: hard-coded until we fix xproto.xml
+            base_func_name = self.c_request_name if not aux else self.c_aux_name
+            if base_func_name == 'xcb_change_gc' and field.c_field_name == 'value_mask':
+                field.enum = 'GC'
+            elif base_func_name == 'xcb_change_window_attributes' and field.c_field_name == 'value_mask':
+                field.enum = 'CW'
+            elif base_func_name == 'xcb_create_window' and field.c_field_name == 'value_mask':
+                field.enum = 'CW'
+            if field.enum:
+                # XXX: why the 'xcb' prefix?
+                key = ('xcb', field.enum)
+
+                tname = _t(key)
+                if namecount[tname] > 1:
+                    tname = _t(key + ('enum',))
+                # _h(' * @param %s A bitmask of #%s values.' % (field.c_field_name, tname))
+
+            if self.doc and field.field_name in self.doc.fields:
+                desc = self.doc.fields[field.field_name]
+                for name in param_names:
+                    desc = desc.replace('`%s`' % name, '\\a %s' % (name))
+                desc = desc.split("\n")
+                desc = [line if line != '' else '\\n' for line in desc]
+                # _h(' * @param %s %s' % (field.c_field_name, "\n * ".join(desc)))
+            # If there is no documentation yet, we simply don't generate an
+            # @param tag. Doxygen will then warn about missing documentation.
+
+    # _h(' * @return A cookie')
+    # _h(' *')
+
+    # if hasattr(self, "doc") and self.doc:
+    #     if self.doc.description:
+    #         desc = self.doc.description
+    #         for name in param_names:
+    #             desc = desc.replace('`%s`' % name, '\\a %s' % (name))
+    #         desc = desc.split("\n")
+    #         _h(' * ' + "\n * ".join(desc))
+    #     else:
+    #         _h(' * No description yet')
+    # else:
+    #     _h(' * Delivers a request to the X server.')
+    # _h(' * ')
+    # if checked:
+    #     _h(' * This form can be used only if the request will not cause')
+    #     _h(' * a reply to be generated. Any returned error will be')
+    #     _h(' * saved for handling by xcb_request_check().')
+    # if unchecked:
+    #     _h(' * This form can be used only if the request will cause')
+    #     _h(' * a reply to be generated. Any returned error will be')
+    #     _h(' * placed in the event queue.')
+    # _h(' */')
+    # _c('')
+    # _hc('')
+    # _hc('/*****************************************************************************')
+    # _hc(' **')
+    # _hc(' ** %s %s', cookie_type, func_name)
+    # _hc(' ** ')
+
+    spacing = ' ' * (maxtypelen - len('xcb_connection_t'))
+    _c(' ** @param xcb_connection_t%s *c', spacing)
+
+    for field in param_fields:
+        c_field_const_type = field.c_field_const_type
+        if field.type.need_serialize and not aux:
+            c_field_const_type = "const void"
+        spacing = ' ' * (maxtypelen - len(c_field_const_type))
+        _c(' ** @param %s%s %s%s', c_field_const_type, spacing, field.c_pointer, field.c_field_name)
+
+    _c(' ** @returns %s', cookie_type)
+    _c(' **')
+    _c(' *****************************************************************************/')
+    _c(' ')
+    _c('%s', cookie_type)
+
+    spacing = ' ' * (maxtypelen - len('xcb_connection_t'))
+    comma = ',' if len(param_fields) else ');'
+    # _h('%s (xcb_connection_t%s *c  /**< */%s', func_name, spacing, comma)
+    comma = ',' if len(param_fields) else ')'
+    _c('%s (xcb_connection_t%s *c  /**< */%s', func_name, spacing, comma)
+
+    func_spacing = ' ' * (len(func_name) + 2)
+    count = len(param_fields)
+    for field in param_fields:
+        count = count - 1
+        c_field_const_type = field.c_field_const_type
+        c_pointer = field.c_pointer
+        if field.type.need_serialize and not aux:
+            c_field_const_type = "const void"
+            c_pointer = '*'
+        spacing = ' ' * (maxtypelen - len(c_field_const_type))
+        comma = ',' if count else ');'
+        # _h('%s%s%s %s%s  /**< */%s', func_spacing, c_field_const_type,
+        #    spacing, c_pointer, field.c_field_name, comma)
+        comma = ',' if count else ')'
+        _c('%s%s%s %s%s  /**< */%s', func_spacing, c_field_const_type,
+           spacing, c_pointer, field.c_field_name, comma)
+
+    count = 2
+    if not self.var_followed_by_fixed_fields:
+        for field in param_fields:
+            if not field.type.fixed_size():
+                count = count + 2
+                if field.type.need_serialize:
+                    # _serialize() keeps track of padding automatically
+                    count -= 1
+    dimension = count + 2
+
+    _c('{')
+    _c('    static const xcb_protocol_request_t xcb_req = {')
+    _c('        /* count */ %d,', count)
+    _c('        /* ext */ %s,', func_ext_global)
+    _c('        /* opcode */ %s,', self.c_request_name.upper())
+    _c('        /* isvoid */ %d', 1 if void else 0)
+    _c('    };')
+    _c('    ')
+
+    _c('    struct iovec xcb_parts[%d];', dimension)
+    _c('    %s xcb_ret;', func_cookie)
+    _c('    %s xcb_out;', self.c_type)
+    if self.var_followed_by_fixed_fields:
+        _c('    /* in the protocol description, variable size fields are followed by fixed size fields */')
+        _c('    void *xcb_aux = 0;')
+
+
+    for idx, f in enumerate(serial_fields):
+        if aux:
+            _c('    void *xcb_aux%d = 0;' % (idx))
+    if list_with_var_size_elems:
+        _c('    unsigned int i;')
+        _c('    unsigned int xcb_tmp_len;')
+        _c('    char *xcb_tmp;')
+    _c('    ')
+    # simple request call tracing
+#    _c('    printf("in function %s\\n");' % func_name)
+
+    # fixed size fields
+    for field in wire_fields:
+        if field.type.fixed_size():
+            if field.type.is_expr:
+                _c('    xcb_out.%s = %s;', field.c_field_name, _c_accessor_get_expr(field.type.expr, None))
+            elif field.type.is_pad:
+                if field.type.nmemb == 1:
+                    _c('    xcb_out.%s = 0;', field.c_field_name)
+                else:
+                    _c('    memset(xcb_out.%s, 0, %d);', field.c_field_name, field.type.nmemb)
+            else:
+                if field.type.nmemb == 1:
+                    _c('    xcb_out.%s = %s;', field.c_field_name, field.c_field_name)
+                else:
+                    _c('    memcpy(xcb_out.%s, %s, %d);', field.c_field_name, field.c_field_name, field.type.nmemb)
+
+    def get_serialize_args(type_obj, c_field_name, aux_var, context='serialize'):
+        serialize_args = get_serialize_params(context, type_obj,
+                                              c_field_name,
+                                              aux_var)[2]
+        return reduce(lambda x,y: "%s, %s" % (x,y), [a[2] for a in serialize_args])
+
+    # calls in order to free dyn. all. memory
+    free_calls = []
+
+    _c('    ')
+    if not self.var_followed_by_fixed_fields:
+        _c('    xcb_parts[2].iov_base = (char *) &xcb_out;')
+        _c('    xcb_parts[2].iov_len = sizeof(xcb_out);')
+        _c('    xcb_parts[3].iov_base = 0;')
+        _c('    xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3;')
+
+        count = 4
+
+        for field in param_fields:
+            if not field.type.fixed_size():
+                _c('    /* %s %s */', field.type.c_type, field.c_field_name)
+                # default: simple cast to char *
+                if not field.type.need_serialize and not field.type.need_sizeof:
+                    _c('    xcb_parts[%d].iov_base = (char *) %s;', count, field.c_field_name)
+                    if field.type.is_list:
+                        # _c("IS_LIST")
+                        if field.type.member.fixed_size():
+                            _c('    xcb_parts[%d].iov_len = %s * sizeof(%s);', count,
+                               _c_accessor_get_expr(field.type.expr, None),
+                               field.type.member.c_wiretype)
+                        else:
+                            list_length = _c_accessor_get_expr(field.type.expr, None)
+
+                            length = ''
+                            _c("    xcb_parts[%d].iov_len = 0;" % count)
+                            _c("    xcb_tmp = (char *)%s;", field.c_field_name)
+                            _c("    for(i=0; i<%s; i++) {" % list_length)
+                            _c("        xcb_tmp_len = %s(xcb_tmp);" %
+                                              (field.type.c_sizeof_name))
+                            _c("        xcb_parts[%d].iov_len += xcb_tmp_len;" % count)
+                            _c("        xcb_tmp += xcb_tmp_len;")
+                            _c("    }")
+                    else:
+                        # not supposed to happen
+                        raise Exception("unhandled variable size field %s" % field.c_field_name)
+                else:
+                    if not aux:
+                        _c('    xcb_parts[%d].iov_base = (char *) %s;', count, field.c_field_name)
+                    idx = serial_fields.index(field)
+                    aux_var = '&xcb_aux%d' % idx
+                    context = 'serialize' if aux else 'sizeof'
+                    _c('    xcb_parts[%d].iov_len = ', count)
+                    if aux:
+                        serialize_args = get_serialize_args(field.type, aux_var, field.c_field_name, context)
+                        _c('      %s (%s);', field.type.c_serialize_name, serialize_args)
+                        _c('    xcb_parts[%d].iov_base = xcb_aux%d;' % (count, idx))
+                        free_calls.append('    free(xcb_aux%d);' % idx)
+                    else:
+                        serialize_args = get_serialize_args(field.type, field.c_field_name, aux_var, context)
+                        func_name = field.type.c_sizeof_name
+                        _c('      %s (%s);', func_name, serialize_args)
+
+                count += 1
+                if not (field.type.need_serialize or field.type.need_sizeof):
+                    # the _serialize() function keeps track of padding automatically
+                    _c('    xcb_parts[%d].iov_base = 0;', count)
+                    _c('    xcb_parts[%d].iov_len = -xcb_parts[%d].iov_len & 3;', count, count-1)
+                    count += 1
+
+    # elif self.var_followed_by_fixed_fields:
+    else:
+        _c('    xcb_parts[2].iov_base = (char *) &xcb_out;')
+        # request header: opcodes + length
+        _c('    xcb_parts[2].iov_len = 2*sizeof(uint8_t) + sizeof(uint16_t);')
+        count += 1
+        # call _serialize()
+        buffer_var = '&xcb_aux'
+        serialize_args = get_serialize_args(self, buffer_var, '&xcb_out', 'serialize')
+        _c('    xcb_parts[%d].iov_len = %s (%s);', count, self.c_serialize_name, serialize_args)
+        _c('    xcb_parts[%d].iov_base = (char *) xcb_aux;', count)
+        free_calls.append('    free(xcb_aux);')
+        # no padding necessary - _serialize() keeps track of padding automatically
+
+    _c('    ')
+    for field in param_fields:
+        if field.isfd:
+            _c('    xcb_send_fd(c, %s);', field.c_field_name)
+
+    _c('    xcb_ret.sequence = xcb_send_request(c, %s, xcb_parts + 2, &xcb_req);', func_flags)
+
+    # free dyn. all. data, if any
+    for f in free_calls:
+        _c(f)
+    _c('    return xcb_ret;')
+    _c('}')
+
+def _cpp_request_helper(self, name, is_void):
+    '''
+    Declares a request function.
+    '''
+
+    request_name = _ext(_n_item(self.name[-1]))
+    c_func_name = _n(self.name)
+
+    param_fields = []
+
+    for field in self.fields:
+        if field.visible:
+            param_fields.append(field)
+
+    for field in param_fields:
+        c_field_const_type = field.c_field_const_type
+        if field.type.need_serialize:
+            c_field_const_type = "const void"
+
+    _cpp_request_names.append(request_name)
+    # self == request
+    _cpp_request_objects[request_name] = CppRequest(self, request_name, is_void, _ns, self.reply)
+
+    # is_obj_func = False
+    # if (len(param_fields) > 0 and len(param_fields[0].field_type) > 1):
+    #     # e.g.: DRAWABLE in { "DRAWABLE" : [], .. }
+    #     obj_name = param_fields[0].field_type[-1]
+    #     is_obj_func = obj_name in _type_objects[get_namespace(_ns)]
+
+    generate_request_specialization = False
+    for field in param_fields:
+        c_field_const_type = field.c_field_const_type
+
+        if field.c_pointer == " ": c_pointer = ""
+        else: c_pointer = " " + field.c_pointer
+
+        if field.type.need_serialize:
+            c_field_const_type = "const void"
+            c_pointer = ' *'
+
+        param = Parameter(field) # , verbose=request_name=="set_screen_config")
+
+        _cpp_request_objects[request_name].add(param)
+
+        if (param.is_const and param.is_pointer
+                and param.c_type == 'void'):
+            generate_request_specialization = True
+
+    _cpp_request_objects[request_name].make_wrapped()
+
+    _interface_class.add(_cpp_request_objects[request_name])
+
+    for key in _object_classes:
+        _object_classes[key].set_namespace(_ns)
+        _object_classes[key].add(_cpp_request_objects[request_name])
+
+    ### C CODE ###
+
+    # if generate_request_specialization:
+    #     if self.reply:
+    #         # _c_type_setup(self.reply, name, ('reply',))
+    #         # Reply structure definition
+    #         # _c_complex(self.reply)
+    #         # Request prototypes
+    #         has_fds = _c_reply_has_fds(self.reply)
+    #         _c_request_helper(self, name, self.c_cookie_type, False, True, False, has_fds)
+    #         _c_request_helper(self, name, self.c_cookie_type, False, False, False, has_fds)
+    #         if self.need_aux:
+    #             _c_request_helper(self, name, self.c_cookie_type, False, True, True, has_fds)
+    #             _c_request_helper(self, name, self.c_cookie_type, False, False, True, has_fds)
+    #         # Reply accessors
+    #         # _c_accessors(self.reply, name + ('reply',), name)
+    #         # _c_reply(self, name)
+    #         # if has_fds:
+    #         #     _c_reply_fds(self, name)
+    #     else:
+    #         # Request prototypes
+    #         _c_request_helper(self, name, 'xcb_void_cookie_t', True, False)
+    #         _c_request_helper(self, name, 'xcb_void_cookie_t', True, True)
+    #         if self.need_aux:
+    #             _c_request_helper(self, name, 'xcb_void_cookie_t', True, False, True)
+    #             _c_request_helper(self, name, 'xcb_void_cookie_t', True, True, True)
+
+    ### C CODE ###
+
+def _c_reply(self, name):
+    '''
+    Declares the function that returns the reply structure.
+    '''
+    spacing1 = ' ' * (len(self.c_cookie_type) - len('xcb_connection_t'))
+    spacing2 = ' ' * (len(self.c_cookie_type) - len('xcb_generic_error_t'))
+    spacing3 = ' ' * (len(self.c_reply_name) + 2)
+
+    # check if _unserialize() has to be called for any field
+    def look_for_special_cases(complex_obj):
+        unserialize_fields = []
+        # no unserialize call in case of switch
+        if not complex_obj.is_switch:
+            for field in complex_obj.fields:
+                # three cases: 1. field with special case
+                #              2. container that contains special case field
+                #              3. list with special case elements
+                if field.type.var_followed_by_fixed_fields:
+                    unserialize_fields.append(field)
+                elif field.type.is_container:
+                    unserialize_fields += look_for_special_cases(field.type)
+                elif field.type.is_list:
+                    if field.type.member.var_followed_by_fixed_fields:
+                        unserialize_fields.append(field)
+                    if field.type.member.is_container:
+                        unserialize_fields += look_for_special_cases(field.type.member)
+        return unserialize_fields
+
+    unserialize_fields = look_for_special_cases(self.reply)
+
+    '''
+    _h('')
+    _h('/**')
+    _h(' * Return the reply')
+    _h(' * @param c      The connection')
+    _h(' * @param cookie The cookie')
+    _h(' * @param e      The xcb_generic_error_t supplied')
+    _h(' *')
+    _h(' * Returns the reply of the request asked by')
+    _h(' * ')
+    _h(' * The parameter @p e supplied to this function must be NULL if')
+    _h(' * %s(). is used.', self.c_unchecked_name)
+    _h(' * Otherwise, it stores the error if any.')
+    _h(' *')
+    _h(' * The returned value must be freed by the caller using free().')
+    _h(' */')
+    _c('')
+    _hc('')
+    _hc('/*****************************************************************************')
+    _hc(' **')
+    _hc(' ** %s * %s', self.c_reply_type, self.c_reply_name)
+    _hc(' ** ')
+    _hc(' ** @param xcb_connection_t%s  *c', spacing1)
+    _hc(' ** @param %s   cookie', self.c_cookie_type)
+    _hc(' ** @param xcb_generic_error_t%s **e', spacing2)
+    _hc(' ** @returns %s *', self.c_reply_type)
+    _hc(' **')
+    _hc(' *****************************************************************************/')
+    '''
+
+    _hc(' ')
+    _hc('%s *', self.c_reply_type)
+    _hc('%s (xcb_connection_t%s  *c  /**< */,', self.c_reply_name, spacing1)
+    _hc('%s%s   cookie  /**< */,', spacing3, self.c_cookie_type)
+    _h('%sxcb_generic_error_t%s **e  /**< */);', spacing3, spacing2)
+    _c('%sxcb_generic_error_t%s **e  /**< */)', spacing3, spacing2)
+    _c('{')
+
+    if len(unserialize_fields)>0:
+        # certain variable size fields need to be unserialized explicitly
+        _c('    %s *reply = (%s *) xcb_wait_for_reply(c, cookie.sequence, e);',
+           self.c_reply_type, self.c_reply_type)
+        _c('    int i;')
+        for field in unserialize_fields:
+            if field.type.is_list:
+                _c('    %s %s_iter = %s(reply);', field.c_iterator_type, field.c_field_name, field.c_iterator_name)
+                _c('    int %s_len = %s(reply);', field.c_field_name, field.c_length_name)
+                _c('    %s *%s_data;', field.c_field_type, field.c_field_name)
+            else:
+                raise Exception('not implemented: call _unserialize() in reply for non-list type %s', field.c_field_type)
+        # call _unserialize(), using the reply as source and target buffer
+        _c('    /* special cases: transform parts of the reply to match XCB data structures */')
+        for field in unserialize_fields:
+            if field.type.is_list:
+                _c('    for(i=0; i<%s_len; i++) {', field.c_field_name)
+                _c('        %s_data = %s_iter.data;', field.c_field_name, field.c_field_name)
+                _c('        %s((const void *)%s_data, &%s_data);', field.type.c_unserialize_name,
+                   field.c_field_name, field.c_field_name)
+                _c('        %s(&%s_iter);', field.type.c_next_name, field.c_field_name)
+                _c('    }')
+        # return the transformed reply
+        _c('    return reply;')
+
+    else:
+        _c('    return (%s *) xcb_wait_for_reply(c, cookie.sequence, e);', self.c_reply_type)
+
+    _c('}')
+
+def _c_reply_has_fds(self):
+    for field in self.fields:
+        if field.isfd:
+            return True
+    return False
+
+def _c_reply_fds(self, name):
+    '''
+    Declares the function that returns fds related to the reply.
+    '''
+    spacing1 = ' ' * (len(self.c_reply_type) - len('xcb_connection_t'))
+    spacing3 = ' ' * (len(self.c_reply_fds_name) + 2)
+    _h('')
+    _h('/**')
+    _h(' * Return the reply fds')
+    _h(' * @param c      The connection')
+    _h(' * @param reply  The reply')
+    _h(' *')
+    _h(' * Returns the array of reply fds of the request asked by')
+    _h(' * ')
+    _h(' * The returned value must be freed by the caller using free().')
+    _h(' */')
+    _c('')
+    _hc('')
+    _hc('/*****************************************************************************')
+    _hc(' **')
+    _hc(' ** int * %s', self.c_reply_fds_name)
+    _hc(' ** ')
+    _hc(' ** @param xcb_connection_t%s  *c', spacing1)
+    _hc(' ** @param %s  *reply', self.c_reply_type)
+    _hc(' ** @returns int *')
+    _hc(' **')
+    _hc(' *****************************************************************************/')
+    _hc(' ')
+    _hc('int *')
+    _hc('%s (xcb_connection_t%s  *c  /**< */,', self.c_reply_fds_name, spacing1)
+    _h('%s%s  *reply  /**< */);', spacing3, self.c_reply_type)
+    _c('%s%s  *reply  /**< */)', spacing3, self.c_reply_type)
+    _c('{')
+
+    _c('    return xcb_get_reply_fds(c, reply, sizeof(%s) + 4 * reply->length);', self.c_reply_type)
+
+    _c('}')
+
+
+def _c_opcode(name, opcode):
+    '''
+    Declares the opcode define for requests, events, and errors.
+    '''
+    _h_setlevel(0)
+    _h('')
+    _h('/** Opcode for %s. */', _n(name))
+    _h('#define %s %s', _n(name).upper(), opcode)
+
+def _c_cookie(self, name):
+    '''
+    Declares the cookie type for a non-void request.
+    '''
+    _h_setlevel(0)
+    _h('')
+    _h('/**')
+    _h(' * @brief %s', self.c_cookie_type)
+    _h(' **/')
+    _h('typedef struct %s {', self.c_cookie_type)
+    _h('    unsigned int sequence; /**<  */')
+    _h('} %s;', self.c_cookie_type)
+
+def _man_request(self, name, cookie_type, void, aux):
+    param_fields = [f for f in self.fields if f.visible]
+
+    func_name = self.c_request_name if not aux else self.c_aux_name
+
+    def create_link(linkname):
+        name = 'man/%s.3' % linkname
+        if manpaths:
+            sys.stdout.write(name)
+        f = open(name, 'w')
+        f.write('.so man3/%s.3' % func_name)
+        f.close()
+
+    if manpaths:
+        sys.stdout.write('man/%s.3 ' % func_name)
+    # Our CWD is src/, so this will end up in src/man/
+    f = open('man/%s.3' % func_name, 'w')
+    f.write('.TH %s 3  %s "XCB" "XCB Requests"\n' % (func_name, today))
+    # Left-adjust instead of adjusting to both sides
+    f.write('.ad l\n')
+    f.write('.SH NAME\n')
+    brief = self.doc.brief if hasattr(self, "doc") and self.doc else ''
+    f.write('%s \\- %s\n' % (func_name, brief))
+    f.write('.SH SYNOPSIS\n')
+    # Don't split words (hyphenate)
+    f.write('.hy 0\n')
+    f.write('.B #include <xcb/%s.h>\n' % _ns.header)
+
+    # function prototypes
+    prototype = ''
+    count = len(param_fields)
+    for field in param_fields:
+        count = count - 1
+        c_field_const_type = field.c_field_const_type
+        c_pointer = field.c_pointer
+        if c_pointer == ' ':
+            c_pointer = ''
+        if field.type.need_serialize and not aux:
+            c_field_const_type = "const void"
+            c_pointer = '*'
+        comma = ', ' if count else ');'
+        prototype += '%s\\ %s\\fI%s\\fP%s' % (c_field_const_type, c_pointer, field.c_field_name, comma)
+
+    f.write('.SS Request function\n')
+    f.write('.HP\n')
+    base_func_name = self.c_request_name if not aux else self.c_aux_name
+    f.write('%s \\fB%s\\fP(xcb_connection_t\\ *\\fIconn\\fP, %s\n' % (cookie_type, base_func_name, prototype))
+    create_link('%s_%s' % (base_func_name, ('checked' if void else 'unchecked')))
+    if not void:
+        f.write('.PP\n')
+        f.write('.SS Reply datastructure\n')
+        f.write('.nf\n')
+        f.write('.sp\n')
+        f.write('typedef %s %s {\n' % (self.reply.c_container, self.reply.c_type))
+        struct_fields = []
+        maxtypelen = 0
+
+        for field in self.reply.fields:
+            if not field.type.fixed_size() and not self.is_switch and not self.is_union:
+                continue
+            if field.wire:
+                struct_fields.append(field)
+
+        for field in struct_fields:
+            length = len(field.c_field_type)
+            # account for '*' pointer_spec
+            if not field.type.fixed_size():
+                length += 1
+            maxtypelen = max(maxtypelen, length)
+
+        def _c_complex_field(self, field, space=''):
+            if (field.type.fixed_size() or
+                # in case of switch with switch children, don't make the field a pointer
+                # necessary for unserialize to work
+                (self.is_switch and field.type.is_switch)):
+                spacing = ' ' * (maxtypelen - len(field.c_field_type))
+                f.write('%s    %s%s \\fI%s\\fP%s;\n' % (space, field.c_field_type, spacing, field.c_field_name, field.c_subscript))
+            else:
+                spacing = ' ' * (maxtypelen - (len(field.c_field_type) + 1))
+                f.write('ELSE %s = %s\n' % (field.c_field_type, field.c_field_name))
+                #_h('%s    %s%s *%s%s; /**<  */', space, field.c_field_type, spacing, field.c_field_name, field.c_subscript)
+
+        if not self.is_switch:
+            for field in struct_fields:
+                _c_complex_field(self, field)
+        else:
+            for b in self.bitcases:
+                space = ''
+                if b.type.has_name:
+                    space = '    '
+                for field in b.type.fields:
+                    _c_complex_field(self, field, space)
+                if b.type.has_name:
+                    print('ERROR: New unhandled documentation case', file=sys.stderr)
+                    pass
+
+        f.write('} \\fB%s\\fP;\n' % self.reply.c_type)
+        f.write('.fi\n')
+
+        f.write('.SS Reply function\n')
+        f.write('.HP\n')
+        f.write(('%s *\\fB%s\\fP(xcb_connection_t\\ *\\fIconn\\fP, %s\\ '
+                 '\\fIcookie\\fP, xcb_generic_error_t\\ **\\fIe\\fP);\n') %
+                (self.c_reply_type, self.c_reply_name, self.c_cookie_type))
+        create_link('%s' % self.c_reply_name)
+
+        has_accessors = False
+        for field in self.reply.fields:
+            if field.type.is_list and not field.type.fixed_size():
+                has_accessors = True
+            elif field.prev_varsized_field is not None or not field.type.fixed_size():
+                has_accessors = True
+
+        if has_accessors:
+            f.write('.SS Reply accessors\n')
+
+        def _c_accessors_field(self, field):
+            '''
+            Declares the accessor functions for a non-list field that follows a variable-length field.
+            '''
+            c_type = self.c_type
+
+            # special case: switch
+            switch_obj = self if self.is_switch else None
+            if self.is_bitcase:
+                switch_obj = self.parents[-1]
+            if switch_obj is not None:
+                c_type = switch_obj.c_type
+
+            if field.type.is_simple:
+                f.write('%s %s (const %s *reply)\n' % (field.c_field_type, field.c_accessor_name, c_type))
+                create_link('%s' % field.c_accessor_name)
+            else:
+                f.write('%s *%s (const %s *reply)\n' % (field.c_field_type, field.c_accessor_name, c_type))
+                create_link('%s' % field.c_accessor_name)
+
+        def _c_accessors_list(self, field):
+            '''
+            Declares the accessor functions for a list field.
+            Declares a direct-accessor function only if the list members are fixed size.
+            Declares length and get-iterator functions always.
+            '''
+            list = field.type
+            c_type = self.reply.c_type
+
+            # special case: switch
+            # in case of switch, 2 params have to be supplied to certain accessor functions:
+            #   1. the anchestor object (request or reply)
+            #   2. the (anchestor) switch object
+            # the reason is that switch is either a child of a request/reply or nested in another switch,
+            # so whenever we need to access a length field, we might need to refer to some anchestor type
+            switch_obj = self if self.is_switch else None
+            if self.is_bitcase:
+                switch_obj = self.parents[-1]
+            if switch_obj is not None:
+                c_type = switch_obj.c_type
+
+            params = []
+            fields = {}
+            parents = self.parents if hasattr(self, 'parents') else [self]
+            # 'R': parents[0] is always the 'toplevel' container type
+            params.append(('const %s *\\fIreply\\fP' % parents[0].c_type, parents[0]))
+            fields.update(_c_helper_field_mapping(parents[0], [('R', '->', parents[0])], flat=True))
+            # auxiliary object for 'R' parameters
+            R_obj = parents[0]
+
+            if switch_obj is not None:
+                # now look where the fields are defined that are needed to evaluate
+                # the switch expr, and store the parent objects in accessor_params and
+                # the fields in switch_fields
+
+                # 'S': name for the 'toplevel' switch
+                toplevel_switch = parents[1]
+                params.append(('const %s *S' % toplevel_switch.c_type, toplevel_switch))
+                fields.update(_c_helper_field_mapping(toplevel_switch, [('S', '->', toplevel_switch)], flat=True))
+
+                # initialize prefix for everything "below" S
+                prefix_str = '/* %s */ S' % toplevel_switch.name[-1]
+                prefix = [(prefix_str, '->', toplevel_switch)]
+
+                # look for fields in the remaining containers
+                for p in parents[2:] + [self]:
+                    # the separator between parent and child is always '.' here,
+                    # because of nested switch statements
+                    if not p.is_bitcase or (p.is_bitcase and p.has_name):
+                        prefix.append((p.name[-1], '.', p))
+                    fields.update(_c_helper_field_mapping(p, prefix, flat=True))
+
+                # auxiliary object for 'S' parameter
+                S_obj = parents[1]
+
+            if list.member.fixed_size():
+                idx = 1 if switch_obj is not None else 0
+                f.write('.HP\n')
+                f.write('%s *\\fB%s\\fP(%s);\n' %
+                        (field.c_field_type, field.c_accessor_name, params[idx][0]))
+                create_link('%s' % field.c_accessor_name)
+
+            f.write('.HP\n')
+            f.write('int \\fB%s\\fP(const %s *\\fIreply\\fP);\n' %
+                    (field.c_length_name, c_type))
+            create_link('%s' % field.c_length_name)
+
+            if field.type.member.is_simple:
+                f.write('.HP\n')
+                f.write('xcb_generic_iterator_t \\fB%s\\fP(const %s *\\fIreply\\fP);\n' %
+                        (field.c_end_name, c_type))
+                create_link('%s' % field.c_end_name)
+            else:
+                f.write('.HP\n')
+                f.write('%s \\fB%s\\fP(const %s *\\fIreply\\fP);\n' %
+                        (field.c_iterator_type, field.c_iterator_name,
+                         c_type))
+                create_link('%s' % field.c_iterator_name)
+
+        for field in self.reply.fields:
+            if field.type.is_list and not field.type.fixed_size():
+                _c_accessors_list(self, field)
+            elif field.prev_varsized_field is not None or not field.type.fixed_size():
+                _c_accessors_field(self, field)
+
+
+    f.write('.br\n')
+    # Re-enable hyphenation and adjusting to both sides
+    f.write('.hy 1\n')
+
+    # argument reference
+    f.write('.SH REQUEST ARGUMENTS\n')
+    f.write('.IP \\fI%s\\fP 1i\n' % 'conn')
+    f.write('The XCB connection to X11.\n')
+    for field in param_fields:
+        f.write('.IP \\fI%s\\fP 1i\n' % (field.c_field_name))
+        printed_enum = False
+        # XXX: hard-coded until we fix xproto.xml
+        if base_func_name == 'xcb_change_gc' and field.c_field_name == 'value_mask':
+            field.enum = 'GC'
+        elif base_func_name == 'xcb_change_window_attributes' and field.c_field_name == 'value_mask':
+            field.enum = 'CW'
+        elif base_func_name == 'xcb_create_window' and field.c_field_name == 'value_mask':
+            field.enum = 'CW'
+        if hasattr(field, "enum") and field.enum:
+            # XXX: why the 'xcb' prefix?
+            key = ('xcb', field.enum)
+            if key in enums:
+                f.write('One of the following values:\n')
+                f.write('.RS 1i\n')
+                enum = enums[key]
+                count = len(enum.values)
+                for (enam, eval) in enum.values:
+                    count = count - 1
+                    f.write('.IP \\fI%s\\fP 1i\n' % (_n(key + (enam,)).upper()))
+                    if hasattr(enum, "doc") and enum.doc and enam in enum.doc.fields:
+                        desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', enum.doc.fields[enam])
+                        f.write('%s\n' % desc)
+                    else:
+                        f.write('TODO: NOT YET DOCUMENTED.\n')
+                f.write('.RE\n')
+                f.write('.RS 1i\n')
+                printed_enum = True
+
+        if hasattr(self, "doc") and self.doc and field.field_name in self.doc.fields:
+            desc = self.doc.fields[field.field_name]
+            desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+            if printed_enum:
+                f.write('\n')
+            f.write('%s\n' % desc)
+        else:
+            f.write('TODO: NOT YET DOCUMENTED.\n')
+        if printed_enum:
+            f.write('.RE\n')
+
+    # Reply reference
+    if not void:
+        f.write('.SH REPLY FIELDS\n')
+        # These fields are present in every reply:
+        f.write('.IP \\fI%s\\fP 1i\n' % 'response_type')
+        f.write(('The type of this reply, in this case \\fI%s\\fP. This field '
+                 'is also present in the \\fIxcb_generic_reply_t\\fP and can '
+                 'be used to tell replies apart from each other.\n') %
+                 _n(self.reply.name).upper())
+        f.write('.IP \\fI%s\\fP 1i\n' % 'sequence')
+        f.write('The sequence number of the last request processed by the X11 server.\n')
+        f.write('.IP \\fI%s\\fP 1i\n' % 'length')
+        f.write('The length of the reply, in words (a word is 4 bytes).\n')
+        for field in self.reply.fields:
+            if (field.c_field_name in frozenset(['response_type', 'sequence', 'length']) or
+                field.c_field_name.startswith('pad')):
+                continue
+
+            if field.type.is_list and not field.type.fixed_size():
+                continue
+            elif field.prev_varsized_field is not None or not field.type.fixed_size():
+                continue
+            f.write('.IP \\fI%s\\fP 1i\n' % (field.c_field_name))
+            printed_enum = False
+            if hasattr(field, "enum") and field.enum:
+                # XXX: why the 'xcb' prefix?
+                key = ('xcb', field.enum)
+                if key in enums:
+                    f.write('One of the following values:\n')
+                    f.write('.RS 1i\n')
+                    enum = enums[key]
+                    count = len(enum.values)
+                    for (enam, eval) in enum.values:
+                        count = count - 1
+                        f.write('.IP \\fI%s\\fP 1i\n' % (_n(key + (enam,)).upper()))
+                        if enum.doc and enam in enum.doc.fields:
+                            desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', enum.doc.fields[enam])
+                            f.write('%s\n' % desc)
+                        else:
+                            f.write('TODO: NOT YET DOCUMENTED.\n')
+                    f.write('.RE\n')
+                    f.write('.RS 1i\n')
+                    printed_enum = True
+
+            if hasattr(self.reply, "doc") and self.reply.doc and field.field_name in self.reply.doc.fields:
+                desc = self.reply.doc.fields[field.field_name]
+                desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+                if printed_enum:
+                    f.write('\n')
+                f.write('%s\n' % desc)
+            else:
+                f.write('TODO: NOT YET DOCUMENTED.\n')
+            if printed_enum:
+                f.write('.RE\n')
+
+
+
+    # text description
+    f.write('.SH DESCRIPTION\n')
+    if hasattr(self, "doc") and self.doc and self.doc.description:
+        desc = self.doc.description
+        desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+        lines = desc.split('\n')
+        f.write('\n'.join(lines) + '\n')
+
+    f.write('.SH RETURN VALUE\n')
+    if void:
+        f.write(('Returns an \\fIxcb_void_cookie_t\\fP. Errors (if any) '
+                 'have to be handled in the event loop.\n\nIf you want to '
+                 'handle errors directly with \\fIxcb_request_check\\fP '
+                 'instead, use \\fI%s_checked\\fP. See '
+                 '\\fBxcb-requests(3)\\fP for details.\n') % (base_func_name))
+    else:
+        f.write(('Returns an \\fI%s\\fP. Errors have to be handled when '
+                 'calling the reply function \\fI%s\\fP.\n\nIf you want to '
+                 'handle errors in the event loop instead, use '
+                 '\\fI%s_unchecked\\fP. See \\fBxcb-requests(3)\\fP for '
+                 'details.\n') %
+                (cookie_type, self.c_reply_name, base_func_name))
+    f.write('.SH ERRORS\n')
+    if hasattr(self, "doc") and self.doc:
+        for errtype, errtext in list(self.doc.errors.items()):
+            f.write('.IP \\fI%s\\fP 1i\n' % (_t(('xcb', errtype, 'error'))))
+            errtext = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', errtext)
+            f.write('%s\n' % (errtext))
+    if not hasattr(self, "doc") or not self.doc or len(self.doc.errors) == 0:
+        f.write('This request does never generate any errors.\n')
+    if hasattr(self, "doc") and self.doc and self.doc.example:
+        f.write('.SH EXAMPLE\n')
+        f.write('.nf\n')
+        f.write('.sp\n')
+        lines = self.doc.example.split('\n')
+        f.write('\n'.join(lines) + '\n')
+        f.write('.fi\n')
+    f.write('.SH SEE ALSO\n')
+    if hasattr(self, "doc") and self.doc:
+        see = ['.BR %s (3)' % 'xcb-requests']
+        if self.doc.example:
+            see.append('.BR %s (3)' % 'xcb-examples')
+        for seename, seetype in list(self.doc.see.items()):
+            if seetype == 'program':
+                see.append('.BR %s (1)' % seename)
+            elif seetype == 'event':
+                see.append('.BR %s (3)' % _t(('xcb', seename, 'event')))
+            elif seetype == 'request':
+                see.append('.BR %s (3)' % _n(('xcb', seename)))
+            elif seetype == 'function':
+                see.append('.BR %s (3)' % seename)
+            else:
+                see.append('TODO: %s (type %s)' % (seename, seetype))
+        f.write(',\n'.join(see) + '\n')
+    f.write('.SH AUTHOR\n')
+    f.write('Generated from %s.xml. Contact xcb@lists.freedesktop.org for corrections and improvements.\n' % _ns.header)
+    f.close()
+
+def _man_event(self, name):
+    if manpaths:
+        sys.stdout.write('man/%s.3 ' % self.c_type)
+    # Our CWD is src/, so this will end up in src/man/
+    f = open('man/%s.3' % self.c_type, 'w')
+    f.write('.TH %s 3  %s "XCB" "XCB Events"\n' % (self.c_type, today))
+    # Left-adjust instead of adjusting to both sides
+    f.write('.ad l\n')
+    f.write('.SH NAME\n')
+    brief = self.doc.brief if hasattr(self, "doc") and self.doc else ''
+    f.write('%s \\- %s\n' % (self.c_type, brief))
+    f.write('.SH SYNOPSIS\n')
+    # Don't split words (hyphenate)
+    f.write('.hy 0\n')
+    f.write('.B #include <xcb/%s.h>\n' % _ns.header)
+
+    f.write('.PP\n')
+    f.write('.SS Event datastructure\n')
+    f.write('.nf\n')
+    f.write('.sp\n')
+    f.write('typedef %s %s {\n' % (self.c_container, self.c_type))
+    struct_fields = []
+    maxtypelen = 0
+
+    for field in self.fields:
+        if not field.type.fixed_size() and not self.is_switch and not self.is_union:
+            continue
+        if field.wire:
+            struct_fields.append(field)
+
+    for field in struct_fields:
+        length = len(field.c_field_type)
+        # account for '*' pointer_spec
+        if not field.type.fixed_size():
+            length += 1
+        maxtypelen = max(maxtypelen, length)
+
+    def _c_complex_field(self, field, space=''):
+        if (field.type.fixed_size() or
+            # in case of switch with switch children, don't make the field a pointer
+            # necessary for unserialize to work
+            (self.is_switch and field.type.is_switch)):
+            spacing = ' ' * (maxtypelen - len(field.c_field_type))
+            f.write('%s    %s%s \\fI%s\\fP%s;\n' % (space, field.c_field_type, spacing, field.c_field_name, field.c_subscript))
+        else:
+            print('ERROR: New unhandled documentation case', file=sys.stderr)
+
+    if not self.is_switch:
+        for field in struct_fields:
+            _c_complex_field(self, field)
+    else:
+        for b in self.bitcases:
+            space = ''
+            if b.type.has_name:
+                space = '    '
+            for field in b.type.fields:
+                _c_complex_field(self, field, space)
+            if b.type.has_name:
+                print('ERROR: New unhandled documentation case', file=sys.stderr)
+                pass
+
+    f.write('} \\fB%s\\fP;\n' % self.c_type)
+    f.write('.fi\n')
+
+
+    f.write('.br\n')
+    # Re-enable hyphenation and adjusting to both sides
+    f.write('.hy 1\n')
+
+    # argument reference
+    f.write('.SH EVENT FIELDS\n')
+    f.write('.IP \\fI%s\\fP 1i\n' % 'response_type')
+    f.write(('The type of this event, in this case \\fI%s\\fP. This field is '
+             'also present in the \\fIxcb_generic_event_t\\fP and can be used '
+             'to tell events apart from each other.\n') % _n(name).upper())
+    f.write('.IP \\fI%s\\fP 1i\n' % 'sequence')
+    f.write('The sequence number of the last request processed by the X11 server.\n')
+
+    if not self.is_switch:
+        for field in struct_fields:
+            # Skip the fields which every event has, we already documented
+            # them (see above).
+            if field.c_field_name in ('response_type', 'sequence'):
+                continue
+            if isinstance(field.type, PadType):
+                continue
+            f.write('.IP \\fI%s\\fP 1i\n' % (field.c_field_name))
+            if hasattr(self, "doc") and self.doc and field.field_name in self.doc.fields:
+                desc = self.doc.fields[field.field_name]
+                desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+                f.write('%s\n' % desc)
+            else:
+                f.write('NOT YET DOCUMENTED.\n')
+
+    # text description
+    f.write('.SH DESCRIPTION\n')
+    if hasattr(self, "doc") and self.doc and self.doc.description:
+        desc = self.doc.description
+        desc = re.sub(r'`([^`]+)`', r'\\fI\1\\fP', desc)
+        lines = desc.split('\n')
+        f.write('\n'.join(lines) + '\n')
+
+    if hasattr(self, "doc") and self.doc and self.doc.example:
+        f.write('.SH EXAMPLE\n')
+        f.write('.nf\n')
+        f.write('.sp\n')
+        lines = self.doc.example.split('\n')
+        f.write('\n'.join(lines) + '\n')
+        f.write('.fi\n')
+    f.write('.SH SEE ALSO\n')
+    if hasattr(self, "doc") and self.doc:
+        see = ['.BR %s (3)' % 'xcb_generic_event_t']
+        if self.doc.example:
+            see.append('.BR %s (3)' % 'xcb-examples')
+        for seename, seetype in list(self.doc.see.items()):
+            if seetype == 'program':
+                see.append('.BR %s (1)' % seename)
+            elif seetype == 'event':
+                see.append('.BR %s (3)' % _t(('xcb', seename, 'event')))
+            elif seetype == 'request':
+                see.append('.BR %s (3)' % _n(('xcb', seename)))
+            elif seetype == 'function':
+                see.append('.BR %s (3)' % seename)
+            else:
+                see.append('TODO: %s (type %s)' % (seename, seetype))
+        f.write(',\n'.join(see) + '\n')
+    f.write('.SH AUTHOR\n')
+    f.write('Generated from %s.xml. Contact xcb@lists.freedesktop.org for corrections and improvements.\n' % _ns.header)
+    f.close()
+
+
+def c_request(self, name):
+    '''
+    Exported function that handles request declarations.
+    '''
+
+    _c_type_setup(self, name, ('request',))
+
+    # Request structure declaration
+    # _c_complex(self)
+
+    request_name = _ext(_n_item(self.name[-1]))
+    c_func_name = _n(self.name)
+
+    if self.reply:
+
+        _c_type_setup(self.reply, name, ('reply',))
+        # Reply structure definition
+        # _c_complex(self.reply)
+
+        # Request prototypes
+        _cpp_request_helper(self, name, False)
+
+        # Reply accessors
+        _c_accessors(self.reply, name + ('reply',), name)
+
+    else:
+        # Request prototypes
+        _cpp_request_helper(self, name, True)
+
+    # We generate the manpage afterwards because _c_type_setup has been called.
+    # TODO: what about aux helpers?
+    # cookie_type = self.c_cookie_type if self.reply else 'xcb_void_cookie_t'
+    # _man_request(self, name, cookie_type, not self.reply, False)
+
+def c_event(self, name):
+    '''
+    Exported function that handles event declarations.
+    '''
+
+    # The generic event structure xcb_ge_event_t has the full_sequence field
+    # at the 32byte boundary. That's why we've to inject this field into GE
+    # events while generating the structure for them. Otherwise we would read
+    # garbage (the internal full_sequence) when accessing normal event fields
+    # there.
+    if hasattr(self, 'is_ge_event') and self.is_ge_event and self.name == name:
+        event_size = 0
+        for field in self.fields:
+            if field.type.size != None and field.type.nmemb != None:
+                event_size += field.type.size * field.type.nmemb
+            if event_size == 32:
+                full_sequence = Field(tcard32, tcard32.name, 'full_sequence', False, True, True)
+                idx = self.fields.index(field)
+                self.fields.insert(idx + 1, full_sequence)
+                break
+
+    _c_type_setup(self, name, ('event',))
+
+    # Opcode define
+    # _c_opcode(name, self.opcodes[name])
+
+    _h('typedef %s %s;', _t(self.name + ('event',)), _t(name + ('event',)))
+    if self.name == name:
+        pass
+        # Structure definition
+        # _c_complex(self)
+    else:
+        pass
+        # Typedef
+        # _h('')
+        # _h('typedef %s %s;', _t(self.name + ('event',)), _t(name + ('event',)))
+
+    # _man_event(self, name)
+
+def cpp_event(self, name):
+    '''
+    Exported function that handles event declarations.
+    '''
+
+    _c_type_setup(self, name, ('event',))
+
+    opcode = _n(name).upper()
+    c_name = _t(self.name + ('event',))
+
+    cpp_event = CppEvent(self.opcodes[name], opcode, c_name, _ns, name, self.fields)
+    _cpp_events.append(cpp_event)
+    _interface_class.add_event(cpp_event)
+
+def c_error(self, name):
+    '''
+    Exported function that handles error declarations.
+    '''
+    _c_type_setup(self, name, ('error',))
+
+    # Opcode define
+    _c_opcode(name, self.opcodes[name])
+
+    if self.name == name:
+        # Structure definition
+        _c_complex(self)
+    else:
+        # Typedef
+        _h('')
+        _h('typedef %s %s;', _t(self.name + ('error',)), _t(name + ('error',)))
+
+def cpp_error(self, name):
+    '''
+    Exported function that handles error declarations.
+    '''
+    _c_type_setup(self, name, ('error',))
+
+    # Opcode define
+    # _c_opcode(name, self.opcodes[name])
+
+    # sys.stderr.write("error declaration: %s\n" % str(self))
+    # sys.stderr.write("opcode: %s\n" % self.opcodes[name])
+    # sys.stderr.write('typedef %s %s;\n\n' % ( _t(self.name + ('error',)), _t(name + ('error',))))
+
+    opcode_name = _n(name).upper()
+    c_name = _t(self.name + ('error',))
+    cpp_error = CppError(self, _ns, name, c_name, self.opcodes[name], opcode_name)
+    _cpp_errors.append(cpp_error)
+    _interface_class.add_error(cpp_error)
+
+    # if self.name == name:
+    #     # Structure definition
+    #     _c_complex(self)
+    # else:
+    #     # Typedef
+    #     _h('')
+    #     _h('typedef %s %s;', _t(self.name + ('error',)), _t(name + ('error',)))
+
+# Main routine starts here
+
+# # Must create an "output" dictionary before any xcbgen imports.
+# output = {'open'    : c_open,
+#           'close'   : c_close,
+#           'simple'  : c_simple,
+#           'enum'    : c_enum,
+#           'struct'  : c_struct,
+#           'union'   : c_union,
+#           'request' : c_request,
+#           'event'   : c_event,
+#           'error'   : c_error,
+#           }
+
+# Must create an "output" dictionary before any xcbgen imports.
+output = {'open'          : c_open,
+          'close'         : c_close,
+          'simple'        : c_simple, # lambda x, y: None,
+          'enum'          : lambda x, y: None,
+          'struct'        : lambda x, y: None,
+          'union'         : lambda x, y: None,
+          'request'       : c_request,
+          'event'         : cpp_event,
+          'error'         : cpp_error,
+          'eventstruct'   : lambda x, y: None,
+          }
+
+# Boilerplate below this point
+
+# Check for the argument that specifies path to the xcbgen python package.
+try:
+    opts, args = getopt.getopt(sys.argv[1:], 'p:m')
+except getopt.GetoptError as err:
+    print(err)
+    print('Usage: c_client.py [-p path] file.xml')
+    sys.exit(1)
+
+for (opt, arg) in opts:
+    if opt == '-p':
+        sys.path.insert(1, arg)
+    elif opt == '-m':
+        manpaths = True
+        sys.stdout.write('man_MANS = ')
+
+# Import the module class
+try:
+    from xcbgen.state import Module
+    from xcbgen.xtypes import *
+except ImportError:
+    print('''
+Failed to load the xcbgen Python package!
+Make sure that xcb/proto installed it on your Python path.
+If not, you will need to create a .pth file or define $PYTHONPATH
+to extend the path.
+Refer to the README file in xcb/proto for more info.
+''')
+    raise
+
+# Ensure the man subdirectory exists
+try:
+    os.mkdir('man')
+except OSError as e:
+    if e.errno != errno.EEXIST:
+        raise
+
+today = time.strftime('%Y-%m-%d', time.gmtime(os.path.getmtime(args[0])))
+
+# Parse the xml header
+module = Module(args[0], output)
+
+# Build type-registry and resolve type dependencies
+module.register()
+module.resolve()
+
+# Output the code
+module.generate()
diff --git a/lib/xpp/generators/cppcookie.py b/lib/xpp/generators/cppcookie.py
new file mode 100644 (file)
index 0000000..217318a
--- /dev/null
@@ -0,0 +1,202 @@
+from utils import _n, _ext, _n_item, get_namespace
+
+_templates = {}
+
+_templates['void_cookie_function'] = \
+'''\
+%s\
+void
+%s_checked(Connection && c%s)
+{%s\
+  xpp::generic::check<Connection, xpp::%s::error::dispatcher>(
+      std::forward<Connection>(c),
+      %s_checked(std::forward<Connection>(c)%s));
+}
+
+%s\
+void
+%s(Connection && c%s)
+{%s\
+  %s(std::forward<Connection>(c)%s);
+}
+'''
+
+def _void_cookie_function(ns, name, c_name, template, return_value, protos, calls, initializer):
+    if len(template) == 0: template = "template<typename Connection>\n"
+    return _templates['void_cookie_function'] % \
+            ( template
+            , name
+            , protos
+            , initializer
+            , ns
+            , c_name
+            , calls
+            , template
+            , name
+            , protos
+            , initializer
+            , c_name
+            , calls
+            )
+
+_templates['cookie_static_getter'] = \
+'''\
+%s\
+    static
+    %s
+    cookie(xcb_connection_t * const c%s)
+    {%s\
+      return base::cookie(c%s);
+    }
+'''
+
+def _cookie_static_getter(template, return_value, protos, calls, initializer):
+    return _templates['cookie_static_getter'] % \
+            ( template
+            , return_value
+            , protos
+            , initializer
+            , calls
+            )
+
+class CppCookie(object):
+    def __init__(self, namespace, is_void, name, reply, parameter_list):
+        self.namespace = namespace
+        self.is_void = is_void
+        self.name = name
+        self.reply = reply
+        self.parameter_list = parameter_list
+        self.request_name = _ext(_n_item(self.name[-1]))
+        self.c_name = "xcb" \
+            + (("_" + get_namespace(namespace)) if namespace.is_ext else "") \
+            + "_" + self.request_name
+
+    def comma(self):
+        return self.parameter_list.comma()
+
+    def calls(self, sort):
+        return self.parameter_list.calls(sort)
+
+    def protos(self, sort, defaults):
+        return self.parameter_list.protos(sort, defaults)
+
+    def iterator_template(self, indent="    ", tail="\n"):
+        prefix = "template<typename " + ("Connection, typename " if self.is_void else "")
+        return indent + prefix \
+                + ", typename ".join(self.parameter_list.iterator_templates \
+                                   + self.parameter_list.templates) \
+                + ">" + tail \
+                if len(self.parameter_list.iterator_templates) > 0 \
+                else ""
+
+    def iterator_calls(self, sort):
+        return self.parameter_list.iterator_calls(sort)
+
+    def iterator_protos(self, sort, defaults):
+        return self.parameter_list.iterator_protos(sort, defaults)
+
+    def iterator_initializers(self):
+        return self.parameter_list.iterator_initializers()
+
+    def void_functions(self, protos, calls, template="", initializer=[]):
+        inits = "" if len(initializer) > 0 else "\n"
+        for i in initializer:
+            inits += "\n"
+            for line in i.split('\n'):
+                inits += "      " + line + "\n"
+
+        return_value = "xcb_void_cookie_t"
+
+        return _void_cookie_function(get_namespace(self.namespace),
+                                     self.request_name,
+                                     self.c_name,
+                                     template,
+                                     return_value,
+                                     self.comma() + protos,
+                                     self.comma() + calls,
+                                     inits)
+
+
+    def static_reply_methods(self, protos, calls, template="", initializer=[]):
+        inits = "" if len(initializer) > 0 else "\n"
+        for i in initializer:
+            inits += "\n"
+            for line in i.split('\n'):
+                inits += "      " + line + "\n"
+
+        if self.is_void: return_value = "xcb_void_cookie_t"
+        else: return_value = self.c_name + "_cookie_t"
+
+        return _cookie_static_getter(template,
+                                     return_value,
+                                     self.comma() + protos,
+                                     self.comma() + calls,
+                                     inits)
+
+
+    def make_static_getter(self):
+        default = self.static_reply_methods(self.protos(False, False), self.calls(False))
+
+        if self.parameter_list.has_defaults:
+            default = self.static_reply_methods(self.protos(True, True), self.calls(False))
+
+        wrapped = ""
+        if self.parameter_list.want_wrap:
+            wrapped = \
+                self.static_reply_methods(self.iterator_protos(True, True),
+                        self.iterator_calls(False), self.iterator_template(),
+                        self.iterator_initializers())
+
+        default_args = ""
+        if self.parameter_list.is_reordered():
+            default_args = \
+                self.static_reply_methods(self.protos(True, True), self.calls(False))
+
+        result = ""
+
+        if (self.parameter_list.has_defaults
+            or self.parameter_list.is_reordered()
+            or self.parameter_list.want_wrap):
+            result += default
+
+        if self.parameter_list.is_reordered():
+            result += "\n" + default_args
+
+        if self.parameter_list.want_wrap:
+            result += "\n" + wrapped
+
+        return result
+
+    def make_void_functions(self):
+        default = self.void_functions(self.protos(False, False), self.calls(False))
+
+        if self.parameter_list.has_defaults:
+            default = self.void_functions(self.protos(True, True), self.calls(False))
+
+        wrapped = ""
+        if self.parameter_list.want_wrap:
+            wrapped = \
+                self.void_functions(self.iterator_protos(True, True),
+                        self.iterator_calls(False),
+                        self.iterator_template(indent=""),
+                        self.iterator_initializers())
+
+        default_args = ""
+        if self.parameter_list.is_reordered():
+            default_args = \
+                self.void_functions(self.protos(True, True), self.calls(False))
+
+        result = ""
+
+        if (self.parameter_list.has_defaults
+            or self.parameter_list.is_reordered()
+            or self.parameter_list.want_wrap):
+            result += default
+
+        if self.parameter_list.is_reordered():
+            result += "\n" + default_args
+
+        if self.parameter_list.want_wrap:
+            result += "\n" + wrapped
+
+        return result
diff --git a/lib/xpp/generators/cpperror.py b/lib/xpp/generators/cpperror.py
new file mode 100644 (file)
index 0000000..dfc2149
--- /dev/null
@@ -0,0 +1,221 @@
+from utils import \
+        get_namespace, \
+        get_ext_name, \
+        _n_item, \
+        _ext, \
+        _reserved_keywords
+
+_templates = {}
+
+_templates['error_dispatcher_class'] = \
+'''\
+namespace error {
+
+class dispatcher
+{
+  public:
+%s\
+%s\
+
+    void
+    operator()(const std::shared_ptr<xcb_generic_error_t> &%s) const
+    {
+%s\
+    }
+
+%s\
+}; // class dispatcher
+
+} // namespace error
+'''
+
+def _error_dispatcher_class(typedef, ctors, switch, members, has_errors):
+    return _templates['error_dispatcher_class'] % \
+        ( typedef
+        , ctors
+        , " error" if has_errors else ""
+        , switch if has_errors else ""
+        , members
+        )
+
+
+def error_dispatcher_class(namespace, cpperrors):
+    ns = get_namespace(namespace)
+
+    ctor_name = "dispatcher"
+
+    typedef = []
+    ctors = []
+    members = []
+    opcode_switch = "error->error_code"
+
+    typedef = [ "typedef xpp::%s::extension extension;\n" % ns ]
+
+    # >>> if begin <<<
+    if namespace.is_ext:
+        opcode_switch = "error->error_code - m_first_error"
+
+        members += \
+            [ "protected:"
+            , "  uint8_t m_first_error;"
+            ]
+
+        ctors = \
+            [ "%s(uint8_t first_error)" % (ctor_name)
+            , "  : m_first_error(first_error)"
+            , "{}"
+            , ""
+            , "%s(const xpp::%s::extension & extension)" % (ctor_name, ns)
+            , "  : %s(extension->first_error)" % ctor_name
+            , "{}"
+            ]
+
+    # >>> if end <<<
+
+    if len(typedef) > 0:
+        typedef = "\n".join(["    " + s for s in typedef]) + "\n"
+    else:
+        typedef = ""
+
+    if len(ctors) > 0:
+        ctors = "\n".join([("    " if len(s) > 0 else "") + s for s in ctors]) + "\n"
+    else:
+        ctors = ""
+
+    if len(members) > 0:
+        members = "\n".join(["  " + s for s in members]) + "\n"
+    else:
+        members = ""
+
+    switch = error_switch_cases(cpperrors, opcode_switch, "error")
+    return _error_dispatcher_class(typedef,
+                                   ctors,
+                                   switch,
+                                   members,
+                                   len(cpperrors) > 0)
+
+def error_switch_cases(cpperrors, arg_switch, arg_error):
+    cases = ""
+    errors = cpperrors
+    templ = [ "        case %s: // %s"
+            , "          throw %s" + "(%s);" % arg_error
+            , ""
+            , ""
+            ]
+
+    cases += "      switch (%s) {\n\n" % arg_switch
+    for e in errors:
+        cases += "\n".join(templ) % (e.opcode_name, e.opcode, e.scoped_name())
+    cases += "      };\n"
+
+    return cases
+
+
+class CppError(object):
+    def __init__(self, error, namespace, name, c_name, opcode, opcode_name):
+        self.error = error
+        self.namespace = namespace
+        self.c_name = c_name
+        self.opcode = opcode
+        self.opcode_name = opcode_name
+
+        self.names = list(map(str.lower, _n_item(name[-1], True)))
+        self.name = "_".join(self.names)
+
+        self.nssopen = ""
+        self.nssclose = ""
+        self.scope = []
+        for name in self.names[0:-1]:
+            if name in _reserved_keywords: name += "_"
+            self.nssopen += " namespace %s {" % name
+            self.nssclose += " }"
+            self.scope.append(name)
+
+    def get_name(self):
+        return _reserved_keywords.get(self.name, self.name)
+
+
+    def scoped_name(self):
+        ns = get_namespace(self.namespace)
+        return "xpp::" + ns + "::error::" + self.get_name()
+
+    def make_class(self):
+        ns = get_namespace(self.namespace)
+        typedef = []
+        members = []
+
+        opcode_accessor = \
+            [ "static uint8_t opcode(void)"
+            , "{"
+            , "  return %s;" % self.opcode_name
+            , "}"
+            ]
+
+        if self.namespace.is_ext:
+            opcode_accessor += \
+                [ ""
+                , "static uint8_t opcode(uint8_t first_error)"
+                , "{"
+                , "  return first_error + opcode();"
+                , "}"
+                , ""
+                , "static uint8_t opcode(const xpp::%s::extension & extension)" % ns
+                , "{"
+                , "  return opcode(extension->first_error);"
+                , "}"
+                ]
+
+            members = \
+                [ "protected:"
+                , "  uint8_t m_first_error;"
+                ]
+
+        if len(opcode_accessor) > 0:
+            opcode_accessor = "\n".join(["    " + s for s in opcode_accessor]) + "\n"
+        else:
+            opcode_accessor = ""
+
+        if len(members) > 0:
+            members = "\n" + "\n".join(["  " + s for s in members]) + "\n"
+        else:
+            members = ""
+
+        if len(typedef) > 0:
+            typedef = "\n".join(["    " + s for s in typedef]) + "\n\n"
+        else:
+            typedef = ""
+
+        name = self.name
+        if self.name in _reserved_keywords: name = self.name + "_"
+
+        return \
+'''
+namespace error {
+class %s
+  : public xpp::generic::error<%s,
+                               %s>
+{
+  public:
+%s\
+    using xpp::generic::error<%s, %s>::error;
+
+    virtual ~%s(void) {}
+
+%s
+    static std::string description(void)
+    {
+      return std::string("%s");
+    }
+%s\
+}; // class %s
+} // namespace error
+''' % (self.get_name(), # class %s
+       self.get_name(), # : public xpp::generic::error<%s,
+       self.c_name, # %s>
+       typedef,
+       self.get_name(), self.c_name, # using xpp::generic::error<%s, %s>::error;
+       self.get_name(), # virtual ~%s(void) {}
+       opcode_accessor,
+       self.opcode_name, # static constexpr const char * opcode_literal
+       members,
+       self.get_name()) # // class %s
diff --git a/lib/xpp/generators/cppevent.py b/lib/xpp/generators/cppevent.py
new file mode 100644 (file)
index 0000000..240ce82
--- /dev/null
@@ -0,0 +1,380 @@
+import sys # stderr
+
+from utils import \
+        get_namespace, \
+        get_ext_name, \
+        _n_item, \
+        _ext, \
+        _reserved_keywords
+
+from resource_classes import _resource_classes
+
+_field_accessor_template_specialization = \
+'''\
+template<typename Connection>
+template<>
+%s
+%s<Connection>::%s<%s>(void) const
+{
+  return %s;
+}\
+'''
+
+_templates = {}
+
+_templates['field_accessor_template'] = \
+'''\
+    template<typename ReturnType = %s, typename ... Parameter>
+    ReturnType
+    %s(Parameter && ... parameter) const
+    {
+      using make = xpp::generic::factory::make<Connection,
+                                               decltype((*this)->%s),
+                                               ReturnType,
+                                               Parameter ...>;
+      return make()(this->m_c,
+                    (*this)->%s,
+                    std::forward<Parameter>(parameter) ...);
+    }\
+'''
+
+def _field_accessor_template(c_type, method_name, member):
+    return _templates['field_accessor_template'] % \
+        ( c_type
+        , method_name
+        , member
+        , member
+        )
+
+_templates['event_dispatcher_class'] = \
+'''\
+namespace event {
+
+template<typename Connection>
+class dispatcher
+{
+  public:
+%s\
+%s\
+
+    template<typename Handler>
+    bool
+    operator()(Handler%s,
+               const std::shared_ptr<xcb_generic_event_t> &%s) const
+    {\
+%s
+      return false;
+    }
+
+%s\
+}; // class dispatcher
+
+} // namespace event
+'''
+
+def _event_dispatcher_class(typedef, ctors, switch, members, has_events):
+    return _templates['event_dispatcher_class'] % \
+        ( typedef
+        , ctors
+        , " handler" if has_events else ""
+        , " event" if has_events else ""
+        , switch if has_events else ""
+        , members
+        )
+
+def event_dispatcher_class(namespace, cppevents):
+    ns = get_namespace(namespace)
+
+    ctor_name = "dispatcher"
+
+    typedef = []
+    ctors = []
+    members = []
+
+    opcode_switch = "event->response_type & ~0x80"
+    typedef = [ "typedef xpp::%s::extension extension;\n" % ns ]
+
+    members = \
+        [ "protected:"
+        , "  Connection m_c;"
+        ]
+
+    ctors = \
+        [ "template<typename C>"
+        , "%s(C && c)" % ctor_name
+        , "  : m_c(std::forward<C>(c))"
+        , "{}"
+        ]
+
+    # >>> if begin <<<
+    if namespace.is_ext:
+        # XXX: The xkb extension contains the sub-event in the member pad0
+        if ns == "xkb":
+            opcode_switch = "event->pad0"
+        else:
+            opcode_switch = "(event->response_type & ~0x80) - m_first_event"
+
+        members += [ "  uint8_t m_first_event;" ]
+
+        ctors = \
+            [ "template<typename C>"
+            , "%s(C && c, uint8_t first_event)" % (ctor_name)
+            , "  : m_c(std::forward<C>(c))"
+            , "  , m_first_event(first_event)"
+            , "{}"
+            , ""
+            , "template<typename C>"
+            , "%s(C && c, const xpp::%s::extension & extension)" % (ctor_name, ns)
+            , "  : %s(std::forward<C>(c), extension->first_event)" % ctor_name
+            , "{}"
+            ]
+
+    # >>> if end <<<
+
+    if len(typedef) > 0:
+        typedef = "\n".join(["    " + s for s in typedef]) + "\n"
+    else:
+        typedef = ""
+
+    if len(ctors) > 0:
+        ctors = "\n".join([("    " if len(s) > 0 else "") + s for s in ctors]) + "\n"
+    else:
+        ctors = ""
+
+    if len(members) > 0:
+        members = "\n".join(["  " + s for s in members]) + "\n"
+    else:
+        members = ""
+
+    switch = event_switch_cases(cppevents, opcode_switch, "handler", "event", namespace)
+
+    return _event_dispatcher_class(typedef,
+                                   ctors,
+                                   switch,
+                                   members,
+                                   len(cppevents) > 0)
+
+def event_switch_cases(cppevents, arg_switch, arg_handler, arg_event, ns):
+    cases = ""
+    first_event_arg = ", m_first_event" if ns.is_ext else ""
+    templ = [ "        case %s:"
+            , "          %s(" % arg_handler + "%s<Connection>" + "(m_c%s, %s));" % (first_event_arg, arg_event)
+            , "          return true;"
+            , ""
+            , ""
+            ]
+
+    distinct_events = [[]]
+    for e in cppevents:
+        done = False
+        for l in distinct_events:
+            if e in l:
+                continue
+            else:
+                l.append(e)
+                done = True
+                break
+
+        if not done:
+            distinct_events.append([e])
+        else:
+            continue
+
+    for l in distinct_events:
+        cases += "\n      switch (%s) {\n\n" % arg_switch
+        for e in l:
+            cases += "\n".join(templ) % (e.opcode_name, e.scoped_name())
+        cases += "      };\n"
+
+    return cases if len(cppevents) > 0 else ""
+
+########## EVENT ##########
+
+class CppEvent(object):
+    def __init__(self, opcode, opcode_name, c_name, namespace, name, fields):
+        self.opcode = opcode
+        self.opcode_name = opcode_name
+        self.c_name = c_name
+        self.namespace = namespace
+        self.fields = fields
+
+        self.names = list(map(str.lower, _n_item(name[-1], True)))
+        self.name = "_".join(self.names)
+
+        self.nssopen = ""
+        self.nssclose = ""
+        self.scope = []
+        for name in self.names[0:-1]:
+            if name in _reserved_keywords: name += "_"
+            self.nssopen += " namespace %s {" % name
+            self.nssclose += " };"
+            self.scope.append(name)
+
+    def __cmp__(self, other):
+        if self.opcode == other.opcode:
+            return 0
+        elif self.opcode < other.opcode:
+            return -1
+        else:
+            return 1
+
+    def get_name(self):
+        return _reserved_keywords.get(self.name, self.name)
+
+
+    def scoped_name(self):
+        ns = get_namespace(self.namespace)
+        return "xpp::" + ns + "::event::" + self.get_name()
+
+    def make_class(self):
+        member_accessors = []
+        member_accessors_special = []
+        for field in self.fields:
+            if field.field_type[-1] in _resource_classes:
+                template_name = field.field_name.capitalize()
+                c_type = field.c_field_type
+                method_name = field.field_name.lower()
+                if (method_name == self.get_name()
+                    or method_name in _reserved_keywords):
+                    method_name += "_"
+                member = field.c_field_name
+
+                member_accessors.append(_field_accessor_template(c_type, method_name, member))
+
+        ns = get_namespace(self.namespace)
+
+        extension = "xpp::%s::extension" % ns
+
+        ctor = \
+            [ "template<typename C>"
+            , "%s(C && c," % self.get_name()
+            , (" " * len(self.get_name())) + " const std::shared_ptr<xcb_generic_event_t> & event)"
+            , "  : base(event)"
+            , "  , m_c(std::forward<C>(c))"
+            , "{}"
+            ]
+
+        m_first_event = ""
+
+        typedef = [ "typedef xpp::%s::extension extension;" % ns ]
+
+        description = \
+            [ "static std::string description(void)"
+            , "{"
+            , "  return std::string(\"%s\");" % self.opcode_name
+            , "}"
+            ]
+
+        opcode_accessor = \
+            [ "static uint8_t opcode(void)"
+            , "{"
+            , "  return %s;" % self.opcode_name
+            , "}"
+            ]
+
+        first_event = []
+
+        if self.namespace.is_ext:
+            opcode_accessor += \
+                [ ""
+                , "static uint8_t opcode(uint8_t first_event)"
+                , "{"
+                , "  return first_event + opcode();"
+                , "}"
+                , ""
+                , "static uint8_t opcode(const xpp::%s::extension & extension)" % ns
+                , "{"
+                , "  return opcode(extension->first_event);"
+                , "}"
+                ]
+
+            first_event = \
+                [ "uint8_t first_event(void)"
+                , "{"
+                , "  return m_first_event;"
+                , "}"
+                ]
+
+            ctor = \
+                [ "template<typename C>"
+                , "%s(C && c," % self.get_name()
+                , (" " * len(self.get_name())) + " uint8_t first_event,"
+                , (" " * len(self.get_name())) + " const std::shared_ptr<xcb_generic_event_t> & event)"
+                , "  : base(event)"
+                , "  , m_c(std::forward<C>(c))"
+                , "  , m_first_event(first_event)"
+                , "{}"
+                ]
+
+            m_first_event = "    const uint8_t m_first_event;\n"
+
+        if len(opcode_accessor) > 0:
+            opcode_accessor = "\n".join(["    " + s for s in opcode_accessor]) + "\n"
+        else:
+            opcode_accessor = ""
+
+        if len(ctor) > 0:
+            ctor = "\n".join(["    " + s for s in ctor]) + "\n"
+        else:
+            ctor = ""
+
+        if len(typedef) > 0:
+            typedef = "\n".join(["    " + s for s in typedef]) + "\n\n"
+        else:
+            typedef = ""
+
+        if len(member_accessors) > 0:
+            member_accessors = "\n" + "\n\n".join(member_accessors) + "\n\n"
+            member_accessors_special = "\n" + "\n\n".join(member_accessors_special) + "\n\n"
+        else:
+            member_accessors = ""
+            member_accessors_special = ""
+
+        if len(description) > 0:
+            description = "\n" + "\n".join(["    " + s for s in description]) + "\n"
+        else:
+            description = ""
+
+        if len(first_event) > 0:
+            first_event = "\n" + "\n".join(["    " + s for s in first_event]) + "\n"
+        else:
+            first_event = ""
+
+        return \
+'''
+namespace event {
+template<typename Connection>
+class %s
+  : public xpp::generic::event<%s>
+{
+  public:
+%s\
+    typedef xpp::generic::event<%s> base;
+
+%s\
+
+    virtual ~%s(void) {}
+
+%s\
+%s\
+%s\
+%s\
+  protected:
+    Connection m_c;
+%s\
+}; // class %s
+%s\
+} // namespace event
+''' % (self.get_name(), # class %s
+       self.c_name, # %s>
+       typedef,
+       self.c_name, # typedef xpp::generic::event<%s>::base;
+       ctor,
+       self.get_name(), # virtual ~%s(void) {}
+       opcode_accessor,
+       description,
+       first_event,
+       member_accessors,
+       m_first_event,
+       self.get_name(), # // class %s
+       member_accessors_special)
diff --git a/lib/xpp/generators/cppreply.py b/lib/xpp/generators/cppreply.py
new file mode 100644 (file)
index 0000000..4decdd8
--- /dev/null
@@ -0,0 +1,138 @@
+from utils import _n, _ext, _n_item, get_namespace
+from resource_classes import _resource_classes
+
+_templates = {}
+
+_templates['reply_class'] = \
+'''\
+namespace reply {
+
+namespace detail {
+
+template<typename Connection,
+         typename Check,
+         typename CookieFunction>
+class %s
+  : public xpp::generic::reply<%s<Connection, Check, CookieFunction>,
+                               Connection,
+                               Check,
+                               SIGNATURE(%s_reply),
+                               CookieFunction>
+{
+  public:
+    typedef xpp::generic::reply<%s<Connection, Check, CookieFunction>,
+                                Connection,
+                                Check,
+                                SIGNATURE(%s_reply),
+                                CookieFunction>
+                                  base;
+
+    template<typename C, typename ... Parameter>
+    %s(C && c, Parameter && ... parameter)
+      : base(std::forward<C>(c), std::forward<Parameter>(parameter) ...)
+    {}
+
+%s\
+%s\
+}; // class %s
+
+} // namespace detail
+
+namespace checked {
+template<typename Connection>
+using %s = detail::%s<
+    Connection, xpp::generic::checked_tag,
+    SIGNATURE(%s)>;
+} // namespace checked
+
+namespace unchecked {
+template<typename Connection>
+using %s = detail::%s<
+    Connection, xpp::generic::unchecked_tag,
+    SIGNATURE(%s_unchecked)>;
+} // namespace unchecked
+
+} // namespace reply
+'''
+
+def _reply_class(name, c_name, ns, cookie, accessors):
+    return _templates['reply_class'] % \
+            ( name
+            , name # base class
+            , c_name # %s_reply
+            , name # typedef
+            , c_name # %s_reply
+            , name # c'tor
+            , cookie.make_static_getter()
+            , accessors
+            , name # // class %s
+            , name # checked { using %s =
+            , name # checked { detail::%s
+            , c_name # checked { SIGNATURE
+            , name # unchecked { using %s =
+            , name # unchecked { detail::%s
+            , c_name # unchecked { SIGNATURE
+            )
+
+_templates['reply_member_accessor'] = \
+'''\
+    template<typename ReturnType = %s, typename ... Parameter>
+    ReturnType
+    %s(Parameter && ... parameter)
+    {
+      using make = xpp::generic::factory::make<Connection,
+                                               decltype(this->get()->%s),
+                                               ReturnType,
+                                               Parameter ...>;
+      return make()(this->m_c,
+                    this->get()->%s,
+                    std::forward<Parameter>(parameter) ...);
+    }
+'''
+
+def _reply_member_accessor(request_name, name, c_type, template_type):
+    return _templates['reply_member_accessor'] % \
+            ( c_type
+            , name
+            , name
+            , name
+            )
+
+class CppReply(object):
+    def __init__(self, namespace, name, cookie, reply, accessors, parameter_list):
+        self.namespace = namespace
+        self.name = name
+        self.reply = reply
+        self.cookie = cookie
+        self.accessors = accessors
+        self.parameter_list = parameter_list
+        self.request_name = _ext(_n_item(self.name[-1]))
+        self.c_name = "xcb" \
+            + (("_" + get_namespace(namespace)) if namespace.is_ext else "") \
+            + "_" + self.request_name
+
+    def make_accessors(self):
+        return "\n".join(["\n%s\n" % a for a in self.accessors])
+
+    def make(self):
+        accessors = [self.make_accessors()]
+        naccessors = len(self.accessors)
+
+        for field in self.reply.fields:
+            if (field.field_type[-1] in _resource_classes
+                and not field.type.is_list
+                and not field.type.is_container):
+
+                naccessors = naccessors + 1
+
+                name = field.field_name.lower()
+                c_type = field.c_field_type
+                template_type = field.field_name.capitalize()
+
+                accessors.append(_reply_member_accessor(self.request_name, name, c_type, template_type))
+
+        result = ""
+        result += _reply_class(
+            self.request_name, self.c_name, get_namespace(self.namespace),
+            self.cookie, "\n".join(accessors))
+        return result
diff --git a/lib/xpp/generators/cpprequest.py b/lib/xpp/generators/cpprequest.py
new file mode 100644 (file)
index 0000000..bb6ee2a
--- /dev/null
@@ -0,0 +1,197 @@
+# vim: set ts=4 sws=4 sw=4:
+
+# from utils import *
+from utils import _n, _ext, _n_item, get_namespace
+from parameter import *
+from resource_classes import _resource_classes
+from cppreply import CppReply
+from cppcookie import CppCookie
+
+_templates = {}
+
+_templates['void_request_function'] = \
+'''\
+template<typename Connection, typename ... Parameter>
+void
+%s_checked(Connection && c, Parameter && ... parameter)
+{
+  xpp::generic::check<Connection, xpp::%s::error::dispatcher>(
+      std::forward<Connection>(c),
+      %s_checked(
+          std::forward<Connection>(c),
+          std::forward<Parameter>(parameter) ...));
+}
+
+template<typename ... Parameter>
+void
+%s(Parameter && ... parameter)
+{
+  %s(std::forward<Parameter>(parameter) ...);
+}
+'''
+
+def _void_request_function(ns, name, c_name):
+    return _templates['void_request_function'] % \
+            ( name
+            , ns
+            , c_name
+            , name
+            , c_name
+            )
+
+_templates['reply_request_function'] = \
+'''\
+template<typename Connection, typename ... Parameter>
+reply::checked::%s<Connection>
+%s(Connection && c, Parameter && ... parameter)
+{
+  return reply::checked::%s<Connection>(
+      std::forward<Connection>(c), std::forward<Parameter>(parameter) ...);
+}
+
+template<typename Connection, typename ... Parameter>
+reply::unchecked::%s<Connection>
+%s_unchecked(Connection && c, Parameter && ... parameter)
+{
+  return reply::unchecked::%s<Connection>(
+      std::forward<Connection>(c), std::forward<Parameter>(parameter) ...);
+}
+'''
+
+def _reply_request_function(name):
+    return _templates['reply_request_function'] % \
+            ( name
+            , name
+            , name
+            , name
+            , name
+            , name)
+
+_templates['inline_reply_class'] = \
+'''\
+    template<typename ... Parameter>
+    auto
+    %s(Parameter && ... parameter) const
+    -> reply::checked::%s<Connection>
+    {
+      return xpp::%s::%s(
+          connection(),
+          %s\
+          std::forward<Parameter>(parameter) ...);
+    }
+
+    template<typename ... Parameter>
+    auto
+    %s_unchecked(Parameter && ... parameter) const
+    -> reply::unchecked::%s<Connection>
+    {
+      return xpp::%s::%s_unchecked(
+          connection(),
+          %s\
+          std::forward<Parameter>(parameter) ...);
+    }
+'''
+
+def _inline_reply_class(request_name, method_name, member, ns):
+    return _templates['inline_reply_class'] % \
+            ( method_name
+            , request_name
+            , ns
+            , request_name
+            , member
+            , method_name
+            , request_name
+            , ns
+            , request_name
+            , member
+            )
+
+_templates['inline_void_class'] = \
+'''\
+    template<typename ... Parameter>
+    void
+    %s_checked(Parameter && ... parameter) const
+    {
+      xpp::%s::%s_checked(connection(),
+                          %s\
+                          std::forward<Parameter>(parameter) ...);
+    }
+
+    template<typename ... Parameter>
+    void
+    %s(Parameter && ... parameter) const
+    {
+      xpp::%s::%s(connection(),
+                  %s\
+                  std::forward<Parameter>(parameter) ...);
+    }
+'''
+
+def _inline_void_class(request_name, method_name, member, ns):
+    return _templates['inline_void_class'] % \
+            ( method_name
+            , ns
+            , request_name
+            , member
+            , method_name
+            , ns
+            , request_name
+            , member
+            )
+
+_replace_special_classes = \
+        { "gcontext" : "gc" }
+
+def replace_class(method, class_name):
+    cn = _replace_special_classes.get(class_name, class_name)
+    return method.replace("_" + cn, "")
+
+class CppRequest(object):
+    def __init__(self, request, name, is_void, namespace, reply):
+        self.request = request
+        self.name = name
+        self.request_name = _ext(_n_item(self.request.name[-1]))
+        self.is_void = is_void
+        self.namespace = namespace
+        self.reply = reply
+        self.c_namespace = \
+            "" if namespace.header.lower() == "xproto" \
+            else get_namespace(namespace)
+        self.accessors = []
+        self.parameter_list = ParameterList()
+
+        self.c_name = "xcb" \
+            + (("_" + get_namespace(namespace)) if namespace.is_ext else "") \
+            + "_" + self.request_name
+
+    def add(self, param):
+        self.parameter_list.add(param)
+
+    def make_wrapped(self):
+        self.parameter_list.make_wrapped()
+
+    def make_class(self):
+        cppcookie = CppCookie(self.namespace, self.is_void, self.request.name, self.reply, self.parameter_list)
+
+        if self.is_void:
+            void_functions = cppcookie.make_void_functions()
+            if len(void_functions) > 0:
+                return void_functions
+            else:
+                return _void_request_function(get_namespace(self.namespace), self.request_name, self.c_name)
+
+        else:
+            cppreply = CppReply(self.namespace, self.request.name, cppcookie, self.reply, self.accessors, self.parameter_list)
+            return cppreply.make() + "\n\n" + _reply_request_function(self.request_name)
+
+    def make_object_class_inline(self, is_connection, class_name=""):
+        member = ""
+        method_name = self.name
+        if not is_connection:
+            member = "resource(),\n"
+            method_name = replace_class(method_name, class_name)
+
+        if self.is_void:
+            return _inline_void_class(self.request_name, method_name, member, get_namespace(self.namespace))
+        else:
+            return _inline_reply_class(self.request_name, method_name, member, get_namespace(self.namespace))
diff --git a/lib/xpp/generators/extensionclass.py b/lib/xpp/generators/extensionclass.py
new file mode 100644 (file)
index 0000000..b2461b0
--- /dev/null
@@ -0,0 +1,45 @@
+from utils import \
+        get_namespace, \
+        get_ext_name, \
+        _n_item, \
+        _ext
+
+class ExtensionClass(object):
+    def __init__(self, namespace):
+        self.namespace = namespace
+
+    def make_class(self):
+        # if not self.namespace.is_ext:
+        #     return ""
+        # else:
+        ns = get_namespace(self.namespace)
+        if self.namespace.is_ext:
+            base = "\n  : public xpp::generic::extension<extension, &xcb_%s_id>\n" % ns
+            ctor = "    using base = xpp::generic::extension<extension, &xcb_%s_id>;\n" % ns + \
+                   "    using base::base;\n"
+        else:
+            base = " "
+            ctor = ""
+
+        return \
+'''\
+template<typename Derived, typename Connection>
+class interface;
+
+namespace event { template<typename Connection> class dispatcher; }
+namespace error { class dispatcher; }
+
+class extension%s{
+  public:
+%s\
+    template<typename Derived, typename Connection>
+    using interface = xpp::%s::interface<Derived, Connection>;
+    template<typename Connection>
+    using event_dispatcher = xpp::%s::event::dispatcher<Connection>;
+    using error_dispatcher = xpp::%s::error::dispatcher;
+};\
+''' % (base,
+       ctor,
+       ns, # typedef xpp::interface::%s interface;
+       ns, # typedef xpp::event::dispatcher::%s dispatcher;
+       ns) # typedef xpp::error::dispatcher::%s dispatcher;
diff --git a/lib/xpp/generators/interfaceclass.py b/lib/xpp/generators/interfaceclass.py
new file mode 100644 (file)
index 0000000..0bcfeea
--- /dev/null
@@ -0,0 +1,86 @@
+# vim: set ts=4 sws=4 sw=4:
+
+from utils import \
+        get_namespace, \
+        get_ext_name, \
+        _n_item, \
+        _ext
+
+from cppevent import event_dispatcher_class
+from cpperror import error_dispatcher_class
+
+_templates = {}
+
+_templates['interface_class'] = \
+"""\
+template<typename Derived, typename Connection>
+class interface
+{
+  protected:
+    Connection
+    connection(void) const
+    {
+      return static_cast<const Derived *>(this)->connection();
+    }
+
+  public:
+%s\
+
+    virtual ~interface(void) {}
+
+    const interface<Derived, Connection> &
+    %s(void)
+    {
+      return *this;
+    }
+
+%s\
+}; // class interface
+"""
+
+_ignore_events = \
+        { "XCB_PRESENT_GENERIC" }
+
+########## INTERFACECLASS ##########
+
+class InterfaceClass(object):
+    def __init__(self):
+        self.requests = []
+        self.events = []
+        self.errors = []
+
+    def add(self, request):
+        self.requests.append(request)
+
+    def add_event(self, event):
+        if event.opcode_name not in _ignore_events:
+            self.events.append(event)
+
+    def add_error(self, error):
+        self.errors.append(error)
+
+    def set_namespace(self, namespace):
+        self.namespace = namespace
+
+    def make_proto(self):
+        ns = get_namespace(self.namespace)
+        methods = ""
+        for request in self.requests:
+            methods += request.make_object_class_inline(True) + "\n\n"
+
+        typedef = []
+        if self.namespace.is_ext:
+            typedef = [ "typedef xpp::%s::extension extension;" % ns ]
+
+        if len(typedef) > 0:
+            typedef = "".join(["    " + s for s in typedef]) + "\n\n"
+        else:
+            typedef = ""
+
+
+        return (_templates['interface_class'] \
+            % (typedef, ns, methods)) + \
+              '\n' + event_dispatcher_class(self.namespace, self.events) + \
+              '\n' + error_dispatcher_class(self.namespace, self.errors)
+
+########## INTERFACECLASS ##########
diff --git a/lib/xpp/generators/objectclass.py b/lib/xpp/generators/objectclass.py
new file mode 100644 (file)
index 0000000..075895a
--- /dev/null
@@ -0,0 +1,69 @@
+# vim: set ts=4 sws=4 sw=4:
+
+import sys # stderr
+import copy # deepcopy
+
+from utils import \
+        get_namespace, \
+        get_ext_name, \
+        _n_item, \
+        _ext
+
+class ObjectClass(object):
+    def __init__(self, name):
+        self.name = name
+        self.requests = []
+
+    def add(self, request):
+        if (len(request.parameter_list.parameter) > 0
+                and request.parameter_list.parameter[0].c_type == self.c_name):
+            request_copy = copy.deepcopy(request)
+            request_copy.parameter_list.parameter.pop(0)
+            request_copy.make_wrapped()
+            self.requests.append(request_copy)
+
+    def set_namespace(self, namespace):
+        self.namespace = namespace
+        name = (get_namespace(namespace) + "_") if namespace.is_ext else ""
+        self.c_name = "xcb_%s_t" % (name + self.name.lower())
+
+    def make_inline(self):
+        ns = get_namespace(self.namespace)
+        name = self.name.lower()
+        c_name = self.c_name
+        methods = ""
+
+        for request in self.requests:
+            methods += request.make_object_class_inline(False, self.name.lower()) + "\n\n"
+
+        if methods == "":
+            return ""
+        else:
+            return \
+"""\
+template<typename Derived, typename Connection>
+class %s
+{
+  protected:
+    Connection
+    connection(void) const
+    {
+      return static_cast<const Derived *>(this)->connection();
+    }
+
+    const %s &
+    resource(void) const
+    {
+      return static_cast<const Derived *>(this)->resource();
+    }
+
+  public:
+    virtual ~%s(void) {}
+
+%s
+}; // class %s
+""" % (name,   # class %s
+       c_name, # %s resource(void) { ... }
+       name, # virtual ~%s(void)
+       methods,
+       name) # }; // class %s
diff --git a/lib/xpp/generators/parameter.py b/lib/xpp/generators/parameter.py
new file mode 100644 (file)
index 0000000..2212544
--- /dev/null
@@ -0,0 +1,269 @@
+# vim: set ts=4 sws=4 sw=4:
+
+import sys # stderr
+
+_templates = {}
+
+_templates['initializer'] = \
+'''\
+typedef typename value_type<%s, ! std::is_pointer<%s>::value>::type
+          vector_type;
+std::vector<vector_type> %s =
+  { value_iterator<%s>(%s), value_iterator<%s>(%s) };
+'''
+
+def _initializer(iter_type, c_name, iter_begin, iter_end):
+    return _templates['initializer'] % \
+            ( iter_type
+            , iter_type
+            , c_name
+            , iter_type
+            , iter_begin
+            , iter_type
+            , iter_end
+            )
+
+class ParameterList(object):
+    def __init__(self):
+        self.want_wrap = False
+        self.has_defaults = False
+        self.parameter = []
+        self.wrap_calls = []
+        self.wrap_protos = []
+        self.iter_calls = []
+        self.iter_2nd_lvl_calls = []
+        self.iter_protos = []
+        self.templates = []
+        self.iterator_templates = []
+        self.initializer = []
+
+    def add(self, param):
+        self.has_defaults = param.default != None
+        self.parameter.append(param)
+
+    def comma(self):
+        return "" if len(self.parameter) == 0 else ", "
+
+    def is_reordered(self):
+        tmp = sorted(self.parameter, key=lambda p: p.default or '')
+        return tmp != self.parameter
+
+    def calls(self, sort, params=None):
+        ps = self.parameter if params == None else params
+        if sort:
+            tmp = sorted(ps, key=lambda p: p.default or '')
+            ps = tmp
+        calls = [p.call() for p in ps]
+        return ", ".join(calls)
+
+    def protos(self, sort, defaults, params=None):
+        if defaults: sort = True
+        ps = self.parameter if params == None else params
+        if sort:
+            tmp = sorted(ps, key=lambda p: p.default or '')
+            ps = tmp
+        protos = [p.proto(defaults) for p in ps]
+        return ", ".join(protos)
+
+    def iterator_initializers(self):
+        return self.initializer
+
+    def make_wrapped(self):
+        self.wrap_calls = []
+        self.wrap_protos = []
+        self.iter_calls = []
+        self.iter_2nd_lvl_calls = []
+        self.iter_protos = []
+        self.initializer = []
+        self.templates = []
+        self.iterator_templates = []
+
+        lenfields = {}
+        # if a parameter is removed, take reduced parameter size into account
+        adjust = 0
+        for index, param in enumerate(self.parameter):
+            prev = index - adjust - 1
+
+            if param.field.type.is_list:
+                name = param.field.type.expr.lenfield_name
+                if name in lenfields:
+                    lenfields[name].append(param.c_name)
+                else:
+                    lenfields[name] = [ param.c_name ]
+
+                # sys.stderr.write("list: %s %s\n\n"
+                #         % ( param.field.type.expr.lenfield_type
+                #           , param.field.type.expr.lenfield_name
+                #           ))
+
+            # SetGamma: takes 1 size, but 3 value lists
+            # if param.field.type.is_list and prev >= 0:
+            if (param.is_const and param.is_pointer
+                    and prev >= 0
+                    and self.parameter[prev].c_name == param.c_name + "_len"):
+
+                adjust = adjust + 1
+                self.want_wrap = True
+                self.wrap_calls.pop(prev)
+                self.wrap_protos.pop(prev)
+                self.iter_calls.pop(prev)
+                self.iter_2nd_lvl_calls.pop(prev)
+                self.iter_protos.pop(prev)
+
+                prev_type = self.parameter[prev].c_type
+                if param.c_type == 'char':
+
+                    def append_proto_string(list):
+                        list.append(Parameter(None, \
+                            c_type='const std::string &',
+                            c_name=param.c_name))
+
+                    def append_call_string(list):
+                        list.append(Parameter(None, \
+                            c_name="static_cast<" + prev_type + ">(" \
+                            + param.c_name + '.length())'))
+
+                        list.append(Parameter(None, \
+                            c_name=param.c_name + '.c_str()'))
+
+                    append_proto_string(self.wrap_protos)
+                    append_proto_string(self.iter_protos)
+                    append_call_string(self.wrap_calls)
+                    append_call_string(self.iter_calls)
+                    append_call_string(self.iter_2nd_lvl_calls)
+
+                else:
+                    param_type = param.c_type
+                    if param_type == "void":
+                        param_type = "Type_" + str(index)
+                        self.templates.append(param_type)
+
+                    prev_type = self.parameter[prev].c_type
+
+                    ### std::vector
+                    self.wrap_protos.append(Parameter(None, \
+                        c_type='const std::vector<' + param_type + '> &',
+                        c_name=param.c_name))
+
+                    self.wrap_calls.append(Parameter(None, \
+                      c_name="static_cast<" + prev_type + ">(" \
+                      + param.c_name + '.size())'))
+
+                    self.wrap_calls.append(Parameter(None, \
+                        c_name=param.c_name + '.data()'))
+
+                    ### Iterator
+                    iter_type = param.c_name.capitalize() + "_Iterator"
+                    iter_begin = param.c_name + "_begin"
+                    iter_end = param.c_name + "_end"
+
+                    if len(self.templates) > 0:
+                        self.templates[-1] += " = typename " + iter_type + "::value_type"
+                    self.iterator_templates.append(iter_type)
+
+                    self.iter_protos.append(Parameter(None, \
+                            c_type=iter_type,
+                            c_name=iter_begin))
+
+                    self.iter_protos.append(Parameter(None, \
+                            c_type=iter_type,
+                            c_name=iter_end))
+
+                    self.iter_calls.append(Parameter(None, \
+                            c_name="static_cast<" + prev_type + ">(" \
+                            + param.c_name + '.size())'))
+
+                    self.iter_calls.append(Parameter(None, \
+                            c_name='const_cast<const vector_type *>(' \
+                            + param.c_name + '.data())'))
+
+                    self.iter_2nd_lvl_calls.append(Parameter(None, \
+                            c_name=iter_begin))
+
+                    self.iter_2nd_lvl_calls.append(Parameter(None, \
+                            c_name=iter_end))
+
+#                     vector_type = \
+#                     '''\
+# typename value_type<%s,
+#                   ! std::is_pointer<%s>::value
+#                  >::type\
+#                     ''' % (iter_type, iter_type)
+
+                    # self.initializer.append( \
+                    #         "std::vector<%s> %s = { value_iterator<%s>(%s), \
+                    #         value_iterator<%s>(%s) };" \
+                    #         % (vector_type, param.c_name,
+                    #             iter_type, iter_begin,
+                    #             iter_type, iter_end))
+
+                    self.initializer.append(
+                            _initializer(iter_type, param.c_name, iter_begin, iter_end))
+
+            else:
+                self.wrap_calls.append(param)
+                self.wrap_protos.append(param)
+                self.iter_calls.append(param)
+                self.iter_2nd_lvl_calls.append(param)
+                self.iter_protos.append(param)
+
+        # end: for index, param in enumerate(self.parameter):
+
+        for k, v in list(lenfields.items()):
+            if len(v) > 1:
+                sys.stderr.write("list: %s, %s\n" % (k, v))
+
+
+    def wrapped_calls(self, sort):
+        return self.calls(sort, params=self.wrap_calls)
+
+    def wrapped_protos(self, sort, defaults):
+        return self.protos(sort, defaults, params=self.wrap_protos)
+
+    def iterator_calls(self, sort):
+        return self.calls(sort, params=self.iter_calls)
+
+    def iterator_2nd_lvl_calls(self, sort):
+        return self.calls(sort, params=self.iter_2nd_lvl_calls)
+
+    def iterator_protos(self, sort, defaults):
+        return self.protos(sort, defaults, params=self.iter_protos)
+
+
+
+_default_parameter_values = \
+    { "xcb_timestamp_t" : "XCB_TIME_CURRENT_TIME" }
+
+class Parameter(object):
+    def __init__(self, field, c_type="", c_name="", verbose=False):
+        self.field = field
+        if field != None:
+            self.c_type = field.c_field_type
+            self.c_name = field.c_field_name
+            self.is_const = field.c_field_const_type == "const " + field.c_field_type
+            self.is_pointer = field.c_pointer != " "
+            # self.serialize = field.type.need_serialize
+            self.default = _default_parameter_values.get(self.c_type)
+            self.with_default = True
+            if verbose:
+                sys.stderr.write("c_type: %s; c_name: %s; default: %s\n" \
+                      % (self.c_type, self.c_name, self.default))
+
+        else:
+            self.c_type = c_type
+            self.c_name = c_name
+            self.is_const = False
+            self.is_pointer = False
+            # self.serialize = field.type.need_serialize
+            self.default = _default_parameter_values.get(self.c_type)
+            self.with_default = True
+
+    def call(self):
+        return self.c_name
+
+    def proto(self, with_default):
+        c_type = ("const " if self.is_const else "") \
+             + self.c_type \
+             + (" *" if self.is_pointer else "")
+        param = " = " + self.default if with_default and self.default != None else ""
+        return c_type + " " + self.c_name + param
diff --git a/lib/xpp/generators/resource_classes.py b/lib/xpp/generators/resource_classes.py
new file mode 100644 (file)
index 0000000..02cece8
--- /dev/null
@@ -0,0 +1,81 @@
+_resource_classes = \
+    {
+    ### XPROTO ###
+      "WINDOW"
+    , "PIXMAP"
+    , "CURSOR"
+    , "FONT"
+    , "GCONTEXT"
+    , "COLORMAP"
+    , "ATOM"
+    , "DRAWABLE"
+    , "FONTABLE"
+    ### XPROTO ###
+
+    ### DAMAGE ###
+    , "DAMAGE"
+    ### DAMAGE ###
+
+    ### GLX, RECORD, XVMC ###
+    , "CONTEXT"
+    ### GLX, RECORD, XVMC ###
+
+    ### GLX ###
+    # , "PIXMAP" # already in XPROTO
+    # , "CONTEXT"
+    , "PBUFFER"
+    # , "WINDOW" # already in XPROTO
+    , "FBCONFIG"
+    ### GLX ###
+
+    ### PRESENT ###
+    , "EVENT"
+    ### PRESENT ###
+
+    ### RANDR ###
+    , "MODE"
+    , "CRTC"
+    , "OUTPUT"
+    , "PROVIDER"
+    ### RANDR ###
+
+    ### RECORD ###
+    # , "CONTEXT"
+    ### RECORD ###
+
+    ### RENDER ###
+    , "GLYPHSET"
+    , "PICTURE"
+    , "PICTFORMAT"
+    ### RENDER ###
+
+    ### SHM ###
+    , "SEG"
+    ### SHM ###
+
+    ### SYNC ###
+    , "ALARM"
+    , "COUNTER"
+    , "FENCE"
+    ### SYNC ###
+
+    ### XFIXES ###
+    , "REGION"
+    , "BARRIER"
+    ### XFIXES ###
+
+    ### XPRINT ###
+    , "PCONTEXT"
+    ### XPRINT ###
+
+    ### XVMC ###
+    # , "CONTEXT"
+    , "SURFACE"
+    , "SUBPICTURE"
+    ### XVMC ###
+
+    ### XV ###
+    , "PORT"
+    , "ENCODING"
+    ### XV ###
+    }
diff --git a/lib/xpp/generators/utils.py b/lib/xpp/generators/utils.py
new file mode 100644 (file)
index 0000000..1f53bee
--- /dev/null
@@ -0,0 +1,64 @@
+import re # compile
+
+_reserved_keywords = {'class' : '_class',
+                      'new'   : '_new',
+                      'delete': '_delete',
+                      'default' : '_default',
+                      'private' : '_private',
+                      'explicit': '_explicit'}
+
+def get_namespace(namespace):
+    if namespace.is_ext:
+        return get_ext_name(namespace.ext_name)
+    else:
+        return "x"
+
+def get_ext_name(string):
+    return _ext(string)
+
+_cname_re = re.compile('([A-Z0-9][a-z]+|[A-Z0-9]+(?![a-z])|[a-z]+)')
+_cname_special_cases = {'DECnet':'decnet'}
+
+def _n_item(string, parts=False):
+    '''
+    Does C-name conversion on a single string fragment.
+    The resulting string is a valid C-name
+    Uses a regexp with some hard-coded special cases.
+    '''
+    if string in _cname_special_cases:
+        return _cname_special_cases[string]
+    else:
+        split = _cname_re.finditer(string)
+        name_parts = [match.group(0) for match in split]
+        if parts:
+            return name_parts
+        else:
+            return '_'.join(name_parts)
+
+_extension_special_cases = ['XPrint', 'XCMisc', 'BigRequests']
+
+def _ext(string):
+    '''
+    Does C-name conversion on an extension name.
+    Has some additional special cases on top of _n_item.
+    '''
+    if string in _extension_special_cases:
+        return _n_item(string).lower()
+    else:
+        return string.lower()
+
+def _n(list, namespace):
+    '''
+    Does C-name conversion on a tuple of strings.
+    Different behavior depending on length of tuple, extension/not extension, etc.
+    Basically C-name converts the individual pieces, then joins with underscores.
+    '''
+    if len(list) == 1:
+        parts = list
+    elif len(list) == 2:
+        parts = [list[0], _n_item(list[1])]
+    elif namespace.is_ext:
+        parts = [list[0], _ext(list[1])] + [_n_item(i) for i in list[2:]]
+    else:
+        parts = [list[0]] + [_n_item(i) for i in list[1:]]
+    return '_'.join(parts).lower()
diff --git a/lib/xpp/include/xpp/atom.hpp b/lib/xpp/include/xpp/atom.hpp
new file mode 100644 (file)
index 0000000..ca51ce5
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef XPP_ATOM_HPP
+#define XPP_ATOM_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class atom
+  : public xpp::generic::resource<Connection, xcb_atom_t,
+                                  xpp::x::atom, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_atom_t, Interfaces ...>;
+
+  public:
+    using base::base;
+    using base::operator=;
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::atom<Connection, Interfaces ...>>
+{
+  typedef xcb_atom_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_ATOM_HPP
diff --git a/lib/xpp/include/xpp/colormap.hpp b/lib/xpp/include/xpp/colormap.hpp
new file mode 100644 (file)
index 0000000..49a8944
--- /dev/null
@@ -0,0 +1,77 @@
+#ifndef XPP_COLORMAP_HPP
+#define XPP_COLORMAP_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class colormap
+  : public xpp::generic::resource<Connection, xcb_colormap_t,
+                                  xpp::x::colormap, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_colormap_t,
+                                        xpp::x::colormap, Interfaces ...>;
+
+    template<typename C, typename Create, typename Destroy>
+    colormap(C && c, Create && create, Destroy && destroy)
+      : base(base::make(std::forward<C>(c),
+                        std::forward<Create>(create),
+                        std::forward<Destroy>(destroy)))
+    {}
+
+  public:
+    using base::base;
+    using base::operator=;
+
+    template<typename C>
+    static
+    colormap<Connection, Interfaces ...>
+    create(C && c, uint8_t alloc, xcb_window_t window, xcb_visualid_t visual)
+    {
+      return colormap(
+          std::forward<C>(c),
+          [&](const Connection & c, const xcb_colormap_t & colormap)
+          {
+            xpp::x::create_colormap(c, alloc, colormap, window, visual);
+          },
+          [&](const Connection & c, const xcb_colormap_t & colormap)
+          {
+            xpp::x::free_colormap(c, colormap);
+          });
+    }
+
+    template<typename C>
+    static
+    colormap<Connection, Interfaces ...>
+    create_checked(C && c, uint8_t alloc,
+                   xcb_window_t window, xcb_visualid_t visual)
+    {
+      return colormap(
+          std::forward<C>(c),
+          [&](const Connection & c, const xcb_colormap_t & colormap)
+          {
+            xpp::x::create_colormap_checked(c, alloc, colormap, window, visual);
+          },
+          [&](const Connection & c, const xcb_colormap_t & colormap)
+          {
+            xpp::x::free_colormap_checked(c, colormap);
+          });
+    }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::colormap<Connection, Interfaces ...>>
+{
+  typedef xcb_colormap_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_COLORMAP_HPP
diff --git a/lib/xpp/include/xpp/connection.hpp b/lib/xpp/include/xpp/connection.hpp
new file mode 100644 (file)
index 0000000..a95902e
--- /dev/null
@@ -0,0 +1,157 @@
+#ifndef XPP_CONNECTION_HPP
+#define XPP_CONNECTION_HPP
+
+#include "core.hpp"
+#include "generic/factory.hpp"
+
+#include "xpp/proto/x.hpp"
+
+namespace xpp {
+
+namespace detail {
+
+template<typename Connection, typename ... Extensions>
+class interfaces
+  : public xpp::x::extension::interface<interfaces<Connection, Extensions ...>, Connection>
+  , public Extensions::template interface<interfaces<Connection, Extensions ...>, Connection> ...
+{
+  public:
+    Connection
+    connection(void) const
+    {
+      return static_cast<const Connection &>(*this);
+    }
+}; // class interfaces
+
+} // namespace detail
+
+template<typename ... Extensions>
+class connection
+  : public xpp::core
+  , public xpp::generic::error_dispatcher
+  , public detail::interfaces<connection<Extensions ...>, Extensions ...>
+  // private interfaces: extensions and error_dispatcher
+  , private xpp::x::extension
+  , private xpp::x::extension::error_dispatcher
+  , private Extensions ...
+  , private Extensions::error_dispatcher ...
+{
+  protected:
+    typedef connection<Extensions ...> self;
+
+
+  public:
+    template<typename ... Parameters>
+    explicit
+    connection(Parameters && ... parameters)
+      : xpp::core::core(std::forward<Parameters>(parameters) ...)
+      , detail::interfaces<connection<Extensions ...>, Extensions ...>(*this)
+      , Extensions(static_cast<xcb_connection_t *>(*this)) ...
+      , Extensions::error_dispatcher(static_cast<Extensions &>(*this).get()) ...
+    {
+      m_root_window = screen_of_display(default_screen())->root;
+    }
+
+    virtual
+    ~connection(void)
+    {}
+
+    virtual
+    operator xcb_connection_t *(void) const
+    {
+      return *(static_cast<const core &>(*this));
+    }
+
+    void
+    operator()(const std::shared_ptr<xcb_generic_error_t> & error) const
+    {
+      check<xpp::x::extension, Extensions ...>(error);
+    }
+
+    template<typename Extension>
+    const Extension &
+    extension(void) const
+    {
+      return static_cast<const Extension &>(*this);
+    }
+
+    // TODO
+    // virtual operator Display * const(void) const
+    // {
+    // }
+
+    template<typename Window = xcb_window_t>
+    Window
+    root(void)
+    {
+      using make = xpp::generic::factory::make<self, xcb_window_t, Window>;
+      return make()(*this, m_root_window);
+    }
+
+    template<typename Window = xcb_window_t>
+    Window
+    root(void) const
+    {
+      using make = xpp::generic::factory::make<self, xcb_window_t, Window>;
+      return make()(*this, m_root_window);
+    }
+
+    virtual
+    shared_generic_event_ptr
+    wait_for_event(void) const
+    {
+      try {
+        return core::wait_for_event();
+      } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+        check<xpp::x::extension, Extensions ...>(error);
+      }
+      // re-throw any exception caused by wait_for_event
+      throw;
+    }
+
+    virtual
+    shared_generic_event_ptr
+    wait_for_special_event(xcb_special_event_t * se) const
+    {
+      try {
+        return core::wait_for_special_event(se);
+      } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+        check<xpp::x::extension, Extensions ...>(error);
+      }
+      // re-throw any exception caused by wait_for_special_event
+      throw;
+    }
+
+  private:
+    xcb_window_t m_root_window;
+
+    template<typename Extension, typename Next, typename ... Rest>
+    void
+    check(const std::shared_ptr<xcb_generic_error_t> & error) const
+    {
+      check<Extension>(error);
+      check<Next, Rest ...>(error);
+    }
+
+    template<typename Extension>
+    void
+    check(const std::shared_ptr<xcb_generic_error_t> & error) const
+    {
+      using error_dispatcher = typename Extension::error_dispatcher;
+      auto & dispatcher = static_cast<const error_dispatcher &>(*this);
+      dispatcher(error);
+    }
+}; // class connection
+
+template<>
+template<typename ... Parameters>
+connection<>::connection(Parameters && ... parameters)
+  : xpp::core::core(std::forward<Parameters>(parameters) ...)
+  , detail::interfaces<connection<>>(*this)
+{
+  m_root_window = screen_of_display(static_cast<core &>(*this).default_screen())->root;
+}
+
+} // namespace xpp
+
+#endif // XPP_CONNECTION_HPP
diff --git a/lib/xpp/include/xpp/core.hpp b/lib/xpp/include/xpp/core.hpp
new file mode 100644 (file)
index 0000000..d4b2c34
--- /dev/null
@@ -0,0 +1,340 @@
+#ifndef XPP_CORE_HPP
+#define XPP_CORE_HPP
+
+#include <string>
+#include <memory>
+#include <stdexcept>
+#include <xcb/xcb.h>
+
+namespace xpp {
+
+class connection_error
+  : public std::runtime_error
+{
+  public:
+    connection_error(uint8_t code, const std::string & description)
+      : std::runtime_error(description + "(" + std::to_string(code) + ")")
+      , m_code(code)
+      , m_description(description)
+    {}
+
+    uint8_t
+    code(void)
+    {
+      return m_code;
+    }
+
+    std::string
+    description(void)
+    {
+      return m_description;
+    }
+
+  protected:
+    uint8_t m_code;
+    std::string m_description;
+};
+
+class core
+{
+  protected:
+    using shared_generic_event_ptr = std::shared_ptr<xcb_generic_event_t>;
+
+    int m_screen = 0;
+    // reference counting for xcb_connection_t
+    std::shared_ptr<xcb_connection_t> m_c;
+
+    shared_generic_event_ptr
+    dispatch(const std::string & producer, xcb_generic_event_t * event) const
+    {
+      if (event) {
+        if (event->response_type == 0) {
+          throw std::shared_ptr<xcb_generic_error_t>(
+              reinterpret_cast<xcb_generic_error_t *>(event));
+        }
+
+        return shared_generic_event_ptr(event, std::free);
+      }
+
+      check_connection();
+      throw std::runtime_error(producer + " failed");
+    }
+
+  public:
+    explicit
+    core(xcb_connection_t * c)
+      : m_c(std::shared_ptr<xcb_connection_t>(c, [](...) {}))
+    {}
+
+    template<typename ... ConnectionParameter>
+    explicit
+    core(xcb_connection_t * (*Connect)(ConnectionParameter ...),
+               ConnectionParameter ... connection_parameter)
+      : m_c(std::shared_ptr<xcb_connection_t>(
+          Connect(connection_parameter ...),
+          [&](void *) { disconnect(); }))
+    {}
+
+    // xcb_connect (const char *displayname, int *screenp)
+    explicit
+    core(const std::string & displayname = "")
+      : core(xcb_connect, displayname.c_str(), &m_screen)
+    {}
+
+    // xcb_connect_to_fd (int fd, xcb_auth_info_t *auth_info)
+    explicit
+    core(int fd, xcb_auth_info_t * auth_info)
+      : core(xcb_connect_to_fd, fd, auth_info)
+    {}
+
+    // xcb_connect_to_display_with_auth_info (
+    //     const char *display, xcb_auth_info_t *auth, int *screen)
+    explicit
+    core(const std::string & display, xcb_auth_info_t * auth)
+      : core(xcb_connect_to_display_with_auth_info,
+                   display.c_str(), auth, &m_screen)
+    {}
+
+    virtual
+    ~core(void)
+    {}
+
+    virtual
+    xcb_connection_t *
+    operator*(void) const
+    {
+      return m_c.get();
+    }
+
+    virtual
+    operator xcb_connection_t *(void) const
+    {
+      return m_c.get();
+    }
+
+    virtual
+    int
+    default_screen(void) const
+    {
+      return m_screen;
+    }
+
+    virtual
+    int
+    flush(void) const
+    {
+      return xcb_flush(m_c.get());
+    }
+
+    virtual
+    uint32_t
+    get_maximum_request_length(void) const
+    {
+      return xcb_get_maximum_request_length(m_c.get());
+    }
+
+    virtual
+    void
+    prefetch_maximum_request_length(void) const
+    {
+      xcb_prefetch_maximum_request_length(m_c.get());
+    }
+
+    virtual
+    shared_generic_event_ptr
+    wait_for_event(void) const
+    {
+      return dispatch("wait_for_event", xcb_wait_for_event(m_c.get()));
+    }
+
+    virtual
+    shared_generic_event_ptr
+    poll_for_event(void) const
+    {
+      return shared_generic_event_ptr(xcb_poll_for_event(m_c.get()));
+    }
+
+    virtual
+    shared_generic_event_ptr
+    poll_for_queued_event(void) const
+    {
+      return shared_generic_event_ptr(xcb_poll_for_queued_event(m_c.get()));
+    }
+
+    virtual
+    shared_generic_event_ptr
+    poll_for_special_event(xcb_special_event_t * se) const
+    {
+      return shared_generic_event_ptr(xcb_poll_for_special_event(m_c.get(), se));
+    }
+
+    // virtual
+    // shared_generic_event_ptr
+    // poll_for_special_event(const std::shared_ptr<xcb_special_event_t> & se) const
+    // {
+    //   return poll_for_special_event(se.get());
+    // }
+
+    virtual
+    shared_generic_event_ptr
+    wait_for_special_event(xcb_special_event_t * se) const
+    {
+      return dispatch("wait_for_special_event",
+                      xcb_wait_for_special_event(m_c.get(), se));
+    }
+
+    // virtual
+    // shared_generic_event_ptr
+    // wait_for_special_event(const std::shared_ptr<xcb_special_event_t> & se) const
+    // {
+    //   return wait_for_special_event(se.get());
+    // }
+
+    // xcb_special_event_t has incomplete type -> no std::shared_ptr
+    virtual
+    xcb_special_event_t *
+    register_for_special_xge(xcb_extension_t * ext,
+                             uint32_t eid,
+                             uint32_t * stamp) const
+    {
+      return xcb_register_for_special_xge(m_c.get(), ext, eid, stamp);
+    }
+
+    virtual
+    void
+    unregister_for_special_event(xcb_special_event_t * se) const
+    {
+      xcb_unregister_for_special_event(m_c.get(), se);
+    }
+
+    virtual
+    std::shared_ptr<xcb_generic_error_t>
+    request_check(xcb_void_cookie_t cookie) const
+    {
+      return std::shared_ptr<xcb_generic_error_t>(
+          xcb_request_check(m_c.get(), cookie));
+    }
+
+    virtual
+    void
+    discard_reply(unsigned int sequence) const
+    {
+      xcb_discard_reply(m_c.get(), sequence);
+    }
+
+    // The result must not be freed.
+    // This storage is managed by the cache itself.
+    virtual
+    const xcb_query_extension_reply_t *
+    get_extension_data(xcb_extension_t * ext) const
+    {
+      return xcb_get_extension_data(m_c.get(), ext);
+    }
+
+    virtual
+    void
+    prefetch_extension_data(xcb_extension_t * ext) const
+    {
+      xcb_prefetch_extension_data(m_c.get(), ext);
+    }
+
+    virtual
+    const xcb_setup_t *
+    get_setup(void) const
+    {
+      return xcb_get_setup(m_c.get());
+    }
+
+    virtual
+    int
+    get_file_descriptor(void) const
+    {
+      return xcb_get_file_descriptor(m_c.get());
+    }
+
+    virtual
+    int
+    connection_has_error(void) const
+    {
+      return xcb_connection_has_error(m_c.get());
+    }
+
+    virtual
+    void
+    disconnect(void) const
+    {
+      xcb_disconnect(m_c.get());
+    }
+
+    // hostname, display, screen
+    virtual
+    std::tuple<std::string, int, int>
+    parse_display(const std::string & name) const
+    {
+      int screen = 0;
+      int display = 0;
+      char * host = NULL;
+      std::string hostname;
+
+      xcb_parse_display(name.c_str(), &host, &display, &screen);
+      if (host != NULL) {
+        hostname = std::string(host);
+      }
+
+      return std::make_tuple(hostname, display, screen);
+    }
+
+    virtual
+    uint32_t
+    generate_id(void) const
+    {
+      return xcb_generate_id(m_c.get());
+    }
+
+    xcb_screen_t *
+    screen_of_display(int screen)
+    {
+      xcb_screen_iterator_t iter;
+
+      iter = xcb_setup_roots_iterator(xcb_get_setup(m_c.get()));
+      for (; iter.rem; --screen, xcb_screen_next(&iter))
+        if (screen == 0)
+          return iter.data;
+
+      return NULL;
+    }
+
+    void
+    check_connection(void) const
+    {
+      switch (xcb_connection_has_error(m_c.get())) {
+        case XCB_CONN_ERROR:
+          throw(connection_error(
+                XCB_CONN_ERROR, "XCB_CONN_ERROR"));
+
+        case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
+          throw(connection_error(XCB_CONN_CLOSED_EXT_NOTSUPPORTED,
+                                 "XCB_CONN_CLOSED_EXT_NOTSUPPORTED"));
+
+        case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
+          throw(connection_error(XCB_CONN_CLOSED_MEM_INSUFFICIENT,
+                                 "XCB_CONN_CLOSED_MEM_INSUFFICIENT"));
+
+        case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
+          throw(connection_error(XCB_CONN_CLOSED_REQ_LEN_EXCEED,
+                                 "XCB_CONN_CLOSED_REQ_LEN_EXCEED"));
+
+        case XCB_CONN_CLOSED_PARSE_ERR:
+          throw(connection_error(XCB_CONN_CLOSED_PARSE_ERR,
+                                 "XCB_CONN_CLOSED_PARSE_ERR"));
+
+        case XCB_CONN_CLOSED_INVALID_SCREEN:
+          throw(connection_error(XCB_CONN_CLOSED_INVALID_SCREEN,
+                                 "XCB_CONN_CLOSED_INVALID_SCREEN"));
+      };
+    }
+}; // class core
+
+} // namespace xpp
+
+#endif // XPP_CORE_HPP
diff --git a/lib/xpp/include/xpp/cursor.hpp b/lib/xpp/include/xpp/cursor.hpp
new file mode 100644 (file)
index 0000000..9237230
--- /dev/null
@@ -0,0 +1,142 @@
+#ifndef XPP_CURSOR_HPP
+#define XPP_CURSOR_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class cursor
+  : public xpp::generic::resource<Connection, xcb_cursor_t,
+                                  xpp::x::cursor, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_cursor_t,
+                                        xpp::x::cursor, Interfaces ...>;
+
+    template<typename C, typename Create, typename Destroy>
+    cursor(C && c, Create && create, Destroy && destroy)
+      : base(base::make(std::forward<C>(c),
+                        std::forward<Create>(create),
+                        std::forward<Destroy>(destroy)))
+    {}
+
+  public:
+    using base::base;
+    using base::operator=;
+
+    template<typename C>
+    static
+    cursor<Connection, Interfaces ...>
+    create(C && c,
+           xcb_pixmap_t source, xcb_pixmap_t mask,
+           uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+           uint16_t back_red, uint16_t back_green, uint16_t back_blue,
+           uint16_t x, uint16_t y)
+    {
+      return cursor(
+            std::forward<C>(c),
+               [&](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::create_cursor(c, cursor,
+                                       source, mask,
+                                       fore_red, fore_green, fore_blue,
+                                       back_red, back_green, back_blue,
+                                       x, y);
+               },
+               [&](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::free_cursor(c, cursor);
+               });
+    }
+
+    template<typename C>
+    static
+    cursor<Connection, Interfaces ...>
+    create_checked(C && c,
+                   xcb_pixmap_t source, xcb_pixmap_t mask,
+                   uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+                   uint16_t back_red, uint16_t back_green, uint16_t back_blue,
+                   uint16_t x, uint16_t y)
+    {
+      return cursor(
+            std::forward<C>(c),
+               [&](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::create_cursor_checked(c, cursor,
+                                               source, mask,
+                                               fore_red, fore_green, fore_blue,
+                                               back_red, back_green, back_blue,
+                                               x, y);
+               },
+               [&](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::free_cursor_checked(c, cursor);
+               });
+    }
+
+    template<typename C>
+    static
+    cursor<Connection, Interfaces ...>
+    create_glyph(C && c,
+                 xcb_font_t source_font, xcb_font_t mask_font,
+                 uint16_t source_char, uint16_t mask_char,
+                 uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+                 uint16_t back_red, uint16_t back_green, uint16_t back_blue)
+    {
+      return cursor(
+              std::forward<C>(c),
+               [&](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::create_glyph_cursor(c, cursor,
+                                             source_font, mask_font,
+                                             source_char, mask_char,
+                                             fore_red, fore_green, fore_blue,
+                                             back_red, back_green, back_blue);
+               },
+               [](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::free_cursor(c, cursor);
+               });
+    }
+
+    template<typename C>
+    static
+    cursor<Connection, Interfaces ...>
+    create_glyph_checked(C && c,
+                         xcb_font_t source_font, xcb_font_t mask_font,
+                         uint16_t source_char, uint16_t mask_char,
+                         uint16_t fore_red, uint16_t fore_green, uint16_t fore_blue,
+                         uint16_t back_red, uint16_t back_green, uint16_t back_blue)
+    {
+      return cursor(
+              std::forward<C>(c),
+               [&](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::create_glyph_cursor_checked(c, cursor,
+                                                     source_font, mask_font,
+                                                     source_char, mask_char,
+                                                     fore_red, fore_green, fore_blue,
+                                                     back_red, back_green, back_blue);
+               },
+               [](Connection & c, const xcb_cursor_t & cursor)
+               {
+                 xpp::x::free_cursor_checked(c, cursor);
+               });
+    }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::cursor<Connection, Interfaces ...>>
+{
+  typedef xcb_cursor_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_CURSOR_HPP
diff --git a/lib/xpp/include/xpp/drawable.hpp b/lib/xpp/include/xpp/drawable.hpp
new file mode 100644 (file)
index 0000000..d23af61
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef XPP_DRAWABLE_HPP
+#define XPP_DRAWABLE_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class drawable
+  : public xpp::generic::resource<Connection, xcb_drawable_t,
+                                  xpp::x::drawable, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_drawable_t,
+                                        xpp::x::drawable, Interfaces ...>;
+
+  public:
+    using base::base;
+    using base::operator=;
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::drawable<Connection, Interfaces ...>>
+{
+  typedef xcb_drawable_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_DRAWABLE_HPP
diff --git a/lib/xpp/include/xpp/event.hpp b/lib/xpp/include/xpp/event.hpp
new file mode 100644 (file)
index 0000000..6092e84
--- /dev/null
@@ -0,0 +1,210 @@
+#ifndef XPP_EVENT_HPP
+#define XPP_EVENT_HPP
+
+#include <climits>
+#include <map>
+#include <vector>
+#include <unordered_map>
+
+#include "xpp/proto/x.hpp"
+
+#define MAX_PRIORITY UINT32_MAX
+
+namespace xpp {
+
+namespace event {
+
+namespace detail {
+
+class dispatcher {
+  public:
+    virtual ~dispatcher(void) {}
+    template<typename Event> void dispatch(const Event & e);
+}; // class dispatcher
+
+template<typename Event>
+class sink : virtual public dispatcher
+{
+  public:
+    virtual ~sink(void) {}
+    virtual void handle(const Event &) = 0;
+};
+
+} // namespace detail
+
+template<typename Event, typename ... Events>
+class sink
+  : public detail::sink<Event>
+  , public detail::sink<Events> ...
+{};
+
+template<typename Connection, typename ... Extensions>
+class registry
+  : public xpp::x::event::dispatcher<Connection>
+  , public Extensions::template event_dispatcher<Connection> ...
+{
+  public:
+    typedef unsigned int priority;
+
+    template<typename C>
+    explicit
+    registry(C && c)
+      : xpp::x::event::dispatcher<Connection>(std::forward<C>(c))
+      , Extensions::template event_dispatcher<Connection>(
+          std::forward<C>(c), c.template extension<Extensions>()) ...
+      , m_c(std::forward<C>(c))
+    {}
+
+    bool
+    dispatch(const std::shared_ptr<xcb_generic_event_t> & event) const
+    {
+      return dispatch<xpp::x::extension, Extensions ...>(event);
+    }
+
+    template<typename Event, typename ... Rest>
+    void
+    attach(priority p, sink<Event, Rest ...> * s)
+    {
+      attach<sink<Event, Rest ...>, Event, Rest ...>(p, s);
+    }
+
+    template<typename Event, typename ... Rest>
+    void
+    detach(priority p, sink<Event, Rest ...> * s)
+    {
+      detach<sink<Event, Rest ...>, Event, Rest ...>(p, s);
+    }
+
+  private:
+    typedef std::multimap<priority, detail::dispatcher *> priority_map;
+
+    Connection m_c;
+    std::unordered_map<uint8_t, priority_map> m_dispatchers;
+
+    template<typename Event>
+    uint8_t opcode(const xpp::x::extension &) const
+    {
+      return Event::opcode();
+    }
+
+    template<typename Event, typename Extension>
+    uint8_t opcode(const Extension & extension) const
+    {
+      return Event::opcode(extension);
+    }
+
+    template<typename Event>
+    uint8_t opcode(void) const
+    {
+      return opcode<Event>(m_c.template extension<typename Event::extension>());
+    }
+
+    template<typename Event>
+    void
+    handle(const Event & event) const
+    {
+      try {
+        for (auto & item : m_dispatchers.at(opcode<Event>())) {
+          item.second->dispatch(event);
+        }
+      } catch (...) {}
+    }
+
+    struct handler {
+      handler(const registry<Connection, Extensions ...> & registry)
+        : m_registry(registry)
+      {}
+
+      const registry<Connection, Extensions ...> & m_registry;
+
+      template<typename Event>
+      void
+      operator()(const Event & event) const
+      {
+        m_registry.handle(event);
+      }
+    };
+
+    template<typename Extension>
+    bool
+    dispatch(const std::shared_ptr<xcb_generic_event_t> & event) const
+    {
+      typedef const typename Extension::template event_dispatcher<Connection> & dispatcher;
+      return static_cast<dispatcher>(*this)(handler(*this), event);
+    }
+
+    template<typename Extension, typename Next, typename ... Rest>
+    bool
+    dispatch(const std::shared_ptr<xcb_generic_event_t> & event) const
+    {
+      dispatch<Extension>(event);
+      return dispatch<Next, Rest ...>(event);
+    }
+
+    template<typename Sink, typename Event>
+    void
+    attach(priority p, Sink * s)
+    {
+      attach(p, s, opcode<Event>());
+    }
+
+    template<typename Sink, typename Event, typename Next, typename ... Rest>
+    void
+    attach(priority p, Sink * s)
+    {
+      attach(p, s, opcode<Event>());
+      attach<Sink, Next, Rest ...>(p, s);
+    }
+
+    void attach(priority p, detail::dispatcher * d, uint8_t opcode)
+    {
+      m_dispatchers[opcode].emplace(p, d);
+    }
+
+    template<typename Sink, typename Event>
+    void
+    detach(priority p, Sink * s)
+    {
+      detach(p, s, opcode<Event>());
+    }
+
+    template<typename Sink, typename Event, typename Next, typename ... Rest>
+    void
+    detach(priority p, Sink * s)
+    {
+      detach(p, s, opcode<Event>());
+      detach<Sink, Next, Rest ...>(p, s);
+    }
+
+    void
+    detach(priority p, detail::dispatcher * d, uint8_t opcode)
+    {
+      try {
+        auto & prio_map = m_dispatchers.at(opcode);
+        const auto & prio_sink_pair = prio_map.equal_range(p);
+        for (auto it = prio_sink_pair.first; it != prio_sink_pair.second; ) {
+          if (d == it->second) {
+            it = prio_map.erase(it);
+          } else {
+            ++it;
+          }
+        }
+      } catch (...) {}
+    }
+
+}; // xpp::event::source
+
+} // namespace event
+
+} // namespace xpp
+
+template<typename Event>
+void xpp::event::detail::dispatcher::dispatch(const Event & e)
+{
+  auto event_sink = dynamic_cast<xpp::event::detail::sink<Event> *>(this);
+  if (event_sink != nullptr) {
+    event_sink->handle(e);
+  }
+}
+
+#endif // XPP_EVENT_HPP
diff --git a/lib/xpp/include/xpp/flags.makefile b/lib/xpp/include/xpp/flags.makefile
new file mode 100644 (file)
index 0000000..e59e93f
--- /dev/null
@@ -0,0 +1,40 @@
+LIBS=x11 \
+     xcb \
+     xcb-icccm \
+     xcb-sync \
+     xcb-xf86dri \
+     xcb-xprint \
+     xcb-xinput \
+     xcb-shape \
+     xcb-shm \
+     xcb-render \
+     xcb-proto \
+     xcb-event \
+     xcb-xfixes \
+     xcb-xkb \
+     xcb-dri3 \
+     xcb-ewmh \
+     xcb-util \
+     xcb-renderutil \
+     xcb-xtest \
+     xcb-xevie \
+     xcb-keysyms \
+     xcb-image \
+     xcb-composite \
+     xcb-randr \
+     xcb-present \
+     xcb-xv \
+     xcb-aux \
+     xcb-record \
+     xcb-dpms \
+     xcb-glx \
+     xcb-atom \
+     xcb-damage \
+     xcb-screensaver \
+     xcb-xvmc \
+     xcb-res \
+     xcb-xinerama \
+     xcb-dri2
+
+CXXFLAGS=-std=c++11 -Wall -O0 $(shell pkg-config --cflags ${LIBS})
+LDFLAGS=$(shell pkg-config --libs ${LIBS})
diff --git a/lib/xpp/include/xpp/font.hpp b/lib/xpp/include/xpp/font.hpp
new file mode 100644 (file)
index 0000000..0525c79
--- /dev/null
@@ -0,0 +1,74 @@
+#ifndef XPP_FONT_HPP
+#define XPP_FONT_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class font
+  : public xpp::generic::resource<Connection, xcb_font_t,
+                                  xpp::x::font, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_font_t,
+                                        xpp::x::font, Interfaces ...>;
+
+    template<typename C, typename Create, typename Destroy>
+    font(C && c, Create && create, Destroy && destroy)
+      : base(base::make(std::forward<C>(c),
+                        std::forward<Create>(create),
+                        std::forward<Destroy>(destroy)))
+    {}
+
+  public:
+    using base::base;
+    using base::operator=;
+
+    template<typename C>
+    static
+    font<Connection, Interfaces ...>
+    open(C && c, const std::string & name) noexcept
+    {
+      return font(std::forward<C>(c),
+                  [&](const Connection & c, const xcb_font_t & font)
+                  {
+                    xpp::x::open_font(c, font, name);
+                  },
+                  [&](const Connection & c, const xcb_font_t & font)
+                  {
+                    xpp::x::close_font(c, font);
+                  });
+    }
+
+    template<typename C>
+    static
+    font<Connection, Interfaces ...>
+    open_checked(C && c, const std::string & name)
+    {
+      return font(std::forward<C>(c),
+                  [&](const Connection & c, const xcb_font_t & font)
+                  {
+                    xpp::x::open_font_checked(c, font, name);
+                  },
+                  [&](const Connection & c, const xcb_font_t & font)
+                  {
+                    xpp::x::close_font_checked(c, font);
+                  });
+    }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::font<Connection, Interfaces ...>>
+{
+  typedef xcb_font_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_FONT_HPP
diff --git a/lib/xpp/include/xpp/fontable.hpp b/lib/xpp/include/xpp/fontable.hpp
new file mode 100644 (file)
index 0000000..e3b5014
--- /dev/null
@@ -0,0 +1,35 @@
+#ifndef XPP_FONTABLE_HPP
+#define XPP_FONTABLE_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class fontable
+  : public xpp::generic::resource<Connection, xcb_fontable_t,
+                                  xpp::x::fontable, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_fontable_t,
+                                        xpp::x::fontable, Interfaces ...>;
+
+  public:
+    using base::base;
+    using base::operator=;
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::fontable<Connection, Interfaces ...>>
+{
+  typedef xcb_fontable_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_FONTABLE_HPP
diff --git a/lib/xpp/include/xpp/gcontext.hpp b/lib/xpp/include/xpp/gcontext.hpp
new file mode 100644 (file)
index 0000000..d6a1299
--- /dev/null
@@ -0,0 +1,114 @@
+#ifndef XPP_GCONTEXT_HPP
+#define XPP_GCONTEXT_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class gcontext
+  : public xpp::generic::resource<Connection, xcb_gcontext_t,
+                                  xpp::x::gcontext, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_gcontext_t,
+                                        xpp::x::gcontext, Interfaces ...>;
+
+    template<typename C, typename Create, typename Destroy>
+    gcontext(C && c, Create && create, Destroy && destroy)
+      : base(base::make(std::forward<C>(c),
+                        std::forward<Create>(create),
+                        std::forward<Destroy>(destroy)))
+    {}
+
+  public:
+    using base::base;
+    using base::operator=;
+
+    template<typename C>
+    static
+    gcontext<Connection, Interfaces ...>
+    create(C && c, xcb_drawable_t drawable,
+           uint32_t value_mask, const uint32_t * value_list)
+    {
+      return gcontext(
+          std::forward<C>(c),
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::create_gc(c, gcontext, drawable, value_mask, value_list);
+          },
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::free_gc(c, gcontext);
+          });
+    }
+
+    template<typename C>
+    static
+    gcontext<Connection, Interfaces ...>
+    create_checked(C && c, xcb_drawable_t drawable,
+                   uint32_t value_mask, const uint32_t * value_list)
+    {
+      return gcontext(
+          std::forward<C>(c),
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::create_gc_checked(c, gcontext, drawable,
+                                      value_mask, value_list);
+          },
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::free_gc_checked(c, gcontext);
+          });
+    }
+
+    template<typename C>
+    static
+    gcontext<Connection, Interfaces ...>
+    copy(C && c, xcb_gcontext_t src_gc, uint32_t value_mask)
+    {
+      return gcontext(
+          std::forward<C>(c),
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::copy_gc(c, src_gc, gcontext, value_mask);
+          },
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::free_gc(c, gcontext);
+          });
+    }
+
+
+    template<typename C>
+    static
+    gcontext<Connection, Interfaces ...>
+    copy_checked(C && c, xcb_gcontext_t src_gc, uint32_t value_mask)
+    {
+      return gcontext(
+          std::forward<C>(c),
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::copy_gc_checked(c, src_gc, gcontext, value_mask);
+          },
+          [&](const Connection & c, const xcb_gcontext_t & gcontext)
+          {
+            xpp::x::free_gc_checked(c, gcontext);
+          });
+    }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::gcontext<Connection, Interfaces ...>>
+{
+  typedef xcb_gcontext_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_GCONTEXT_HPP
diff --git a/lib/xpp/include/xpp/generic.hpp b/lib/xpp/include/xpp/generic.hpp
new file mode 100644 (file)
index 0000000..9fca692
--- /dev/null
@@ -0,0 +1,15 @@
+#ifndef XPP_GENERIC_HPP
+#define XPP_GENERIC_HPP
+
+#include "generic/error.hpp"
+#include "generic/event.hpp"
+#include "generic/factory.hpp"
+#include "generic/request.hpp"
+#include "generic/resource.hpp"
+#include "generic/extension.hpp"
+#include "generic/signature.hpp"
+#include "generic/reply_iterator.hpp"
+#include "generic/iterator_traits.hpp"
+#include "generic/input_iterator_adapter.hpp"
+
+#endif // XPP_GENERIC_HPP
diff --git a/lib/xpp/include/xpp/generic/error.hpp b/lib/xpp/include/xpp/generic/error.hpp
new file mode 100644 (file)
index 0000000..c3d86cf
--- /dev/null
@@ -0,0 +1,96 @@
+#ifndef XPP_GENERIC_ERROR_HPP
+#define XPP_GENERIC_ERROR_HPP
+
+#include <iostream> // shared_ptr
+#include <memory> // shared_ptr
+#include <xcb/xcb.h> // xcb_generic_error_t
+
+namespace xpp { namespace generic {
+
+class error_dispatcher {
+  public:
+    virtual
+      void operator()(const std::shared_ptr<xcb_generic_error_t> &) const = 0;
+};
+
+namespace detail {
+
+template<typename Object>
+void
+dispatch(const Object & object,
+         const std::shared_ptr<xcb_generic_error_t> & error,
+         std::true_type)
+{
+  static_cast<const xpp::generic::error_dispatcher &>(object)(error);
+}
+
+template<typename Object>
+void
+dispatch(const Object &,
+         const std::shared_ptr<xcb_generic_error_t> & error,
+         std::false_type)
+{
+  throw error;
+}
+
+} // namespace detail
+
+template<typename Object>
+void
+dispatch(const Object & object,
+         const std::shared_ptr<xcb_generic_error_t> & error)
+{
+  detail::dispatch(object,
+                   error,
+                   std::is_base_of<xpp::generic::error_dispatcher, Object>());
+}
+
+template<typename Derived, typename Error>
+class error
+  : public std::runtime_error
+{
+  public:
+    error(const std::shared_ptr<xcb_generic_error_t> & error)
+      : runtime_error(get_error_description(error.get()))
+      , m_error(error)
+    {}
+
+    virtual
+    ~error(void)
+    {}
+
+    virtual
+    operator const Error &(void) const
+    {
+      return reinterpret_cast<const Error &>(*m_error);
+    }
+
+    virtual
+    const Error &
+    operator*(void) const
+    {
+      return reinterpret_cast<const Error &>(*m_error);
+    }
+
+    virtual
+    Error *
+    operator->(void) const
+    {
+      return reinterpret_cast<Error * const>(m_error.get());
+    }
+
+  protected:
+    virtual
+    std::string
+    get_error_description(xcb_generic_error_t * error) const
+    {
+      return std::string(Derived::description())
+        + " (" + std::to_string(error->error_code) + ")";
+    }
+
+    std::shared_ptr<xcb_generic_error_t> m_error;
+}; // class error
+
+} } // xpp::generic
+
+#endif // XPP_GENERIC_ERROR_HPP
diff --git a/lib/xpp/include/xpp/generic/event.hpp b/lib/xpp/include/xpp/generic/event.hpp
new file mode 100644 (file)
index 0000000..ae0d0a1
--- /dev/null
@@ -0,0 +1,45 @@
+#ifndef XPP_GENERIC_EVENT_HPP
+#define XPP_GENERIC_EVENT_HPP
+
+#include <memory> // shared_ptr
+#include <xcb/xcb.h> // xcb_generic_event_t
+
+namespace xpp { namespace generic {
+
+template<typename Event>
+class event {
+  public:
+    event(const std::shared_ptr<xcb_generic_event_t> & event)
+      : m_event(event)
+    {}
+
+    virtual
+    ~event(void) {}
+
+    virtual
+    operator const Event &(void) const
+    {
+      return reinterpret_cast<const Event &>(*m_event);
+    }
+
+    virtual
+    const Event &
+    operator*(void) const
+    {
+      return reinterpret_cast<const Event &>(*m_event);
+    }
+
+    virtual
+    Event *
+    operator->(void) const
+    {
+      return reinterpret_cast<Event * const>(m_event.get());
+    }
+
+  protected:
+    std::shared_ptr<xcb_generic_event_t> m_event;
+}; // class event
+
+} } // namespace xpp::generic
+
+#endif // XPP_GENERIC_EVENT_HPP
diff --git a/lib/xpp/include/xpp/generic/extension.hpp b/lib/xpp/include/xpp/generic/extension.hpp
new file mode 100644 (file)
index 0000000..ad001ce
--- /dev/null
@@ -0,0 +1,59 @@
+#ifndef XPP_GENERIC_EXTENSION_HPP
+#define XPP_GENERIC_EXTENSION_HPP
+
+// #include <iostream>
+#include <xcb/xcb.h>
+
+namespace xpp { namespace generic {
+
+template<typename Derived, xcb_extension_t * Id>
+class extension
+{
+  public:
+    extension(xcb_connection_t * const c)
+      : m_c(c)
+    {
+      prefetch();
+    }
+
+    const xcb_query_extension_reply_t &
+    operator*(void) const
+    {
+      return *m_extension;
+    }
+
+    const xcb_query_extension_reply_t *
+    operator->(void) const
+    {
+      return m_extension;
+    }
+
+    operator const xcb_query_extension_reply_t *(void) const
+    {
+      return m_extension;
+    }
+
+    Derived &
+    get(void)
+    {
+      m_extension = xcb_get_extension_data(m_c, Id);
+      return static_cast<Derived &>(*this);
+    }
+
+    Derived &
+    prefetch(void)
+    {
+      xcb_prefetch_extension_data(m_c, Id);
+      return static_cast<Derived &>(*this);
+    }
+
+  private:
+    xcb_connection_t * m_c = nullptr;
+    // The result must not be freed.
+    // This storage is managed by the cache itself.
+    const xcb_query_extension_reply_t * m_extension = nullptr;
+}; // class extension
+
+} } // namespace xpp::generic
+
+#endif // XPP_GENERIC_EXTENSION_HPP
diff --git a/lib/xpp/include/xpp/generic/factory.hpp b/lib/xpp/include/xpp/generic/factory.hpp
new file mode 100644 (file)
index 0000000..1e023d1
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef XPP_GENERIC_FACTORY_HPP
+#define XPP_GENERIC_FACTORY_HPP
+
+#include <utility> // std::forward
+
+namespace xpp { namespace generic {
+
+namespace factory {
+
+template<typename ReturnType>
+class make_object
+{
+  public:
+    template<typename Connection, typename ... Parameter>
+    ReturnType
+    operator()(Connection &&, Parameter && ... parameter) const
+    {
+      return ReturnType { std::forward<Parameter>(parameter) ... };
+    }
+};
+
+template<typename ReturnType>
+class make_object_with_member
+{
+  public:
+    template<typename Member, typename Connection, typename ... Parameter>
+    ReturnType
+    operator()(Connection && c, Member && member, Parameter && ... parameter) const
+    {
+      return ReturnType { std::forward<Member>(member)
+                        , std::forward<Connection>(c)
+                        , std::forward<Parameter>(parameter) ...
+                        };
+    }
+};
+
+template<typename ReturnType>
+class make_object_with_connection
+{
+  public:
+    template<typename Connection, typename ... Parameter>
+    ReturnType
+    operator()(Connection && c, Parameter && ... parameter) const
+    {
+      return ReturnType { std::forward<Connection>(c)
+                        , std::forward<Parameter>(parameter) ...
+                        };
+    }
+};
+
+template<typename ReturnType>
+class make_fundamental {
+  public:
+    template<typename Connection, typename Member, typename ... Parameter>
+    ReturnType
+    operator()(Connection &&, Member && member) const
+    {
+      return std::forward<Member>(member);
+    }
+};
+
+template<typename Connection,
+         typename MemberType,
+         typename ReturnType,
+         typename ... Parameter>
+class make
+  : public std::conditional<
+      std::is_constructible<ReturnType, MemberType>::value,
+      make_fundamental<ReturnType>,
+      typename std::conditional<
+        std::is_constructible<ReturnType,
+                              MemberType,
+                              Connection,
+                              Parameter ...>::value,
+        make_object_with_member<ReturnType>,
+        typename std::conditional<
+          std::is_constructible<ReturnType,
+                                Connection,
+                                MemberType,
+                                Parameter ...>::value,
+          make_object_with_connection<ReturnType>,
+          make_object<ReturnType>
+        >::type
+      >::type
+    >::type
+{};
+
+} // namespace factory
+
+} } // xpp::generic
+
+#endif // XPP_GENERIC_FACTORY_HPP
diff --git a/lib/xpp/include/xpp/generic/input_iterator_adapter.hpp b/lib/xpp/include/xpp/generic/input_iterator_adapter.hpp
new file mode 100644 (file)
index 0000000..9fe8172
--- /dev/null
@@ -0,0 +1,163 @@
+#ifndef XPP_GENERIC_INPUT_ITERATOR_ADAPTER_HPP
+#define XPP_GENERIC_INPUT_ITERATOR_ADAPTER_HPP
+
+#include <utility>
+#include <iterator>
+#include <type_traits>
+
+#define GENERATE_HAS_MEMBER(member)                                               \
+                                                                                  \
+template<typename T, bool B>                                                      \
+class HasMember_##member {                                                        \
+  private:                                                                        \
+    using Yes = char[2];                                                          \
+    using  No = char[1];                                                          \
+                                                                                  \
+    struct Fallback { int member; };                                              \
+    struct Derived : T, Fallback {};                                              \
+                                                                                  \
+    template<typename U>                                                          \
+    static No& test(decltype(U::member)*);                                        \
+    template<typename U>                                                          \
+    static Yes& test(U*);                                                         \
+                                                                                  \
+  public:                                                                         \
+    static constexpr bool RESULT = sizeof(test<Derived>(nullptr)) == sizeof(Yes); \
+};                                                                                \
+                                                                                  \
+template<typename T>                                                              \
+class HasMember_##member<T, false> {                                              \
+  public:                                                                         \
+    static constexpr bool RESULT = false;                                         \
+};                                                                                \
+                                                                                  \
+template<typename T>                                                              \
+struct has_member_##member                                                        \
+  : public std::integral_constant<                                                \
+        bool,                                                                     \
+        HasMember_##member<T, std::is_class<T>::value>::RESULT                    \
+      >                                                                           \
+{};
+
+GENERATE_HAS_MEMBER(first)
+GENERATE_HAS_MEMBER(second)
+
+// namespace iterator {
+
+template<typename Iterator>
+struct value_iterator_base {
+  value_iterator_base(const Iterator & iterator)
+    : m_iterator(iterator)
+  {}
+
+  bool
+  operator==(const value_iterator_base & other)
+  {
+    return m_iterator == other.m_iterator;
+  }
+
+  bool
+  operator!=(const value_iterator_base & other)
+  {
+    return m_iterator != other.m_iterator;
+  }
+
+  void
+  operator++(void)
+  {
+    ++m_iterator;
+  }
+
+  template<typename Key, typename Value>
+  const Value &
+  get_value(const std::pair<Key, Value> & pair)
+  {
+    return pair.second;
+  }
+
+  template<typename Value>
+  const Value &
+  get_value(const Value & v)
+  {
+    return v;
+  }
+
+  Iterator m_iterator;
+};
+
+template<typename Iterator>
+struct value_iterator_pair
+  : public value_iterator_base<Iterator>
+  , public std::iterator<typename std::input_iterator_tag,
+                         // value_type
+                         typename Iterator::value_type::second_type,
+                         typename std::iterator_traits<Iterator>::difference_type,
+                         // pointer
+                         typename Iterator::value_type::second_type *,
+                         // reference
+                         const typename Iterator::value_type::second_type &>
+{
+  typedef value_iterator_base<Iterator> base;
+  using base::base;
+
+  const typename Iterator::value_type::second_type &
+  operator*(void)
+  {
+    return base::get_value(*base::m_iterator);
+  }
+};
+
+template<typename Iterator>
+struct value_iterator_integral
+  : public value_iterator_base<Iterator>
+  , public std::iterator<typename std::input_iterator_tag,
+                         typename std::iterator_traits<Iterator>::value_type,
+                         typename std::iterator_traits<Iterator>::difference_type,
+                         typename std::iterator_traits<Iterator>::pointer,
+                         typename std::iterator_traits<Iterator>::reference>
+{
+  typedef value_iterator_base<Iterator> base;
+  using base::base;
+
+  const typename Iterator::value_type &
+  operator*(void)
+  {
+    return base::get_value(*base::m_iterator);
+  }
+};
+
+template<typename Iterator>
+struct value_iterator
+  : public std::conditional<
+        has_member_first<typename Iterator::value_type>::value
+        && has_member_second<typename Iterator::value_type>::value,
+        value_iterator_pair<Iterator>,
+        value_iterator_integral<Iterator>
+      >::type
+{
+  typedef typename std::conditional<
+      has_member_first<typename Iterator::value_type>::value
+      && has_member_second<typename Iterator::value_type>::value,
+      value_iterator_pair<Iterator>,
+      value_iterator_integral<Iterator>
+    >::type base;
+  using base::base;
+};
+
+template<typename T, bool B = true>
+struct value_type {
+  typedef typename std::conditional<
+    has_member_second<typename T::value_type>::value,
+    typename T::value_type::second_type,
+    typename T::value_type>::type
+      type;
+};
+
+template<typename T>
+struct value_type<T, false> {
+  typedef typename std::remove_const<
+            typename std::remove_pointer<T>::type
+          >::type type;
+};
+
+#endif // XPP_GENERIC_INPUT_ITERATOR_ADAPTER_HPP
diff --git a/lib/xpp/include/xpp/generic/iterator_traits.hpp b/lib/xpp/include/xpp/generic/iterator_traits.hpp
new file mode 100644 (file)
index 0000000..8a75d68
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef XPP_GENERIC_ITERATOR_TRAITS_HPP
+#define XPP_GENERIC_ITERATOR_TRAITS_HPP
+
+namespace xpp {
+
+namespace generic {
+
+template<typename T>
+struct traits
+{
+  typedef T type;
+};
+
+template<typename Object>
+struct conversion_type
+{
+  using type = typename traits<Object>::type;
+};
+
+} // namespace generic
+
+}
+
+#endif // XPP_GENERIC_ITERATOR_TRAITS_HPP
diff --git a/lib/xpp/include/xpp/generic/reply_iterator.hpp b/lib/xpp/include/xpp/generic/reply_iterator.hpp
new file mode 100644 (file)
index 0000000..4758dd8
--- /dev/null
@@ -0,0 +1,376 @@
+#ifndef XPP_GENERIC_REPLY_ITERATOR_HPP
+#define XPP_GENERIC_REPLY_ITERATOR_HPP
+
+#include <cstdlib> // size_t
+#include <memory>
+#include <stack>
+#include <xcb/xcb.h> // xcb_str_*
+#include "factory.hpp"
+#include "signature.hpp"
+#include "iterator_traits.hpp"
+
+#define NEXT_TEMPLATE \
+  void (&Next)(XcbIterator *)
+
+#define NEXT_SIGNATURE \
+  xpp::generic::signature<void (XcbIterator *), Next>
+
+#define SIZEOF_TEMPLATE \
+  int (&SizeOf)(const void *)
+
+#define SIZEOF_SIGNATURE \
+  xpp::generic::signature<int (const void *), SizeOf>
+
+#define GETITERATOR_TEMPLATE \
+  XcbIterator (&GetIterator)(const Reply *)
+
+#define GETITERATOR_SIGNATURE \
+  xpp::generic::signature<XcbIterator (const Reply *), GetIterator>
+
+#define ACCESSOR_TEMPLATE \
+  Data * (&Accessor)(const Reply *)
+
+#define ACCESSOR_SIGNATURE \
+  xpp::generic::signature<Data * (const Reply *), Accessor>
+
+#define LENGTH_TEMPLATE \
+  int (&Length)(const Reply *)
+
+#define LENGTH_SIGNATURE \
+  xpp::generic::signature<int (const Reply *), Length>
+
+namespace xpp {
+
+namespace generic {
+
+template<typename Data>
+class get
+{
+  public:
+    Data
+    operator()(Data * const data)
+    {
+      return *data;
+    }
+};
+
+template<>
+class get<xcb_str_t>
+{
+  public:
+    std::string
+    operator()(xcb_str_t * const data)
+    {
+      return std::string(xcb_str_name(data),
+                         xcb_str_name_length(data));
+    }
+};
+
+namespace detail
+{
+
+template<typename F>
+struct function_traits;
+
+template<typename Signature, Signature& S>
+struct function_traits<signature<Signature, S>> : function_traits<Signature> {};
+
+template<typename R, typename... Args>
+struct function_traits<R(*)(Args...)> : function_traits<R(Args...)> {};
+
+template<typename R, typename... Args>
+struct function_traits<R(Args...)>
+{
+  using result_type = R;
+  const static std::size_t arity = sizeof...(Args);
+
+  template <std::size_t I>
+  struct argument
+  {
+    static_assert(I < arity, "invalid argument index");
+    using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
+  };
+};
+}
+
+// iterator for variable size data fields
+
+template<typename ... Types>
+class iterator;
+
+template<typename Connection,
+         typename Object,
+         typename NextTemplate,
+         NextTemplate& Next,
+         typename SizeOfTemplate,
+         SizeOfTemplate& SizeOf,
+         typename GetIteratorTemplate,
+         GetIteratorTemplate& GetIterator>
+class iterator<Connection,
+               Object,
+               xpp::generic::signature<NextTemplate, Next>,
+               xpp::generic::signature<SizeOfTemplate, SizeOf>,
+               xpp::generic::signature<GetIteratorTemplate, GetIterator>>
+  : public std::iterator<typename std::input_iterator_tag,
+                         Object,
+                         typename std::size_t,
+                         Object *,
+                         const Object &>
+{
+  protected:
+    using self = iterator<Connection,
+                          Object,
+                          xpp::generic::signature<NextTemplate, Next>,
+                          xpp::generic::signature<SizeOfTemplate, SizeOf>,
+                          xpp::generic::signature<GetIteratorTemplate, GetIterator>>;
+
+    using get_iterator_traits = detail::function_traits<GetIteratorTemplate>;
+    using const_reply_ptr = typename get_iterator_traits::template argument<0>::type;
+    using Reply = typename std::remove_pointer<typename std::remove_const<const_reply_ptr>::type>::type;
+    using XcbIterator = typename get_iterator_traits::result_type;
+
+    Connection m_c;
+    std::shared_ptr<Reply> m_reply;
+    std::stack<std::size_t> m_lengths;
+    XcbIterator m_iterator;
+
+  public:
+    iterator(void) {}
+
+    template<typename C>
+    iterator(C && c, const std::shared_ptr<Reply> & reply)
+      : m_c(std::forward<C>(c))
+      , m_reply(reply)
+      , m_iterator(GetIterator(reply.get()))
+    {}
+
+    bool
+    operator==(const iterator & other)
+    {
+      return m_iterator.rem == other.m_iterator.rem;
+    }
+
+    bool
+    operator!=(const iterator & other)
+    {
+      return ! (*this == other);
+    }
+
+    auto
+    operator*(void) -> decltype(get<Object>()(this->m_iterator.data))
+    {
+      return get<Object>()(m_iterator.data);
+    }
+
+    // prefix
+    self &
+    operator++(void)
+    {
+      m_lengths.push(SizeOf(m_iterator.data));
+      Next(&m_iterator);
+      return *this;
+    }
+
+    // postfix
+    self
+    operator++(int)
+    {
+      auto copy = *this;
+      ++(*this);
+      return copy;
+    }
+
+    // prefix
+    self &
+    operator--(void)
+    {
+      typedef typename std::remove_pointer<decltype(m_iterator.data)>::type data_t;
+      if (m_lengths.empty()) {
+        data_t * data = m_iterator.data;
+        data_t * prev = data - m_lengths.top();
+        m_lengths.pop();
+        m_iterator.index = (char *)m_iterator.data - (char *)prev;
+        m_iterator.data = prev;
+        ++m_iterator.rem;
+      }
+      return *this;
+    }
+
+    // postfix
+    self
+    operator--(int)
+    {
+      auto copy = *this;
+      --(*this);
+      return copy;
+    }
+
+    template<typename C>
+    static
+    self
+    begin(C && c, const std::shared_ptr<Reply> & reply)
+    {
+      return self { std::forward<C>(c), reply };
+    }
+
+    template<typename C>
+    static
+    self
+    end(C && c, const std::shared_ptr<Reply> & reply)
+    {
+      auto it = self { std::forward<C>(c), reply };
+      it.m_iterator.rem = 0;
+      return it;
+    }
+}; // class iterator
+
+// iterator for fixed size data fields
+
+template<typename Connection,
+         typename Object,
+         typename AccessorTemplate,
+         AccessorTemplate& Accessor,
+         typename LengthTemplate,
+         LengthTemplate& Length>
+class iterator<Connection,
+               Object,
+               signature<AccessorTemplate, Accessor>,
+               signature<LengthTemplate, Length>>
+  : public std::iterator<typename std::input_iterator_tag,
+                         Object,
+                         typename std::size_t,
+                         Object *,
+                         const Object &>
+{
+  protected:
+
+    using accessor_traits = detail::function_traits<AccessorTemplate>;
+    using Data = typename std::remove_pointer<typename accessor_traits::result_type>::type;
+    using const_reply_ptr = typename accessor_traits::template argument<0>::type;
+    using Reply = typename std::remove_pointer<typename std::remove_const<const_reply_ptr>::type>::type;
+
+    using data_t = typename std::conditional<std::is_void<Data>::value,
+      typename xpp::generic::conversion_type<Object>::type, Data>::type;
+    using make = xpp::generic::factory::make<Connection, data_t, Object>;
+
+    Connection m_c;
+    std::size_t m_index = 0;
+    std::shared_ptr<Reply> m_reply;
+
+  public:
+    typedef iterator<Connection,
+                     Object,
+                     signature<AccessorTemplate, Accessor>,
+                     signature<LengthTemplate, Length>>
+                       self;
+
+    iterator(void) {}
+
+    template<typename C>
+    iterator(C && c,
+             const std::shared_ptr<Reply> & reply,
+             std::size_t index)
+      : m_c(c)
+      , m_index(index)
+      , m_reply(reply)
+    {
+      if (std::is_void<Data>::value) {
+        m_index /= sizeof(data_t);
+      }
+    }
+
+    bool operator==(const iterator & other)
+    {
+      return m_index == other.m_index;
+    }
+
+    bool operator!=(const iterator & other)
+    {
+      return ! (*this == other);
+    }
+
+    Object operator*(void)
+    {
+      return make()(m_c, static_cast<data_t *>(Accessor(m_reply.get()))[m_index]);
+    }
+
+    // prefix
+    self & operator++(void)
+    {
+      ++m_index;
+      return *this;
+    }
+
+    // postfix
+    self operator++(int)
+    {
+      auto copy = *this;
+      ++(*this);
+      return copy;
+    }
+
+    // prefix
+    self & operator--(void)
+    {
+      --m_index;
+      return *this;
+    }
+
+    // postfix
+    self operator--(int)
+    {
+      auto copy = *this;
+      --(*this);
+      return copy;
+    }
+
+    template<typename C>
+    static
+    self
+    begin(C && c, const std::shared_ptr<Reply> & reply)
+    {
+      return self { std::forward<C>(c), reply, 0 };
+    }
+
+    template<typename C>
+    static
+    self
+    end(C && c, const std::shared_ptr<Reply> & reply)
+    {
+      return self { std::forward<C>(c),
+                    reply,
+                    static_cast<std::size_t>(Length(reply.get())) };
+    }
+}; // class iterator
+
+template<typename Connection, typename Reply, typename Iterator>
+class list {
+  private:
+    // before public part, to make decltype in begin() & end() work!
+    Connection m_c;
+    std::shared_ptr<Reply> m_reply;
+
+  public:
+    template<typename C>
+    list(C && c, const std::shared_ptr<Reply> & reply)
+      : m_c(std::forward<C>(c)), m_reply(reply)
+    {}
+
+    auto
+    begin(void) -> decltype(Iterator::begin(this->m_c, this->m_reply))
+    {
+      return Iterator::begin(m_c, m_reply);
+    }
+
+    auto
+    end(void) -> decltype(Iterator::end(this->m_c, this->m_reply))
+    {
+      return Iterator::end(m_c, m_reply);
+    }
+}; // class list
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_GENERIC_REPLY_ITERATOR_HPP
diff --git a/lib/xpp/include/xpp/generic/request.hpp b/lib/xpp/include/xpp/generic/request.hpp
new file mode 100644 (file)
index 0000000..cc93775
--- /dev/null
@@ -0,0 +1,129 @@
+#ifndef XPP_GENERIC_REQUEST_HPP
+#define XPP_GENERIC_REQUEST_HPP
+
+#include <array>
+#include <memory>
+#include <cstdlib>
+#include <xcb/xcb.h>
+#include "error.hpp"
+#include "signature.hpp"
+
+#define REPLY_TEMPLATE \
+  typename Reply, \
+  typename Cookie, \
+  Reply *(&ReplyFunction)(xcb_connection_t *, Cookie, xcb_generic_error_t **)
+
+#define REPLY_SIGNATURE \
+  xpp::generic::signature<Reply *(xcb_connection_t *, \
+                                  Cookie, \
+                                  xcb_generic_error_t **), \
+                          ReplyFunction>
+
+#define REPLY_COOKIE_TEMPLATE \
+  typename ... CookieParameter, \
+  Cookie(&CookieFunction)(CookieParameter ...)
+
+#define REPLY_COOKIE_SIGNATURE \
+  xpp::generic::signature<Cookie(CookieParameter ...), CookieFunction>
+
+namespace xpp { namespace generic {
+
+template<typename Connection, typename Dispatcher>
+void
+check(Connection && c, const xcb_void_cookie_t & cookie)
+{
+  xcb_generic_error_t * error =
+    xcb_request_check(std::forward<Connection>(c), cookie);
+  if (error) {
+    dispatch(std::forward<Connection>(c),
+             std::shared_ptr<xcb_generic_error_t>(error, std::free));
+  }
+}
+
+struct checked_tag {};
+struct unchecked_tag {};
+
+template<typename ... Types>
+class reply;
+
+template<typename Derived,
+         typename Connection,
+         typename Check,
+         REPLY_TEMPLATE,
+         REPLY_COOKIE_TEMPLATE>
+class reply<Derived,
+            Connection,
+            Check,
+            REPLY_SIGNATURE,
+            REPLY_COOKIE_SIGNATURE>
+{
+  public:
+    template<typename C, typename ... Parameter>
+    reply(C && c, Parameter && ... parameter)
+      : m_c(std::forward<C>(c))
+      , m_cookie(Derived::cookie(std::forward<C>(c),
+                                 std::forward<Parameter>(parameter) ...))
+    {}
+
+    operator bool(void)
+    {
+      return m_reply.operator bool();
+    }
+
+    const Reply &
+    operator*(void)
+    {
+      return *get();
+    }
+
+    Reply *
+    operator->(void)
+    {
+      return get().get();
+    }
+
+    const std::shared_ptr<Reply> &
+    get(void)
+    {
+      if (! m_reply) {
+        m_reply = get(Check());
+      }
+      return m_reply;
+    }
+
+    template<typename ... Parameter>
+    static
+    Cookie
+    cookie(Parameter && ... parameter)
+    {
+      return CookieFunction(std::forward<Parameter>(parameter) ...);
+    }
+
+  protected:
+    Connection m_c;
+    Cookie m_cookie;
+    std::shared_ptr<Reply> m_reply;
+
+    std::shared_ptr<Reply>
+    get(checked_tag)
+    {
+      xcb_generic_error_t * error = nullptr;
+      auto reply = std::shared_ptr<Reply>(ReplyFunction(m_c, m_cookie, &error),
+                                          std::free);
+      if (error) {
+        dispatch(m_c, std::shared_ptr<xcb_generic_error_t>(error, std::free));
+      }
+      return reply;
+    }
+
+    std::shared_ptr<Reply>
+    get(unchecked_tag)
+    {
+      return std::shared_ptr<Reply>(ReplyFunction(m_c, m_cookie, nullptr),
+                                    std::free);
+    }
+};
+
+} } // namespace xpp::generic
+
+#endif // XPP_GENERIC_REQUEST_HPP
diff --git a/lib/xpp/include/xpp/generic/resource.hpp b/lib/xpp/include/xpp/generic/resource.hpp
new file mode 100644 (file)
index 0000000..500113d
--- /dev/null
@@ -0,0 +1,142 @@
+#ifndef XPP_GENERIC_RESOURCE_HPP
+#define XPP_GENERIC_RESOURCE_HPP
+
+#include <iostream> // std::{hex,dec}
+#include <memory> // std::shared_ptr
+#include "iterator_traits.hpp"
+
+#include <xcb/xcb.h> // xcb_generate_id
+
+namespace xpp {
+
+namespace generic {
+
+namespace detail {
+
+template<typename Connection, typename Resource, typename ResourceId,
+         template<typename, typename> class ... Interfaces>
+class interfaces
+  : public Interfaces<interfaces<Connection, Resource, ResourceId, Interfaces ...>,
+                      Connection> ...
+{
+  public:
+    const ResourceId &
+    resource(void) const
+    {
+      return *static_cast<const Resource &>(*this);
+    }
+
+    Connection
+    connection(void) const
+    {
+      return static_cast<const Resource &>(*this).connection();
+    }
+}; // class interfaces
+
+}
+
+template<typename Connection, typename ResourceId,
+         template<typename, typename> class ... Interfaces>
+class resource
+  : public detail::interfaces<Connection,
+                              resource<Connection, ResourceId, Interfaces ...>,
+                              ResourceId, Interfaces ...>
+{
+  protected:
+    using self = resource<Connection, ResourceId, Interfaces ...>;
+
+    Connection m_c;
+    // reference counting for Resource object
+    std::shared_ptr<ResourceId> m_resource;
+
+    resource(Connection c)
+      : m_c(c)
+    {}
+
+    template<typename C, typename Create, typename Destroy>
+    static
+    self
+    make(C && c, Create create, Destroy destroy)
+    {
+      self resource(std::forward<C>(c));
+
+      auto xid = xcb_generate_id(std::forward<C>(c));
+
+      // class create before instatiating the shared_ptr
+      // create might fail and throw an error, hence shared_ptr would hold an
+      // invalid xid, causing possibly another exception in destroy()
+      // when create() throws, then the shared_ptr will not be created
+      create(std::forward<C>(c), xid);
+
+      resource.m_resource =
+        std::shared_ptr<ResourceId>(new ResourceId(xid),
+          [&](ResourceId * r)
+          {
+            destroy(resource.m_c, *r);
+            delete r;
+          });
+
+      return resource;
+    }
+
+  public:
+    template<typename C>
+    resource(C && c, const ResourceId & resource_id)
+      : m_c(std::forward<C>(c))
+      , m_resource(std::make_shared<ResourceId>(resource_id))
+    {}
+
+    resource(const resource<Connection, ResourceId, Interfaces ...> & other)
+      : m_c(other.m_c)
+      , m_resource(other.m_resource)
+    {}
+
+    virtual
+    void
+    operator=(const resource<Connection, ResourceId, Interfaces ...> & other)
+    {
+      m_c = other.m_c;
+      m_resource = other.m_resource;
+    }
+
+    virtual
+    void
+    operator=(const ResourceId & resource)
+    {
+      m_resource = std::make_shared<ResourceId>(resource);
+    }
+
+    virtual
+    const ResourceId &
+    operator*(void) const
+    {
+      return *m_resource;
+    }
+
+    virtual
+    operator const ResourceId &(void) const
+    {
+      return *m_resource;
+    }
+
+    Connection
+    connection(void) const
+    {
+      return m_c;
+    }
+}; // class resource
+
+template<typename Connection, typename ResourceId,
+         template<typename, typename> class ... Interfaces>
+std::ostream &
+operator<<(std::ostream & os,
+           const resource<Connection, ResourceId, Interfaces ...> & resource)
+{
+  return os << std::hex << "0x" << *resource << std::dec;
+}
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_GENERIC_RESOURCE_HPP
diff --git a/lib/xpp/include/xpp/generic/signature.hpp b/lib/xpp/include/xpp/generic/signature.hpp
new file mode 100644 (file)
index 0000000..4aa72d5
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef XPP_GENERIC_SIGNATURE_HPP
+#define XPP_GENERIC_SIGNATURE_HPP
+
+#define SIGNATURE(NAME) \
+  xpp::generic::signature<decltype(NAME), NAME>
+
+namespace xpp
+{
+  namespace generic
+  {
+    template<typename Signature, Signature & S>
+    class signature;
+  }
+}
+
+#endif // XPP_GENERIC_SIGNATURE_HPP
diff --git a/lib/xpp/include/xpp/pixmap.hpp b/lib/xpp/include/xpp/pixmap.hpp
new file mode 100644 (file)
index 0000000..4284a28
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef XPP_PIXMAP_HPP
+#define XPP_PIXMAP_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class pixmap
+  : public xpp::generic::resource<Connection, xcb_pixmap_t,
+                                  xpp::x::pixmap, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_pixmap_t,
+                                        xpp::x::pixmap, Interfaces ...>;
+
+    template<typename C, typename Create, typename Destroy>
+    pixmap(C && c, Create && create, Destroy && destroy)
+      : base(base::make(std::forward<C>(c),
+                        std::forward<Create>(create),
+                        std::forward<Destroy>(destroy)))
+    {}
+
+  public:
+    using base::base;
+    using base::operator=;
+
+    template<typename C>
+    static
+    pixmap<Connection, Interfaces ...>
+    create(C && c, uint8_t depth, xcb_drawable_t drawable,
+                   uint16_t width, uint16_t height)
+    {
+      return pixmap(
+        std::forward<C>(c),
+        [&](const Connection & c, const xcb_pixmap_t & pixmap)
+        {
+          xpp::x::create_pixmap(c, depth, pixmap, drawable, width, height);
+        },
+        [&](const Connection & c, const xcb_pixmap_t & pixmap)
+        {
+          xpp::x::free_pixmap(c, pixmap);
+        });
+    }
+
+    template<typename C>
+    static
+    pixmap<Connection, Interfaces ...>
+    create_checked(C && c, uint8_t depth, xcb_drawable_t drawable,
+                   uint16_t width, uint16_t height)
+    {
+      return pixmap(
+        std::forward<C>(c),
+        [&](const Connection & c, const xcb_pixmap_t & pixmap)
+        {
+          xpp::x::create_pixmap_checked(c, depth, pixmap, drawable, width, height);
+        },
+        [&](const Connection & c, const xcb_pixmap_t & pixmap)
+        {
+          xpp::x::free_pixmap_checked(c, pixmap);
+        });
+    }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::pixmap<Connection, Interfaces ...>>
+{
+  typedef xcb_pixmap_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_PIXMAP_HPP
diff --git a/lib/xpp/include/xpp/valueparam.hpp b/lib/xpp/include/xpp/valueparam.hpp
new file mode 100644 (file)
index 0000000..98e2625
--- /dev/null
@@ -0,0 +1,50 @@
+#ifndef X_VALUEPARAM_HPP
+#define X_VALUEPARAM_HPP
+
+#include <map>
+#include <vector>
+
+namespace xpp {
+
+class valueparam {
+  public:
+    valueparam &
+    set(const uint32_t & bit, const uint32_t & value)
+    {
+      m_has_changed = true;
+      m_values_map[bit] = value;
+      return *this;
+    }
+
+    uint32_t
+    mask(void)
+    {
+      return m_mask;
+    }
+
+    uint32_t * const
+    values(void)
+    {
+      if (m_has_changed) {
+        m_values.clear();
+      }
+
+      for (auto & item : m_values_map) {
+        m_values.push_back(item.second);
+      }
+
+      m_has_changed = false;
+
+      return m_values.data();
+    }
+
+  private:
+    bool m_has_changed = true;
+    uint32_t m_mask = 0;
+    std::vector<uint32_t> m_values;
+    std::map<uint32_t, uint32_t> m_values_map;
+};
+
+} // namespace xpp
+
+#endif // X_VALUEPARAM_HPP
diff --git a/lib/xpp/include/xpp/window.hpp b/lib/xpp/include/xpp/window.hpp
new file mode 100644 (file)
index 0000000..9a261c3
--- /dev/null
@@ -0,0 +1,91 @@
+#ifndef XPP_WINDOW_HPP
+#define XPP_WINDOW_HPP
+
+#include "xpp/proto/x.hpp"
+#include "generic/resource.hpp"
+
+namespace xpp {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+class window
+  : public xpp::generic::resource<Connection, xcb_window_t,
+                                  xpp::x::window, Interfaces ...>
+{
+  protected:
+    using base = xpp::generic::resource<Connection, xcb_window_t,
+                                        xpp::x::window, Interfaces ...>;
+
+    template<typename C, typename Create, typename Destroy>
+    window(C && c, Create && create, Destroy && destroy)
+      : base(base::make(std::forward<C>(c),
+                        std::forward<Create>(create),
+                        std::forward<Destroy>(destroy)))
+    {}
+
+  public:
+    window(const window&) = default;
+    using base::base;
+    using base::operator=;
+
+    template<typename C>
+    static
+    window<Connection, Interfaces ...>
+    create(C && c, uint8_t depth, xcb_window_t parent,
+                   int16_t x, int16_t y, uint16_t width, uint16_t height,
+                   uint16_t border_width,
+                   uint16_t _class, xcb_visualid_t visual,
+                   uint32_t value_mask, const uint32_t * value_list)
+    {
+      return window(
+        std::forward<C>(c),
+        [&](const Connection & c, const xcb_window_t & window)
+        {
+          xpp::x::create_window(c, depth, window, parent,
+                                        x, y, width, height, border_width,
+                                        _class, visual,
+                                        value_mask, value_list);
+        },
+        [&](const Connection & c, const xcb_window_t & window)
+        {
+          xpp::x::destroy_window(c, window);
+        });
+    }
+
+    template<typename C>
+    static
+    window<Connection, Interfaces ...>
+    create_checked(C && c, uint8_t depth, xcb_window_t parent,
+                   int16_t x, int16_t y, uint16_t width, uint16_t height,
+                   uint16_t border_width,
+                   uint16_t _class, xcb_visualid_t visual,
+                   uint32_t value_mask, const uint32_t * value_list)
+    {
+      return window(
+        std::forward<C>(c),
+        [&](const Connection & c, const xcb_window_t & window)
+        {
+          xpp::x::create_window_checked(c, depth, window, parent,
+                                        x, y, width, height, border_width,
+                                        _class, visual,
+                                        value_mask, value_list);
+        },
+        [&](const Connection & c, const xcb_window_t & window)
+        {
+          xpp::x::destroy_window_checked(c, window);
+        });
+    }
+};
+
+namespace generic {
+
+template<typename Connection, template<typename, typename> class ... Interfaces>
+struct traits<xpp::window<Connection, Interfaces ...>>
+{
+  typedef xcb_window_t type;
+};
+
+} // namespace generic
+
+} // namespace xpp
+
+#endif // XPP_WINDOW_HPP
diff --git a/lib/xpp/include/xpp/xpp.hpp b/lib/xpp/include/xpp/xpp.hpp
new file mode 100644 (file)
index 0000000..16597b9
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef XPP_HPP
+#define XPP_HPP
+
+#include "generic.hpp"
+
+#include "atom.hpp"
+#include "colormap.hpp"
+#include "cursor.hpp"
+#include "drawable.hpp"
+#include "font.hpp"
+#include "fontable.hpp"
+#include "gcontext.hpp"
+#include "pixmap.hpp"
+#include "window.hpp"
+
+#include "event.hpp"
+#include "connection.hpp"
+
+#endif // XPP_HPP
diff --git a/lib/xpp/src/examples/Makefile b/lib/xpp/src/examples/Makefile
new file mode 100644 (file)
index 0000000..9b01809
--- /dev/null
@@ -0,0 +1,14 @@
+include ../../include/flags.makefile
+
+CXXFLAGS+=-g
+CXXFLAGS+=-Wextra
+
+CPPSRCS=$(shell find . -name '*.cpp')
+EXAMPLES=${CPPSRCS:./%.cpp=%}
+
+all: ${EXAMPLES}
+
+clean:
+       rm -f ${EXAMPLES}
+
+.PHONY: clean
diff --git a/lib/xpp/src/examples/demo_01.cpp b/lib/xpp/src/examples/demo_01.cpp
new file mode 100644 (file)
index 0000000..6e589cb
--- /dev/null
@@ -0,0 +1,138 @@
+// This demo will allow the user to click on a window, grab the keyboard on it
+// and then print every key press and release event. Will exit when Escape is
+// pressed
+
+#include <iostream>
+
+#include <X11/Xlib.h> // XKeysymToString
+#include <X11/keysym.h> // XK_Escape
+#include <X11/cursorfont.h> // XC_cross
+
+#include "../xpp.hpp"
+
+// global variable to indicate whether the event loop should exit
+bool g_quit = false;
+
+// typedefs for convenience
+namespace x {
+  typedef xpp::connection<> connection;
+  typedef xpp::event::registry<connection &> registry;
+
+  typedef xpp::font<connection &> font;
+  typedef xpp::cursor<connection &> cursor;
+  typedef xpp::window<connection &> window;
+
+  typedef xpp::x::event::key_press<connection &> key_press;
+  typedef xpp::x::event::key_release<connection &> key_release;
+  typedef xpp::x::event::button_press<connection &> button_press;
+};
+
+// The event handler class
+// Implements the xpp::event::sink<..> interface with all events we are
+// interested in as template parameters
+template<typename Connection>
+class key_printer
+  : public xpp::event::sink<x::key_press, x::key_release, x::button_press>
+{
+  public:
+    template<typename C>
+    key_printer(C && c)
+      : m_c(std::forward<C>(c))
+    {}
+
+    // xpp::event::sink<x::key_press>::handle(...) interface
+    void handle(const x::key_press & e)
+    {
+      auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+      // take the first value from the kbd_mapping list
+      // This might throw, but for simplicity, no error handling here
+      auto keysym = *kbd_mapping.keysyms().begin();
+
+      if (keysym == XK_Escape) {
+        std::cerr << "quitting" << std::endl;
+        // parameter has a default value: XCB_TIME_CURRENT_TIME
+        m_c.ungrab_keyboard();
+        g_quit = true;
+      } else {
+        std::cerr << "key press: " << XKeysymToString(keysym) << std::endl;
+      }
+    }
+
+    // xpp::event::sink<x::key_release>::handle(...) interface
+    void handle(const x::key_release & e)
+    {
+      auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+      auto keysym = *kbd_mapping.keysyms().begin();
+      std::cerr << "key release: " << XKeysymToString(keysym) << std::endl;
+    }
+
+    // xpp::event::sink<x::button_press>::handle(...) interface
+    void handle(const x::button_press & e)
+    {
+      m_c.ungrab_pointer(XCB_TIME_CURRENT_TIME);
+
+      // event & reply accessors have a default template parameter, the c-type
+      // Usable with any type which is constructible from the c-type or
+      // connection + c-type
+      // xcb_window_t grab_window = e.event();
+      x::window grab_window = e.event<x::window>();
+
+      if (e->event == e->root) {
+        // xpp::window, etc. are assignable with the c-type
+        grab_window = e.child();
+        // xpp::window, etc. are implicitly convertible to c-type
+        auto translate = grab_window.translate_coordinates(grab_window, 1, 1);
+        grab_window = translate->child;
+      }
+
+      *m_c.grab_keyboard(true, grab_window,
+                         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+
+      std::cerr << "Grabbed " << grab_window
+                << ". Press Escape to quit." << std::endl;
+    }
+
+  private:
+    Connection m_c;
+};
+
+int main(int, char **)
+{
+  try {
+    // xpp::connection is implicitly convertible to xcb_connection_t *
+    // Hence, it can be used with all xcb_* c functions.
+    // However, this is not demonstrated here.
+    x::connection connection;
+    x::registry registry(connection);
+
+    key_printer<x::connection &> key_printer(connection);
+    registry.attach(0, &key_printer);
+
+    x::font font = x::font::open_checked(connection, "cursor");
+
+    // x::font, etc. is implicitly convertible to xcb_font_t
+    x::cursor cursor = x::cursor::create_glyph_checked(connection, font, font,
+        XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+
+    *connection.grab_pointer(false, connection.root(),
+                             XCB_EVENT_MASK_BUTTON_PRESS,
+                             XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+                             XCB_NONE, cursor);
+                             // default value for time = XCB_TIME_CURRENT_TIME);
+
+    std::cerr << "Please click on a window" << std::endl;
+
+    while (! g_quit) {
+      connection.flush();
+      registry.dispatch(connection.wait_for_event());
+    }
+
+  } catch (const std::exception & error) {
+    std::cerr << "Exception (std::exception) in "
+              << __FILE__ << " @ line " << __LINE__ << ", what(): "
+              << error.what() << std::endl;
+    std::exit(EXIT_FAILURE);
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/lib/xpp/src/examples/demo_02.cpp b/lib/xpp/src/examples/demo_02.cpp
new file mode 100644 (file)
index 0000000..0d0270c
--- /dev/null
@@ -0,0 +1,229 @@
+#include <iostream>
+
+#include "../xpp.hpp"
+#include "../proto/randr.hpp"
+#include "../proto/damage.hpp"
+#include "../proto/render.hpp"
+
+// typedefs for convenience
+namespace x {
+  typedef xpp::connection<xpp::randr::extension,
+                          xpp::damage::extension,
+                          xpp::render::extension>
+                            connection;
+
+  typedef xpp::event::registry<connection &,
+                               xpp::randr::extension,
+                               xpp::damage::extension,
+                               xpp::render::extension>
+                                 registry;
+
+  typedef xpp::font<connection &> font;
+  typedef xpp::cursor<connection &> cursor;
+  typedef xpp::window<connection &> window;
+  typedef xpp::window<xcb_connection_t *> xcb_window;
+};
+
+int main(int, char **)
+{
+  x::connection connection;
+  x::registry registry(connection);
+
+  // Print out all available font paths
+  auto && paths = connection.get_font_path().path();
+  std::cerr << "paths "
+            << "(length: " << std::distance(paths.begin(), paths.end()) << "):"
+            << std::endl;
+  for (auto && path : paths) {
+    std::cerr << "path [" << path.length() << "]: " << path << std::endl;
+  }
+  std::cerr << std::endl;
+
+  // Print out all available fonts
+  auto && fonts = connection.list_fonts(8, 1, "*").names();
+  std::cerr << "fonts "
+            << "(length: " << std::distance(fonts.begin(), fonts.end()) << "):"
+            << std::endl;
+  for (auto && name : fonts) {
+    std::cerr << "font [" << name.length() << "]: " << name << std::endl;
+  }
+  std::cerr << std::endl;
+
+  // Print all windows and their subwindows
+  auto tree = connection.root<x::window>().query_tree();
+  std::cerr << "children: " << std::endl;
+  for (auto && child : tree.children<x::xcb_window>()) {
+    std::cerr << child << " ";
+    auto siblings = child.query_tree().children();
+    auto siblings_length = std::distance(siblings.begin(), siblings.end());
+    if (siblings_length > 0) {
+      std::cerr << std::hex << "[" << siblings_length
+                << " sibling" << (siblings_length > 1 ? "s" : "") << ": ";
+      for (auto && sibling : siblings) {
+        std::cerr << "0x" << sibling << (--siblings_length > 0 ? ", " : "");
+      }
+      std::cerr << std::dec << "]" << std::endl;
+    } else {
+      std::cerr << "[no siblings]" << std::endl;
+    }
+  }
+  std::cerr << std::endl;
+
+  // Creates an atom called "XPP_STRING_PROPERTY_DEMO" with a string property
+  // "xpp is working" on the root window
+  // check with `xprop -root XPP_STRING_PROPERTY_DEMO`
+  try {
+    auto my_string_atom =
+      xpp::x::intern_atom(connection, false, "XPP_STRING_PROPERTY_DEMO");
+
+    std::string my_string("xpp is working!");
+
+    std::cerr << "atom for \"XPP_STRING_PROPERTY_DEMO\": "
+              << my_string_atom.atom() << std::endl;
+
+    auto atom_name = connection.get_atom_name(my_string_atom.atom());
+    std::cerr << "atom name: " << atom_name.name() << std::endl;;
+
+    connection.change_property_checked(
+        XCB_PROP_MODE_REPLACE, connection.root(),
+        my_string_atom.atom(), XCB_ATOM_STRING, 8,
+        // using Iterator begin + end here
+        my_string.begin(), my_string.end());
+
+    // this will deliberately fail because window = 0
+    // However, the previous call to change_property succeeded,
+    // so everything is just fine
+    connection.change_property_checked( XCB_PROP_MODE_REPLACE, 0,
+        my_string_atom.atom(), XCB_ATOM_STRING, 8,
+        // using length() & c_str()
+        my_string.length(), my_string.c_str());
+
+  } catch (const std::exception & e) {
+    std::cerr << "change property failed: " << e.what() << std::endl;
+  }
+  std::cerr << std::endl;
+
+  // Get the _NET_CLIENT_LIST_STACKING property
+  // If an error occurs, it will be thrown only when trying to access the reply
+  std::string _net_client_list_stacking = "_NET_CLIENT_LIST_STACKING";
+  auto net_client_list_stacking_atom =
+    connection.intern_atom(false, _net_client_list_stacking);
+  auto net_client_list_stacking = connection.get_property(
+      false, connection.root(), net_client_list_stacking_atom.atom(),
+      XCB_ATOM_WINDOW, 0, UINT32_MAX);
+
+  try {
+    std::cerr << _net_client_list_stacking << " (xcb_window_t):" << std::hex;
+    for (auto && w : net_client_list_stacking.value<xcb_window_t>()) {
+      std::cerr << " 0x" << w;
+    }
+    std::cerr << std::dec << std::endl;
+
+    std::cerr << _net_client_list_stacking << " (x::window):";
+    for (auto && w : net_client_list_stacking.value<x::window>()) {
+      std::cerr << " " << w;
+    }
+    std::cerr << std::endl;
+
+    std::cerr << _net_client_list_stacking << " (x::xcb_window):";
+    for (auto && w : net_client_list_stacking.value<x::xcb_window>()) {
+      std::cerr << " " << w;
+    }
+    std::cerr << std::endl;
+
+  } catch (const std::exception & e) {
+    std::cerr << "Could not get " << _net_client_list_stacking << " property: "
+              << e.what() << std::endl;
+  }
+  std::cerr << std::endl;
+
+  // Randr needs query_version to work properly in subsequent calls
+  // If methods are ambiguous (like query_version, then the extension interface
+  // can be accessed through "extension_name()" (e.g. randr() or damage())
+  connection.randr().query_version(XCB_RANDR_MAJOR_VERSION,
+                                   XCB_RANDR_MINOR_VERSION);
+
+  connection.select_input_checked(connection.root(), XCB_RANDR_NOTIFY);
+
+  const auto & randr_ext = connection.extension<xpp::randr::extension>();
+
+  std::cerr << "RandR Extension" << std::endl;
+  std::cerr << "\tfirst_event: " << (int)randr_ext->first_event << std::endl;
+  std::cerr << "\tfirst_error: " << (int)randr_ext->first_error << std::endl;
+
+  const auto & damage_ext = connection.extension<xpp::damage::extension>();
+
+  std::cerr << "Damage Extension" << std::endl;
+  std::cerr << "\tfirst_event: " << (int)damage_ext->first_event << std::endl;
+  std::cerr << "\tfirst_error: " << (int)damage_ext->first_error << std::endl;
+
+  std::cerr << std::endl;
+
+  try {
+    // Produces XCB_RANDR_BAD_OUTPUT error
+    auto output_info = connection.get_output_info(-1);
+    output_info.get();
+  } catch (const std::exception & e) {
+    std::cerr << "get_output_info error: " << e.what() << std::endl;
+  }
+
+  try {
+    // Produces XCB_RANDR_BAD_CRTC error
+    auto crtc_info = connection.get_crtc_info(-1);
+    crtc_info.get();
+  } catch (const std::exception & e) {
+    std::cerr << "get_crtc_info error: " << e.what() << std::endl;
+  }
+
+  // Produces XCB_RANDR_BAD_OUTPUT error in event queue
+  auto output_info = connection.get_output_info_unchecked(-1);
+  output_info.get();
+
+  // Produces XCB_RANDR_BAD_CRTC error in event queue
+  auto crtc_info = connection.get_crtc_info_unchecked(-1);
+  crtc_info.get();
+
+  try {
+    // XCB_VALUE error
+    connection.change_output_property_checked(-1, -1, -1, 0, 0, 0, nullptr);
+  } catch (const std::exception & e) {
+    std::cerr << "change_output_property error: " << e.what() << std::endl;
+  }
+
+  // XCB_VALUE error in event queue
+  connection.change_output_property(-1, -1, -1, 0, 0, 0, nullptr);
+
+  try {
+    // XCB_RENDER_PICT_FORMAT error
+    auto pict_index_values = connection.query_pict_index_values(-1);
+    pict_index_values.get();
+  } catch (const std::exception & e) {
+    std::cerr << "query_pict_index_values error: " << e.what() << std::endl;
+  }
+
+  try {
+    // XCB_RENDER_PICTURE error
+    connection.change_picture_checked(-1, 0, nullptr);
+  } catch (const std::exception & e) {
+    std::cerr << "change_picture error: " << e.what() << std::endl;
+  }
+
+  // XCB_RENDER_PICT_FORMAT error in event queue
+  auto pict_index_values = connection.query_pict_index_values_unchecked(-1);
+  pict_index_values.get();
+
+  // XCB_RENDER_PICTURE error in event queue
+  connection.change_picture(-1, 0, nullptr);
+
+  // Poll the event queue a couple of times to get the errors
+  for (int i = 0; i < 5; ++i) {
+    connection.flush();
+    try {
+      registry.dispatch(connection.wait_for_event());
+    } catch (const std::exception & e) {
+      std::cerr << "std::exception in event queue: " << e.what() << std::endl;
+    }
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/lib/xpp/src/tests/.gitignore b/lib/xpp/src/tests/.gitignore
new file mode 100644 (file)
index 0000000..41aa905
--- /dev/null
@@ -0,0 +1,2 @@
+event
+iterator
diff --git a/lib/xpp/src/tests/Makefile b/lib/xpp/src/tests/Makefile
new file mode 100644 (file)
index 0000000..1393fa1
--- /dev/null
@@ -0,0 +1,25 @@
+include ../../include/flags.makefile
+
+# CXX=clang
+CXXFLAGS+=-g
+# CXXFLAGS+=-Wextra
+# CXXFLAGS+=-ftime-report
+
+CPPSRCS=event.cpp \
+        requests.cpp \
+        iterator.cpp
+
+all: ${CPPSRCS}
+
+${CPPSRCS}:
+       ${CXX} ${LDFLAGS} ${CXXFLAGS} -o $(@:%.cpp=%) $@
+
+xlib-test: xlib-test.cpp
+       ${CXX} $(shell pkg-config --libs --cflags x11 xrandr) -o $@ $<
+
+version:
+
+clean:
+       rm -f ${CPPSRCS:%.cpp=%}
+
+.PHONY: ${CPPSRCS} clean
diff --git a/lib/xpp/src/tests/README.md b/lib/xpp/src/tests/README.md
new file mode 100644 (file)
index 0000000..2467113
--- /dev/null
@@ -0,0 +1,2 @@
+Just a couple of experiments to try out concepts, language features, ideas.
+Might or might not compile, run or crash, etc.
diff --git a/lib/xpp/src/tests/callable.cpp b/lib/xpp/src/tests/callable.cpp
new file mode 100644 (file)
index 0000000..f67be11
--- /dev/null
@@ -0,0 +1,83 @@
+// compile with `g++ -std=c++11 test.cpp`
+#include <iostream>
+
+#define CALLABLE(FUNCTION) callable<decltype(FUNCTION), FUNCTION>
+
+template<typename Signature, Signature & S>
+struct callable;
+
+template<typename Return,
+         typename ... Args, Return (&Function)(Args ...)>
+struct callable<Return(Args ...), Function> {
+  Return operator()(Args ... args)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    return Function(args ...);
+  }
+};
+
+template<typename ... Arguments>
+class one_size_fits_them_all;
+
+// A generic template
+template<typename T, typename U, typename V,
+         typename F1, typename F2, typename F3>
+class one_size_fits_them_all<T, U, V, F1, F2, F3>
+{
+  public:
+    one_size_fits_them_all(void)
+    {
+      std::cerr << "generic one_size_fits_them_all" << std::endl
+                << __PRETTY_FUNCTION__ << std::endl << std::endl;
+      F1()();
+      F2()();
+      F3()();
+      std::cerr << std::endl;
+    }
+};
+
+// A specialized template
+template<typename T, typename Callable>
+class one_size_fits_them_all<T, int, int, void, void, Callable>
+{
+  public:
+    one_size_fits_them_all(void)
+    {
+      std::cerr << "specialized one_size_fits_them_all" << std::endl
+                << __PRETTY_FUNCTION__ << std::endl << std::endl;
+      Callable()();
+      std::cerr << std::endl;
+    }
+};
+
+void f1(void)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f2(void)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f3(void)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+int main(int argc, char ** argv)
+{
+  // generic template
+  auto generic = one_size_fits_them_all<
+    int, int, int, CALLABLE(f1), CALLABLE(f2), CALLABLE(f3)>();
+
+  // specialized template
+  auto specialized_int = one_size_fits_them_all<
+    int, int, int, void, void, CALLABLE(f1)>();
+
+  // specialized template
+  auto specialized_double = one_size_fits_them_all<
+    double, int, int, void, void, CALLABLE(f3)>();
+
+  return 0;
+}
diff --git a/lib/xpp/src/tests/error_test.cpp b/lib/xpp/src/tests/error_test.cpp
new file mode 100644 (file)
index 0000000..599a32f
--- /dev/null
@@ -0,0 +1,497 @@
+#include <iostream>
+#include <string>
+
+#include <xcb/xcb.h>
+
+#include "../event.hpp"
+#include "../core/value_iterator.hpp"
+#include "../core/connection.hpp"
+#include "../core/extension.hpp"
+#include "../core/error.hpp"
+
+#include "../request.hpp"
+#include "../request_ng.hpp"
+
+#define CHECKED_REQUEST(NAME) \
+  decltype(NAME ## _reply), NAME ## _reply, \
+  decltype(NAME), NAME
+
+#define UNCHECKED_REQUEST(NAME) \
+  decltype(NAME ## _reply), NAME ## _reply, \
+  decltype(NAME ## _unchecked), NAME ## _unchecked
+
+namespace xpp { namespace generic {
+
+// class get_screen_info_checked
+//   : public checked_reply_request<xpp::connection<xpp::extension::randr>,
+//                                  xpp::extension::randr,
+//                                  CHECKED_REQUEST(xcb_randr_get_screen_info)>
+// {
+//   public:
+//     typedef checked_reply_request<xpp::connection<xpp::extension::randr>,
+//                                   xpp::extension::randr,
+//                                   CHECKED_REQUEST(xcb_randr_get_screen_info)>
+//                                     base;
+//     using base::base;
+// };
+
+// class get_screen_info_unchecked
+//   : public unchecked_reply_request<UNCHECKED_REQUEST(xcb_randr_get_screen_info)>
+// {
+//   public:
+//     typedef unchecked_reply_request<UNCHECKED_REQUEST(xcb_randr_get_screen_info)>
+//                                     base;
+//     using base::base;
+// };
+
+// template<typename NoThrow = void>
+// class get_screen_info
+//   : public std::conditional<std::is_same<NoThrow, std::nothrow_t>::value,
+//                             get_screen_info_unchecked,
+//                             get_screen_info_checked>::type
+// {
+//   public:
+//     typedef typename std::conditional<
+//       std::is_same<NoThrow, std::nothrow_t>::value,
+//       get_screen_info_unchecked,
+//       get_screen_info_checked>::type
+//         base;
+//     using base::base;
+// };
+
+/*
+class query_tree_checked
+  : public checked_reply_request<xpp::connection<>, void,
+                                 CHECKED_REQUEST(xcb_query_tree)>
+{
+  public:
+    typedef checked_reply_request<xpp::connection<>, void,
+                                  CHECKED_REQUEST(xcb_query_tree)>
+                                    base;
+    using base::base;
+};
+
+class query_tree_unchecked
+  : public unchecked_reply_request<UNCHECKED_REQUEST(xcb_query_tree)>
+{
+  public:
+    typedef unchecked_reply_request<UNCHECKED_REQUEST(xcb_query_tree)>
+                                    base;
+    using base::base;
+};
+
+template<typename NoThrow = void>
+class query_tree
+  : public std::conditional<std::is_same<NoThrow, std::nothrow_t>::value,
+                            query_tree_unchecked,
+                            query_tree_checked
+    >::type
+{
+  public:
+    typedef typename std::conditional<
+      std::is_same<NoThrow, std::nothrow_t>::value,
+      query_tree_unchecked,
+      query_tree_checked
+        >::type
+        base;
+    using base::base;
+};
+*/
+
+// class query_tree_checked
+//   : public xpp::generic::checked::request<xpp::extension::x,
+//                                              CHECKED_REQUEST(xcb_query_tree)>
+// {
+//   typedef xpp::generic::checked::request<xpp::extension::x,
+//                                             CHECKED_REQUEST(xcb_query_tree)>
+//                                               base;
+//   using base::base;
+// };
+
+// class map_window_checked
+//   : public ::test::generic::checked::request<xpp::extension::x,
+//                                              CHECKED_REQUEST(xcb_map_window)>
+// {
+//   typedef ::test::generic::checked::request<xpp::extension::x,
+//                                             CHECKED_REQUEST(xcb_map_window)>
+//                                               base;
+//   using base::base;
+// };
+
+// class get_screen_info
+//   // : public wrapper<xpp::connection<xpp::extension::randr>, xpp::extension::randr,
+//   : public wrapper<xpp::connection<xpp::extension::randr>,
+//                    xpp::extension::randr, xpp::extension::x,
+//                    decltype(xcb_randr_get_screen_info_reply), xcb_randr_get_screen_info_reply,
+//                    decltype(xcb_randr_get_screen_info), xcb_randr_get_screen_info>
+// {
+//   public:
+//     // typedef wrapper<xpp::extension::randr,
+//     typedef wrapper<xpp::connection<xpp::extension::randr>,
+//                     xpp::extension::randr, xpp::extension::x,
+//                     decltype(xcb_randr_get_screen_info_reply), xcb_randr_get_screen_info_reply,
+//                     decltype(xcb_randr_get_screen_info), xcb_randr_get_screen_info>
+//                       base;
+//     using base::base;
+// };
+
+// class query_tree
+//   // : public wrapper<xpp::extension::x,
+//   : public wrapper<xpp::connection<>,
+//                    xpp::extension::x, xpp::extension::x,
+//                    decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+//                    decltype(xcb_query_tree), xcb_query_tree>
+// {
+//   public:
+//     typedef wrapper<xpp::connection<>,
+//                     xpp::extension::x, xpp::extension::x,
+//                     decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+//                     decltype(xcb_query_tree), xcb_query_tree>
+//                       base;
+//     using base::base;
+// };
+
+// class dpms_capable
+//   // : public wrapper<// xpp::connection<xpp::extension::dpms>, xpp::extension::dpms,
+//   : public wrapper<xpp::extension::dpms,
+//                    decltype(xcb_dpms_capable_reply), xcb_dpms_capable_reply,
+//                    decltype(xcb_dpms_capable), xcb_dpms_capable>
+// {
+//   public:
+//     // typedef wrapper<// xpp::connection<xpp::extension::dpms>, xpp::extension::dpms,
+//     typedef wrapper<xpp::extension::dpms,
+//                     decltype(xcb_dpms_capable_reply), xcb_dpms_capable_reply,
+//                     decltype(xcb_dpms_capable), xcb_dpms_capable>
+//                       base;
+//     using base::base;
+// };
+
+// class render_query_pict_index_values
+//   // : public wrapper<// xpp::connection<xpp::extension::render>, xpp::extension::render,
+//   : public wrapper<xpp::extension::render,
+//                    decltype(xcb_render_query_pict_index_values_reply), xcb_render_query_pict_index_values_reply,
+//                    decltype(xcb_render_query_pict_index_values), xcb_render_query_pict_index_values>
+// {
+//   public:
+//     // typedef wrapper<// xpp::connection<xpp::extension::render>, xpp::extension::render,
+//     typedef wrapper<xpp::extension::render,
+//                     decltype(xcb_render_query_pict_index_values_reply), xcb_render_query_pict_index_values_reply,
+//                     decltype(xcb_render_query_pict_index_values), xcb_render_query_pict_index_values>
+//                       base;
+//     using base::base;
+// };
+
+// class render_query_filters
+//   // : public wrapper<xpp::extension::render,
+//   : public wrapper<xpp::connection<xpp::extension::render>,
+//                    xpp::extension::render, xpp::extension::x,
+//                    decltype(xcb_render_query_filters_reply), xcb_render_query_filters_reply,
+//                    decltype(xcb_render_query_filters), xcb_render_query_filters>
+// {
+//   public:
+//     // typedef wrapper<xpp::extension::render,
+//     typedef wrapper<xpp::connection<xpp::extension::render>,
+//                     xpp::extension::render, xpp::extension::x,
+//                     decltype(xcb_render_query_filters_reply), xcb_render_query_filters_reply,
+//                     decltype(xcb_render_query_filters), xcb_render_query_filters>
+//                       base;
+//     using base::base;
+// };
+
+// template<>
+// class query_tree<xcb_connection_t>
+//   : public wrapper<xcb_connection_t, void,
+//                    decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+//                    decltype(xcb_query_tree), xcb_query_tree>
+// {
+//   public:
+//     typedef wrapper<xcb_connection_t, void,
+//                     decltype(xcb_query_tree_reply), xcb_query_tree_reply,
+//                     decltype(xcb_query_tree), xcb_query_tree>
+//                       base;
+//     using base::base;
+// };
+
+}; }; // namespace xpp::generic
+
+void error_dispatcher(xcb_generic_error_t * error)
+{
+  switch (error->error_code) {
+    case XCB_WINDOW:
+      xcb_window_error_t * e = (xcb_window_error_t *)error;
+      std::cerr << "XCB_WINDOW with bad_value: " << e->bad_value << std::endl;
+      // throw xpp::error::generic<XCB_WINDOW, xcb_window_error_t>(error);
+      throw xpp::generic::error<XCB_WINDOW, xcb_window_error_t>(error);
+      // throw xpp::x::window_error(error);
+      break;
+  }
+}
+
+void check_error_direct(xcb_generic_error_t * error)
+{
+  if (error) {
+    std::cerr << "error code: " << (int)error->error_code << std::endl;
+    try {
+    error_dispatcher(error);
+    } catch (const std::exception & exception) {
+      std::cerr << "caught exception: " << exception.what() << std::endl;
+    }
+  } else {
+    std::cerr << "no error occurred" << std::endl;
+  }
+}
+
+void check_error_event(xcb_connection_t * c)
+{
+  xcb_generic_event_t * event = xcb_poll_for_event(c);
+  xcb_flush(c);
+  if (event) {
+    uint8_t response = event->response_type & ~0x80;
+    std::cerr << "event response: " << (int)response << std::endl;
+    check_error_direct((xcb_generic_error_t *)event);
+  } else {
+    std::cerr << "no event available" << std::endl;
+  }
+}
+
+void
+map_window(xcb_connection_t * c, xcb_window_t window)
+{
+  xcb_map_window(c, window);
+}
+
+template<typename Extension>
+void
+map_window(const Extension & e, xcb_connection_t * c, xcb_window_t window)
+{
+  typedef typename Extension::error_dispatcher dispatcher;
+  dispatcher(/* e->first_error */)(
+      xcb_request_check(c, xcb_map_window_checked(c, window)));
+}
+
+template<typename ... Parameters>
+void
+map_window(const xpp::connection<Parameters ...> & c, xcb_window_t window)
+{
+  map_window(static_cast<const xpp::extension::x &>(c), c, window);
+}
+
+int main(int argc, char ** argv)
+{
+  xpp::connection<xpp::extension::randr,
+                  xpp::extension::render,
+                  xpp::extension::dpms> c("");
+  // xpp::connection<> c("");
+
+  // auto tree_1 = xpp::generic::query_tree<decltype(c), xpp::extension::x>(c, 0);
+  // auto tree_1 = xpp::generic::query_tree<decltype(c)>(c, 0);
+  // auto tree_1 = xpp::generic::query_tree<>(static_cast<xcb_connection_t *>(c), 0);
+
+  auto & randr  = static_cast<xpp::randr::protocol &>(c);
+  auto & render = static_cast<xpp::render::protocol &>(c);
+  auto & dpms   = static_cast<xpp::dpms::protocol &>(c);
+
+  auto & randr_ext  = static_cast<xpp::extension::randr &>(c);
+  auto & render_ext = static_cast<xpp::extension::render &>(c);
+  auto & dpms_ext   = static_cast<xpp::extension::dpms &>(c);
+
+  *randr.query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+  *render.query_version(XCB_RENDER_MAJOR_VERSION, XCB_RENDER_MINOR_VERSION);
+  *dpms.get_version(XCB_DPMS_MAJOR_VERSION, XCB_DPMS_MINOR_VERSION);
+
+  std::cerr << "randr first_error: " << (int)randr_ext->first_error << std::endl;
+  std::cerr << "render first_error: " << (int)render_ext->first_error << std::endl;
+  std::cerr << "dpms first_error: " << (int)dpms_ext->first_error << std::endl;
+
+  // auto tree = xpp::generic::query_tree(c, 0);
+  // std::cerr << "tree go" << std::endl;
+  // try {
+  //   tree.get();
+  //   auto error = tree.error();
+  //   std::cerr << error.get() << std::endl;
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+  // std::cerr << "tree fin" << std::endl << std::endl;
+
+  // auto screen_info = xpp::generic::get_screen_info(c, 0);
+  // std::cerr << "screen_info go" << std::endl;
+  // try {
+  //   screen_info.get();
+  //   auto error = screen_info.error();
+  //   std::cerr << error.get() << std::endl;
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+  // std::cerr << "screen_info fin" << std::endl << std::endl;
+
+  // auto dpms_capable = xpp::generic::dpms_capable(c);
+  // std::cerr << "dpms_capable go" << std::endl;
+  // try {
+  //   dpms_capable.get();
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+  // std::cerr << "dpms_capable fin" << std::endl;
+
+  // auto pict_index_values = xpp::generic::render_query_pict_index_values(c, -1);
+  // std::cerr << "pict_index_values go" << std::endl;
+  // try {
+  //   pict_index_values.get();
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+  // std::cerr << "pict_index_values fin" << std::endl;
+
+  // auto filters = xpp::generic::render_query_filters(c, 0);
+  // std::cerr << "filters go" << std::endl;
+  // try {
+  //   filters.get();
+  //   auto error = filters.error();
+  //   std::cerr << error.get() << std::endl;
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+  // std::cerr << "filters fin" << std::endl << std::endl;
+
+
+
+  // // auto tree_2 = xpp::generic::query_tree<xcb_connection_t>(c, 0);
+  // auto tree_2 = xpp::generic::query_tree<>(c, 0);
+  // std::cerr << "tree_2 go" << std::endl;
+  // try {
+  //   tree_2.get();
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+  // std::cerr << "tree_2 fin" << std::endl;
+
+  // auto reply = xpp::request::x::query_tree(c, 0);
+  // reply.get();
+  // reply.check((xpp::extension::x &)c);
+
+  // auto reply = xpp::request::x::query_tree(c, 0);
+  // reply.get();
+  // try {
+  //   reply.check((xpp::extension::x &)c);
+  // } catch (const std::exception & exception) {
+  //   std::cerr << "Exception: " << exception.what() << std::endl;
+  // }
+
+
+  // return 0;
+
+  /*
+  map_window(*c, 0);
+
+  try {
+    map_window(c, 0);
+  } catch (...) {}
+
+  try {
+    map_window((xpp::extension::x &)c, c, 0);
+    map_window((xpp::extension::x &)c, c, 0);
+    // map_window((xpp::extension::randr &)c, c, 0);
+  } catch (...) {}
+  */
+
+  // xcb_generic_error_t * generic_error = NULL;
+  // xcb_query_tree_reply_t * query_tree_reply = NULL;
+
+  // checked request with reply, error directly available
+  {
+  std::cerr << "checked request with reply, error directly available" << std::endl;
+  // xcb_query_tree_cookie_t query_tree_cookie = xcb_query_tree(c, 5);
+  // query_tree_reply = xcb_query_tree_reply(c, query_tree_cookie, &generic_error);
+  try {
+    auto screen_info = xpp::request::randr::get_screen_info(c, 0);
+    // auto tree = xpp::generic::query_tree<>(c, 5);
+    screen_info.get();
+  } catch (const xpp::x::error::window & e) {
+    std::cerr << "xpp::x::error::window: " << e.what() << std::endl;
+  } catch (const std::exception & e) {
+    std::cerr << "std::exception: " << e.what() << std::endl;
+  }
+  // auto reply = tree.get();
+  c.flush();
+  // std::cerr << "query_tree_reply: " << reply.get() << std::endl;
+  // check_error_direct(tree.error());
+  // std::cerr << "query_tree_reply: " << query_tree_reply << std::endl;
+  // query_tree_reply = NULL;
+  // check_error_direct(generic_error);
+  // check_error_direct(generic_error);
+  // generic_error = NULL;
+  }
+  std::cerr << std::endl;
+
+  // checked request with reply, error directly available
+  {
+  std::cerr << "checked request with reply, error directly available" << std::endl;
+  // xcb_query_tree_cookie_t query_tree_cookie = xcb_query_tree(c, 5);
+  // query_tree_reply = xcb_query_tree_reply(c, query_tree_cookie, &generic_error);
+  try {
+    auto tree = xpp::request::x::query_tree(c, 5);
+    // auto tree = xpp::generic::query_tree<>(c, 5);
+    tree.get();
+  } catch (const xpp::x::error::window & e) {
+    std::cerr << "xpp::x::error::window: " << e.what() << std::endl;
+  } catch (const std::exception & e) {
+    std::cerr << "std::exception: " << e.what() << std::endl;
+  }
+  // auto reply = tree.get();
+  c.flush();
+  // std::cerr << "query_tree_reply: " << reply.get() << std::endl;
+  // check_error_direct(tree.error());
+  // std::cerr << "query_tree_reply: " << query_tree_reply << std::endl;
+  // query_tree_reply = NULL;
+  // check_error_direct(generic_error);
+  // check_error_direct(generic_error);
+  // generic_error = NULL;
+  }
+  std::cerr << std::endl;
+
+  // // unchecked request with reply, error in event queue
+  // {
+  // std::cerr << "unchecked request with reply, error in event queue" << std::endl;
+  // // xcb_query_tree_cookie_t query_tree_cookie = xcb_query_tree_unchecked(c, 17);
+  // // query_tree_reply = xcb_query_tree_reply(c, query_tree_cookie, &generic_error);
+  // auto tree = xpp::generic::query_tree<std::nothrow_t>(c, 5);
+  // auto reply = tree.get();
+  // c.flush();
+  // std::cerr << "query_tree_reply: " << reply.get() << std::endl;
+  // // std::cerr << "query_tree_reply: " << query_tree_reply << std::endl;
+  // // std::cerr << "query_tree_reply error: " << generic_error << std::endl;
+  // // query_tree_reply = NULL;
+  // check_error_event(c);
+  // }
+  // std::cerr << std::endl;
+
+  // // checked request without reply, error directly available
+  // {
+  // std::cerr << "checked request without reply, error directly available" << std::endl;
+  // // xcb_void_cookie_t void_cookie = xcb_map_window_checked(c, 42);
+  // try {
+  //   ::test::map_window(c, 0);
+  //   // xpp::request::x::map_window(c, 0);
+  // } catch (const xpp::x::error::window & e) {
+  //   std::cerr << "xpp::x::error::window: " << e.what() << std::endl;
+  // } catch (const std::exception & e) {
+  //   std::cerr << "std::exception: " << e.what() << std::endl;
+  // }
+  // c.flush();
+  // // generic_error = xcb_request_check(c, void_cookie);
+  // // check_error_direct(generic_error);
+  // // generic_error = NULL;
+  // }
+  // std::cerr << std::endl;
+
+  // unchecked request without reply, error in event queue
+  {
+  std::cerr << "unchecked request without reply, error in event queue" << std::endl;
+  xcb_map_window(c, 0);
+  c.flush();
+  check_error_event(c);
+  }
+
+  return 0;
+}
diff --git a/lib/xpp/src/tests/event.cpp b/lib/xpp/src/tests/event.cpp
new file mode 100644 (file)
index 0000000..6ee81a2
--- /dev/null
@@ -0,0 +1,125 @@
+#include <iostream>
+
+#include "../event.hpp"
+#include "../connection.hpp"
+
+using namespace xpp;
+using namespace event;
+
+xcb_window_t
+get_window(xcb_button_press_event_t * const e)
+{
+  return e->event;
+}
+
+xcb_window_t
+get_window(xcb_motion_notify_event_t * const e)
+{
+  return e->event;
+}
+
+namespace test {
+
+class handler : public dispatcher
+              , public sink<button::press>
+              , public sink<button::release>
+              , public sink<motion::notify>
+{
+  public:
+    void handle(const button::press &)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    }
+
+    void handle(const button::release & e)
+    {
+      if (XCB_BUTTON_PRESS == (e->response_type & ~0x80)) {
+        std::cerr << __PRETTY_FUNCTION__ << " XCB_BUTTON_PRESS" << std::endl;
+      } else {
+        std::cerr << __PRETTY_FUNCTION__ << " XCB_BUTTON_RELEASE" << std::endl;
+      }
+    }
+
+    void handle(const motion::notify &)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    }
+};
+
+class container : public direct::container {
+  public:
+    dispatcher * const
+      at(const unsigned int & window) const
+    {
+      return m_dispatcher.at(window);
+    }
+
+    std::unordered_map<unsigned int, dispatcher *> m_dispatcher;
+};
+
+struct foo {
+  void bar(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+class foo_container : public any::container<foo> {
+  public:
+    foo * const at(const unsigned int & window)
+    {
+      return &m_foos.at(window);
+    }
+
+    std::unordered_map<unsigned int, foo> m_foos;
+};
+
+class foo_handler : public any::adapter<foo, button::press, 0, get_window> {
+  public:
+    using adapter::adapter;
+
+    void handle(foo * const f, const button::press & e)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << " response_type: " << (int)(e->response_type & ~0x80) << std::endl;
+      f->bar();
+    }
+};
+
+}; // namespace test
+
+int main(int argc, char ** argv)
+{
+  connection c("");
+  source source(c);
+
+  auto tree = c.query_tree(c.root());
+
+  test::handler handler;
+  test::container container;
+
+  test::foo_container foo_container;
+  test::foo_handler foo_handler(source, foo_container);
+
+  for (auto & window : tree.children()) {
+    *(c.grab_pointer(false, window,
+                     XCB_EVENT_MASK_BUTTON_PRESS
+                     | XCB_EVENT_MASK_BUTTON_RELEASE
+                     | XCB_EVENT_MASK_POINTER_MOTION,
+                     XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+                     XCB_NONE, XCB_NONE, XCB_TIME_CURRENT_TIME));
+
+    container.m_dispatcher[window] = &handler;
+    foo_container.m_foos[window] = test::foo();
+  }
+
+  dispatcher * dispatcher[] =
+    { new direct::adapter<button::press, 0, get_window>(source, container)
+    , new direct::adapter<button::release, 0, get_window>(source, container)
+    , new direct::adapter<motion::notify, 0, get_window>(source, container)
+    };
+
+  source.run();
+
+  for (auto * d : dispatcher) {
+    delete d;
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/lib/xpp/src/tests/iterator.cpp b/lib/xpp/src/tests/iterator.cpp
new file mode 100644 (file)
index 0000000..4732653
--- /dev/null
@@ -0,0 +1,155 @@
+#include <iostream>
+
+#include "../connection.hpp"
+
+class test {
+  public:
+
+    template<typename Data>
+    class iterator {
+      public:
+        iterator(Data * const data, std::size_t index)
+          : m_data(data), m_index(index)
+        {}
+
+        bool operator==(const iterator & other)
+        {
+          return m_index == other.m_index;
+        }
+
+        bool operator!=(const iterator & other)
+        {
+          return ! (*this == other);
+        }
+
+        const Data & operator*(void)
+        {
+          return m_data[m_index];
+        }
+
+        // prefix
+        iterator & operator++(void)
+        {
+          ++m_index;
+          return *this;
+        }
+
+        // postfix
+        iterator operator++(int)
+        {
+          auto copy = *this;
+          ++(*this);
+          return copy;
+        }
+
+        // prefix
+        iterator & operator--(void)
+        {
+          --m_index;
+          return *this;
+        }
+
+        // postfix
+        iterator operator--(int)
+        {
+          auto copy = *this;
+          --(*this);
+          return copy;
+        }
+
+      private:
+        Data * const m_data;
+        std::size_t m_index = 0;
+    };
+
+
+    template<typename T>
+    iterator<T> begin(void)
+    {
+      throw "This must not happen!";
+    }
+
+    template<typename T>
+    iterator<T> end(void)
+    {
+      throw "This must not happen!";
+    }
+
+  // private:
+    std::vector<int> m_ints;
+    std::vector<double> m_doubles;
+
+};
+
+template<>
+test::iterator<int> test::begin<int>(void)
+{
+  return iterator<int>(m_ints.data(), 0);
+}
+
+template<>
+test::iterator<double> test::begin<double>(void)
+{
+  return iterator<double>(m_doubles.data(), 0);
+}
+
+template<>
+test::iterator<int> test::end<int>(void)
+{
+  return iterator<int>(m_ints.data(), m_ints.size());
+}
+
+template<>
+test::iterator<double> test::end<double>(void)
+{
+  return iterator<double>(m_doubles.data(), m_ints.size());
+}
+
+int main(int argc, char ** argv)
+{
+  xpp::connection c("");
+
+  auto tree = c.query_tree(c.root());
+
+  std::cerr << "#windows (children_len): " << tree->children_len << std::endl;
+  std::cerr << "#windows (length):       " << tree->length << std::endl;
+
+  std::cerr << std::hex;
+  for (auto & window : tree.children()) {
+    std::cerr << "0x" << window << "; ";
+  }
+  std::cerr << std::dec << std::endl;;
+
+  std::cerr << std::hex;
+  for (auto it = tree.children().begin(); it != tree.children().end(); ++it) {
+    std::cerr << "0x" << *it << "; ";
+  }
+  std::cerr << std::dec << std::endl;;
+
+  std::cerr << std::hex;
+  auto it = tree.children().begin();
+  std::cerr << "it  : " << *it     << std::endl;
+  std::cerr << "++it: " << *(++it) << std::endl;
+  std::cerr << "it  : " << *it     << std::endl;
+  std::cerr << "it++: " << *(it++) << std::endl;
+  std::cerr << "it  : " << *it     << std::endl;
+  std::cerr << "++it: " << *(++it) << std::endl;
+  std::cerr << "it  : " << *it     << std::endl;
+  std::cerr << "--it: " << *(--it) << std::endl;
+  std::cerr << "it  : " << *it     << std::endl;
+  std::cerr << "it--: " << *(it--) << std::endl;
+  std::cerr << "it  : " << *it     << std::endl;
+  std::cerr << std::dec << std::endl;;
+
+  auto atom = c.intern_atom(false, "_NET_CLIENT_LIST_STACKING");
+  auto properties = c.get_property<xcb_window_t>(
+      false, c.root(), atom->atom, XCB_ATOM_WINDOW, 0, UINT32_MAX);
+
+  std::cerr << std::hex;
+  for (auto & window : properties) {
+    std::cerr << "0x" << window << "; ";
+  }
+  std::cerr << std::dec << std::endl;;
+
+  return EXIT_SUCCESS;
+}
diff --git a/lib/xpp/src/tests/requests.cpp b/lib/xpp/src/tests/requests.cpp
new file mode 100644 (file)
index 0000000..0df0989
--- /dev/null
@@ -0,0 +1,910 @@
+#include <iostream>
+
+// #include "../request.hpp"
+// #include "../core/connection.hpp"
+// #include "../core/window.hpp"
+// #include <xcb/xcb.h>
+// #include "../gen/sure_dude.hpp"
+// #include "../gen/xproto-stub.hpp"
+
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/cursorfont.h> // XC_cross
+
+#include "../xpp.hpp"
+#include "../proto/randr.hpp"
+#include "../proto/damage.hpp"
+#include "../proto/render.hpp"
+
+
+/*
+namespace xpp { namespace x { namespace error {
+
+void
+dispatcher::operator()(const std::shared_ptr<xcb_generic_error_t> &) const
+{
+  throw std::runtime_error(
+        std::string(__PRETTY_FUNCTION__)
+      + "\n\thandling errors like a boss with first_error = "
+      + std::to_string((uint32_t)m_first_error));
+}
+
+}; }; }; // namespace xpp::x::error
+*/
+
+// namespace my {
+// 
+// struct dispatcher {
+//   dispatcher(xcb_connection_t *) {}
+//   void
+//   operator()(const std::shared_ptr<xcb_generic_error_t> &) const
+//   {
+//     std::cerr << __PRETTY_FUNCTION__ << "\n\thandling errors like a boss" << std::endl;
+//   }
+// };
+// 
+// }; // namespace my
+
+/*
+namespace xpp {
+
+struct dummy_extension {
+  uint8_t first_error = 42;
+};
+
+struct connection
+  : public dummy_extension
+  , public xpp::x::extension
+  , public xpp::x::error::dispatcher
+  // , public xpp::x::protocol<connection>
+  // , virtual public xpp::generic::connection<xpp::connection>
+  , public xpp::x::protocol<connection &>
+  , virtual public xpp::generic::connection<xpp::connection &>
+{
+  connection(xcb_connection_t * c)
+    : xpp::x::error::dispatcher(
+      static_cast<dummy_extension &>(*this).first_error)
+    , m_c(c)
+    , m_data(new int)
+  {
+    m_data = std::make_shared<int>();
+    std::cerr << "connection c'tor: m_data.use_count(): "
+              << m_data.use_count() << std::endl;
+  }
+
+  ~connection(void)
+  {
+    std::cerr << "~connection d'tor: m_data.use_count(): "
+              << m_data.use_count() << std::endl;
+  }
+
+  connection(connection & other)
+    : xpp::x::error::dispatcher(
+      static_cast<const dummy_extension &>(other).first_error)
+    , m_c(other.m_c)
+    // , m_data(other.m_data)
+  {
+    std::cerr << "connection copy c'tor" << std::endl;
+  }
+
+  connection(connection && other) = default;
+  // connection(const connection && other)
+  //   : xpp::x::error::dispatcher(
+  //      static_cast<const dummy_extension &&>(other).first_error)
+  //   , m_c(other.m_c)
+  // {
+  //   std::cerr << "connection move c'tor" << std::endl;
+  // }
+
+  operator xcb_connection_t * const(void) const
+  {
+    return m_c;
+  }
+
+  xpp::connection &
+  // xpp::connection
+  get(void)
+  {
+    return *this;
+  }
+
+  xcb_connection_t * m_c;
+  // std::vector<int> m_data;
+  std::shared_ptr<int> m_data;
+};
+
+class window
+  : public xpp::x::window<xpp::connection &>
+  // , virtual public xpp::iterable<void>
+  , virtual public xpp::iterable<xcb_window_t>
+  // , virtual public xpp::xcb::type<const xcb_window_t &>
+  // , virtual protected xpp::generic::connection<connection &>
+{
+  public:
+    window(xpp::connection & c, const xcb_window_t & window)
+      : m_c(c)
+      , m_window(window)
+    {}
+
+    // xpp::iterable<const xcb_window_t &>
+    virtual
+    void
+    operator=(xcb_window_t window)
+    {
+      m_window = window;
+    }
+
+    virtual operator const xcb_window_t &(void) const
+    {
+      return m_window;
+    }
+
+  protected:
+    xpp::connection & m_c;
+    xcb_window_t m_window;
+
+    xpp::connection &
+    get(void)
+    {
+      return m_c;
+    }
+};
+
+struct dummy_atom {
+  dummy_atom(const xcb_atom_t & atom, xcb_connection_t * const) : m_atom(atom) {}
+  xcb_atom_t m_atom;
+};
+
+}; // xpp
+*/
+
+// namespace xpp { namespace generic {
+// 
+// template<REPLY_TEMPLATE>
+// struct reply_getter<xpp::connection, REPLY_SIGNATURE, checked_tag>
+// {
+//   std::shared_ptr<Reply>
+//   operator()(const xpp::connection & c, const Cookie & cookie)
+//   {
+//     std::cerr << "reply_getter special NO error" << std::endl;
+//     return std::shared_ptr<Reply>(ReplyFunction(c, cookie, nullptr), std::free);
+//   }
+// };
+// 
+// }; };
+
+bool g_quit = false;
+
+namespace x {
+  typedef xpp::connection<xpp::randr::extension,
+                          xpp::damage::extension,
+                          xpp::render::extension>
+                            connection;
+
+  typedef xpp::event::registry<connection &,
+                               xpp::randr::extension,
+                               xpp::damage::extension,
+                               xpp::render::extension>
+                                 registry;
+
+  typedef xpp::font<connection &> font;
+  typedef xpp::cursor<connection &> cursor;
+  typedef xpp::window<connection &> window;
+  typedef xpp::window<xcb_connection_t *> xcb_window;
+
+  typedef xpp::x::event::key_press<connection &> key_press;
+  typedef xpp::x::event::key_release<connection &> key_release;
+  typedef xpp::x::event::button_press<connection &> button_press;
+  typedef xpp::randr::event::notify<connection &> randr_notify;
+  typedef xpp::randr::event::screen_change_notify<connection &> randr_screen_change_notify;
+  typedef xpp::damage::event::notify<connection &> damage_notify;
+};
+
+class one_event
+  : public xpp::event::sink<x::key_press>
+{
+  public:
+    void handle(const x::key_press &) {}
+};
+
+class two_event
+  : public xpp::event::sink<x::key_press, x::key_release>
+{
+  public:
+    void handle(const x::key_press &) {}
+    void handle(const x::key_release &) {}
+};
+
+class more_events
+  : public xpp::event::sink<x::randr_notify, x::randr_screen_change_notify, x::damage_notify>
+{
+  void handle(const x::randr_notify &)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  }
+  void handle(const x::randr_screen_change_notify &)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  }
+  void handle(const x::damage_notify &) {}
+};
+
+template<typename Connection>
+class key_printer
+  : public xpp::event::sink<x::key_press,
+                            x::key_release,
+                            x::button_press>
+                            // xpp::randr::event::notify,
+                            // xpp::damage::event::notify,
+                            // xpp::xfixes::event::selection_notify,
+                            // xpp::screensaver::event::notify
+                            // >
+{
+  public:
+    template<typename C>
+    key_printer(C && c)
+      : m_c(std::forward<C>(c))
+    {}
+
+    void handle(const x::key_press & e)
+    {
+      auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+      auto keysym = *kbd_mapping.keysyms().begin();
+
+      if (keysym == XK_Escape) {
+        std::cerr << "quitting" << std::endl;
+        m_c.ungrab_keyboard(XCB_TIME_CURRENT_TIME);
+        g_quit = true;
+      } else {
+        std::cerr << "key pressed: " << XKeysymToString(keysym) << std::endl;
+      }
+    }
+
+    void handle(const x::key_release & e)
+    {
+      auto kbd_mapping = m_c.get_keyboard_mapping(e->detail, 1);
+      auto keysym = *kbd_mapping.keysyms().begin();
+      std::cerr << "key released: " << XKeysymToString(keysym) << std::endl;
+    }
+
+    void handle(const x::button_press & e)
+    {
+      m_c.ungrab_pointer(XCB_TIME_CURRENT_TIME);
+
+      std::cerr << "root: 0x"
+                << std::hex << e->root << std::dec
+                << "; event: 0x"
+                << std::hex << e->event << std::dec
+                << "; child: 0x"
+                << std::hex << e->child << std::dec
+                << std::endl;
+
+      // xcb_window_t w = e.event();
+      x::window grab_window = e.event<x::window>();
+      std::cerr << "grab_window: " << grab_window << std::endl;
+
+      if (e->event == e->root) {
+        grab_window = e.child();
+        std::cerr << "new grab_window: " << grab_window << std::endl;
+        auto translate = grab_window.translate_coordinates(grab_window, 1, 1);
+        grab_window = translate->child;
+      }
+
+      std::cerr << "grabbing "
+                << std::hex << grab_window << std::dec
+                << std::endl;
+
+      *m_c.grab_keyboard(true, grab_window,
+                         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+      // g_quit = true;
+    }
+
+    // void handle(const xpp::randr::event::notify & e)
+    // {
+    //   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    // }
+
+    // void handle(const xpp::damage::event::notify & e)
+    // {
+    //   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    // }
+
+    // void handle(const xpp::xfixes::event::selection_notify & e)
+    // {
+    //   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    // }
+
+    // void handle(const xpp::screensaver::event::notify & e)
+    // {
+    //   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    // }
+
+    // does not work
+    // template<int OpCode, typename Event>
+    // void handle(const xpp::generic::event<OpCode, Event> &)
+    // {
+    //   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    // }
+
+  private:
+    Connection m_c;
+};
+
+xcb_screen_t *
+screen_of_display(xcb_connection_t * c, int screen)
+{
+  xcb_screen_iterator_t iter;
+
+  iter = xcb_setup_roots_iterator(xcb_get_setup(c));
+  for (; iter.rem; --screen, xcb_screen_next(&iter))
+    if (screen == 0)
+      return iter.data;
+
+  return NULL;
+}
+
+int main(int argc, char ** argv)
+{
+  // int default_screen;
+  // xcb_connection_t * c = xcb_connect(nullptr, &default_screen);
+  // xcb_screen_t * screen = screen_of_display(c, default_screen);
+
+  // x::error_handler eh;
+  x::connection connection;
+  // xcb_connection_t * xcb_c = nullptr;
+
+  try {
+    x::window window(connection, argc > 1 ? std::strtol(argv[1], NULL, 10) : 0);
+    auto tree = window.query_tree();
+    std::cerr << "children of window (" << window << "): ";
+    for (auto && child : tree.children<x::window>()) {
+      std::cerr << child << ", ";
+    }
+    std::cerr << std::endl;
+  } catch (const std::exception & exception) {
+    std::cerr << std::endl;
+    std::cerr << "window exception: " << exception.what() << std::endl;
+  }
+
+  auto net_client_list_stacking_atom = connection.intern_atom(
+      false, "_NET_CLIENT_LIST_STACKING");
+  auto net_client_list_stacking = connection.get_property(
+      false, connection.root(), net_client_list_stacking_atom.atom(),
+      XCB_ATOM_WINDOW, 0, UINT32_MAX);
+
+  std::cerr << "_NET_CLIENT_LIST_STACKING (xcb_window_t):" << std::hex;
+  for (auto && w : net_client_list_stacking.value<xcb_window_t>()) {
+    std::cerr << " 0x" << w;
+  }
+  std::cerr << std::dec << std::endl;
+
+  std::cerr << "_NET_CLIENT_LIST_STACKING (x::window):";
+  for (auto && w : net_client_list_stacking.value<x::window>()) {
+    std::cerr << " " << w;
+  }
+  std::cerr << std::endl;
+
+  std::cerr << "_NET_CLIENT_LIST_STACKING (x::xcb_window):";
+  for (auto && w : net_client_list_stacking.value<x::xcb_window>()) {
+    std::cerr << " " << w;
+  }
+  std::cerr << std::endl;
+
+// #ifndef __clang__
+  // static_cast<xpp::randr::protocol<const x::connection &> &>(connection)
+  //   .query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+  // connection.interface<xpp::randr::extension>()
+  connection.randr().query_version(
+      XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+  const xpp::randr::extension & randr = connection.extension<xpp::randr::extension>();
+  // xpp::randr::extension & randr = static_cast<xpp::randr::extension &>(connection);
+  std::cerr << "RandR Extension" << std::endl;
+  std::cerr << "\tfirst_event: " << (int)randr->first_event << std::endl;
+  std::cerr << "\tfirst_error: " << (int)randr->first_error << std::endl;
+
+  connection.select_input_checked(connection.root(), XCB_RANDR_NOTIFY);
+
+  const xpp::damage::extension & damage = connection.extension<xpp::damage::extension>();
+  std::cerr << "Damage Extension" << std::endl;
+  std::cerr << "\tfirst_event: " << (int)damage->first_event << std::endl;
+  std::cerr << "\tfirst_error: " << (int)damage->first_error << std::endl;
+// #endif
+
+  x::registry registry(connection);
+
+  // key_printer<x::connection &> key_printer(connection);
+  std::vector<key_printer<x::connection &>> key_printers(100, connection);
+  // std::vector<key_printer<x::connection &> *> key_printers(100, new key_printer<x::connection &>(connection));
+
+  const int n = 2;
+  // registry.attach(0, &key_printer);
+  for (int i =  0; i < n; ++i) {
+    registry.attach(0, &key_printers[i]);
+    // registry.attach(0, key_printers[i]);
+  }
+
+  for (int i =  0; i < n - 1; ++i) {
+    registry.detach(0, &key_printers[i]);
+    // registry.detach(0, key_printers[i]);
+  }
+
+  one_event oe;
+  two_event te;
+  more_events me;
+
+  registry.attach(0, &oe);
+  registry.attach(0, &te);
+  registry.detach(0, &oe);
+  registry.detach(0, &te);
+
+  registry.attach(0, &me);
+  // registry.detach(0, &me);
+
+
+  // auto font = connection.generate_id();
+  // connection.open_font(font, "cursor");
+
+  // auto cursor = connection.generate_id();
+  // connection.create_glyph_cursor(cursor, font, font,
+      // XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+  // connection.close_font(font);
+  // connection.close_cursor(cursor);
+    // x::cursor cursor_xid = x::cursor(connection, 0);
+
+
+  try {
+    x::font font = x::font::open_checked(connection, "cursor");
+
+    x::cursor cursor = x::cursor::create_glyph_checked(connection, font, font,
+        XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+
+    *connection.grab_pointer(false, connection.root(),
+                             XCB_EVENT_MASK_BUTTON_PRESS,
+                             XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+                             XCB_NONE, cursor, XCB_TIME_CURRENT_TIME);
+
+    std::cerr << "Please click on a window" << std::endl;
+
+  } catch (const std::exception & error) {
+    std::cerr << "Exception (std::exception) in "
+              << __FILE__ << " @ line " << __LINE__ << ", what(): "
+              << error.what() << std::endl;
+    std::exit(EXIT_FAILURE);
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr << "Exception (std::shared_ptr<xcb_generic_error_t>) in "
+              << __FILE__ << " @ line " << __LINE__ << ", error_code: "
+              << (int)error->error_code << std::endl;
+    std::exit(EXIT_FAILURE);
+  } catch (...) {
+    std::cerr << "Exception (...) in "
+              << __FILE__ << " @ line " << __LINE__
+              << std::endl;
+    std::exit(EXIT_FAILURE);
+  }
+
+
+  // xcb_randr_get_output_info_cookie_t goic = xcb_randr_get_output_info_unchecked(connection, -1, XCB_TIME_CURRENT_TIME);
+  // xcb_randr_get_output_info_reply_t * goir = xcb_randr_get_output_info_reply(connection, goic, nullptr);
+
+  // XCB_RANDR_BAD_OUTPUT
+  auto output_info = connection.get_output_info_unchecked(-1);
+  output_info.get();
+
+  // XCB_RANDR_BAD_CRTC
+  // auto crtc_info = connection.get_crtc_info_unchecked(-1);
+  // crtc_info.get();
+
+  // auto output_property = connection.query_output_property_unchecked(-1, -1);
+  // output_property.get();
+
+  connection.change_output_property(-1, -1, -1, 0, 0, 0, nullptr);
+
+  // connection.map_window(-1);
+
+  // auto & damage_proto = static_cast<xpp::damage::protocol<const x::connection &> &>(connection);
+  // damage_proto.create(-1, -1, 0);
+  // damage_proto.destroy(-1);
+  // damage_proto.subtract(-1, 0, 0);
+
+  connection.query_pict_index_values_unchecked(-1);
+  connection.change_picture(-1, 0, nullptr);
+
+// clang_complete does not like this
+// causes vim to segfault
+// #if not defined __clang__
+
+  while (! g_quit) {
+    connection.flush();
+    try {
+      registry.dispatch(connection.wait_for_event());
+    } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+      std::cerr << "Caught std::shared_ptr<xcb_generic_error_t> in event loop "
+                << "(code: " << (int)error->error_code << ")" << std::endl;
+    } catch (const std::exception & error) {
+      std::cerr << "Caught std::exception in event loop: " << error.what() << std::endl;
+    } catch (...) {
+      std::cerr << "Something really bad has happened" << std::endl;
+    }
+  }
+
+// #endif
+
+  // xpp::x::event::key_press<x::connection &>
+  //   xek_1(connection, std::shared_ptr<xcb_generic_event_t>((xcb_generic_event_t *)new xcb_key_press_event_t));
+  // xpp::x::event::key_press<xcb_connection_t *>
+  //   xek_2(connection, std::shared_ptr<xcb_generic_event_t>(((xcb_generic_event_t *)new xcb_key_press_event_t)));
+  // xpp::x::event::key_press<xcb_connection_t *>
+  //   xek_3(xcb_c, std::shared_ptr<xcb_generic_event_t>(((xcb_generic_event_t *)new xcb_key_press_event_t)));
+
+  // xpp::window<x::connection>      w_1(connection);
+  // xpp::window<xcb_connection_t *> w_2(xcb_c);
+  // static_cast<xcb_window_t &>(w_1) = 12;
+
+  // if (xcb_connection_has_error(c)) {
+  //   std::cerr << "Connection has error" << std::endl;
+  //   return EXIT_FAILURE;
+  // }
+
+  // xpp::x::error::dispatcher ed(connection);
+
+  // xcb_void_cookie_t mwc = xcb_map_window_checked(c, -1);
+  // xcb_flush(c);
+  // xcb_generic_error_t * error = xcb_request_check(c, mwc);
+  // xcb_flush(c);
+  // if (error) {
+  //   std::free(error);
+  //   std::cerr << "mwc error" << std::endl;
+  // }
+
+  /*
+  std::shared_ptr<xcb_query_tree_reply_t> qtr;
+
+  typedef xpp::fixed::detail::simple<
+      x::connection &, xcb_window_t,
+      SIGNATURE(xcb_query_tree_children),
+      SIGNATURE(xcb_query_tree_children_length)>
+        simple_query_tree_iterator;
+  simple_query_tree_iterator(connection, qtr, 0);
+
+  typedef xpp::fixed::detail::object<
+      x::connection &, x::window,
+      SIGNATURE(xcb_query_tree_children),
+      SIGNATURE(xcb_query_tree_children_length)>
+        object_query_tree_iterator;
+  object_query_tree_iterator(connection, qtr, 0);
+  */
+
+  // typedef xpp::iterator<
+  //     x::connection, x::window,
+  //     SIGNATURE(xcb_query_tree_children),
+  //     SIGNATURE(xcb_query_tree_children_length)>
+  //       query_tree_iter;
+
+  // auto qt_iter = query_tree_iter(connection, qtr, 0);
+
+  // typedef xpp::generic::list<x::connection,
+  //                            xcb_query_tree_reply_t,
+  //                            query_tree_iter>
+  //                              query_tree_list;
+
+  // auto qt_list = query_tree_list(connection, qtr);
+  // auto qt_list_begin = qt_list.begin();
+
+  // std::shared_ptr<xcb_query_tree_reply_t> qtr;
+  // auto simple_iter = xpp::fixed::iterator::simple<
+  //     x::connection, xcb_window_t, xcb_window_t, xcb_query_tree_reply_t,
+  //     xcb_query_tree_children,
+  //     xcb_query_tree_children_length>(connection, qtr, 0);
+
+  // auto object_iter = xpp::fixed::iterator::object<
+  //     x::connection, xcb_window_t, x::window, xcb_query_tree_reply_t,
+  //     xcb_query_tree_children,
+  //     xcb_query_tree_children_length>(connection, qtr, 0);
+
+  // std::shared_ptr<xcb_get_font_path_reply_t> fpr;
+  // typedef xpp::iterator<x::connection,
+  //                       xcb_str_t,
+  //                       xcb_str_t,
+  //                       SIGNATURE(xcb_str_next),
+  //                       SIGNATURE(xcb_str_sizeof),
+  //                       SIGNATURE(xcb_get_font_path_path_iterator)>
+  //                         font_path_iter;
+
+  std::cerr << "fonts:" << std::endl;
+  auto fonts = connection.list_fonts(8, 1, "*");
+  for (auto && name : fonts.names()) {
+    std::cerr << "font [" << name.length() << "]: " << name << std::endl;
+  }
+
+  std::cerr << "paths:" << std::endl;
+  // auto font_paths = connection.get_font_path();
+  for (auto && path : connection.get_font_path().path()) {
+    std::cerr << "path [" << path.length() << "]: " << path << std::endl;
+  }
+
+  auto tree = connection.root<x::window>().query_tree();
+  std::cerr << "children: ";
+  for (auto && child : tree.children<x::xcb_window>()) {
+    std::cerr << child << ", ";
+    auto siblings = child.query_tree();
+    std::cerr << "(siblings: ";
+    for (auto && sibling : siblings.children()) {
+      std::cerr << sibling << ", ";
+    }
+    std::cerr << "), " << std::endl;
+  }
+  std::cerr << std::endl;
+
+  // auto tree_2 = connection.query_tree(screen->root);
+  // std::cerr << "children: ";
+  // for (auto & w : tree_2.children<x::window>()) {
+  //   std::cerr << w << ", ";
+  // }
+  // std::cerr << std::endl;
+
+  // xcb_window_t window = 27263111;
+  xcb_window_t window = 0;
+
+  // test::map_window(c, window);
+  // test::map_window(c, window, 0);
+  xpp::x::map_window(connection, window);
+  connection.map_window(window);
+
+  try {
+    // test::map_window_checked(c, window);
+    // xpp::x::map_window_checked(c, window);
+    connection.map_window_checked(window);
+  } catch (const std::exception & e) {
+    std::cerr << "CATCH: map_window exception:" << std::endl
+              << "\twhat(): " << e.what() << std::endl;
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr << "CATCH: map_window error: " << (int)error->error_code << std::endl;
+  }
+
+  // auto attrs_0 = test::get_window_attributes(c, window);
+  // auto attrs_1 = test::get_window_attributes(connection, window);
+  // auto attrs_2 = test::get_window_attributes_unchecked(connection, window);
+
+  xpp::x::reply::checked::get_window_attributes<xcb_connection_t *>
+    gwar(connection, window);
+
+  auto attrs_0 = connection.get_window_attributes(window);
+  // auto attrs_0 = xpp::x::get_window_attributes(c, window);
+  auto attrs_1 = xpp::x::get_window_attributes(connection, window);
+  auto attrs_2 = xpp::x::get_window_attributes_unchecked(connection, window);
+
+  try {
+    attrs_0.get();
+  } catch (const std::exception & e) {
+    std::cerr << "CATCH: attrs_0 exception:" << std::endl
+              << "\twhat(): " << e.what() << std::endl;
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr << "CATCH: attrs_0 std::shared_ptr<xcb_generic_error_t>::error_code: " << (int)error->error_code << std::endl;
+  }
+
+  if (! attrs_0) {
+    std::cerr << "attrs_0 invalid" << std::endl;
+  } else {
+    std::cerr << "attrs_0 valid" << std::endl;
+  }
+
+  try {
+    attrs_1.get();
+  } catch (const std::exception & e) {
+    std::cerr << "CATCH: attrs_1 exception:" << std::endl
+              << "\twhat(): " << e.what() << std::endl;
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr << "CATCH: attrs_1 std::shared_ptr<xcb_generic_error_t>::error_code: " << (int)error->error_code << std::endl;
+  }
+
+  if (! attrs_1) {
+    std::cerr << "attrs_1 invalid" << std::endl;
+  } else {
+    std::cerr << "attrs_1 valid" << std::endl;
+  }
+
+  try {
+    attrs_2.get();
+  } catch (const std::exception & e) {
+    std::cerr << "CATCH: attrs_2 exception:" << std::endl
+              << "\twhat(): " << e.what() << std::endl;
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr << "CATCH: attrs_2 std::shared_ptr<xcb_generic_error_t>::error_code: " << (int)error->error_code << std::endl;
+  }
+
+  if (! attrs_2) {
+    std::cerr << "attrs_2 invalid" << std::endl;
+  } else {
+    std::cerr << "attrs_2 valid" << std::endl;
+  }
+
+  try {
+    auto my_string_atom = xpp::x::intern_atom(connection, false, "MY_STRING");
+    std::string my_string("SUPER COOL");
+
+    std::cerr << "atom \"MY_STRING\": " << my_string_atom.atom() << std::endl;;
+
+    auto atom_name = connection.get_atom_name(my_string_atom.atom());
+    std::cerr << "atom name: " << atom_name.name() << std::endl;;
+
+    // xpp::x::change_property_checked(c,
+    connection.change_property_checked(
+        XCB_PROP_MODE_REPLACE, connection.root(),
+        my_string_atom.atom(), XCB_ATOM_STRING, 8,
+        my_string.begin(), my_string.end());
+
+    // xpp::x::change_property_checked(c,
+    connection.change_property_checked(
+        XCB_PROP_MODE_REPLACE, 0,
+        my_string_atom.atom(), XCB_ATOM_STRING, 8,
+        my_string.length(), my_string.c_str());
+  } catch (const std::exception & e) {
+    std::cerr << "change property failed: " << e.what() << std::endl;
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr << "generic error: " << (int)error->error_code << std::endl;
+  }
+
+  connection.flush();
+
+  // xcb_disconnect(c);
+
+
+  /*
+  std::cerr << std::endl << "intern_atom 1" << std::endl;
+  auto atom_reply_1 =
+    xpp::x::intern_atom_unchecked(c, false, 25, "_NET_CLIENT_LIST_STACKING");
+  atom_reply_1.get();
+  if (atom_reply_1) {
+    std::cerr<<"atom_reply_1 success"<<std::endl;
+  }
+
+  std::cerr << std::endl << "intern_atom 2" << std::endl;
+  auto atom_reply_2 = xpp::x::intern_atom(c, false, "_NET_CLIENT_LIST_STACKING");
+  atom_reply_2.get();
+  if (atom_reply_2) {
+    std::cerr<<"atom_reply_2 success"<<std::endl;
+  }
+
+  std::cerr << std::endl << "intern_atom 3" << std::endl;
+  auto atom_reply_3 = xpp::x::intern_atom(c, false, "_NET_CLIENT_LIST_STAC");
+  try {
+    atom_reply_3.get();
+    std::cerr<<"atom_reply_3 success"<<std::endl;
+  } catch (const std::shared_ptr<xcb_generic_error_t> & error) {
+    std::cerr<<"CATCH: atom_reply_3 success, error_code: "
+    << (int)error->error_code <<std::endl;
+  }
+
+  std::cerr << std::endl << "intern_atom 1 reply: ";
+  xcb_atom_t atom = atom_reply_1->atom;
+  std::cerr << (int)atom << std::endl;
+
+  std::cerr << std::endl << "intern_atom 2 reply: ";
+  auto dummy_atom = atom_reply_2.atom<xpp::dummy_atom>();
+  std::cerr << (int)dummy_atom.m_atom << std::endl;
+
+  auto my_string_atom = xpp::x::intern_atom(c, true, "MY_STRING");
+  std::string my_string("LADIDA");
+  xpp::x::change_property(
+      c, XCB_PROP_MODE_REPLACE, 0, my_string_atom.atom(), XCB_ATOM_STRING, 8,
+      my_string.begin(), my_string.end());
+  */
+
+  /*
+  std::cerr << std::endl << "intern_atom 1" << std::endl;
+  auto atom_reply_1 = intern_atom(c, false, 25, "_NET_CLIENT_LIST_STACKING");
+
+  std::cerr << std::endl << "intern_atom 2" << std::endl;
+  auto atom_reply_2 = intern_atom(c, false, std::string("_NET_CLIENT_LIST_STACKING"));
+
+  std::cerr << std::endl << "intern_atom 3" << std::endl;
+  auto atom_reply_3 = intern_atom<xpp::connection,
+                                  my::dispatcher,
+                                  xpp::x::error::dispatcher>
+                                    (c, false, "FOO BAZ");
+  atom_reply_3.get();
+
+  std::cerr << std::endl << "intern_atom 1 reply: ";
+  xcb_atom_t atom = atom_reply_1->atom;
+  std::cerr << (int)atom << std::endl;
+
+  std::cerr << std::endl << "intern_atom 2 reply: ";
+  auto dummy_atom = atom_reply_2.atom<xpp::dummy_atom>();
+  std::cerr << (int)dummy_atom.m_atom << std::endl;
+
+  std::cerr << std::endl << "map_window 1" << std::endl;
+  map_window(connection, 0);
+  std::cerr << std::endl << "map_window 2" << std::endl;
+  map_window<xpp::connection>(c, 0);
+  std::cerr << std::endl << "map_window 3" << std::endl;
+  map_window<xpp::connection, my::dispatcher, xpp::x::error::dispatcher>(connection, 0);
+
+  auto my_string_atom =
+    intern_atom<xpp::connection, my::dispatcher>(connection, true, "MY_STRING");
+  std::string my_string("LADIDA");
+  change_property<xpp::connection, my::dispatcher>(
+      c, XCB_PROP_MODE_REPLACE, 0, my_string_atom.atom(), XCB_ATOM_STRING, 8,
+      my_string.begin(), my_string.end());
+  */
+
+  /*
+  std::cerr << std::endl << "auto wa_1" << std::endl;
+  auto wa_1 = get_window_attributes(c, 0);
+  std::cerr << std::endl << "auto wa_2" << std::endl;
+  auto wa_2 = get_window_attributes<xpp::connection,
+                                    my::dispatcher,
+                                    xpp::x::error::dispatcher>
+                                      (connection, 0);
+  std::cerr << std::endl << "auto wa_3" << std::endl;
+  auto wa_3 = get_window_attributes<xpp::connection,
+                                    my::dispatcher,
+                                    xpp::x::error::dispatcher>
+                                      (connection, 0);
+
+  std::cerr << std::endl << "wa_1" << std::endl;
+  auto r1 = wa_1.get();
+  std::cerr << std::endl << "wa_2" << std::endl;
+  auto r2 = wa_2.get();
+  std::cerr << std::endl << "wa_3" << std::endl;
+  auto r3 = wa_3.get();
+  */
+
+  // try {
+  //   std::cerr << "YOLOLOLOL" << std::endl;
+  //   xcb_colormap_t cm = wa_3.colormap();
+  //   cm = wa_1->colormap;
+  //   cm = r1->colormap;
+  // } catch (...) {}
+
+  // map_window<xpp::connection>(c, 0);
+  // map_window<xpp::connection, my::dispatcher, xpp::x::error::dispatcher>(c, 0);
+
+  // auto wa_cookie = get_window_attributes_cookie(c, 0);
+  // auto wa_reply_1 = get_window_attributes_reply<xpp::connection>(connection, wa_cookie);
+  // auto wa_reply_2 = get_window_attributes_reply<
+  //   xpp::connection, my::dispatcher, xpp::x::error::dispatcher>(
+  //     connection, get_window_attributes_cookie()(c, 0));
+
+  // wa_reply_1.get();
+  // wa_reply_2.get();
+
+  // auto wa = get_window_attributes(c, 0);
+  // auto wa_error = get_window_attributes(ed, c, 0);
+  // wa_error.get();
+
+  // map_window_cookie<xpp::connection>()(connection, 0);
+  // map_window_cookie<xpp::connection>()(ed, connection, 0);
+  // map_window_cookie<xpp::connection>()(connection, connection, 0);
+  // map_window_cookie<xpp::connection>()(xpp::x::error::dispatcher(), connection, 0);
+
+  // get_window_attributes_cookie wac_1(c, 0);
+  // get_window_attributes_cookie wac_2(c, 0);
+
+  // get_window_attributes_reply<> war_1(connection, wac_1);
+  // get_window_attributes_reply<> war_2(connection, wac_2);
+
+  // get_window_attributes_reply<my::dispatcher, xpp::x::error::dispatcher>
+  //   war_3(connection, get_window_attributes_cookie()(c, 0), my::dispatcher(), xpp::x::error::dispatcher());
+
+  // war_1.get();
+  // war_2.get();
+  // war_3.get();
+
+
+  // map_window_cookie<xpp::connection> mwc_unchecked;
+  // mwc_checked(, connection, 0);
+  // mwc_checked(xpp::x::error::dispatcher(), connection, 0);
+  // mwc_unchecked(connection, 0);
+  // map_window_cookie<checked,   xpp::connection> mwc_checked;
+  // map_window_cookie<unchecked, xpp::connection> mwc_unchecked;
+
+  // map_window(c, 0);
+  // map_window<xpp::generic::checked>(c, 0);
+  // auto reply = get_window_attributes(connection, 0);
+  // reply.get();
+
+  return EXIT_SUCCESS;
+}
diff --git a/lib/xpp/src/tests/resource.cpp b/lib/xpp/src/tests/resource.cpp
new file mode 100644 (file)
index 0000000..a359aea
--- /dev/null
@@ -0,0 +1,49 @@
+#include <iostream>
+
+#include "../xpp.hpp"
+#include "../proto/randr.hpp"
+#include "../proto/damage.hpp"
+#include "../proto/render.hpp"
+
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+#include <X11/cursorfont.h> // XC_cross
+
+namespace x {
+  typedef xpp::connection<
+                          // xpp::randr::extension,
+                          // xpp::damage::extension,
+                          // xpp::render::extension
+                         >
+                            connection;
+
+  typedef xpp::event::registry<connection &
+                               // xpp::randr::extension,
+                               // xpp::damage::extension,
+                               // xpp::render::extension>
+                              >
+                                 registry;
+
+  typedef xpp::font<connection &> font;
+  typedef xpp::cursor<connection &> cursor;
+  typedef xpp::window<connection &> window;
+  typedef xpp::window<xcb_connection_t *> xcb_window;
+
+  typedef xpp::x::event::key_press<connection &> key_press;
+  typedef xpp::x::event::key_release<connection &> key_release;
+  typedef xpp::x::event::button_press<connection &> button_press;
+  typedef xpp::randr::event::notify<connection &> randr_notify;
+  typedef xpp::randr::event::screen_change_notify<connection &> randr_screen_change_notify;
+  typedef xpp::damage::event::notify<connection &> damage_notify;
+};
+
+int main(int argc, char ** argv)
+{
+  x::connection connection;
+  xcb_font_t font = 0;
+  auto cursor_1 = x::cursor(connection, 0);
+  auto cursor_2 = x::cursor::create_glyph(connection, font, font,
+      XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+
+  return 0;
+}
diff --git a/lib/xpp/src/tests/sizeof.cpp b/lib/xpp/src/tests/sizeof.cpp
new file mode 100644 (file)
index 0000000..11faf76
--- /dev/null
@@ -0,0 +1,767 @@
+// compile with `g++ -std=c++11 test.cpp`
+#include <iostream>
+#include <map>
+#include <unordered_map>
+#include <vector>
+#include <algorithm>
+
+namespace test1 {
+
+template<typename T>
+struct interface {
+  static std::size_t size_of(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    return sizeof(T);
+  }
+};
+
+template<>
+struct interface<void> {
+  static std::size_t size_of(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    return sizeof(char);
+  }
+};
+
+template<typename T>
+struct A : public interface<T> {
+  A(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " size_of(): "
+              << this->size_of() << std::endl;
+  }
+
+  static std::size_t size_of(void)
+  {
+    std::cerr << "YOLOLOL" << std::endl;
+    return 0;
+  }
+};
+
+// template<typename T>
+struct B : public interface<int> {
+  B(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " size_of(): "
+              << this->size_of() << std::endl;
+  }
+};
+
+// template<typename T>
+struct C : public interface<double> {
+  C(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " size_of(): "
+              << this->size_of() << std::endl;
+  }
+};
+
+int main(int argc, char ** argv)
+{
+  test1::A<int> t1_1;
+  std::cerr << std::endl;
+  test1::A<double> t1_2;
+  std::cerr << std::endl;
+  test1::A<char> t1_3;
+  std::cerr << std::endl;
+  test1::A<uint16_t> t1_4;
+  std::cerr << std::endl;
+  test1::A<void> t1_5;
+  std::cerr << std::endl;
+
+  test1::B t2_1;
+  std::cerr << std::endl;
+
+  test1::C t3_1;
+  std::cerr << std::endl;
+
+  return 0;
+}
+
+};
+
+namespace test2 {
+
+struct interface {
+  static void test(void);
+};
+
+struct A : public interface {
+  static void test(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " (A::test)" << std::endl;
+  }
+  A(void)
+  {
+    test();
+  }
+};
+
+struct B : public interface {
+  static void test(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " (B::test)" << std::endl;
+  }
+  B(void)
+  {
+    test();
+  }
+};
+
+void test(const interface & i)
+{
+  A::test();
+  B::test();
+  // decltype(i)::test();
+}
+
+int main(int argc, char ** argv)
+{
+  test2::A a;
+  test2::B b;
+  test2::test(a);
+  // test2::test();
+
+  return 0;
+}
+
+};
+
+namespace test3 {
+
+template<typename T>
+struct Base {
+  T t;
+};
+
+template<int N>
+struct select_type;
+
+template<>
+struct select_type<1> {
+  Base<int> base;
+};
+
+template<>
+struct select_type<2> {
+  Base<double> base;
+};
+
+int main(int argc, char ** argv)
+{
+  constexpr int i = 0;
+  test3::select_type<1> st_1;
+  test3::select_type<2> st_2;
+
+  return 0;
+}
+
+};
+
+namespace test4 {
+
+template<typename T>
+struct i {
+  virtual bool f(T) = 0;
+};
+
+namespace a {
+  struct a : public i<int> {
+    bool f(int i)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+      return i == m_i;
+    }
+    int m_i = 0;
+  };
+};
+
+namespace b {
+  struct b : public i<int> {
+    bool f(int i)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+      return i == m_i;
+    }
+    int m_i = 1;
+  };
+};
+
+namespace c {
+  struct c : public i<int> {
+    bool f(int i)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+      return i == m_i;
+    }
+    int m_i = 2;
+  };
+};
+
+template<typename ... IS>
+struct z : public IS ... {
+
+  // template<typename ... VS>
+  void
+  run(int i)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " with i = " << i << std::endl;
+    f(i, static_cast<IS *>(this) ...);
+  }
+
+  template<typename I, typename ... ISS>
+  void
+  f(int v, I * i, ISS ... is)
+  {
+    if (! i->f(v)) {
+      f(v, is ...);
+    } else {
+      std::cerr << "We've got a winner!" << std::endl;
+    }
+  }
+
+  template<typename I>
+  void
+  f(int v, I * i)
+  {
+    i->f(v);
+  }
+
+};
+
+int main(int argc, char ** argv)
+{
+  test4::z<test4::a::a, test4::b::b, test4::c::c> z;
+  for (auto i : { 0, 1, 2, 3 }) {
+    z.run(i);
+  }
+  return 0;
+}
+
+};
+
+namespace test5 {
+
+struct pod_generic {
+  int id;
+  int m_int;
+};
+
+struct pod_int {
+  int id = 0;
+  int m_int;
+};
+
+struct pod_double {
+  int id = 1;
+  double m_double;
+};
+
+struct pod_string {
+  int id = 2;
+  std::string m_string;
+};
+
+template<int OpCode>
+struct my_pod_wrapper {
+  static const int opcode = OpCode;
+};
+
+struct my_pod_int_wrapper : public my_pod_wrapper<0> {
+  my_pod_int_wrapper(pod_generic * pg)
+    : m_pi((pod_int *)pg) {}
+  pod_int * m_pi;
+};
+
+struct my_pod_double_wrapper : public my_pod_wrapper<1> {
+  my_pod_double_wrapper(pod_generic * pg)
+    : m_pd((pod_double *)pg) {}
+  pod_double * m_pd;
+};
+
+struct my_pod_string_wrapper : public my_pod_wrapper<2> {
+  my_pod_string_wrapper(pod_generic * pg)
+    : m_ps((pod_string *)pg) {}
+  pod_string * m_ps;
+};
+
+class dispatcher {
+  public:
+    virtual ~dispatcher(void) {}
+    template<typename E> void dispatch(const E & e);
+};
+
+template<typename ... Events>
+class sink;
+
+template<typename E>
+class sink<E> : virtual public dispatcher {
+  public:
+    virtual void handle(const E & e) = 0;
+};
+
+template<typename Event, typename ... Events>
+class sink<Event, Events ...>
+  : virtual public sink<Event>
+  , virtual public sink<Events> ...
+{};
+
+template<typename E>
+void dispatcher::dispatch(const E & e)
+{
+  dynamic_cast<sink<E> *>(this)->handle(e);
+}
+
+template<int ExtensionId>
+class pod_dispatcher {
+  public:
+    template<typename Dispatcher>
+    bool
+    operator()(pod_generic * pg, const Dispatcher & D) const
+    {
+      // std::cerr << __PRETTY_FUNCTION__ << std::endl;
+      std::cerr << "POD_DISPATCHER<" << ExtensionId << ">" << std::endl;
+      switch (pg->id) {
+        case 0:
+          // std::cerr << "dispatch with my_pod_int_wrapper" << std::endl;
+          D(my_pod_int_wrapper(pg));
+          return true;
+
+        case 1:
+          // std::cerr << "dispatch with my_pod_double_wrapper" << std::endl;
+          D(my_pod_double_wrapper(pg));
+          return true;
+
+        case 2:
+          // std::cerr << "dispatch with my_pod_string_wrapper" << std::endl;
+          D(my_pod_string_wrapper(pg));
+          return true;
+      };
+
+      return false;
+    }
+};
+
+template<>
+class pod_dispatcher<1> {
+  public:
+    template<typename Dispatcher>
+    bool
+    operator()(pod_generic * pg, const Dispatcher & D) const
+    {
+      std::cerr << "POD_DISPATCHER<1>" << std::endl;
+      switch (pg->id) {
+        case 0:
+          // std::cerr << "dispatch with my_pod_int_wrapper" << std::endl;
+          D(my_pod_int_wrapper(pg));
+          return true;
+
+        case 1:
+          // std::cerr << "dispatch with my_pod_double_wrapper" << std::endl;
+          D(my_pod_double_wrapper(pg));
+          return true;
+
+        case 2:
+          // std::cerr << "dispatch with my_pod_string_wrapper" << std::endl;
+          D(my_pod_string_wrapper(pg));
+          return true;
+      };
+
+      return false;
+    }
+};
+
+class a : public sink<my_pod_int_wrapper,
+                      my_pod_double_wrapper,
+                      my_pod_string_wrapper>
+{
+  public:
+    virtual void handle(const my_pod_int_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+    virtual void handle(const my_pod_double_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+    virtual void handle(const my_pod_string_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+class b : public sink<my_pod_string_wrapper,
+                      my_pod_double_wrapper>
+{
+  public:
+    virtual void handle(const my_pod_string_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+    virtual void handle(const my_pod_double_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+class c : public sink<my_pod_double_wrapper>
+{
+  public:
+    virtual void handle(const my_pod_double_wrapper &) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+template<typename ... POD_Dispatcher>
+struct handler_registry {
+
+  template<typename D>
+  bool
+  dispatch(pod_generic * pg) const
+  {
+    std::cerr << "RecursiveDispatchEnd_EvenBetter" << std::endl;
+    return D()(pg, *this);
+  }
+
+  template<typename D1, typename D2, typename ... Dispatcher>
+  bool
+  dispatch(pod_generic * pg) const
+  {
+    std::cerr << "BigFatDispatch_ManFeelsGood" << std::endl;
+    D1()(pg, *this);
+    return dispatch<D2, Dispatcher ...>(pg);
+  }
+
+  bool
+  dispatch(pod_generic * pg) const
+  {
+    std::cerr << "InitialDispatch_JustAbitWeiry" << std::endl;
+    return dispatch<POD_Dispatcher ...>(pg);
+  }
+
+  template<typename Event>
+  void
+  operator()(const Event & e) const
+  {
+    try {
+      for (auto & item : m_dispatcher.at(Event::opcode)) {
+        item.second->dispatch(e);
+      }
+    } catch (...) {}
+  }
+
+  template<typename Event1, typename Event2, typename ... Events>
+  void
+  attach(sink<Event1, Event2, Events ...> * s)
+  {
+    attach(Event1::opcode, s);
+    attach((sink<Event2, Events ...> *)s);
+  }
+
+  template<typename Event>
+  void
+  attach(sink<Event> * s)
+  {
+    attach(Event::opcode, s);
+  }
+
+  void attach(unsigned int opcode, dispatcher * d)
+  {
+    m_dispatcher[opcode].emplace(0, d);
+  }
+
+  std::unordered_map<unsigned int,
+                     std::multimap<unsigned int, dispatcher *>> m_dispatcher;
+};
+
+template<typename I>
+void foo(int i, I j) {}
+
+int main(int argc, char ** argv)
+{
+  int i = 42;
+  std::string s = "42";
+
+  pod_int pi;
+  pod_double pd;
+  pod_string ps;
+
+  test5::a a;
+  test5::b b;
+  test5::c c;
+
+  handler_registry<pod_dispatcher<0>,
+                   pod_dispatcher<1>,
+                   pod_dispatcher<2>> registry;
+
+  registry.attach(&a);
+  registry.attach(&b);
+  registry.attach(&c);
+
+  registry.dispatch((test5::pod_generic *)&pi);
+  registry.dispatch((test5::pod_generic *)&ps);
+  registry.dispatch((test5::pod_generic *)&pd);
+
+  return 0;
+}
+
+};
+
+namespace test6 {
+  template <typename... Types>
+    struct foo {};
+
+  template < typename... Types1, template <typename...> class T
+    , typename... Types2, template <typename...> class V
+    , typename U >
+    void
+    bar(const T<Types1...>&, const V<Types2...>&, const U& u)
+    {
+      std::cout << sizeof...(Types1) << std::endl;
+      std::cout << sizeof...(Types2) << std::endl;
+      std::cout << u << std::endl;
+    }
+
+  int main(int argc, char ** argv)
+    {
+      foo<char, int, float> f1;
+      foo<char, int> f2;
+      bar(f1, f2, 9);
+      return 0;
+    }
+};
+
+namespace test7 {
+
+static constexpr std::size_t * my_ext_1 = nullptr;
+static constexpr std::size_t * my_ext_2 = nullptr;
+static constexpr std::size_t * my_ext_3 = nullptr;
+static constexpr std::size_t * my_ext_4 = nullptr;
+
+// static const std::size_t id_4_0 = reinterpret_cast<std::size_t>(&my_ext_4 + 0);
+// static const std::size_t id_4_1 = reinterpret_cast<std::size_t>(&my_ext_4 + 1);
+// static const std::size_t id_4_2 = reinterpret_cast<std::size_t>(&my_ext_4 + 2);
+
+namespace p1 {
+template<std::size_t * Id>
+struct ext {};
+
+template<>
+struct ext<my_ext_1> {
+  void call(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+};
+
+namespace p2 {
+template<std::size_t * Id>
+struct ext {};
+
+template<>
+struct ext<my_ext_2> {
+  void call(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+};
+
+namespace p3 {
+template<std::size_t * Id>
+struct ext {};
+
+template<>
+struct ext<my_ext_3> {
+  void call(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+};
+
+struct proto_1 {
+  typedef p1::ext<my_ext_1> ext;
+  template<typename Handler, typename Event>
+  void dispatch(const Handler & h, const Event & e)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    e.call(static_cast<const typename Event::ext &>(h));
+  }
+};
+
+struct proto_2 {
+  typedef p2::ext<my_ext_2> ext;
+  template<typename Handler, typename Event>
+  void dispatch(const Handler & h, const Event & e)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    e.call(static_cast<const typename Event::ext &>(h));
+  }
+};
+
+struct proto_3 {
+  typedef p3::ext<my_ext_3> ext;
+  template<typename Handler, typename Event>
+  void dispatch(const Handler & h, const Event & e)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    e.call(static_cast<const typename Event::ext &>(h));
+  }
+};
+
+struct event_1 {
+  typedef p1::ext<my_ext_1> ext;
+  typedef proto_1 proto;
+  // void call(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  void call(const ext & e) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; e.call(); }
+};
+
+struct event_2 {
+  typedef p2::ext<my_ext_2> ext;
+  typedef proto_2 proto;
+  // void call(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  void call(const ext & e) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; e.call(); }
+};
+
+struct event_3 {
+  typedef p3::ext<my_ext_3> ext;
+  typedef proto_3 proto;
+  // void call(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  void call(const ext & e) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; e.call(); }
+};
+
+struct pod_event_1 {
+  const int id = 1;
+};
+
+struct pod_event_2 {
+  const int id = 2;
+};
+
+struct pod_event_3 {
+  const int id = 3;
+};
+
+template<typename ... Protos>
+struct proto
+  : public Protos ...
+  , public Protos::ext ...
+{
+
+  template<typename Event>
+  void run(const Event & event)
+  {
+    static_cast<typename Event::proto *>(this)->dispatch(*this, event);
+  }
+
+};
+
+int main(int argc, char ** argv)
+{
+  std::cerr << "my_ext_1: " << my_ext_1 << std::endl;
+  std::cerr << "my_ext_2: " << my_ext_2 << std::endl;
+  std::cerr << "my_ext_3: " << my_ext_3 << std::endl;
+
+  proto<proto_1, proto_2, proto_3> p;
+
+  event_1 e_1;
+  event_2 e_2;
+  event_3 e_3;
+
+  p.run(e_1);
+  p.run(e_2);
+  p.run(e_3);
+
+  return 0;
+}
+
+}; // namespace test7
+
+namespace test8 {
+
+struct a {
+  void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct b {
+  void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct c {
+  void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct wrap_hull
+{
+  virtual void do_cool_stuff(void) = 0;
+};
+
+template<typename ... Args>
+struct wrap
+  : public wrap_hull
+  , public Args ...
+{
+  void do_cool_stuff(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+template<typename ... Args>
+struct test : public Args ...
+{
+  void trigger(void)
+  {
+    run<Args ...>();
+  }
+
+  template<typename Arg, typename Next, typename ... Rest>
+  void run(void)
+  {
+    run<Arg>();
+    run<Next, Rest ...>();
+  }
+
+  template<typename Arg>
+  void run(void)
+  {
+    Arg()();
+  }
+
+  template<typename Arg, typename Next, typename ... Rest>
+  void
+  insert(wrap<Arg, Next, Rest ...> * w)
+  {
+    insert<Arg, Next, Rest ...>(
+        reinterpret_cast<wrap<Arg> *>(w),
+        reinterpret_cast<wrap<Next, Rest> *>(w) ...);
+  }
+
+  template<typename Arg, typename Next, typename ... Rest>
+  void
+  insert(wrap<Arg> * w, wrap<Next, Rest ...> * ws ...)
+  {
+    insert<Arg>(w);
+    insert<Next, Rest ...>(
+        reinterpret_cast<wrap<Next> *>(w),
+        reinterpret_cast<wrap<Rest> *>(w) ...);
+  }
+
+  template<typename Arg>
+  void
+  insert(wrap_hull * wh)
+  {
+    m_wraps.push_back(wh);
+  }
+
+  void call_objects(void)
+  {
+    for (auto * wh : m_wraps) {
+      wh->do_cool_stuff();
+    }
+  }
+
+  std::vector<wrap_hull *> m_wraps;
+};
+
+int main(int argc, char ** argv)
+{
+  struct test<a, b, c> t;
+  t.trigger();
+
+  struct wrap<a, b, c> w;
+  t.insert(&w);
+
+  t.call_objects();
+
+  return 0;
+}
+
+}; // namespace test8
+
+int main(int argc, char ** argv)
+{
+  // return test1::main(argc, argv);
+  // return test2::main(argc, argv);
+  // return test3::main(argc, argv);
+  // return test4::main(argc, argv);
+  // return test5::main(argc, argv);
+  // return test6::main(argc, argv);
+  // return test7::main(argc, argv);
+  return test8::main(argc, argv);
+}
diff --git a/lib/xpp/src/tests/template.cpp b/lib/xpp/src/tests/template.cpp
new file mode 100644 (file)
index 0000000..a173791
--- /dev/null
@@ -0,0 +1,101 @@
+// compile with `g++ -std=c++11 test.cpp`
+#include <iostream>
+
+#define CALLABLE(FUNCTION) callable<decltype(FUNCTION), FUNCTION>
+
+template<typename Signature, Signature & S>
+struct callable;
+
+template<typename Return,
+         typename ... Args, Return (&Function)(Args ...)>
+struct callable<Return(Args ...), Function> {
+  Return operator()(Args ... args)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    return Function(args ...);
+  }
+};
+
+template<typename ... Arguments>
+class one_size_fits_them_all;
+
+// A generic template
+template<typename T, typename U, typename V,
+         typename F1, typename F2, typename F3>
+class one_size_fits_them_all<T, U, V, F1, F2, F3>
+{
+  public:
+    one_size_fits_them_all(void)
+    {
+      std::cerr << "generic one_size_fits_them_all" << std::endl
+                << __PRETTY_FUNCTION__ << std::endl << std::endl;
+      F1()();
+      F2()();
+      F3()();
+      std::cerr << std::endl;
+    }
+};
+
+// A specialized template
+template<typename T, typename Callable>
+class one_size_fits_them_all<T, int, int, void, void, Callable>
+{
+  public:
+    one_size_fits_them_all(void)
+    {
+      std::cerr << "specialized one_size_fits_them_all" << std::endl
+                << __PRETTY_FUNCTION__ << std::endl << std::endl;
+      Callable()();
+      std::cerr << std::endl;
+    }
+};
+
+void f1(void)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f2(void)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+void f3(void)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl << std::endl;
+}
+
+template<typename T>
+struct interface {
+  static std::size_t size_of(void) { return sizeof(T); }
+};
+
+template<typename T>
+struct test : public interface<T> {
+  test(void)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << " size_of(): " << this->size_of() << std::endl;
+  }
+};
+
+int main(int argc, char ** argv)
+{
+  // generic template
+  auto generic = one_size_fits_them_all<
+    int, int, int, CALLABLE(f1), CALLABLE(f2), CALLABLE(f3)>();
+
+  // specialized template
+  auto specialized_int = one_size_fits_them_all<
+    int, int, int, void, void, CALLABLE(f1)>();
+
+  // specialized template
+  auto specialized_double = one_size_fits_them_all<
+    double, int, int, void, void, CALLABLE(f3)>();
+
+  test<int> t1;
+  test<double> t2;
+  test<char> t3;
+  test<uint16_t> t4;
+
+  return 0;
+}
diff --git a/lib/xpp/src/tests/test.cpp b/lib/xpp/src/tests/test.cpp
new file mode 100644 (file)
index 0000000..0cb14cb
--- /dev/null
@@ -0,0 +1,1255 @@
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+#include <memory>
+#include <chrono>
+
+#include <list>
+
+template<typename T>
+struct is_callable {
+private:
+    typedef char(&yes)[1];
+    typedef char(&no)[2];
+
+    struct Dummy {};
+    struct Fallback { void operator()(); };
+    // struct Derived : T, Fallback { };
+    struct Derived : std::conditional<! std::is_fundamental<T>::value,
+                                      T,
+                                      Dummy>::type,
+                     Fallback { };
+
+    template<typename U, U> struct Check;
+
+    template<typename>
+    static yes test(...);
+
+    template<typename C>
+    static no test(Check<void (Fallback::*)(), &C::operator()>*);
+
+public:
+    static const bool value = sizeof(test<Derived>(0)) == sizeof(yes);
+};
+
+
+// #include <X11/Xlib.h>
+// #include <X11/keysymdef.h>
+// #include <X11/extensions/Xrandr.h>
+// #include <X11/cursorfont.h> // XC_cross
+
+// #include <xcb/xcbext.h>
+
+// #include "../event.hpp"
+// #include "../core/value_iterator.hpp"
+// #include "../core/connection.hpp"
+
+// template<typename Iterator>
+// void
+// test(Iterator begin, Iterator end)
+// {
+//   std::cerr << "before initializer:";
+//   for (auto it = begin; it != end; ++it) {
+//     std::cerr << " " << *it;
+//   }
+//   std::cerr << std::endl;
+// 
+//   // auto vector = { begin, end };
+//   // std::vector<typename value_trait<Iterator>::value_type> vector(begin, end);
+// 
+//   std::vector<typename value_type<Iterator,
+//                                   ! std::is_pointer<Iterator>::value
+//                                  >::type>
+//                                     vector(begin, end);
+// 
+//   // std::initializer_list<Iterator> vector = { begin, end };
+// 
+//   std::cerr << "after initializer (size: " << vector.size() << "):";
+//   for (auto & v : vector) {
+//     std::cerr << " " << v;
+//   }
+//   std::cerr << std::endl;
+// }
+
+struct foo {
+  static void create(int * c, unsigned int xid)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  }
+
+  static void destroy(int * c, unsigned int xid)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  }
+};
+
+struct bar {
+  static void create(int * c, unsigned int xid, double d)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  }
+  static void destroy(int * c, unsigned int xid)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  }
+};
+
+template<typename Xid, typename ... Parameters>
+struct allocator {
+
+  template<void (*Allocate)(int *, Xid, Parameters ...)>
+  struct allocate
+  {
+    void
+    operator()(int * c, Xid xid, Parameters ... parameters)
+    {
+      Allocate(c, xid, parameters ...);
+    }
+  };
+
+  template<void (*Deallocate)(int *, Xid)>
+  struct deallocate
+  {
+    void
+    operator()(int * c, Xid xid)
+    {
+      Deallocate(c, xid);
+    }
+  };
+
+};
+
+template<typename Xid, typename Allocate = void, typename Deallocate = void>
+struct xid {
+
+  template<typename ... Parameters>
+  xid(Parameters ... parameters)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    m_xid = std::shared_ptr<Xid>(new Xid(0), // xcb_generate_id(c)),
+                                 [&](Xid * xid)
+                                 {
+                                   Deallocate()(NULL, *xid);
+                                   delete xid;
+                                 });
+    Allocate()(NULL, 0, parameters ...);
+  }
+
+  std::shared_ptr<Xid> m_xid;
+};
+
+template<typename Xid>
+struct xid<Xid, void, void>
+{
+  Xid xid;
+};
+
+template<typename Signature1, Signature1 & S1, typename Signature2, Signature2 & S2>
+struct wrapper;
+
+template<typename Return1,
+         typename ... Args1,
+         Return1(&F1)(Args1 ...),
+         typename Return2,
+         typename ... Args2,
+         Return2(&F2)(Args2 ...)>
+struct wrapper<Return1(Args1 ...), F1, Return2(Args2 ...), F2> {
+  static
+  void
+  allocate(Args1 ... args)
+  {
+    F1(args ...);
+  }
+  static
+  void
+  deallocate(Args2 ... args)
+  {
+    F2(args ...);
+  }
+};
+
+struct caller {
+  template<typename Callee>
+  void operator()(const Callee & callee, int i)
+  {
+    switch (i) {
+      case 0: callee(42);
+        break;
+      case 1: callee(2.73);
+        break;
+      case 2: callee(std::string("foo"));
+        break;
+    };
+  }
+};
+
+struct callee {
+  void operator()(int i) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  template<typename Arg>
+  void operator()(const Arg &) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+namespace mylib {
+
+namespace request {
+  enum { checked, unchecked };
+};
+
+namespace extension {
+
+using mylib::request::checked;
+using mylib::request::unchecked;
+
+namespace request {
+template<int RequestType = unchecked>
+struct void_request {
+  void operator()(void) const noexcept(RequestType == unchecked)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  };
+};
+};
+
+// template<>
+// struct void_request<checked> {
+//   void operator()(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; };
+// };
+
+namespace request {
+template<int RequestType = checked>
+struct reply_request {
+  void operator()(void) const noexcept(RequestType == unchecked)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << std::endl;
+  };
+};
+};
+
+// template<>
+// struct reply_request<unchecked> {
+//   void operator()(void) const { std::cerr << __PRETTY_FUNCTION__ << std::endl; };
+// };
+
+struct interface {
+  template<int RequestType = unchecked>
+  void void_request(void) const { request::void_request<RequestType>()(); }
+  template<int RequestType = checked>
+  void reply_request(void) const { request::reply_request<RequestType>()(); }
+};
+
+}; // extension
+
+struct c : public extension::interface
+{};
+
+}; // mylib
+
+namespace test00 {
+
+enum { checked, unchecked };
+
+template<std::size_t check = unchecked>
+struct template_struct {
+  template<typename Connection, typename ... Parameter>
+  template_struct(Connection c, Parameter ... parameter) {}
+};
+
+template_struct<> ts(nullptr);
+
+template<std::size_t check = unchecked, typename Connection, typename ... Parameter>
+void
+template_function(Connection c, Parameter ... parameter)
+{
+  if (check == checked) {
+    std::cerr << "if (check) { // check == checked" << std::endl;
+  } else {
+    std::cerr << "} else { // check == unchecked" << std::endl;
+  }
+}
+
+struct an_interface {
+  template<typename std::size_t Check = unchecked, typename ... Parameter>
+  void
+  template_function(Parameter ... parameter)
+  {
+    test00::template_function<Check>(m_c, parameter ...);
+  }
+  double m_c;
+};
+
+void test(void)
+{
+//   two t;
+//   // t.do_it_void();
+//   // t.do_it_void().unchecked();
+//   auto reply_1 = t.do_it_reply();
+//   auto reply_2 = t.do_it_reply().unchecked();
+//   // reply.checked();
+//   // auto reply = t.do_it_reply().checked();
+
+  // template_function(t);
+  // template_function(t, reply_1, reply_2);
+  // template_function<checked>(reply_1, reply_2);
+  // template_function<checked>(reply_2);
+
+  an_interface i;
+  i.template_function(0);
+  i.template_function<checked>(0);
+}
+
+};
+
+namespace test01 {
+
+struct a {
+  a(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  a(const std::string & string) : m_string(string) {}
+  a(const a & other) : m_string(other.m_string) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  a(a && other) : m_string(std::move(other.m_string)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  ~a(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  std::string m_string = "struct a";
+};
+
+struct b {
+  // b(const a & a) : m_a(a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  // template<typename A>
+  // b(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  // b(a & a) : m_a(a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  // b(a && a) : m_a(std::move(a)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  b(a && a) : m_a(std::move(a)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  b(const a & a) : m_a(a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+
+  // b(const b & other) : m_a(other.m_a) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+  b(b && other) : m_a(std::move(other.m_a)) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+
+  ~b(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+
+  // const a & m_a;
+  a m_a;
+};
+
+b
+// foo(const a & a)
+foo(const a & a)
+{
+  // return b(std::move(a));
+  return b(a);
+}
+
+void test(void)
+{
+  // b bb = foo(a());
+  // b bb = foo(std::move(a()));
+  a aa("a string on the stack");
+  // b b1 = foo(aa);
+  std::cerr << "(before move) aa.m_string: " <<  aa.m_string << std::endl;
+  b b2 = foo(std::move(aa));
+  std::cerr << "(after move) aa.m_string: " <<  aa.m_string << std::endl;
+  // std::cerr << "b1.m_a.m_string: " <<  b1.m_a.m_string << std::endl;
+  std::cerr << "b2.m_a.m_string: " <<  b2.m_a.m_string << std::endl;
+}
+
+};
+
+namespace test02 {
+
+struct a {
+  static void foo(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct b : a {
+  static void foo(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct c : b {
+  static void foo(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+// struct d : a, b {
+//   static void foo(void)
+//   {
+//     std::cerr << __PRETTY_FUNCTION__ << std::endl;
+//     a::foo();
+//     b::foo();
+//   }
+// };
+
+void test(void)
+{
+  a::foo();
+  b::foo();
+  c::foo();
+  // d::foo();
+}
+
+};
+
+namespace test03 {
+
+struct connection {};
+
+struct baz {
+  int member;
+};
+
+struct fro {
+  // fro(const int & i)
+  // {
+  //   std::cerr << __PRETTY_FUNCTION__ << ": " << i << std::endl;
+  // }
+
+  fro(const int & i, connection *)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << ": " << i << std::endl;
+  }
+};
+
+struct foo {
+  foo(const int & i, double d)
+  {
+    std::cerr << __PRETTY_FUNCTION__ << ": " << i << "; d: " << d << std::endl;
+  }
+
+  // foo(int i, connection *, double d)
+  // {
+  //   std::cerr << __PRETTY_FUNCTION__ << ": " << i << "; d: " << d << std::endl;
+  // }
+};
+
+namespace reply_member {
+
+template<typename ReturnType>
+class get_with_object {
+  public:
+    template<typename Arg, typename ... Parameter>
+    ReturnType
+    operator()(Arg & a, connection *, Parameter ... parameter)
+    {
+      return ReturnType { a, parameter ... };
+    }
+};
+
+template<typename ReturnType>
+class get_with_object_and_connection {
+  public:
+    template<typename Arg, typename ... Parameter>
+    ReturnType
+    operator()(const Arg & a, connection * i, Parameter ... parameter)
+    {
+      return ReturnType { a, i, parameter ... };
+    }
+};
+
+template<typename ReturnType>
+class get_fundamental {
+  public:
+    template<typename Arg>
+    ReturnType
+    operator()(const Arg & arg, connection *)
+    {
+      return ReturnType { arg };
+    }
+};
+
+template<typename MemberType, typename ReturnType, typename ... Parameter>
+class get
+  : public std::conditional<
+               std::is_constructible<ReturnType, MemberType>::value,
+               get_fundamental<ReturnType>,
+               typename std::conditional<
+                            std::is_constructible<ReturnType,
+                                                  MemberType,
+                                                  connection *,
+                                                  Parameter ...>::value,
+                            get_with_object_and_connection<ReturnType>,
+                            get_with_object<ReturnType>
+                        >::type
+           >::type
+{};
+
+};
+
+struct c {
+  template<typename ReturnType = int, typename ... Parameter>
+  ReturnType
+  c_get(Parameter ... parameter)
+  {
+    using get = reply_member::get<decltype(b.member), ReturnType, Parameter ...>;
+    return get()(b.member, i, parameter ...);
+  }
+
+  baz b;
+  connection * i;
+};
+
+void
+test(void)
+{
+  c cc;
+  cc.b.member = 42;
+
+  auto m_int = cc.c_get();
+  std::cerr << "m: " << m_int << std::endl;
+
+  // auto m_fro_i = cc.c_get<fro>();
+  // auto m_foo_d = cc.c_get<foo>(3.14);
+}
+
+}; // test03
+
+namespace test04 {
+
+struct a {
+  a(const std::string & s) : m_s(s) {}
+  void operator()(const std::string & s) const
+  {
+    std::cerr << "a: " << s << "; m_s: " << m_s << std::endl;
+  }
+  std::string m_s;
+};
+
+struct b {
+  void operator()(const std::string & s) const
+  {
+    std::cerr << "b: " << s << std::endl;
+  }
+};
+
+struct c : a, b {
+  c(void) : a("c : a") {}
+  // void operator() (const std::string & s) { a::operator()(s); b::operator()(s); }
+};
+
+struct d {
+  void
+  operator()(const std::string & s) const
+  {
+    std::cerr << "d: " << s << std::endl;
+  }
+};
+
+template<typename Base, typename Arg>
+void
+check(const Base & base, const Arg & arg)
+{
+  base(arg);
+}
+
+template<typename Base, typename Arg, typename E>
+void
+check(const Base & base, const Arg & arg)
+{
+  static_cast<const E &>(base)(arg);
+}
+
+template<typename Base, typename Arg, typename E, typename Next, typename ... Rest>
+void
+check(const Base & base, const Arg & arg)
+{
+  check<Base, Arg, E>(base, arg);
+  check<Base, Arg, Next, Rest ...>(base, arg);
+}
+
+template<typename E, typename ... ES>
+struct foo_base {
+  foo_base(const E & e)
+    : m_e(e)
+  {}
+
+  E m_e;
+
+  void
+  check_error(const std::string & s)
+  {
+    check<E, std::string, ES ...>(m_e, s);
+  }
+};
+
+template<>
+struct foo_base<void> {
+  foo_base(void)
+  {}
+
+  void
+  check_error(const std::string & s)
+  {
+    std::cerr << "foo_base<void>" << std::endl;
+  }
+};
+
+template<typename E = void, typename ... ES>
+struct foo
+  : public foo_base<E, ES ...>
+{
+  typedef foo_base<E, ES ...> base;
+
+  using base::foo_base;
+
+  template<typename ... P2>
+  void
+  operator()(const std::string & s, P2 ... p2)
+  {
+    base::check_error(s);
+  }
+
+}; // struct foo;
+
+template<typename T>
+using decay = typename std::decay<T>::type;
+
+template<typename Condition, typename T = void>
+using enable_if =
+  typename std::enable_if<Condition::value, T>::type;
+
+template<typename Condition, typename T = void>
+using disable_if =
+  typename std::enable_if<! Condition::value, T>::type;
+
+// template<typename P, typename ... PS>
+// typename std::enable_if<is_callable<decay<P>>::value, void>::type
+// foo_function_dispatch(const std::string & s, P && p, PS ... ps)
+// {
+//   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+//   foo_function(p, s, ps ...);
+// }
+
+// template<typename P, typename ... PS>
+// typename std::enable_if<! is_callable<decay<P>>::value, void>::type
+// foo_function_dispatch(const std::string & s, P && p, PS ... ps)
+// {
+//   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+//   foo_function(s, p, ps ...);
+// }
+
+template<typename Parameter,
+         typename ... Parameters,
+         typename = disable_if<is_callable<decay<Parameter>>>>
+void
+foo_function(const std::string & s, Parameter && p, Parameters ... ps)
+{
+  foo<>()(s, ps ...);
+}
+
+template<typename ... ErrorHandlers,
+         typename ErrorHandler,
+         typename ... Parameters,
+         typename = enable_if<is_callable<decay<ErrorHandler>>>>
+void
+foo_function(const std::string & s, ErrorHandler && e, Parameters ... ps)
+{
+  (foo<ErrorHandler, ErrorHandlers ...>(e))(s, ps ...);
+}
+
+struct foo_iface {
+
+  foo_iface(void) {}
+  foo_iface(const std::string & s) : m_s(s) {}
+
+  template<typename ... ErrorHandlers, typename ... Parameters>
+  void
+  foo_method(Parameters ... ps)
+  {
+    foo_function<ErrorHandlers ...>(m_s, ps ...);
+  }
+
+  // template<typename P,
+  //          typename ... PS,
+  //          typename = disable_if<is_callable<decay<P>>>>
+  // void
+  // foo_method(P && p, PS ... ps)
+  // {
+  //   foo_function(m_s, p, ps ...);
+  // }
+
+  // template<typename ... ErrorHandlers,
+  //          typename E,
+  //          typename ... PS,
+  //          typename = enable_if<is_callable<decay<E>>>>
+  // void
+  // foo_method(E && e, PS ... ps)
+  // {
+  //   foo_function<ErrorHandlers ...>(m_s, e, ps ...);
+  // }
+
+  std::string m_s = "foo_iface";
+};
+
+// template<typename ... ES, typename E, typename ... PS, typename P>
+// void
+// foo_with_error(const std::string & s, PS ... ps, const E & e)
+// {
+//   (foo<E, ES ...>(e))(s, ps ...);
+//   // foo<std::function<void(const std::string &)>> f([](const std::string & s) { std::cerr << "lambda: " << s << std::endl; });
+//   // f(s, ps ...);
+// }
+
+struct e {
+  int i;
+};
+
+void
+test(void)
+{
+  // d dd;
+  // (foo<d>(dd))("aaa 42 aaa", 5,6,7);
+  // ((foo<c, a, b>(c())))("ccc 42 ccc", 5,6,7);
+
+  foo_function("hhh 42 hhh", a("abcde"), 5,6,7);
+  foo_function("jjj 42 jjj", 5,6,7);
+  foo_function("kkk 42 kkk", [](const std::string & s){ std::cerr << "lambda: " << s << std::endl; }, 5,6,7);
+
+  foo_iface fi;
+  fi.foo_method(5,6,7);
+  fi.foo_method(b(), 5,6,7);
+  c cc;
+  fi.foo_method<a,b>(cc, 5,6,7);
+
+  std::cerr << std::boolalpha << std::endl
+            // << "has_member_operator(): " << has_member_operator<b>::value << std::endl
+            << "is_callable: " << is_callable<b>::value << std::endl
+            << "is_trivial: " << std::is_trivial<b>::value << std::endl
+            << "is_pod: " << std::is_pod<b>::value << std::endl
+            << "is_standard_layout: " << std::is_standard_layout<b>::value << std::endl
+            << "is_fundamental: " << std::is_fundamental<b>::value << std::endl
+            << "is_class: " << std::is_class<b>::value << std::endl
+            << "is_object: " << std::is_object<b>::value << std::endl
+            << std::endl;
+
+  std::cerr << std::boolalpha << std::endl
+            << "is_callable: " << is_callable<e>::value << std::endl
+            << "is_trivial: " << std::is_trivial<e>::value << std::endl
+            << "is_pod: " << std::is_pod<e>::value << std::endl
+            << "is_standard_layout: " << std::is_standard_layout<e>::value << std::endl
+            << "is_fundamental: " << std::is_fundamental<e>::value << std::endl
+            << "is_class: " << std::is_class<e>::value << std::endl
+            << "is_object: " << std::is_object<e>::value << std::endl
+            << std::endl;
+
+  std::cerr << std::boolalpha << std::endl
+            << "is_callable: " << is_callable<int>::value << std::endl
+            << std::endl;
+
+  // foo<b>(b(1))("bbb 42 bbb", 5,6,7);
+  // (foo<b>(b()))("bbb 42 bbb", 5,6,7);
+  // foo<a, b>(c())("ccc 42 ccc", 5,6,7);
+  // foo<>()("ddd 42 ddd", 5,6,7);
+  // foo<a>()(a("a2"), "eee 42 eee", 5,6,7);
+  // (foo<a>(a("a2")))("eee 42 eee", 5,6,7);
+}
+
+}; // test04
+
+namespace test05 {
+
+template<typename T>
+struct a {
+  struct member {
+    struct get {
+      void operator()(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+    };
+  };
+};
+
+struct b : a<int> {
+  void
+  fun(void)
+  {
+    using get = member::get;
+    get()();
+  }
+};
+
+void
+test(void)
+{
+  b bb;
+  bb.fun();
+}
+
+}; // test05
+
+namespace test06 {
+
+void foo(int i)
+{
+  std::cerr << __PRETTY_FUNCTION__ << std::endl;
+}
+
+auto &bar = foo;
+
+struct a {
+  virtual void foo(void) = 0;
+};
+
+struct b : a {
+  using a::foo;
+  void bar(void) { foo(); }
+  // decltype(a::foo) & bar = a::foo;
+};
+
+void test(void)
+{
+  foo(1);
+  bar(2);
+}
+
+}; // test05
+
+namespace test07 {
+
+class a {
+  static const char * foo ;
+  static const std::string bar ;
+  static constexpr const char * baz = "baz";
+};
+const char * a::foo = "foo";
+const std::string a::bar = "bar";
+
+void
+test(void)
+{
+  // a aa;
+}
+
+}; // test05
+
+namespace test08 {
+
+template<typename T>
+struct print {
+  template<typename X>
+  std::ostream &
+  operator()(std::ostream & os, X && t)
+  {
+    return os << "forward: " << std::forward<T>(t);
+  }
+};
+
+template<typename T>
+struct print<T *> {
+  std::ostream &
+  operator()(std::ostream & os, T * t)
+  {
+    return os << "pointer: " << *t;
+  }
+};
+
+template<typename T>
+struct a {
+  explicit a(T && t)
+    : m_t(std::forward<T>(t))
+  {
+    std::ostream & os = std::cerr;
+    os << __PRETTY_FUNCTION__ << " m_t: ";
+    print<T>()(os, m_t);
+    os << std::endl;
+  }
+
+  explicit a(const T & t)
+    : m_t(t)
+  {
+    std::ostream & os = std::cerr;
+    os << __PRETTY_FUNCTION__ << " m_t: ";
+    print<T>()(os, m_t);
+    os << std::endl;
+  }
+
+  ~a(void)
+  {
+    std::ostream & os = std::cerr;
+    os << __PRETTY_FUNCTION__ << " m_t: ";
+    print<T>()(os, m_t);
+    os << std::endl;
+  }
+
+  T m_t;
+};
+
+void
+test(void)
+{
+  int i = 0;
+  double d = 0.0;
+  a<int> ai(i);
+  a<double> ad(d);
+  a<int> aim(std::move(i));
+  a<double> adm(std::move(d));
+  a<int *> aip(&i);
+  a<double *> adp(&d);
+}
+
+}; // test05
+
+namespace test09 {
+
+template<typename T, typename U>
+struct a {
+  explicit a(const T & t, const U & u)
+    : m_t(t)
+    , m_u(u)
+  {}
+  explicit a(const T & t)
+    : m_t(t)
+  {}
+  void operator=(const U & u) { m_u = u; }
+  void set(const U & u) { m_u = u; }
+  T m_t;
+  U m_u;
+};
+
+int t = 0;
+
+a<int, int>
+get_by_value(const int & u)
+{
+  return a<int, int>{ t, u };
+}
+
+a<int, int> reference_return_a(t);
+
+const a<int, int> &
+get_by_reference(const int & u)
+{
+  reference_return_a = u;
+  // reference_return_a.set(u);
+  return reference_return_a;
+}
+
+a<int, int> &&
+get_by_move(const int & u)
+{
+  return std::move(a<int, int>{t, u});
+  // return a<int, int>{t, u};
+}
+
+void
+test(void)
+{
+  int len = 1000000;
+  std::chrono::time_point<std::chrono::high_resolution_clock> start;
+  std::chrono::time_point<std::chrono::high_resolution_clock> stop;
+
+  std::cerr << "get_by_value:" << std::endl;
+  start = std::chrono::high_resolution_clock::now();
+  for (int i = 0; i < len; ++i) {
+    a<int, int> aa = get_by_value(i);
+    std::cerr << "\r" << aa.m_u;
+  }
+  stop = std::chrono::high_resolution_clock::now();
+  std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+  std::cerr << "get_by_value_rvalue:" << std::endl;
+  start = std::chrono::high_resolution_clock::now();
+  for (int i = 0; i < len; ++i) {
+    a<int, int> && aa = get_by_value(i);
+    std::cerr << "\r" << aa.m_u;
+  }
+  stop = std::chrono::high_resolution_clock::now();
+  std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+  std::cerr << "get_by_reference:" << std::endl;
+  start = std::chrono::high_resolution_clock::now();
+  for (int i = 0; i < len; ++i) {
+    const a<int, int> & aa = get_by_reference(i);
+    std::cerr << "\r" << aa.m_u;
+  }
+  stop = std::chrono::high_resolution_clock::now();
+  std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+  // invalid examples
+  /*
+  std::cerr << "get_by_move_value:" << std::endl;
+  start = std::chrono::high_resolution_clock::now();
+  for (int i = 0; i < len; ++i) {
+    a<int, int> aa = get_by_move(i);
+    std::cerr << "\r" << aa.m_u;
+  }
+  stop = std::chrono::high_resolution_clock::now();
+  std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+
+  std::cerr << "get_by_move_rvalue:" << std::endl;
+  start = std::chrono::high_resolution_clock::now();
+  for (int i = 0; i < len; ++i) {
+    a<int, int> && aa = get_by_move(i);
+    std::cerr << "\r" << aa.m_u;
+  }
+  stop = std::chrono::high_resolution_clock::now();
+  std::cerr << std::endl << "duration: " << (stop - start).count() << std::endl;
+  */
+}
+
+}; // test05
+
+namespace test10 {
+
+class a {
+  public:
+    a(const unsigned int & i) : m_int(std::make_shared<unsigned int>(i)) {}
+    operator unsigned int &(void)
+    {
+      return *m_int;
+    }
+    std::shared_ptr<unsigned int> m_int;
+};
+
+void
+test(void)
+{
+  a aa { 0 };
+  std::cerr << *aa.m_int << std::endl;
+  static_cast<unsigned int &>(aa) = 42;
+  std::cerr << *aa.m_int << std::endl;
+}
+
+}; // test05
+
+namespace test11 {
+
+template<typename T>
+class a {
+  public:
+    a(const T & i) {}
+    a(const T & i, const unsigned int &) {}
+};
+
+void
+test(void)
+{
+  std::cerr << std::boolalpha;
+  // std::cerr << std::is_constructible<a, const unsigned int &>::value << std::endl;
+  // std::cerr << std::is_constructible<a, unsigned int &>::value << std::endl;
+  // std::cerr << std::is_constructible<a, unsigned int>::value << std::endl;
+  std::cerr << std::is_constructible<a<unsigned int>, const unsigned int &>::value << std::endl;
+  std::cerr << std::is_constructible<a<unsigned int>, unsigned int &>::value << std::endl;
+  std::cerr << std::is_constructible<a<unsigned int>, unsigned int>::value << std::endl;
+}
+
+}; // test05
+
+namespace test12 {
+
+void
+test(void)
+{
+  char a[2*sizeof(int)];
+  uint * b = reinterpret_cast<uint *>(a);
+  b[0] = (uint)'a' + ((uint)'b' << 8) + ((uint)'c' << 16) + ((uint)'d' << 24);
+  b[1] = (uint)'e' + ((uint)'f' << 8) + ((uint)'g' << 16) + ((uint)'h' << 24);
+  std::cerr << b[0] << ": " << a[0] << " " << a[1] << " " << a[2] << " " << a[3] << std::endl;
+  std::cerr << b[1] << ": " << a[4] << " " << a[5] << " " << a[6] << " " << a[7] << std::endl;
+}
+
+}; // test05
+
+namespace test13 {
+
+template<typename ... Args>
+struct a {
+};
+
+template<typename ... Types> struct b;
+
+// template<template<typename ...> class T, typename ... Args>
+// template<template<typename ... Args> class T> // , typename ... Args>
+// template<typename ... Args>
+// template<template<typename T<typename ... Args>>>
+
+template<typename ... Args, template<typename ...> class T>
+struct b<T<Args ...>> {
+  b(const T<Args ...> & t)
+    : m_t(t)
+  {}
+  T<Args ...> m_t;
+};
+
+template<typename ... Args, template<typename ...> class T>
+struct b<T<Args ...> &>
+  : public b<T<Args ...>>
+{
+  using base = b<T<Args ...>>;
+  using base::base;
+};
+
+void
+test(void)
+{
+  a<int> aa_1;
+  b<a<int>> bb_1(aa_1);
+  b<a<int> &> bb_2(aa_1);
+}
+
+}; // test05
+
+namespace test15 {
+
+struct i {
+  virtual void fun(void) = 0;
+};
+
+struct a : i {
+  void fun(void) { std::cerr << __PRETTY_FUNCTION__ << std::endl; }
+};
+
+struct b {
+};
+
+template<typename I>
+struct with_i : I
+{
+  virtual
+  void run(void)
+  {
+    std::cerr << "with_i" << std::endl;
+    static_cast<i *>(this)->fun();
+  }
+};
+
+struct without_i
+{
+  virtual
+  void run(void) { std::cerr << "without_i" << std::endl; }
+};
+
+template<typename T>
+struct i_test
+  : std::conditional<std::is_base_of<i, T>::value, with_i<T>, without_i>::type
+{};
+
+void
+test(void)
+{
+  i_test<a> a_test;
+  a_test.run();
+  i_test<b> b_test;
+  b_test.run();
+}
+
+}; // test05
+
+int main(int argc, char ** argv)
+{
+  // if (argc != 2) {
+  //   std::cerr << "Need one string argument" << std::endl;
+  //   return 1;
+  // }
+
+  // xid<xcb_window_t> xid_1;
+
+  // xid<xcb_window_t,
+  //     allocator<xcb_window_t>::allocate<&foo::create>,
+  //     allocator<xcb_window_t>::deallocate<&foo::destroy>>
+  //       xid_2;
+
+  // xid<xcb_window_t,
+  //     allocator<xcb_window_t, double>::allocate<&bar::create>,
+  //     allocator<xcb_window_t>::deallocate<&bar::destroy>>
+  //       xid_3(0.0);
+
+  // wrapper<decltype(foo::create), foo::create,
+  //         decltype(foo::destroy), foo::destroy>::allocate(NULL, 0);
+
+  // wrapper<decltype(foo::create), foo::create,
+  //         decltype(foo::destroy), foo::destroy>::deallocate(NULL, 0);
+
+  // w_1.allocate(NULL, 0);
+  // w_1.deallocate(NULL, 0);
+
+  // // xpp::resource class!
+  // // >>>>>>>>><<<<<<<<<<<
+  // wrapper<decltype(bar::create), bar::create,
+  //         decltype(bar::destroy), bar::destroy>::allocate(NULL, 0, 0.0);
+
+  // wrapper<decltype(bar::create), bar::create,
+  //         decltype(bar::destroy), bar::destroy>::deallocate(NULL, 0);
+
+  // wrapper<decltype(bar::create), bar::create,
+  //         decltype(bar::destroy), bar::destroy> w_2;
+  // w_2.allocate(NULL, 0, 0.0);
+  // w_2.deallocate(NULL, 0);
+
+  // wrapper<decltype(bar::create), bar::create> w_2;
+
+  // xpp::connection<> connection("");
+
+  // std::map<int, char> char_map;
+  // char_map[0] = 'a';
+  // char_map[1] = 'b';
+  // char_map[2] = 'c';
+
+  // value_iterator<std::map<int, char>::iterator> begin =
+  //   value_iterator<std::map<int, char>::iterator>(char_map.begin());
+  // value_iterator<std::map<int, char>::iterator> end =
+  //   value_iterator<std::map<int, char>::iterator>(char_map.end());
+
+  // std::vector<char> chars_1 = std::vector<char>(begin, end); std::vector<char> chars_2 = { begin, end };
+  // const char * legacy_chars = "abc";
+  // std::vector<char> chars_3 = { legacy_chars, legacy_chars + 3 };
+
+  // // for (auto it = begin; it != end; ++it) {
+  // for (auto & c : chars_1) {
+  //   std::cerr << "value: " << c << std::endl;
+  // }
+
+  // value_iterator<std::vector<char>::iterator> v_begin =
+  //   value_iterator<std::vector<char>::iterator>(chars_1.begin());
+  // value_iterator<std::vector<char>::iterator> v_end =
+  //   value_iterator<std::vector<char>::iterator>(chars_1.end());
+
+  // std::vector<char> vchar_1(v_begin, v_end);
+  // std::vector<char> vchar_2 = { v_begin, v_end };
+
+  // std::string my_string(argv[1]);
+  // std::cerr << "test 1" << std::endl;
+  // test(my_string.begin(), my_string.end());
+  // std::cerr << "test 2" << std::endl;
+  // test(my_string.c_str(), my_string.c_str() + my_string.length());
+
+  // auto atom = connection.intern_atom(false, "MY_STRING");
+
+  // xpp::request::x::change_property(connection, XCB_PROP_MODE_REPLACE,
+  //     connection.root(), atom->atom, XCB_ATOM_STRING, 8, my_string.length(),
+  //     my_string.c_str());
+
+  // connection.change_property(XCB_PROP_MODE_REPLACE, connection.root(),
+  //     atom->atom, XCB_ATOM_STRING, 8,
+  //     begin, end);
+
+  // connection.change_property(XCB_PROP_MODE_REPLACE, connection.root(),
+  //     atom->atom, XCB_ATOM_STRING, 8,
+  //     v_begin, v_end);
+
+  // connection.flush();
+
+  // caller()(callee(), 0);
+  // caller()(callee(), 1);
+  // caller()(callee(), 2);
+
+  // using mylib::request::checked;
+  // using mylib::request::unchecked;
+
+  // mylib::extension::request::void_request<>()();
+  // mylib::extension::request::void_request<checked>()();
+
+  // mylib::c c;
+  // c.void_request<>();
+  // c.reply_request<>();
+
+  // test00::test();
+  // test01::test();
+  // test02::test();
+  // test03::test();
+  // test04::test();
+  // test05::test();
+  // test06::test();
+  // test07::test();
+  // test08::test();
+  // test09::test();
+  // test10::test();
+  // test11::test();
+  // test12::test();
+  // test13::test();
+  // test14::test();
+  test15::test();
+
+  return 0;
+}
diff --git a/lib/xpp/src/tests/xlib-test.cpp b/lib/xpp/src/tests/xlib-test.cpp
new file mode 100644 (file)
index 0000000..77b07c5
--- /dev/null
@@ -0,0 +1,33 @@
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+int main(int argc, char ** argv)
+{
+  Display * dpy = XOpenDisplay(NULL);
+  Window root = DefaultRootWindow(dpy);
+
+  XRRScreenConfiguration * screen_cfg = XRRGetScreenInfo(dpy, root);
+  std::cerr << "rate: " << XRRConfigCurrentRate(screen_cfg) << std::endl;
+
+  for (int nsizes = 0; nsizes < 16; ++nsizes) {
+    int nrates = 0;
+    short * rates = XRRConfigRates(screen_cfg, nsizes, &nrates);
+    std::cerr << "nrates: " << nrates << std::endl;
+    for (int i = 0; i < nrates; ++i) {
+      std::cerr << "rate: " << rates[i] << std::endl;
+    }
+  }
+
+  int nhosts = 0;
+  int state = 0;
+  XHostAddress * host_addresses = XListHosts(dpy, &nhosts, &state);
+  for (int i = 0; i < nhosts; ++i) {
+    std::cerr << "address: " << host_addresses[i].address << std::endl;
+  }
+
+  return 0;
+}
diff --git a/lib/xpp/src/tests/xlib.cpp b/lib/xpp/src/tests/xlib.cpp
new file mode 100644 (file)
index 0000000..2039c5f
--- /dev/null
@@ -0,0 +1,30 @@
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xrandr.h>
+
+int main(int argc, char ** argv)
+{
+  Display * dpy = XOpenDisplay(NULL);
+  Window root = DefaultRootWindow(dpy);
+
+  XRRScreenConfiguration * screen_cfg = XRRGetScreenInfo(dpy, root);
+  std::cerr << "rate: " << XRRConfigCurrentRate(screen_cfg) << std::endl;
+
+  int nrates = 0;
+  short * rates = XRRConfigRates(screen_cfg, 10, &nrates);
+  std::cerr << "nrates: " << nrates << std::endl;
+  for (int i = 0; i < nrates; ++i) {
+    std::cerr << "rate: " << rates[i] << std::endl;
+  }
+
+  int nhosts = 0;
+  XHostAddress * host_addresses = XListHosts(dpy, &nhosts, True);
+  for (int i = 0; i < nhosts; ++i) {
+    std::cerr << "address: " << host_addresses[i].address << std::endl;
+  }
+
+  return EXIT_SUCCESS;
+}
diff --git a/lib/xpp/src/tests/xproto.cpp b/lib/xpp/src/tests/xproto.cpp
new file mode 100644 (file)
index 0000000..cb835d4
--- /dev/null
@@ -0,0 +1,870 @@
+#include <climits>
+#include <unistd.h>
+#include <iostream>
+
+#include <X11/Xlib.h>
+#include <X11/keysymdef.h>
+#include <X11/extensions/Xrandr.h>
+#include <X11/cursorfont.h> // XC_cross
+
+// #include <xcb/randr.h>
+// // #include "../gen/xproto_requests_with_accessors.hpp"
+// #include "../gen/randr_requests.hpp"
+// #include "../gen/xproto.hpp"
+// #include "../gen/randr.hpp"
+
+// #include "../xpp.hpp"
+
+#include "../event.hpp"
+#include "../core/connection.hpp"
+// #include "../gen/xproto.hpp"
+// #include "../gen/randr.hpp"
+
+bool g_quit = false;
+
+class key_printer
+  : public xpp::event::sink<xpp::x::event::key_press,
+                            xpp::x::event::key_release,
+                            xpp::x::event::button_press,
+                            xpp::randr::event::notify,
+                            xpp::damage::event::notify,
+                            xpp::xfixes::event::selection_notify,
+                            xpp::screensaver::event::notify
+                            >
+{
+  public:
+    key_printer(const xpp::x::protocol & x)
+      : m_x(x)
+    {}
+
+    void handle(const xpp::x::event::key_press & e)
+    {
+      auto kbd_mapping = m_x.get_keyboard_mapping(e->detail, 1);
+      auto keysym = *kbd_mapping.keysyms().begin();
+
+      if (keysym == XK_Escape) {
+        std::cerr << "quitting" << std::endl;
+        m_x.ungrab_keyboard();
+        g_quit = true;
+      } else {
+        std::cerr << "key pressed: " << XKeysymToString(keysym) << std::endl;
+      }
+    }
+
+    void handle(const xpp::x::event::key_release & e)
+    {
+      auto kbd_mapping = m_x.get_keyboard_mapping(e->detail, 1);
+      auto keysym = *kbd_mapping.keysyms().begin();
+      std::cerr << "key released: " << XKeysymToString(keysym) << std::endl;
+    }
+
+    void handle(const xpp::x::event::button_press & e)
+    {
+      m_x.ungrab_pointer();
+
+      std::cerr << "root: 0x"
+                << std::hex << e->root << std::dec
+                << "; event: 0x"
+                << std::hex << e->event << std::dec
+                << "; child: 0x"
+                << std::hex << e->child << std::dec
+                << std::endl;
+
+      xpp::window grab_window = e.event<xpp::window>();
+      std::cerr << "grab_window: " << grab_window << std::endl;
+
+      if (e->event == e->root) {
+        grab_window = e.child();
+        std::cerr << "new grab_window: " << grab_window << std::endl;
+        auto translate = grab_window.translate_coordinates(grab_window, 1, 1);
+        grab_window = translate->child;
+      }
+
+      std::cerr << "grabbing "
+                << std::hex << grab_window << std::dec
+                << std::endl;
+
+      *m_x.grab_keyboard(true, grab_window,
+                         XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+    }
+
+    void handle(const xpp::randr::event::notify & e)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    }
+
+    void handle(const xpp::damage::event::notify & e)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    }
+
+    void handle(const xpp::xfixes::event::selection_notify & e)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    }
+
+    void handle(const xpp::screensaver::event::notify & e)
+    {
+      std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    }
+
+    // does not work
+    // template<int OpCode, typename Event>
+    // void handle(const xpp::generic::event<OpCode, Event> &)
+    // {
+    //   std::cerr << __PRETTY_FUNCTION__ << std::endl;
+    // }
+
+  private:
+    const xpp::x::protocol & m_x;
+};
+
+class xevent : public xpp::x::event::dispatcher {
+  public:
+
+    template<typename Event>
+    void operator()(const Event &) const
+    {
+    }
+
+    // template<int OpCode, typename Event>
+    // void operator()(const xpp::generic::event<OpCode, Event> &) const
+    // {
+    // }
+
+    void doit(xcb_generic_event_t * const e) const
+    {
+#if not defined __clang__
+      dispatcher::operator()(*this, e);
+#endif
+    }
+
+  protected:
+    operator xcb_connection_t * const(void) { return nullptr; }
+};
+
+int main(int argc, char ** argv)
+{
+  xpp::connection<xpp::extension::randr,
+                  xpp::extension::render,
+                  xpp::extension::xv,
+                  xpp::extension::damage,
+                  xpp::extension::xinerama,
+                  xpp::extension::xfixes,
+                  xpp::extension::input,
+                  xpp::extension::screensaver>
+                    connection("");
+
+  // xpp::request::x::map_window(connection, 0);
+  // xpp::request::x::query_tree(connection, 0);
+
+  xpp::event::registry<xpp::extension::randr,
+                       xpp::extension::render,
+                       xpp::extension::xv,
+                       xpp::extension::damage,
+                       xpp::extension::xinerama,
+                       xpp::extension::xfixes,
+                       xpp::extension::input,
+                       xpp::extension::screensaver>
+                         registry(connection);
+
+  std::vector<key_printer *> printers(100, new key_printer(connection));
+
+  for (int i =  0; i < 100; ++i) {
+    registry.attach(0, printers[i]);
+  }
+
+  for (int i =  0; i < 99; ++i) {
+    registry.detach(0, printers[i]);
+  }
+
+  auto font_id = connection.generate_id();
+  connection.open_font(font_id, "cursor");
+  auto cursor_id = connection.generate_id();
+  connection.create_glyph_cursor(cursor_id, font_id, font_id,
+      XC_cross, XC_cross + 1, 0, 0, 0, 0xffff, 0xffff, 0xffff);
+  connection.close_font(font_id);
+
+  *connection.grab_pointer(false, connection.root(),
+                           XCB_EVENT_MASK_BUTTON_PRESS,
+                           XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC,
+                           XCB_NONE, cursor_id);
+
+  connection.free_cursor(cursor_id);
+
+  std::cerr << "Please click on a window" << std::endl;
+
+// clang_complete does not like this
+// causes vim to segfault
+#if not defined __clang__
+
+  while (! g_quit) {
+    connection.flush();
+    registry.dispatch(connection.wait_for_event());
+  }
+
+#endif
+
+
+  // WATCHOUT FOR THE RETURN
+  // >>>>>>>>>>>>>>>>>>>>>>>
+  return 0;
+  // <<<<<<<<<<<<<<<<<<<<<<<
+
+
+
+
+
+  *((xpp::randr::protocol &)connection).query_version(
+      XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+  // auto & randr = (xpp::protocol::randr &)c;
+  // auto version = randr.query_version(
+  //     XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+  // std::cerr << "randr_major: " << version->major_version
+  //           << "; randr_minor: " << version->minor_version << std::endl;
+
+  // auto screens = connection.query_screens();
+
+  // for (auto & info : screens.screen_info()) {
+  //   std::cerr << "screen @ "
+  //             << info.x_org << "x" << info.x_org
+  //             << "+"
+  //             << info.width << "+" << info.height
+  //             << std::endl;
+  // }
+
+  std::cerr << "connection.root().query_tree().children<xcb_window_t>():" << std::endl;
+  for (auto & window : connection.root().query_tree().children()) {
+    std::cerr << window << ", ";
+  }
+  std::cerr << std::endl;
+
+  std::cerr << "connection.root().query_tree().children<xpp::window>():" << std::endl;
+  for (auto & window : connection.root().query_tree().children<xpp::window>()) {
+    std::cerr << window << ": ";
+    for (auto & child : window.query_tree().children<xpp::window>()) {
+      std::cerr << child << ", ";
+    }
+    std::cerr << std::endl;
+  }
+  std::cerr << std::endl;
+
+  auto atom = xpp::request::x::intern_atom(
+      connection, false, "_NET_CLIENT_LIST_STACKING");
+  auto property = xpp::request::x::get_property(
+      connection, false, connection.root(), atom->atom, XCB_ATOM_WINDOW, 0, UINT_MAX);
+
+  std::cerr << "windows (xcb_window_t):" << std::hex << std::endl;
+  for (auto & w : property.value<xcb_window_t>()) {
+    std::cerr << "0x" << w << ", ";
+  }
+  std::cerr << std::dec << std::endl;
+
+  std::cerr << "windows (xpp::window)" << std::hex << std::endl;
+  for (auto & w : property.value<xpp::window>()) {
+    std::cerr << w << ": ";
+    for (auto & child : w.query_tree().children<xpp::window>()) {
+      std::cerr << child << ", ";
+    }
+    std::cerr << std::endl;
+  }
+  std::cerr << std::dec << std::endl;
+
+  std::cerr << "hosts:" << std::endl;
+  auto hosts = xpp::request::x::list_hosts(connection);
+  for (auto & host : hosts.hosts()) {
+    std::cerr << "host: " << xcb_host_address(&host) << std::endl;
+  }
+
+  std::cerr << "fonts:" << std::endl;
+  auto fonts = xpp::request::x::list_fonts(connection, 8, 1, "*");
+  for (auto & name : fonts.names()) {
+    std::cerr << "font (" << name.length() << "): " << name << std::endl;
+  }
+
+  auto font_paths = xpp::request::x::get_font_path(connection);
+  for (auto & path : font_paths.path()) {
+    std::cerr << "path (" << path.length() << "): " << path << std::endl;
+  }
+
+  auto screen_info = xpp::request::randr::get_screen_info(connection, connection.root());
+  std::cerr << "nsizes: " << (int)screen_info->nSizes << std::endl;
+  std::cerr << "SizeID: " << (int)screen_info->sizeID << std::endl;
+  int i = 0;
+  for (auto & rate : screen_info.rates()) {
+    if (++i > screen_info->nSizes) break;
+
+    uint16_t * rates = xcb_randr_refresh_rates_rates(&rate);
+    std::cerr << "rates (length: "
+      << xcb_randr_refresh_rates_rates_length(&rate)
+      << "): ";
+
+    for (int j = 0; j < xcb_randr_refresh_rates_rates_length(&rate); ++j) {
+      std::cerr << rates[j];
+      if (j < xcb_randr_refresh_rates_rates_length(&rate) - 1) {
+        std::cerr << ", ";
+      }
+    }
+    std::cerr << std::endl;
+
+  }
+
+/*
+  xcb_list_hosts_cookie_t hosts_cookie = xcb_list_hosts(c);
+  xcb_list_hosts_reply_t * hosts_reply = xcb_list_hosts_reply(connection, hosts_cookie, NULL);
+
+  std::cerr << "list hosts" << std::endl;
+  xcb_host_iterator_t host_iter = xcb_list_hosts_hosts_iterator(hosts_reply);
+  for ( ; host_iter.rem > 0; xcb_host_next(&host_iter) ) {
+    xcb_host_t * host = (xcb_host_t *)host_iter.data;
+    std::cerr << "host adress: " << xcb_host_address(host) << std::endl;
+    std::cerr << "host family: " << (int)host->family << std::endl;
+    std::cerr << "host address_len: " << (int)host->address_len << std::endl;
+  }
+
+  std::cerr << "Randr refresh rates: " << std::endl;
+  xcb_randr_get_screen_info_cookie_t screen_info_cookie =
+    xcb_randr_get_screen_info(connection, connection.root());
+  xcb_randr_get_screen_info_reply_t * screen_info_reply =
+    xcb_randr_get_screen_info_reply(connection, screen_info_cookie, NULL);
+  std::cerr << "Randr refresh rates length: "
+            << xcb_randr_get_screen_info_rates_length(screen_info_reply)
+            << std::endl;
+
+  xcb_randr_refresh_rates_iterator_t refresh_rates_iter =
+    xcb_randr_get_screen_info_rates_iterator(screen_info_reply);
+
+  for (int i = 0; i < screen_info->nSizes; ++i) {
+    xcb_randr_refresh_rates_next(&refresh_rates_iter);
+
+    xcb_randr_refresh_rates_t * rate =
+      (xcb_randr_refresh_rates_t *)refresh_rates_iter.data;
+    uint16_t * rates = xcb_randr_refresh_rates_rates(rate);
+    std::cerr << "rates (length: "
+      << xcb_randr_refresh_rates_rates_length(rate)
+      << "): ";
+    for (int j = 0; j < xcb_randr_refresh_rates_rates_length(rate); ++j) {
+      std::cerr << rates[j];
+      if (j < xcb_randr_refresh_rates_rates_length(rate) - 1) {
+        std::cerr << ", ";
+      }
+    }
+    std::cerr << std::endl;
+  }
+
+  auto pict_formats = xpp::request::render::query_pict_formats(c);
+  for (auto & format : pict_formats.formats()) {
+    std::cerr << "format.depth: " << (int)format.depth << std::endl;;
+  }
+  */
+
+    /*
+  {
+  Display * dpy = XOpenDisplay(NULL);
+  Window root = DefaultRootWindow(dpy);
+  XRRScreenConfiguration * screen_cfg = XRRGetScreenInfo(dpy, root);
+  std::cerr << "rate: " << XRRConfigCurrentRate(screen_cfg) << std::endl;
+  int nrates = 0;
+  short * rates = XRRConfigRates(screen_cfg, 10, &nrates);
+  std::cerr << "nrates: " << nrates << std::endl;
+  for (int i = 0; i < nrates; ++i) {
+    std::cerr << "rate: " << rates[i] << std::endl;
+  }
+  }
+  */
+
+// auto iter = xpp::iterator<xcb_str_t, xcb_str_t, xcb_list_fonts_reply_t,
+//      xcb_str_iterator_t,
+//                xpp::callable<decltype(xcb_str_next), xcb_str_next>,
+//                xpp::callable<decltype(xcb_str_sizeof), xcb_str_sizeof>,
+//                xpp::callable<decltype(xpp::callable_test), xpp::callable_test>>();
+
+// auto iter2 = xpp::iterator<int, int, int, int,
+//                xpp::callable<decltype(xpp::next), xpp::next>,
+//                xpp::callable<decltype(xpp::size_of), xpp::size_of>,
+//                xpp::callable<decltype(xpp::get_iterator), xpp::get_iterator>>();
+
+  // xcb_window_t root = 0;
+  // auto x = connection.x();
+  // auto windows = connection.x().query_tree(root);
+  // auto primary_output = connection.randr().get_output_primary(root);
+
+  // auto window = *windows.children().begin();
+  // auto geometry = window.get_geometry();
+
+
+
+  // primary_output->output;
+
+  /*
+  xpp::connection c("");
+  // xpp::xproto::connection cc(*c);
+  std::cerr << "c: " << *c << std::endl;
+  std::cerr << "connection.root() " << connection.root() << std::endl;
+
+  uint32_t mask = XCB_CW_BACK_PIXEL
+                | XCB_CW_BORDER_PIXEL
+                | XCB_CW_OVERRIDE_REDIRECT;
+  uint32_t values[] = { 0xff123456, 0xff654321, true };
+  // uint32_t values[] = { 0, 0, true };
+
+  xpp::window window_1(
+      connection, 24, *connection.root(), 25, 25, 50, 50, 2,
+      XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_WINDOW_CLASS_COPY_FROM_PARENT,
+      mask, values);
+
+  std::cerr << window_1 << std::endl;
+  */
+
+  // {
+  //   auto window_2 = window_1;
+  //   std::cerr << window_2 << std::endl;
+  //   {
+  //     auto window_3 = window_2;
+  //     std::cerr << window_3 << std::endl;
+  //     window_3.map();
+  //     connection.flush();
+  //   }
+  //   sleep(3);
+  // }
+
+  // window_1.map();
+  // connection.flush();
+  // sleep(1);
+
+  // connection.root().query_tree().children();
+
+
+  // intern_atom
+  /*
+  auto atom =
+    xpp::xproto::request::intern_atom(*connection, false, "_NET_CLIENT_LIST_STACKING");
+  auto atom_name = xpp::xproto::request::get_atom_name(*connection, atom->atom);
+  std::cerr << atom_name.name() << std::endl;
+
+  auto property = xpp::xproto::request::get_property(
+      *connection, false, *connection.root(), atom->atom, XCB_ATOM_WINDOW, 0, UINT_MAX);
+
+  std::cerr << "windows" << std::hex << std::endl;
+  for (auto & p : property.value<xcb_window_t>()) {
+    std::cerr << "0x" << p << ", ";
+  }
+  std::cerr << std::dec << std::endl;
+  */
+
+  /*
+  typedef xpp::generic::fixed_size::iterator<void,
+                                             xcb_window_t,
+                                             xcb_get_property_reply_t,
+                                             xcb_get_property_value,
+                                             xcb_get_property_value_length>
+                                               window_iterator;
+
+  window_iterator window_begin = property.value<xcb_window_t>().begin();
+  window_iterator window_end = property.value<xcb_window_t>().end();
+  */
+
+  // list_fonts_with_info && list_fonts
+  /*
+  auto fonts_with_info =
+    xpp::xproto::request::list_fonts_with_info(*connection, 8, "*");
+
+  for (int i = 0; i < 8; ++i) {
+    std::cerr << fonts_with_info.name() << std::endl;
+    fonts_with_info.reset();
+  }
+
+  auto fonts = xpp::xproto::request::list_fonts(*connection, 8, "*");
+  for (auto & font : fonts.names()) {
+    std::cerr << "(" << font.length() << "): " << font << std::endl;
+  }
+  */
+
+  // auto grab = xpp::xproto::request::grab_keyboard(
+  //     *connection, false, connection.root(), XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC);
+
+  // query_tree iterator
+  /*
+  typedef xpp::generic::fixed_size::iterator<xcb_window_t,
+                                             xcb_window_t,
+                                             xcb_query_tree_reply_t,
+                                             xcb_query_tree_children,
+                                             xcb_query_tree_children_length>
+                                               children_iterator;
+
+  children_iterator children_begin, children_end;
+  */
+
+  // {
+  //   auto tree = xpp::xproto::request::query_tree(*connection, connection.root());
+  //   children_begin = tree.children().begin();
+  //   children_end = tree.children().end();
+  // }
+
+  // std::cerr << "windows" << std::endl;
+  // for (auto it = children_begin; it != children_end; ++it) {
+  //   std::cerr << std::hex << "0x" << *it << ", ";
+  // }
+  // std::cerr << std::endl;
+
+  // std::cerr << "crtcs" << std::endl;
+  // auto primary = xpp::request::randr::get_output_primary(*connection, connection.root());
+  // for (auto & crtc : xpp::request::randr::get_output_info(
+  //       *connection, primary->output, XCB_TIME_CURRENT_TIME).crtcs()) {
+  //   std::cerr << "crtc: " << crtc << std::endl;
+  // }
+
+  // list_hosts
+  /*
+  std::cerr << "hosts" << std::endl;
+  for (auto & host : xpp::xproto::request::list_hosts(*c).hosts()) {
+    std::cerr << std::string((char *)xcb_host_address(&host),
+                             xcb_host_address_length(&host))
+              << std::endl;
+  }
+  */
+
+  // std::cerr << "extensions" << std::endl;
+  // for (auto & name : xpp::xproto::request::list_extensions(*c).names()) {
+  //   std::cerr << std::string((char *)xcb_str_name(&name), xcb_str_name_length(&name)) << std::endl;
+  // }
+
+  // get_font_path
+  /*
+  for (auto & path : xpp::xproto::request::get_font_path(*c).path()) {
+    std::cerr << "(" << path.length() << ") path: " << path << std::endl;
+  }
+  */
+
+  // list_fonts
+  /*
+  const size_t n_fonts = 16;
+
+  std::cerr << "fonts (xpp 2)" << std::endl;
+
+  typedef xpp::generic::variable_size::iterator<xcb_str_t,
+                                                xcb_str_t,
+                                                xcb_list_fonts_reply_t,
+                                                xcb_str_iterator_t,
+                                                xcb_str_next,
+                                                xcb_str_sizeof,
+                                                xcb_list_fonts_names_iterator>
+                                                  names_iterator;
+
+  names_iterator names_begin, names_end;
+
+  {
+    auto names = xpp::xproto::request::list_fonts(*connection, n_fonts, 1, "*");
+
+    names_begin = names_iterator::begin(names.get());
+    names_end = names_iterator::end(names.get());
+  }
+
+  for (auto it = names_begin; it != names_end; ++it) {
+    std::cerr << it->length() << ": " << *it << std::endl;
+  }
+  */
+
+  return EXIT_SUCCESS;
+}
+
+
+  // auto it = names_begin;
+  // std::cerr << "it  : " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // ++it;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // ++it;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it++;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it++;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it++;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it--;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // --it;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it++;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // ++it;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it++;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // ++it;
+  // std::cerr << "it++: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it--;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // --it;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it--;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // --it;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it--;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // --it;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it--;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // --it;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // it--;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // --it;
+  // std::cerr << "it--: " << xcb_str_name_length(&*it) << ": "
+  //           << std::string((char *)xcb_str_name(&*it), xcb_str_name_length(&*it))
+  //           << std::endl;
+
+  // {
+  //   std::cerr << "fonts (native)" << std::endl;
+  //   xcb_list_fonts_cookie_t cookie = xcb_list_fonts(*connection, n_fonts, 1, "*");
+  //   xcb_list_fonts_reply_t * reply = xcb_list_fonts_reply(*connection, cookie, NULL);
+  //   xcb_str_iterator_t iter = xcb_list_fonts_names_iterator(reply);
+  //   xcb_str_t * name, * R, * prev;
+  //   int len1, len2, len3, len4;
+
+  //   // for ( ; iter.rem; xcb_str_next(&iter)) {
+  //   //   xcb_str_t * name = iter.data;
+  //   //   std::cerr << xcb_str_name_length(name) << ": " << std::string((char *)xcb_str_name(name), xcb_str_name_length(name)) << std::endl;
+  //   // }
+
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   xcb_str_next(&iter);
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   xcb_str_next(&iter);
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   len1 = xcb_str_sizeof(iter.data);
+  //   xcb_str_next(&iter);
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   len2 = xcb_str_sizeof(iter.data);
+  //   xcb_str_next(&iter);
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   len3 = xcb_str_sizeof(iter.data);
+  //   xcb_str_next(&iter);
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   len4 = xcb_str_sizeof(iter.data);
+  //   xcb_str_next(&iter);
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+
+
+
+  //   ++iter.rem;
+  //   R = iter.data;
+  //   // xcb_str_t * prev = R - xcb_str_sizeof(R);
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_sizeof(R));
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t) - 1;
+  //   prev = (xcb_str_t *)((char *)R) - len4;
+  //   iter.index = (char *) iter.data - (char *) prev;
+  //   iter.data = prev;
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   ++iter.rem;
+  //   R = iter.data;
+  //   // xcb_str_t * prev = R - xcb_str_sizeof(R);
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R));
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t) - 2;
+  //   prev = (xcb_str_t *)((char *)R) - len3;
+  //   iter.index = (char *) iter.data - (char *) prev;
+  //   iter.data = prev;
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   ++iter.rem;
+  //   R = iter.data;
+  //   // xcb_str_t * prev = R - xcb_str_sizeof(R);
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R));
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t);
+  //   prev = (xcb_str_t *)((char *)R) - len2;
+  //   iter.index = (char *) iter.data - (char *) prev;
+  //   iter.data = prev;
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   ++iter.rem;
+  //   R = iter.data;
+  //   // xcb_str_t * prev = R - xcb_str_sizeof(R);
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R));
+  //   // prev = (xcb_str_t *)(((char *)R) - xcb_str_name_length(R)) - sizeof(xcb_str_t);
+  //   prev = (xcb_str_t *)((char *)R) - len1;
+  //   iter.index = (char *) iter.data - (char *) prev;
+  //   iter.data = prev;
+  //   name = iter.data;
+  //   std::cerr << std::endl;
+  //   std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   std::cerr << "index: 0x" << std::hex << iter.index <<  " (" << std::dec << iter.index << ")" << std::endl;
+  //   std::cerr << "rem: " << iter.rem << std::dec << std::endl;
+  //   std::cerr << xcb_str_name_length(name) << ": "
+  //             << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //             << std::endl;
+
+  //   // xcb_str_next(&iter);
+  //   // name = iter.data;
+  //   // std::cerr << "data: 0x" << std::hex << iter.data << " (" << std::dec << (unsigned long)iter.data << ")" << std::endl;
+  //   // std::cerr << "index: 0x" << std::hex << iter.index << std::dec << std::endl;
+  //   // std::cerr << xcb_str_name_length(name) << ": "
+  //   //           << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //   //           << std::endl;
+
+  //   // ++iter.rem;
+  //   // R = iter.data;
+  //   // prev = R - xcb_str_sizeof(R);
+  //   // iter.index = (char *) iter.data - (char *) prev;
+  //   // iter.data = prev;
+  //   // name = iter.data;
+  //   // std::cerr << "data: 0x" << std::hex << iter.data << "(" << std::dec << iter.data << ")" << std::endl;
+  //   // std::cerr << "index: 0x" << std::hex << iter.index << std::endl;
+  //   // std::cerr << xcb_str_name_length(name) << ": "
+  //   //           << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //   //           << std::endl;
+
+  //   // xcb_str_next(&iter);
+  //   // name = iter.data;
+  //   // std::cerr << "data: 0x" << std::hex << iter.data << "(" << std::dec << iter.data << ")" << std::endl;
+  //   // std::cerr << "index: 0x" << std::hex << iter.index << std::endl;
+  //   // std::cerr << xcb_str_name_length(name) << ": "
+  //   //           << std::string((char *)xcb_str_name(name), xcb_str_name_length(name))
+  //   //           << std::endl;
+
+  //   std::cerr << "length: " << xcb_list_fonts_names_length(reply) << std::endl;
+  // }
+
diff --git a/lib/xpp/src/xpp.cpp b/lib/xpp/src/xpp.cpp
new file mode 100644 (file)
index 0000000..cda479b
--- /dev/null
@@ -0,0 +1 @@
+#include "xpp.hpp"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644 (file)
index 0000000..342e2e5
--- /dev/null
@@ -0,0 +1,102 @@
+#
+# Configure src
+#
+
+# Source tree {{{
+
+file(GLOB_RECURSE files RELATIVE ${CMAKE_CURRENT_LIST_DIR} *.c[p]*)
+list(REMOVE_ITEM files main.cpp ipc.cpp)
+
+configure_file(
+  ${CMAKE_CURRENT_LIST_DIR}/settings.cpp.cmake
+  ${CMAKE_BINARY_DIR}/generated-sources/settings.cpp
+  ESCAPE_QUOTES)
+
+list(APPEND files ${CMAKE_BINARY_DIR}/generated-sources/settings.cpp)
+
+if(NOT ENABLE_ALSA)
+  list(REMOVE_ITEM files modules/alsa.cpp)
+  list(REMOVE_ITEM files adapters/alsa/control.cpp)
+  list(REMOVE_ITEM files adapters/alsa/mixer.cpp)
+endif()
+if(NOT ENABLE_CURL)
+  list(REMOVE_ITEM files modules/github.cpp)
+  list(REMOVE_ITEM files utils/http.cpp)
+endif()
+if(NOT ENABLE_MPD)
+  list(REMOVE_ITEM files modules/mpd.cpp)
+  list(REMOVE_ITEM files adapters/mpd.cpp)
+endif()
+if(NOT ENABLE_NETWORK)
+  list(REMOVE_ITEM files modules/network.cpp)
+  list(REMOVE_ITEM files adapters/net.cpp)
+  list(REMOVE_ITEM files adapters/net_iw.cpp)
+  list(REMOVE_ITEM files adapters/net_nl.cpp)
+endif()
+if(WITH_LIBNL)
+  list(REMOVE_ITEM files adapters/net_iw.cpp)
+else()
+  list(REMOVE_ITEM files adapters/net_nl.cpp)
+endif()
+if(NOT ENABLE_I3)
+  list(REMOVE_ITEM files modules/i3.cpp)
+  list(REMOVE_ITEM files utils/i3.cpp)
+endif()
+if(NOT ENABLE_PULSEAUDIO)
+  list(REMOVE_ITEM files modules/pulseaudio.cpp)
+  list(REMOVE_ITEM files adapters/pulseaudio.cpp)
+endif()
+if(NOT WITH_XRANDR)
+  list(REMOVE_ITEM files x11/extensions/randr.cpp)
+endif()
+if(NOT WITH_XCOMPOSITE)
+  list(REMOVE_ITEM files x11/extensions/composite.cpp)
+endif()
+if(NOT WITH_XKB)
+  list(REMOVE_ITEM files x11/extensions/xkb.cpp)
+  list(REMOVE_ITEM files modules/xkeyboard.cpp)
+endif()
+if(NOT WITH_XRM)
+  list(REMOVE_ITEM files x11/xresources.cpp)
+endif()
+if(NOT WITH_XCURSOR)
+  list(REMOVE_ITEM files x11/cursor.cpp)
+endif()
+# }}}
+
+# Target: polybar {{{
+
+add_library(poly STATIC ${files})
+target_include_directories(poly PUBLIC ${dirs})
+target_link_libraries(poly ${libs} Threads::Threads)
+target_compile_options(poly PUBLIC $<$<CXX_COMPILER_ID:GNU>:$<$<CONFIG:MinSizeRel>:-flto>>)
+
+add_executable(polybar main.cpp)
+target_link_libraries(polybar poly)
+
+install(TARGETS polybar
+  DESTINATION ${CMAKE_INSTALL_BINDIR}
+  COMPONENT runtime)
+
+# }}}
+# Target: polybar-msg {{{
+
+if(BUILD_IPC_MSG)
+  add_executable(polybar-msg
+    ipc.cpp
+    utils/env.cpp
+    utils/file.cpp
+    utils/string.cpp)
+  target_include_directories(polybar-msg PRIVATE ${dirs})
+  target_compile_options(polybar-msg PUBLIC $<$<CXX_COMPILER_ID:GNU>:$<$<CONFIG:MinSizeRel>:-flto>>)
+
+  install(TARGETS polybar-msg
+    DESTINATION ${CMAKE_INSTALL_BINDIR}
+    COMPONENT runtime)
+
+endif()
+
+# }}}
+
+# Export source file list so that it can be used for test compilation
+set(files ${files} PARENT_SCOPE)
diff --git a/src/adapters/alsa/control.cpp b/src/adapters/alsa/control.cpp
new file mode 100644 (file)
index 0000000..3d7d3a4
--- /dev/null
@@ -0,0 +1,121 @@
+#include "adapters/alsa/control.hpp"
+#include "adapters/alsa/generic.hpp"
+
+POLYBAR_NS
+
+namespace alsa {
+  /**
+   * Construct control object
+   */
+  control::control(int numid) : m_numid(numid) {
+    int err{0};
+
+    if ((err = snd_ctl_open(&m_ctl, ALSA_SOUNDCARD, SND_CTL_NONBLOCK | SND_CTL_READONLY)) == -1) {
+      throw_exception<control_error>("Could not open control '" + string{ALSA_SOUNDCARD} + "'", err);
+    }
+
+    snd_config_update_free_global();
+
+    if ((err = snd_hctl_open_ctl(&m_hctl, m_ctl)) == -1) {
+      snd_ctl_close(m_ctl);
+      throw_exception<control_error>("Failed to open hctl", err);
+    }
+
+    snd_config_update_free_global();
+
+    if ((err = snd_hctl_load(m_hctl)) == -1) {
+      throw_exception<control_error>("Failed to load hctl", err);
+    }
+
+    snd_ctl_elem_id_t* m_id{nullptr};
+    snd_ctl_elem_id_alloca(&m_id);
+    snd_ctl_elem_id_set_numid(m_id, m_numid);
+
+    snd_ctl_elem_info_t* m_info{nullptr};
+    snd_ctl_elem_info_alloca(&m_info);
+    snd_ctl_elem_info_set_id(m_info, m_id);
+
+    if ((err = snd_ctl_elem_info(m_ctl, m_info)) == -1) {
+      throw_exception<control_error>("Could not get control datal", err);
+    }
+
+    snd_ctl_elem_info_get_id(m_info, m_id);
+
+    if ((m_elem = snd_hctl_find_elem(m_hctl, m_id)) == nullptr) {
+      throw control_error("Could not find control with id " + to_string(snd_ctl_elem_id_get_numid(m_id)));
+    }
+
+    if ((err = snd_ctl_subscribe_events(m_ctl, 1)) == -1) {
+      throw control_error("Could not subscribe to events: " + to_string(snd_ctl_elem_id_get_numid(m_id)));
+    }
+  }
+
+  /**
+   * Deconstruct control object
+   */
+  control::~control() {
+    if (m_hctl != nullptr) {
+      snd_hctl_close(m_hctl);
+    }
+  }
+
+  /**
+   * Get the id number
+   */
+  int control::get_numid() {
+    return m_numid;
+  }
+
+  /**
+   * Wait for events
+   */
+  bool control::wait(int timeout) {
+    assert(m_ctl);
+
+    int err{0};
+
+    if ((err = snd_ctl_wait(m_ctl, timeout)) == -1) {
+      throw_exception<control_error>("Failed to wait for events", err);
+    }
+
+    snd_ctl_event_t* event{nullptr};
+    snd_ctl_event_alloca(&event);
+
+    if ((err = snd_ctl_read(m_ctl, event)) == -1) {
+      return false;
+    }
+
+    if (snd_ctl_event_get_type(event) == SND_CTL_EVENT_ELEM) {
+      return snd_ctl_event_elem_get_mask(event) & SND_CTL_EVENT_MASK_VALUE;
+    }
+
+    return false;
+  }
+
+  /**
+   * Check if the interface is in use
+   */
+  bool control::test_device_plugged() {
+    assert(m_elem);
+
+    snd_ctl_elem_value_t* m_value{nullptr};
+    snd_ctl_elem_value_alloca(&m_value);
+
+    int err{0};
+
+    if ((err = snd_hctl_elem_read(m_elem, m_value)) == -1) {
+      throw_exception<control_error>("Could not read control value", err);
+    }
+
+    return snd_ctl_elem_value_get_boolean(m_value, 0);
+  }
+
+  /**
+   * Process queued events
+   */
+  void control::process_events() {
+    wait(0);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/adapters/alsa/mixer.cpp b/src/adapters/alsa/mixer.cpp
new file mode 100644 (file)
index 0000000..b83a18e
--- /dev/null
@@ -0,0 +1,232 @@
+#include <cmath>
+
+#include "adapters/alsa/generic.hpp"
+#include "adapters/alsa/mixer.hpp"
+#include "utils/math.hpp"
+
+#define MAX_LINEAR_DB_SCALE 24
+
+POLYBAR_NS
+
+namespace alsa {
+  /**
+   * Construct mixer object
+   */
+  mixer::mixer(string&& mixer_selem_name, string&& soundcard_name)
+      : m_name(forward<string>(mixer_selem_name)), s_name(soundcard_name) {
+    int err = 0;
+
+    if ((err = snd_mixer_open(&m_mixer, 1)) == -1) {
+      throw_exception<mixer_error>("Failed to open hardware mixer", err);
+    }
+
+    snd_config_update_free_global();
+
+    if ((err = snd_mixer_attach(m_mixer, s_name.c_str())) == -1) {
+      throw_exception<mixer_error>("Failed to attach hardware mixer control", err);
+    }
+    if ((err = snd_mixer_selem_register(m_mixer, nullptr, nullptr)) == -1) {
+      throw_exception<mixer_error>("Failed to register simple mixer element", err);
+    }
+    if ((err = snd_mixer_load(m_mixer)) == -1) {
+      throw_exception<mixer_error>("Failed to load mixer", err);
+    }
+
+    snd_mixer_selem_id_t* sid{nullptr};
+    snd_mixer_selem_id_alloca(&sid);
+    snd_mixer_selem_id_set_index(sid, 0);
+    snd_mixer_selem_id_set_name(sid, m_name.c_str());
+
+    if ((m_elem = snd_mixer_find_selem(m_mixer, sid)) == nullptr) {
+      throw mixer_error("Cannot find simple element");
+    }
+  }
+
+  /**
+   * Deconstruct mixer
+   */
+  mixer::~mixer() {
+    if (m_mixer != nullptr) {
+      snd_mixer_close(m_mixer);
+    }
+  }
+
+  /**
+   * Get mixer name
+   */
+  const string& mixer::get_name() {
+    return m_name;
+  }
+
+  /**
+   * Get the name of the soundcard that is associated with the mixer
+   */
+  const string& mixer::get_sound_card() {
+    return s_name;
+  }
+
+  /**
+   * Wait for events
+   */
+  bool mixer::wait(int timeout) {
+    assert(m_mixer);
+
+    int err = 0;
+
+    if ((err = snd_mixer_wait(m_mixer, timeout)) == -1) {
+      throw_exception<mixer_error>("Failed to wait for events", err);
+    }
+
+    return process_events() > 0;
+  }
+
+  /**
+   * Process queued mixer events
+   */
+  int mixer::process_events() {
+    int num_events{0};
+    if ((num_events = snd_mixer_handle_events(m_mixer)) == -1) {
+      throw_exception<mixer_error>("Failed to process pending events", num_events);
+    }
+
+    return num_events;
+  }
+
+  /**
+   * Get volume in percentage
+   */
+  int mixer::get_volume() {
+    assert(m_elem != nullptr);
+
+    long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
+
+    snd_mixer_selem_get_playback_volume_range(m_elem, &vol_min, &vol_max);
+
+    for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) {
+      if (snd_mixer_selem_has_playback_channel(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i))) {
+        snd_mixer_selem_get_playback_volume(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i), &vol);
+        vol_total += vol;
+        chan_n++;
+      }
+    }
+
+    return math_util::unbounded_percentage(vol_total / chan_n, vol_min, vol_max);
+  }
+
+  /**
+   * Get normalized volume in percentage
+   */
+  int mixer::get_normalized_volume() {
+    assert(m_elem != nullptr);
+
+    long chan_n = 0, vol_total = 0, vol, vol_min, vol_max;
+    double normalized, min_norm;
+
+    snd_mixer_selem_get_playback_dB_range(m_elem, &vol_min, &vol_max);
+
+    for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) {
+      if (snd_mixer_selem_has_playback_channel(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i))) {
+        snd_mixer_selem_get_playback_dB(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i), &vol);
+        vol_total += vol;
+        chan_n++;
+      }
+    }
+
+    if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) {
+       return math_util::percentage(vol_total / chan_n, vol_min, vol_max);
+    }
+
+    normalized = pow(10, (vol_total / chan_n - vol_max) / 6000.0);
+    if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) {
+      min_norm = pow(10, (vol_min - vol_max) / 6000.0);
+      normalized = (normalized - min_norm) / (1 - min_norm);
+    }
+
+    return 100.0f * normalized + 0.5f;
+  }
+
+  /**
+   * Set volume to given percentage
+   */
+  void mixer::set_volume(float percentage) {
+    assert(m_elem != nullptr);
+
+    if (is_muted()) {
+      return;
+    }
+
+    long vol_min, vol_max;
+    snd_mixer_selem_get_playback_volume_range(m_elem, &vol_min, &vol_max);
+    snd_mixer_selem_set_playback_volume_all(m_elem, math_util::percentage_to_value<int>(percentage, vol_min, vol_max));
+  }
+
+  /**
+   * Set normalized volume to given percentage
+   */
+  void mixer::set_normalized_volume(float percentage) {
+    assert(m_elem != nullptr);
+
+    if (is_muted()) {
+      return;
+    }
+
+    long vol_min, vol_max;
+    double min_norm;
+    percentage = percentage / 100.0f;
+
+    snd_mixer_selem_get_playback_dB_range(m_elem, &vol_min, &vol_max);
+
+    if (vol_max - vol_min <= MAX_LINEAR_DB_SCALE * 100) {
+      snd_mixer_selem_set_playback_dB_all(m_elem, lrint(percentage * (vol_max - vol_min)) + vol_min, 0);
+      return;
+    }
+
+    if (vol_min != SND_CTL_TLV_DB_GAIN_MUTE) {
+      min_norm = pow(10, (vol_min - vol_max) / 6000.0);
+      percentage = percentage * (1 - min_norm) + min_norm;
+    }
+
+    snd_mixer_selem_set_playback_dB_all(m_elem, lrint(6000.0 * log10(percentage)) + vol_max, 0);
+  }
+
+  /**
+   * Set mute state
+   */
+  void mixer::set_mute(bool mode) {
+    assert(m_elem != nullptr);
+
+    snd_mixer_selem_set_playback_switch_all(m_elem, mode);
+  }
+
+  /**
+   * Toggle mute state
+   */
+  void mixer::toggle_mute() {
+    assert(m_elem != nullptr);
+
+    int state;
+
+    snd_mixer_selem_get_playback_switch(m_elem, SND_MIXER_SCHN_MONO, &state);
+    snd_mixer_selem_set_playback_switch_all(m_elem, !state);
+  }
+
+  /**
+   * Get current mute state
+   */
+  bool mixer::is_muted() {
+    assert(m_elem != nullptr);
+
+    int state = 0;
+
+    for (int i = 0; i <= SND_MIXER_SCHN_LAST; i++) {
+      if (snd_mixer_selem_has_playback_channel(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i))) {
+        int state_ = 0;
+        snd_mixer_selem_get_playback_switch(m_elem, static_cast<snd_mixer_selem_channel_id_t>(i), &state_);
+        state = state || state_;
+      }
+    }
+    return !state;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/adapters/mpd.cpp b/src/adapters/mpd.cpp
new file mode 100644 (file)
index 0000000..4a5438a
--- /dev/null
@@ -0,0 +1,494 @@
+#include <cassert>
+#include <csignal>
+#include <thread>
+#include <utility>
+
+#include "adapters/mpd.hpp"
+#include "components/logger.hpp"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+#define TRACE_BOOL(mode) m_log.trace("mpdconnection.%s: %s", __func__, mode ? "true" : "false");
+
+namespace mpd {
+  sig_atomic_t g_connection_closed = 0;
+  void g_mpd_signal_handler(int signum) {
+    if (signum == SIGPIPE) {
+      g_connection_closed = 1;
+    }
+  }
+
+  void check_connection(mpd_connection* conn) {
+    if (g_connection_closed) {
+      g_connection_closed = 0;
+      throw server_error("Connection closed (broken pipe)");
+    } else if (conn == nullptr) {
+      throw client_error("Not connected to server", MPD_ERROR_STATE);
+    }
+  }
+
+  void check_errors(mpd_connection* conn) {
+    check_connection(conn);
+
+    string err_msg;
+
+    switch (mpd_connection_get_error(conn)) {
+      case MPD_ERROR_SUCCESS:
+        return;
+      case MPD_ERROR_SERVER:
+        {
+          err_msg = mpd_connection_get_error_message(conn);
+          enum mpd_server_error server_err = mpd_connection_get_server_error(conn);
+          mpd_connection_clear_error(conn);
+          throw server_error(err_msg, server_err);
+        }
+      default:
+        {
+          err_msg = mpd_connection_get_error_message(conn);
+          enum mpd_error err = mpd_connection_get_error(conn);
+          mpd_connection_clear_error(conn);
+          throw client_error(err_msg, err);
+        }
+    }
+  }
+
+  // deleters {{{
+
+  namespace details {
+    void mpd_connection_deleter::operator()(mpd_connection* conn) {
+      if (conn != nullptr) {
+        mpd_connection_free(conn);
+      }
+    }
+
+    void mpd_status_deleter::operator()(mpd_status* status) {
+      mpd_status_free(status);
+    }
+
+    void mpd_song_deleter::operator()(mpd_song* song) {
+      mpd_song_free(song);
+    }
+  }
+
+  // }}}
+  // class: mpdsong {{{
+
+  mpdsong::operator bool() {
+    return m_song != nullptr;
+  }
+
+  string mpdsong::get_artist() {
+    assert(m_song);
+    auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ARTIST, 0);
+    return string{tag != nullptr ? tag : ""};
+  }
+
+  string mpdsong::get_album_artist() {
+    assert(m_song);
+    auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ALBUM_ARTIST, 0);
+    return string{tag != nullptr ? tag : ""};
+}
+
+  string mpdsong::get_album() {
+    assert(m_song);
+    auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_ALBUM, 0);
+    return string{tag != nullptr ? tag : ""};
+  }
+
+  string mpdsong::get_date() {
+    assert(m_song);
+    auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_DATE, 0);
+    return string{tag != nullptr ? tag : ""};
+  }
+
+  string mpdsong::get_title() {
+    assert(m_song);
+    auto tag = mpd_song_get_tag(m_song.get(), MPD_TAG_TITLE, 0);
+    if (tag == nullptr) {
+      tag = mpd_song_get_tag(m_song.get(), MPD_TAG_NAME, 0);
+      if (tag == nullptr) {
+        auto uri = mpd_song_get_uri(m_song.get());
+        auto name = strrchr(uri, '/');
+        tag = name ? name + 1 : uri;
+      }
+    }
+    return string{tag};
+  }
+
+  unsigned mpdsong::get_duration() {
+    assert(m_song);
+    return mpd_song_get_duration(m_song.get());
+  }
+
+  // }}}
+  // class: mpdconnection {{{
+
+  mpdconnection::mpdconnection(
+      const logger& logger, string host, unsigned int port, string password, unsigned int timeout)
+      : m_log(logger), m_host(move(host)), m_port(port), m_password(move(password)), m_timeout(timeout) {
+    memset(&m_signal_action, 0, sizeof(m_signal_action));
+    m_signal_action.sa_handler = &g_mpd_signal_handler;
+    if (sigaction(SIGPIPE, &m_signal_action, nullptr) == -1) {
+      throw mpd_exception("Could not setup signal handler: "s + std::strerror(errno));
+    }
+  }
+
+  mpdconnection::~mpdconnection() {
+    m_signal_action.sa_handler = SIG_DFL;
+    sigaction(SIGPIPE, &m_signal_action, nullptr);
+  }
+
+  void mpdconnection::connect() {
+    try {
+      m_log.trace("mpdconnection.connect: %s, %i, \"%s\", timeout: %i", m_host, m_port, m_password, m_timeout);
+      m_connection.reset(mpd_connection_new(m_host.c_str(), m_port, m_timeout * 1000));
+      check_errors(m_connection.get());
+
+      if (!m_password.empty()) {
+        noidle();
+        assert(!m_listactive);
+        mpd_run_password(m_connection.get(), m_password.c_str());
+        check_errors(m_connection.get());
+      }
+
+      m_fd = mpd_connection_get_fd(m_connection.get());
+      check_errors(m_connection.get());
+    } catch (const client_error& e) {
+      disconnect();
+      throw e;
+    }
+  }
+
+  void mpdconnection::disconnect() {
+    m_connection.reset();
+    m_idle = false;
+    m_listactive = false;
+  }
+
+  bool mpdconnection::connected() {
+    return m_connection && m_connection != nullptr;
+  }
+
+  bool mpdconnection::retry_connection(int interval) {
+    if (connected()) {
+      return true;
+    }
+
+    while (true) {
+      try {
+        connect();
+        return true;
+      } catch (const mpd_exception& e) {
+        std::this_thread::sleep_for(chrono::duration<double>(interval));
+      }
+    }
+
+    return false;
+  }
+
+  int mpdconnection::get_fd() {
+    return m_fd;
+  }
+
+  void mpdconnection::idle() {
+    check_connection(m_connection.get());
+    if (!m_idle) {
+      mpd_send_idle(m_connection.get());
+      check_errors(m_connection.get());
+      m_idle = true;
+    }
+  }
+
+  int mpdconnection::noidle() {
+    check_connection(m_connection.get());
+    int flags = 0;
+    if (m_idle && mpd_send_noidle(m_connection.get())) {
+      m_idle = false;
+      flags = mpd_recv_idle(m_connection.get(), true);
+      mpd_response_finish(m_connection.get());
+      check_errors(m_connection.get());
+    }
+    return flags;
+  }
+
+  unique_ptr<mpdstatus> mpdconnection::get_status() {
+    check_prerequisites();
+    auto status = make_unique<mpdstatus>(this);
+    check_errors(m_connection.get());
+    return status;
+  }
+
+  unique_ptr<mpdstatus> mpdconnection::get_status_safe() {
+    try {
+      return get_status();
+    } catch (const mpd_exception& e) {
+      return {};
+    }
+  }
+
+  unique_ptr<mpdsong> mpdconnection::get_song() {
+    check_prerequisites_commands_list();
+    mpd_send_current_song(m_connection.get());
+    mpd_song_t song{mpd_recv_song(m_connection.get()), mpd_song_t::deleter_type{}};
+    mpd_response_finish(m_connection.get());
+    check_errors(m_connection.get());
+    if (song != nullptr) {
+      return make_unique<mpdsong>(move(song));
+    }
+    return unique_ptr<mpdsong>{};
+  }
+
+  void mpdconnection::play() {
+    try {
+      check_prerequisites_commands_list();
+      mpd_run_play(m_connection.get());
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.play: %s", e.what());
+    }
+  }
+
+  void mpdconnection::pause(bool state) {
+    try {
+      TRACE_BOOL(state);
+      check_prerequisites_commands_list();
+      mpd_run_pause(m_connection.get(), state);
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.pause: %s", e.what());
+    }
+  }
+
+  void mpdconnection::toggle() {
+    try {
+      check_prerequisites_commands_list();
+      mpd_run_toggle_pause(m_connection.get());
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.toggle: %s", e.what());
+    }
+  }
+
+  void mpdconnection::stop() {
+    try {
+      check_prerequisites_commands_list();
+      mpd_run_stop(m_connection.get());
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.stop: %s", e.what());
+    }
+  }
+
+  void mpdconnection::prev() {
+    try {
+      check_prerequisites_commands_list();
+      mpd_run_previous(m_connection.get());
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.prev: %s", e.what());
+    }
+  }
+
+  void mpdconnection::next() {
+    try {
+      check_prerequisites_commands_list();
+      mpd_run_next(m_connection.get());
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.next: %s", e.what());
+    }
+  }
+
+  void mpdconnection::seek(int songid, int pos) {
+    try {
+      check_prerequisites_commands_list();
+      mpd_run_seek_id(m_connection.get(), songid, pos);
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.seek: %s", e.what());
+    }
+  }
+
+  void mpdconnection::set_repeat(bool mode) {
+    try {
+      TRACE_BOOL(mode);
+      check_prerequisites_commands_list();
+      mpd_run_repeat(m_connection.get(), mode);
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.set_repeat: %s", e.what());
+    }
+  }
+
+  void mpdconnection::set_random(bool mode) {
+    try {
+      TRACE_BOOL(mode);
+      check_prerequisites_commands_list();
+      mpd_run_random(m_connection.get(), mode);
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.set_random: %s", e.what());
+    }
+  }
+
+  void mpdconnection::set_single(bool mode) {
+    try {
+      TRACE_BOOL(mode);
+      check_prerequisites_commands_list();
+      mpd_run_single(m_connection.get(), mode);
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.set_single: %s", e.what());
+    }
+  }
+
+  void mpdconnection::set_consume(bool mode) {
+    try {
+      TRACE_BOOL(mode);
+      check_prerequisites_commands_list();
+      mpd_run_consume(m_connection.get(), mode);
+      check_errors(m_connection.get());
+    } catch (const mpd_exception& e) {
+      m_log.err("mpdconnection.set_consume: %s", e.what());
+    }
+  }
+
+  mpdconnection::operator mpd_connection_t::element_type*() {
+    return m_connection.get();
+  }
+
+  void mpdconnection::check_prerequisites() {
+    check_connection(m_connection.get());
+    noidle();
+  }
+
+  void mpdconnection::check_prerequisites_commands_list() {
+    noidle();
+    assert(!m_listactive);
+    check_prerequisites();
+  }
+
+  // }}}
+  // class: mpdstatus {{{
+
+  mpdstatus::mpdstatus(mpdconnection* conn, bool autoupdate) {
+    fetch_data(conn);
+    if (autoupdate) {
+      update(-1, conn);
+    }
+  }
+
+  void mpdstatus::fetch_data(mpdconnection* conn) {
+    m_status.reset(mpd_run_status(*conn));
+    m_updated_at = chrono::system_clock::now();
+    m_songid = mpd_status_get_song_id(m_status.get());
+    m_queuelen = mpd_status_get_queue_length(m_status.get());
+    m_random = mpd_status_get_random(m_status.get());
+    m_repeat = mpd_status_get_repeat(m_status.get());
+    m_single = mpd_status_get_single(m_status.get());
+    m_consume = mpd_status_get_consume(m_status.get());
+    m_elapsed_time = mpd_status_get_elapsed_time(m_status.get());
+    m_total_time = mpd_status_get_total_time(m_status.get());
+  }
+
+  void mpdstatus::update(int event, mpdconnection* connection) {
+    /*
+     * Only update if either the player state (play, stop, pause, seek, ...), the options (random, repeat, ...),
+     * or the playlist has been changed
+     */
+    if (connection == nullptr || !static_cast<bool>(event & (MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
+      return;
+    }
+
+    fetch_data(connection);
+
+    m_elapsed_time_ms = m_elapsed_time * 1000;
+
+    auto state = mpd_status_get_state(m_status.get());
+
+    switch (state) {
+      case MPD_STATE_PAUSE:
+        m_state = mpdstate::PAUSED;
+        break;
+      case MPD_STATE_PLAY:
+        m_state = mpdstate::PLAYING;
+        break;
+      case MPD_STATE_STOP:
+        m_state = mpdstate::STOPPED;
+        break;
+      default:
+        m_state = mpdstate::UNKNOWN;
+    }
+  }
+
+  bool mpdstatus::random() const {
+    return m_random;
+  }
+
+  bool mpdstatus::repeat() const {
+    return m_repeat;
+  }
+
+  bool mpdstatus::single() const {
+    return m_single;
+  }
+
+  bool mpdstatus::consume() const {
+    return m_consume;
+  }
+
+  bool mpdstatus::match_state(mpdstate state) const {
+    return state == m_state;
+  }
+
+  int mpdstatus::get_songid() const {
+    return m_songid;
+  }
+
+  int mpdstatus::get_queuelen() const {
+    return m_queuelen;
+  }
+
+  unsigned mpdstatus::get_total_time() const {
+    return m_total_time;
+  }
+
+  unsigned mpdstatus::get_elapsed_time() const {
+    return m_elapsed_time;
+  }
+
+  unsigned mpdstatus::get_elapsed_percentage() {
+    if (m_total_time == 0) {
+      return 0;
+    }
+    return static_cast<int>(float(m_elapsed_time) / float(m_total_time) * 100.0 + 0.5f);
+  }
+
+  string mpdstatus::get_formatted_elapsed() {
+    char buffer[32];
+    snprintf(buffer, sizeof(buffer), "%lu:%02lu", m_elapsed_time / 60, m_elapsed_time % 60);
+    return {buffer};
+  }
+
+  string mpdstatus::get_formatted_total() {
+    char buffer[32];
+    snprintf(buffer, sizeof(buffer), "%lu:%02lu", m_total_time / 60, m_total_time % 60);
+    return {buffer};
+  }
+
+  int mpdstatus::get_seek_position(int percentage) {
+    if (m_total_time == 0) {
+      return 0;
+    }
+    math_util::cap<int>(0, 100, percentage);
+    return math_util::percentage_to_value<double>(percentage, m_total_time);
+  }
+
+  // }}}
+}
+
+#undef TRACE_BOOL
+
+POLYBAR_NS_END
diff --git a/src/adapters/net.cpp b/src/adapters/net.cpp
new file mode 100644 (file)
index 0000000..5fd5499
--- /dev/null
@@ -0,0 +1,320 @@
+#include "adapters/net.hpp"
+
+#include <arpa/inet.h>
+#include <linux/ethtool.h>
+#include <linux/if_link.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <iomanip>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "utils/command.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace net {
+  /**
+   * Test if interface with given name is a wireless device
+   */
+  bool is_wireless_interface(const string& ifname) {
+    return file_util::exists("/sys/class/net/" + ifname + "/wireless");
+  }
+
+  static const string NO_IP = string("N/A");
+
+  // class : network {{{
+
+  /**
+   * Construct network interface
+   */
+  network::network(string interface) : m_log(logger::make()), m_interface(move(interface)) {
+    if (if_nametoindex(m_interface.c_str()) == 0) {
+      throw network_error("Invalid network interface \"" + m_interface + "\"");
+    }
+
+    m_socketfd = file_util::make_file_descriptor(socket(AF_INET, SOCK_DGRAM, 0));
+    if (!*m_socketfd) {
+      throw network_error("Failed to open socket");
+    }
+
+    check_tuntap_or_bridge();
+  }
+
+  /**
+   * Query device driver for information
+   */
+  bool network::query(bool accumulate) {
+    m_status.previous = m_status.current;
+    m_status.current.transmitted = 0;
+    m_status.current.received = 0;
+    m_status.current.time = std::chrono::system_clock::now();
+    m_status.ip = NO_IP;
+    m_status.ip6 = NO_IP;
+
+    struct ifaddrs* ifaddr;
+    if (getifaddrs(&ifaddr) == -1 || ifaddr == nullptr) {
+      return false;
+    }
+
+    for (auto ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
+      if (ifa->ifa_addr == nullptr) {
+        continue;
+      }
+
+      if (m_interface.compare(0, m_interface.length(), ifa->ifa_name) != 0) {
+        if (!accumulate || (ifa->ifa_data == nullptr && ifa->ifa_addr->sa_family != AF_PACKET)) {
+          continue;
+        }
+      }
+
+      struct sockaddr_in6* sa6;
+
+      switch (ifa->ifa_addr->sa_family) {
+        case AF_INET:
+          char ip_buffer[NI_MAXHOST];
+          getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), ip_buffer, NI_MAXHOST, nullptr, 0, NI_NUMERICHOST);
+          m_status.ip = string{ip_buffer};
+          break;
+
+        case AF_INET6:
+          char ip6_buffer[INET6_ADDRSTRLEN];
+          sa6 = reinterpret_cast<decltype(sa6)>(ifa->ifa_addr);
+          if (IN6_IS_ADDR_LINKLOCAL(&sa6->sin6_addr)) {
+            continue;
+          }
+          if (IN6_IS_ADDR_SITELOCAL(&sa6->sin6_addr)) {
+            continue;
+          }
+          if ((((unsigned char*)sa6->sin6_addr.s6_addr)[0] & 0xFE) == 0xFC) {
+            /* Skip Unique Local Addresses (fc00::/7) */
+            continue;
+          }
+          if (inet_ntop(AF_INET6, &sa6->sin6_addr, ip6_buffer, INET6_ADDRSTRLEN) == 0) {
+            m_log.warn("inet_ntop() " + string(strerror(errno)));
+            continue;
+          }
+          m_status.ip6 = string{ip6_buffer};
+          break;
+
+        case AF_PACKET:
+          if (ifa->ifa_data == nullptr) {
+            continue;
+          }
+          struct rtnl_link_stats* link_state = reinterpret_cast<decltype(link_state)>(ifa->ifa_data);
+          if (link_state == nullptr) {
+            continue;
+          }
+          m_status.current.transmitted += link_state->tx_bytes;
+          m_status.current.received += link_state->rx_bytes;
+          break;
+      }
+    }
+
+    freeifaddrs(ifaddr);
+
+    return true;
+  }
+
+  /**
+   * Run ping command to test internet connectivity
+   */
+  bool network::ping() const {
+    try {
+      auto exec = "ping -c 2 -W 2 -I " + m_interface + " " + string(CONNECTION_TEST_IP);
+      auto ping = command_util::make_command<output_policy::IGNORED>(exec);
+      return ping && ping->exec(true) == EXIT_SUCCESS;
+    } catch (const std::exception& err) {
+      return false;
+    }
+  }
+
+  /**
+   * Get interface ipv4 address
+   */
+  string network::ip() const {
+    return m_status.ip;
+  }
+
+  /**
+   * Get interface ipv6 address
+   */
+  string network::ip6() const {
+    return m_status.ip6;
+  }
+
+  /**
+   * Get download speed rate
+   */
+  string network::downspeed(int minwidth) const {
+    float bytes_diff = m_status.current.received - m_status.previous.received;
+    return format_speedrate(bytes_diff, minwidth);
+  }
+
+  /**
+   * Get upload speed rate
+   */
+  string network::upspeed(int minwidth) const {
+    float bytes_diff = m_status.current.transmitted - m_status.previous.transmitted;
+    return format_speedrate(bytes_diff, minwidth);
+  }
+
+  /**
+   * Set if unknown counts as up
+   */
+  void network::set_unknown_up(bool unknown) {
+    m_unknown_up = unknown;
+  }
+
+  /**
+   * Query driver info to check if the
+   * interface is a TUN/TAP device or BRIDGE
+   */
+  void network::check_tuntap_or_bridge() {
+    struct ethtool_drvinfo driver {};
+    struct ifreq request {};
+
+    driver.cmd = ETHTOOL_GDRVINFO;
+
+    memset(&request, 0, sizeof(request));
+
+    /*
+     * Only copy array size minus one bytes over to ensure there is a
+     * terminating NUL byte (which is guaranteed by memset)
+     */
+    strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
+
+    request.ifr_data = reinterpret_cast<char*>(&driver);
+
+    if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
+      return;
+    }
+
+    // Check if it's a TUN/TAP device
+    if (strncmp(driver.bus_info, "tun", 3) == 0) {
+      m_tuntap = true;
+    } else if (strncmp(driver.bus_info, "tap", 3) == 0) {
+      m_tuntap = true;
+    } else {
+      m_tuntap = false;
+    }
+
+    if (strncmp(driver.driver, "bridge", 6) == 0) {
+      m_bridge = true;
+    }
+  }
+
+  /**
+   * Test if the network interface is in a valid state
+   */
+  bool network::test_interface() const {
+    auto operstate = file_util::contents("/sys/class/net/" + m_interface + "/operstate");
+    bool up = operstate.compare(0, 2, "up") == 0;
+    return m_unknown_up ? (up || operstate.compare(0, 7, "unknown") == 0) : up;
+  }
+
+  /**
+   * Format up- and download speed
+   */
+  string network::format_speedrate(float bytes_diff, int minwidth) const {
+    // Get time difference in seconds as a float
+    const std::chrono::duration<float> duration = m_status.current.time - m_status.previous.time;
+    float time_diff = duration.count();
+    float speedrate = bytes_diff / time_diff;
+
+    vector<string> suffixes{"GB", "MB"};
+    string suffix{"KB"};
+
+    while ((speedrate /= 1000) > 999) {
+      suffix = suffixes.back();
+      suffixes.pop_back();
+    }
+
+    return sstream() << std::setw(minwidth) << std::setfill(' ') << std::setprecision(0) << std::fixed << speedrate
+                     << " " << suffix << "/s";
+  }
+
+  // }}}
+  // class : wired_network {{{
+
+  /**
+   * Query device driver for information
+   */
+  bool wired_network::query(bool accumulate) {
+    if (!network::query(accumulate)) {
+      return false;
+    }
+
+    if (m_tuntap) {
+      return true;
+    }
+
+    if (m_bridge) {
+      /* If bridge network then link speed cannot be computed
+       * TODO: Identify the physical network in bridge and compute the link speed
+       */
+      return true;
+    }
+
+    struct ifreq request {};
+    struct ethtool_cmd data {};
+
+    memset(&request, 0, sizeof(request));
+    strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
+    data.cmd = ETHTOOL_GSET;
+    request.ifr_data = reinterpret_cast<char*>(&data);
+
+    if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
+      m_linkspeed = -1;
+    } else {
+      m_linkspeed = data.speed;
+    }
+
+    return true;
+  }
+
+  /**
+   * Check current connection state
+   */
+  bool wired_network::connected() const {
+    if (!m_tuntap && !network::test_interface()) {
+      return false;
+    }
+
+    struct ethtool_value data {};
+    struct ifreq request {};
+
+    memset(&request, 0, sizeof(request));
+    strncpy(request.ifr_name, m_interface.c_str(), IFNAMSIZ - 1);
+    data.cmd = ETHTOOL_GLINK;
+    request.ifr_data = reinterpret_cast<char*>(&data);
+
+    if (ioctl(*m_socketfd, SIOCETHTOOL, &request) == -1) {
+      return false;
+    }
+
+    return data.data != 0;
+  }
+
+  /**
+   *
+   * about the current connection
+   */
+  string wired_network::linkspeed() const {
+    return m_linkspeed == -1 ? "N/A"
+                             : (m_linkspeed < 1000 ? (to_string(m_linkspeed) + " Mbit/s")
+                                                   : (to_string(m_linkspeed / 1000) + " Gbit/s"));
+  }
+
+  // }}}
+
+}  // namespace net
+
+POLYBAR_NS_END
diff --git a/src/adapters/net_iw.cpp b/src/adapters/net_iw.cpp
new file mode 100644 (file)
index 0000000..49c46f9
--- /dev/null
@@ -0,0 +1,137 @@
+#include "adapters/net.hpp"
+
+#include "utils/file.hpp"
+
+POLYBAR_NS
+
+namespace net {
+  // class : wireless_network {{{
+
+  /**
+   * Query the wireless device for information
+   * about the current connection
+   */
+  bool wireless_network::query(bool accumulate) {
+    if (!network::query(accumulate)) {
+      return false;
+    }
+
+    auto socket_fd = file_util::make_file_descriptor(iw_sockets_open());
+    if (!*socket_fd) {
+      return false;
+    }
+
+    struct iwreq req {};
+
+    if (iw_get_ext(*socket_fd, m_interface.c_str(), SIOCGIWMODE, &req) == -1) {
+      return false;
+    }
+
+    // Ignore interfaces in ad-hoc mode
+    if (req.u.mode == IW_MODE_ADHOC) {
+      return false;
+    }
+
+    query_essid(*socket_fd);
+    query_quality(*socket_fd);
+
+    return true;
+  }
+
+  /**
+   * Check current connection state
+   */
+  bool wireless_network::connected() const {
+    if (!network::test_interface()) {
+      return false;
+    }
+    return !m_essid.empty();
+  }
+
+  /**
+   * ESSID reported by last query
+   */
+  string wireless_network::essid() const {
+    return m_essid;
+  }
+
+  /**
+   * Signal strength percentage reported by last query
+   */
+  int wireless_network::signal() const {
+    return m_signalstrength.percentage();
+  }
+
+  /**
+   * Link quality percentage reported by last query
+   */
+  int wireless_network::quality() const {
+    return m_linkquality.percentage();
+  }
+
+  /**
+   * Query for ESSID
+   */
+  void wireless_network::query_essid(const int& socket_fd) {
+    char essid[IW_ESSID_MAX_SIZE + 1];
+
+    struct iwreq req {};
+    req.u.essid.pointer = &essid;
+    req.u.essid.length = sizeof(essid);
+    req.u.essid.flags = 0;
+
+    if (iw_get_ext(socket_fd, m_interface.c_str(), SIOCGIWESSID, &req) != -1) {
+      m_essid = string{essid};
+    } else {
+      m_essid.clear();
+    }
+  }
+
+  /**
+   * Query for device driver quality values
+   */
+  void wireless_network::query_quality(const int& socket_fd) {
+    iwrange range{};
+    iwstats stats{};
+
+    // Fill range
+    if (iw_get_range_info(socket_fd, m_interface.c_str(), &range) == -1) {
+      return;
+    }
+    // Fill stats
+    if (iw_get_stats(socket_fd, m_interface.c_str(), &stats, &range, 1) == -1) {
+      return;
+    }
+
+    // Check if the driver supplies the quality value
+    if (stats.qual.updated & IW_QUAL_QUAL_INVALID) {
+      return;
+    }
+    // Check if the driver supplies the quality level value
+    if (stats.qual.updated & IW_QUAL_LEVEL_INVALID) {
+      return;
+    }
+
+    // Check if the link quality has been uodated
+    if (stats.qual.updated & IW_QUAL_QUAL_UPDATED) {
+      m_linkquality.val = stats.qual.qual;
+      m_linkquality.max = range.max_qual.qual;
+    }
+
+    // Check if the signal strength has been uodated
+    if (stats.qual.updated & IW_QUAL_LEVEL_UPDATED) {
+      m_signalstrength.val = stats.qual.level;
+      m_signalstrength.max = range.max_qual.level;
+
+      // Check if the values are defined in dBm
+      if (stats.qual.level > range.max_qual.level) {
+        m_signalstrength.val -= 0x100;
+        m_signalstrength.max = (stats.qual.level - range.max_qual.level) - 0x100;
+      }
+    }
+  }
+
+  // }}}
+}  // namespace net
+
+POLYBAR_NS_END
diff --git a/src/adapters/net_nl.cpp b/src/adapters/net_nl.cpp
new file mode 100644 (file)
index 0000000..0a72601
--- /dev/null
@@ -0,0 +1,231 @@
+#include "adapters/net.hpp"
+
+#include <linux/nl80211.h>
+#include <netlink/genl/ctrl.h>
+#include <netlink/genl/genl.h>
+
+#include "utils/file.hpp"
+
+POLYBAR_NS
+
+namespace net {
+  // class : wireless_network {{{
+
+  /**
+   * Query the wireless device for information
+   * about the current connection
+   */
+  bool wireless_network::query(bool accumulate) {
+    if (!network::query(accumulate)) {
+      return false;
+    }
+
+    struct nl_sock* sk = nl_socket_alloc();
+    if (sk == nullptr) {
+      return false;
+    }
+
+    if (genl_connect(sk) < 0) {
+      return false;
+    }
+
+    int driver_id = genl_ctrl_resolve(sk, "nl80211");
+    if (driver_id < 0) {
+      nl_socket_free(sk);
+      return false;
+    }
+
+    if (nl_socket_modify_cb(sk, NL_CB_VALID, NL_CB_CUSTOM, scan_cb, this) != 0) {
+      nl_socket_free(sk);
+      return false;
+    }
+
+    struct nl_msg* msg = nlmsg_alloc();
+    if (msg == nullptr) {
+      nl_socket_free(sk);
+      return false;
+    }
+
+    if ((genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, driver_id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0) == nullptr) ||
+        nla_put_u32(msg, NL80211_ATTR_IFINDEX, m_ifid) < 0) {
+      nlmsg_free(msg);
+      nl_socket_free(sk);
+      return false;
+    }
+
+    // nl_send_sync always frees msg
+    if (nl_send_sync(sk, msg) < 0) {
+      nl_socket_free(sk);
+      return false;
+    }
+
+    nl_socket_free(sk);
+
+    return true;
+  }
+
+  /**
+   * Check current connection state
+   */
+  bool wireless_network::connected() const {
+    if (!network::test_interface()) {
+      return false;
+    }
+    return !m_essid.empty();
+  }
+
+  /**
+   * ESSID reported by last query
+   */
+  string wireless_network::essid() const {
+    return m_essid;
+  }
+
+  /**
+   * Signal strength percentage reported by last query
+   */
+  int wireless_network::signal() const {
+    return m_signalstrength.percentage();
+  }
+
+  /**
+   * Link quality percentage reported by last query
+   */
+  int wireless_network::quality() const {
+    return m_linkquality.percentage();
+  }
+
+  /**
+   * Callback to parse scan results
+   */
+  int wireless_network::scan_cb(struct nl_msg* msg, void* instance) {
+    auto wn = static_cast<wireless_network*>(instance);
+    auto gnlh = static_cast<genlmsghdr*>(nlmsg_data(nlmsg_hdr(msg)));
+    struct nlattr* tb[NL80211_ATTR_MAX + 1];
+    struct nlattr* bss[NL80211_BSS_MAX + 1];
+
+    struct nla_policy bss_policy[NL80211_BSS_MAX + 1]{};
+    bss_policy[NL80211_BSS_TSF].type = NLA_U64;
+    bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32;
+    bss_policy[NL80211_BSS_BSSID].type = NLA_UNSPEC;
+    bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16;
+    bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16;
+    bss_policy[NL80211_BSS_INFORMATION_ELEMENTS].type = NLA_UNSPEC;
+    bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32;
+    bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8;
+    bss_policy[NL80211_BSS_STATUS].type = NLA_U32;
+
+    if (nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), nullptr) < 0) {
+      return NL_SKIP;
+    }
+
+    if (tb[NL80211_ATTR_BSS] == nullptr) {
+      return NL_SKIP;
+    }
+
+    if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS], bss_policy) != 0) {
+      return NL_SKIP;
+    }
+
+    if (!wn->associated_or_joined(bss)) {
+      return NL_SKIP;
+    }
+
+    wn->parse_essid(bss);
+    wn->parse_frequency(bss);
+    wn->parse_signal(bss);
+    wn->parse_quality(bss);
+
+    return NL_SKIP;
+  }
+
+  /**
+   * Check for a connection to a AP
+   */
+  bool wireless_network::associated_or_joined(struct nlattr** bss) {
+    if (bss[NL80211_BSS_STATUS] == nullptr) {
+      return false;
+    }
+
+    auto status = nla_get_u32(bss[NL80211_BSS_STATUS]);
+
+    switch (status) {
+      case NL80211_BSS_STATUS_ASSOCIATED:
+      case NL80211_BSS_STATUS_IBSS_JOINED:
+      case NL80211_BSS_STATUS_AUTHENTICATED:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  /**
+   * Set the ESSID
+   */
+  void wireless_network::parse_essid(struct nlattr** bss) {
+    m_essid.clear();
+
+    if (bss[NL80211_BSS_INFORMATION_ELEMENTS] != nullptr) {
+      // Information Element ID from ieee80211.h
+      #define WLAN_EID_SSID 0
+
+      auto ies = static_cast<char*>(nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]));
+      auto ies_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
+      const auto hdr_len = 2;
+
+      while (ies_len > hdr_len && ies[0] != WLAN_EID_SSID) {
+        ies_len -= ies[1] + hdr_len;
+        ies += ies[1] + hdr_len;
+      }
+
+      if (ies_len > hdr_len && ies_len > ies[1] + hdr_len) {
+        auto essid_begin = ies + hdr_len;
+        auto essid_end = essid_begin + ies[1];
+
+        std::copy(essid_begin, essid_end, std::back_inserter(m_essid));
+      }
+    }
+  }
+
+  /**
+   * Set frequency
+   */
+  void wireless_network::parse_frequency(struct nlattr** bss) {
+    if (bss[NL80211_BSS_FREQUENCY] != nullptr) {
+      // in MHz
+      m_frequency = static_cast<int>(nla_get_u32(bss[NL80211_BSS_FREQUENCY]));
+    }
+  }
+
+  /**
+   * Set device driver quality values
+   */
+  void wireless_network::parse_quality(struct nlattr** bss) {
+    if (bss[NL80211_BSS_SIGNAL_UNSPEC] != nullptr) {
+      // Signal strength in unspecified units, scaled to 0..100 (u8)
+      m_linkquality.val = nla_get_u8(bss[NL80211_BSS_SIGNAL_UNSPEC]);
+      m_linkquality.max = 100;
+    }
+  }
+
+  /**
+   * Set the signalstrength
+   */
+  void wireless_network::parse_signal(struct nlattr** bss) {
+    if (bss[NL80211_BSS_SIGNAL_MBM] != nullptr) {
+      // signalstrength in dBm
+      int signalstrength = static_cast<int>(nla_get_u32(bss[NL80211_BSS_SIGNAL_MBM])) / 100;
+
+      // WiFi-hardware usually operates in the range -90 to -20dBm.
+      const int hardware_max = -20;
+      const int hardware_min = -90;
+      signalstrength = std::max(hardware_min, std::min(signalstrength, hardware_max));
+
+      // Shift for positive values
+      m_signalstrength.val = signalstrength - hardware_min;
+      m_signalstrength.max = hardware_max - hardware_min;
+    }
+  }
+}  // namespace net
+
+POLYBAR_NS_END
diff --git a/src/adapters/pulseaudio.cpp b/src/adapters/pulseaudio.cpp
new file mode 100644 (file)
index 0000000..9f0c1b3
--- /dev/null
@@ -0,0 +1,329 @@
+#include "adapters/pulseaudio.hpp"
+#include "components/logger.hpp"
+
+POLYBAR_NS
+
+/**
+ * Construct pulseaudio object
+ */
+pulseaudio::pulseaudio(const logger& logger, string&& sink_name, bool max_volume) : m_log(logger), spec_s_name(sink_name) {
+  m_mainloop = pa_threaded_mainloop_new();
+  if (!m_mainloop) {
+    throw pulseaudio_error("Could not create pulseaudio threaded mainloop.");
+  }
+  pa_threaded_mainloop_lock(m_mainloop);
+
+  m_context = pa_context_new(pa_threaded_mainloop_get_api(m_mainloop), "polybar");
+  if (!m_context) {
+    pa_threaded_mainloop_unlock(m_mainloop);
+    pa_threaded_mainloop_free(m_mainloop);
+    throw pulseaudio_error("Could not create pulseaudio context.");
+  }
+
+  pa_context_set_state_callback(m_context, context_state_callback, this);
+
+  if (pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0) {
+    pa_context_disconnect(m_context);
+    pa_context_unref(m_context);
+    pa_threaded_mainloop_unlock(m_mainloop);
+    pa_threaded_mainloop_free(m_mainloop);
+    throw pulseaudio_error("Could not connect pulseaudio context.");
+  }
+
+  if (pa_threaded_mainloop_start(m_mainloop) < 0) {
+    pa_context_disconnect(m_context);
+    pa_context_unref(m_context);
+    pa_threaded_mainloop_unlock(m_mainloop);
+    pa_threaded_mainloop_free(m_mainloop);
+    throw pulseaudio_error("Could not start pulseaudio mainloop.");
+  }
+
+  m_log.trace("pulseaudio: started mainloop");
+
+  pa_threaded_mainloop_wait(m_mainloop);
+  if (pa_context_get_state(m_context) != PA_CONTEXT_READY) {
+    pa_threaded_mainloop_unlock(m_mainloop);
+    pa_threaded_mainloop_stop(m_mainloop);
+    pa_context_disconnect(m_context);
+    pa_context_unref(m_context);
+    pa_threaded_mainloop_free(m_mainloop);
+    throw pulseaudio_error("Could not connect to pulseaudio server.");
+  }
+
+  pa_operation *op{nullptr};
+  if (!sink_name.empty()) {
+    op = pa_context_get_sink_info_by_name(m_context, sink_name.c_str(), sink_info_callback, this);
+    wait_loop(op, m_mainloop);
+  }
+  if (s_name.empty()) {
+    // get the sink index
+    op = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this);
+    wait_loop(op, m_mainloop);
+    m_log.notice("pulseaudio: using default sink %s", s_name);
+  } else {
+    m_log.trace("pulseaudio: using sink %s", s_name);
+  }
+
+  m_max_volume = max_volume ? PA_VOLUME_UI_MAX : PA_VOLUME_NORM;
+
+  auto event_types = static_cast<pa_subscription_mask_t>(PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SERVER);
+  op = pa_context_subscribe(m_context, event_types, simple_callback, this);
+  wait_loop(op, m_mainloop);
+  if (!success)
+    throw pulseaudio_error("Failed to subscribe to sink.");
+  pa_context_set_subscribe_callback(m_context, subscribe_callback, this);
+
+  update_volume(op);
+
+  pa_threaded_mainloop_unlock(m_mainloop);
+
+}
+
+/**
+ * Deconstruct pulseaudio
+ */
+pulseaudio::~pulseaudio() {
+  pa_threaded_mainloop_stop(m_mainloop);
+  pa_context_disconnect(m_context);
+  pa_context_unref(m_context);
+  pa_threaded_mainloop_free(m_mainloop);
+
+}
+
+/**
+ * Get sink name
+ */
+const string& pulseaudio::get_name() {
+  return s_name;
+}
+
+/**
+ * Wait for events
+ */
+bool pulseaudio::wait() {
+  return m_events.size() > 0;
+}
+
+/**
+ * Process queued pulseaudio events
+ */
+int pulseaudio::process_events() {
+  int ret = m_events.size();
+  pa_threaded_mainloop_lock(m_mainloop);
+  pa_operation *o{nullptr};
+  // clear the queue
+  while (!m_events.empty()) {
+    switch (m_events.front()) {
+      // try to get specified sink
+      case evtype::NEW:
+        // redundant if already using specified sink
+        if (!spec_s_name.empty()) {
+          o = pa_context_get_sink_info_by_name(m_context, spec_s_name.c_str(), sink_info_callback, this);
+          wait_loop(o, m_mainloop);
+          break;
+        }
+        // FALLTHRU
+      case evtype::SERVER:
+        // don't fallthrough only if always using default sink
+        if (!spec_s_name.empty()) {
+          break;
+        }
+        // FALLTHRU
+      // get default sink
+      case evtype::REMOVE:
+        o = pa_context_get_sink_info_by_name(m_context, DEFAULT_SINK, sink_info_callback, this);
+        wait_loop(o, m_mainloop);
+        if (spec_s_name != s_name)
+          m_log.notice("pulseaudio: using default sink %s", s_name);
+        break;
+      default:
+        break;
+    }
+    update_volume(o);
+    m_events.pop();
+  }
+  pa_threaded_mainloop_unlock(m_mainloop);
+  return ret;
+}
+
+/**
+ * Get volume in percentage
+ */
+int pulseaudio::get_volume() {
+  // alternatively, user pa_cvolume_avg_mask() to average selected channels
+  return static_cast<int>(pa_cvolume_max(&cv) * 100.0f / PA_VOLUME_NORM + 0.5f);
+}
+
+/**
+ * Get volume in decibels
+ */
+double pulseaudio::get_decibels() {
+  return pa_sw_volume_to_dB(pa_cvolume_max(&cv));
+}
+
+/**
+ * Set volume to given percentage
+ */
+void pulseaudio::set_volume(float percentage) {
+  pa_threaded_mainloop_lock(m_mainloop);
+  pa_volume_t vol = math_util::percentage_to_value<pa_volume_t>(percentage, PA_VOLUME_MUTED, PA_VOLUME_NORM);
+  pa_cvolume_scale(&cv, vol);
+  pa_operation *op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this);
+  wait_loop(op, m_mainloop);
+  if (!success)
+    throw pulseaudio_error("Failed to set sink volume.");
+  pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Increment or decrement volume by given percentage (prevents accumulation of rounding errors from get_volume)
+ */
+void pulseaudio::inc_volume(int delta_perc) {
+  pa_threaded_mainloop_lock(m_mainloop);
+  pa_volume_t vol = math_util::percentage_to_value<pa_volume_t>(abs(delta_perc), PA_VOLUME_NORM);
+  if (delta_perc > 0) {
+    pa_volume_t current = pa_cvolume_max(&cv);
+    if (current + vol <= m_max_volume) {
+      pa_cvolume_inc(&cv, vol);
+    } else if (current < m_max_volume) {
+      // avoid rounding errors and set to m_max_volume directly
+      pa_cvolume_scale(&cv, m_max_volume);
+    } else {
+      m_log.notice("pulseaudio: maximum volume reached");
+    }
+  } else
+    pa_cvolume_dec(&cv, vol);
+  pa_operation *op = pa_context_set_sink_volume_by_index(m_context, m_index, &cv, simple_callback, this);
+  wait_loop(op, m_mainloop);
+  if (!success)
+    throw pulseaudio_error("Failed to set sink volume.");
+  pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Set mute state
+ */
+void pulseaudio::set_mute(bool mode) {
+  pa_threaded_mainloop_lock(m_mainloop);
+  pa_operation *op = pa_context_set_sink_mute_by_index(m_context, m_index, mode, simple_callback, this);
+  wait_loop(op, m_mainloop);
+  if (!success)
+    throw pulseaudio_error("Failed to mute sink.");
+  pa_threaded_mainloop_unlock(m_mainloop);
+}
+
+/**
+ * Toggle mute state
+ */
+void pulseaudio::toggle_mute() {
+  set_mute(!is_muted());
+}
+
+/**
+ * Get current mute state
+ */
+bool pulseaudio::is_muted() {
+  return muted;
+}
+
+/**
+ * Update local volume cache
+ */
+void pulseaudio::update_volume(pa_operation *o) {
+  o = pa_context_get_sink_info_by_index(m_context, m_index, get_sink_volume_callback, this);
+  wait_loop(o, m_mainloop);
+}
+
+/**
+ * Callback when getting volume
+ */
+void pulseaudio::get_sink_volume_callback(pa_context *, const pa_sink_info *info, int, void *userdata) {
+  pulseaudio* This = static_cast<pulseaudio *>(userdata);
+  if (info) {
+    This->cv = info->volume;
+    This->muted = info->mute;
+  }
+  pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Callback when subscribing to changes
+ */
+void pulseaudio::subscribe_callback(pa_context *, pa_subscription_event_type_t t, uint32_t idx, void* userdata) {
+  pulseaudio *This = static_cast<pulseaudio *>(userdata);
+  switch(t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+    case PA_SUBSCRIPTION_EVENT_SERVER:
+      switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
+        case PA_SUBSCRIPTION_EVENT_CHANGE:
+          This->m_events.emplace(evtype::SERVER);
+        break;
+      }
+      break;
+    case PA_SUBSCRIPTION_EVENT_SINK:
+      switch(t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
+        case PA_SUBSCRIPTION_EVENT_NEW:
+            This->m_events.emplace(evtype::NEW);
+          break;
+        case PA_SUBSCRIPTION_EVENT_CHANGE:
+          if (idx == This->m_index)
+            This->m_events.emplace(evtype::CHANGE);
+          break;
+        case PA_SUBSCRIPTION_EVENT_REMOVE:
+          if (idx == This->m_index)
+            This->m_events.emplace(evtype::REMOVE);
+          break;
+      }
+      break;
+  }
+  pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Simple callback to check for success
+ */
+void pulseaudio::simple_callback(pa_context *, int success, void *userdata) {
+  pulseaudio *This = static_cast<pulseaudio *>(userdata);
+  This->success = success;
+  pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+
+/**
+ * Callback when getting sink info & existence
+ */
+void pulseaudio::sink_info_callback(pa_context *, const pa_sink_info *info, int eol, void *userdata) {
+  pulseaudio *This = static_cast<pulseaudio *>(userdata);
+  if (!eol && info) {
+    This->m_index = info->index;
+    This->s_name = info->name;
+  }
+  pa_threaded_mainloop_signal(This->m_mainloop, 0);
+}
+
+/**
+ * Callback when context state changes
+ */
+void pulseaudio::context_state_callback(pa_context *context, void *userdata) {
+  pulseaudio* This = static_cast<pulseaudio *>(userdata);
+  switch (pa_context_get_state(context)) {
+    case PA_CONTEXT_READY:
+    case PA_CONTEXT_TERMINATED:
+    case PA_CONTEXT_FAILED:
+      pa_threaded_mainloop_signal(This->m_mainloop, 0);
+      break;
+
+    case PA_CONTEXT_UNCONNECTED:
+    case PA_CONTEXT_CONNECTING:
+    case PA_CONTEXT_AUTHORIZING:
+    case PA_CONTEXT_SETTING_NAME:
+      break;
+  }
+}
+
+inline void pulseaudio::wait_loop(pa_operation *op, pa_threaded_mainloop *loop) {
+  while (pa_operation_get_state(op) != PA_OPERATION_DONE)
+    pa_threaded_mainloop_wait(loop);
+  pa_operation_unref(op);
+}
+
+POLYBAR_NS_END
diff --git a/src/cairo/utils.cpp b/src/cairo/utils.cpp
new file mode 100644 (file)
index 0000000..9fce145
--- /dev/null
@@ -0,0 +1,176 @@
+#include <map>
+
+#include "cairo/utils.hpp"
+
+POLYBAR_NS
+
+namespace cairo {
+  namespace utils {
+
+    // implementation : device_lock {{{
+
+    device_lock::device_lock(cairo_device_t* device) {
+      auto status = cairo_device_acquire(device);
+      if (status == CAIRO_STATUS_SUCCESS) {
+        m_device = device;
+      }
+    }
+    device_lock::~device_lock() {
+      cairo_device_release(m_device);
+    }
+    device_lock::operator bool() const {
+      return m_device != nullptr;
+    }
+    device_lock::operator cairo_device_t*() const {
+      return m_device;
+    }
+
+    // }}}
+    // implementation : ft_face_lock {{{
+
+    ft_face_lock::ft_face_lock(cairo_scaled_font_t* font) : m_font(font) {
+      m_face = cairo_ft_scaled_font_lock_face(m_font);
+    }
+    ft_face_lock::~ft_face_lock() {
+      cairo_ft_scaled_font_unlock_face(m_font);
+    }
+    ft_face_lock::operator FT_Face() const {
+      return m_face;
+    }
+
+    // }}}
+    // implementation : unicode_character {{{
+
+    unicode_character::unicode_character() : codepoint(0), offset(0), length(0) {}
+
+    // }}}
+
+    /**
+     * \see <cairo/cairo.h>
+     */
+    cairo_operator_t str2operator(const string& mode, cairo_operator_t fallback) {
+      if (mode.empty()) {
+        return fallback;
+      }
+      static std::map<string, cairo_operator_t> modes;
+      if (modes.empty()) {
+        modes["clear"s] = CAIRO_OPERATOR_CLEAR;
+        modes["source"s] = CAIRO_OPERATOR_SOURCE;
+        modes["over"s] = CAIRO_OPERATOR_OVER;
+        modes["in"s] = CAIRO_OPERATOR_IN;
+        modes["out"s] = CAIRO_OPERATOR_OUT;
+        modes["atop"s] = CAIRO_OPERATOR_ATOP;
+        modes["dest"s] = CAIRO_OPERATOR_DEST;
+        modes["dest-over"s] = CAIRO_OPERATOR_DEST_OVER;
+        modes["dest-in"s] = CAIRO_OPERATOR_DEST_IN;
+        modes["dest-out"s] = CAIRO_OPERATOR_DEST_OUT;
+        modes["dest-atop"s] = CAIRO_OPERATOR_DEST_ATOP;
+        modes["xor"s] = CAIRO_OPERATOR_XOR;
+        modes["add"s] = CAIRO_OPERATOR_ADD;
+        modes["saturate"s] = CAIRO_OPERATOR_SATURATE;
+        modes["multiply"s] = CAIRO_OPERATOR_MULTIPLY;
+        modes["screen"s] = CAIRO_OPERATOR_SCREEN;
+        modes["overlay"s] = CAIRO_OPERATOR_OVERLAY;
+        modes["darken"s] = CAIRO_OPERATOR_DARKEN;
+        modes["lighten"s] = CAIRO_OPERATOR_LIGHTEN;
+        modes["color-dodge"s] = CAIRO_OPERATOR_COLOR_DODGE;
+        modes["color-burn"s] = CAIRO_OPERATOR_COLOR_BURN;
+        modes["hard-light"s] = CAIRO_OPERATOR_HARD_LIGHT;
+        modes["soft-light"s] = CAIRO_OPERATOR_SOFT_LIGHT;
+        modes["difference"s] = CAIRO_OPERATOR_DIFFERENCE;
+        modes["exclusion"s] = CAIRO_OPERATOR_EXCLUSION;
+        modes["hsl-hue"s] = CAIRO_OPERATOR_HSL_HUE;
+        modes["hsl-saturation"s] = CAIRO_OPERATOR_HSL_SATURATION;
+        modes["hsl-color"s] = CAIRO_OPERATOR_HSL_COLOR;
+        modes["hsl-luminosity"s] = CAIRO_OPERATOR_HSL_LUMINOSITY;
+      }
+      auto it = modes.find(mode);
+      return it != modes.end() ? it->second : fallback;
+    }
+
+    /**
+     * \brief Create a UCS-4 codepoint from a utf-8 encoded string
+     */
+    bool utf8_to_ucs4(const unsigned char* src, unicode_charlist& result_list) {
+      if (!src) {
+        return false;
+      }
+      const unsigned char* first = src;
+      while (*first) {
+        int len = 0;
+        unsigned long result = 0;
+        if ((*first >> 7) == 0) {
+          len = 1;
+          result = *first;
+        } else if ((*first >> 5) == 6) {
+          len = 2;
+          result = *first & 31;
+        } else if ((*first >> 4) == 14) {
+          len = 3;
+          result = *first & 15;
+        } else if ((*first >> 3) == 30) {
+          len = 4;
+          result = *first & 7;
+        } else {
+          return false;
+        }
+        const unsigned char* next;
+        for (next = first + 1; *next && ((*next >> 6) == 2) && (next - first < len); next++) {
+          result = result << 6;
+          result |= *next & 63;
+        }
+        unicode_character uc_char;
+        uc_char.codepoint = result;
+        uc_char.offset = first - src;
+        uc_char.length = next - first;
+        result_list.push_back(uc_char);
+        first = next;
+      }
+      return true;
+    }
+
+    /**
+     * \brief Convert a UCS-4 codepoint to a utf-8 encoded string
+     */
+    size_t ucs4_to_utf8(char* utf8, unsigned int ucs) {
+      if (ucs <= 0x7f) {
+        *utf8 = ucs;
+        return 1;
+      } else if (ucs <= 0x07ff) {
+        *(utf8++) = ((ucs >> 6) & 0xff) | 0xc0;
+        *utf8 = (ucs & 0x3f) | 0x80;
+        return 2;
+      } else if (ucs <= 0xffff) {
+        *(utf8++) = ((ucs >> 12) & 0x0f) | 0xe0;
+        *(utf8++) = ((ucs >> 6) & 0x3f) | 0x80;
+        *utf8 = (ucs & 0x3f) | 0x80;
+        return 3;
+      } else if (ucs <= 0x1fffff) {
+        *(utf8++) = ((ucs >> 18) & 0x07) | 0xf0;
+        *(utf8++) = ((ucs >> 12) & 0x3f) | 0x80;
+        *(utf8++) = ((ucs >> 6) & 0x3f) | 0x80;
+        *utf8 = (ucs & 0x3f) | 0x80;
+        return 4;
+      } else if (ucs <= 0x03ffffff) {
+        *(utf8++) = ((ucs >> 24) & 0x03) | 0xf8;
+        *(utf8++) = ((ucs >> 18) & 0x3f) | 0x80;
+        *(utf8++) = ((ucs >> 12) & 0x3f) | 0x80;
+        *(utf8++) = ((ucs >> 6) & 0x3f) | 0x80;
+        *utf8 = (ucs & 0x3f) | 0x80;
+        return 5;
+      } else if (ucs <= 0x7fffffff) {
+        *(utf8++) = ((ucs >> 30) & 0x01) | 0xfc;
+        *(utf8++) = ((ucs >> 24) & 0x3f) | 0x80;
+        *(utf8++) = ((ucs >> 18) & 0x3f) | 0x80;
+        *(utf8++) = ((ucs >> 12) & 0x3f) | 0x80;
+        *(utf8++) = ((ucs >> 6) & 0x3f) | 0x80;
+        *utf8 = (ucs & 0x3f) | 0x80;
+        return 6;
+      } else {
+        return 0;
+      }
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/components/bar.cpp b/src/components/bar.cpp
new file mode 100644 (file)
index 0000000..4fdfbce
--- /dev/null
@@ -0,0 +1,978 @@
+#include "components/bar.hpp"
+
+#include <algorithm>
+
+#include "components/config.hpp"
+#include "components/parser.hpp"
+#include "components/renderer.hpp"
+#include "components/screen.hpp"
+#include "components/taskqueue.hpp"
+#include "components/types.hpp"
+#include "drawtypes/label.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "utils/bspwm.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/icccm.hpp"
+#include "x11/tray_manager.hpp"
+
+#if WITH_XCURSOR
+#include "x11/cursor.hpp"
+#endif
+
+#if ENABLE_I3
+#include "utils/i3.hpp"
+#endif
+
+POLYBAR_NS
+
+using namespace signals::ui;
+
+/**
+ * Create instance
+ */
+bar::make_type bar::make(bool only_initialize_values) {
+  // clang-format off
+  return factory_util::unique<bar>(
+        connection::make(),
+        signal_emitter::make(),
+        config::make(),
+        logger::make(),
+        screen::make(),
+        tray_manager::make(),
+        parser::make(),
+        taskqueue::make(),
+        only_initialize_values);
+  // clang-format on
+}
+
+/**
+ * Construct bar instance
+ *
+ * TODO: Break out all tray handling
+ */
+bar::bar(connection& conn, signal_emitter& emitter, const config& config, const logger& logger,
+    unique_ptr<screen>&& screen, unique_ptr<tray_manager>&& tray_manager, unique_ptr<parser>&& parser,
+    unique_ptr<taskqueue>&& taskqueue, bool only_initialize_values)
+    : m_connection(conn)
+    , m_sig(emitter)
+    , m_conf(config)
+    , m_log(logger)
+    , m_screen(forward<decltype(screen)>(screen))
+    , m_tray(forward<decltype(tray_manager)>(tray_manager))
+    , m_parser(forward<decltype(parser)>(parser))
+    , m_taskqueue(forward<decltype(taskqueue)>(taskqueue)) {
+  string bs{m_conf.section()};
+
+  // Get available RandR outputs
+  auto monitor_name = m_conf.get(bs, "monitor", ""s);
+  auto monitor_name_fallback = m_conf.get(bs, "monitor-fallback", ""s);
+  m_opts.monitor_strict = m_conf.get(bs, "monitor-strict", m_opts.monitor_strict);
+  m_opts.monitor_exact = m_conf.get(bs, "monitor-exact", m_opts.monitor_exact);
+  auto monitors = randr_util::get_monitors(m_connection, m_connection.screen()->root, m_opts.monitor_strict, false);
+
+  if (monitors.empty()) {
+    throw application_error("No monitors found");
+  }
+
+  // if monitor_name is not defined, first check for primary monitor
+  if (monitor_name.empty()) {
+    for (auto&& mon : monitors) {
+      if (mon->primary) {
+        monitor_name = mon->name;
+        break;
+      }
+    }
+  }
+
+  // if still not found (and not strict matching), get first connected monitor
+  if (monitor_name.empty() && !m_opts.monitor_strict) {
+    auto connected_monitors = randr_util::get_monitors(m_connection, m_connection.screen()->root, true, false);
+    if (!connected_monitors.empty()) {
+      monitor_name = connected_monitors[0]->name;
+      m_log.warn("No monitor specified, using \"%s\"", monitor_name);
+    }
+  }
+
+  // if still not found, get first monitor
+  if (monitor_name.empty()) {
+    monitor_name = monitors[0]->name;
+    m_log.warn("No monitor specified, using \"%s\"", monitor_name);
+  }
+
+  // get the monitor data based on the name
+  m_opts.monitor = randr_util::match_monitor(monitors, monitor_name, m_opts.monitor_exact);
+  monitor_t fallback{};
+
+  if (!monitor_name_fallback.empty()) {
+    fallback = randr_util::match_monitor(monitors, monitor_name_fallback, m_opts.monitor_exact);
+  }
+
+  if (!m_opts.monitor) {
+    if (fallback) {
+      m_opts.monitor = move(fallback);
+      m_log.warn("Monitor \"%s\" not found, reverting to fallback \"%s\"", monitor_name, m_opts.monitor->name);
+    } else {
+      throw application_error("Monitor \"" + monitor_name + "\" not found or disconnected");
+    }
+  }
+
+  m_log.info("Loaded monitor %s (%ix%i+%i+%i)", m_opts.monitor->name, m_opts.monitor->w, m_opts.monitor->h,
+      m_opts.monitor->x, m_opts.monitor->y);
+
+  try {
+    m_opts.override_redirect = m_conf.get<bool>(bs, "dock");
+    m_conf.warn_deprecated(bs, "dock", "override-redirect");
+  } catch (const key_error& err) {
+    m_opts.override_redirect = m_conf.get(bs, "override-redirect", m_opts.override_redirect);
+  }
+
+  m_opts.dimvalue = m_conf.get(bs, "dim-value", 1.0);
+  m_opts.dimvalue = math_util::cap(m_opts.dimvalue, 0.0, 1.0);
+
+  m_opts.cursor_click = m_conf.get(bs, "cursor-click", ""s);
+  m_opts.cursor_scroll = m_conf.get(bs, "cursor-scroll", ""s);
+#if WITH_XCURSOR
+  if (!m_opts.cursor_click.empty() && !cursor_util::valid(m_opts.cursor_click)) {
+    m_log.warn("Ignoring unsupported cursor-click option '%s'", m_opts.cursor_click);
+    m_opts.cursor_click.clear();
+  }
+  if (!m_opts.cursor_scroll.empty() && !cursor_util::valid(m_opts.cursor_scroll)) {
+    m_log.warn("Ignoring unsupported cursor-scroll option '%s'", m_opts.cursor_scroll);
+    m_opts.cursor_scroll.clear();
+  }
+#endif
+
+  // Build WM_NAME
+  m_opts.wmname = m_conf.get(bs, "wm-name", "polybar-" + bs.substr(4) + "_" + m_opts.monitor->name);
+  m_opts.wmname = string_util::replace(m_opts.wmname, " ", "-");
+
+  // Load configuration values
+  m_opts.origin = m_conf.get(bs, "bottom", false) ? edge::BOTTOM : edge::TOP;
+  m_opts.spacing = m_conf.get(bs, "spacing", m_opts.spacing);
+  m_opts.separator = drawtypes::load_optional_label(m_conf, bs, "separator", "");
+  m_opts.locale = m_conf.get(bs, "locale", ""s);
+
+  auto radius = m_conf.get<double>(bs, "radius", 0.0);
+  m_opts.radius.top = m_conf.get(bs, "radius-top", radius);
+  m_opts.radius.bottom = m_conf.get(bs, "radius-bottom", radius);
+
+  auto padding = m_conf.get<unsigned int>(bs, "padding", 0U);
+  m_opts.padding.left = m_conf.get(bs, "padding-left", padding);
+  m_opts.padding.right = m_conf.get(bs, "padding-right", padding);
+
+  auto margin = m_conf.get<unsigned int>(bs, "module-margin", 0U);
+  m_opts.module_margin.left = m_conf.get(bs, "module-margin-left", margin);
+  m_opts.module_margin.right = m_conf.get(bs, "module-margin-right", margin);
+
+  if (only_initialize_values) {
+    return;
+  }
+
+  // Load values used to adjust the struts atom
+  m_opts.strut.top = m_conf.get("global/wm", "margin-top", 0);
+  m_opts.strut.bottom = m_conf.get("global/wm", "margin-bottom", 0);
+
+  // Load commands used for fallback click handlers
+  vector<action> actions;
+  actions.emplace_back(action{mousebtn::LEFT, m_conf.get(bs, "click-left", ""s)});
+  actions.emplace_back(action{mousebtn::MIDDLE, m_conf.get(bs, "click-middle", ""s)});
+  actions.emplace_back(action{mousebtn::RIGHT, m_conf.get(bs, "click-right", ""s)});
+  actions.emplace_back(action{mousebtn::SCROLL_UP, m_conf.get(bs, "scroll-up", ""s)});
+  actions.emplace_back(action{mousebtn::SCROLL_DOWN, m_conf.get(bs, "scroll-down", ""s)});
+  actions.emplace_back(action{mousebtn::DOUBLE_LEFT, m_conf.get(bs, "double-click-left", ""s)});
+  actions.emplace_back(action{mousebtn::DOUBLE_MIDDLE, m_conf.get(bs, "double-click-middle", ""s)});
+  actions.emplace_back(action{mousebtn::DOUBLE_RIGHT, m_conf.get(bs, "double-click-right", ""s)});
+
+  for (auto&& act : actions) {
+    if (!act.command.empty()) {
+      m_opts.actions.emplace_back(action{act.button, act.command});
+    }
+  }
+
+  const auto parse_or_throw_color = [&](string key, rgba def) -> rgba {
+    try {
+      rgba color = m_conf.get(bs, key, def);
+
+      /*
+       * These are the base colors of the bar and cannot be alpha only
+       * In that case, we just use the alpha channel on the default value.
+       */
+      return color.try_apply_alpha_to(def);
+    } catch (const exception& err) {
+      throw application_error(sstream() << "Failed to set " << key << " (reason: " << err.what() << ")");
+    }
+  };
+
+  // Load background
+  for (auto&& step : m_conf.get_list<rgba>(bs, "background", {})) {
+    m_opts.background_steps.emplace_back(step);
+  }
+
+  if (!m_opts.background_steps.empty()) {
+    m_opts.background = m_opts.background_steps[0];
+
+    if (m_conf.has(bs, "background")) {
+      m_log.warn("Ignoring `%s.background` (overridden by gradient background)", bs);
+    }
+  } else {
+    m_opts.background = parse_or_throw_color("background", m_opts.background);
+  }
+
+  // Load foreground
+  m_opts.foreground = parse_or_throw_color("foreground", m_opts.foreground);
+
+  // Load over-/underline
+  auto line_color = m_conf.get(bs, "line-color", rgba{0xFFFF0000});
+  auto line_size = m_conf.get(bs, "line-size", 0);
+
+  m_opts.overline.size = m_conf.get(bs, "overline-size", line_size);
+  m_opts.overline.color = parse_or_throw_color("overline-color", line_color);
+  m_opts.underline.size = m_conf.get(bs, "underline-size", line_size);
+  m_opts.underline.color = parse_or_throw_color("underline-color", line_color);
+
+  // Load border settings
+  auto border_color = m_conf.get(bs, "border-color", rgba{0x00000000});
+  auto border_size = m_conf.get(bs, "border-size", ""s);
+  auto border_top = m_conf.deprecated(bs, "border-top", "border-top-size", border_size);
+  auto border_bottom = m_conf.deprecated(bs, "border-bottom", "border-bottom-size", border_size);
+  auto border_left = m_conf.deprecated(bs, "border-left", "border-left-size", border_size);
+  auto border_right = m_conf.deprecated(bs, "border-right", "border-right-size", border_size);
+
+  m_opts.borders.emplace(edge::TOP, border_settings{});
+  m_opts.borders[edge::TOP].size = geom_format_to_pixels(border_top, m_opts.monitor->h);
+  m_opts.borders[edge::TOP].color = parse_or_throw_color("border-top-color", border_color);
+  m_opts.borders.emplace(edge::BOTTOM, border_settings{});
+  m_opts.borders[edge::BOTTOM].size = geom_format_to_pixels(border_bottom, m_opts.monitor->h);
+  m_opts.borders[edge::BOTTOM].color = parse_or_throw_color("border-bottom-color", border_color);
+  m_opts.borders.emplace(edge::LEFT, border_settings{});
+  m_opts.borders[edge::LEFT].size = geom_format_to_pixels(border_left, m_opts.monitor->w);
+  m_opts.borders[edge::LEFT].color = parse_or_throw_color("border-left-color", border_color);
+  m_opts.borders.emplace(edge::RIGHT, border_settings{});
+  m_opts.borders[edge::RIGHT].size = geom_format_to_pixels(border_right, m_opts.monitor->w);
+  m_opts.borders[edge::RIGHT].color = parse_or_throw_color("border-right-color", border_color);
+
+  // Load geometry values
+  auto w = m_conf.get(m_conf.section(), "width", "100%"s);
+  auto h = m_conf.get(m_conf.section(), "height", "24"s);
+  auto offsetx = m_conf.get(m_conf.section(), "offset-x", ""s);
+  auto offsety = m_conf.get(m_conf.section(), "offset-y", ""s);
+
+  m_opts.size.w = geom_format_to_pixels(w, m_opts.monitor->w);
+  m_opts.size.h = geom_format_to_pixels(h, m_opts.monitor->h);
+  m_opts.offset.x = geom_format_to_pixels(offsetx, m_opts.monitor->w);
+  m_opts.offset.y = geom_format_to_pixels(offsety, m_opts.monitor->h);
+
+  // Apply offsets
+  m_opts.pos.x = m_opts.offset.x + m_opts.monitor->x;
+  m_opts.pos.y = m_opts.offset.y + m_opts.monitor->y;
+  m_opts.size.h += m_opts.borders[edge::TOP].size;
+  m_opts.size.h += m_opts.borders[edge::BOTTOM].size;
+
+  if (m_opts.origin == edge::BOTTOM) {
+    m_opts.pos.y = m_opts.monitor->y + m_opts.monitor->h - m_opts.size.h - m_opts.offset.y;
+  }
+
+  if (m_opts.size.w <= 0 || m_opts.size.w > m_opts.monitor->w) {
+    throw application_error("Resulting bar width is out of bounds (" + to_string(m_opts.size.w) + ")");
+  } else if (m_opts.size.h <= 0 || m_opts.size.h > m_opts.monitor->h) {
+    throw application_error("Resulting bar height is out of bounds (" + to_string(m_opts.size.h) + ")");
+  }
+
+  m_log.info("Bar geometry: %ix%i+%i+%i; Borders: %d,%d,%d,%d", m_opts.size.w, m_opts.size.h, m_opts.pos.x,
+      m_opts.pos.y, m_opts.borders[edge::TOP].size, m_opts.borders[edge::RIGHT].size, m_opts.borders[edge::BOTTOM].size,
+      m_opts.borders[edge::LEFT].size);
+
+  m_log.trace("bar: Attach X event sink");
+  m_connection.attach_sink(this, SINK_PRIORITY_BAR);
+
+  m_log.trace("bar: Attach signal receiver");
+  m_sig.attach(this);
+}
+
+/**
+ * Cleanup signal handlers and destroy the bar window
+ */
+bar::~bar() {
+  std::lock_guard<std::mutex> guard(m_mutex);
+  m_connection.detach_sink(this, SINK_PRIORITY_BAR);
+  m_sig.detach(this);
+}
+
+/**
+ * Get the bar settings container
+ */
+const bar_settings bar::settings() const {
+  return m_opts;
+}
+
+/**
+ * Parse input string and redraw the bar window
+ *
+ * \param data Input string
+ * \param force Unless true, do not parse unchanged data
+ */
+void bar::parse(string&& data, bool force) {
+  if (!m_mutex.try_lock()) {
+    return;
+  }
+
+  std::lock_guard<std::mutex> guard(m_mutex, std::adopt_lock);
+
+  bool unchanged = data == m_lastinput;
+
+  m_lastinput = data;
+
+  if (force) {
+    m_log.trace("bar: Force update");
+  } else if (!m_visible) {
+    return m_log.trace("bar: Ignoring update (invisible)");
+  } else if (m_opts.shaded) {
+    return m_log.trace("bar: Ignoring update (shaded)");
+  } else if (unchanged) {
+    return m_log.trace("bar: Ignoring update (unchanged)");
+  }
+
+  auto rect = m_opts.inner_area();
+
+  if (m_tray && !m_tray->settings().detached && m_tray->settings().configured_slots) {
+    auto trayalign = m_tray->settings().align;
+    auto traywidth = m_tray->settings().configured_w;
+    if (trayalign == alignment::LEFT) {
+      rect.x += traywidth;
+      rect.width -= traywidth;
+    } else if (trayalign == alignment::RIGHT) {
+      rect.width -= traywidth;
+    }
+  }
+
+  m_log.info("Redrawing bar window");
+  m_renderer->begin(rect);
+
+  try {
+    m_parser->parse(settings(), data);
+  } catch (const parser_error& err) {
+    m_log.err("Failed to parse contents (reason: %s)", err.what());
+  }
+
+  m_renderer->end();
+
+  const auto check_dblclicks = [&]() -> bool {
+    for (auto&& action : m_renderer->actions()) {
+      if (static_cast<int>(action.button) >= static_cast<int>(mousebtn::DOUBLE_LEFT)) {
+        return true;
+      }
+    }
+    for (auto&& action : m_opts.actions) {
+      if (static_cast<int>(action.button) >= static_cast<int>(mousebtn::DOUBLE_LEFT)) {
+        return true;
+      }
+    }
+    return false;
+  };
+  m_dblclicks = check_dblclicks();
+}
+
+/**
+ * Hide the bar by unmapping its X window
+ */
+void bar::hide() {
+  if (!m_visible) {
+    return;
+  }
+
+  try {
+    m_log.info("Hiding bar window");
+    m_sig.emit(visibility_change{false});
+    m_connection.unmap_window_checked(m_opts.window);
+    m_connection.flush();
+    m_visible = false;
+  } catch (const exception& err) {
+    m_log.err("Failed to unmap bar window (err=%s", err.what());
+  }
+}
+
+/**
+ * Show the bar by mapping its X window and
+ * trigger a redraw of previous content
+ */
+void bar::show() {
+  if (m_visible) {
+    return;
+  }
+
+  try {
+    m_log.info("Showing bar window");
+    m_sig.emit(visibility_change{true});
+    /**
+     * First reconfigures the window so that WMs that discard some information
+     * when unmapping have the correct window properties (geometry etc).
+     */
+    reconfigue_window();
+    m_connection.map_window_checked(m_opts.window);
+    m_connection.flush();
+    m_visible = true;
+    parse(string{m_lastinput}, true);
+  } catch (const exception& err) {
+    m_log.err("Failed to map bar window (err=%s", err.what());
+  }
+}
+
+/**
+ * Toggle the bar's visibility state
+ */
+void bar::toggle() {
+  if (m_visible) {
+    hide();
+  } else {
+    show();
+  }
+}
+
+/**
+ * Move the bar window above defined sibling
+ * in the X window stack
+ */
+void bar::restack_window() {
+  string wm_restack;
+
+  try {
+    wm_restack = m_conf.get(m_conf.section(), "wm-restack");
+  } catch (const key_error& err) {
+    return;
+  }
+
+  auto restacked = false;
+
+  if (wm_restack == "bspwm") {
+    restacked = bspwm_util::restack_to_root(m_connection, m_opts.monitor, m_opts.window);
+#if ENABLE_I3
+  } else if (wm_restack == "i3" && m_opts.override_redirect) {
+    restacked = i3_util::restack_to_root(m_connection, m_opts.window);
+  } else if (wm_restack == "i3" && !m_opts.override_redirect) {
+    m_log.warn("Ignoring restack of i3 window (not needed when `override-redirect = false`)");
+    wm_restack.clear();
+#endif
+  } else {
+    m_log.warn("Ignoring unsupported wm-restack option '%s'", wm_restack);
+    wm_restack.clear();
+  }
+
+  if (restacked) {
+    m_log.info("Successfully restacked bar window");
+  } else if (!wm_restack.empty()) {
+    m_log.err("Failed to restack bar window");
+  }
+}
+
+void bar::reconfigue_window() {
+  m_log.trace("bar: Reconfigure window");
+  restack_window();
+  reconfigure_geom();
+  reconfigure_struts();
+  reconfigure_wm_hints();
+}
+
+/**
+ * Reconfigure window geometry
+ */
+void bar::reconfigure_geom() {
+  window win{m_connection, m_opts.window};
+  win.reconfigure_geom(m_opts.size.w, m_opts.size.h, m_opts.pos.x, m_opts.pos.y);
+}
+
+/**
+ * Reconfigure window position
+ */
+void bar::reconfigure_pos() {
+  window win{m_connection, m_opts.window};
+  win.reconfigure_pos(m_opts.pos.x, m_opts.pos.y);
+}
+
+/**
+ * Reconfigure window strut values
+ */
+void bar::reconfigure_struts() {
+  auto geom = m_connection.get_geometry(m_screen->root());
+  auto w = m_opts.size.w + m_opts.offset.x;
+  auto h = m_opts.size.h + m_opts.offset.y;
+
+  if (m_opts.origin == edge::BOTTOM) {
+    h += m_opts.strut.top;
+  } else {
+    h += m_opts.strut.bottom;
+  }
+
+  if (m_opts.origin == edge::BOTTOM && m_opts.monitor->y + m_opts.monitor->h < geom->height) {
+    h += geom->height - (m_opts.monitor->y + m_opts.monitor->h);
+  } else if (m_opts.origin != edge::BOTTOM) {
+    h += m_opts.monitor->y;
+  }
+
+  window win{m_connection, m_opts.window};
+  win.reconfigure_struts(w, h, m_opts.pos.x, m_opts.origin == edge::BOTTOM);
+}
+
+/**
+ * Reconfigure window wm hint values
+ */
+void bar::reconfigure_wm_hints() {
+  const auto& win = m_opts.window;
+
+  m_log.trace("bar: Set window WM_NAME");
+  icccm_util::set_wm_name(m_connection, win, m_opts.wmname.c_str(), m_opts.wmname.size(), "polybar\0Polybar", 15_z);
+
+  m_log.trace("bar: Set window _NET_WM_WINDOW_TYPE");
+  ewmh_util::set_wm_window_type(win, {_NET_WM_WINDOW_TYPE_DOCK});
+
+  m_log.trace("bar: Set window _NET_WM_STATE");
+  ewmh_util::set_wm_state(win, {_NET_WM_STATE_STICKY, _NET_WM_STATE_ABOVE});
+
+  m_log.trace("bar: Set window _NET_WM_DESKTOP");
+  ewmh_util::set_wm_desktop(win, 0xFFFFFFFF);
+
+  m_log.trace("bar: Set window _NET_WM_PID");
+  ewmh_util::set_wm_pid(win);
+}
+
+/**
+ * Broadcast current map state
+ */
+void bar::broadcast_visibility() {
+  auto attr = m_connection.get_window_attributes(m_opts.window);
+
+  if (attr->map_state == XCB_MAP_STATE_UNVIEWABLE) {
+    m_sig.emit(visibility_change{false});
+  } else if (attr->map_state == XCB_MAP_STATE_UNMAPPED) {
+    m_sig.emit(visibility_change{false});
+  } else {
+    m_sig.emit(visibility_change{true});
+  }
+}
+
+/**
+ * Event handler for XCB_DESTROY_NOTIFY events
+ */
+void bar::handle(const evt::client_message& evt) {
+  if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_opts.window) {
+    m_log.err("Bar window has been destroyed, shutting down...");
+    m_connection.disconnect();
+  }
+}
+
+/**
+ * Event handler for XCB_DESTROY_NOTIFY events
+ */
+void bar::handle(const evt::destroy_notify& evt) {
+  if (evt->window == m_opts.window) {
+    m_connection.disconnect();
+  }
+}
+
+/**
+ * Event handler for XCB_ENTER_NOTIFY events
+ *
+ * Used to brighten the window by setting the
+ * _NET_WM_WINDOW_OPACITY atom value
+ */
+void bar::handle(const evt::enter_notify&) {
+#if 0
+#ifdef DEBUG_SHADED
+  if (m_opts.origin == edge::TOP) {
+    m_taskqueue->defer_unique("window-hover", 25ms, [&](size_t) { m_sig.emit(signals::ui::unshade_window{}); });
+    return;
+  }
+#endif
+#endif
+  if (m_opts.dimmed) {
+    m_taskqueue->defer_unique("window-dim", 25ms, [&](size_t) {
+      m_opts.dimmed = false;
+      m_sig.emit(dim_window{1.0});
+    });
+  } else if (m_taskqueue->exist("window-dim")) {
+    m_taskqueue->purge("window-dim");
+  }
+}
+
+/**
+ * Event handler for XCB_LEAVE_NOTIFY events
+ *
+ * Used to dim the window by setting the
+ * _NET_WM_WINDOW_OPACITY atom value
+ */
+void bar::handle(const evt::leave_notify&) {
+#if 0
+#ifdef DEBUG_SHADED
+  if (m_opts.origin == edge::TOP) {
+    m_taskqueue->defer_unique("window-hover", 25ms, [&](size_t) { m_sig.emit(signals::ui::shade_window{}); });
+    return;
+  }
+#endif
+#endif
+  if (!m_opts.dimmed) {
+    m_taskqueue->defer_unique("window-dim", 3s, [&](size_t) {
+      m_opts.dimmed = true;
+      m_sig.emit(dim_window{double(m_opts.dimvalue)});
+    });
+  }
+}
+
+/**
+ * Event handler for XCB_MOTION_NOTIFY events
+ *
+ * Used to change the cursor depending on the module
+ */
+void bar::handle(const evt::motion_notify& evt) {
+  if (!m_mutex.try_lock()) {
+    return;
+  }
+
+  std::lock_guard<std::mutex> guard(m_mutex, std::adopt_lock);
+
+  m_log.trace("bar: Detected motion: %i at pos(%i, %i)", evt->detail, evt->event_x, evt->event_y);
+#if WITH_XCURSOR
+  m_motion_pos = evt->event_x;
+  // scroll cursor is less important than click cursor, so we shouldn't return until we are sure there is no click
+  // action
+  bool found_scroll = false;
+  const auto find_click_area = [&](const action& action) {
+    if (!m_opts.cursor_click.empty() &&
+        !(action.button == mousebtn::SCROLL_UP || action.button == mousebtn::SCROLL_DOWN ||
+            action.button == mousebtn::NONE)) {
+      if (!string_util::compare(m_opts.cursor, m_opts.cursor_click)) {
+        m_opts.cursor = m_opts.cursor_click;
+        m_sig.emit(cursor_change{string{m_opts.cursor}});
+      }
+      return true;
+    } else if (!m_opts.cursor_scroll.empty() &&
+               (action.button == mousebtn::SCROLL_UP || action.button == mousebtn::SCROLL_DOWN)) {
+      if (!found_scroll) {
+        found_scroll = true;
+      }
+    }
+    return false;
+  };
+
+  for (auto&& action : m_renderer->actions()) {
+    if (action.test(m_motion_pos)) {
+      m_log.trace("Found matching input area");
+      if (find_click_area(action))
+        return;
+    }
+  }
+  if (found_scroll) {
+    if (!string_util::compare(m_opts.cursor, m_opts.cursor_scroll)) {
+      m_opts.cursor = m_opts.cursor_scroll;
+      m_sig.emit(cursor_change{string{m_opts.cursor}});
+    }
+    return;
+  }
+  for (auto&& action : m_opts.actions) {
+    if (!action.command.empty()) {
+      m_log.trace("Found matching fallback handler");
+      if (find_click_area(action))
+        return;
+    }
+  }
+  if (found_scroll) {
+    if (!string_util::compare(m_opts.cursor, m_opts.cursor_scroll)) {
+      m_opts.cursor = m_opts.cursor_scroll;
+      m_sig.emit(cursor_change{string{m_opts.cursor}});
+    }
+    return;
+  }
+  if (!string_util::compare(m_opts.cursor, "default")) {
+    m_log.trace("No matching cursor area found");
+    m_opts.cursor = "default";
+    m_sig.emit(cursor_change{string{m_opts.cursor}});
+    return;
+  }
+#endif
+}
+
+/**
+ * Event handler for XCB_BUTTON_PRESS events
+ *
+ * Used to map mouse clicks to bar actions
+ */
+void bar::handle(const evt::button_press& evt) {
+  if (!m_mutex.try_lock()) {
+    return;
+  }
+
+  std::lock_guard<std::mutex> guard(m_mutex, std::adopt_lock);
+
+  if (m_buttonpress.deny(evt->time)) {
+    return m_log.trace_x("bar: Ignoring button press (throttled)...");
+  }
+
+  m_log.trace("bar: Received button press: %i at pos(%i, %i)", evt->detail, evt->event_x, evt->event_y);
+
+  m_buttonpress_btn = static_cast<mousebtn>(evt->detail);
+  m_buttonpress_pos = evt->event_x;
+
+  const auto deferred_fn = [&](size_t) {
+    /*
+     * Iterate over all defined actions in reverse order until matching action is found
+     * To properly handle nested actions we iterate in reverse because nested actions are added later than their
+     * surrounding action block
+     */
+    auto actions = m_renderer->actions();
+    for (auto action = actions.rbegin(); action != actions.rend(); action++) {
+      if (action->button == m_buttonpress_btn && !action->active && action->test(m_buttonpress_pos)) {
+        m_log.trace("Found matching input area");
+        m_sig.emit(button_press{string{action->command}});
+        return;
+      }
+    }
+
+    for (auto&& action : m_opts.actions) {
+      if (action.button == m_buttonpress_btn && !action.command.empty()) {
+        m_log.trace("Found matching fallback handler");
+        m_sig.emit(button_press{string{action.command}});
+        return;
+      }
+    }
+    m_log.info("No matching input area found (btn=%i)", static_cast<int>(m_buttonpress_btn));
+  };
+
+  const auto check_double = [&](string&& id, mousebtn&& btn) {
+    if (!m_taskqueue->exist(id)) {
+      m_doubleclick.event = evt->time;
+      m_taskqueue->defer(id, taskqueue::deferred::duration{m_doubleclick.offset}, deferred_fn);
+    } else if (m_doubleclick.deny(evt->time)) {
+      m_doubleclick.event = 0;
+      m_buttonpress_btn = btn;
+      m_taskqueue->defer_unique(id, 0ms, deferred_fn);
+    }
+  };
+
+  // If there are no double click handlers defined we can
+  // just by-pass the click timer handling
+  if (!m_dblclicks) {
+    deferred_fn(0);
+  } else if (evt->detail == static_cast<int>(mousebtn::LEFT)) {
+    check_double("buttonpress-left", mousebtn::DOUBLE_LEFT);
+  } else if (evt->detail == static_cast<int>(mousebtn::MIDDLE)) {
+    check_double("buttonpress-middle", mousebtn::DOUBLE_MIDDLE);
+  } else if (evt->detail == static_cast<int>(mousebtn::RIGHT)) {
+    check_double("buttonpress-right", mousebtn::DOUBLE_RIGHT);
+  } else {
+    deferred_fn(0);
+  }
+}
+
+/**
+ * Event handler for XCB_EXPOSE events
+ *
+ * Used to redraw the bar
+ */
+void bar::handle(const evt::expose& evt) {
+  if (evt->window == m_opts.window && evt->count == 0) {
+    if (m_tray->settings().running) {
+      broadcast_visibility();
+    }
+
+    m_log.trace("bar: Received expose event");
+    m_renderer->flush();
+  }
+}
+
+/**
+ * Event handler for XCB_PROPERTY_NOTIFY events
+ *
+ * - Emit events whenever the bar window's
+ * visibility gets changed. This allows us to toggle the
+ * state of the tray container even though the tray
+ * window restacking failed.  Used as a fallback for
+ * tedious WM's, like i3.
+ */
+void bar::handle(const evt::property_notify& evt) {
+#ifdef DEBUG_LOGGER_VERBOSE
+  string atom_name = m_connection.get_atom_name(evt->atom).name();
+  m_log.trace_x("bar: property_notify(%s)", atom_name);
+#endif
+
+  if (evt->window == m_opts.window && evt->atom == WM_STATE) {
+    broadcast_visibility();
+  }
+}
+
+void bar::handle(const evt::configure_notify&) {
+  // The absolute position of the window in the root may be different after configuration is done
+  // (for example, because the parent is not positioned at 0/0 in the root window).
+  // Notify components that the geometry may have changed (used by the background manager for example).
+  m_sig.emit(signals::ui::update_geometry{});
+}
+
+bool bar::on(const signals::eventqueue::start&) {
+  m_log.trace("bar: Create renderer");
+  m_renderer = renderer::make(m_opts);
+  m_opts.window = m_renderer->window();
+
+  // Subscribe to window enter and leave events
+  // if we should dim the window
+  if (m_opts.dimvalue != 1.0) {
+    m_connection.ensure_event_mask(m_opts.window, XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW);
+  }
+  if (!m_opts.cursor_click.empty() || !m_opts.cursor_scroll.empty()) {
+    m_connection.ensure_event_mask(m_opts.window, XCB_EVENT_MASK_POINTER_MOTION);
+  }
+  m_connection.ensure_event_mask(m_opts.window, XCB_EVENT_MASK_STRUCTURE_NOTIFY);
+
+  m_log.info("Bar window: %s", m_connection.id(m_opts.window));
+  reconfigue_window();
+
+  m_log.trace("bar: Map window");
+  m_connection.map_window_checked(m_opts.window);
+
+  // With the mapping, the absolute position of our window may have changed (due to re-parenting for example).
+  // Notify all components that depend on the absolute bar position (such as the background manager).
+  m_sig.emit(signals::ui::update_geometry{});
+
+  // Reconfigure window position after mapping (required by Openbox)
+  reconfigure_pos();
+
+  m_log.trace("bar: Draw empty bar");
+  m_renderer->begin(m_opts.inner_area());
+  m_renderer->end();
+
+  m_sig.emit(signals::ui::ready{});
+
+  // TODO: tray manager could run this internally on ready event
+  m_log.trace("bar: Setup tray manager");
+  m_tray->setup(static_cast<const bar_settings&>(m_opts));
+
+  broadcast_visibility();
+
+  return true;
+}
+
+bool bar::on(const signals::ui::unshade_window&) {
+  m_opts.shaded = false;
+  m_opts.shade_size.w = m_opts.size.w;
+  m_opts.shade_size.h = m_opts.size.h;
+  m_opts.shade_pos.x = m_opts.pos.x;
+  m_opts.shade_pos.y = m_opts.pos.y;
+
+  double distance{static_cast<double>(m_opts.shade_size.h - m_connection.get_geometry(m_opts.window)->height)};
+  double steptime{25.0 / 2.0};
+  m_anim_step = distance / steptime / 2.0;
+
+  m_taskqueue->defer_unique(
+      "window-shade", 25ms,
+      [&](size_t remaining) {
+        if (!m_opts.shaded) {
+          m_sig.emit(signals::ui::tick{});
+        }
+        if (!remaining) {
+          m_renderer->flush();
+        }
+        if (m_opts.dimmed) {
+          m_opts.dimmed = false;
+          m_sig.emit(dim_window{1.0});
+        }
+      },
+      taskqueue::deferred::duration{25ms}, 10U);
+
+  return true;
+}
+
+bool bar::on(const signals::ui::shade_window&) {
+  taskqueue::deferred::duration offset{2000ms};
+
+  if (!m_opts.shaded && m_opts.shade_size.h != m_opts.size.h) {
+    offset = taskqueue::deferred::duration{25ms};
+  }
+
+  m_opts.shaded = true;
+  m_opts.shade_size.h = 5;
+  m_opts.shade_size.w = m_opts.size.w;
+  m_opts.shade_pos.x = m_opts.pos.x;
+  m_opts.shade_pos.y = m_opts.pos.y;
+
+  if (m_opts.origin == edge::BOTTOM) {
+    m_opts.shade_pos.y = m_opts.pos.y + m_opts.size.h - m_opts.shade_size.h;
+  }
+
+  double distance{static_cast<double>(m_connection.get_geometry(m_opts.window)->height - m_opts.shade_size.h)};
+  double steptime{25.0 / 2.0};
+  m_anim_step = distance / steptime / 2.0;
+
+  m_taskqueue->defer_unique(
+      "window-shade", 25ms,
+      [&](size_t remaining) {
+        if (m_opts.shaded) {
+          m_sig.emit(signals::ui::tick{});
+        }
+        if (!remaining) {
+          m_renderer->flush();
+        }
+        if (!m_opts.dimmed) {
+          m_opts.dimmed = true;
+          m_sig.emit(dim_window{double{m_opts.dimvalue}});
+        }
+      },
+      move(offset), 10U);
+
+  return true;
+}
+
+bool bar::on(const signals::ui::tick&) {
+  auto geom = m_connection.get_geometry(m_opts.window);
+  if (geom->y == m_opts.shade_pos.y && geom->height == m_opts.shade_size.h) {
+    return false;
+  }
+
+  unsigned int mask{0};
+  unsigned int values[7]{0};
+  xcb_params_configure_window_t params{};
+
+  if (m_opts.shade_size.h > geom->height) {
+    XCB_AUX_ADD_PARAM(&mask, &params, height, static_cast<unsigned int>(geom->height + m_anim_step));
+    params.height = std::max(1U, std::min(params.height, static_cast<unsigned int>(m_opts.shade_size.h)));
+  } else if (m_opts.shade_size.h < geom->height) {
+    XCB_AUX_ADD_PARAM(&mask, &params, height, static_cast<unsigned int>(geom->height - m_anim_step));
+    params.height = std::max(1U, std::max(params.height, static_cast<unsigned int>(m_opts.shade_size.h)));
+  }
+
+  if (m_opts.shade_pos.y > geom->y) {
+    XCB_AUX_ADD_PARAM(&mask, &params, y, static_cast<int>(geom->y + m_anim_step));
+    params.y = std::min(params.y, static_cast<int>(m_opts.shade_pos.y));
+  } else if (m_opts.shade_pos.y < geom->y) {
+    XCB_AUX_ADD_PARAM(&mask, &params, y, static_cast<int>(geom->y - m_anim_step));
+    params.y = std::max(params.y, static_cast<int>(m_opts.shade_pos.y));
+  }
+
+  connection::pack_values(mask, &params, values);
+
+  m_connection.configure_window(m_opts.window, mask, values);
+  m_connection.flush();
+
+  return false;
+}
+
+bool bar::on(const signals::ui::dim_window& sig) {
+  m_opts.dimmed = sig.cast() != 1.0;
+  ewmh_util::set_wm_window_opacity(m_opts.window, sig.cast() * 0xFFFFFFFF);
+  return false;
+}
+
+#if WITH_XCURSOR
+bool bar::on(const signals::ui::cursor_change& sig) {
+  if (!cursor_util::set_cursor(m_connection, m_connection.screen(), m_opts.window, sig.cast())) {
+    m_log.warn("Failed to create cursor context");
+  }
+  m_connection.flush();
+  return false;
+}
+#endif
+
+POLYBAR_NS_END
diff --git a/src/components/builder.cpp b/src/components/builder.cpp
new file mode 100644 (file)
index 0000000..2ed7648
--- /dev/null
@@ -0,0 +1,567 @@
+#include "components/builder.hpp"
+
+#include <utility>
+
+#include "drawtypes/label.hpp"
+#include "utils/actions.hpp"
+#include "utils/color.hpp"
+#include "utils/string.hpp"
+#include "utils/time.hpp"
+POLYBAR_NS
+
+builder::builder(const bar_settings& bar) : m_bar(bar) {
+  reset();
+}
+
+void builder::reset() {
+  /* Add all values as keys so that we never have to check if a key exists in
+   * the map
+   */
+  m_tags.clear();
+  m_tags[syntaxtag::NONE] = 0;
+  m_tags[syntaxtag::A] = 0;
+  m_tags[syntaxtag::B] = 0;
+  m_tags[syntaxtag::F] = 0;
+  m_tags[syntaxtag::T] = 0;
+  m_tags[syntaxtag::R] = 0;
+  m_tags[syntaxtag::o] = 0;
+  m_tags[syntaxtag::u] = 0;
+  m_tags[syntaxtag::P] = 0;
+
+  m_colors.clear();
+  m_colors[syntaxtag::B] = string();
+  m_colors[syntaxtag::F] = string();
+  m_colors[syntaxtag::o] = string();
+  m_colors[syntaxtag::u] = string();
+
+  m_attrs.clear();
+  m_attrs[attribute::NONE] = false;
+  m_attrs[attribute::UNDERLINE] = false;
+  m_attrs[attribute::OVERLINE] = false;
+
+  m_output.clear();
+  m_fontindex = 1;
+}
+
+/**
+ * Flush contents of the builder and return built string
+ *
+ * This will also close any unclosed tags
+ */
+string builder::flush() {
+  if (m_tags[syntaxtag::B]) {
+    background_close();
+  }
+  if (m_tags[syntaxtag::F]) {
+    color_close();
+  }
+  if (m_tags[syntaxtag::T]) {
+    font_close();
+  }
+  if (m_tags[syntaxtag::o]) {
+    overline_color_close();
+  }
+  if (m_tags[syntaxtag::u]) {
+    underline_color_close();
+  }
+  if (m_attrs[attribute::UNDERLINE]) {
+    underline_close();
+  }
+  if (m_attrs[attribute::OVERLINE]) {
+    overline_close();
+  }
+
+  while (m_tags[syntaxtag::A]) {
+    action_close();
+  }
+
+  string output{m_output};
+
+  reset();
+
+  return output;
+}
+
+/**
+ * Insert raw text string
+ */
+void builder::append(string text) {
+  m_output.reserve(text.size());
+  m_output += move(text);
+}
+
+/**
+ * Insert text node
+ *
+ * This will also parse raw syntax tags
+ */
+void builder::node(string str) {
+  if (str.empty()) {
+    return;
+  }
+
+  append(move(str));
+}
+
+/**
+ * Insert text node with specific font index
+ *
+ * \see builder::node
+ */
+void builder::node(string str, int font_index) {
+  font(font_index);
+  node(move(str));
+  font_close();
+}
+
+/**
+ * Insert tags for given label
+ */
+void builder::node(const label_t& label) {
+  if (!label || !*label) {
+    return;
+  }
+
+  auto text = label->get();
+
+  if (label->m_margin.left > 0) {
+    space(label->m_margin.left);
+  }
+
+  if (label->m_overline.has_color()) {
+    overline(label->m_overline);
+  }
+  if (label->m_underline.has_color()) {
+    underline(label->m_underline);
+  }
+
+  if (label->m_background.has_color()) {
+    background(label->m_background);
+  }
+  if (label->m_foreground.has_color()) {
+    color(label->m_foreground);
+  }
+
+  if (label->m_padding.left > 0) {
+    space(label->m_padding.left);
+  }
+
+  node(text, label->m_font);
+
+  if (label->m_padding.right > 0) {
+    space(label->m_padding.right);
+  }
+
+  if (label->m_background.has_color()) {
+    background_close();
+  }
+  if (label->m_foreground.has_color()) {
+    color_close();
+  }
+
+  if (label->m_underline.has_color()) {
+    underline_close();
+  }
+  if (label->m_overline.has_color()) {
+    overline_close();
+  }
+
+  if (label->m_margin.right > 0) {
+    space(label->m_margin.right);
+  }
+}
+
+/**
+ * Repeat text string n times
+ */
+void builder::node_repeat(const string& str, size_t n) {
+  string text;
+  text.reserve(str.size() * n);
+  while (n--) {
+    text += str;
+  }
+  node(text);
+}
+
+/**
+ * Repeat label contents n times
+ */
+void builder::node_repeat(const label_t& label, size_t n) {
+  string text;
+  string label_text{label->get()};
+  text.reserve(label_text.size() * n);
+  while (n--) {
+    text += label_text;
+  }
+  label_t tmp{new label_t::element_type{text}};
+  tmp->replace_defined_values(label);
+  node(tmp);
+}
+
+/**
+ * Insert tag that will offset the contents by given pixels
+ */
+void builder::offset(int pixels) {
+  if (pixels == 0) {
+    return;
+  }
+  tag_open(syntaxtag::O, to_string(pixels));
+}
+
+/**
+ * Insert spaces
+ */
+void builder::space(size_t width) {
+  if (width) {
+    m_output.append(width, ' ');
+  } else {
+    space();
+  }
+}
+void builder::space() {
+  m_output.append(m_bar.spacing, ' ');
+}
+
+/**
+ * Remove trailing space
+ */
+void builder::remove_trailing_space(size_t len) {
+  if (len == 0_z || len > m_output.size()) {
+    return;
+  } else if (m_output.substr(m_output.size() - len) == string(len, ' ')) {
+    m_output.erase(m_output.size() - len);
+  }
+}
+void builder::remove_trailing_space() {
+  remove_trailing_space(m_bar.spacing);
+}
+
+/**
+ * Insert tag to alter the current font index
+ */
+void builder::font(int index) {
+  if (index == 0) {
+    return;
+  }
+  m_fontindex = index;
+  tag_open(syntaxtag::T, to_string(index));
+}
+
+/**
+ * Insert tag to reset the font index
+ */
+void builder::font_close() {
+  m_fontindex = 1;
+  tag_close(syntaxtag::T);
+}
+
+/**
+ * Insert tag to alter the current background color
+ */
+void builder::background(rgba color) {
+  color = color.try_apply_alpha_to(m_bar.background);
+
+  auto hex = color_util::simplify_hex(color);
+  m_colors[syntaxtag::B] = hex;
+  tag_open(syntaxtag::B, hex);
+}
+
+/**
+ * Insert tag to reset the background color
+ */
+void builder::background_close() {
+  m_colors[syntaxtag::B].clear();
+  tag_close(syntaxtag::B);
+}
+
+/**
+ * Insert tag to alter the current foreground color
+ */
+void builder::color(rgba color) {
+  color = color.try_apply_alpha_to(m_bar.foreground);
+
+  auto hex = color_util::simplify_hex(color);
+  m_colors[syntaxtag::F] = hex;
+  tag_open(syntaxtag::F, hex);
+}
+
+/**
+ * Insert tag to reset the foreground color
+ */
+void builder::color_close() {
+  m_colors[syntaxtag::F].clear();
+  tag_close(syntaxtag::F);
+}
+
+/**
+ * Insert tag to alter the current overline/underline color
+ */
+void builder::line_color(const rgba& color) {
+  overline_color(color);
+  underline_color(color);
+}
+
+/**
+ * Close overline/underline color tag
+ */
+void builder::line_color_close() {
+  overline_color_close();
+  underline_color_close();
+}
+
+/**
+ * Insert tag to alter the current overline color
+ */
+void builder::overline_color(rgba color) {
+  auto hex = color_util::simplify_hex(color);
+  m_colors[syntaxtag::o] = hex;
+  tag_open(syntaxtag::o, hex);
+  tag_open(attribute::OVERLINE);
+}
+
+/**
+ * Close underline color tag
+ */
+void builder::overline_color_close() {
+  m_colors[syntaxtag::o].clear();
+  tag_close(syntaxtag::o);
+}
+
+/**
+ * Insert tag to alter the current underline color
+ */
+void builder::underline_color(rgba color) {
+  auto hex = color_util::simplify_hex(color);
+  m_colors[syntaxtag::u] = hex;
+  tag_open(syntaxtag::u, hex);
+  tag_open(attribute::UNDERLINE);
+}
+
+/**
+ * Close underline color tag
+ */
+void builder::underline_color_close() {
+  tag_close(syntaxtag::u);
+  m_colors[syntaxtag::u].clear();
+}
+
+/**
+ * Insert tag to enable the overline attribute
+ */
+void builder::overline(const rgba& color) {
+  if (color.has_color()) {
+    overline_color(color);
+  } else {
+    tag_open(attribute::OVERLINE);
+  }
+}
+
+/**
+ * Close overline attribute tag
+ */
+void builder::overline_close() {
+  tag_close(attribute::OVERLINE);
+}
+
+/**
+ * Insert tag to enable the underline attribute
+ */
+void builder::underline(const rgba& color) {
+  if (color.has_color()) {
+    underline_color(color);
+  } else {
+    tag_open(attribute::UNDERLINE);
+  }
+}
+
+/**
+ * Close underline attribute tag
+ */
+void builder::underline_close() {
+  tag_close(attribute::UNDERLINE);
+}
+
+/**
+ * Add a polybar control tag
+ */
+void builder::control(controltag tag) {
+  string str;
+  switch (tag) {
+    case controltag::R:
+      str = "R";
+      break;
+    default:
+      break;
+  }
+
+  if (!str.empty()) {
+    tag_open(syntaxtag::P, str);
+  }
+}
+
+/**
+ * Open action tag with the given action string
+ *
+ * The action string is escaped, if needed.
+ */
+void builder::action(mousebtn index, string action) {
+  if (!action.empty()) {
+    action = string_util::replace_all(action, ":", "\\:");
+    tag_open(syntaxtag::A, to_string(static_cast<int>(index)) + ":" + action + ":");
+  }
+}
+
+/**
+ * Open action tag for the action of the given module
+ */
+void builder::action(mousebtn btn, const modules::module_interface& module, string action_name, string data) {
+  action(btn, actions_util::get_action_string(module, action_name, data));
+}
+
+/**
+ * Wrap label in action tag
+ */
+void builder::action(mousebtn index, string action_name, const label_t& label) {
+  if (label && *label) {
+    action(index, action_name);
+    node(label);
+    tag_close(syntaxtag::A);
+  }
+}
+
+/**
+ * Wrap label in module action tag
+ */
+void builder::action(
+    mousebtn btn, const modules::module_interface& module, string action_name, string data, const label_t& label) {
+  action(btn, actions_util::get_action_string(module, action_name, data), label);
+}
+
+/**
+ * Close command tag
+ */
+void builder::action_close() {
+  tag_close(syntaxtag::A);
+}
+
+/**
+ * Insert directive to change value of given tag
+ */
+void builder::tag_open(syntaxtag tag, const string& value) {
+  m_tags[tag]++;
+
+  switch (tag) {
+    case syntaxtag::NONE:
+      break;
+    case syntaxtag::A:
+      append("%{A" + value + "}");
+      break;
+    case syntaxtag::F:
+      append("%{F" + value + "}");
+      break;
+    case syntaxtag::B:
+      append("%{B" + value + "}");
+      break;
+    case syntaxtag::T:
+      append("%{T" + value + "}");
+      break;
+    case syntaxtag::u:
+      append("%{u" + value + "}");
+      break;
+    case syntaxtag::o:
+      append("%{o" + value + "}");
+      break;
+    case syntaxtag::R:
+      append("%{R}");
+      break;
+    case syntaxtag::O:
+      append("%{O" + value + "}");
+      break;
+    case syntaxtag::P:
+      append("%{P" + value + "}");
+      break;
+  }
+}
+
+/**
+ * Insert directive to use given attribute unless already set
+ */
+void builder::tag_open(attribute attr) {
+  if (m_attrs[attr]) {
+    return;
+  }
+
+  m_attrs[attr] = true;
+
+  switch (attr) {
+    case attribute::NONE:
+      break;
+    case attribute::UNDERLINE:
+      append("%{+u}");
+      break;
+    case attribute::OVERLINE:
+      append("%{+o}");
+      break;
+  }
+}
+
+/**
+ * Insert directive to reset given tag if it's open and closable
+ */
+void builder::tag_close(syntaxtag tag) {
+  if (!m_tags[tag]) {
+    return;
+  }
+
+  m_tags[tag]--;
+
+  switch (tag) {
+    case syntaxtag::A:
+      append("%{A}");
+      break;
+    case syntaxtag::F:
+      append("%{F-}");
+      break;
+    case syntaxtag::B:
+      append("%{B-}");
+      break;
+    case syntaxtag::T:
+      append("%{T-}");
+      break;
+    case syntaxtag::u:
+      append("%{u-}");
+      break;
+    case syntaxtag::o:
+      append("%{o-}");
+      break;
+    case syntaxtag::NONE:
+    case syntaxtag::R:
+    case syntaxtag::P:
+    case syntaxtag::O:
+      break;
+  }
+}
+
+/**
+ * Insert directive to remove given attribute if set
+ */
+void builder::tag_close(attribute attr) {
+  if (!m_attrs[attr]) {
+    return;
+  }
+
+  m_attrs[attr] = false;
+
+  switch (attr) {
+    case attribute::NONE:
+      break;
+    case attribute::UNDERLINE:
+      append("%{-u}");
+      break;
+    case attribute::OVERLINE:
+      append("%{-o}");
+      break;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/components/command_line.cpp b/src/components/command_line.cpp
new file mode 100644 (file)
index 0000000..35759a0
--- /dev/null
@@ -0,0 +1,204 @@
+#include <algorithm>
+
+#include "components/command_line.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+namespace command_line {
+  /**
+   * Create instance
+   */
+  parser::make_type parser::make(string&& scriptname, const options&& opts) {
+    return factory_util::unique<parser>("Usage: " + scriptname + " [OPTION]... BAR", forward<decltype(opts)>(opts));
+  }
+
+  /**
+   * Construct parser
+   */
+  parser::parser(string&& synopsis, const options&& opts)
+      : m_synopsis(forward<decltype(synopsis)>(synopsis)), m_opts(forward<decltype(opts)>(opts)) {}
+
+  /**
+   * Print application usage message
+   */
+  void parser::usage() const {
+    printf("%s\n\n", m_synopsis.c_str());
+
+    // get the length of the longest string in the flag column
+    // which is used to align the description fields
+    size_t maxlen{0};
+
+    for (const auto& m_opt : m_opts) {
+      size_t len{m_opt.flag_long.length() + m_opt.flag.length() + 4};
+      maxlen = len > maxlen ? len : maxlen;
+    }
+
+    for (auto& opt : m_opts) {
+      size_t pad = maxlen - opt.flag_long.length() - opt.token.length();
+
+      printf("  %s, %s", opt.flag.c_str(), opt.flag_long.c_str());
+
+      if (!opt.token.empty()) {
+        printf("=%s", opt.token.c_str());
+        pad--;
+      }
+
+      // output the list with accepted values
+      if (!opt.values.empty()) {
+        printf("%*s\n", static_cast<int>(pad + opt.desc.length()), opt.desc.c_str());
+
+        pad = pad + opt.flag_long.length() + opt.token.length() + 7;
+
+        printf("%*c%s is one of: ", static_cast<int>(pad), ' ', opt.token.c_str());
+
+        for (auto& v : opt.values) {
+          printf("%s%s", v.c_str(), v != opt.values.back() ? ", " : "");
+        }
+      } else {
+        printf("%*s", static_cast<int>(pad + opt.desc.length()), opt.desc.c_str());
+      }
+
+      printf("\n");
+    }
+
+    printf("\n");
+  }
+
+  /**
+   * Process input values
+   */
+  void parser::process_input(const vector<string>& values) {
+    for (size_t i = 0; i < values.size(); i++) {
+      parse(values[i], values.size() > i + 1 ? values[i + 1] : "");
+    }
+  }
+
+  /**
+   * Test if the passed option was provided
+   */
+  bool parser::has(const string& option) const {
+    return m_optvalues.find(option) != m_optvalues.end();
+  }
+
+  /**
+   * Test if a positional argument is defined at given index
+   */
+  bool parser::has(size_t index) const {
+    return m_posargs.size() > index;
+  }
+
+  /**
+   * Get the value defined for given option
+   */
+  string parser::get(string opt) const {
+    if (has(forward<string>(opt))) {
+      return m_optvalues.find(opt)->second;
+    }
+    return "";
+  }
+
+  /**
+   * Get the positional argument at given index
+   */
+  string parser::get(size_t index) const {
+    return has(index) ? m_posargs[index] : "";
+  }
+
+  /**
+   * Compare option value with given string
+   */
+  bool parser::compare(string opt, const string& val) const {
+    return get(move(opt)) == val;
+  }
+
+  /**
+   * Compare positional argument at given index with given string
+   */
+  bool parser::compare(size_t index, const string& val) const {
+    return get(index) == val;
+  }
+
+  /**
+   * Compare option with its short version
+   */
+  auto parser::is_short(const string& option, const string& opt_short) const {
+    return option.compare(0, opt_short.length(), opt_short) == 0;
+  }
+
+  /**
+   * Compare option with its long version
+   */
+  auto parser::is_long(const string& option, const string& opt_long) const {
+    return option.compare(0, opt_long.length(), opt_long) == 0;
+  }
+
+  /**
+   * Compare option with both versions
+   */
+  auto parser::is(const string& option, string opt_short, string opt_long) const {
+    return is_short(option, move(opt_short)) || is_long(option, move(opt_long));
+  }
+
+  /**
+   * Parse option value
+   */
+  auto parser::parse_value(string input, const string& input_next, choices values) const {
+    string opt = move(input);
+    size_t pos;
+    string value;
+
+    if (input_next.empty() && opt.compare(0, 2, "--") != 0) {
+      throw value_error("Missing argument for option " + opt);
+    } else if ((pos = opt.find('=')) == string::npos && opt.compare(0, 2, "--") == 0) {
+      throw value_error("Missing argument for option " + opt);
+    } else if (pos == string::npos && !input_next.empty()) {
+      value = input_next;
+    } else {
+      value = opt.substr(pos + 1);
+      opt = opt.substr(0, pos);
+    }
+
+    if (!values.empty() && std::find(values.begin(), values.end(), value) == values.end()) {
+      throw value_error("Invalid argument for option " + opt);
+    }
+
+    return value;
+  }
+
+  /**
+   * Parse and validate passed arguments and flags
+   */
+  void parser::parse(const string& input, const string& input_next) {
+    auto skipped = m_skipnext;
+    if (m_skipnext) {
+      m_skipnext = false;
+      if (!input_next.empty()) {
+        return;
+      }
+    }
+
+    for (auto&& opt : m_opts) {
+      if (is(input, opt.flag, opt.flag_long)) {
+        if (opt.token.empty()) {
+          m_optvalues.insert(make_pair(opt.flag_long.substr(2), ""));
+        } else {
+          auto value = parse_value(input, input_next, opt.values);
+          m_skipnext = (value == input_next);
+          m_optvalues.insert(make_pair(opt.flag_long.substr(2), value));
+        }
+        return;
+      }
+    }
+
+    if (skipped) {
+      return;
+    } else if (input[0] != '-') {
+      m_posargs.emplace_back(input);
+    } else {
+      throw argument_error("Unrecognized option " + input);
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/components/config.cpp b/src/components/config.cpp
new file mode 100644 (file)
index 0000000..5be2c22
--- /dev/null
@@ -0,0 +1,246 @@
+#include "components/config.hpp"
+
+#include <climits>
+#include <fstream>
+
+#include "cairo/utils.hpp"
+#include "utils/color.hpp"
+#include "utils/env.hpp"
+#include "utils/factory.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace chrono = std::chrono;
+
+/**
+ * Create instance
+ */
+config::make_type config::make(string path, string bar) {
+  return *factory_util::singleton<std::remove_reference_t<config::make_type>>(logger::make(), move(path), move(bar));
+}
+
+/**
+ * Get path of loaded file
+ */
+const string& config::filepath() const {
+  return m_file;
+}
+
+/**
+ * Get the section name of the bar in use
+ */
+string config::section() const {
+  return "bar/" + m_barname;
+}
+
+void config::use_xrm() {
+#if WITH_XRM
+  /*
+   * Initialize the xresource manager if there are any xrdb refs
+   * present in the configuration
+   */
+  if (!m_xrm) {
+    m_log.info("Enabling xresource manager");
+    m_xrm.reset(new xresource_manager{connection::make()});
+  }
+#endif
+}
+
+void config::set_sections(sectionmap_t sections) {
+  m_sections = move(sections);
+  copy_inherited();
+}
+
+void config::set_included(file_list included) {
+  m_included = move(included);
+}
+
+/**
+ * Print a deprecation warning if the given parameter is set
+ */
+void config::warn_deprecated(const string& section, const string& key, string replacement) const {
+  try {
+    auto value = get<string>(section, key);
+    m_log.warn(
+        "The config parameter `%s.%s` is deprecated, use `%s.%s` instead.", section, key, section, move(replacement));
+  } catch (const key_error& err) {
+  }
+}
+
+/**
+ * Look for sections set up to inherit from a base section
+ * and copy the missing parameters
+ *
+ * Multiple sections can be specified, separated by a space.
+ *
+ *   [sub/section]
+ *   inherit = section1 section2
+ */
+void config::copy_inherited() {
+  for (auto&& section : m_sections) {
+    std::vector<string> inherit_sections;
+
+    // Collect all sections to be inherited
+    for (auto&& param : section.second) {
+      string key_name = param.first;
+      if (key_name == "inherit") {
+        auto inherit = param.second;
+        inherit = dereference<string>(section.first, key_name, inherit, inherit);
+
+        std::vector<string> sections = string_util::split(std::move(inherit), ' ');
+
+        inherit_sections.insert(inherit_sections.end(), sections.begin(), sections.end());
+
+      } else if (key_name.find("inherit") == 0) {
+        // Legacy support for keys that just start with 'inherit'
+        m_log.warn(
+            "\"%s.%s\": Using anything other than 'inherit' for inheriting section keys is deprecated. "
+            "The 'inherit' key supports multiple section names separated by a space.",
+            section.first, key_name);
+
+        auto inherit = param.second;
+        inherit = dereference<string>(section.first, key_name, inherit, inherit);
+        if (inherit.empty() || m_sections.find(inherit) == m_sections.end()) {
+          throw value_error(
+              "Invalid section \"" + inherit + "\" defined for \"" + section.first + "." + key_name + "\"");
+        }
+
+        inherit_sections.push_back(std::move(inherit));
+      }
+    }
+
+    for (const auto& base_name : inherit_sections) {
+      const auto base_section = m_sections.find(base_name);
+      if (base_section == m_sections.end()) {
+        throw value_error("Invalid section \"" + base_name + "\" defined for \"" + section.first + ".inherit\"");
+      }
+
+      m_log.trace("config: Inheriting keys from \"%s\" in \"%s\"", base_name, section.first);
+
+      /*
+       * Iterate the base and copy the parameters that haven't been defined
+       * yet.
+       */
+      for (auto&& base_param : base_section->second) {
+        section.second.emplace(base_param.first, base_param.second);
+      }
+    }
+  }
+}
+
+template <>
+string config::convert(string&& value) const {
+  return forward<string>(value);
+}
+
+template <>
+const char* config::convert(string&& value) const {
+  return value.c_str();
+}
+
+template <>
+char config::convert(string&& value) const {
+  return value.c_str()[0];
+}
+
+template <>
+int config::convert(string&& value) const {
+  return std::strtol(value.c_str(), nullptr, 10);
+}
+
+template <>
+short config::convert(string&& value) const {
+  return static_cast<short>(std::strtol(value.c_str(), nullptr, 10));
+}
+
+template <>
+bool config::convert(string&& value) const {
+  string lower{string_util::lower(forward<string>(value))};
+
+  return (lower == "true" || lower == "yes" || lower == "on" || lower == "1");
+}
+
+template <>
+float config::convert(string&& value) const {
+  return std::strtof(value.c_str(), nullptr);
+}
+
+template <>
+double config::convert(string&& value) const {
+  return std::strtod(value.c_str(), nullptr);
+}
+
+template <>
+long config::convert(string&& value) const {
+  return std::strtol(value.c_str(), nullptr, 10);
+}
+
+template <>
+long long config::convert(string&& value) const {
+  return std::strtoll(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned char config::convert(string&& value) const {
+  return std::strtoul(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned short config::convert(string&& value) const {
+  return std::strtoul(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned int config::convert(string&& value) const {
+  return std::strtoul(value.c_str(), nullptr, 10);
+}
+
+template <>
+unsigned long config::convert(string&& value) const {
+  unsigned long v{std::strtoul(value.c_str(), nullptr, 10)};
+  return v < ULONG_MAX ? v : 0UL;
+}
+
+template <>
+unsigned long long config::convert(string&& value) const {
+  unsigned long long v{std::strtoull(value.c_str(), nullptr, 10)};
+  return v < ULLONG_MAX ? v : 0ULL;
+}
+
+template <>
+chrono::seconds config::convert(string&& value) const {
+  return chrono::seconds{convert<chrono::seconds::rep>(forward<string>(value))};
+}
+
+template <>
+chrono::milliseconds config::convert(string&& value) const {
+  return chrono::milliseconds{convert<chrono::milliseconds::rep>(forward<string>(value))};
+}
+
+template <>
+chrono::duration<double> config::convert(string&& value) const {
+  return chrono::duration<double>{convert<double>(forward<string>(value))};
+}
+
+template <>
+rgba config::convert(string&& value) const {
+  if (value.empty()) {
+    return rgba{};
+  }
+
+  rgba ret{value};
+
+  if (!ret.has_color()) {
+    throw value_error("\"" + value + "\" is an invalid color value.");
+  }
+
+  return ret;
+}
+
+template <>
+cairo_operator_t config::convert(string&& value) const {
+  return cairo::utils::str2operator(forward<string>(value), CAIRO_OPERATOR_OVER);
+}
+
+POLYBAR_NS_END
diff --git a/src/components/config_parser.cpp b/src/components/config_parser.cpp
new file mode 100644 (file)
index 0000000..dca093e
--- /dev/null
@@ -0,0 +1,295 @@
+#include "components/config_parser.hpp"
+
+#include <algorithm>
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+config_parser::config_parser(const logger& logger, string&& file, string&& bar)
+    : m_log(logger), m_config(file_util::expand(file)), m_barname(move(bar)) {}
+
+config::make_type config_parser::parse() {
+  m_log.notice("Parsing config file: %s", m_config);
+
+  parse_file(m_config, {});
+
+  sectionmap_t sections = create_sectionmap();
+
+  if (sections.find("bar/" + m_barname) == sections.end()) {
+    throw application_error("Undefined bar: " + m_barname);
+  }
+
+  /*
+   * The first element in the files vector is always the main config file and
+   * because it has unique filenames, we can use all the elements from the
+   * second element onwards for the included list
+   */
+  file_list included(m_files.begin() + 1, m_files.end());
+  config::make_type result = config::make(m_config, m_barname);
+
+  // Cast to non-const to set sections, included and xrm
+  config& m_conf = const_cast<config&>(result);
+
+  m_conf.set_sections(move(sections));
+  m_conf.set_included(move(included));
+  if (use_xrm) {
+    m_conf.use_xrm();
+  }
+
+  return result;
+}
+
+sectionmap_t config_parser::create_sectionmap() {
+  sectionmap_t sections{};
+
+  string current_section{};
+
+  for (const line_t& line : m_lines) {
+    if (!line.useful) {
+      continue;
+    }
+
+    if (line.is_header) {
+      current_section = line.header;
+    } else {
+      // The first valid line in the config is not a section definition
+      if (current_section.empty()) {
+        throw syntax_error("First valid line in config must be section header", m_files[line.file_index], line.line_no);
+      }
+
+      const string& key = line.key;
+      const string& value = line.value;
+
+      valuemap_t& valuemap = sections[current_section];
+
+      if (valuemap.find(key) == valuemap.end()) {
+        valuemap.emplace(key, value);
+      } else {
+        // Key already exists in this section
+        throw syntax_error("Duplicate key name \"" + key + "\" defined in section \"" + current_section + "\"",
+            m_files[line.file_index], line.line_no);
+      }
+    }
+  }
+
+  return sections;
+}
+
+void config_parser::parse_file(const string& file, file_list path) {
+  if (std::find(path.begin(), path.end(), file) != path.end()) {
+    string path_str{};
+
+    for (const auto& p : path) {
+      path_str += ">\t" + p + "\n";
+    }
+
+    path_str += ">\t" + file;
+
+    // We have already parsed this file in this path, so there are cyclic dependencies
+    throw application_error("include-file: Dependency cycle detected:\n" + path_str);
+  }
+
+  if (!file_util::exists(file)) {
+    throw application_error("Failed to open config file " + file + ": " + strerror(errno));
+  }
+
+  if (!file_util::is_file(file)) {
+    throw application_error("Config file " + file + " is not a file");
+  }
+
+  m_log.trace("config_parser: Parsing %s", file);
+
+  int file_index;
+
+  auto found = std::find(m_files.begin(), m_files.end(), file);
+
+  if (found == m_files.end()) {
+    file_index = m_files.size();
+    m_files.push_back(file);
+  } else {
+    /*
+     * `file` is already in the `files` vector so we calculate its index.
+     *
+     * This means that the file was already parsed, this can happen without
+     * cyclic dependencies, if the file is included twice
+     */
+    file_index = found - m_files.begin();
+  }
+
+  path.push_back(file);
+
+  int line_no = 0;
+
+  string line_str{};
+
+  std::ifstream in(file);
+
+  if (!in) {
+    throw application_error("Failed to open config file " + file + ": " + strerror(errno));
+  }
+
+  while (std::getline(in, line_str)) {
+    line_no++;
+    line_t line;
+    try {
+      line = parse_line(line_str);
+
+      // parse_line doesn't set these
+      line.file_index = file_index;
+      line.line_no = line_no;
+    } catch (syntax_error& err) {
+      /*
+       * Exceptions thrown by parse_line doesn't have the line
+       * numbers and files set, so we have to add them here
+       */
+      throw syntax_error(err.get_msg(), m_files[file_index], line_no);
+    }
+
+    // Skip useless lines (comments, empty lines)
+    if (!line.useful) {
+      continue;
+    }
+
+    if (!line.is_header && line.key == "include-file") {
+      parse_file(file_util::expand(line.value), path);
+    } else if (!line.is_header && line.key == "include-directory") {
+      const string expanded_path = file_util::expand(line.value);
+      vector<string> file_list = file_util::list_files(expanded_path);
+      sort(file_list.begin(), file_list.end());
+      for (const auto& filename : file_list) {
+        parse_file(expanded_path + "/" + filename, path);
+      }
+    } else {
+      m_lines.push_back(line);
+    }
+  }
+}
+
+line_t config_parser::parse_line(const string& line) {
+  if (string_util::contains(line, "\ufeff")) {
+    throw syntax_error(
+        "This config file uses UTF-8 with BOM, which is not supported. Please use plain UTF-8 without BOM.");
+  }
+
+  string line_trimmed = string_util::trim(line, isspace);
+  line_type type = get_line_type(line_trimmed);
+
+  line_t result = {};
+
+  if (type == line_type::EMPTY || type == line_type::COMMENT) {
+    result.useful = false;
+    return result;
+  }
+
+  if (type == line_type::UNKNOWN) {
+    throw syntax_error("Unknown line type: " + line_trimmed);
+  }
+
+  result.useful = true;
+
+  if (type == line_type::HEADER) {
+    result.is_header = true;
+    result.header = parse_header(line_trimmed);
+  } else if (type == line_type::KEY) {
+    result.is_header = false;
+    auto key_value = parse_key(line_trimmed);
+    result.key = key_value.first;
+    result.value = key_value.second;
+  }
+
+  return result;
+}
+
+line_type config_parser::get_line_type(const string& line) {
+  if (line.empty()) {
+    return line_type::EMPTY;
+  }
+
+  switch (line[0]) {
+    case '[':
+      return line_type::HEADER;
+
+    case ';':
+    case '#':
+      return line_type::COMMENT;
+
+    default: {
+      if (string_util::contains(line, "=")) {
+        return line_type::KEY;
+      } else {
+        return line_type::UNKNOWN;
+      }
+    }
+  }
+}
+
+string config_parser::parse_header(const string& line) {
+  if (line.back() != ']') {
+    throw syntax_error("Missing ']' in header '" + line + "'");
+  }
+
+  // Stripping square brackets
+  string header = line.substr(1, line.size() - 2);
+
+  if (!is_valid_name(header)) {
+    throw invalid_name_error("Section", header);
+  }
+
+  if (m_reserved_section_names.find(header) != m_reserved_section_names.end()) {
+    throw syntax_error("'" + header + "' is reserved and cannot be used as a section name");
+  }
+
+  return header;
+}
+
+std::pair<string, string> config_parser::parse_key(const string& line) {
+  size_t pos = line.find_first_of('=');
+
+  string key = string_util::trim(line.substr(0, pos), isspace);
+  string value = string_util::trim(line.substr(pos + 1), isspace);
+
+  if (!is_valid_name(key)) {
+    throw invalid_name_error("Key", key);
+  }
+
+  /*
+   * Only if the string is surrounded with double quotes, do we treat them
+   * not as part of the value and remove them.
+   */
+  if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
+    value = value.substr(1, value.size() - 2);
+  }
+
+  // TODO check value for references
+
+#if WITH_XRM
+  // Use xrm, if at least one value is an xrdb reference
+  if (!use_xrm && value.find("${xrdb") == 0) {
+    use_xrm = true;
+  }
+#endif
+
+  return {move(key), move(value)};
+}
+
+bool config_parser::is_valid_name(const string& name) {
+  if (name.empty()) {
+    return false;
+  }
+
+  for (const char c : name) {
+    // Names with forbidden chars or spaces are not valid
+    if (isspace(c) || m_forbidden_chars.find_first_of(c) != string::npos) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+POLYBAR_NS_END
diff --git a/src/components/controller.cpp b/src/components/controller.cpp
new file mode 100644 (file)
index 0000000..86a1e98
--- /dev/null
@@ -0,0 +1,869 @@
+#include "components/controller.hpp"
+
+#include <csignal>
+#include <utility>
+
+#include "components/bar.hpp"
+#include "components/builder.hpp"
+#include "components/config.hpp"
+#include "components/ipc.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "modules/meta/base.hpp"
+#include "modules/meta/event_handler.hpp"
+#include "modules/meta/factory.hpp"
+#include "utils/actions.hpp"
+#include "utils/factory.hpp"
+#include "utils/inotify.hpp"
+#include "utils/process.hpp"
+#include "utils/string.hpp"
+#include "utils/time.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+
+POLYBAR_NS
+
+array<int, 2> g_eventpipe{{-1, -1}};
+sig_atomic_t g_reload{0};
+sig_atomic_t g_terminate{0};
+
+void interrupt_handler(int signum) {
+  if (g_reload || g_terminate) {
+    return;
+  }
+
+  g_terminate = 1;
+  g_reload = (signum == SIGUSR1);
+  if (write(g_eventpipe[PIPE_WRITE], &g_terminate, 1) == -1) {
+    throw system_error("Failed to write to eventpipe");
+  }
+}
+
+/**
+ * Build controller instance
+ */
+controller::make_type controller::make(unique_ptr<ipc>&& ipc, unique_ptr<inotify_watch>&& config_watch) {
+  return factory_util::unique<controller>(connection::make(), signal_emitter::make(), logger::make(), config::make(),
+      bar::make(), forward<decltype(ipc)>(ipc), forward<decltype(config_watch)>(config_watch));
+}
+
+/**
+ * Construct controller
+ */
+controller::controller(connection& conn, signal_emitter& emitter, const logger& logger, const config& config,
+    unique_ptr<bar>&& bar, unique_ptr<ipc>&& ipc, unique_ptr<inotify_watch>&& confwatch)
+    : m_connection(conn)
+    , m_sig(emitter)
+    , m_log(logger)
+    , m_conf(config)
+    , m_bar(forward<decltype(bar)>(bar))
+    , m_ipc(forward<decltype(ipc)>(ipc))
+    , m_confwatch(forward<decltype(confwatch)>(confwatch)) {
+  if (m_conf.has("settings", "throttle-input-for")) {
+    m_log.warn(
+        "The config parameter 'settings.throttle-input-for' is deprecated, it will be removed in the future. Please "
+        "remove it from your config");
+  }
+
+  m_swallow_limit = m_conf.deprecated("settings", "eventqueue-swallow", "throttle-output", m_swallow_limit);
+  m_swallow_update = m_conf.deprecated("settings", "eventqueue-swallow-time", "throttle-output-for", m_swallow_update);
+
+  if (pipe(g_eventpipe.data()) == 0) {
+    m_queuefd[PIPE_READ] = make_unique<file_descriptor>(g_eventpipe[PIPE_READ]);
+    m_queuefd[PIPE_WRITE] = make_unique<file_descriptor>(g_eventpipe[PIPE_WRITE]);
+  } else {
+    throw system_error("Failed to create event channel pipes");
+  }
+
+  m_log.trace("controller: Install signal handler");
+  struct sigaction act {};
+  memset(&act, 0, sizeof(act));
+  act.sa_handler = &interrupt_handler;
+  sigaction(SIGINT, &act, nullptr);
+  sigaction(SIGQUIT, &act, nullptr);
+  sigaction(SIGTERM, &act, nullptr);
+  sigaction(SIGUSR1, &act, nullptr);
+  sigaction(SIGALRM, &act, nullptr);
+
+  m_log.trace("controller: Setup user-defined modules");
+  size_t created_modules{0};
+  created_modules += setup_modules(alignment::LEFT);
+  created_modules += setup_modules(alignment::CENTER);
+  created_modules += setup_modules(alignment::RIGHT);
+
+  if (!created_modules) {
+    throw application_error("No modules created");
+  }
+}
+
+/**
+ * Deconstruct controller
+ */
+controller::~controller() {
+  m_log.trace("controller: Uninstall sighandler");
+  signal(SIGINT, SIG_DFL);
+  signal(SIGQUIT, SIG_DFL);
+  signal(SIGTERM, SIG_DFL);
+  signal(SIGALRM, SIG_DFL);
+
+  if (g_reload) {
+    // Cause SIGUSR1 to be ignored until registered in the new polybar process
+    signal(SIGUSR1, SIG_IGN);
+  }
+
+  m_log.trace("controller: Detach signal receiver");
+  m_sig.detach(this);
+
+  m_log.trace("controller: Stop modules");
+  for (auto&& module : m_modules) {
+    auto module_name = module->name();
+    auto cleanup_ms = time_util::measure([&module] { module->stop(); });
+    m_log.info("Deconstruction of %s took %lu ms.", module_name, cleanup_ms);
+  }
+
+  m_log.trace("controller: Joining threads");
+  for (auto&& t : m_threads) {
+    if (t.joinable()) {
+      t.join();
+    }
+  }
+}
+
+/**
+ * Run the main loop
+ */
+bool controller::run(bool writeback, string snapshot_dst) {
+  m_log.info("Starting application");
+  m_log.trace("controller: Main thread id = %i", concurrency_util::thread_id(this_thread::get_id()));
+
+  assert(!m_connection.connection_has_error());
+
+  m_writeback = writeback;
+  m_snapshot_dst = move(snapshot_dst);
+
+  m_sig.attach(this);
+
+  size_t started_modules{0};
+  for (const auto& module : m_modules) {
+    auto evt_handler = dynamic_cast<event_handler_interface*>(&*module);
+
+    if (evt_handler != nullptr) {
+      evt_handler->connect(m_connection);
+    }
+
+    try {
+      m_log.info("Starting %s", module->name());
+      module->start();
+      started_modules++;
+    } catch (const application_error& err) {
+      m_log.err("Failed to start '%s' (reason: %s)", module->name(), err.what());
+    }
+  }
+
+  if (!started_modules) {
+    throw application_error("No modules started");
+  }
+
+  m_connection.flush();
+  m_event_thread = thread(&controller::process_eventqueue, this);
+
+  read_events();
+
+  if (m_event_thread.joinable()) {
+    enqueue(make_quit_evt(static_cast<bool>(g_reload)));
+    m_event_thread.join();
+  }
+
+  m_log.notice("Termination signal received, shutting down...");
+
+  return !g_reload;
+}
+
+/**
+ * Enqueue event
+ */
+bool controller::enqueue(event&& evt) {
+  if (!m_process_events && evt.type != event_type::QUIT) {
+    return false;
+  }
+  if (!m_queue.enqueue(forward<decltype(evt)>(evt))) {
+    m_log.warn("Failed to enqueue event");
+    return false;
+  }
+  // if (write(g_eventpipe[PIPE_WRITE], " ", 1) == -1) {
+  //   m_log.err("Failed to write to eventpipe (reason: %s)", strerror(errno));
+  // }
+  return true;
+}
+
+/**
+ * Enqueue input data
+ */
+bool controller::enqueue(string&& input_data) {
+  if (!m_inputdata.empty()) {
+    m_log.trace("controller: Swallowing input event (pending data)");
+  } else {
+    m_inputdata = forward<string>(input_data);
+    return enqueue(make_input_evt());
+  }
+  return false;
+}
+
+/**
+ * Read events from configured file descriptors
+ */
+void controller::read_events() {
+  m_log.info("Entering event loop (thread-id=%lu)", this_thread::get_id());
+
+  int fd_connection{-1};
+  int fd_confwatch{-1};
+  int fd_ipc{-1};
+
+  vector<int> fds;
+  fds.emplace_back(*m_queuefd[PIPE_READ]);
+  fds.emplace_back((fd_connection = m_connection.get_file_descriptor()));
+
+  if (m_confwatch) {
+    m_log.trace("controller: Attach config watch");
+    m_confwatch->attach(IN_MODIFY | IN_IGNORED);
+    fds.emplace_back((fd_confwatch = m_confwatch->get_file_descriptor()));
+  }
+
+  if (m_ipc) {
+    fds.emplace_back((fd_ipc = m_ipc->get_file_descriptor()));
+  }
+
+  while (!g_terminate) {
+    fd_set readfds{};
+    FD_ZERO(&readfds);
+
+    int maxfd{0};
+    for (auto&& fd : fds) {
+      FD_SET(fd, &readfds);
+      maxfd = std::max(maxfd, fd);
+    }
+
+    // Wait until event is ready on one of the configured streams
+    int events = select(maxfd + 1, &readfds, nullptr, nullptr, nullptr);
+
+    // Check for errors
+    if (events == -1) {
+      /*
+       * The Interrupt errno is generated when polybar is stopped, so it
+       * shouldn't generate an error message
+       */
+      if (errno != EINTR) {
+        m_log.err("select failed in event loop: %s", strerror(errno));
+      }
+
+      break;
+    }
+
+    if (g_terminate || m_connection.connection_has_error()) {
+      break;
+    }
+
+    // Process event on the internal fd
+    if (m_queuefd[PIPE_READ] && FD_ISSET(static_cast<int>(*m_queuefd[PIPE_READ]), &readfds)) {
+      char buffer[BUFSIZ];
+      if (read(static_cast<int>(*m_queuefd[PIPE_READ]), &buffer, BUFSIZ) == -1) {
+        m_log.err("Failed to read from eventpipe (err: %s)", strerror(errno));
+      }
+    }
+
+    // Process event on the config inotify watch fd
+    unique_ptr<inotify_event> confevent;
+    if (fd_confwatch > -1 && FD_ISSET(fd_confwatch, &readfds) && (confevent = m_confwatch->await_match())) {
+      if (confevent->mask & IN_IGNORED) {
+        // IN_IGNORED: file was deleted or filesystem was unmounted
+        //
+        // This happens in some configurations of vim when a file is saved,
+        // since it is not actually issuing calls to write() but rather
+        // moves a file into the original's place after moving the original
+        // file to a different location (and subsequently deleting it).
+        //
+        // We need to re-attach the watch to the new file in this case.
+        fds.erase(
+            std::remove_if(fds.begin(), fds.end(), [fd_confwatch](int fd) { return fd == fd_confwatch; }), fds.end());
+        m_confwatch = inotify_util::make_watch(m_confwatch->path());
+        m_confwatch->attach(IN_MODIFY | IN_IGNORED);
+        fds.emplace_back((fd_confwatch = m_confwatch->get_file_descriptor()));
+      }
+      m_log.info("Configuration file changed");
+      g_terminate = 1;
+      g_reload = 1;
+    }
+
+    // Process event on the xcb connection fd
+    if (fd_connection > -1 && FD_ISSET(fd_connection, &readfds)) {
+      shared_ptr<xcb_generic_event_t> evt{};
+      while ((evt = shared_ptr<xcb_generic_event_t>(xcb_poll_for_event(m_connection), free)) != nullptr) {
+        try {
+          m_connection.dispatch_event(evt);
+        } catch (xpp::connection_error& err) {
+          m_log.err("X connection error, terminating... (what: %s)", m_connection.error_str(err.code()));
+        } catch (const exception& err) {
+          m_log.err("Error in X event loop: %s", err.what());
+        }
+      }
+    }
+
+    // Process event on the ipc fd
+    if (fd_ipc > -1 && FD_ISSET(fd_ipc, &readfds)) {
+      m_ipc->receive_message();
+      fds.erase(std::remove_if(fds.begin(), fds.end(), [fd_ipc](int fd) { return fd == fd_ipc; }), fds.end());
+      fds.emplace_back((fd_ipc = m_ipc->get_file_descriptor()));
+    }
+  }
+}
+
+/**
+ * Eventqueue worker loop
+ */
+void controller::process_eventqueue() {
+  m_log.info("Eventqueue worker (thread-id=%lu)", this_thread::get_id());
+  if (!m_writeback) {
+    m_sig.emit(signals::eventqueue::start{});
+  } else {
+    // bypass the start eventqueue signal
+    m_sig.emit(signals::ui::ready{});
+  }
+
+  while (!g_terminate) {
+    event evt{};
+    m_queue.wait_dequeue(evt);
+
+    if (g_terminate) {
+      break;
+    } else if (evt.type == event_type::QUIT) {
+      if (evt.flag) {
+        on(signals::eventqueue::exit_reload{});
+      } else {
+        on(signals::eventqueue::exit_terminate{});
+      }
+    } else if (evt.type == event_type::INPUT) {
+      process_inputdata();
+    } else if (evt.type == event_type::UPDATE && evt.flag) {
+      process_update(true);
+    } else {
+      event next{};
+      size_t swallowed{0};
+      while (swallowed++ < m_swallow_limit && m_queue.wait_dequeue_timed(next, m_swallow_update)) {
+        if (next.type == event_type::QUIT) {
+          evt = next;
+          break;
+        } else if (next.type == event_type::INPUT) {
+          evt = next;
+          break;
+        } else if (evt.type != next.type) {
+          enqueue(move(next));
+          break;
+        } else {
+          m_log.trace_x("controller: Swallowing event within timeframe");
+          evt = next;
+        }
+      }
+
+      if (evt.type == event_type::UPDATE) {
+        process_update(evt.flag);
+      } else if (evt.type == event_type::INPUT) {
+        process_inputdata();
+      } else if (evt.type == event_type::QUIT) {
+        if (evt.flag) {
+          on(signals::eventqueue::exit_reload{});
+        } else {
+          on(signals::eventqueue::exit_terminate{});
+        }
+      } else if (evt.type == event_type::CHECK) {
+        on(signals::eventqueue::check_state{});
+      } else {
+        m_log.warn("Unknown event type for enqueued event (%d)", evt.type);
+      }
+    }
+  }
+}
+
+/**
+ * Tries to match the given command to a legacy action string and sends the
+ * appropriate new action (and data) to the right module if possible.
+ *
+ * \returns true iff the given command matches a legacy action string and was
+ *          successfully forwarded to a module
+ */
+bool controller::try_forward_legacy_action(const string& cmd) {
+  /*
+   * Maps legacy action names to a module type and the new action name in that module.
+   *
+   * We try to match the old action name as a prefix, and everything after it will also be added to the end of the new
+   * action string (for example "mpdseek+5" will be redirected to "seek.+5" in the first mpd module).
+   *
+   * The action will be delivered to the first module of that type so that it is consistent with existing behavior.
+   * If the module does not support the action or no matching module is found, the command is forwarded to the shell.
+   *
+   * TODO Remove when deprecated action names are removed
+   */
+// clang-format off
+#define A_MAP(old, module_name, event) {old, {string(module_name::TYPE), string(module_name::event)}}
+
+  static const std::unordered_map<string, std::pair<string, const string>> legacy_actions{
+    A_MAP("datetoggle", date_module, EVENT_TOGGLE),
+#if ENABLE_ALSA
+    A_MAP("volup", alsa_module, EVENT_INC),
+    A_MAP("voldown", alsa_module, EVENT_DEC),
+    A_MAP("volmute", alsa_module, EVENT_TOGGLE),
+#endif
+#if ENABLE_PULSEAUDIO
+    A_MAP("pa_volup", pulseaudio_module, EVENT_INC),
+    A_MAP("pa_voldown", pulseaudio_module, EVENT_DEC),
+    A_MAP("pa_volmute", pulseaudio_module, EVENT_TOGGLE),
+#endif
+    A_MAP("xbacklight+", xbacklight_module, EVENT_INC),
+    A_MAP("xbacklight-", xbacklight_module, EVENT_DEC),
+    A_MAP("backlight+", backlight_module, EVENT_INC),
+    A_MAP("backlight-", backlight_module, EVENT_DEC),
+#if ENABLE_XKEYBOARD
+    A_MAP("xkeyboard/switch", xkeyboard_module, EVENT_SWITCH),
+#endif
+#if ENABLE_MPD
+    A_MAP("mpdplay", mpd_module, EVENT_PLAY),
+    A_MAP("mpdpause", mpd_module, EVENT_PAUSE),
+    A_MAP("mpdstop", mpd_module, EVENT_STOP),
+    A_MAP("mpdprev", mpd_module, EVENT_PREV),
+    A_MAP("mpdnext", mpd_module, EVENT_NEXT),
+    A_MAP("mpdrepeat", mpd_module, EVENT_REPEAT),
+    A_MAP("mpdsingle", mpd_module, EVENT_SINGLE),
+    A_MAP("mpdrandom", mpd_module, EVENT_RANDOM),
+    A_MAP("mpdconsume", mpd_module, EVENT_CONSUME),
+    // Has data
+    A_MAP("mpdseek", mpd_module, EVENT_SEEK),
+#endif
+    // Has data
+    A_MAP("xworkspaces-focus=", xworkspaces_module, EVENT_FOCUS),
+    A_MAP("xworkspaces-next", xworkspaces_module, EVENT_NEXT),
+    A_MAP("xworkspaces-prev", xworkspaces_module, EVENT_PREV),
+    // Has data
+    A_MAP("bspwm-deskfocus", bspwm_module, EVENT_FOCUS),
+    A_MAP("bspwm-desknext", bspwm_module, EVENT_NEXT),
+    A_MAP("bspwm-deskprev", bspwm_module, EVENT_PREV),
+#if ENABLE_I3
+    // Has data
+    A_MAP("i3wm-wsfocus-", i3_module, EVENT_FOCUS),
+    A_MAP("i3wm-wsnext", i3_module, EVENT_NEXT),
+    A_MAP("i3wm-wsprev", i3_module, EVENT_PREV),
+#endif
+    // Has data
+    A_MAP("menu-open-", menu_module, EVENT_OPEN),
+    A_MAP("menu-close", menu_module, EVENT_CLOSE),
+  };
+#undef A_MAP
+  // clang-format on
+
+  // Check if any key in the map is a prefix for the `cmd`
+  for (const auto& entry : legacy_actions) {
+    const auto& key = entry.first;
+    if (cmd.compare(0, key.length(), key) == 0) {
+      string type = entry.second.first;
+      auto data = cmd.substr(key.length());
+      string action = entry.second.second;
+
+      // Search for the first module that matches the type for this legacy action
+      for (auto&& module : m_modules) {
+        if (module->type() == type) {
+          auto module_name = module->name_raw();
+          // TODO make this message more descriptive and maybe link to some documentation
+          // TODO use route to string methods to print action name that should be used.
+          if (data.empty()) {
+            m_log.warn("The action '%s' is deprecated, use '#%s.%s' instead!", cmd, module_name, action);
+          } else {
+            m_log.warn("The action '%s' is deprecated, use '#%s.%s.%s' instead!", cmd, module_name, action, data);
+          }
+          m_log.warn("Consult the 'Actions' page in the polybar documentation for more information.");
+          m_log.info(
+              "Forwarding legacy action '%s' to module '%s' as '%s' with data '%s'", cmd, module_name, action, data);
+          if (!module->input(action, data)) {
+            m_log.err("Failed to forward deprecated action to %s module", type);
+            // Forward to shell if the module cannot accept the action to not break existing behavior.
+            return false;
+          }
+          // Only deliver to the first matching module.
+          return true;
+        }
+      }
+    }
+  }
+
+  /*
+   * If we couldn't find any matching legacy action, we return false and let
+   * the command be forwarded to the shell
+   */
+  return false;
+}
+
+bool controller::forward_action(const actions_util::action& action_triple) {
+  string module_name = std::get<0>(action_triple);
+  string action = std::get<1>(action_triple);
+  string data = std::get<2>(action_triple);
+
+  m_log.info("Forwarding action to modules (module: '%s', action: '%s', data: '%s')", module_name, action, data);
+
+  int num_delivered = 0;
+
+  // Forwards the action to all modules that match the name
+  for (auto&& module : m_modules) {
+    if (module->name_raw() == module_name) {
+      if (!module->input(action, data)) {
+        m_log.err("The '%s' module does not support the '%s' action.", module_name, action);
+      }
+
+      num_delivered++;
+    }
+  }
+
+  if (num_delivered == 0) {
+    m_log.err("Could not forward action to module: No module named '%s' (action: '%s', data: '%s')", module_name,
+        action, data);
+  } else {
+    m_log.info("Delivered action to %d module%s", num_delivered, num_delivered > 1 ? "s" : "");
+  }
+  return true;
+}
+
+/**
+ * Process stored input data
+ */
+void controller::process_inputdata() {
+  if (m_inputdata.empty()) {
+    return;
+  }
+
+  const string cmd = std::move(m_inputdata);
+  m_inputdata = string{};
+
+  m_log.trace("controller: Processing inputdata: %s", cmd);
+
+  // Every command that starts with '#' is considered an action string.
+  if (cmd.front() == '#') {
+    try {
+      this->forward_action(actions_util::parse_action_string(cmd));
+    } catch (runtime_error& e) {
+      m_log.err("Invalid action string (action: %s, reason: %s)", cmd, e.what());
+    }
+
+    return;
+  }
+
+  if (this->try_forward_legacy_action(cmd)) {
+    return;
+  }
+
+  try {
+    // Run input as command if it's not an input for a module
+    m_log.info("Forwarding command to shell... (input: %s)", cmd);
+    m_log.info("Executing shell command: %s", cmd);
+    process_util::fork_detached([cmd] { process_util::exec_sh(cmd.c_str()); });
+    process_update(true);
+  } catch (const application_error& err) {
+    m_log.err("controller: Error while forwarding input to shell -> %s", err.what());
+  }
+}
+
+/**
+ * Process eventqueue update event
+ */
+bool controller::process_update(bool force) {
+  const bar_settings& bar{m_bar->settings()};
+  string contents;
+  string padding_left(bar.padding.left, ' ');
+  string padding_right(bar.padding.right, ' ');
+  string margin_left(bar.module_margin.left, ' ');
+  string margin_right(bar.module_margin.right, ' ');
+
+  builder build{bar};
+  build.node(bar.separator);
+  string separator{build.flush()};
+
+  for (const auto& block : m_blocks) {
+    string block_contents;
+    bool is_left = false;
+    bool is_center = false;
+    bool is_right = false;
+    bool is_first = true;
+
+    if (block.first == alignment::LEFT) {
+      is_left = true;
+    } else if (block.first == alignment::CENTER) {
+      is_center = true;
+    } else if (block.first == alignment::RIGHT) {
+      is_right = true;
+    }
+
+    for (const auto& module : block.second) {
+      if (!module->running()) {
+        continue;
+      }
+
+      string module_contents;
+
+      try {
+        module_contents = module->contents();
+      } catch (const exception& err) {
+        m_log.err("Failed to get contents for \"%s\" (err: %s)", module->name(), err.what());
+      }
+
+      if (module_contents.empty()) {
+        continue;
+      }
+
+      if (!block_contents.empty() && !margin_right.empty()) {
+        block_contents += margin_right;
+      }
+
+      if (!block_contents.empty() && !separator.empty()) {
+        block_contents += separator;
+      }
+
+      if (!block_contents.empty() && !margin_left.empty() && !(is_left && is_first)) {
+        block_contents += margin_left;
+      }
+
+      block_contents.reserve(module_contents.size());
+      block_contents += module_contents;
+
+      is_first = false;
+    }
+
+    if (block_contents.empty()) {
+      continue;
+    } else if (is_left) {
+      contents += "%{l}";
+      contents += padding_left;
+    } else if (is_center) {
+      contents += "%{c}";
+    } else if (is_right) {
+      contents += "%{r}";
+      block_contents += padding_right;
+    }
+
+    contents += block_contents;
+  }
+
+  try {
+    if (!m_writeback) {
+      m_bar->parse(move(contents), force);
+    } else {
+      std::cout << contents << std::endl;
+    }
+  } catch (const exception& err) {
+    m_log.err("Failed to update bar contents (reason: %s)", err.what());
+  }
+
+  return true;
+}
+
+/**
+ * Creates module instances for all the modules in the given alignment block
+ */
+size_t controller::setup_modules(alignment align) {
+  size_t count{0};
+
+  string key;
+
+  switch (align) {
+    case alignment::LEFT:
+      key = "modules-left";
+      break;
+
+    case alignment::CENTER:
+      key = "modules-center";
+      break;
+
+    case alignment::RIGHT:
+      key = "modules-right";
+      break;
+
+    case alignment::NONE:
+      m_log.err("controller: Tried to setup modules for alignment NONE");
+      break;
+  }
+
+  string configured_modules;
+  if (!key.empty()) {
+    configured_modules = m_conf.get(m_conf.section(), key, ""s);
+  }
+
+  for (auto& module_name : string_util::split(configured_modules, ' ')) {
+    if (module_name.empty()) {
+      continue;
+    }
+
+    try {
+      auto type = m_conf.get("module/" + module_name, "type");
+
+      if (type == ipc_module::TYPE && !m_ipc) {
+        throw application_error("Inter-process messaging needs to be enabled");
+      }
+
+      auto ptr = make_module(move(type), m_bar->settings(), module_name, m_log);
+      module_t module = shared_ptr<modules::module_interface>(ptr);
+      ptr = nullptr;
+
+      m_modules.push_back(module);
+      m_blocks[align].push_back(module);
+      count++;
+    } catch (const runtime_error& err) {
+      m_log.err("Disabling module \"%s\" (reason: %s)", module_name, err.what());
+    }
+  }
+
+  return count;
+}
+
+/**
+ * Process broadcast events
+ */
+bool controller::on(const signals::eventqueue::notify_change&) {
+  return enqueue(make_update_evt(false));
+}
+
+/**
+ * Process forced broadcast events
+ */
+bool controller::on(const signals::eventqueue::notify_forcechange&) {
+  return enqueue(make_update_evt(true));
+}
+
+/**
+ * Process eventqueue terminate event
+ */
+bool controller::on(const signals::eventqueue::exit_terminate&) {
+  raise(SIGALRM);
+  return true;
+}
+
+/**
+ * Process eventqueue reload event
+ */
+bool controller::on(const signals::eventqueue::exit_reload&) {
+  raise(SIGUSR1);
+  return true;
+}
+
+/**
+ * Process eventqueue check event
+ */
+bool controller::on(const signals::eventqueue::check_state&) {
+  for (const auto& module : m_modules) {
+    if (module->running()) {
+      return true;
+    }
+  }
+  m_log.warn("No running modules...");
+  on(signals::eventqueue::exit_terminate{});
+  return true;
+}
+
+/**
+ * Process ui ready event
+ */
+bool controller::on(const signals::ui::ready&) {
+  m_process_events = true;
+  enqueue(make_update_evt(true));
+
+  if (!m_snapshot_dst.empty()) {
+    m_threads.emplace_back(thread([&] {
+      this_thread::sleep_for(3s);
+      m_sig.emit(signals::ui::request_snapshot{move(m_snapshot_dst)});
+      enqueue(make_update_evt(true));
+    }));
+  }
+
+  // let the event bubble
+  return false;
+}
+
+/**
+ * Process ui button press event
+ */
+bool controller::on(const signals::ui::button_press& evt) {
+  string input{evt.cast()};
+
+  if (input.empty()) {
+    m_log.err("Cannot enqueue empty input");
+    return false;
+  }
+
+  enqueue(move(input));
+  return true;
+}
+
+/**
+ * Process ipc action messages
+ */
+bool controller::on(const signals::ipc::action& evt) {
+  string action{evt.cast()};
+
+  if (action.empty()) {
+    m_log.err("Cannot enqueue empty ipc action");
+    return false;
+  }
+
+  m_log.info("Enqueuing ipc action: %s", action);
+  enqueue(move(action));
+  return true;
+}
+
+/**
+ * Process ipc command messages
+ */
+bool controller::on(const signals::ipc::command& evt) {
+  string command{evt.cast()};
+
+  if (command.empty()) {
+    return false;
+  }
+
+  if (command == "quit") {
+    enqueue(make_quit_evt(false));
+  } else if (command == "restart") {
+    enqueue(make_quit_evt(true));
+  } else if (command == "hide") {
+    m_bar->hide();
+  } else if (command == "show") {
+    m_bar->show();
+  } else if (command == "toggle") {
+    m_bar->toggle();
+  } else {
+    m_log.warn("\"%s\" is not a valid ipc command", command);
+  }
+
+  return true;
+}
+
+/**
+ * Process ipc hook messages
+ */
+bool controller::on(const signals::ipc::hook& evt) {
+  string hook{evt.cast()};
+
+  for (const auto& module : m_modules) {
+    if (!module->running()) {
+      continue;
+    }
+    auto ipc = std::dynamic_pointer_cast<ipc_module>(module);
+    if (ipc != nullptr) {
+      ipc->on_message(hook);
+    }
+  }
+
+  return true;
+}
+
+bool controller::on(const signals::ui::update_background&) {
+  enqueue(make_update_evt(true));
+
+  return false;
+}
+
+POLYBAR_NS_END
diff --git a/src/components/ipc.cpp b/src/components/ipc.cpp
new file mode 100644 (file)
index 0000000..d51ea58
--- /dev/null
@@ -0,0 +1,85 @@
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include "components/ipc.hpp"
+#include "components/logger.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "utils/factory.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+/**
+ * Create instance
+ */
+ipc::make_type ipc::make() {
+  return factory_util::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);
+  m_fd = file_util::make_file_descriptor(m_path, O_RDONLY | O_NONBLOCK);
+}
+
+/**
+ * Deconstruct ipc handler
+ */
+ipc::~ipc() {
+  m_fd.reset();
+
+  if (!m_path.empty()) {
+    m_log.trace("ipc: Removing file handle");
+    unlink(m_path.c_str());
+  }
+}
+
+/**
+ * Receive available ipc messages and delegate valid events
+ */
+void ipc::receive_message() {
+  m_log.info("Receiving ipc message");
+
+  char buffer[BUFSIZ]{'\0'};
+  ssize_t bytes_read{0};
+
+  if ((bytes_read = read(*m_fd, &buffer, BUFSIZ)) == -1) {
+    m_log.err("Failed to read from ipc channel (err: %s)", strerror(errno));
+  } else if (bytes_read > 0) {
+    string payload{string_util::trim(string{buffer}, '\n')};
+
+    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 if (!payload.empty()) {
+      m_log.warn("Received unknown ipc message: (payload=%s)", payload);
+    }
+  }
+
+  m_fd = file_util::make_file_descriptor(m_path, O_RDONLY | O_NONBLOCK);
+}
+
+/**
+ * Get the file descriptor to the ipc channel
+ */
+int ipc::get_file_descriptor() const {
+  return *m_fd;
+}
+
+POLYBAR_NS_END
diff --git a/src/components/logger.cpp b/src/components/logger.cpp
new file mode 100644 (file)
index 0000000..f515179
--- /dev/null
@@ -0,0 +1,100 @@
+#include "components/logger.hpp"
+
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/concurrency.hpp"
+#include "utils/factory.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+/**
+ * Convert string
+ */
+const char* logger::convert(string& arg) const {
+  return arg.c_str();
+}
+
+const char* logger::convert(const string& arg) const {
+  return arg.c_str();
+}
+
+/**
+ * Convert thread id
+ */
+size_t logger::convert(const std::thread::id arg) const {
+  return concurrency_util::thread_id(arg);
+}
+
+/**
+ * Create instance
+ */
+logger::make_type logger::make(loglevel level) {
+  return *factory_util::singleton<std::remove_reference_t<logger::make_type>>(level);
+}
+
+/**
+ * Construct logger
+ */
+logger::logger(loglevel level) : m_level(level) {
+  // clang-format off
+  if (isatty(m_fd)) {
+    m_prefixes[loglevel::TRACE]   = "\r\033[0;32m- \033[0m";
+    m_prefixes[loglevel::INFO]    = "\r\033[1;32m* \033[0m";
+    m_prefixes[loglevel::NOTICE]  = "\r\033[1;34mnotice: \033[0m";
+    m_prefixes[loglevel::WARNING] = "\r\033[1;33mwarn: \033[0m";
+    m_prefixes[loglevel::ERROR]   = "\r\033[1;31merror: \033[0m";
+    m_suffixes[loglevel::TRACE]   = "\033[0m";
+    m_suffixes[loglevel::INFO]    = "\033[0m";
+    m_suffixes[loglevel::NOTICE]  = "\033[0m";
+    m_suffixes[loglevel::WARNING] = "\033[0m";
+    m_suffixes[loglevel::ERROR]   = "\033[0m";
+  } else {
+    m_prefixes.emplace(make_pair(loglevel::TRACE,   "polybar|trace: "));
+    m_prefixes.emplace(make_pair(loglevel::INFO,    "polybar|info:  "));
+    m_prefixes.emplace(make_pair(loglevel::NOTICE,  "polybar|notice:  "));
+    m_prefixes.emplace(make_pair(loglevel::WARNING, "polybar|warn:  "));
+    m_prefixes.emplace(make_pair(loglevel::ERROR,   "polybar|error: "));
+    m_suffixes.emplace(make_pair(loglevel::TRACE,   ""));
+    m_suffixes.emplace(make_pair(loglevel::INFO,    ""));
+    m_suffixes.emplace(make_pair(loglevel::NOTICE,  ""));
+    m_suffixes.emplace(make_pair(loglevel::WARNING, ""));
+    m_suffixes.emplace(make_pair(loglevel::ERROR,   ""));
+  }
+  // clang-format on
+}
+
+/**
+ * Set output verbosity
+ */
+void logger::verbosity(loglevel level) {
+#ifndef DEBUG_LOGGER
+  if (level == loglevel::TRACE) {
+    throw application_error("Trace logging is not enabled...");
+  }
+#endif
+  m_level = level;
+}
+
+/**
+ * Convert given loglevel name to its enum type counterpart
+ */
+loglevel logger::parse_verbosity(const string& name, loglevel fallback) {
+  if (string_util::compare(name, "error")) {
+    return loglevel::ERROR;
+  } else if (string_util::compare(name, "warning")) {
+    return loglevel::WARNING;
+  } else if (string_util::compare(name, "notice")) {
+    return loglevel::NOTICE;
+  } else if (string_util::compare(name, "info")) {
+    return loglevel::INFO;
+  } else if (string_util::compare(name, "trace")) {
+    return loglevel::TRACE;
+  } else {
+    return fallback;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/components/parser.cpp b/src/components/parser.cpp
new file mode 100644 (file)
index 0000000..eb5069a
--- /dev/null
@@ -0,0 +1,323 @@
+#include "components/parser.hpp"
+
+#include <cassert>
+
+#include "components/types.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "settings.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/memory.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+using namespace signals::parser;
+
+/**
+ * Create instance
+ */
+parser::make_type parser::make() {
+  return factory_util::unique<parser>(signal_emitter::make());
+}
+
+/**
+ * Construct parser instance
+ */
+parser::parser(signal_emitter& emitter) : m_sig(emitter) {}
+
+/**
+ * Process input string
+ */
+void parser::parse(const bar_settings& bar, string data) {
+  while (!data.empty()) {
+    size_t pos{string::npos};
+
+    if (data.compare(0, 2, "%{") == 0 && (pos = data.find('}')) != string::npos) {
+      codeblock(data.substr(2, pos - 2), bar);
+      data.erase(0, pos + 1);
+    } else if ((pos = data.find("%{")) != string::npos) {
+      data.erase(0, text(data.substr(0, pos)));
+    } else {
+      data.erase(0, text(data.substr(0)));
+    }
+  }
+
+  if (!m_actions.empty()) {
+    throw unclosed_actionblocks(to_string(m_actions.size()) + " unclosed action block(s)");
+  }
+}
+
+/**
+ * Process contents within tag blocks, i.e: %{...}
+ */
+void parser::codeblock(string&& data, const bar_settings& bar) {
+  size_t pos;
+
+  while (data.length()) {
+    data = string_util::ltrim(move(data), ' ');
+
+    if (data.empty()) {
+      break;
+    }
+
+    char tag{data[0]};
+
+    /*
+     * Contains the string from the current position to the next space or
+     * closing curly bracket (})
+     *
+     * This may be unsuitable for some tags (e.g. action tag) to use
+     * These MUST set value to the actual string they parsed from the beginning
+     * of data (or a string with the same length). The length of value is used
+     * to progress the cursor further.
+     *
+     * example:
+     *
+     * data = A1:echo "test": ...}
+     *
+     * erase(0,1)
+     * -> data = 1:echo "test": ...}
+     *
+     * case 'A', parse_action_cmd
+     * -> value = echo "test"
+     *
+     * Padding value
+     * -> value = echo "test"0::
+     *
+     * erase(0, value.length())
+     * -> data =  ...}
+     *
+     */
+    string value;
+
+    // Remove the tag
+    data.erase(0, 1);
+
+    if ((pos = data.find_first_of(" }")) != string::npos) {
+      value = data.substr(0, pos);
+    } else {
+      value = data;
+    }
+
+    switch (tag) {
+      case 'B':
+        m_sig.emit(change_background{parse_color(value, bar.background)});
+        break;
+
+      case 'F':
+        m_sig.emit(change_foreground{parse_color(value, bar.foreground)});
+        break;
+
+      case 'T':
+        m_sig.emit(change_font{parse_fontindex(value)});
+        break;
+
+      case 'U':
+        m_sig.emit(change_underline{parse_color(value, bar.underline.color)});
+        m_sig.emit(change_overline{parse_color(value, bar.overline.color)});
+        break;
+
+      case 'u':
+        m_sig.emit(change_underline{parse_color(value, bar.underline.color)});
+        break;
+
+      case 'o':
+        m_sig.emit(change_overline{parse_color(value, bar.overline.color)});
+        break;
+
+      case 'R':
+        m_sig.emit(reverse_colors{});
+        break;
+
+      case 'O':
+        m_sig.emit(offset_pixel{static_cast<int>(std::strtol(value.c_str(), nullptr, 10))});
+        break;
+
+      case 'l':
+        m_sig.emit(change_alignment{alignment::LEFT});
+        break;
+
+      case 'c':
+        m_sig.emit(change_alignment{alignment::CENTER});
+        break;
+
+      case 'r':
+        m_sig.emit(change_alignment{alignment::RIGHT});
+        break;
+
+      case '+':
+        m_sig.emit(attribute_set{parse_attr(value[0])});
+        break;
+
+      case '-':
+        m_sig.emit(attribute_unset{parse_attr(value[0])});
+        break;
+
+      case '!':
+        m_sig.emit(attribute_toggle{parse_attr(value[0])});
+        break;
+
+      case 'A': {
+        bool has_btn_id = (data[0] != ':');
+        if (isdigit(data[0]) || !has_btn_id) {
+          value = parse_action_cmd(data.substr(has_btn_id ? 1 : 0));
+          mousebtn btn = parse_action_btn(data);
+          m_actions.push_back(static_cast<int>(btn));
+
+          // Unescape colons inside command before sending it to the renderer
+          auto cmd = string_util::replace_all(value, "\\:", ":");
+          m_sig.emit(action_begin{action{btn, cmd}});
+
+          /*
+           * make sure value has the same length as the inside of the action
+           * tag which is btn_id + ':' + value + ':'
+           */
+          if (has_btn_id) {
+            value += "0";
+          }
+          value += "::";
+        } else if (!m_actions.empty()) {
+          m_sig.emit(action_end{parse_action_btn(value)});
+          m_actions.pop_back();
+        }
+        break;
+      }
+
+      // Internal Polybar control tags
+      case 'P':
+        m_sig.emit(control{parse_control(value)});
+        break;
+
+      default:
+        throw unrecognized_token("Unrecognized token '" + string{tag} + "'");
+    }
+
+    if (!data.empty()) {
+      // Remove the parsed string from data
+      data.erase(0, !value.empty() ? value.length() : 1);
+    }
+  }
+}
+
+/**
+ * Process text contents
+ */
+size_t parser::text(string&& data) {
+#ifdef DEBUG_WHITESPACE
+  string::size_type p;
+  while ((p = data.find(' ')) != string::npos) {
+    data.replace(p, 1, "-"s);
+  }
+#endif
+
+  m_sig.emit(signals::parser::text{forward<string>(data)});
+  return data.size();
+}
+
+/**
+ * Process color hex string and convert it to the correct value
+ */
+rgba parser::parse_color(const string& s, rgba fallback) {
+  if (!s.empty() && s[0] != '-') {
+    rgba ret = rgba{s};
+
+    if (!ret.has_color() || ret.type() == rgba::ALPHA_ONLY) {
+      logger::make().warn(
+          "Invalid color in formatting tag detected: \"%s\", using fallback \"%s\". This is an issue with one of your "
+          "formatting tags. If it is not, please report this as a bug.",
+          s, static_cast<string>(fallback));
+      return fallback;
+    }
+
+    return ret;
+  }
+  return fallback;
+}
+
+/**
+ * Process font index and convert it to the correct value
+ */
+int parser::parse_fontindex(const string& s) {
+  if (s.empty() || s[0] == '-') {
+    return 0;
+  }
+
+  try {
+    return std::stoul(s, nullptr, 10);
+  } catch (const std::invalid_argument& err) {
+    return 0;
+  }
+}
+
+/**
+ * Process attribute token and convert it to the correct value
+ */
+attribute parser::parse_attr(const char attr) {
+  switch (attr) {
+    case 'o':
+      return attribute::OVERLINE;
+    case 'u':
+      return attribute::UNDERLINE;
+    default:
+      throw unrecognized_token("Unrecognized attribute '" + string{attr} + "'");
+  }
+}
+
+/**
+ * Process action button token and convert it to the correct value
+ */
+mousebtn parser::parse_action_btn(const string& data) {
+  if (data[0] == ':') {
+    return mousebtn::LEFT;
+  } else if (isdigit(data[0])) {
+    return static_cast<mousebtn>(data[0] - '0');
+  } else if (!m_actions.empty()) {
+    return static_cast<mousebtn>(m_actions.back());
+  } else {
+    return mousebtn::NONE;
+  }
+}
+
+/**
+ * Process action command string
+ *
+ * data is the action cmd surrounded by unescaped colons followed by an
+ * arbitrary string
+ *
+ * Returns everything inside the unescaped colons as is
+ */
+string parser::parse_action_cmd(string&& data) {
+  if (data[0] != ':') {
+    return "";
+  }
+
+  size_t end{1};
+  while ((end = data.find(':', end)) != string::npos && data[end - 1] == '\\') {
+    end++;
+  }
+
+  if (end == string::npos) {
+    return "";
+  }
+
+  return data.substr(1, end - 1);
+}
+
+controltag parser::parse_control(const string& data) {
+  if (data.length() != 1) {
+    return controltag::NONE;
+  }
+
+  switch (data[0]) {
+    case 'R':
+      return controltag::R;
+      break;
+    default:
+      return controltag::NONE;
+      break;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/components/renderer.cpp b/src/components/renderer.cpp
new file mode 100644 (file)
index 0000000..59f7d61
--- /dev/null
@@ -0,0 +1,896 @@
+#include "components/renderer.hpp"
+
+#include "cairo/context.hpp"
+#include "components/config.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "x11/atoms.hpp"
+#include "x11/background_manager.hpp"
+#include "x11/connection.hpp"
+#include "x11/winspec.hpp"
+
+POLYBAR_NS
+
+static constexpr double BLOCK_GAP{20.0};
+
+/**
+ * Create instance
+ */
+renderer::make_type renderer::make(const bar_settings& bar) {
+  // clang-format off
+  return factory_util::unique<renderer>(
+      connection::make(),
+      signal_emitter::make(),
+      config::make(),
+      logger::make(),
+      forward<decltype(bar)>(bar),
+      background_manager::make());
+  // clang-format on
+}
+
+/**
+ * Construct renderer instance
+ */
+renderer::renderer(connection& conn, signal_emitter& sig, const config& conf, const logger& logger,
+    const bar_settings& bar, background_manager& background)
+    : m_connection(conn)
+    , m_sig(sig)
+    , m_conf(conf)
+    , m_log(logger)
+    , m_bar(forward<const bar_settings&>(bar))
+    , m_rect(m_bar.inner_area()) {
+  m_sig.attach(this);
+  m_log.trace("renderer: Get TrueColor visual");
+  {
+    if ((m_visual = m_connection.visual_type(m_connection.screen(), 32)) == nullptr) {
+      m_log.err("No 32-bit TrueColor visual found...");
+
+      if ((m_visual = m_connection.visual_type(m_connection.screen(), 24)) == nullptr) {
+        m_log.err("No 24-bit TrueColor visual found...");
+      } else {
+        m_depth = 24;
+      }
+    }
+    if (m_visual == nullptr) {
+      throw application_error("No matching TrueColor");
+    }
+  }
+
+  m_log.trace("renderer: Allocate colormap");
+  {
+    m_colormap = m_connection.generate_id();
+    m_connection.create_colormap(XCB_COLORMAP_ALLOC_NONE, m_colormap, m_connection.screen()->root, m_visual->visual_id);
+  }
+
+  m_log.trace("renderer: Allocate output window");
+  {
+    // clang-format off
+    m_window = winspec(m_connection)
+      << cw_size(m_bar.size)
+      << cw_pos(m_bar.pos)
+      << cw_depth(m_depth)
+      << cw_visual(m_visual->visual_id)
+      << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT)
+      << cw_params_back_pixel(0)
+      << cw_params_border_pixel(0)
+      << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED)
+      << cw_params_colormap(m_colormap)
+      << cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE
+                             |XCB_EVENT_MASK_EXPOSURE
+                             |XCB_EVENT_MASK_BUTTON_PRESS)
+      << cw_params_override_redirect(m_bar.override_redirect)
+      << cw_flush(true);
+    // clang-format on
+  }
+
+  m_log.trace("renderer: Allocate window pixmaps");
+  {
+    m_pixmap = m_connection.generate_id();
+    m_connection.create_pixmap(m_depth, m_pixmap, m_window, m_bar.size.w, m_bar.size.h);
+  }
+
+  m_log.trace("renderer: Allocate graphic contexts");
+  {
+    unsigned int mask{0};
+    unsigned int value_list[32]{0};
+    xcb_params_gc_t params{};
+    XCB_AUX_ADD_PARAM(&mask, &params, foreground, m_bar.foreground);
+    XCB_AUX_ADD_PARAM(&mask, &params, graphics_exposures, 0);
+    connection::pack_values(mask, &params, value_list);
+    m_gcontext = m_connection.generate_id();
+    m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list);
+  }
+
+  m_log.trace("renderer: Allocate alignment blocks");
+  {
+    m_blocks.emplace(alignment::LEFT, alignment_block{nullptr, 0.0, 0.0});
+    m_blocks.emplace(alignment::CENTER, alignment_block{nullptr, 0.0, 0.0});
+    m_blocks.emplace(alignment::RIGHT, alignment_block{nullptr, 0.0, 0.0});
+  }
+
+  m_log.trace("renderer: Allocate cairo components");
+  {
+    m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, m_visual, m_bar.size.w, m_bar.size.h);
+    m_context = make_unique<cairo::context>(*m_surface, m_log);
+  }
+
+  m_log.trace("renderer: Load fonts");
+  {
+    double dpi_x = 96, dpi_y = 96;
+    if (m_conf.has(m_conf.section(), "dpi")) {
+      dpi_x = dpi_y = m_conf.get<double>("dpi");
+    } else {
+      if (m_conf.has(m_conf.section(), "dpi-x")) {
+        dpi_x = m_conf.get<double>("dpi-x");
+      }
+      if (m_conf.has(m_conf.section(), "dpi-y")) {
+        dpi_y = m_conf.get<double>("dpi-y");
+      }
+    }
+
+    // dpi to be comptued
+    if (dpi_x <= 0 || dpi_y <= 0) {
+      auto screen = m_connection.screen();
+      if (dpi_x <= 0) {
+        dpi_x = screen->width_in_pixels * 25.4 / screen->width_in_millimeters;
+      }
+      if (dpi_y <= 0) {
+        dpi_y = screen->height_in_pixels * 25.4 / screen->height_in_millimeters;
+      }
+    }
+
+    m_log.info("Configured DPI = %gx%g", dpi_x, dpi_y);
+
+    auto fonts = m_conf.get_list<string>(m_conf.section(), "font", {});
+    if (fonts.empty()) {
+      m_log.warn("No fonts specified, using fallback font \"fixed\"");
+      fonts.emplace_back("fixed");
+    }
+
+    for (const auto& f : fonts) {
+      int offset{0};
+      string pattern{f};
+      size_t pos = pattern.rfind(';');
+      if (pos != string::npos) {
+        offset = std::strtol(pattern.substr(pos + 1).c_str(), nullptr, 10);
+        pattern.erase(pos);
+      }
+      auto font = cairo::make_font(*m_context, string{pattern}, offset, dpi_x, dpi_y);
+      m_log.notice("Loaded font \"%s\" (name=%s, offset=%i, file=%s)", pattern, font->name(), offset, font->file());
+      *m_context << move(font);
+    }
+  }
+
+  m_pseudo_transparency = m_conf.get<bool>("settings", "pseudo-transparency", m_pseudo_transparency);
+  if (m_pseudo_transparency) {
+    m_log.trace("Activate root background manager");
+    m_background = background.observe(m_bar.outer_area(false), m_window);
+  }
+
+  m_comp_bg = m_conf.get<cairo_operator_t>("settings", "compositing-background", m_comp_bg);
+  m_comp_fg = m_conf.get<cairo_operator_t>("settings", "compositing-foreground", m_comp_fg);
+  m_comp_ol = m_conf.get<cairo_operator_t>("settings", "compositing-overline", m_comp_ol);
+  m_comp_ul = m_conf.get<cairo_operator_t>("settings", "compositing-underline", m_comp_ul);
+  m_comp_border = m_conf.get<cairo_operator_t>("settings", "compositing-border", m_comp_border);
+
+  m_fixedcenter = m_conf.get(m_conf.section(), "fixed-center", true);
+}
+
+/**
+ * Deconstruct instance
+ */
+renderer::~renderer() {
+  m_sig.detach(this);
+}
+
+/**
+ * Get output window
+ */
+xcb_window_t renderer::window() const {
+  return m_window;
+}
+
+/**
+ * Get completed action blocks
+ */
+const vector<action_block> renderer::actions() const {
+  return m_actions;
+}
+
+/**
+ * Begin render routine
+ */
+void renderer::begin(xcb_rectangle_t rect) {
+  m_log.trace_x("renderer: begin (geom=%ix%i+%i+%i)", rect.width, rect.height, rect.x, rect.y);
+
+  // Reset state
+  m_rect = rect;
+  m_actions.clear();
+  m_attr.reset();
+  m_align = alignment::NONE;
+
+  // Reset colors
+  m_bg = m_bar.background;
+  m_fg = m_bar.foreground;
+  m_ul = m_bar.underline.color;
+  m_ol = m_bar.overline.color;
+
+  // Clear canvas
+  m_context->save();
+  m_context->clear();
+
+  // when pseudo-transparency is requested, render the bar into a new layer
+  // that will later be composited against the desktop background
+  if (m_pseudo_transparency) {
+    m_context->push();
+  }
+
+  // Create corner mask
+  if (m_bar.radius && m_cornermask == nullptr) {
+    m_context->save();
+    m_context->push();
+    // clang-format off
+    *m_context << cairo::rounded_corners{
+        static_cast<double>(m_rect.x),
+        static_cast<double>(m_rect.y),
+        static_cast<double>(m_rect.width),
+        static_cast<double>(m_rect.height), m_bar.radius};
+    // clang-format on
+    *m_context << rgba{0xffffffff};
+    m_context->fill();
+    m_context->pop(&m_cornermask);
+    m_context->restore();
+  }
+
+  fill_borders();
+
+  // clang-format off
+  m_context->clip(cairo::rect{
+      static_cast<double>(m_rect.x),
+      static_cast<double>(m_rect.y),
+      static_cast<double>(m_rect.width),
+      static_cast<double>(m_rect.height)});
+  // clang-format on
+}
+
+/**
+ * End render routine
+ */
+void renderer::end() {
+  m_log.trace_x("renderer: end");
+
+  for (auto&& a : m_actions) {
+    a.start_x += block_x(a.align) + m_rect.x;
+    a.end_x += block_x(a.align) + m_rect.x;
+  }
+
+  if (m_align != alignment::NONE) {
+    m_log.trace_x("renderer: pop(%i)", static_cast<int>(m_align));
+    m_context->pop(&m_blocks[m_align].pattern);
+
+    // Capture the concatenated block contents
+    // so that it can be masked with the corner pattern
+    m_context->push();
+
+    // Draw the background on the new layer to make up for
+    // the areas not covered by the alignment blocks
+    fill_background();
+
+    for (auto&& b : m_blocks) {
+      flush(b.first);
+    }
+
+    cairo_pattern_t* blockcontents{};
+    m_context->pop(&blockcontents);
+
+    if (m_cornermask != nullptr) {
+      *m_context << blockcontents;
+      m_context->mask(m_cornermask);
+    } else {
+      *m_context << blockcontents;
+      m_context->paint();
+    }
+
+    m_context->destroy(&blockcontents);
+  } else {
+    fill_background();
+  }
+
+  // For pseudo-transparency, capture the contents of the rendered bar and
+  // composite it against the desktop wallpaper. This way transparent parts of
+  // the bar will be filled by the wallpaper creating illusion of transparency.
+  if (m_pseudo_transparency) {
+    cairo_pattern_t* barcontents{};
+    m_context->pop(&barcontents);  // corresponding push is in renderer::begin
+
+    auto root_bg = m_background->get_surface();
+    if (root_bg != nullptr) {
+      m_log.trace_x("renderer: root background");
+      *m_context << *root_bg;
+      m_context->paint();
+      *m_context << CAIRO_OPERATOR_OVER;
+    }
+    *m_context << barcontents;
+    m_context->paint();
+    m_context->destroy(&barcontents);
+  }
+
+  m_context->restore();
+  m_surface->flush();
+
+  flush();
+
+  m_sig.emit(signals::ui::changed{});
+}
+
+/**
+ * Flush contents of given alignment block
+ */
+void renderer::flush(alignment a) {
+  if (m_blocks[a].pattern == nullptr) {
+    return;
+  }
+
+  m_context->save();
+
+  double x = static_cast<int>(block_x(a) + 0.5);
+  double y = static_cast<int>(block_y(a) + 0.5);
+  double w = static_cast<int>(block_w(a) + 0.5);
+  double h = static_cast<int>(block_h(a) + 0.5);
+  double xw = x + w;
+  bool fits{xw <= m_rect.width};
+
+  m_log.trace("renderer: flush(%i geom=%gx%g+%g+%g, falloff=%i)", static_cast<int>(a), w, h, x, y, !fits);
+
+  // Set block shape
+  *m_context << cairo::abspos{0.0, 0.0};
+  *m_context << cairo::rect{m_rect.x + x, m_rect.y + y, w, h};
+
+  // Restrict drawing to the block rectangle
+  m_context->clip(true);
+
+  // Clear the area covered by the block
+  m_context->clear();
+
+  *m_context << cairo::translate{x, 0.0};
+  *m_context << m_blocks[a].pattern;
+  m_context->paint();
+
+  *m_context << cairo::abspos{0.0, 0.0};
+  m_context->destroy(&m_blocks[a].pattern);
+  m_context->restore();
+
+  if (!fits) {
+    // Paint falloff gradient at the end of the visible block
+    // to indicate that the content expands past the canvas
+
+    /*
+     * How many pixels are hidden
+     */
+    double overflow = xw - m_rect.width;
+    double visible_width = w - overflow;
+
+    /*
+     * Width of the falloff gradient. Depends on how much of the block is hidden
+     */
+    double fsize = std::max(5.0, std::min(std::abs(overflow), 30.0));
+    m_log.trace("renderer: Drawing falloff (pos=%g, size=%g, overflow=%g)", visible_width - fsize, fsize, overflow);
+    m_context->save();
+    *m_context << cairo::translate{(double) m_rect.x, (double) m_rect.y};
+    *m_context << cairo::abspos{0.0, 0.0};
+    *m_context << cairo::rect{x + visible_width - fsize, y, fsize, h};
+    m_context->clip(true);
+    *m_context << cairo::linear_gradient{x + visible_width - fsize, y, x + visible_width, y, {rgba{0x00000000}, rgba{0xFF000000}}};
+    m_context->paint(0.25);
+    m_context->restore();
+  }
+}
+
+/**
+ * Flush pixmap contents onto the target window
+ */
+void renderer::flush() {
+  m_log.trace_x("renderer: flush");
+
+  highlight_clickable_areas();
+
+#if 0
+#ifdef DEBUG_SHADED
+  if (m_bar.shaded && m_bar.origin == edge::TOP) {
+    m_log.trace_x(
+        "renderer: copy pixmap (shaded=1, geom=%dx%d+%d+%d)", m_rect.width, m_rect.height, m_rect.x, m_rect.y);
+    auto geom = m_connection.get_geometry(m_window);
+    auto x1 = 0;
+    auto y1 = m_rect.height - m_bar.shade_size.h - m_rect.y - geom->height;
+    auto x2 = m_rect.x;
+    auto y2 = m_rect.y;
+    auto w = m_rect.width;
+    auto h = m_rect.height - m_bar.shade_size.h + geom->height;
+    m_connection.copy_area(m_pixmap, m_window, m_gcontext, x1, y1, x2, y2, w, h);
+    m_connection.flush();
+    return;
+  }
+#endif
+#endif
+
+  m_surface->flush();
+  m_connection.copy_area(m_pixmap, m_window, m_gcontext, 0, 0, 0, 0, m_bar.size.w, m_bar.size.h);
+  m_connection.flush();
+
+  if (!m_snapshot_dst.empty()) {
+    try {
+      m_surface->write_png(m_snapshot_dst);
+      m_log.info("Successfully wrote %s", m_snapshot_dst);
+    } catch (const exception& err) {
+      m_log.err("Failed to write snapshot (err: %s)", err.what());
+    }
+    m_snapshot_dst.clear();
+  }
+}
+
+/**
+ * Get x position of block for given alignment
+ *
+ * The position is relative to m_rect.x (the left side of the bar w/o borders and tray)
+ */
+double renderer::block_x(alignment a) const {
+  switch (a) {
+    case alignment::CENTER: {
+      // The leftmost x position this block can start at
+      double min_pos = block_w(alignment::LEFT);
+
+      if (min_pos != 0) {
+        min_pos += BLOCK_GAP;
+      }
+
+      double right_width = block_w(alignment::RIGHT);
+      /*
+       * The rightmost x position this block can end at
+       *
+       * We can't use block_x(alignment::RIGHT) because that would lead to infinite recursion
+       */
+      double max_pos = m_rect.width - right_width;
+
+      if (right_width != 0) {
+        max_pos -= BLOCK_GAP;
+      }
+
+      /*
+       * x position of the center of this block
+       *
+       * With fixed-center this will be the center of the bar unless it is pushed to the left by a large right block
+       * Without fixed-center this will be the middle between the end of the left and the start of the right block.
+       */
+      double base_pos{0.0};
+
+      if (m_fixedcenter) {
+        /*
+         * This is in the middle of the *bar*. Not just the middle of m_rect because this way we need to account for the
+         * tray.
+         *
+         * The resulting position is relative to the very left of the bar (including border and tray), so we need to
+         * compensate for that by subtracting m_rect.x
+         */
+        base_pos = m_bar.size.w / 2.0 - m_rect.x;
+
+        /*
+         * The center block can be moved to the left if the right block is too large
+         */
+        base_pos = std::min(base_pos, max_pos - block_w(a) / 2.0);
+      }
+      else {
+        base_pos = (min_pos + max_pos) / 2.0;
+      }
+
+      /*
+       * The left block always has priority (even with fixed-center = true)
+       */
+      return std::max(base_pos - block_w(a) / 2.0, min_pos);
+    }
+    case alignment::RIGHT: {
+      /*
+       * The block immediately to the left of this block
+       *
+       * Generally the center block unless it is empty.
+       */
+      alignment left_barrier = alignment::CENTER;
+
+      if (block_w(alignment::CENTER) == 0) {
+        left_barrier = alignment::LEFT;
+      }
+
+      // The minimum x position this block can start at
+      double min_pos = block_x(left_barrier) + block_w(left_barrier);
+
+      if (block_w(left_barrier) != 0) {
+        min_pos += BLOCK_GAP;
+      }
+
+      return std::max(m_rect.width - block_w(a), min_pos);
+    }
+    default:
+      return 0.0;
+  }
+}
+
+/**
+ * Get y position of block for given alignment
+ */
+double renderer::block_y(alignment) const {
+  return 0.0;
+}
+
+/**
+ * Get block width for given alignment
+ */
+double renderer::block_w(alignment a) const {
+  return m_blocks.at(a).x;
+}
+
+/**
+ * Get block height for given alignment
+ */
+double renderer::block_h(alignment) const {
+  return m_rect.height;
+}
+
+#if 0
+void renderer::reserve_space(edge side, unsigned int w) {
+  m_log.trace_x("renderer: reserve_space(%i, %i)", static_cast<int>(side), w);
+
+  m_cleararea.side = side;
+  m_cleararea.size = w;
+
+  switch (side) {
+    case edge::NONE:
+      break;
+    case edge::TOP:
+      m_rect.y += w;
+      m_rect.height -= w;
+      break;
+    case edge::BOTTOM:
+      m_rect.height -= w;
+      break;
+    case edge::LEFT:
+      m_rect.x += w;
+      m_rect.width -= w;
+      break;
+    case edge::RIGHT:
+      m_rect.width -= w;
+      break;
+    case edge::ALL:
+      m_rect.x += w;
+      m_rect.y += w;
+      m_rect.width -= w * 2;
+      m_rect.height -= w * 2;
+      break;
+  }
+}
+#endif
+
+/**
+ * Fill background color
+ */
+void renderer::fill_background() {
+  m_context->save();
+  *m_context << m_comp_bg;
+
+  if (!m_bar.background_steps.empty()) {
+    m_log.trace_x("renderer: gradient background (steps=%lu)", m_bar.background_steps.size());
+    *m_context << cairo::linear_gradient{0.0, 0.0 + m_rect.y, 0.0, 0.0 + m_rect.height, m_bar.background_steps};
+  } else {
+    m_log.trace_x("renderer: solid background #%08x", m_bar.background);
+    *m_context << m_bar.background;
+  }
+
+  m_context->paint();
+  m_context->restore();
+}
+
+/**
+ * Fill overline color
+ */
+void renderer::fill_overline(double x, double w) {
+  if (m_bar.overline.size && m_attr.test(static_cast<int>(attribute::OVERLINE))) {
+    m_log.trace_x("renderer: overline(x=%f, w=%f)", x, w);
+    m_context->save();
+    *m_context << m_comp_ol;
+    *m_context << m_ol;
+    *m_context << cairo::rect{x, static_cast<double>(m_rect.y), w, static_cast<double>(m_bar.overline.size)};
+    m_context->fill();
+    m_context->restore();
+  }
+}
+
+/**
+ * Fill underline color
+ */
+void renderer::fill_underline(double x, double w) {
+  if (m_bar.underline.size && m_attr.test(static_cast<int>(attribute::UNDERLINE))) {
+    m_log.trace_x("renderer: underline(x=%f, w=%f)", x, w);
+    m_context->save();
+    *m_context << m_comp_ul;
+    *m_context << m_ul;
+    *m_context << cairo::rect{x, static_cast<double>(m_rect.y + m_rect.height - m_bar.underline.size), w,
+        static_cast<double>(m_bar.underline.size)};
+    m_context->fill();
+    m_context->restore();
+  }
+}
+
+/**
+ * Fill border colors
+ */
+void renderer::fill_borders() {
+  m_context->save();
+  *m_context << m_comp_border;
+
+  if (m_bar.borders.at(edge::TOP).size) {
+    cairo::rect top{0.0, 0.0, 0.0, 0.0};
+    top.x += m_bar.borders.at(edge::LEFT).size;
+    top.w += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size;
+    top.h += m_bar.borders.at(edge::TOP).size;
+    m_log.trace_x("renderer: border T(%.0f, #%08x)", top.h, m_bar.borders.at(edge::TOP).color);
+    (*m_context << top << m_bar.borders.at(edge::TOP).color).fill();
+  }
+
+  if (m_bar.borders.at(edge::BOTTOM).size) {
+    cairo::rect bottom{0.0, 0.0, 0.0, 0.0};
+    bottom.x += m_bar.borders.at(edge::LEFT).size;
+    bottom.y += m_bar.size.h - m_bar.borders.at(edge::BOTTOM).size;
+    bottom.w += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size;
+    bottom.h += m_bar.borders.at(edge::BOTTOM).size;
+    m_log.trace_x("renderer: border B(%.0f, #%08x)", bottom.h, m_bar.borders.at(edge::BOTTOM).color);
+    (*m_context << bottom << m_bar.borders.at(edge::BOTTOM).color).fill();
+  }
+
+  if (m_bar.borders.at(edge::LEFT).size) {
+    cairo::rect left{0.0, 0.0, 0.0, 0.0};
+    left.w += m_bar.borders.at(edge::LEFT).size;
+    left.h += m_bar.size.h;
+    m_log.trace_x("renderer: border L(%.0f, #%08x)", left.w, m_bar.borders.at(edge::LEFT).color);
+    (*m_context << left << m_bar.borders.at(edge::LEFT).color).fill();
+  }
+
+  if (m_bar.borders.at(edge::RIGHT).size) {
+    cairo::rect right{0.0, 0.0, 0.0, 0.0};
+    right.x += m_bar.size.w - m_bar.borders.at(edge::RIGHT).size;
+    right.w += m_bar.borders.at(edge::RIGHT).size;
+    right.h += m_bar.size.h;
+    m_log.trace_x("renderer: border R(%.0f, #%08x)", right.w, m_bar.borders.at(edge::RIGHT).color);
+    (*m_context << right << m_bar.borders.at(edge::RIGHT).color).fill();
+  }
+
+  m_context->restore();
+}
+
+/**
+ * Draw text contents
+ */
+void renderer::draw_text(const string& contents) {
+  m_log.trace_x("renderer: text(%s)", contents.c_str());
+
+  cairo::abspos origin{};
+  origin.x = m_rect.x + m_blocks[m_align].x;
+  origin.y = m_rect.y + m_rect.height / 2.0;
+
+  cairo::textblock block{};
+  block.align = m_align;
+  block.contents = contents;
+  block.font = m_font;
+  block.x_advance = &m_blocks[m_align].x;
+  block.y_advance = &m_blocks[m_align].y;
+  block.bg_rect = cairo::rect{0.0, 0.0, 0.0, 0.0};
+
+  // Only draw text background if the color differs from
+  // the background color of the bar itself
+  // Note: this means that if the user explicitly set text
+  // background color equal to background-0 it will be ignored
+  if (m_bg != m_bar.background) {
+    block.bg = m_bg;
+    block.bg_operator = m_comp_bg;
+    block.bg_rect.x = m_rect.x;
+    block.bg_rect.y = m_rect.y;
+    block.bg_rect.h = m_rect.height;
+  }
+
+  m_context->save();
+  *m_context << origin;
+  *m_context << m_comp_fg;
+  *m_context << m_fg;
+  *m_context << block;
+  m_context->restore();
+
+  double dx = m_rect.x + m_blocks[m_align].x - origin.x;
+  if (dx > 0.0) {
+    fill_underline(origin.x, dx);
+    fill_overline(origin.x, dx);
+  }
+}
+
+/**
+ * Colorize the bounding box of created action blocks
+ */
+void renderer::highlight_clickable_areas() {
+#ifdef DEBUG_HINTS
+  map<alignment, int> hint_num{};
+  for (auto&& action : m_actions) {
+    if (!action.active) {
+      int n = hint_num.find(action.align)->second++;
+      double x = action.start_x;
+      double y = m_rect.y;
+      double w = action.width();
+      double h = m_rect.height;
+
+      m_context->save();
+      *m_context << CAIRO_OPERATOR_DIFFERENCE << (n % 2 ? 0xFF00FF00 : 0xFFFF0000);
+      *m_context << cairo::rect{x, y, w, h};
+      m_context->fill();
+      m_context->restore();
+    }
+  }
+  m_surface->flush();
+#endif
+}
+
+bool renderer::on(const signals::ui::request_snapshot& evt) {
+  m_snapshot_dst = evt.cast();
+  return true;
+}
+
+bool renderer::on(const signals::parser::change_background& evt) {
+  const rgba color{evt.cast()};
+  if (color != m_bg) {
+    m_log.trace_x("renderer: change_background(#%08x)", color);
+    m_bg = color;
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::change_foreground& evt) {
+  const rgba color{evt.cast()};
+  if (color != m_fg) {
+    m_log.trace_x("renderer: change_foreground(#%08x)", color);
+    m_fg = color;
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::change_underline& evt) {
+  const rgba color{evt.cast()};
+  if (color != m_ul) {
+    m_log.trace_x("renderer: change_underline(#%08x)", color);
+    m_ul = color;
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::change_overline& evt) {
+  const rgba color{evt.cast()};
+  if (color != m_ol) {
+    m_log.trace_x("renderer: change_overline(#%08x)", color);
+    m_ol = color;
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::change_font& evt) {
+  const int font{evt.cast()};
+  if (font != m_font) {
+    m_log.trace_x("renderer: change_font(%i)", font);
+    m_font = font;
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::change_alignment& evt) {
+  auto align = static_cast<const alignment&>(evt.cast());
+  if (align != m_align) {
+    m_log.trace_x("renderer: change_alignment(%i)", static_cast<int>(align));
+
+    if (m_align != alignment::NONE) {
+      m_log.trace_x("renderer: pop(%i)", static_cast<int>(m_align));
+      m_context->pop(&m_blocks[m_align].pattern);
+    }
+
+    m_align = align;
+    m_blocks[m_align].x = 0.0;
+    m_blocks[m_align].y = 0.0;
+    m_context->push();
+    m_log.trace_x("renderer: push(%i)", static_cast<int>(m_align));
+
+    fill_background();
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::reverse_colors&) {
+  m_log.trace_x("renderer: reverse_colors");
+  std::swap(m_fg, m_bg);
+  return true;
+}
+
+bool renderer::on(const signals::parser::offset_pixel& evt) {
+  m_log.trace_x("renderer: offset_pixel(%f)", evt.cast());
+  m_blocks[m_align].x += evt.cast();
+  return true;
+}
+
+bool renderer::on(const signals::parser::attribute_set& evt) {
+  m_log.trace_x("renderer: attribute_set(%i)", static_cast<int>(evt.cast()));
+  m_attr.set(static_cast<int>(evt.cast()), true);
+  return true;
+}
+
+bool renderer::on(const signals::parser::attribute_unset& evt) {
+  m_log.trace_x("renderer: attribute_unset(%i)", static_cast<int>(evt.cast()));
+  m_attr.set(static_cast<int>(evt.cast()), false);
+  return true;
+}
+
+bool renderer::on(const signals::parser::attribute_toggle& evt) {
+  m_log.trace_x("renderer: attribute_toggle(%i)", static_cast<int>(evt.cast()));
+  m_attr.flip(static_cast<int>(evt.cast()));
+  return true;
+}
+
+bool renderer::on(const signals::parser::action_begin& evt) {
+  auto a = evt.cast();
+  m_log.trace_x("renderer: action_begin(btn=%i, command=%s)", static_cast<int>(a.button), a.command);
+  action_block action{};
+  action.button = a.button == mousebtn::NONE ? mousebtn::LEFT : a.button;
+  action.align = m_align;
+  action.start_x = m_blocks.at(m_align).x;
+  action.command = a.command;
+  action.active = true;
+  m_actions.emplace_back(action);
+  return true;
+}
+
+bool renderer::on(const signals::parser::action_end& evt) {
+  auto btn = evt.cast();
+
+  /*
+   * Iterate actions in reverse and find the FIRST active action that matches
+   */
+  m_log.trace_x("renderer: action_end(btn=%i)", static_cast<int>(btn));
+  for (auto action = m_actions.rbegin(); action != m_actions.rend(); action++) {
+    if (action->active && action->align == m_align && action->button == btn) {
+      action->end_x = m_blocks.at(action->align).x;
+      action->active = false;
+      break;
+    }
+  }
+  return true;
+}
+
+bool renderer::on(const signals::parser::text& evt) {
+  auto text = evt.cast();
+  draw_text(text);
+  return true;
+}
+
+bool renderer::on(const signals::parser::control& evt) {
+  auto ctrl = evt.cast();
+
+  switch (ctrl) {
+    case controltag::R:
+      m_bg = m_bar.background;
+      m_fg = m_bar.foreground;
+      m_ul = m_bar.underline.color;
+      m_ol = m_bar.overline.color;
+      m_font = 0;
+      m_attr.reset();
+      break;
+
+    case controltag::NONE:
+      break;
+  }
+
+  return true;
+}
+
+POLYBAR_NS_END
diff --git a/src/components/screen.cpp b/src/components/screen.cpp
new file mode 100644 (file)
index 0000000..03ce8a2
--- /dev/null
@@ -0,0 +1,147 @@
+#include <csignal>
+#include <algorithm>
+#include <thread>
+
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "components/screen.hpp"
+#include "components/types.hpp"
+#include "events/signal.hpp"
+#include "events/signal_emitter.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/registry.hpp"
+#include "x11/types.hpp"
+#include "x11/winspec.hpp"
+
+POLYBAR_NS
+
+using namespace signals::eventqueue;
+
+/**
+ * Create instance
+ */
+screen::make_type screen::make() {
+  return factory_util::unique<screen>(connection::make(), signal_emitter::make(), logger::make(), config::make());
+}
+
+/**
+ * Construct screen instance
+ */
+screen::screen(connection& conn, signal_emitter& emitter, const logger& logger, const config& conf)
+    : m_connection(conn)
+    , m_sig(emitter)
+    , m_log(logger)
+    , m_conf(conf)
+    , m_root(conn.root())
+    , m_monitors(randr_util::get_monitors(m_connection, m_root, true, false))
+    , m_size({conn.screen()->width_in_pixels, conn.screen()->height_in_pixels}) {
+  // Check if the reloading has been disabled by the user
+  if (!m_conf.get("settings", "screenchange-reload", false)) {
+    return;
+  }
+
+  // clang-format off
+  m_proxy = winspec(m_connection)
+    << cw_size(1U, 1U)
+    << cw_pos(-1, -1)
+    << cw_parent(m_root)
+    << cw_params_override_redirect(true)
+    << cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE)
+    << cw_flush(true);
+  // clang-format on
+
+  // Update the root windows event mask
+  auto attributes = m_connection.get_window_attributes(m_root);
+  auto root_mask = attributes->your_event_mask;
+  attributes->your_event_mask = attributes->your_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
+  m_connection.change_window_attributes(m_root, XCB_CW_EVENT_MASK, &attributes->your_event_mask);
+
+  // Receive randr events
+  m_connection.randr().select_input(m_proxy, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
+
+  // Create window used as event proxy
+  m_connection.map_window(m_proxy);
+  m_connection.flush();
+
+  // Wait until the proxy window has been mapped
+  using evt = xcb_map_notify_event_t;
+  m_connection.wait_for_response<evt, XCB_MAP_NOTIFY>([&](const evt* evt) -> bool { return evt->window == m_proxy; });
+
+  // Restore the root windows event mask
+  m_connection.change_window_attributes(m_root, XCB_CW_EVENT_MASK, &root_mask);
+
+  // Finally attach the sink the process randr events
+  m_connection.attach_sink(this, SINK_PRIORITY_SCREEN);
+}
+
+/**
+ * Deconstruct screen instance
+ */
+screen::~screen() {
+  m_connection.detach_sink(this, SINK_PRIORITY_SCREEN);
+
+  if (m_proxy != XCB_NONE) {
+    m_connection.destroy_window(m_proxy);
+  }
+}
+
+/**
+ * Handle XCB_RANDR_SCREEN_CHANGE_NOTIFY events
+ *
+ * If any of the monitors have changed we raise USR1 to trigger a reload
+ */
+void screen::handle(const evt::randr_screen_change_notify& evt) {
+  if (m_sigraised || evt->request_window != m_proxy) {
+    return;
+  }
+
+  auto screen = m_connection.screen(true);
+  auto changed = false;
+
+  // We need to reload if the screen size changed as well
+  if (screen->width_in_pixels != m_size.w || screen->height_in_pixels != m_size.h) {
+    changed = true;
+  } else {
+    changed = have_monitors_changed();
+  }
+
+  if (changed) {
+    m_log.notice("randr_screen_change_notify (%ux%u)... reloading", evt->width, evt->height);
+    m_sig.emit(exit_reload{});
+    m_sigraised = true;
+  }
+}
+
+/**
+ * Checks if the stored monitor list is different from a newly fetched one
+ *
+ * Fetches the monitor list and compares it with the one stored
+ */
+bool screen::have_monitors_changed() const {
+  auto monitors = randr_util::get_monitors(m_connection, m_root, true, false);
+
+  if(monitors.size() != m_monitors.size()) {
+    return true;
+  }
+
+  for (auto m : m_monitors) {
+    auto it = std::find_if(monitors.begin(), monitors.end(),
+        [m] (auto& monitor) -> bool {
+        return m->equals(*monitor);
+        });
+
+    /*
+     * Every monitor in the stored list should also exist in the newly fetched
+     * list. If this holds then the two lists are equivalent since they have
+     * the same size
+     */
+    if(it == monitors.end()) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+POLYBAR_NS_END
diff --git a/src/components/taskqueue.cpp b/src/components/taskqueue.cpp
new file mode 100644 (file)
index 0000000..b841879
--- /dev/null
@@ -0,0 +1,110 @@
+#include <algorithm>
+
+#include "components/taskqueue.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+taskqueue::make_type taskqueue::make() {
+  return factory_util::unique<taskqueue>();
+}
+
+taskqueue::taskqueue() {
+  m_thread = std::thread([&] {
+    while (m_active) {
+      std::unique_lock<std::mutex> guard(m_lock);
+
+      if (m_deferred.empty()) {
+        m_hold.wait(guard);
+      } else {
+        auto now = deferred::clock::now();
+        auto wait = m_deferred.front()->now + m_deferred.front()->wait;
+        for (auto&& task : m_deferred) {
+          auto when = task->now + task->wait;
+          if (when < wait) {
+            wait = move(when);
+          }
+        }
+        if (wait > now) {
+          m_hold.wait_for(guard, wait - now);
+        }
+      }
+      if (!m_deferred.empty()) {
+        guard.unlock();
+        tick();
+      }
+    }
+  });
+}
+
+taskqueue::~taskqueue() {
+  if (m_active && m_thread.joinable()) {
+    m_active = false;
+    m_hold.notify_all();
+    m_thread.join();
+  }
+}
+
+void taskqueue::defer(
+    string id, deferred::duration ms, deferred::callback fn, deferred::duration offset, size_t count) {
+  std::unique_lock<std::mutex> guard(m_lock);
+  deferred::timepoint now{chrono::time_point_cast<deferred::duration>(deferred::clock::now() + move(offset))};
+  m_deferred.emplace_back(make_unique<deferred>(move(id), move(now), move(ms), move(fn), move(count)));
+  guard.unlock();
+  m_hold.notify_one();
+}
+
+void taskqueue::defer_unique(
+    string id, deferred::duration ms, deferred::callback fn, deferred::duration offset, size_t count) {
+  purge(id);
+  std::unique_lock<std::mutex> guard(m_lock);
+  deferred::timepoint now{chrono::time_point_cast<deferred::duration>(deferred::clock::now() + move(offset))};
+  m_deferred.emplace_back(make_unique<deferred>(move(id), move(now), move(ms), move(fn), move(count)));
+  guard.unlock();
+  m_hold.notify_one();
+}
+
+void taskqueue::tick() {
+  if (!m_lock.try_lock()) {
+    return;
+  }
+  std::unique_lock<std::mutex> guard(m_lock, std::adopt_lock);
+  auto now = chrono::time_point_cast<deferred::duration>(deferred::clock::now());
+  vector<pair<deferred::callback, size_t>> cbs;
+  for (auto it = m_deferred.rbegin(); it != m_deferred.rend(); ++it) {
+    auto& task = *it;
+    if (task->now + task->wait > now) {
+      continue;
+    } else if (task->count--) {
+      cbs.emplace_back(make_pair(task->func, task->count));
+      task->now = now;
+    } else {
+      m_deferred.erase(std::remove_if(m_deferred.begin(), m_deferred.end(),
+                           [&](const unique_ptr<deferred>& d) { return d == task; }),
+          m_deferred.end());
+    }
+  }
+  guard.unlock();
+  for (auto&& p : cbs) {
+    p.first(p.second);
+  }
+}
+
+bool taskqueue::purge(const string& id) {
+  std::lock_guard<std::mutex> guard(m_lock);
+  return m_deferred.erase(std::remove_if(m_deferred.begin(), m_deferred.end(),
+                              [id](const unique_ptr<deferred>& d) { return d->id == id; }),
+             m_deferred.end()) == m_deferred.end();
+}
+
+bool taskqueue::exist(const string& id) {
+  std::lock_guard<std::mutex> guard(m_lock);
+  for (const auto& task : m_deferred) {
+    if (task->id == id) {
+      return true;
+    }
+  }
+  return false;
+}
+
+POLYBAR_NS_END
diff --git a/src/drawtypes/animation.cpp b/src/drawtypes/animation.cpp
new file mode 100644 (file)
index 0000000..a65fafb
--- /dev/null
@@ -0,0 +1,63 @@
+#include "drawtypes/animation.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  void animation::add(label_t&& frame) {
+    m_frames.emplace_back(forward<decltype(frame)>(frame));
+    m_framecount = m_frames.size();
+    m_frame = m_framecount - 1;
+  }
+
+  label_t animation::get() const {
+    return m_frames[m_frame];
+  }
+
+  unsigned int animation::framerate() const {
+    return m_framerate_ms;
+  }
+
+  animation::operator bool() const {
+    return !m_frames.empty();
+  }
+
+  void animation::increment() {
+    auto tmp = m_frame.load();
+    ++tmp;
+    tmp %= m_framecount;
+
+    m_frame = tmp;
+  }
+
+  /**
+   * Create an animation by loading values
+   * from the configuration
+   */
+  animation_t load_animation(const config& conf, const string& section, string name, bool required) {
+    vector<label_t> vec;
+    vector<string> frames;
+
+    name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+    auto anim_defaults = load_optional_label(conf, section, name);
+
+    if (required) {
+      frames = conf.get_list(section, name);
+    } else {
+      frames = conf.get_list(section, name, {});
+    }
+
+    for (size_t i = 0; i < frames.size(); i++) {
+      vec.emplace_back(forward<label_t>(load_optional_label(conf, section, name + "-" + to_string(i), frames[i])));
+      vec.back()->copy_undefined(anim_defaults);
+    }
+
+    auto framerate = conf.get(section, name + "-framerate", 1000);
+
+    return factory_util::shared<animation>(move(vec), framerate);
+  }
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/src/drawtypes/iconset.cpp b/src/drawtypes/iconset.cpp
new file mode 100644 (file)
index 0000000..55426ee
--- /dev/null
@@ -0,0 +1,38 @@
+#include "drawtypes/iconset.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  void iconset::add(string id, label_t&& icon) {
+    m_icons.emplace(id, forward<decltype(icon)>(icon));
+  }
+
+  bool iconset::has(const string& id) {
+    return m_icons.find(id) != m_icons.end();
+  }
+
+  label_t iconset::get(const string& id, const string& fallback_id, bool fuzzy_match) {
+    // Try to match exactly first
+    auto icon = m_icons.find(id);
+    if (icon != m_icons.end()) {
+      return icon->second;
+    }
+
+    // If fuzzy matching is turned on, try that first before returning the fallback.
+    if (fuzzy_match) {
+      for (auto const& icon : m_icons) {
+        if (id.find(icon.first) != std::string::npos) {
+          return icon.second;
+        }
+      }
+    }
+
+    return m_icons.find(fallback_id)->second;
+  }
+
+  iconset::operator bool() {
+    return !m_icons.empty();
+  }
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/src/drawtypes/label.cpp b/src/drawtypes/label.cpp
new file mode 100644 (file)
index 0000000..6a99e20
--- /dev/null
@@ -0,0 +1,306 @@
+#include "drawtypes/label.hpp"
+
+#include <cmath>
+#include <utility>
+
+#include "utils/factory.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  /**
+   * Gets the text from the label as it should be rendered
+   *
+   * Here tokens are replaced with values and minlen and maxlen properties are applied
+   */
+  string label::get() const {
+    const size_t len = string_util::char_len(m_tokenized);
+    if (len >= m_minlen) {
+      string text = m_tokenized;
+      if (m_maxlen > 0 && len > m_maxlen) {
+        if (m_ellipsis) {
+          text = string_util::utf8_truncate(std::move(text), m_maxlen - 3) + "...";
+        } else {
+          text = string_util::utf8_truncate(std::move(text), m_maxlen);
+        }
+      }
+      return text;
+    }
+
+    const size_t num_fill_chars = m_minlen - len;
+    size_t right_fill_len = 0;
+    size_t left_fill_len = 0;
+    if (m_alignment == alignment::RIGHT) {
+      left_fill_len = num_fill_chars;
+    } else if (m_alignment == alignment::LEFT) {
+      right_fill_len = num_fill_chars;
+    } else {
+      right_fill_len = std::ceil(num_fill_chars / 2.0);
+      left_fill_len = right_fill_len;
+      // The text is positioned one character to the left if we can't perfectly center it
+      if (len + left_fill_len + right_fill_len > m_minlen) {
+        --left_fill_len;
+      }
+    }
+    return string(left_fill_len, ' ') + m_tokenized + string(right_fill_len, ' ');
+  }
+
+  label::operator bool() {
+    return !m_tokenized.empty();
+  }
+
+  label_t label::clone() {
+    vector<token> tokens;
+    if (!m_tokens.empty()) {
+      std::back_insert_iterator<decltype(tokens)> back_it(tokens);
+      std::copy(m_tokens.begin(), m_tokens.end(), back_it);
+    }
+    return factory_util::shared<label>(m_text, m_foreground, m_background, m_underline, m_overline, m_font, m_padding,
+        m_margin, m_minlen, m_maxlen, m_alignment, m_ellipsis, move(tokens));
+  }
+
+  void label::clear() {
+    m_tokenized.clear();
+  }
+
+  void label::reset_tokens() {
+    m_tokenized = m_text;
+  }
+
+  void label::reset_tokens(const string& tokenized) {
+    m_tokenized = tokenized;
+  }
+
+  bool label::has_token(const string& token) const {
+    return m_tokenized.find(token) != string::npos;
+  }
+
+  void label::replace_token(const string& token, string replacement) {
+    if (!has_token(token)) {
+      return;
+    }
+
+    for (auto&& tok : m_tokens) {
+      string repl{replacement};
+      if (token == tok.token) {
+        if (tok.max != 0_z && string_util::char_len(repl) > tok.max) {
+          repl = string_util::utf8_truncate(std::move(repl), tok.max) + tok.suffix;
+        } else if (tok.min != 0_z && repl.length() < tok.min) {
+          repl.insert(0_z, tok.min - repl.length(), tok.zpad ? '0' : ' ');
+        }
+
+        /*
+         * Only replace first occurence, so that the proper token objects can be used
+         */
+        m_tokenized = string_util::replace(m_tokenized, token, move(repl));
+      }
+    }
+  }
+
+  void label::replace_defined_values(const label_t& label) {
+    if (label->m_foreground.has_color()) {
+      m_foreground = label->m_foreground;
+    }
+    if (label->m_background.has_color()) {
+      m_background = label->m_background;
+    }
+    if (label->m_underline.has_color()) {
+      m_underline = label->m_underline;
+    }
+    if (label->m_overline.has_color()) {
+      m_overline = label->m_overline;
+    }
+    if (label->m_font != 0) {
+      m_font = label->m_font;
+    }
+    if (label->m_padding.left != 0U) {
+      m_padding.left = label->m_padding.left;
+    }
+    if (label->m_padding.right != 0U) {
+      m_padding.right = label->m_padding.right;
+    }
+    if (label->m_margin.left != 0U) {
+      m_margin.left = label->m_margin.left;
+    }
+    if (label->m_margin.right != 0U) {
+      m_margin.right = label->m_margin.right;
+    }
+    if (label->m_maxlen != 0_z) {
+      m_maxlen = label->m_maxlen;
+      m_ellipsis = label->m_ellipsis;
+    }
+  }
+
+  void label::copy_undefined(const label_t& label) {
+    if (!m_foreground.has_color() && label->m_foreground.has_color()) {
+      m_foreground = label->m_foreground;
+    }
+    if (!m_background.has_color() && label->m_background.has_color()) {
+      m_background = label->m_background;
+    }
+    if (!m_underline.has_color() && label->m_underline.has_color()) {
+      m_underline = label->m_underline;
+    }
+    if (!m_overline.has_color() && label->m_overline.has_color()) {
+      m_overline = label->m_overline;
+    }
+    if (m_font == 0 && label->m_font != 0) {
+      m_font = label->m_font;
+    }
+    if (m_padding.left == 0U && label->m_padding.left != 0U) {
+      m_padding.left = label->m_padding.left;
+    }
+    if (m_padding.right == 0U && label->m_padding.right != 0U) {
+      m_padding.right = label->m_padding.right;
+    }
+    if (m_margin.left == 0U && label->m_margin.left != 0U) {
+      m_margin.left = label->m_margin.left;
+    }
+    if (m_margin.right == 0U && label->m_margin.right != 0U) {
+      m_margin.right = label->m_margin.right;
+    }
+    if (m_maxlen == 0_z && label->m_maxlen != 0_z) {
+      m_maxlen = label->m_maxlen;
+      m_ellipsis = label->m_ellipsis;
+    }
+  }
+
+  /**
+   * Create a label by loading values from the configuration
+   */
+  label_t load_label(const config& conf, const string& section, string name, bool required, string def) {
+    vector<token> tokens;
+    size_t start, end, pos;
+
+    name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+    string text;
+
+    struct side_values padding {
+    }, margin{};
+
+    if (required) {
+      text = conf.get(section, name);
+    } else {
+      text = conf.get(section, name, move(def));
+    }
+
+    const auto get_left_right = [&](string key) {
+      auto value = conf.get(section, key, 0U);
+      auto left = conf.get(section, key + "-left", value);
+      auto right = conf.get(section, key + "-right", value);
+      return side_values{static_cast<unsigned short int>(left), static_cast<unsigned short int>(right)};
+    };
+
+    padding = get_left_right(name + "-padding");
+    margin = get_left_right(name + "-margin");
+
+    string line{text};
+
+    while ((start = line.find('%')) != string::npos && (end = line.find('%', start + 1)) != string::npos) {
+      auto token_str = line.substr(start, end - start + 1);
+
+      // ignore false positives
+      //   lemonbar tags %{...}
+      //   trailing percentage signs %token%%
+      if (token_str.find_first_of("abcdefghijklmnopqrstuvwxyz") != 1) {
+        line.erase(0, end);
+        continue;
+      }
+
+      line.erase(start, end - start + 1);
+      tokens.emplace_back(token{token_str, 0_z, 0_z});
+      auto& token = tokens.back();
+
+      // find min delimiter
+      if ((pos = token_str.find(':')) == string::npos) {
+        continue;
+      }
+
+      // strip min/max specifiers from the label string token
+      token.token = token_str.substr(0, pos) + '%';
+      text = string_util::replace(text, token_str, token.token);
+
+      try {
+        token.min = std::stoul(&token_str[pos + 1], nullptr, 10);
+        // When the number starts with 0 the string is 0-padded
+        token.zpad = token_str[pos + 1] == '0';
+      } catch (const std::invalid_argument& err) {
+        continue;
+      }
+
+      // find max delimiter
+      if ((pos = token_str.find(':', pos + 1)) == string::npos) {
+        continue;
+      }
+
+      try {
+        token.max = std::stoul(&token_str[pos + 1], nullptr, 10);
+      } catch (const std::invalid_argument& err) {
+        continue;
+      }
+
+      // ignore max lengths less than min
+      if (token.max < token.min) {
+        token.max = 0_z;
+      }
+
+      // find suffix delimiter
+      if ((pos = token_str.find(':', pos + 1)) != string::npos) {
+        token.suffix = token_str.substr(pos + 1, token_str.size() - pos - 2);
+      }
+    }
+    size_t minlen = conf.get(section, name + "-minlen", 0_z);
+    string alignment_conf_value = conf.get(section, name + "-alignment", "left"s);
+    alignment label_alignment;
+    if (alignment_conf_value == "right") {
+      label_alignment = alignment::RIGHT;
+    } else if (alignment_conf_value == "left") {
+      label_alignment = alignment::LEFT;
+    } else if (alignment_conf_value == "center") {
+      label_alignment = alignment::CENTER;
+    } else {
+      throw application_error(sstream() << "Label " << section << "." << name << " has invalid alignment "
+                                        << alignment_conf_value << ", expecting one of: right, left, center.");
+    }
+
+    size_t maxlen = conf.get(section, name + "-maxlen", 0_z);
+    if (maxlen > 0 && maxlen < minlen) {
+      throw application_error(sstream() << "Label " << section << "." << name << " has maxlen " << maxlen
+                                        << " which is smaller than minlen " << minlen);
+    }
+    bool ellipsis = conf.get(section, name + "-ellipsis", true);
+
+    if (ellipsis && maxlen > 0 && maxlen < 3) {
+      throw application_error(sstream() << "Label " << section << "." << name << " has maxlen " << maxlen
+                                        << ", which is smaller than length of ellipsis (3)");
+    }
+
+    // clang-format off
+    return factory_util::shared<label>(text,
+        conf.get(section, name + "-foreground", rgba{}),
+        conf.get(section, name + "-background", rgba{}),
+        conf.get(section, name + "-underline", rgba{}),
+        conf.get(section, name + "-overline", rgba{}),
+        conf.get(section, name + "-font", 0),
+        padding,
+        margin,
+        minlen,
+        maxlen,
+        label_alignment,
+        ellipsis,
+        move(tokens));
+    // clang-format on
+  }
+
+  /**
+   * Create a label by loading optional values from the configuration
+   */
+  label_t load_optional_label(const config& conf, string section, string name, string def) {
+    return load_label(conf, move(section), move(name), false, move(def));
+  }
+
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/src/drawtypes/progressbar.cpp b/src/drawtypes/progressbar.cpp
new file mode 100644 (file)
index 0000000..34dfe87
--- /dev/null
@@ -0,0 +1,138 @@
+#include "drawtypes/progressbar.hpp"
+
+#include <utility>
+
+#include "drawtypes/label.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  progressbar::progressbar(const bar_settings& bar, int width, string format)
+      : m_builder(factory_util::unique<builder>(bar)), m_format(move(format)), m_width(width) {}
+
+  void progressbar::set_fill(label_t&& fill) {
+    m_fill = forward<decltype(fill)>(fill);
+  }
+
+  void progressbar::set_empty(label_t&& empty) {
+    m_empty = forward<decltype(empty)>(empty);
+  }
+
+  void progressbar::set_indicator(label_t&& indicator) {
+    if (!m_indicator && indicator.get()) {
+      m_width--;
+    }
+    m_indicator = forward<decltype(indicator)>(indicator);
+  }
+
+  void progressbar::set_gradient(bool mode) {
+    m_gradient = mode;
+  }
+
+  void progressbar::set_colors(vector<rgba>&& colors) {
+    m_colors = forward<decltype(colors)>(colors);
+
+    m_colorstep = m_colors.empty() ? 1 : m_width / m_colors.size();
+  }
+
+  string progressbar::output(float percentage) {
+    string output{m_format};
+
+    // Get fill/empty widths based on percentage
+    unsigned int perc = math_util::cap(percentage, 0.0f, 100.0f);
+    unsigned int fill_width = math_util::percentage_to_value(perc, m_width);
+    unsigned int empty_width = m_width - fill_width;
+
+    // Output fill icons
+    fill(perc, fill_width);
+    output = string_util::replace_all(output, "%fill%", m_builder->flush());
+
+    // Output indicator icon
+    m_builder->node(m_indicator);
+    output = string_util::replace_all(output, "%indicator%", m_builder->flush());
+
+    // Output empty icons
+    m_builder->node_repeat(m_empty, empty_width);
+    output = string_util::replace_all(output, "%empty%", m_builder->flush());
+
+    return output;
+  }
+
+  void progressbar::fill(unsigned int perc, unsigned int fill_width) {
+    if (m_colors.empty()) {
+      m_builder->node_repeat(m_fill, fill_width);
+    } else if (m_gradient) {
+      size_t color = 0;
+      for (size_t i = 0; i < fill_width; i++) {
+        if (i % m_colorstep == 0 && color < m_colors.size()) {
+          m_fill->m_foreground = m_colors[color++];
+        }
+        m_builder->node(m_fill);
+      }
+    } else {
+      size_t color = math_util::percentage_to_value<size_t>(perc, m_colors.size() - 1);
+      m_fill->m_foreground = m_colors[color];
+      m_builder->node_repeat(m_fill, fill_width);
+    }
+  }
+
+  /**
+   * Create a progressbar by loading values
+   * from the configuration
+   */
+  progressbar_t load_progressbar(const bar_settings& bar, const config& conf, const string& section, string name) {
+    // Remove the start and end tag from the name in case a format tag is passed
+    name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+    string format = "%fill%%indicator%%empty%";
+    unsigned int width;
+
+    if ((format = conf.get(section, name + "-format", format)).empty()) {
+      throw application_error("Invalid format defined at [" + section + "." + name + "]");
+    }
+    if ((width = conf.get<decltype(width)>(section, name + "-width")) < 1) {
+      throw application_error("Invalid width defined at [" + section + "." + name + "]");
+    }
+
+    auto pbar = factory_util::shared<progressbar>(bar, width, format);
+    pbar->set_gradient(conf.get(section, name + "-gradient", true));
+    pbar->set_colors(conf.get_list(section, name + "-foreground", vector<rgba>{}));
+
+    label_t icon_empty;
+    label_t icon_fill;
+    label_t icon_indicator;
+
+    if (format.find("%empty%") != string::npos) {
+      icon_empty = load_label(conf, section, name + "-empty");
+    }
+    if (format.find("%fill%") != string::npos) {
+      icon_fill = load_label(conf, section, name + "-fill");
+    }
+    if (format.find("%indicator%") != string::npos) {
+      icon_indicator = load_label(conf, section, name + "-indicator");
+    }
+
+    // If a foreground/background color is defined for the indicator
+    // but not for the empty icon we use the bar's default colors to
+    // avoid color bleed
+    if (icon_empty && icon_indicator) {
+      if (icon_indicator->m_background.has_color() && !icon_empty->m_background.has_color()) {
+        icon_empty->m_background = bar.background;
+      }
+      if (icon_indicator->m_foreground.has_color() && !icon_empty->m_foreground.has_color()) {
+        icon_empty->m_foreground = bar.foreground;
+      }
+    }
+
+    pbar->set_empty(move(icon_empty));
+    pbar->set_fill(move(icon_fill));
+    pbar->set_indicator(move(icon_indicator));
+
+    return pbar;
+  }
+}  // namespace drawtypes
+
+POLYBAR_NS_END
diff --git a/src/drawtypes/ramp.cpp b/src/drawtypes/ramp.cpp
new file mode 100644 (file)
index 0000000..45a9cfb
--- /dev/null
@@ -0,0 +1,66 @@
+#include "drawtypes/ramp.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace drawtypes {
+  void ramp::add(label_t&& icon) {
+    m_icons.emplace_back(forward<decltype(icon)>(icon));
+  }
+
+  label_t ramp::get(size_t index) {
+    return m_icons[index];
+  }
+
+  label_t ramp::get_by_percentage(float percentage) {
+    size_t index = percentage * m_icons.size() / 100.0f;
+    return m_icons[math_util::cap<size_t>(index, 0, m_icons.size() - 1)];
+  }
+
+  label_t ramp::get_by_percentage_with_borders(float percentage) {
+    size_t index;
+    if (percentage <= 0.0f) {
+      index = 0;
+    } else if (percentage >= 100.0f) {
+      index = m_icons.size() - 1;
+    } else {
+      index = percentage * (m_icons.size() - 2) / 100.0f + 1;
+      index = math_util::cap<size_t>(index, 0, m_icons.size() - 1);
+    }
+    return m_icons[index];
+  }
+
+  ramp::operator bool() {
+    return !m_icons.empty();
+  }
+
+  /**
+   * Create a ramp by loading values
+   * from the configuration
+   */
+  ramp_t load_ramp(const config& conf, const string& section, string name, bool required) {
+    name = string_util::ltrim(string_util::rtrim(move(name), '>'), '<');
+
+    auto ramp_defaults = load_optional_label(conf, section, name);
+
+    vector<label_t> vec;
+    vector<string> icons;
+
+    if (required) {
+      icons = conf.get_list<string>(section, name);
+    } else {
+      icons = conf.get_list<string>(section, name, {});
+    }
+
+    for (size_t i = 0; i < icons.size(); i++) {
+      auto icon = load_optional_label(conf, section, name + "-" + to_string(i), icons[i]);
+      icon->copy_undefined(ramp_defaults);
+      vec.emplace_back(move(icon));
+    }
+
+    return factory_util::shared<drawtypes::ramp>(move(vec));
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/events/signal_emitter.cpp b/src/events/signal_emitter.cpp
new file mode 100644 (file)
index 0000000..e153944
--- /dev/null
@@ -0,0 +1,15 @@
+#include "events/signal_emitter.hpp"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+signal_receivers_t g_signal_receivers;
+
+/**
+ * Create instance
+ */
+signal_emitter::make_type signal_emitter::make() {
+  return static_cast<signal_emitter&>(*factory_util::singleton<signal_emitter>());
+}
+
+POLYBAR_NS_END
diff --git a/src/events/signal_receiver.cpp b/src/events/signal_receiver.cpp
new file mode 100644 (file)
index 0000000..634a1b6
--- /dev/null
@@ -0,0 +1 @@
+#include "events/signal_receiver.hpp"
diff --git a/src/ipc.cpp b/src/ipc.cpp
new file mode 100644 (file)
index 0000000..951ab18
--- /dev/null
@@ -0,0 +1,134 @@
+#include <fcntl.h>
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <vector>
+
+#include "common.hpp"
+#include "utils/file.hpp"
+#include "utils/io.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);
+      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;
+}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644 (file)
index 0000000..4f61876
--- /dev/null
@@ -0,0 +1,169 @@
+#include "components/bar.hpp"
+#include "components/command_line.hpp"
+#include "components/config.hpp"
+#include "components/config_parser.hpp"
+#include "components/controller.hpp"
+#include "components/ipc.hpp"
+#include "utils/env.hpp"
+#include "utils/inotify.hpp"
+#include "utils/process.hpp"
+#include "x11/connection.hpp"
+
+using namespace polybar;
+
+int main(int argc, char** argv) {
+  // clang-format off
+  const command_line::options opts{
+      command_line::option{"-h", "--help", "Display this help and exit"},
+      command_line::option{"-v", "--version", "Display build details and exit"},
+      command_line::option{"-l", "--log", "Set the logging verbosity (default: notice)", "LEVEL", {"error", "warning", "notice", "info", "trace"}},
+      command_line::option{"-q", "--quiet", "Be quiet (will override -l)"},
+      command_line::option{"-c", "--config", "Path to the configuration file", "FILE"},
+      command_line::option{"-r", "--reload", "Reload when the configuration has been modified"},
+      command_line::option{"-d", "--dump", "Print value of PARAM in bar section and exit", "PARAM"},
+      command_line::option{"-m", "--list-monitors", "Print list of available monitors and exit (Removes cloned monitors)"},
+      command_line::option{"-M", "--list-all-monitors", "Print list of all available monitors (Including cloned monitors) and exit"},
+      command_line::option{"-w", "--print-wmname", "Print the generated WM_NAME and exit"},
+      command_line::option{"-s", "--stdout", "Output data to stdout instead of drawing it to the X window"},
+      command_line::option{"-p", "--png", "Save png snapshot to FILE after running for 3 seconds", "FILE"},
+  };
+  // clang-format on
+
+  unsigned char exit_code{EXIT_SUCCESS};
+  bool reload{false};
+
+  logger& logger{const_cast<decltype(logger)>(logger::make(loglevel::NOTICE))};
+
+  try {
+    //==================================================
+    // Parse command line arguments
+    //==================================================
+    string scriptname{argv[0]};
+    vector<string> args{argv + 1, argv + argc};
+
+    command_line::parser::make_type cli{command_line::parser::make(move(scriptname), move(opts))};
+    cli->process_input(args);
+
+    if (cli->has("quiet")) {
+      logger.verbosity(loglevel::ERROR);
+    } else if (cli->has("log")) {
+      logger.verbosity(logger::parse_verbosity(cli->get("log")));
+    }
+
+    if (cli->has("help")) {
+      cli->usage();
+      return EXIT_SUCCESS;
+    } else if (cli->has("version")) {
+      print_build_info(version_details(args));
+      return EXIT_SUCCESS;
+    }
+
+    //==================================================
+    // Connect to X server
+    //==================================================
+    auto xcb_error = 0;
+    auto xcb_screen = 0;
+    auto xcb_connection = xcb_connect(nullptr, &xcb_screen);
+
+    if (xcb_connection == nullptr) {
+      throw application_error("A connection to X could not be established...");
+    } else if ((xcb_error = xcb_connection_has_error(xcb_connection))) {
+      throw application_error("X connection error... (what: " + connection::error_str(xcb_error) + ")");
+    }
+
+    connection& conn{connection::make(xcb_connection, xcb_screen)};
+    conn.ensure_event_mask(conn.root(), XCB_EVENT_MASK_PROPERTY_CHANGE);
+
+    //==================================================
+    // List available XRandR entries
+    //==================================================
+    if (cli->has("list-monitors") || cli->has("list-all-monitors")) {
+      bool purge_clones = !cli->has("list-all-monitors");
+      auto monitors = randr_util::get_monitors(conn, conn.root(), true, purge_clones);
+      for (auto&& mon : monitors) {
+        if (WITH_XRANDR_MONITORS && mon->output == XCB_NONE) {
+          printf("%s: %ix%i+%i+%i (XRandR monitor%s)\n", mon->name.c_str(), mon->w, mon->h, mon->x, mon->y,
+              mon->primary ? ", primary" : "");
+        } else {
+          printf("%s: %ix%i+%i+%i%s\n", mon->name.c_str(), mon->w, mon->h, mon->x, mon->y,
+              mon->primary ? " (primary)" : "");
+        }
+      }
+      return EXIT_SUCCESS;
+    }
+
+    //==================================================
+    // Load user configuration
+    //==================================================
+    string confpath;
+
+    // Make sure a bar name is passed in
+    if (!cli->has(0)) {
+      cli->usage();
+      return EXIT_FAILURE;
+    } else if (cli->has(1)) {
+      fprintf(stderr, "Unrecognized argument \"%s\"\n", cli->get(1).c_str());
+      cli->usage();
+      return EXIT_FAILURE;
+    }
+
+    if (cli->has("config")) {
+      confpath = cli->get("config");
+    } else {
+      confpath = file_util::get_config_path();
+    }
+    if (confpath.empty()) {
+      throw application_error("Define configuration using --config=PATH");
+    }
+
+    config_parser parser{logger, move(confpath), cli->get(0)};
+    config::make_type conf = parser.parse();
+
+    //==================================================
+    // Dump requested data
+    //==================================================
+    if (cli->has("dump")) {
+      printf("%s\n", conf.get(conf.section(), cli->get("dump")).c_str());
+      return EXIT_SUCCESS;
+    }
+    if (cli->has("print-wmname")) {
+      printf("%s\n", bar::make(true)->settings().wmname.c_str());
+      return EXIT_SUCCESS;
+    }
+
+    //==================================================
+    // Create controller and run application
+    //==================================================
+    unique_ptr<ipc> ipc{};
+    unique_ptr<inotify_watch> config_watch{};
+
+    if (conf.get(conf.section(), "enable-ipc", false)) {
+      ipc = ipc::make();
+    }
+    if (cli->has("reload")) {
+      config_watch = inotify_util::make_watch(conf.filepath());
+    }
+
+    auto ctrl = controller::make(move(ipc), move(config_watch));
+
+    if (!ctrl->run(cli->has("stdout"), cli->get("png"))) {
+      reload = true;
+    }
+  } catch (const exception& err) {
+    logger.err(err.what());
+    exit_code = EXIT_FAILURE;
+  }
+
+  logger.info("Waiting for spawned processes to end");
+  while (process_util::notify_childprocess()) {
+    ;
+  }
+
+  if (reload) {
+    logger.info("Re-launching application...");
+    process_util::exec(move(argv[0]), move(argv));
+  }
+
+  logger.info("Reached end of application...");
+  return exit_code;
+}
diff --git a/src/modules/alsa.cpp b/src/modules/alsa.cpp
new file mode 100644 (file)
index 0000000..c817b2d
--- /dev/null
@@ -0,0 +1,276 @@
+#include "modules/alsa.hpp"
+#include "adapters/alsa/control.hpp"
+#include "adapters/alsa/generic.hpp"
+#include "adapters/alsa/mixer.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/math.hpp"
+
+#include "modules/meta/base.inl"
+
+#include "settings.hpp"
+
+POLYBAR_NS
+
+using namespace alsa;
+
+namespace modules {
+  template class module<alsa_module>;
+
+  alsa_module::alsa_module(const bar_settings& bar, string name_) : event_module<alsa_module>(bar, move(name_)) {
+    // Load configuration values
+    m_mapped = m_conf.get(name(), "mapped", m_mapped);
+    m_interval = m_conf.get(name(), "interval", m_interval);
+
+    auto master_mixer_name = m_conf.get(name(), "master-mixer", "Master"s);
+    auto speaker_mixer_name = m_conf.get(name(), "speaker-mixer", ""s);
+    auto headphone_mixer_name = m_conf.get(name(), "headphone-mixer", ""s);
+
+    // m_soundcard_name: Master Soundcard Name
+    // s_soundcard_name: Speaker Soundcard Name
+    // h_soundcard_name: Headphone Soundcard Name
+    auto m_soundcard_name = m_conf.get(name(), "master-soundcard", "default"s);
+    auto s_soundcard_name = m_conf.get(name(), "speaker-soundcard", "default"s);
+    auto h_soundcard_name = m_conf.get(name(), "headphone-soundcard", "default"s);
+
+    if (!headphone_mixer_name.empty()) {
+      m_headphoneid = m_conf.get<decltype(m_headphoneid)>(name(), "headphone-id");
+    }
+
+    if (string_util::compare(speaker_mixer_name, "master")) {
+      throw module_error("Master mixer is already defined");
+    }
+    if (string_util::compare(headphone_mixer_name, "master")) {
+      throw module_error("Master mixer is already defined");
+    }
+
+    // Setup mixers
+    try {
+      if (!master_mixer_name.empty()) {
+        m_mixer[mixer::MASTER].reset(new mixer_t::element_type{move(master_mixer_name), move(m_soundcard_name)});
+      }
+      if (!speaker_mixer_name.empty()) {
+        m_mixer[mixer::SPEAKER].reset(new mixer_t::element_type{move(speaker_mixer_name), move(s_soundcard_name)});
+      }
+      if (!headphone_mixer_name.empty()) {
+        m_mixer[mixer::HEADPHONE].reset(new mixer_t::element_type{move(headphone_mixer_name), move(h_soundcard_name)});
+      }
+      if (m_mixer[mixer::HEADPHONE]) {
+        m_ctrl[control::HEADPHONE].reset(new control_t::element_type{m_headphoneid});
+      }
+      if (m_mixer.empty()) {
+        throw module_error("No configured mixers");
+      }
+    } catch (const mixer_error& err) {
+      throw module_error(err.what());
+    } catch (const control_error& err) {
+      throw module_error(err.what());
+    }
+
+    // Add formats and elements
+    m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
+    m_formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});
+
+    if (m_formatter->has(TAG_BAR_VOLUME)) {
+      m_bar_volume = load_progressbar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
+    }
+    if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
+      m_label_volume = load_optional_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
+      m_label_muted = load_optional_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_RAMP_VOLUME)) {
+      m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME);
+      m_ramp_headphones = load_ramp(m_conf, name(), TAG_RAMP_HEADPHONES, false);
+    }
+  }
+
+  void alsa_module::teardown() {
+    m_mixer.clear();
+    m_ctrl.clear();
+    snd_config_update_free_global();
+  }
+
+  bool alsa_module::has_event() {
+    // Poll for mixer and control events
+    try {
+      if (m_mixer[mixer::MASTER] && m_mixer[mixer::MASTER]->wait(25)) {
+        return true;
+      }
+      if (m_mixer[mixer::SPEAKER] && m_mixer[mixer::SPEAKER]->wait(25)) {
+        return true;
+      }
+      if (m_mixer[mixer::HEADPHONE] && m_mixer[mixer::HEADPHONE]->wait(25)) {
+        return true;
+      }
+      if (m_ctrl[control::HEADPHONE] && m_ctrl[control::HEADPHONE]->wait(25)) {
+        return true;
+      }
+    } catch (const alsa_exception& e) {
+      m_log.err("%s: %s", name(), e.what());
+    }
+
+    return false;
+  }
+
+  bool alsa_module::update() {
+    // Consume pending events
+    if (m_mixer[mixer::MASTER]) {
+      m_mixer[mixer::MASTER]->process_events();
+    }
+    if (m_mixer[mixer::SPEAKER]) {
+      m_mixer[mixer::SPEAKER]->process_events();
+    }
+    if (m_mixer[mixer::HEADPHONE]) {
+      m_mixer[mixer::HEADPHONE]->process_events();
+    }
+    if (m_ctrl[control::HEADPHONE]) {
+      m_ctrl[control::HEADPHONE]->process_events();
+    }
+
+    // Get volume, mute and headphone state
+    m_volume = 100;
+    m_muted = false;
+    m_headphones = false;
+
+    try {
+      if (m_mixer[mixer::MASTER]) {
+        m_volume = m_volume * (m_mapped ? m_mixer[mixer::MASTER]->get_normalized_volume() / 100.0f
+                                        : m_mixer[mixer::MASTER]->get_volume() / 100.0f);
+        m_muted = m_muted || m_mixer[mixer::MASTER]->is_muted();
+      }
+    } catch (const alsa_exception& err) {
+      m_log.err("%s: Failed to query master mixer (%s)", name(), err.what());
+    }
+
+    try {
+      if (m_ctrl[control::HEADPHONE] && m_ctrl[control::HEADPHONE]->test_device_plugged()) {
+        m_headphones = true;
+        m_volume = m_volume * (m_mapped ? m_mixer[mixer::HEADPHONE]->get_normalized_volume() / 100.0f
+                                        : m_mixer[mixer::HEADPHONE]->get_volume() / 100.0f);
+        m_muted = m_muted || m_mixer[mixer::HEADPHONE]->is_muted();
+      }
+    } catch (const alsa_exception& err) {
+      m_log.err("%s: Failed to query headphone mixer (%s)", name(), err.what());
+    }
+
+    try {
+      if (!m_headphones && m_mixer[mixer::SPEAKER]) {
+        m_volume = m_volume * (m_mapped ? m_mixer[mixer::SPEAKER]->get_normalized_volume() / 100.0f
+                                        : m_mixer[mixer::SPEAKER]->get_volume() / 100.0f);
+        m_muted = m_muted || m_mixer[mixer::SPEAKER]->is_muted();
+      }
+    } catch (const alsa_exception& err) {
+      m_log.err("%s: Failed to query speaker mixer (%s)", name(), err.what());
+    }
+
+    // Replace label tokens
+    if (m_label_volume) {
+      m_label_volume->reset_tokens();
+      m_label_volume->replace_token("%percentage%", to_string(m_volume));
+    }
+
+    if (m_label_muted) {
+      m_label_muted->reset_tokens();
+      m_label_muted->replace_token("%percentage%", to_string(m_volume));
+    }
+
+    return true;
+  }
+
+  string alsa_module::get_format() const {
+    return m_muted ? FORMAT_MUTED : FORMAT_VOLUME;
+  }
+
+  string alsa_module::get_output() {
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapper
+    // with the cmd handlers
+    string output{module::get_output()};
+
+    if (m_handle_events) {
+      m_builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "");
+      m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+      m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+    }
+
+    m_builder->append(output);
+
+    return m_builder->flush();
+  }
+
+  bool alsa_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_BAR_VOLUME) {
+      builder->node(m_bar_volume->output(m_volume));
+    } else if (tag == TAG_RAMP_VOLUME && (!m_headphones || !*m_ramp_headphones)) {
+      builder->node(m_ramp_volume->get_by_percentage(m_volume));
+    } else if (tag == TAG_RAMP_VOLUME && m_headphones && *m_ramp_headphones) {
+      builder->node(m_ramp_headphones->get_by_percentage(m_volume));
+    } else if (tag == TAG_LABEL_VOLUME) {
+      builder->node(m_label_volume);
+    } else if (tag == TAG_LABEL_MUTED) {
+      builder->node(m_label_muted);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool alsa_module::input(const string& action, const string&) {
+    if (!m_handle_events) {
+      return false;
+    } else if (!m_mixer[mixer::MASTER]) {
+      return false;
+    }
+
+    try {
+      vector<mixer_t> mixers;
+      bool headphones{m_headphones};
+
+      if (m_mixer[mixer::MASTER] && !m_mixer[mixer::MASTER]->get_name().empty()) {
+        mixers.emplace_back(new mixer_t::element_type(
+            string{m_mixer[mixer::MASTER]->get_name()}, string{m_mixer[mixer::MASTER]->get_sound_card()}));
+      }
+      if (m_mixer[mixer::HEADPHONE] && !m_mixer[mixer::HEADPHONE]->get_name().empty() && headphones) {
+        mixers.emplace_back(new mixer_t::element_type(
+            string{m_mixer[mixer::HEADPHONE]->get_name()}, string{m_mixer[mixer::HEADPHONE]->get_sound_card()}));
+      }
+      if (m_mixer[mixer::SPEAKER] && !m_mixer[mixer::SPEAKER]->get_name().empty() && !headphones) {
+        mixers.emplace_back(new mixer_t::element_type(
+            string{m_mixer[mixer::SPEAKER]->get_name()}, string{m_mixer[mixer::SPEAKER]->get_sound_card()}));
+      }
+
+      if (action == EVENT_TOGGLE) {
+        for (auto&& mixer : mixers) {
+          mixer->set_mute(m_muted || mixers[0]->is_muted());
+        }
+      } else if (action == EVENT_INC) {
+        for (auto&& mixer : mixers) {
+          m_mapped ? mixer->set_normalized_volume(math_util::cap<float>(mixer->get_normalized_volume() + m_interval, 0, 100))
+                   : mixer->set_volume(math_util::cap<float>(mixer->get_volume() + m_interval, 0, 100));
+        }
+      } else if (action == EVENT_DEC) {
+        for (auto&& mixer : mixers) {
+          m_mapped ? mixer->set_normalized_volume(math_util::cap<float>(mixer->get_normalized_volume() - m_interval, 0, 100))
+                   : mixer->set_volume(math_util::cap<float>(mixer->get_volume() - m_interval, 0, 100));
+        }
+      } else {
+        return false;
+      }
+
+      for (auto&& mixer : mixers) {
+        if (mixer->wait(0)) {
+          mixer->process_events();
+        }
+      }
+    } catch (const exception& err) {
+      m_log.err("%s: Failed to handle command (%s)", name(), err.what());
+    }
+
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp
new file mode 100644 (file)
index 0000000..8bdae4b
--- /dev/null
@@ -0,0 +1,144 @@
+#include "modules/backlight.hpp"
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "utils/file.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<backlight_module>;
+
+  void backlight_module::brightness_handle::filepath(const string& path) {
+    if (!file_util::exists(path)) {
+      throw module_error("The file '" + path + "' does not exist");
+    }
+    m_path = path;
+  }
+
+  float backlight_module::brightness_handle::read() const {
+    return std::strtof(file_util::contents(m_path).c_str(), nullptr);
+  }
+
+  backlight_module::backlight_module(const bar_settings& bar, string name_)
+      : inotify_module<backlight_module>(bar, move(name_)) {
+    auto card = m_conf.get(name(), "card");
+
+    // Get flag to check if we should add scroll handlers for changing value
+    m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+
+    // Add formats and elements
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR, TAG_RAMP});
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_BAR)) {
+      m_progressbar = load_progressbar(m_bar, m_conf, name(), TAG_BAR);
+    }
+    if (m_formatter->has(TAG_RAMP)) {
+      m_ramp = load_ramp(m_conf, name(), TAG_RAMP);
+    }
+
+    // Build path to the sysfs folder the current/maximum brightness values are located
+    m_path_backlight = string_util::replace(PATH_BACKLIGHT, "%card%", card);
+
+    /*
+     * amdgpu drivers set the actual_brightness in a different scale than [0, max_brightness]
+     * The only sensible way is to use the 'brightness' file instead
+     * Ref: https://github.com/Alexays/Waybar/issues/335
+     */
+    std::string brightness_type = ((card.substr(0, 9) == "amdgpu_bl") ? "brightness" : "actual_brightness");
+    auto path_backlight_val = m_path_backlight + "/" + brightness_type;
+
+    m_val.filepath(path_backlight_val);
+    m_max.filepath(m_path_backlight + "/max_brightness");
+
+    // Add inotify watch
+    watch(path_backlight_val);
+  }
+
+  void backlight_module::idle() {
+    sleep(75ms);
+  }
+
+  bool backlight_module::on_event(inotify_event* event) {
+    if (event != nullptr) {
+      m_log.trace("%s: %s", name(), event->filename);
+    }
+
+    m_max_brightness = m_max.read();
+    m_percentage = static_cast<int>(m_val.read() / m_max_brightness * 100.0f + 0.5f);
+
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%percentage%", to_string(m_percentage));
+    }
+
+    return true;
+  }
+
+  string backlight_module::get_output() {
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapped
+    // with the cmd handlers
+    string output{module::get_output()};
+
+    if (m_scroll) {
+      m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+      m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+    }
+
+    m_builder->append(std::move(output));
+
+    m_builder->action_close();
+    m_builder->action_close();
+
+    return m_builder->flush();
+  }
+
+  bool backlight_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_BAR) {
+      builder->node(m_progressbar->output(m_percentage));
+    } else if (tag == TAG_RAMP) {
+      builder->node(m_ramp->get_by_percentage(m_percentage));
+    } else if (tag == TAG_LABEL) {
+      builder->node(m_label);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool backlight_module::input(const string& action, const string&) {
+    double value_mod{0.0};
+
+    if (action == EVENT_INC) {
+      value_mod = 5.0;
+    } else if (action == EVENT_DEC) {
+      value_mod = -5.0;
+    } else {
+      return false;
+    }
+
+    m_log.info("%s: Changing value by %f%", name(), value_mod);
+
+    try {
+      int rounded = math_util::cap<double>(m_percentage + value_mod, 0.0, 100.0) + 0.5;
+      int value = math_util::percentage_to_value<int>(rounded, m_max_brightness);
+      file_util::write_contents(m_path_backlight + "/brightness", to_string(value));
+    } catch (const exception& err) {
+      m_log.err(
+          "%s: Unable to change backlight value. Your system may require additional "
+          "configuration. Please read the module documentation.\n(reason: %s)",
+          name(), err.what());
+    }
+
+    return true;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp
new file mode 100644 (file)
index 0000000..a3f5773
--- /dev/null
@@ -0,0 +1,372 @@
+#include "modules/battery.hpp"
+#include "drawtypes/animation.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/file.hpp"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<battery_module>;
+
+  template <typename ValueReader>
+  typename ValueReader::return_type read(ValueReader& reader) {
+    std::lock_guard<ValueReader> guard(reader);
+    return reader.read();
+  }
+
+  /**
+   * Bootstrap module by setting up required components
+   */
+  battery_module::battery_module(const bar_settings& bar, string name_)
+      : inotify_module<battery_module>(bar, move(name_)) {
+    // Load configuration values
+    m_fullat = math_util::min(m_conf.get(name(), "full-at", m_fullat), 100);
+    m_interval = m_conf.get<decltype(m_interval)>(name(), "poll-interval", 5s);
+    m_lastpoll = chrono::system_clock::now();
+
+    auto path_adapter = string_util::replace(PATH_ADAPTER, "%adapter%", m_conf.get(name(), "adapter", "ADP1"s)) + "/";
+    auto path_battery = string_util::replace(PATH_BATTERY, "%battery%", m_conf.get(name(), "battery", "BAT0"s)) + "/";
+
+    // Make state reader
+    if (file_util::exists((m_fstate = path_adapter + "online"))) {
+      m_state_reader = make_unique<state_reader>([=] { return file_util::contents(m_fstate).compare(0, 1, "1") == 0; });
+    } else if (file_util::exists((m_fstate = path_battery + "status"))) {
+      m_state_reader =
+          make_unique<state_reader>([=] { return file_util::contents(m_fstate).compare(0, 8, "Charging") == 0; });
+    } else {
+      throw module_error("No suitable way to get current charge state");
+    }
+
+    // Make capacity reader
+    if ((m_fcapnow = file_util::pick({path_battery + "charge_now", path_battery + "energy_now"})).empty()) {
+      throw module_error("No suitable way to get current capacity value");
+    } else if ((m_fcapfull = file_util::pick({path_battery + "charge_full", path_battery + "energy_full"})).empty()) {
+      throw module_error("No suitable way to get max capacity value");
+    }
+
+    m_capacity_reader = make_unique<capacity_reader>([=] {
+      auto cap_now = std::strtoul(file_util::contents(m_fcapnow).c_str(), nullptr, 10);
+      auto cap_max = std::strtoul(file_util::contents(m_fcapfull).c_str(), nullptr, 10);
+      return math_util::percentage(cap_now, 0UL, cap_max);
+    });
+
+    // Make rate reader
+    if ((m_fvoltage = file_util::pick({path_battery + "voltage_now"})).empty()) {
+      throw module_error("No suitable way to get current voltage value");
+    } else if ((m_frate = file_util::pick({path_battery + "current_now", path_battery + "power_now"})).empty()) {
+      throw module_error("No suitable way to get current charge rate value");
+    }
+
+    m_rate_reader = make_unique<rate_reader>([this] {
+      unsigned long rate{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10)};
+      unsigned long volt{std::strtoul(file_util::contents(m_fvoltage).c_str(), nullptr, 10) / 1000UL};
+      unsigned long now{std::strtoul(file_util::contents(m_fcapnow).c_str(), nullptr, 10)};
+      unsigned long max{std::strtoul(file_util::contents(m_fcapfull).c_str(), nullptr, 10)};
+      unsigned long cap{read(*m_state_reader) ? max - now : now};
+
+      if (rate && volt && cap) {
+        auto remaining = (cap / volt);
+        auto current_rate = (rate / volt);
+
+        if (remaining && current_rate) {
+          return 3600UL * remaining / current_rate;
+        }
+      }
+
+      return 0UL;
+    });
+
+    // Make consumption reader
+    m_consumption_reader = make_unique<consumption_reader>([this] {
+      float consumption;
+
+      // if the rate we found was the current, calculate power (P = I*V)
+      if (string_util::contains(m_frate, "current_now")) {
+        unsigned long current{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10)};
+        unsigned long voltage{std::strtoul(file_util::contents(m_fvoltage).c_str(), nullptr, 10)};
+
+        consumption = ((voltage / 1000.0) * (current /  1000.0)) / 1e6;
+      // if it was power, just use as is
+      } else {
+        unsigned long power{std::strtoul(file_util::contents(m_frate).c_str(), nullptr, 10)};
+
+        consumption = power / 1e6;
+      }
+
+      // convert to string with 2 decimmal places
+      string rtn(16, '\0'); // 16 should be plenty big. Cant see it needing more than 6/7..
+      auto written = std::snprintf(&rtn[0], rtn.size(), "%.2f", consumption);
+      rtn.resize(written);
+
+      return rtn;
+    });
+
+    // Load state and capacity level
+    m_state = current_state();
+    m_percentage = current_percentage();
+
+    // Add formats and elements
+    m_formatter->add(FORMAT_CHARGING, TAG_LABEL_CHARGING,
+        {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_CHARGING, TAG_LABEL_CHARGING});
+    m_formatter->add(FORMAT_DISCHARGING, TAG_LABEL_DISCHARGING,
+        {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_ANIMATION_DISCHARGING, TAG_LABEL_DISCHARGING});
+    m_formatter->add(FORMAT_FULL, TAG_LABEL_FULL, {TAG_BAR_CAPACITY, TAG_RAMP_CAPACITY, TAG_LABEL_FULL});
+
+    if (m_formatter->has(TAG_ANIMATION_CHARGING, FORMAT_CHARGING)) {
+      m_animation_charging = load_animation(m_conf, name(), TAG_ANIMATION_CHARGING);
+    }
+    if (m_formatter->has(TAG_ANIMATION_DISCHARGING, FORMAT_DISCHARGING)) {
+      m_animation_discharging = load_animation(m_conf, name(), TAG_ANIMATION_DISCHARGING);
+    }
+    if (m_formatter->has(TAG_BAR_CAPACITY)) {
+      m_bar_capacity = load_progressbar(m_bar, m_conf, name(), TAG_BAR_CAPACITY);
+    }
+    if (m_formatter->has(TAG_RAMP_CAPACITY)) {
+      m_ramp_capacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
+    }
+    if (m_formatter->has(TAG_LABEL_CHARGING, FORMAT_CHARGING)) {
+      m_label_charging = load_optional_label(m_conf, name(), TAG_LABEL_CHARGING, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_LABEL_DISCHARGING, FORMAT_DISCHARGING)) {
+      m_label_discharging = load_optional_label(m_conf, name(), TAG_LABEL_DISCHARGING, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_LABEL_FULL, FORMAT_FULL)) {
+      m_label_full = load_optional_label(m_conf, name(), TAG_LABEL_FULL, "%percentage%%");
+    }
+
+    // Create inotify watches
+    watch(m_fcapnow, IN_ACCESS);
+    watch(m_fstate, IN_ACCESS);
+
+    // Setup time if token is used
+    if ((m_label_charging && m_label_charging->has_token("%time%")) ||
+        (m_label_discharging && m_label_discharging->has_token("%time%"))) {
+      if (!m_bar.locale.empty()) {
+        setlocale(LC_TIME, m_bar.locale.c_str());
+      }
+      m_timeformat = m_conf.get(name(), "time-format", "%H:%M:%S"s);
+    }
+  }
+
+  /**
+   * Dispatch the subthread used to update the
+   * charging animation when the module is started
+   */
+  void battery_module::start() {
+    this->inotify_module::start();
+    // We only start animation thread if there is at least one animation.
+    if (m_animation_charging || m_animation_discharging) {
+      m_subthread = thread(&battery_module::subthread, this);
+    }
+  }
+
+  /**
+   * Release wake lock when stopping the module
+   */
+  void battery_module::teardown() {
+    if (m_subthread.joinable()) {
+      m_subthread.join();
+    }
+  }
+
+  /**
+   * Idle between polling inotify watches for events.
+   *
+   * If the defined interval has been reached, trigger a manual
+   * poll in case the inotify events aren't fired.
+   *
+   * This fallback is needed because some systems won't
+   * report inotify events for files on sysfs.
+   */
+  void battery_module::idle() {
+    if (m_interval.count() > 0) {
+      auto now = chrono::system_clock::now();
+      if (chrono::duration_cast<decltype(m_interval)>(now - m_lastpoll) > m_interval) {
+        m_lastpoll = now;
+        m_log.info("%s: Polling values (inotify fallback)", name());
+        read(*m_capacity_reader);
+      }
+    }
+
+    this->inotify_module::idle();
+  }
+
+  /**
+   * Update values when tracked files have changed
+   */
+  bool battery_module::on_event(inotify_event* event) {
+    auto state = current_state();
+    auto percentage = current_percentage();
+
+    // Reset timer to avoid unnecessary polling
+    m_lastpoll = chrono::system_clock::now();
+
+    if (event != nullptr) {
+      m_log.trace("%s: Inotify event reported for %s", name(), event->filename);
+
+      if (state == m_state && percentage == m_percentage && m_unchanged--) {
+        return false;
+      }
+
+      m_unchanged = SKIP_N_UNCHANGED;
+    }
+
+    m_state = state;
+    m_percentage = percentage;
+
+    const auto label = [this] {
+      if (m_state == battery_module::state::FULL) {
+        return m_label_full;
+      } else if (m_state == battery_module::state::DISCHARGING) {
+        return m_label_discharging;
+      } else {
+        return m_label_charging;
+      }
+    }();
+
+    if (label) {
+      label->reset_tokens();
+      label->replace_token("%percentage%", to_string(clamp_percentage(m_percentage, m_state)));
+      label->replace_token("%percentage_raw%", to_string(m_percentage));
+      label->replace_token("%consumption%", current_consumption());
+
+      if (m_state != battery_module::state::FULL && !m_timeformat.empty()) {
+        label->replace_token("%time%", current_time());
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Get the output format based on state
+   */
+  string battery_module::get_format() const {
+    if (m_state == battery_module::state::CHARGING) {
+      return FORMAT_CHARGING;
+    } else if (m_state == battery_module::state::DISCHARGING) {
+      return FORMAT_DISCHARGING;
+    } else {
+      return FORMAT_FULL;
+    }
+  }
+
+  /**
+   * Generate module output using defined drawtypes
+   */
+  bool battery_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_ANIMATION_CHARGING) {
+      builder->node(m_animation_charging->get());
+    } else if (tag == TAG_ANIMATION_DISCHARGING) {
+      builder->node(m_animation_discharging->get());
+    } else if (tag == TAG_BAR_CAPACITY) {
+      builder->node(m_bar_capacity->output(clamp_percentage(m_percentage, m_state)));
+    } else if (tag == TAG_RAMP_CAPACITY) {
+      builder->node(m_ramp_capacity->get_by_percentage(clamp_percentage(m_percentage, m_state)));
+    } else if (tag == TAG_LABEL_CHARGING) {
+      builder->node(m_label_charging);
+    } else if (tag == TAG_LABEL_DISCHARGING) {
+      builder->node(m_label_discharging);
+    } else if (tag == TAG_LABEL_FULL) {
+      builder->node(m_label_full);
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Get the current battery state
+   */
+  battery_module::state battery_module::current_state() {
+    if (read(*m_capacity_reader) >= m_fullat) {
+      return battery_module::state::FULL;
+    } else if (!read(*m_state_reader)) {
+      return battery_module::state::DISCHARGING;
+    } else {
+      return battery_module::state::CHARGING;
+    }
+  }
+
+  /**
+   * Get the current capacity level
+   */
+  int battery_module::current_percentage() {
+    return read(*m_capacity_reader);
+  }
+
+  int battery_module::clamp_percentage(int percentage, state state) const {
+    if (state == battery_module::state::FULL && percentage >= m_fullat) {
+      return 100;
+    }
+    return percentage;
+  }
+
+  /**
+  * Get the current power consumption
+  */
+  string battery_module::current_consumption() {
+    return read(*m_consumption_reader);
+  }
+
+  /**
+   * Get estimate of remaining time until fully dis-/charged
+   */
+  string battery_module::current_time() {
+    struct tm t {
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, nullptr
+    };
+
+    chrono::seconds sec{read(*m_rate_reader)};
+    if (sec.count() > 0) {
+      t.tm_hour = chrono::duration_cast<chrono::hours>(sec).count();
+      sec -= chrono::seconds{3600 * t.tm_hour};
+      t.tm_min = chrono::duration_cast<chrono::minutes>(sec).count();
+      sec -= chrono::seconds{60 * t.tm_min};
+      t.tm_sec = chrono::duration_cast<chrono::seconds>(sec).count();
+    }
+
+    char buffer[256]{0};
+    strftime(buffer, sizeof(buffer), m_timeformat.c_str(), &t);
+    return {buffer};
+  }
+
+  /**
+   * Subthread runner that emits update events to refresh <animation-charging>
+   * or <animation-discharging> in case they are used. Note, that it is ok to
+   * use a single thread, because the two animations are never shown at the
+   * same time.
+   */
+  void battery_module::subthread() {
+    m_log.trace("%s: Start of subthread", name());
+
+    while (running()) {
+      auto now = chrono::steady_clock::now();
+      auto framerate = 1000U;  // milliseconds
+      if (m_state == battery_module::state::CHARGING && m_animation_charging) {
+        m_animation_charging->increment();
+        broadcast();
+        framerate = m_animation_charging->framerate();
+      } else if (m_state == battery_module::state::DISCHARGING && m_animation_discharging) {
+        m_animation_discharging->increment();
+        broadcast();
+        framerate = m_animation_discharging->framerate();
+      }
+
+      // We don't count the the first part of the loop to be as close as possible to the framerate.
+      now += chrono::milliseconds(framerate);
+      this_thread::sleep_until(now);
+    }
+
+    m_log.trace("%s: End of subthread", name());
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/bspwm.cpp b/src/modules/bspwm.cpp
new file mode 100644 (file)
index 0000000..f78dde5
--- /dev/null
@@ -0,0 +1,504 @@
+#include "modules/bspwm.hpp"
+
+#include <sys/socket.h>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/factory.hpp"
+#include "utils/file.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace {
+  using bspwm_state = modules::bspwm_module::state;
+
+  unsigned int make_mask(bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
+    unsigned int mask{0U};
+    if (static_cast<unsigned int>(s1)) {
+      mask |= 1U << (static_cast<unsigned int>(s1) - 1U);
+    }
+    if (static_cast<unsigned int>(s2)) {
+      mask |= 1U << (static_cast<unsigned int>(s2) - 1U);
+    }
+    return mask;
+  }
+
+  unsigned int check_mask(unsigned int base, bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
+    unsigned int mask{0U};
+    if (static_cast<unsigned int>(s1)) {
+      mask |= 1U << (static_cast<unsigned int>(s1) - 1U);
+    }
+    if (static_cast<unsigned int>(s2)) {
+      mask |= 1U << (static_cast<unsigned int>(s2) - 1U);
+    }
+    return (base & mask) == mask;
+  }
+}  // namespace
+
+namespace modules {
+  template class module<bspwm_module>;
+
+  bspwm_module::bspwm_module(const bar_settings& bar, string name_) : event_module<bspwm_module>(bar, move(name_)) {
+    auto socket_path = bspwm_util::get_socket_path();
+
+    if (!file_util::exists(socket_path)) {
+      throw module_error("Could not find socket: " + (socket_path.empty() ? "<empty>" : socket_path));
+    }
+
+    // Create ipc subscriber
+    m_subscriber = bspwm_util::make_subscriber();
+
+    // Load configuration values
+    m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
+    m_click = m_conf.get(name(), "enable-click", m_click);
+    m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+    m_revscroll = m_conf.get(name(), "reverse-scroll", m_revscroll);
+    m_inlinemode = m_conf.get(name(), "inline-mode", m_inlinemode);
+    m_fuzzy_match = m_conf.get(name(), "fuzzy-match", m_fuzzy_match);
+
+    // Add formats and create components
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE}, {TAG_LABEL_MONITOR, TAG_LABEL_MODE});
+
+    if (m_formatter->has(TAG_LABEL_MONITOR)) {
+      m_monitorlabel = load_optional_label(m_conf, name(), "label-monitor", DEFAULT_MONITOR_LABEL);
+    }
+
+    if (m_formatter->has(TAG_LABEL_STATE)) {
+      // XXX: Warn about deprecated parameters
+      m_conf.warn_deprecated(name(), "label-dimmed-active", "label-dimmed-focused");
+
+      // clang-format off
+      try {
+        m_statelabels.emplace(make_mask(state::FOCUSED), load_label(m_conf, name(), "label-active", DEFAULT_LABEL));
+        m_conf.warn_deprecated(name(), "label-active", "label-focused and label-dimmed-focused");
+      } catch (const key_error& err) {
+        m_statelabels.emplace(make_mask(state::FOCUSED), load_optional_label(m_conf, name(), "label-focused", DEFAULT_LABEL));
+      }
+
+      m_statelabels.emplace(make_mask(state::OCCUPIED),
+          load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL));
+      m_statelabels.emplace(make_mask(state::URGENT),
+          load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL));
+      m_statelabels.emplace(make_mask(state::EMPTY),
+          load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL));
+      m_statelabels.emplace(make_mask(state::DIMMED),
+          load_optional_label(m_conf, name(), "label-dimmed"));
+
+      vector<pair<state, string>> focused_overrides{
+        {state::OCCUPIED, "label-focused-occupied"},
+        {state::URGENT, "label-focused-urgent"},
+        {state::EMPTY, "label-focused-empty"}};
+
+      for (auto&& os : focused_overrides) {
+        unsigned int mask{make_mask(state::FOCUSED, os.first)};
+        try {
+          m_statelabels.emplace(mask, load_label(m_conf, name(), os.second));
+        } catch (const key_error& err) {
+          m_statelabels.emplace(mask, m_statelabels.at(make_mask(state::FOCUSED))->clone());
+        }
+      }
+
+      vector<pair<state, string>> dimmed_overrides{
+        {state::FOCUSED, "label-dimmed-focused"},
+        {state::OCCUPIED, "label-dimmed-occupied"},
+        {state::URGENT, "label-dimmed-urgent"},
+        {state::EMPTY, "label-dimmed-empty"}};
+
+      for (auto&& os : dimmed_overrides) {
+        m_statelabels.emplace(make_mask(state::DIMMED, os.first),
+            load_optional_label(m_conf, name(), os.second, m_statelabels.at(make_mask(os.first))->get()));
+      }
+      // clang-format on
+    }
+
+    if (m_formatter->has(TAG_LABEL_MODE)) {
+      m_modelabels.emplace(mode::LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle"));
+      m_modelabels.emplace(mode::LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"));
+      m_modelabels.emplace(mode::STATE_FULLSCREEN, load_optional_label(m_conf, name(), "label-fullscreen"));
+      m_modelabels.emplace(mode::STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating"));
+      m_modelabels.emplace(mode::STATE_PSEUDOTILED, load_optional_label(m_conf, name(), "label-pseudotiled"));
+      m_modelabels.emplace(mode::NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked"));
+      m_modelabels.emplace(mode::NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky"));
+      m_modelabels.emplace(mode::NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private"));
+      m_modelabels.emplace(mode::NODE_MARKED, load_optional_label(m_conf, name(), "label-marked"));
+    }
+
+    m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
+
+    m_icons = factory_util::shared<iconset>();
+    m_icons->add(DEFAULT_ICON, factory_util::shared<label>(m_conf.get(name(), DEFAULT_ICON, ""s)));
+
+    for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
+      auto vec = string_util::tokenize(workspace, ';');
+      if (vec.size() == 2) {
+        m_icons->add(vec[0], factory_util::shared<label>(vec[1]));
+      }
+    }
+  }
+
+  void bspwm_module::stop() {
+    if (m_subscriber) {
+      m_log.info("%s: Disconnecting from socket", name());
+      m_subscriber->disconnect();
+    }
+    event_module::stop();
+  }
+
+  bool bspwm_module::has_event() {
+    if (m_subscriber->poll(POLLHUP, 0)) {
+      m_log.notice("%s: Reconnecting to socket...", name());
+      m_subscriber = bspwm_util::make_subscriber();
+    }
+    return m_subscriber->peek(1);
+  }
+
+  bool bspwm_module::update() {
+    if (!m_subscriber) {
+      return false;
+    }
+
+    string data{m_subscriber->receive(BUFSIZ)};
+    bool result = false;
+
+    for (auto&& status_line : string_util::split(data, '\n')) {
+      // Need to return true if ANY of the handle_status calls
+      // return true
+      result = this->handle_status(status_line) || result;
+    }
+
+    return result;
+  }
+
+  bool bspwm_module::handle_status(string& data) {
+    if (data.empty()) {
+      return false;
+    }
+
+    size_t prefix_len{strlen(BSPWM_STATUS_PREFIX)};
+    if (data.compare(0, prefix_len, BSPWM_STATUS_PREFIX) != 0) {
+      m_log.err("%s: Unknown status '%s'", name(), data);
+      return false;
+    }
+
+    string_util::hash_type hash;
+    if ((hash = string_util::hash(data)) == m_hash) {
+      return false;
+    }
+
+    m_hash = hash;
+
+    size_t pos;
+
+    // Extract the string for the defined monitor
+    if (m_pinworkspaces) {
+      const auto needle_active = ":M" + m_bar.monitor->name + ":";
+      const auto needle_inactive = ":m" + m_bar.monitor->name + ":";
+
+      if ((pos = data.find(BSPWM_STATUS_PREFIX)) != string::npos) {
+        data = data.replace(pos, prefix_len, ":");
+      }
+      if ((pos = data.find(needle_active)) != string::npos) {
+        data.erase(0, pos + 1);
+      }
+      if ((pos = data.find(needle_inactive)) != string::npos) {
+        data.erase(0, pos + 1);
+      }
+      if ((pos = data.find(":m", 1)) != string::npos) {
+        data.erase(pos);
+      }
+      if ((pos = data.find(":M", 1)) != string::npos) {
+        data.erase(pos);
+      }
+    } else if ((pos = data.find(BSPWM_STATUS_PREFIX)) != string::npos) {
+      data = data.replace(pos, prefix_len, ":");
+    } else {
+      return false;
+    }
+
+    m_log.info("%s: Parsing socket data: %s", name(), data);
+
+    m_monitors.clear();
+
+    size_t workspace_n{0U};
+
+    for (auto&& tag : string_util::split(data, ':')) {
+      auto value = tag.substr(1);
+      auto mode_flag = mode::NONE;
+      unsigned int workspace_mask{0U};
+
+      if (tag[0] == 'm' || tag[0] == 'M') {
+        m_monitors.emplace_back(factory_util::unique<bspwm_monitor>());
+        m_monitors.back()->name = value;
+
+        if (m_monitorlabel) {
+          m_monitors.back()->label = m_monitorlabel->clone();
+          m_monitors.back()->label->replace_token("%name%", value);
+        }
+      }
+
+      switch (tag[0]) {
+        case 'm':
+          m_monitors.back()->focused = false;
+          break;
+        case 'M':
+          m_monitors.back()->focused = true;
+          break;
+        case 'F':
+          workspace_mask = make_mask(state::FOCUSED, state::EMPTY);
+          break;
+        case 'O':
+          workspace_mask = make_mask(state::FOCUSED, state::OCCUPIED);
+          break;
+        case 'U':
+          workspace_mask = make_mask(state::FOCUSED, state::URGENT);
+          break;
+        case 'f':
+          workspace_mask = make_mask(state::EMPTY);
+          break;
+        case 'o':
+          workspace_mask = make_mask(state::OCCUPIED);
+          break;
+        case 'u':
+          workspace_mask = make_mask(state::URGENT);
+          break;
+        case 'L':
+          switch (value[0]) {
+            case 0:
+              break;
+            case 'M':
+              mode_flag = mode::LAYOUT_MONOCLE;
+              break;
+            case 'T':
+              mode_flag = mode::LAYOUT_TILED;
+              break;
+            default:
+              m_log.warn("%s: Undefined L => '%s'", name(), value);
+          }
+          break;
+
+        case 'T':
+          switch (value[0]) {
+            case 0:
+              break;
+            case 'T':
+              break;
+            case '=':
+              mode_flag = mode::STATE_FULLSCREEN;
+              break;
+            case 'F':
+              mode_flag = mode::STATE_FLOATING;
+              break;
+            case 'P':
+              mode_flag = mode::STATE_PSEUDOTILED;
+              break;
+            default:
+              m_log.warn("%s: Undefined T => '%s'", name(), value);
+          }
+          break;
+
+        case 'G':
+          if (!m_monitors.back()->focused) {
+            break;
+          }
+
+          for (size_t i = 0U; i < value.length(); i++) {
+            switch (value[i]) {
+              case 0:
+                break;
+              case 'L':
+                mode_flag = mode::NODE_LOCKED;
+                break;
+              case 'S':
+                mode_flag = mode::NODE_STICKY;
+                break;
+              case 'P':
+                mode_flag = mode::NODE_PRIVATE;
+                break;
+              case 'M':
+                mode_flag = mode::NODE_MARKED;
+                break;
+              default:
+                m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1));
+            }
+
+            if (mode_flag != mode::NONE && !m_modelabels.empty()) {
+              m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
+            }
+          }
+          continue;
+
+        default:
+          m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1));
+          continue;
+      }
+
+      if (!m_monitors.back()) {
+        m_log.warn("%s: No monitor created", name());
+        continue;
+      }
+
+      if (workspace_mask && m_formatter->has(TAG_LABEL_STATE)) {
+        auto icon = m_icons->get(value, DEFAULT_ICON, m_fuzzy_match);
+        auto label = m_statelabels.at(workspace_mask)->clone();
+
+        if (!m_monitors.back()->focused) {
+          if (m_statelabels[make_mask(state::DIMMED)]) {
+            label->replace_defined_values(m_statelabels[make_mask(state::DIMMED)]);
+          }
+          if (workspace_mask & make_mask(state::EMPTY)) {
+            label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::EMPTY)]);
+          }
+          if (workspace_mask & make_mask(state::OCCUPIED)) {
+            label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::OCCUPIED)]);
+          }
+          if (workspace_mask & make_mask(state::FOCUSED)) {
+            label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::FOCUSED)]);
+          }
+          if (workspace_mask & make_mask(state::URGENT)) {
+            label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::URGENT)]);
+          }
+        }
+
+        label->reset_tokens();
+        label->replace_token("%name%", value);
+        label->replace_token("%icon%", icon->get());
+        label->replace_token("%index%", to_string(++workspace_n));
+
+        m_monitors.back()->workspaces.emplace_back(workspace_mask, move(label));
+      }
+
+      if (mode_flag != mode::NONE && !m_modelabels.empty()) {
+        m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
+      }
+    }
+
+    return true;
+  }
+
+  string bspwm_module::get_output() {
+    string output;
+    for (m_index = 0U; m_index < m_monitors.size(); m_index++) {
+      if (m_index > 0) {
+        m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
+      }
+      output += this->event_module::get_output();
+    }
+    return output;
+  }
+
+  bool bspwm_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_MONITOR) {
+      builder->node(m_monitors[m_index]->label);
+      return true;
+    } else if (tag == TAG_LABEL_STATE && !m_monitors[m_index]->workspaces.empty()) {
+      size_t workspace_n{0U};
+
+      if (m_scroll) {
+        builder->action(mousebtn::SCROLL_DOWN, *this, m_revscroll ? EVENT_NEXT : EVENT_PREV, "");
+        builder->action(mousebtn::SCROLL_UP, *this, m_revscroll ? EVENT_PREV : EVENT_NEXT, "");
+      }
+
+      for (auto&& ws : m_monitors[m_index]->workspaces) {
+        if (ws.second.get()) {
+          if (workspace_n != 0 && *m_labelseparator) {
+            builder->node(m_labelseparator);
+          }
+
+          workspace_n++;
+
+          if (m_click) {
+            builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, sstream() << m_index << "+" << workspace_n, ws.second);
+          } else {
+            builder->node(ws.second);
+          }
+
+          if (m_inlinemode && m_monitors[m_index]->focused && check_mask(ws.first, bspwm_state::FOCUSED)) {
+            for (auto&& mode : m_monitors[m_index]->modes) {
+              builder->node(mode);
+            }
+          }
+        }
+      }
+
+      if (m_scroll) {
+        builder->action_close();
+        builder->action_close();
+      }
+
+      return workspace_n > 0;
+    } else if (tag == TAG_LABEL_MODE && !m_inlinemode && m_monitors[m_index]->focused &&
+               !m_monitors[m_index]->modes.empty()) {
+      int modes_n = 0;
+
+      for (auto&& mode : m_monitors[m_index]->modes) {
+        if (mode && *mode) {
+          builder->node(mode);
+          modes_n++;
+        }
+      }
+
+      return modes_n > 0;
+    }
+
+    return false;
+  }
+
+  bool bspwm_module::input(const string& action, const string& data) {
+    auto send_command = [this](string payload_cmd, string log_info) {
+      try {
+        auto ipc = bspwm_util::make_connection();
+        auto payload = bspwm_util::make_payload(payload_cmd);
+        m_log.info("%s: %s", name(), log_info);
+        ipc->send(payload->data, payload->len, 0);
+        ipc->disconnect();
+      } catch (const system_error& err) {
+        m_log.err("%s: %s", name(), err.what());
+      }
+    };
+
+    if (action == EVENT_FOCUS) {
+      size_t separator{string_util::find_nth(data, 0, "+", 1)};
+      size_t monitor_n{std::strtoul(data.substr(0, separator).c_str(), nullptr, 10)};
+      string workspace_n{data.substr(separator + 1)};
+
+      if (monitor_n < m_monitors.size()) {
+        send_command("desktop -f " + m_monitors[monitor_n]->name + ":^" + workspace_n,
+            "Sending desktop focus command to ipc handler");
+      } else {
+        m_log.err("%s: Invalid monitor index in command: %s", name(), data);
+      }
+
+      return true;
+    }
+
+    string scrolldir;
+
+    if (action == EVENT_NEXT) {
+      scrolldir = "next";
+    } else if (action == EVENT_PREV) {
+      scrolldir = "prev";
+    } else {
+      return false;
+    }
+
+    string modifier;
+
+    if (m_pinworkspaces) {
+      modifier = ".local";
+      for (const auto& mon : m_monitors) {
+        if (m_bar.monitor->match(mon->name, false) && !mon->focused) {
+          send_command("monitor -f " + mon->name, "Sending monitor focus command to ipc handler");
+          break;
+        }
+      }
+    }
+
+    send_command("desktop -f " + scrolldir + modifier, "Sending desktop " + scrolldir + " command to ipc handler");
+
+    return true;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/counter.cpp b/src/modules/counter.cpp
new file mode 100644 (file)
index 0000000..3abd5e4
--- /dev/null
@@ -0,0 +1,30 @@
+#include "modules/counter.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<counter_module>;
+
+  counter_module::counter_module(const bar_settings& bar, string name_)
+      : timer_module<counter_module>(bar, move(name_)) {
+    set_interval(1s);
+    m_formatter->add(DEFAULT_FORMAT, TAG_COUNTER, {TAG_COUNTER});
+  }
+
+  bool counter_module::update() {
+    m_counter++;
+    return true;
+  }
+
+  bool counter_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_COUNTER) {
+      builder->node(to_string(m_counter));
+      return true;
+    }
+    return false;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/cpu.cpp b/src/modules/cpu.cpp
new file mode 100644 (file)
index 0000000..3e3363d
--- /dev/null
@@ -0,0 +1,162 @@
+#include <fstream>
+#include <istream>
+
+#include "modules/cpu.hpp"
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/math.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<cpu_module>;
+
+  cpu_module::cpu_module(const bar_settings& bar, string name_) : timer_module<cpu_module>(bar, move(name_)) {
+    set_interval(1s);
+
+    m_ramp_padding = m_conf.get<decltype(m_ramp_padding)>(name(), "ramp-coreload-spacing", 1);
+
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_LOAD, TAG_RAMP_LOAD, TAG_RAMP_LOAD_PER_CORE});
+
+    // warmup cpu times
+    read_values();
+    read_values();
+
+    if (m_formatter->has(TAG_BAR_LOAD)) {
+      m_barload = load_progressbar(m_bar, m_conf, name(), TAG_BAR_LOAD);
+    }
+    if (m_formatter->has(TAG_RAMP_LOAD)) {
+      m_rampload = load_ramp(m_conf, name(), TAG_RAMP_LOAD);
+    }
+    if (m_formatter->has(TAG_RAMP_LOAD_PER_CORE)) {
+      m_rampload_core = load_ramp(m_conf, name(), TAG_RAMP_LOAD_PER_CORE);
+    }
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage%%");
+    }
+  }
+
+  bool cpu_module::update() {
+    if (!read_values()) {
+      return false;
+    }
+
+    m_total = 0.0f;
+    m_load.clear();
+
+    auto cores_n = m_cputimes.size();
+    if (!cores_n) {
+      return false;
+    }
+
+    vector<string> percentage_cores;
+    for (size_t i = 0; i < cores_n; i++) {
+      auto load = get_load(i);
+      m_total += load;
+      m_load.emplace_back(load);
+
+      if (m_label) {
+        percentage_cores.emplace_back(to_string(static_cast<int>(load + 0.5)));
+      }
+    }
+
+    m_total = m_total / static_cast<float>(cores_n);
+
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%percentage%", to_string(static_cast<int>(m_total + 0.5)));
+      m_label->replace_token("%percentage-sum%", to_string(static_cast<int>(m_total * static_cast<float>(cores_n) + 0.5)));
+      m_label->replace_token("%percentage-cores%", string_util::join(percentage_cores, "% ") + "%");
+
+      for (size_t i = 0; i < percentage_cores.size(); i++) {
+        m_label->replace_token("%percentage-core" + to_string(i + 1) + "%", percentage_cores[i]);
+      }
+    }
+
+    return true;
+  }
+
+  bool cpu_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL) {
+      builder->node(m_label);
+    } else if (tag == TAG_BAR_LOAD) {
+      builder->node(m_barload->output(m_total));
+    } else if (tag == TAG_RAMP_LOAD) {
+      builder->node(m_rampload->get_by_percentage(m_total));
+    } else if (tag == TAG_RAMP_LOAD_PER_CORE) {
+      auto i = 0;
+      for (auto&& load : m_load) {
+        if (i++ > 0) {
+          builder->space(m_ramp_padding);
+        }
+        builder->node(m_rampload_core->get_by_percentage(load));
+      }
+      builder->node(builder->flush());
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool cpu_module::read_values() {
+    m_cputimes_prev.swap(m_cputimes);
+    m_cputimes.clear();
+
+    try {
+      std::ifstream in(PATH_CPU_INFO);
+      string str;
+
+      while (std::getline(in, str) && str.compare(0, 3, "cpu") == 0) {
+        // skip line with accumulated value
+        if (str.compare(0, 4, "cpu ") == 0) {
+          continue;
+        }
+
+        auto values = string_util::split(str, ' ');
+
+        m_cputimes.emplace_back(new cpu_time);
+        m_cputimes.back()->user = std::stoull(values[1], nullptr, 10);
+        m_cputimes.back()->nice = std::stoull(values[2], nullptr, 10);
+        m_cputimes.back()->system = std::stoull(values[3], nullptr, 10);
+        m_cputimes.back()->idle = std::stoull(values[4], nullptr, 10);
+        m_cputimes.back()->steal = std::stoull(values[8], nullptr, 10);
+        m_cputimes.back()->total = m_cputimes.back()->user + m_cputimes.back()->nice + m_cputimes.back()->system +
+                                   m_cputimes.back()->idle + m_cputimes.back()->steal;
+      }
+    } catch (const std::ios_base::failure& e) {
+      m_log.err("Failed to read CPU values (what: %s)", e.what());
+    }
+
+    return !m_cputimes.empty();
+  }
+
+  float cpu_module::get_load(size_t core) const {
+    if (m_cputimes.empty() || m_cputimes_prev.empty()) {
+      return 0;
+    } else if (core >= m_cputimes.size() || core >= m_cputimes_prev.size()) {
+      return 0;
+    }
+
+    auto& last = m_cputimes[core];
+    auto& prev = m_cputimes_prev[core];
+
+    auto last_idle = last->idle;
+    auto prev_idle = prev->idle;
+
+    auto diff = last->total - prev->total;
+
+    if (diff == 0) {
+      return 0;
+    }
+
+    float percentage = 100.0f * (diff - (last_idle - prev_idle)) / diff;
+
+    return math_util::cap<float>(percentage, 0, 100);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/date.cpp b/src/modules/date.cpp
new file mode 100644 (file)
index 0000000..5bb4b6b
--- /dev/null
@@ -0,0 +1,96 @@
+#include "modules/date.hpp"
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<date_module>;
+
+  date_module::date_module(const bar_settings& bar, string name_) : timer_module<date_module>(bar, move(name_)) {
+    if (!m_bar.locale.empty()) {
+      datetime_stream.imbue(std::locale(m_bar.locale.c_str()));
+    }
+
+    m_dateformat = m_conf.get(name(), "date", ""s);
+    m_dateformat_alt = m_conf.get(name(), "date-alt", ""s);
+    m_timeformat = m_conf.get(name(), "time", ""s);
+    m_timeformat_alt = m_conf.get(name(), "time-alt", ""s);
+
+    if (m_dateformat.empty() && m_timeformat.empty()) {
+      throw module_error("No date or time format specified");
+    }
+
+    set_interval(1s);
+
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_DATE});
+
+    if (m_formatter->has(TAG_DATE)) {
+      m_log.warn("%s: The format tag `<date>` is deprecated, use `<label>` instead.", name());
+
+      m_formatter->get(DEFAULT_FORMAT)->value =
+          string_util::replace_all(m_formatter->get(DEFAULT_FORMAT)->value, TAG_DATE, TAG_LABEL);
+    }
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), "label", "%date%");
+    }
+  }
+
+  bool date_module::update() {
+    auto time = std::time(nullptr);
+
+    auto date_format = m_toggled ? m_dateformat_alt : m_dateformat;
+    // Clear stream contents
+    datetime_stream.str("");
+    datetime_stream << std::put_time(localtime(&time), date_format.c_str());
+    auto date_string = datetime_stream.str();
+
+    auto time_format = m_toggled ? m_timeformat_alt : m_timeformat;
+    // Clear stream contents
+    datetime_stream.str("");
+    datetime_stream << std::put_time(localtime(&time), time_format.c_str());
+    auto time_string = datetime_stream.str();
+
+    if (m_date == date_string && m_time == time_string) {
+      return false;
+    }
+
+    m_date = date_string;
+    m_time = time_string;
+
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%date%", m_date);
+      m_label->replace_token("%time%", m_time);
+    }
+
+    return true;
+  }
+
+  bool date_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL) {
+      if (!m_dateformat_alt.empty() || !m_timeformat_alt.empty()) {
+        builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "", m_label);
+      } else {
+        builder->node(m_label);
+      }
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+
+  bool date_module::input(const string& action, const string&) {
+    if (action != EVENT_TOGGLE) {
+      return false;
+    }
+    m_toggled = !m_toggled;
+    wakeup();
+    return true;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/fs.cpp b/src/modules/fs.cpp
new file mode 100644 (file)
index 0000000..ed1cc67
--- /dev/null
@@ -0,0 +1,185 @@
+#include <sys/statvfs.h>
+#include <fstream>
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/fs.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "utils/string.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+// Columns in /proc/self/mountinfo
+#define MOUNTINFO_DIR 4
+#define MOUNTINFO_TYPE 7
+#define MOUNTINFO_FSNAME 8
+
+namespace modules {
+  template class module<fs_module>;
+
+  /**
+   * Bootstrap the module by reading config values and
+   * setting up required components
+   */
+  fs_module::fs_module(const bar_settings& bar, string name_) : timer_module<fs_module>(bar, move(name_)) {
+    m_mountpoints = m_conf.get_list(name(), "mount");
+    m_remove_unmounted = m_conf.get(name(), "remove-unmounted", m_remove_unmounted);
+    m_fixed = m_conf.get(name(), "fixed-values", m_fixed);
+    m_spacing = m_conf.get(name(), "spacing", m_spacing);
+    set_interval(30s);
+
+    // Add formats and elements
+    m_formatter->add(
+        FORMAT_MOUNTED, TAG_LABEL_MOUNTED, {TAG_LABEL_MOUNTED, TAG_BAR_FREE, TAG_BAR_USED, TAG_RAMP_CAPACITY});
+    m_formatter->add(FORMAT_UNMOUNTED, TAG_LABEL_UNMOUNTED, {TAG_LABEL_UNMOUNTED});
+
+    if (m_formatter->has(TAG_LABEL_MOUNTED)) {
+      m_labelmounted = load_optional_label(m_conf, name(), TAG_LABEL_MOUNTED, "%mountpoint% %percentage_free%%");
+    }
+    if (m_formatter->has(TAG_LABEL_UNMOUNTED)) {
+      m_labelunmounted = load_optional_label(m_conf, name(), TAG_LABEL_UNMOUNTED, "%mountpoint% is not mounted");
+    }
+    if (m_formatter->has(TAG_BAR_FREE)) {
+      m_barfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE);
+    }
+    if (m_formatter->has(TAG_BAR_USED)) {
+      m_barused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED);
+    }
+    if (m_formatter->has(TAG_RAMP_CAPACITY)) {
+      m_rampcapacity = load_ramp(m_conf, name(), TAG_RAMP_CAPACITY);
+    }
+
+    // Warn about "unreachable" format tag
+    if (m_formatter->has(TAG_LABEL_UNMOUNTED) && m_remove_unmounted) {
+      m_log.warn("%s: Defined format tag \"%s\" will never be used (reason: `remove-unmounted = true`)", name(),
+          string{TAG_LABEL_UNMOUNTED});
+    }
+  }
+
+  /**
+   * Update mountpoints
+   */
+  bool fs_module::update() {
+    m_mounts.clear();
+
+    vector<vector<string>> mountinfo;
+    std::ifstream filestream("/proc/self/mountinfo");
+    string line;
+
+    // Get details for mounted filesystems
+    while (std::getline(filestream, line)) {
+      auto cols = string_util::split(line, ' ');
+      if (std::find(m_mountpoints.begin(), m_mountpoints.end(), cols[MOUNTINFO_DIR]) != m_mountpoints.end()) {
+        mountinfo.emplace_back(move(cols));
+      }
+    }
+
+    // Get data for defined mountpoints
+    for (auto&& mountpoint : m_mountpoints) {
+      auto details = std::find_if(mountinfo.begin(), mountinfo.end(),
+          [&](const vector<string>& m) { return m.size() > 4 && m[4] == mountpoint; });
+
+      m_mounts.emplace_back(new fs_mount{mountpoint, details != mountinfo.end()});
+      struct statvfs buffer {};
+
+      if (!m_mounts.back()->mounted) {
+        m_log.warn("%s: Mountpoint %s is not mounted", name(), mountpoint);
+      } else if (statvfs(mountpoint.c_str(), &buffer) == -1) {
+        m_log.err("%s: Failed to query filesystem (statvfs() error: %s)", name(), strerror(errno));
+      } else {
+        auto& mount = m_mounts.back();
+        mount->mountpoint = details->at(MOUNTINFO_DIR);
+        mount->type = details->at(MOUNTINFO_TYPE);
+        mount->fsname = details->at(MOUNTINFO_FSNAME);
+
+        // see: https://en.cppreference.com/w/cpp/filesystem/space
+        mount->bytes_total = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_blocks);
+        mount->bytes_free = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_bfree);
+        mount->bytes_used = mount->bytes_total - mount->bytes_free;
+        mount->bytes_avail = static_cast<uint64_t>(buffer.f_frsize) * static_cast<uint64_t>(buffer.f_bavail);
+
+        mount->percentage_free = math_util::percentage<double>(mount->bytes_avail, mount->bytes_used + mount->bytes_avail);
+        mount->percentage_used = math_util::percentage<double>(mount->bytes_used, mount->bytes_used + mount->bytes_avail);
+      }
+    }
+
+    if (m_remove_unmounted) {
+      for (auto&& mount : m_mounts) {
+        if (!mount->mounted) {
+          m_log.info("%s: Removing mountpoint \"%s\" (reason: `remove-unmounted = true`)", name(), mount->mountpoint);
+          m_mountpoints.erase(
+              std::remove(m_mountpoints.begin(), m_mountpoints.end(), mount->mountpoint), m_mountpoints.end());
+          m_mounts.erase(std::remove(m_mounts.begin(), m_mounts.end(), mount), m_mounts.end());
+        }
+      }
+    }
+
+    return true;
+  }
+
+  /**
+   * Generate the module output
+   */
+  string fs_module::get_output() {
+    string output;
+
+    for (m_index = 0_z; m_index < m_mounts.size(); ++m_index) {
+      if (!output.empty()) {
+        m_builder->space(m_spacing);
+      }
+      output += timer_module::get_output();
+    }
+
+    return output;
+  }
+
+  /**
+   * Select format based on fs state
+   */
+  string fs_module::get_format() const {
+    return m_mounts[m_index]->mounted ? FORMAT_MOUNTED : FORMAT_UNMOUNTED;
+  }
+
+  /**
+   * Output content using configured format tags
+   */
+  bool fs_module::build(builder* builder, const string& tag) const {
+    auto& mount = m_mounts[m_index];
+
+    if (tag == TAG_BAR_FREE) {
+      builder->node(m_barfree->output(mount->percentage_free));
+    } else if (tag == TAG_BAR_USED) {
+      builder->node(m_barused->output(mount->percentage_used));
+    } else if (tag == TAG_RAMP_CAPACITY) {
+      builder->node(m_rampcapacity->get_by_percentage(mount->percentage_free));
+    } else if (tag == TAG_LABEL_MOUNTED) {
+      m_labelmounted->reset_tokens();
+      m_labelmounted->replace_token("%mountpoint%", mount->mountpoint);
+      m_labelmounted->replace_token("%type%", mount->type);
+      m_labelmounted->replace_token("%fsname%", mount->fsname);
+      m_labelmounted->replace_token("%percentage_free%", to_string(mount->percentage_free));
+      m_labelmounted->replace_token("%percentage_used%", to_string(mount->percentage_used));
+      m_labelmounted->replace_token(
+          "%total%", string_util::filesize(mount->bytes_total, m_fixed ? 2 : 0, m_fixed, m_bar.locale));
+      m_labelmounted->replace_token(
+          "%free%", string_util::filesize(mount->bytes_avail, m_fixed ? 2 : 0, m_fixed, m_bar.locale));
+      m_labelmounted->replace_token(
+          "%used%", string_util::filesize(mount->bytes_used, m_fixed ? 2 : 0, m_fixed, m_bar.locale));
+      builder->node(m_labelmounted);
+    } else if (tag == TAG_LABEL_UNMOUNTED) {
+      m_labelunmounted->reset_tokens();
+      m_labelunmounted->replace_token("%mountpoint%", mount->mountpoint);
+      builder->node(m_labelunmounted);
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/github.cpp b/src/modules/github.cpp
new file mode 100644 (file)
index 0000000..f40b15a
--- /dev/null
@@ -0,0 +1,135 @@
+#include "modules/github.hpp"
+
+#include <cassert>
+
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/concurrency.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<github_module>;
+
+  /**
+   * Construct module
+   */
+  github_module::github_module(const bar_settings& bar, string name_)
+      : timer_module<github_module>(bar, move(name_)), m_http(http_util::make_downloader()) {
+    m_accesstoken = m_conf.get(name(), "token");
+    m_user = m_conf.get(name(), "user", ""s);
+    m_api_url = m_conf.get(name(), "api-url", "https://api.github.com/"s);
+    if (m_api_url.back() != '/') {
+      m_api_url += '/';
+    }
+
+    set_interval(60s);
+    m_empty_notifications = m_conf.get(name(), "empty-notifications", m_empty_notifications);
+
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), TAG_LABEL, "Notifications: %notifications%");
+    }
+
+    m_formatter->add(FORMAT_OFFLINE, TAG_LABEL_OFFLINE, {TAG_LABEL_OFFLINE});
+
+    if (m_formatter->has(TAG_LABEL_OFFLINE)) {
+      m_label_offline = load_optional_label(m_conf, name(), TAG_LABEL_OFFLINE, "Offline");
+    }
+
+    update_label(0);
+  }
+
+  /**
+   * Update module contents
+   */
+  bool github_module::update() {
+    auto notification = get_number_of_notification();
+
+    update_label(notification);
+
+    return true;
+  }
+
+  string github_module::request() {
+    string content;
+    if (m_user.empty()) {
+      content = m_http->get(m_api_url + "notifications?access_token=" + m_accesstoken);
+    } else {
+      content = m_http->get(m_api_url + "notifications", m_user, m_accesstoken);
+    }
+
+    long response_code{m_http->response_code()};
+    switch (response_code) {
+      case 200:
+        break;
+      case 401:
+        throw module_error("Bad credentials");
+      case 403:
+        throw module_error("Maximum number of login attempts exceeded");
+      default:
+        throw module_error("Unspecified error (" + to_string(response_code) + ")");
+    }
+
+    return content;
+  }
+
+  int github_module::get_number_of_notification() {
+    string content;
+    try {
+      content = request();
+    } catch (application_error& e) {
+      if (!m_offline) {
+        m_log.info("%s: cannot complete the request to github: %s", name(), e.what());
+      }
+      m_offline = true;
+      return -1;
+    }
+
+    m_offline = false;
+
+    size_t pos{0};
+    size_t notifications{0};
+
+    while ((pos = content.find("\"unread\":true", pos + 1)) != string::npos) {
+      notifications++;
+    }
+
+    return notifications;
+  }
+
+  string github_module::get_format() const {
+    return m_offline ? FORMAT_OFFLINE : DEFAULT_FORMAT;
+  }
+
+  void github_module::update_label(const int notifications) {
+    if (!m_label) {
+      return;
+    }
+
+    if (0 != notifications || m_empty_notifications) {
+      m_label->reset_tokens();
+      m_label->replace_token("%notifications%", to_string(notifications));
+    } else {
+      m_label->clear();
+    }
+  }
+
+  /**
+   * Build module content
+   */
+  bool github_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL) {
+      builder->node(m_label);
+      return true;
+    } else if (tag == TAG_LABEL_OFFLINE) {
+      builder->node(m_label_offline);
+      return true;
+    }
+
+    return false;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/i3.cpp b/src/modules/i3.cpp
new file mode 100644 (file)
index 0000000..43d175c
--- /dev/null
@@ -0,0 +1,272 @@
+#include "modules/i3.hpp"
+
+#include <sys/socket.h>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/factory.hpp"
+#include "utils/file.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<i3_module>;
+
+  i3_module::i3_module(const bar_settings& bar, string name_) : event_module<i3_module>(bar, move(name_)) {
+    auto socket_path = i3ipc::get_socketpath();
+
+    if (!file_util::exists(socket_path)) {
+      throw module_error("Could not find socket: " + (socket_path.empty() ? "<empty>" : socket_path));
+    }
+
+    m_ipc = factory_util::unique<i3ipc::connection>();
+
+    // Load configuration values
+    m_click = m_conf.get(name(), "enable-click", m_click);
+    m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+    m_revscroll = m_conf.get(name(), "reverse-scroll", m_revscroll);
+    m_wrap = m_conf.get(name(), "wrapping-scroll", m_wrap);
+    m_indexsort = m_conf.get(name(), "index-sort", m_indexsort);
+    m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
+    m_strip_wsnumbers = m_conf.get(name(), "strip-wsnumbers", m_strip_wsnumbers);
+    m_fuzzy_match = m_conf.get(name(), "fuzzy-match", m_fuzzy_match);
+
+    m_conf.warn_deprecated(name(), "wsname-maxlen", "%name:min:max%");
+
+    // Add formats and create components
+    m_formatter->add(DEFAULT_FORMAT, DEFAULT_TAGS, {TAG_LABEL_STATE, TAG_LABEL_MODE});
+
+    if (m_formatter->has(TAG_LABEL_STATE)) {
+      m_statelabels.insert(
+          make_pair(state::FOCUSED, load_optional_label(m_conf, name(), "label-focused", DEFAULT_WS_LABEL)));
+      m_statelabels.insert(
+          make_pair(state::UNFOCUSED, load_optional_label(m_conf, name(), "label-unfocused", DEFAULT_WS_LABEL)));
+      m_statelabels.insert(
+          make_pair(state::VISIBLE, load_optional_label(m_conf, name(), "label-visible", DEFAULT_WS_LABEL)));
+      m_statelabels.insert(
+          make_pair(state::URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL)));
+    }
+
+    if (m_formatter->has(TAG_LABEL_MODE)) {
+      m_modelabel = load_optional_label(m_conf, name(), "label-mode", "%mode%");
+    }
+
+    m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
+
+    m_icons = factory_util::shared<iconset>();
+    m_icons->add(DEFAULT_WS_ICON, factory_util::shared<label>(m_conf.get(name(), DEFAULT_WS_ICON, ""s)));
+
+    for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
+      auto vec = string_util::tokenize(workspace, ';');
+      if (vec.size() == 2) {
+        m_icons->add(vec[0], factory_util::shared<label>(vec[1]));
+      }
+    }
+
+    try {
+      if (m_modelabel) {
+        m_ipc->on_mode_event = [this](const i3ipc::mode_t& mode) {
+          m_modeactive = (mode.change != DEFAULT_MODE);
+          if (m_modeactive) {
+            m_modelabel->reset_tokens();
+            m_modelabel->replace_token("%mode%", mode.change);
+          }
+        };
+      }
+      m_ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_MODE);
+    } catch (const exception& err) {
+      throw module_error(err.what());
+    }
+  }
+
+  i3_module::workspace::operator bool() {
+    return label && *label;
+  }
+
+  void i3_module::stop() {
+    try {
+      if (m_ipc) {
+        m_log.info("%s: Disconnecting from socket", name());
+        shutdown(m_ipc->get_event_socket_fd(), SHUT_RDWR);
+        shutdown(m_ipc->get_main_socket_fd(), SHUT_RDWR);
+      }
+    } catch (...) {
+    }
+
+    event_module::stop();
+  }
+
+  bool i3_module::has_event() {
+    try {
+      m_ipc->handle_event();
+      return true;
+    } catch (const exception& err) {
+      try {
+        m_log.warn("%s: Attempting to reconnect socket (reason: %s)", name(), err.what());
+        m_ipc->connect_event_socket(true);
+        m_log.info("%s: Reconnecting socket succeeded", name());
+      } catch (const exception& err) {
+        m_log.err("%s: Failed to reconnect socket (reason: %s)", name(), err.what());
+      }
+      return false;
+    }
+  }
+
+  bool i3_module::update() {
+    /*
+     * update only populates m_workspaces and those are only needed when
+     * <label-state> appears in the format
+     */
+    if (!m_formatter->has(TAG_LABEL_STATE)) {
+      return true;
+    }
+    m_workspaces.clear();
+    i3_util::connection_t ipc;
+
+    try {
+      vector<shared_ptr<i3_util::workspace_t>> workspaces;
+
+      if (m_pinworkspaces) {
+        workspaces = i3_util::workspaces(ipc, m_bar.monitor->name);
+      } else {
+        workspaces = i3_util::workspaces(ipc);
+      }
+
+      if (m_indexsort) {
+        sort(workspaces.begin(), workspaces.end(), i3_util::ws_numsort);
+      }
+
+      for (auto&& ws : workspaces) {
+        state ws_state{state::NONE};
+
+        if (ws->focused) {
+          ws_state = state::FOCUSED;
+        } else if (ws->urgent) {
+          ws_state = state::URGENT;
+        } else if (ws->visible) {
+          ws_state = state::VISIBLE;
+        } else {
+          ws_state = state::UNFOCUSED;
+        }
+
+        string ws_name{ws->name};
+
+        // Remove workspace numbers "0:"
+        if (m_strip_wsnumbers) {
+          ws_name.erase(0, string_util::find_nth(ws_name, 0, ":", 1) + 1);
+        }
+
+        // Trim leading and trailing whitespace
+        ws_name = string_util::trim(move(ws_name), ' ');
+
+        auto icon = m_icons->get(ws->name, DEFAULT_WS_ICON, m_fuzzy_match);
+        auto label = m_statelabels.find(ws_state)->second->clone();
+
+        label->reset_tokens();
+        label->replace_token("%output%", ws->output);
+        label->replace_token("%name%", ws_name);
+        label->replace_token("%icon%", icon->get());
+        label->replace_token("%index%", to_string(ws->num));
+        m_workspaces.emplace_back(factory_util::unique<workspace>(ws->name, ws_state, move(label)));
+      }
+
+      return true;
+    } catch (const exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+      return false;
+    }
+  }
+
+  bool i3_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_MODE && m_modeactive) {
+      builder->node(m_modelabel);
+    } else if (tag == TAG_LABEL_STATE && !m_workspaces.empty()) {
+      if (m_scroll) {
+        builder->action(mousebtn::SCROLL_DOWN, *this, m_revscroll ? EVENT_NEXT : EVENT_PREV, "");
+        builder->action(mousebtn::SCROLL_UP, *this, m_revscroll ? EVENT_PREV : EVENT_NEXT, "");
+      }
+
+      bool first = true;
+      for (auto&& ws : m_workspaces) {
+        /*
+         * The separator should only be inserted in between the workspaces, so
+         * we insert it in front of all workspaces except the first one.
+         */
+        if (first) {
+          first = false;
+        } else if (*m_labelseparator) {
+          builder->node(m_labelseparator);
+        }
+
+        if (m_click) {
+          builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, ws->name, ws->label);
+        } else {
+          builder->node(ws->label);
+        }
+      }
+
+      if (m_scroll) {
+        builder->action_close();
+        builder->action_close();
+      }
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+
+  bool i3_module::input(const string& action, const string& data) {
+    try {
+      const i3_util::connection_t conn{};
+
+      if (action == EVENT_FOCUS) {
+        m_log.info("%s: Sending workspace focus command to ipc handler", name());
+        conn.send_command(make_workspace_command(data));
+        return true;
+      }
+
+      if (action != EVENT_NEXT && action != EVENT_PREV) {
+        return false;
+      }
+
+      bool next = action == EVENT_NEXT;
+
+      auto workspaces = i3_util::workspaces(conn, m_bar.monitor->name);
+      auto current_ws = std::find_if(workspaces.begin(), workspaces.end(), [](auto ws) { return ws->visible; });
+
+      if (current_ws == workspaces.end()) {
+        m_log.warn("%s: Current workspace not found", name());
+        return false;
+      }
+
+      if (next && (m_wrap || std::next(current_ws) != workspaces.end())) {
+        if (!(*current_ws)->focused) {
+          m_log.info("%s: Sending workspace focus command to ipc handler", name());
+          conn.send_command(make_workspace_command((*current_ws)->name));
+        }
+        m_log.info("%s: Sending workspace next_on_output command to ipc handler", name());
+        conn.send_command("workspace next_on_output");
+      } else if (!next && (m_wrap || current_ws != workspaces.begin())) {
+        if (!(*current_ws)->focused) {
+          m_log.info("%s: Sending workspace focus command to ipc handler", name());
+          conn.send_command(make_workspace_command((*current_ws)->name));
+        }
+        m_log.info("%s: Sending workspace prev_on_output command to ipc handler", name());
+        conn.send_command("workspace prev_on_output");
+      }
+
+    } catch (const exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+    }
+
+    return true;
+  }
+
+  string i3_module::make_workspace_command(const string& workspace) {
+    return "workspace \"" + workspace + "\"";
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/ipc.cpp b/src/modules/ipc.cpp
new file mode 100644 (file)
index 0000000..c808276
--- /dev/null
@@ -0,0 +1,130 @@
+#include "modules/ipc.hpp"
+
+#include "components/ipc.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<ipc_module>;
+
+  /**
+   * Load user-defined ipc hooks and
+   * create formatting tags
+   */
+  ipc_module::ipc_module(const bar_settings& bar, string name_) : static_module<ipc_module>(bar, move(name_)) {
+    size_t index = 0;
+
+    for (auto&& command : m_conf.get_list<string>(name(), "hook")) {
+      m_hooks.emplace_back(new hook{name() + to_string(++index), command});
+    }
+
+    if (m_hooks.empty()) {
+      throw module_error("No hooks defined");
+    }
+
+    if ((m_initial = m_conf.get(name(), "initial", 0_z)) && m_initial > m_hooks.size()) {
+      throw module_error("Initial hook out of bounds (defined: " + to_string(m_hooks.size()) + ")");
+    }
+
+    // clang-format off
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::LEFT, m_conf.get(name(), "click-left", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::MIDDLE, m_conf.get(name(), "click-middle", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::RIGHT, m_conf.get(name(), "click-right", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::SCROLL_UP, m_conf.get(name(), "scroll-up", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::SCROLL_DOWN, m_conf.get(name(), "scroll-down", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::DOUBLE_LEFT, m_conf.get(name(), "double-click-left", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::DOUBLE_MIDDLE, m_conf.get(name(), "double-click-middle", ""s)));
+    m_actions.emplace(make_pair<mousebtn, string>(mousebtn::DOUBLE_RIGHT, m_conf.get(name(), "double-click-right", ""s)));
+    // clang-format on
+
+    const auto pid_token = [](string& s) {
+      string::size_type p = s.find("%pid%");
+      if (p != string::npos) {
+        s.replace(p, 5, to_string(getpid()));
+      }
+    };
+
+    for (auto& action : m_actions) {
+      pid_token(action.second);
+    }
+    for (auto& hook : m_hooks) {
+      pid_token(hook->command);
+    }
+
+    m_formatter->add(DEFAULT_FORMAT, TAG_OUTPUT, {TAG_OUTPUT});
+  }
+
+  /**
+   * Start module and run first defined hook if configured to
+   */
+  void ipc_module::start() {
+    if (m_initial) {
+      auto command = command_util::make_command<output_policy::REDIRECTED>(m_hooks.at(m_initial - 1)->command);
+      command->exec(false);
+      command->tail([this](string line) { m_output = line; });
+    }
+    static_module::start();
+  }
+
+  /**
+   * Wrap the output with defined mouse actions
+   */
+  string ipc_module::get_output() {
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapper
+    // with the cmd handlers
+    string output{module::get_output()};
+
+    for (auto&& action : m_actions) {
+      if (!action.second.empty()) {
+        m_builder->action(action.first, action.second);
+      }
+    }
+
+    m_builder->append(output);
+    return m_builder->flush();
+  }
+
+  /**
+   * Output content retrieved from hook commands
+   */
+  bool ipc_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_OUTPUT) {
+      builder->node(m_output);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Map received message hook to the ones
+   * configured from the user config and
+   * execute its command
+   */
+  void ipc_module::on_message(const string& message) {
+    for (auto&& hook : m_hooks) {
+      if (hook->payload != message) {
+        continue;
+      }
+
+      m_log.info("%s: Found matching hook (%s)", name(), hook->payload);
+
+      try {
+        // Clear the output in case the command produces no output
+        m_output.clear();
+        auto command = command_util::make_command<output_policy::REDIRECTED>(hook->command);
+        command->exec(false);
+        command->tail([this](string line) { m_output = line; });
+      } catch (const exception& err) {
+        m_log.err("%s: Failed to execute hook command (err: %s)", err.what());
+        m_output.clear();
+      }
+
+      broadcast();
+    }
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/memory.cpp b/src/modules/memory.cpp
new file mode 100644 (file)
index 0000000..5882da0
--- /dev/null
@@ -0,0 +1,148 @@
+#include <fstream>
+#include <iomanip>
+#include <istream>
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/memory.hpp"
+#include "utils/math.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<memory_module>;
+
+  memory_module::memory_module(const bar_settings& bar, string name_) : timer_module<memory_module>(bar, move(name_)) {
+    set_interval(1s);
+
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR_USED, TAG_BAR_FREE, TAG_RAMP_USED, TAG_RAMP_FREE,
+                                                 TAG_BAR_SWAP_USED, TAG_BAR_SWAP_FREE, TAG_RAMP_SWAP_USED, TAG_RAMP_SWAP_FREE});
+
+    if (m_formatter->has(TAG_BAR_USED)) {
+      m_bar_memused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_USED);
+    }
+    if (m_formatter->has(TAG_BAR_FREE)) {
+      m_bar_memfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_FREE);
+    }
+    if(m_formatter->has(TAG_RAMP_USED)) {
+      m_ramp_memused = load_ramp(m_conf, name(), TAG_RAMP_USED);
+    }
+    if(m_formatter->has(TAG_RAMP_FREE)) {
+      m_ramp_memfree = load_ramp(m_conf, name(), TAG_RAMP_FREE);
+    }
+    if (m_formatter->has(TAG_BAR_SWAP_USED)) {
+      m_bar_swapused = load_progressbar(m_bar, m_conf, name(), TAG_BAR_SWAP_USED);
+    }
+    if (m_formatter->has(TAG_BAR_SWAP_FREE)) {
+      m_bar_swapfree = load_progressbar(m_bar, m_conf, name(), TAG_BAR_SWAP_FREE);
+    }
+    if(m_formatter->has(TAG_RAMP_SWAP_USED)) {
+      m_ramp_swapused = load_ramp(m_conf, name(), TAG_RAMP_SWAP_USED);
+    }
+    if(m_formatter->has(TAG_RAMP_SWAP_FREE)) {
+      m_ramp_swapfree = load_ramp(m_conf, name(), TAG_RAMP_SWAP_FREE);
+    }
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage_used%%");
+    }
+  }
+
+  bool memory_module::update() {
+    unsigned long long kb_total{0ULL};
+    unsigned long long kb_avail{0ULL};
+    unsigned long long kb_swap_total{0ULL};
+    unsigned long long kb_swap_free{0ULL};
+
+    try {
+      std::ifstream meminfo(PATH_MEMORY_INFO);
+      std::map<std::string, unsigned long long int> parsed;
+
+      std::string line;
+      while (std::getline(meminfo, line)) {
+        size_t sep_off = line.find(':');
+        size_t value_off = line.find_first_of("123456789", sep_off);
+
+        if (sep_off == std::string::npos || value_off == std::string::npos) continue;
+
+        std::string id = line.substr(0, sep_off);
+        unsigned long long int value = std::strtoull(&line[value_off], nullptr, 10);
+        parsed[id] = value;
+      }
+
+      kb_total = parsed["MemTotal"];
+      kb_swap_total = parsed["SwapTotal"];
+      kb_swap_free = parsed["SwapFree"];
+
+      // newer kernels (3.4+) have an accurate available memory field,
+      // see https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
+      // for details
+      if (parsed.count("MemAvailable")) {
+        kb_avail = parsed["MemAvailable"];
+      } else {
+        // old kernel; give a best-effort approximation of available memory
+        kb_avail = parsed["MemFree"] + parsed["Buffers"] + parsed["Cached"] + parsed["SReclaimable"] - parsed["Shmem"];
+      }
+    } catch (const std::exception& err) {
+      m_log.err("Failed to read memory values (what: %s)", err.what());
+    }
+
+    m_perc_memfree = math_util::percentage(kb_avail, kb_total);
+    m_perc_memused = 100 - m_perc_memfree;
+    m_perc_swap_free = math_util::percentage(kb_swap_free, kb_swap_total);
+    m_perc_swap_used = 100 - m_perc_swap_free;
+
+    // replace tokens
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%gb_used%", string_util::filesize_gib(kb_total - kb_avail, 2, m_bar.locale));
+      m_label->replace_token("%gb_free%", string_util::filesize_gib(kb_avail, 2, m_bar.locale));
+      m_label->replace_token("%gb_total%", string_util::filesize_gib(kb_total, 2, m_bar.locale));
+      m_label->replace_token("%mb_used%", string_util::filesize_mib(kb_total - kb_avail, 0, m_bar.locale));
+      m_label->replace_token("%mb_free%", string_util::filesize_mib(kb_avail, 0, m_bar.locale));
+      m_label->replace_token("%mb_total%", string_util::filesize_mib(kb_total, 0, m_bar.locale));
+      m_label->replace_token("%percentage_used%", to_string(m_perc_memused));
+      m_label->replace_token("%percentage_free%", to_string(m_perc_memfree));
+      m_label->replace_token("%percentage_swap_used%", to_string(m_perc_swap_used));
+      m_label->replace_token("%percentage_swap_free%", to_string(m_perc_swap_free));
+      m_label->replace_token("%mb_swap_total%", string_util::filesize_mib(kb_swap_total, 0, m_bar.locale));
+      m_label->replace_token("%mb_swap_free%", string_util::filesize_mib(kb_swap_free, 0, m_bar.locale));
+      m_label->replace_token("%mb_swap_used%", string_util::filesize_mib(kb_swap_total - kb_swap_free, 0, m_bar.locale));
+      m_label->replace_token("%gb_swap_total%", string_util::filesize_gib(kb_swap_total, 2, m_bar.locale));
+      m_label->replace_token("%gb_swap_free%", string_util::filesize_gib(kb_swap_free, 2, m_bar.locale));
+      m_label->replace_token("%gb_swap_used%", string_util::filesize_gib(kb_swap_total - kb_swap_free, 2, m_bar.locale));
+    }
+
+    return true;
+  }
+
+  bool memory_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_BAR_USED) {
+      builder->node(m_bar_memused->output(m_perc_memused));
+    } else if (tag == TAG_BAR_FREE) {
+      builder->node(m_bar_memfree->output(m_perc_memfree));
+    } else if (tag == TAG_LABEL) {
+      builder->node(m_label);
+    } else if (tag == TAG_RAMP_FREE) {
+      builder->node(m_ramp_memfree->get_by_percentage(m_perc_memfree));
+    } else if (tag == TAG_RAMP_USED) {
+      builder->node(m_ramp_memused->get_by_percentage(m_perc_memused));
+    } else if (tag == TAG_BAR_SWAP_USED) {
+      builder->node(m_bar_swapused->output(m_perc_swap_used));
+    } else if (tag == TAG_BAR_SWAP_FREE) {
+      builder->node(m_bar_swapfree->output(m_perc_swap_free));
+    } else if (tag == TAG_RAMP_SWAP_FREE) {
+      builder->node(m_ramp_swapfree->get_by_percentage(m_perc_swap_free));
+    } else if (tag == TAG_RAMP_SWAP_USED) {
+      builder->node(m_ramp_swapused->get_by_percentage(m_perc_swap_used));
+    } else {
+      return false;
+    }
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/menu.cpp b/src/modules/menu.cpp
new file mode 100644 (file)
index 0000000..714bc46
--- /dev/null
@@ -0,0 +1,158 @@
+#include "modules/menu.hpp"
+
+#include "drawtypes/label.hpp"
+#include "events/signal.hpp"
+#include "modules/meta/base.inl"
+#include "utils/actions.hpp"
+#include "utils/factory.hpp"
+#include "utils/scope.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<menu_module>;
+
+  menu_module::menu_module(const bar_settings& bar, string name_) : static_module<menu_module>(bar, move(name_)) {
+    m_expand_right = m_conf.get(name(), "expand-right", m_expand_right);
+
+    string default_format;
+    if(m_expand_right) {
+      default_format += TAG_LABEL_TOGGLE;
+      default_format += TAG_MENU;
+    } else {
+      default_format += TAG_MENU;
+      default_format += TAG_LABEL_TOGGLE;
+    }
+
+
+    m_formatter->add(DEFAULT_FORMAT, default_format, {TAG_LABEL_TOGGLE, TAG_MENU});
+
+    if (m_formatter->has(TAG_LABEL_TOGGLE)) {
+      m_labelopen = load_label(m_conf, name(), "label-open");
+      m_labelclose = load_optional_label(m_conf, name(), "label-close", "x");
+    }
+
+    m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
+
+    if (!m_formatter->has(TAG_MENU)) {
+      return;
+    }
+
+    while (true) {
+      string level_param{"menu-" + to_string(m_levels.size())};
+
+      if (m_conf.get(name(), level_param + "-0", ""s).empty()) {
+        break;
+      }
+
+      m_log.trace("%s: Creating menu level %i", name(), m_levels.size());
+      m_levels.emplace_back(factory_util::unique<menu_tree>());
+
+      while (true) {
+        string item_param{level_param + "-" + to_string(m_levels.back()->items.size())};
+
+        if (m_conf.get(name(), item_param, ""s).empty()) {
+          break;
+        }
+
+        m_log.trace("%s: Creating menu level item %i", name(), m_levels.back()->items.size());
+        auto item = factory_util::unique<menu_tree_item>();
+        item->label = load_label(m_conf, name(), item_param);
+        item->exec = m_conf.get(name(), item_param + "-exec", actions_util::get_action_string(*this, EVENT_CLOSE, ""));
+        m_levels.back()->items.emplace_back(move(item));
+      }
+    }
+  }
+
+  bool menu_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_TOGGLE && m_level == -1) {
+      builder->action(mousebtn::LEFT, *this, string(EVENT_OPEN), "0", m_labelopen);
+    } else if (tag == TAG_LABEL_TOGGLE && m_level > -1) {
+      builder->action(mousebtn::LEFT, *this, EVENT_CLOSE, "", m_labelclose);
+    } else if (tag == TAG_MENU && m_level > -1) {
+      auto spacing = m_formatter->get(get_format())->spacing;
+      //Insert separator after menu-toggle and before menu-items for expand-right=true
+      if (m_expand_right && *m_labelseparator) {
+        builder->node(m_labelseparator);
+        builder->space(spacing);
+      }
+      auto&& items = m_levels[m_level]->items;
+      for (size_t i = 0; i < items.size(); i++) {
+        auto&& item = items[i];
+        builder->action(
+            mousebtn::LEFT, *this, string(EVENT_EXEC), to_string(m_level) + "-" + to_string(i), item->label);
+        if (item != m_levels[m_level]->items.back()) {
+          builder->space(spacing);
+          if (*m_labelseparator) {
+            builder->node(m_labelseparator);
+            builder->space(spacing);
+          }
+        //Insert separator after last menu-item and before menu-toggle for expand-right=false
+        } else if (!m_expand_right && *m_labelseparator) {
+          builder->space(spacing);
+          builder->node(m_labelseparator);
+        }
+      }
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool menu_module::input(const string& action, const string& data) {
+    if (action == EVENT_EXEC) {
+      auto sep = data.find("-");
+
+      if (sep == data.npos) {
+        m_log.err("%s: Malformed data for exec action (data: '%s')", name(), data);
+        return false;
+      }
+
+      auto level = std::strtoul(data.substr(0, sep).c_str(), nullptr, 10);
+      auto item = std::strtoul(data.substr(sep + 1).c_str(), nullptr, 10);
+
+      if (level >= m_levels.size() || item >= m_levels[level]->items.size()) {
+        m_log.err("%s: menu-exec-%d-%d doesn't exist (data: '%s')", name(), level, item, data);
+        return false;
+      }
+
+      string exec = m_levels[level]->items[item]->exec;
+      // Send exec action to be executed
+      m_sig.emit(signals::ipc::action{std::move(exec)});
+
+      /*
+       * Only close the menu if the executed action is visible in the menu
+       * This stops the menu from closing, if the exec action comes from an
+       * external source
+       */
+      if (m_level == (int) level) {
+        m_level = -1;
+        broadcast();
+      }
+
+    } else if (action == EVENT_OPEN) {
+      auto level = data;
+
+      if (level.empty()) {
+        level = "0";
+      }
+      m_level = std::strtol(level.c_str(), nullptr, 10);
+      m_log.info("%s: Opening menu level '%i'", name(), static_cast<int>(m_level));
+
+      if (static_cast<size_t>(m_level) >= m_levels.size()) {
+        m_log.warn("%s: Cannot open unexisting menu level '%s'", name(), level);
+        m_level = -1;
+      }
+    } else if (action == EVENT_CLOSE) {
+      m_log.info("%s: Closing menu tree", name());
+      m_level = -1;
+    } else {
+      return false;
+    }
+
+    broadcast();
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/meta/base.cpp b/src/modules/meta/base.cpp
new file mode 100644 (file)
index 0000000..1164b45
--- /dev/null
@@ -0,0 +1,172 @@
+#include "modules/meta/base.hpp"
+
+#include <utility>
+
+#include "components/builder.hpp"
+#include "drawtypes/label.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  // module_format {{{
+
+  string module_format::decorate(builder* builder, string output) {
+    if (output.empty()) {
+      builder->flush();
+      return "";
+    }
+    if (offset != 0) {
+      builder->offset(offset);
+    }
+    if (margin > 0) {
+      builder->space(margin);
+    }
+    if (bg.has_color()) {
+      builder->background(bg);
+    }
+    if (fg.has_color()) {
+      builder->color(fg);
+    }
+    if (ul.has_color()) {
+      builder->underline(ul);
+    }
+    if (ol.has_color()) {
+      builder->overline(ol);
+    }
+    if (font > 0) {
+      builder->font(font);
+    }
+    if (padding > 0) {
+      builder->space(padding);
+    }
+
+    builder->node(prefix);
+
+    if (bg.has_color()) {
+      builder->background(bg);
+    }
+    if (fg.has_color()) {
+      builder->color(fg);
+    }
+    if (ul.has_color()) {
+      builder->underline(ul);
+    }
+    if (ol.has_color()) {
+      builder->overline(ol);
+    }
+
+    builder->append(move(output));
+    builder->node(suffix);
+
+    if (padding > 0) {
+      builder->space(padding);
+    }
+    if (font > 0) {
+      builder->font_close();
+    }
+    if (ol.has_color()) {
+      builder->overline_close();
+    }
+    if (ul.has_color()) {
+      builder->underline_close();
+    }
+    if (fg.has_color()) {
+      builder->color_close();
+    }
+    if (bg.has_color()) {
+      builder->background_close();
+    }
+    if (margin > 0) {
+      builder->space(margin);
+    }
+
+    return builder->flush();
+  }
+
+  // }}}
+  // module_formatter {{{
+
+  void module_formatter::add(string name, string fallback, vector<string>&& tags, vector<string>&& whitelist) {
+    const auto formatdef = [&](const string& param, const auto& fallback) {
+      return m_conf.get("settings", "format-" + param, fallback);
+    };
+
+    auto format = make_unique<module_format>();
+    format->value = m_conf.get(m_modname, name, move(fallback));
+    format->fg = m_conf.get(m_modname, name + "-foreground", formatdef("foreground", format->fg));
+    format->bg = m_conf.get(m_modname, name + "-background", formatdef("background", format->bg));
+    format->ul = m_conf.get(m_modname, name + "-underline", formatdef("underline", format->ul));
+    format->ol = m_conf.get(m_modname, name + "-overline", formatdef("overline", format->ol));
+    format->ulsize = m_conf.get(m_modname, name + "-underline-size", formatdef("underline-size", format->ulsize));
+    format->olsize = m_conf.get(m_modname, name + "-overline-size", formatdef("overline-size", format->olsize));
+    format->spacing = m_conf.get(m_modname, name + "-spacing", formatdef("spacing", format->spacing));
+    format->padding = m_conf.get(m_modname, name + "-padding", formatdef("padding", format->padding));
+    format->margin = m_conf.get(m_modname, name + "-margin", formatdef("margin", format->margin));
+    format->offset = m_conf.get(m_modname, name + "-offset", formatdef("offset", format->offset));
+    format->font = m_conf.get(m_modname, name + "-font", formatdef("font", format->font));
+    format->tags.swap(tags);
+
+    try {
+      format->prefix = load_label(m_conf, m_modname, name + "-prefix");
+    } catch (const key_error& err) {
+      // prefix not defined
+    }
+
+    try {
+      format->suffix = load_label(m_conf, m_modname, name + "-suffix");
+    } catch (const key_error& err) {
+      // suffix not defined
+    }
+
+    vector<string> tag_collection;
+    tag_collection.reserve(format->tags.size() + whitelist.size());
+    tag_collection.insert(tag_collection.end(), format->tags.begin(), format->tags.end());
+    tag_collection.insert(tag_collection.end(), whitelist.begin(), whitelist.end());
+
+    size_t start, end;
+    string value{format->value};
+    while ((start = value.find('<')) != string::npos && (end = value.find('>', start)) != string::npos) {
+      if (start > 0) {
+        value.erase(0, start);
+        end -= start;
+        start = 0;
+      }
+      string tag{value.substr(start, end + 1)};
+      if (find(tag_collection.begin(), tag_collection.end(), tag) == tag_collection.end()) {
+        throw undefined_format_tag(tag + " is not a valid format tag for \"" + name + "\"");
+      }
+      value.erase(0, tag.size());
+    }
+
+    m_formats.insert(make_pair(move(name), move(format)));
+  }
+
+  bool module_formatter::has(const string& tag, const string& format_name) {
+    auto format = m_formats.find(format_name);
+    if (format == m_formats.end()) {
+      throw undefined_format(format_name);
+    }
+    return format->second->value.find(tag) != string::npos;
+  }
+
+  bool module_formatter::has(const string& tag) {
+    for (auto&& format : m_formats) {
+      if (format.second->value.find(tag) != string::npos) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  shared_ptr<module_format> module_formatter::get(const string& format_name) {
+    auto format = m_formats.find(format_name);
+    if (format == m_formats.end()) {
+      throw undefined_format("Format \"" + format_name + "\" has not been added");
+    }
+    return format->second;
+  }
+
+  // }}}
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/mpd.cpp b/src/modules/mpd.cpp
new file mode 100644 (file)
index 0000000..ea867f2
--- /dev/null
@@ -0,0 +1,408 @@
+#include "modules/mpd.hpp"
+
+#include <csignal>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "modules/meta/base.inl"
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<mpd_module>;
+
+  mpd_module::mpd_module(const bar_settings& bar, string name_) : event_module<mpd_module>(bar, move(name_)) {
+    m_host = m_conf.get(name(), "host", m_host);
+    m_port = m_conf.get(name(), "port", m_port);
+    m_pass = m_conf.get(name(), "password", m_pass);
+    m_synctime = m_conf.get(name(), "interval", m_synctime);
+
+    // Add formats and elements {{{
+    auto format_online = m_conf.get<string>(name(), FORMAT_ONLINE, TAG_LABEL_SONG);
+    for (auto&& format : {FORMAT_PLAYING, FORMAT_PAUSED, FORMAT_STOPPED}) {
+      m_formatter->add(format, format_online,
+          {TAG_BAR_PROGRESS, TAG_TOGGLE, TAG_TOGGLE_STOP, TAG_LABEL_SONG, TAG_LABEL_TIME, TAG_ICON_RANDOM,
+              TAG_ICON_REPEAT, TAG_ICON_REPEAT_ONE, TAG_ICON_SINGLE, TAG_ICON_PREV, TAG_ICON_STOP, TAG_ICON_PLAY,
+              TAG_ICON_PAUSE, TAG_ICON_NEXT, TAG_ICON_SEEKB, TAG_ICON_SEEKF, TAG_ICON_CONSUME});
+
+      auto mod_format = m_formatter->get(format);
+
+      mod_format->fg = m_conf.get(name(), FORMAT_ONLINE + "-foreground"s, mod_format->fg);
+      mod_format->bg = m_conf.get(name(), FORMAT_ONLINE + "-background"s, mod_format->bg);
+      mod_format->ul = m_conf.get(name(), FORMAT_ONLINE + "-underline"s, mod_format->ul);
+      mod_format->ol = m_conf.get(name(), FORMAT_ONLINE + "-overline"s, mod_format->ol);
+      mod_format->ulsize = m_conf.get(name(), FORMAT_ONLINE + "-underline-size"s, mod_format->ulsize);
+      mod_format->olsize = m_conf.get(name(), FORMAT_ONLINE + "-overline-size"s, mod_format->olsize);
+      mod_format->spacing = m_conf.get(name(), FORMAT_ONLINE + "-spacing"s, mod_format->spacing);
+      mod_format->padding = m_conf.get(name(), FORMAT_ONLINE + "-padding"s, mod_format->padding);
+      mod_format->margin = m_conf.get(name(), FORMAT_ONLINE + "-margin"s, mod_format->margin);
+      mod_format->offset = m_conf.get(name(), FORMAT_ONLINE + "-offset"s, mod_format->offset);
+      mod_format->font = m_conf.get(name(), FORMAT_ONLINE + "-font"s, mod_format->font);
+
+      try {
+        mod_format->prefix = load_label(m_conf, name(), FORMAT_ONLINE + "-prefix"s);
+      } catch (const key_error& err) {
+        // format-online-prefix not defined
+      }
+
+      try {
+        mod_format->suffix = load_label(m_conf, name(), FORMAT_ONLINE + "-suffix"s);
+      } catch (const key_error& err) {
+        // format-online-suffix not defined
+      }
+    }
+
+    m_formatter->add(FORMAT_OFFLINE, "", {TAG_LABEL_OFFLINE});
+
+    m_icons = factory_util::shared<iconset>();
+
+    if (m_formatter->has(TAG_ICON_PLAY) || m_formatter->has(TAG_TOGGLE) || m_formatter->has(TAG_TOGGLE_STOP)) {
+      m_icons->add("play", load_label(m_conf, name(), TAG_ICON_PLAY));
+    }
+    if (m_formatter->has(TAG_ICON_PAUSE) || m_formatter->has(TAG_TOGGLE)) {
+      m_icons->add("pause", load_label(m_conf, name(), TAG_ICON_PAUSE));
+    }
+    if (m_formatter->has(TAG_ICON_STOP) || m_formatter->has(TAG_TOGGLE_STOP)) {
+      m_icons->add("stop", load_label(m_conf, name(), TAG_ICON_STOP));
+    }
+    if (m_formatter->has(TAG_ICON_PREV)) {
+      m_icons->add("prev", load_label(m_conf, name(), TAG_ICON_PREV));
+    }
+    if (m_formatter->has(TAG_ICON_NEXT)) {
+      m_icons->add("next", load_label(m_conf, name(), TAG_ICON_NEXT));
+    }
+    if (m_formatter->has(TAG_ICON_SEEKB)) {
+      m_icons->add("seekb", load_label(m_conf, name(), TAG_ICON_SEEKB));
+    }
+    if (m_formatter->has(TAG_ICON_SEEKF)) {
+      m_icons->add("seekf", load_label(m_conf, name(), TAG_ICON_SEEKF));
+    }
+    if (m_formatter->has(TAG_ICON_RANDOM)) {
+      m_icons->add("random", load_label(m_conf, name(), TAG_ICON_RANDOM));
+    }
+    if (m_formatter->has(TAG_ICON_REPEAT)) {
+      m_icons->add("repeat", load_label(m_conf, name(), TAG_ICON_REPEAT));
+    }
+
+    if (m_formatter->has(TAG_ICON_SINGLE)) {
+      m_icons->add("single", load_label(m_conf, name(), TAG_ICON_SINGLE));
+    } else if (m_formatter->has(TAG_ICON_REPEAT_ONE)) {
+      m_conf.warn_deprecated(name(), "icon-repeatone", "icon-single");
+
+      m_icons->add("single", load_label(m_conf, name(), TAG_ICON_REPEAT_ONE));
+    }
+
+    if (m_formatter->has(TAG_ICON_CONSUME)) {
+      m_icons->add("consume", load_label(m_conf, name(), TAG_ICON_CONSUME));
+    }
+
+    if (m_formatter->has(TAG_LABEL_SONG)) {
+      m_label_song = load_optional_label(m_conf, name(), TAG_LABEL_SONG, "%artist% - %title%");
+    }
+    if (m_formatter->has(TAG_LABEL_TIME)) {
+      m_label_time = load_optional_label(m_conf, name(), TAG_LABEL_TIME, "%elapsed% / %total%");
+    }
+    if (m_formatter->has(TAG_ICON_RANDOM) || m_formatter->has(TAG_ICON_REPEAT) ||
+        m_formatter->has(TAG_ICON_REPEAT_ONE) || m_formatter->has(TAG_ICON_SINGLE) ||
+        m_formatter->has(TAG_ICON_CONSUME)) {
+      m_toggle_on_color = m_conf.get(name(), "toggle-on-foreground", rgba{});
+      m_toggle_off_color = m_conf.get(name(), "toggle-off-foreground", rgba{});
+    }
+    if (m_formatter->has(TAG_LABEL_OFFLINE, FORMAT_OFFLINE)) {
+      m_label_offline = load_label(m_conf, name(), TAG_LABEL_OFFLINE);
+    }
+    if (m_formatter->has(TAG_BAR_PROGRESS)) {
+      m_bar_progress = load_progressbar(m_bar, m_conf, name(), TAG_BAR_PROGRESS);
+    }
+
+    // }}}
+
+    m_lastsync = chrono::system_clock::now();
+
+    try {
+      m_mpd = factory_util::unique<mpdconnection>(m_log, m_host, m_port, m_pass);
+      m_mpd->connect();
+      m_status = m_mpd->get_status();
+    } catch (const mpd_exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+      m_mpd.reset();
+    }
+  }
+
+  void mpd_module::teardown() {
+    m_mpd.reset();
+  }
+
+  inline bool mpd_module::connected() const {
+    return m_mpd && m_mpd->connected();
+  }
+
+  void mpd_module::idle() {
+    if (connected()) {
+      m_quick_attempts = 0;
+      sleep(80ms);
+    } else {
+      sleep(m_quick_attempts++ < 5 ? 0.5s : 2s);
+    }
+  }
+
+  bool mpd_module::has_event() {
+    bool def = false;
+
+    if (!connected() && m_statebroadcasted == mpd::connection_state::CONNECTED) {
+      def = true;
+    } else if (connected() && m_statebroadcasted == mpd::connection_state::DISCONNECTED) {
+      def = true;
+    }
+
+    try {
+      if (!m_mpd) {
+        m_mpd = factory_util::unique<mpdconnection>(m_log, m_host, m_port, m_pass);
+      }
+      if (!connected()) {
+        m_mpd->connect();
+      }
+    } catch (const mpd_exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+      m_mpd.reset();
+      return def;
+    }
+
+    if (!connected()) {
+      return def;
+    }
+
+    if (!m_status) {
+      m_status = m_mpd->get_status_safe();
+    }
+
+    try {
+      m_mpd->idle();
+
+      int idle_flags = 0;
+      if ((idle_flags = m_mpd->noidle()) != 0) {
+        // Update status on every event
+        m_status->update(idle_flags, m_mpd.get());
+        return true;
+      }
+    } catch (const mpd_exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+      m_mpd.reset();
+      return def;
+    }
+
+    if ((m_label_time || m_bar_progress) && m_status->match_state(mpdstate::PLAYING)) {
+      auto now = chrono::system_clock::now();
+      auto diff = now - m_lastsync;
+
+      if (chrono::duration_cast<chrono::milliseconds>(diff).count() > m_synctime * 1000) {
+        m_lastsync = now;
+        return true;
+      }
+    }
+
+    return def;
+  }
+
+  bool mpd_module::update() {
+    if (connected()) {
+      m_statebroadcasted = mpd::connection_state::CONNECTED;
+    } else if (!connected() && m_statebroadcasted != mpd::connection_state::DISCONNECTED) {
+      m_statebroadcasted = mpd::connection_state::DISCONNECTED;
+    } else if (!connected()) {
+      return false;
+    }
+
+    if (!m_status) {
+      if (connected() && (m_status = m_mpd->get_status_safe())) {
+        return false;
+      }
+    }
+
+    if (m_status && m_status->match_state(mpdstate::PLAYING)) {
+      // Always update the status while playing
+      m_status->update(-1, m_mpd.get());
+    }
+
+    string artist;
+    string album_artist;
+    string album;
+    string title;
+    string date;
+    string elapsed_str;
+    string total_str;
+
+    try {
+      if (m_status) {
+        elapsed_str = m_status->get_formatted_elapsed();
+        total_str = m_status->get_formatted_total();
+      }
+
+      if (m_mpd) {
+        auto song = m_mpd->get_song();
+
+        if (song && song.get()) {
+          artist = song->get_artist();
+          album_artist = song->get_album_artist();
+          album = song->get_album();
+          title = song->get_title();
+          date = song->get_date();
+        }
+      }
+    } catch (const mpd_exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+      m_mpd.reset();
+    }
+
+    if (m_label_song) {
+      m_label_song->reset_tokens();
+      m_label_song->replace_token("%artist%", !artist.empty() ? artist : "untitled artist");
+      m_label_song->replace_token("%album-artist%", !album_artist.empty() ? album_artist : "untitled album artist");
+      m_label_song->replace_token("%album%", !album.empty() ? album : "untitled album");
+      m_label_song->replace_token("%title%", !title.empty() ? title : "untitled track");
+      m_label_song->replace_token("%date%", !date.empty() ? date : "unknown date");
+    }
+
+    if (m_label_time) {
+      m_label_time->reset_tokens();
+      m_label_time->replace_token("%elapsed%", elapsed_str);
+      m_label_time->replace_token("%total%", total_str);
+    }
+
+    if (m_icons->has("random")) {
+      m_icons->get("random")->m_foreground = m_status && m_status->random() ? m_toggle_on_color : m_toggle_off_color;
+    }
+    if (m_icons->has("repeat")) {
+      m_icons->get("repeat")->m_foreground = m_status && m_status->repeat() ? m_toggle_on_color : m_toggle_off_color;
+    }
+    if (m_icons->has("single")) {
+      m_icons->get("single")->m_foreground = m_status && m_status->single() ? m_toggle_on_color : m_toggle_off_color;
+    }
+    if (m_icons->has("consume")) {
+      m_icons->get("consume")->m_foreground = m_status && m_status->consume() ? m_toggle_on_color : m_toggle_off_color;
+    }
+
+    return true;
+  }
+
+  string mpd_module::get_format() const {
+    if (!connected()) {
+      return FORMAT_OFFLINE;
+    } else if (m_status->match_state(mpdstate::PLAYING)) {
+      return FORMAT_PLAYING;
+    } else if (m_status->match_state(mpdstate::PAUSED)) {
+      return FORMAT_PAUSED;
+    } else {
+      return FORMAT_STOPPED;
+    }
+  }
+
+  string mpd_module::get_output() {
+    if (m_status && m_status->get_queuelen() == 0) {
+      m_log.info("%s: Hiding module since queue is empty", name());
+      return "";
+    } else {
+      return event_module::get_output();
+    }
+  }
+
+  bool mpd_module::build(builder* builder, const string& tag) const {
+    bool is_playing = m_status && m_status->match_state(mpdstate::PLAYING);
+    bool is_paused = m_status && m_status->match_state(mpdstate::PAUSED);
+    bool is_stopped = m_status && m_status->match_state(mpdstate::STOPPED);
+
+    if (tag == TAG_LABEL_SONG && !is_stopped) {
+      builder->node(m_label_song);
+    } else if (tag == TAG_LABEL_TIME && !is_stopped) {
+      builder->node(m_label_time);
+    } else if (tag == TAG_BAR_PROGRESS && !is_stopped) {
+      builder->node(m_bar_progress->output(!m_status ? 0 : m_status->get_elapsed_percentage()));
+    } else if (tag == TAG_LABEL_OFFLINE) {
+      builder->node(m_label_offline);
+    } else if (tag == TAG_ICON_RANDOM) {
+      builder->action(mousebtn::LEFT, *this, EVENT_RANDOM, "", m_icons->get("random"));
+    } else if (tag == TAG_ICON_REPEAT) {
+      builder->action(mousebtn::LEFT, *this, EVENT_REPEAT, "", m_icons->get("repeat"));
+    } else if (tag == TAG_ICON_REPEAT_ONE || tag == TAG_ICON_SINGLE) {
+      builder->action(mousebtn::LEFT, *this, EVENT_SINGLE, "", m_icons->get("single"));
+    } else if (tag == TAG_ICON_CONSUME) {
+      builder->action(mousebtn::LEFT, *this, EVENT_CONSUME, "", m_icons->get("consume"));
+    } else if (tag == TAG_ICON_PREV) {
+      builder->action(mousebtn::LEFT, *this, EVENT_PREV, "", m_icons->get("prev"));
+    } else if ((tag == TAG_ICON_STOP || tag == TAG_TOGGLE_STOP) && (is_playing || is_paused)) {
+      builder->action(mousebtn::LEFT, *this, EVENT_STOP, "", m_icons->get("stop"));
+    } else if ((tag == TAG_ICON_PAUSE || tag == TAG_TOGGLE) && is_playing) {
+      builder->action(mousebtn::LEFT, *this, EVENT_PAUSE, "", m_icons->get("pause"));
+    } else if ((tag == TAG_ICON_PLAY || tag == TAG_TOGGLE || tag == TAG_TOGGLE_STOP) && !is_playing) {
+      builder->action(mousebtn::LEFT, *this, EVENT_PLAY, "", m_icons->get("play"));
+    } else if (tag == TAG_ICON_NEXT) {
+      builder->action(mousebtn::LEFT, *this, EVENT_NEXT, "", m_icons->get("next"));
+    } else if (tag == TAG_ICON_SEEKB) {
+      builder->action(mousebtn::LEFT, *this, EVENT_SEEK, "-5"s, m_icons->get("seekb"));
+    } else if (tag == TAG_ICON_SEEKF) {
+      builder->action(mousebtn::LEFT, *this, EVENT_SEEK, "+5"s, m_icons->get("seekf"));
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+
+  bool mpd_module::input(const string& action, const string& data) {
+    m_log.info("%s: event: %s", name(), action);
+
+    try {
+      auto mpd = factory_util::unique<mpdconnection>(m_log, m_host, m_port, m_pass);
+      mpd->connect();
+
+      auto status = mpd->get_status();
+
+      bool is_playing = status->match_state(mpdstate::PLAYING);
+      bool is_paused = status->match_state(mpdstate::PAUSED);
+      bool is_stopped = status->match_state(mpdstate::STOPPED);
+
+      if (action == EVENT_PLAY && !is_playing) {
+        mpd->play();
+      } else if (action == EVENT_PAUSE && !is_paused) {
+        mpd->pause(true);
+      } else if (action == EVENT_STOP && !is_stopped) {
+        mpd->stop();
+      } else if (action == EVENT_PREV && !is_stopped) {
+        mpd->prev();
+      } else if (action == EVENT_NEXT && !is_stopped) {
+        mpd->next();
+      } else if (action == EVENT_SINGLE) {
+        mpd->set_single(!status->single());
+      } else if (action == EVENT_REPEAT) {
+        mpd->set_repeat(!status->repeat());
+      } else if (action == EVENT_RANDOM) {
+        mpd->set_random(!status->random());
+      } else if (action == EVENT_CONSUME) {
+        mpd->set_consume(!status->consume());
+      } else if (action == EVENT_SEEK) {
+        int percentage = 0;
+        if (data.empty()) {
+          return false;
+        } else if (data[0] == '+') {
+          percentage = status->get_elapsed_percentage() + std::strtol(data.substr(1).c_str(), nullptr, 10);
+        } else if (data[0] == '-') {
+          percentage = status->get_elapsed_percentage() - std::strtol(data.substr(1).c_str(), nullptr, 10);
+        } else {
+          percentage = std::strtol(data.c_str(), nullptr, 10);
+        }
+        mpd->seek(status->get_songid(), status->get_seek_position(percentage));
+      } else {
+        return false;
+      }
+    } catch (const mpd_exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+      m_mpd.reset();
+    }
+
+    return true;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/network.cpp b/src/modules/network.cpp
new file mode 100644 (file)
index 0000000..cf7d2bd
--- /dev/null
@@ -0,0 +1,193 @@
+#include "modules/network.hpp"
+
+#include "drawtypes/animation.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/factory.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<network_module>;
+
+  network_module::network_module(const bar_settings& bar, string name_)
+      : timer_module<network_module>(bar, move(name_)) {
+    // Load configuration values
+    m_interface = m_conf.get(name(), "interface", m_interface);
+    m_ping_nth_update = m_conf.get(name(), "ping-interval", m_ping_nth_update);
+    m_udspeed_minwidth = m_conf.get(name(), "udspeed-minwidth", m_udspeed_minwidth);
+    m_accumulate = m_conf.get(name(), "accumulate-stats", m_accumulate);
+    set_interval(1s);
+    m_unknown_up = m_conf.get<bool>(name(), "unknown-as-up", false);
+
+    m_conf.warn_deprecated(name(), "udspeed-minwidth", "%downspeed:min:max% and %upspeed:min:max%");
+
+    // Add formats
+    m_formatter->add(FORMAT_CONNECTED, TAG_LABEL_CONNECTED, {TAG_RAMP_SIGNAL, TAG_RAMP_QUALITY, TAG_LABEL_CONNECTED});
+    m_formatter->add(FORMAT_DISCONNECTED, TAG_LABEL_DISCONNECTED, {TAG_LABEL_DISCONNECTED});
+
+    // Create elements for format-connected
+    if (m_formatter->has(TAG_RAMP_SIGNAL, FORMAT_CONNECTED)) {
+      m_ramp_signal = load_ramp(m_conf, name(), TAG_RAMP_SIGNAL);
+    }
+    if (m_formatter->has(TAG_RAMP_QUALITY, FORMAT_CONNECTED)) {
+      m_ramp_quality = load_ramp(m_conf, name(), TAG_RAMP_QUALITY);
+    }
+    if (m_formatter->has(TAG_LABEL_CONNECTED, FORMAT_CONNECTED)) {
+      m_label[connection_state::CONNECTED] =
+          load_optional_label(m_conf, name(), TAG_LABEL_CONNECTED, "%ifname% %local_ip%");
+    }
+
+    // Create elements for format-disconnected
+    if (m_formatter->has(TAG_LABEL_DISCONNECTED, FORMAT_DISCONNECTED)) {
+      m_label[connection_state::DISCONNECTED] = load_optional_label(m_conf, name(), TAG_LABEL_DISCONNECTED, "");
+      m_label[connection_state::DISCONNECTED]->reset_tokens();
+      m_label[connection_state::DISCONNECTED]->replace_token("%ifname%", m_interface);
+    }
+
+    // Create elements for format-packetloss if we are told to test connectivity
+    if (m_ping_nth_update > 0) {
+      m_formatter->add(FORMAT_PACKETLOSS, TAG_LABEL_CONNECTED,
+          {TAG_ANIMATION_PACKETLOSS, TAG_LABEL_PACKETLOSS, TAG_LABEL_CONNECTED});
+
+      if (m_formatter->has(TAG_LABEL_PACKETLOSS, FORMAT_PACKETLOSS)) {
+        m_label[connection_state::PACKETLOSS] = load_optional_label(m_conf, name(), TAG_LABEL_PACKETLOSS, "");
+      }
+      if (m_formatter->has(TAG_ANIMATION_PACKETLOSS, FORMAT_PACKETLOSS)) {
+        m_animation_packetloss = load_animation(m_conf, name(), TAG_ANIMATION_PACKETLOSS);
+      }
+    }
+
+    // Get an intstance of the network interface
+    if (net::is_wireless_interface(m_interface)) {
+      m_wireless = factory_util::unique<net::wireless_network>(m_interface);
+      m_wireless->set_unknown_up(m_unknown_up);
+    } else {
+      m_wired = factory_util::unique<net::wired_network>(m_interface);
+      m_wired->set_unknown_up(m_unknown_up);
+    };
+
+    // We only need to start the subthread if the packetloss animation is used
+    if (m_animation_packetloss) {
+      m_threads.emplace_back(thread(&network_module::subthread_routine, this));
+    }
+  }
+
+  void network_module::teardown() {
+    m_wireless.reset();
+    m_wired.reset();
+  }
+
+  bool network_module::update() {
+    net::network* network =
+        m_wireless ? static_cast<net::network*>(m_wireless.get()) : static_cast<net::network*>(m_wired.get());
+
+    if (!network->query(m_accumulate)) {
+      m_log.warn("%s: Failed to query interface '%s'", name(), m_interface);
+      m_connected = false;
+      return false;
+    }
+
+    try {
+      if (m_wireless) {
+        m_signal = m_wireless->signal();
+        m_quality = m_wireless->quality();
+      }
+    } catch (const net::network_error& err) {
+      m_log.warn("%s: Error getting interface data (%s)", name(), err.what());
+    }
+
+    m_connected = network->connected();
+
+    // Ignore the first run
+    if (m_counter == -1) {
+      m_counter = 0;
+    } else if (m_ping_nth_update > 0 && m_connected && (++m_counter % m_ping_nth_update) == 0) {
+      m_packetloss = !network->ping();
+      m_counter = 0;
+    }
+
+    auto upspeed = network->upspeed(m_udspeed_minwidth);
+    auto downspeed = network->downspeed(m_udspeed_minwidth);
+
+    // Update label contents
+    const auto replace_tokens = [&](label_t& label) {
+      label->reset_tokens();
+      label->replace_token("%ifname%", m_interface);
+      label->replace_token("%local_ip%", network->ip());
+      label->replace_token("%local_ip6%", network->ip6());
+      label->replace_token("%upspeed%", upspeed);
+      label->replace_token("%downspeed%", downspeed);
+
+      if (m_wired) {
+        label->replace_token("%linkspeed%", m_wired->linkspeed());
+      } else if (m_wireless) {
+        label->replace_token("%essid%", m_wireless->essid());
+        label->replace_token("%signal%", to_string(m_signal));
+        label->replace_token("%quality%", to_string(m_quality));
+      }
+    };
+
+    if (m_label[connection_state::CONNECTED]) {
+      replace_tokens(m_label[connection_state::CONNECTED]);
+    }
+    if (m_label[connection_state::PACKETLOSS]) {
+      replace_tokens(m_label[connection_state::PACKETLOSS]);
+    }
+    if (m_label[connection_state::DISCONNECTED]) {
+      replace_tokens(m_label[connection_state::DISCONNECTED]);
+    }
+
+    return true;
+  }
+
+  string network_module::get_format() const {
+    if (!m_connected) {
+      return FORMAT_DISCONNECTED;
+    } else if (m_packetloss && m_ping_nth_update > 0) {
+      return FORMAT_PACKETLOSS;
+    } else {
+      return FORMAT_CONNECTED;
+    }
+  }
+
+  bool network_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_CONNECTED) {
+      builder->node(m_label.at(connection_state::CONNECTED));
+    } else if (tag == TAG_LABEL_DISCONNECTED) {
+      builder->node(m_label.at(connection_state::DISCONNECTED));
+    } else if (tag == TAG_LABEL_PACKETLOSS) {
+      builder->node(m_label.at(connection_state::PACKETLOSS));
+    } else if (tag == TAG_ANIMATION_PACKETLOSS) {
+      builder->node(m_animation_packetloss->get());
+    } else if (tag == TAG_RAMP_SIGNAL) {
+      builder->node(m_ramp_signal->get_by_percentage(m_signal));
+    } else if (tag == TAG_RAMP_QUALITY) {
+      builder->node(m_ramp_quality->get_by_percentage(m_quality));
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  void network_module::subthread_routine() {
+    const chrono::milliseconds framerate{m_animation_packetloss->framerate()};
+
+    while (running()) {
+      auto now = chrono::steady_clock::now();
+      if (m_connected && m_packetloss) {
+        m_animation_packetloss->increment();
+        broadcast();
+      }
+
+      now += framerate;
+      this_thread::sleep_until(now);
+    }
+
+    m_log.trace("%s: Reached end of network subthread", name());
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp
new file mode 100644 (file)
index 0000000..e99ca04
--- /dev/null
@@ -0,0 +1,171 @@
+#include "modules/pulseaudio.hpp"
+
+#include "adapters/pulseaudio.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "modules/meta/base.inl"
+#include "settings.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<pulseaudio_module>;
+
+  pulseaudio_module::pulseaudio_module(const bar_settings& bar, string name_)
+      : event_module<pulseaudio_module>(bar, move(name_)) {
+    // Load configuration values
+    m_interval = m_conf.get(name(), "interval", m_interval);
+
+    auto sink_name = m_conf.get(name(), "sink", ""s);
+    bool m_max_volume = m_conf.get(name(), "use-ui-max", true);
+
+    try {
+      m_pulseaudio = factory_util::unique<pulseaudio>(m_log, move(sink_name), m_max_volume);
+    } catch (const pulseaudio_error& err) {
+      throw module_error(err.what());
+    }
+
+    // Add formats and elements
+    m_formatter->add(FORMAT_VOLUME, TAG_LABEL_VOLUME, {TAG_RAMP_VOLUME, TAG_LABEL_VOLUME, TAG_BAR_VOLUME});
+    m_formatter->add(FORMAT_MUTED, TAG_LABEL_MUTED, {TAG_RAMP_VOLUME, TAG_LABEL_MUTED, TAG_BAR_VOLUME});
+
+    if (m_formatter->has(TAG_BAR_VOLUME)) {
+      m_bar_volume = load_progressbar(m_bar, m_conf, name(), TAG_BAR_VOLUME);
+    }
+    if (m_formatter->has(TAG_LABEL_VOLUME, FORMAT_VOLUME)) {
+      m_label_volume = load_optional_label(m_conf, name(), TAG_LABEL_VOLUME, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_LABEL_MUTED, FORMAT_MUTED)) {
+      m_label_muted = load_optional_label(m_conf, name(), TAG_LABEL_MUTED, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_RAMP_VOLUME)) {
+      m_ramp_volume = load_ramp(m_conf, name(), TAG_RAMP_VOLUME);
+    }
+  }
+
+  void pulseaudio_module::teardown() {
+    m_pulseaudio.reset();
+  }
+
+  bool pulseaudio_module::has_event() {
+    // Poll for mixer and control events
+    try {
+      if (m_pulseaudio->wait())
+        return true;
+    } catch (const pulseaudio_error& e) {
+      m_log.err("%s: %s", name(), e.what());
+    }
+    return false;
+  }
+
+  bool pulseaudio_module::update() {
+    // Consume pending events
+    m_pulseaudio->process_events();
+
+    // Get volume and mute state
+    m_volume = 100;
+    m_decibels = PA_DECIBEL_MININFTY;
+    m_muted = false;
+
+    try {
+      if (m_pulseaudio) {
+        m_volume = m_volume * m_pulseaudio->get_volume() / 100.0f;
+        m_decibels = m_pulseaudio->get_decibels();
+        m_muted = m_muted || m_pulseaudio->is_muted();
+      }
+    } catch (const pulseaudio_error& err) {
+      m_log.err("%s: Failed to query pulseaudio sink (%s)", name(), err.what());
+    }
+
+    // Replace label tokens
+    if (m_label_volume) {
+      m_label_volume->reset_tokens();
+      m_label_volume->replace_token("%percentage%", to_string(m_volume));
+      m_label_volume->replace_token("%decibels%", string_util::floating_point(m_decibels, 2, true));
+    }
+
+    if (m_label_muted) {
+      m_label_muted->reset_tokens();
+      m_label_muted->replace_token("%percentage%", to_string(m_volume));
+      m_label_muted->replace_token("%decibels%", string_util::floating_point(m_decibels, 2, true));
+    }
+
+    return true;
+  }
+
+  string pulseaudio_module::get_format() const {
+    return m_muted ? FORMAT_MUTED : FORMAT_VOLUME;
+  }
+
+  string pulseaudio_module::get_output() {
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapper
+    // with the cmd handlers
+    string output{module::get_output()};
+
+    if (m_handle_events) {
+      auto click_middle = m_conf.get(name(), "click-middle", ""s);
+      auto click_right = m_conf.get(name(), "click-right", ""s);
+
+      if (!click_middle.empty()) {
+        m_builder->action(mousebtn::MIDDLE, click_middle);
+      }
+
+      if (!click_right.empty()) {
+        m_builder->action(mousebtn::RIGHT, click_right);
+      }
+
+      m_builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "");
+      m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+      m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+    }
+
+    m_builder->append(output);
+
+    return m_builder->flush();
+  }
+
+  bool pulseaudio_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_BAR_VOLUME) {
+      builder->node(m_bar_volume->output(m_volume));
+    } else if (tag == TAG_RAMP_VOLUME) {
+      builder->node(m_ramp_volume->get_by_percentage(m_volume));
+    } else if (tag == TAG_LABEL_VOLUME) {
+      builder->node(m_label_volume);
+    } else if (tag == TAG_LABEL_MUTED) {
+      builder->node(m_label_muted);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  bool pulseaudio_module::input(const string& action, const string&) {
+    if (!m_handle_events) {
+      return false;
+    }
+
+    try {
+      if (m_pulseaudio && !m_pulseaudio->get_name().empty()) {
+        if (action == EVENT_TOGGLE) {
+          m_pulseaudio->toggle_mute();
+        } else if (action == EVENT_INC) {
+          // cap above 100 (~150)?
+          m_pulseaudio->inc_volume(m_interval);
+        } else if (action == EVENT_DEC) {
+          m_pulseaudio->inc_volume(-m_interval);
+        } else {
+          return false;
+        }
+      }
+    } catch (const exception& err) {
+      m_log.err("%s: Failed to handle command (%s)", name(), err.what());
+    }
+
+    return true;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/script.cpp b/src/modules/script.cpp
new file mode 100644 (file)
index 0000000..0fc51fb
--- /dev/null
@@ -0,0 +1,220 @@
+#include "modules/script.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<script_module>;
+
+  /**
+   * Construct script module by loading configuration values
+   * and setting up formatting objects
+   */
+  script_module::script_module(const bar_settings& bar, string name_)
+      : module<script_module>(bar, move(name_)), m_handler([&]() -> function<chrono::duration<double>()> {
+
+        m_tail = m_conf.get(name(), "tail", false);
+        // Handler for continuous tail commands {{{
+
+        if (m_tail) {
+          return [&] {
+            if (!m_command || !m_command->is_running()) {
+              string exec{string_util::replace_all(m_exec, "%counter%", to_string(++m_counter))};
+              m_log.info("%s: Invoking shell command: \"%s\"", name(), exec);
+              m_command = command_util::make_command<output_policy::REDIRECTED>(exec);
+
+              try {
+                m_command->exec(false);
+              } catch (const exception& err) {
+                m_log.err("%s: %s", name(), err.what());
+                throw module_error("Failed to execute command, stopping module...");
+              }
+            }
+
+            int fd = m_command->get_stdout(PIPE_READ);
+            while (!m_stopping && fd != -1 && m_command->is_running() && !io_util::poll(fd, POLLHUP, 0)) {
+              if (!io_util::poll_read(fd, 25)) {
+                continue;
+              } else if ((m_output = m_command->readline()) != m_prev) {
+                m_prev = m_output;
+                broadcast();
+              }
+            }
+
+            if (m_stopping) {
+              return chrono::duration<double>{0};
+            } else if (m_command && !m_command->is_running()) {
+              return std::max(m_command->get_exit_status() == 0 ? m_interval : 1s, m_interval);
+            } else {
+              return m_interval;
+            }
+          };
+        }
+
+        // }}}
+        // Handler for basic shell commands {{{
+
+        return [&] {
+          try {
+            auto exec = string_util::replace_all(m_exec, "%counter%", to_string(++m_counter));
+            m_log.info("%s: Invoking shell command: \"%s\"", name(), exec);
+            m_command = command_util::make_command<output_policy::REDIRECTED>(exec);
+            m_command->exec(true);
+          } catch (const exception& err) {
+            m_log.err("%s: %s", name(), err.what());
+            throw module_error("Failed to execute command, stopping module...");
+          }
+
+          int fd = m_command->get_stdout(PIPE_READ);
+          if (fd != -1 && io_util::poll_read(fd) && (m_output = m_command->readline()) != m_prev) {
+            broadcast();
+            m_prev = m_output;
+          } else if (m_command->get_exit_status() != 0) {
+            m_output.clear();
+            m_prev.clear();
+            broadcast();
+          }
+
+          return std::max(m_command->get_exit_status() == 0 ? m_interval : 1s, m_interval);
+        };
+
+        // }}}
+      }()) {
+    // Load configuration values
+    m_exec = m_conf.get(name(), "exec", m_exec);
+    m_exec_if = m_conf.get(name(), "exec-if", m_exec_if);
+    m_interval = m_conf.get<decltype(m_interval)>(name(), "interval", 5s);
+
+    // Load configured click handlers
+    m_actions[mousebtn::LEFT] = m_conf.get(name(), "click-left", ""s);
+    m_actions[mousebtn::MIDDLE] = m_conf.get(name(), "click-middle", ""s);
+    m_actions[mousebtn::RIGHT] = m_conf.get(name(), "click-right", ""s);
+    m_actions[mousebtn::DOUBLE_LEFT] = m_conf.get(name(), "double-click-left", ""s);
+    m_actions[mousebtn::DOUBLE_MIDDLE] = m_conf.get(name(), "double-click-middle", ""s);
+    m_actions[mousebtn::DOUBLE_RIGHT] = m_conf.get(name(), "double-click-right", ""s);
+    m_actions[mousebtn::SCROLL_UP] = m_conf.get(name(), "scroll-up", ""s);
+    m_actions[mousebtn::SCROLL_DOWN] = m_conf.get(name(), "scroll-down", ""s);
+
+    // Setup formatting
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), "label", "%output%");
+    }
+  }
+
+  /**
+   * Start the module worker
+   */
+  void script_module::start() {
+    m_mainthread = thread([&] {
+      try {
+        while (running() && !m_stopping) {
+          if (check_condition()) {
+            sleep(process(m_handler));
+          } else if (m_interval > 1s) {
+            sleep(m_interval);
+          } else {
+            sleep(1s);
+          }
+        }
+      } catch (const exception& err) {
+        halt(err.what());
+      }
+    });
+  }
+
+  /**
+   * Stop the module worker by terminating any running commands
+   */
+  void script_module::stop() {
+    m_stopping = true;
+    wakeup();
+
+    std::lock_guard<decltype(m_handler)> guard(m_handler);
+
+    m_command.reset();
+    module::stop();
+  }
+
+  /**
+   * Check if defined condition is met
+   */
+  bool script_module::check_condition() {
+    if (m_exec_if.empty()) {
+      return true;
+    } else if (command_util::make_command<output_policy::IGNORED>(m_exec_if)->exec(true) == 0) {
+      return true;
+    } else if (!m_output.empty()) {
+      broadcast();
+      m_output.clear();
+      m_prev.clear();
+    }
+    return false;
+  }
+
+  /**
+   * Process mutex wrapped script handler
+   */
+  chrono::duration<double> script_module::process(const decltype(m_handler) & handler) const {
+    std::lock_guard<decltype(handler)> guard(handler);
+    return handler();
+  }
+
+  /**
+   * Generate module output
+   */
+  string script_module::get_output() {
+    if (m_output.empty()) {
+      return "";
+    }
+
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%output%", m_output);
+    }
+
+    string cnt{to_string(m_counter)};
+    string output{module::get_output()};
+
+    for (auto btn : {mousebtn::LEFT, mousebtn::MIDDLE, mousebtn::RIGHT,
+                     mousebtn::DOUBLE_LEFT, mousebtn::DOUBLE_MIDDLE,
+                     mousebtn::DOUBLE_RIGHT, mousebtn::SCROLL_UP,
+                     mousebtn::SCROLL_DOWN}) {
+
+      auto action = m_actions[btn];
+
+      if (!action.empty()) {
+        auto action_replaced = string_util::replace_all(action, "%counter%", cnt);
+
+        /*
+         * The pid token is only for tailed commands.
+         * If the command is not specified or running, replacement is unnecessary as well
+         */
+        if(m_tail && m_command && m_command->is_running()) {
+          action_replaced = string_util::replace_all(action_replaced, "%pid%", to_string(m_command->get_pid()));
+        }
+        m_builder->action(btn, action_replaced);
+      }
+    }
+
+    m_builder->append(output);
+
+    return m_builder->flush();
+  }
+
+  /**
+   * Output format tags
+   */
+  bool script_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL) {
+      builder->node(m_label);
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/systray.cpp b/src/modules/systray.cpp
new file mode 100644 (file)
index 0000000..f102d61
--- /dev/null
@@ -0,0 +1,69 @@
+#if DEBUG
+#include "modules/systray.hpp"
+#include "drawtypes/label.hpp"
+#include "x11/connection.hpp"
+#include "x11/tray_manager.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<systray_module>;
+
+  /**
+   * Construct module
+   */
+  systray_module::systray_module(const bar_settings& bar, string name_)
+      : static_module<systray_module>(bar, move(name_)), m_connection(connection::make()) {
+    // Add formats and elements
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_TOGGLE, {TAG_LABEL_TOGGLE, TAG_TRAY_CLIENTS});
+
+    if (m_formatter->has(TAG_LABEL_TOGGLE)) {
+      m_label = load_label(m_conf, name(), TAG_LABEL_TOGGLE);
+    }
+  }
+
+  /**
+   * Update
+   */
+  void systray_module::update() {
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%title%", "");
+    }
+
+    broadcast();
+  }
+
+  /**
+   * Build output
+   */
+  bool systray_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_TOGGLE) {
+      builder->action(mousebtn::LEFT, *this, EVENT_TOGGLE, "", m_label);
+    } else if (tag == TAG_TRAY_CLIENTS && !m_hidden) {
+      builder->append(TRAY_PLACEHOLDER);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Handle input event
+   */
+  bool systray_module::input(const string& action, const string&) {
+    if (action.find(EVENT_TOGGLE) != 0) {
+      return false;
+    }
+
+    m_hidden = !m_hidden;
+    broadcast();
+
+    return true;
+  }
+}
+
+POLYBAR_NS_END
+#endif
diff --git a/src/modules/temperature.cpp b/src/modules/temperature.cpp
new file mode 100644 (file)
index 0000000..98b195e
--- /dev/null
@@ -0,0 +1,108 @@
+#include "modules/temperature.hpp"
+
+#include "drawtypes/label.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/file.hpp"
+#include "utils/math.hpp"
+#include <cmath>
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<temperature_module>;
+
+  temperature_module::temperature_module(const bar_settings& bar, string name_)
+      : timer_module<temperature_module>(bar, move(name_)) {
+    m_zone = m_conf.get(name(), "thermal-zone", 0);
+    m_path = m_conf.get(name(), "hwmon-path", ""s);
+    m_tempbase = m_conf.get(name(), "base-temperature", 0);
+    m_tempwarn = m_conf.get(name(), "warn-temperature", 80);
+    set_interval(1s);
+    m_units = m_conf.get(name(), "units", m_units);
+
+    if (m_path.empty()) {
+      m_path = string_util::replace(PATH_TEMPERATURE_INFO, "%zone%", to_string(m_zone));
+    }
+
+    if (!file_util::exists(m_path)) {
+      throw module_error("The file '" + m_path + "' does not exist");
+    }
+
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_RAMP});
+    m_formatter->add(FORMAT_WARN, TAG_LABEL_WARN, {TAG_LABEL_WARN, TAG_RAMP});
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label[temp_state::NORMAL] = load_optional_label(m_conf, name(), TAG_LABEL, "%temperature-c%");
+    }
+    if (m_formatter->has(TAG_LABEL_WARN)) {
+      m_label[temp_state::WARN] = load_optional_label(m_conf, name(), TAG_LABEL_WARN, "%temperature-c%");
+    }
+    if (m_formatter->has(TAG_RAMP)) {
+      m_ramp = load_ramp(m_conf, name(), TAG_RAMP);
+    }
+
+    // Deprecation warning for the %temperature% token
+    if((m_label[temp_state::NORMAL] && m_label[temp_state::NORMAL]->has_token("%temperature%")) ||
+        ((m_label[temp_state::WARN] && m_label[temp_state::WARN]->has_token("%temperature%")))) {
+      m_log.warn("%s: The token `%%temperature%%` is deprecated, use `%%temperature-c%%` instead.", name());
+    }
+  }
+
+  bool temperature_module::update() {
+    m_temp = std::strtol(file_util::contents(m_path).c_str(), nullptr, 10) / 1000.0f + 0.5f;
+    int temp_f = floor(((1.8 * m_temp) + 32) + 0.5);
+    m_perc = math_util::unbounded_percentage(m_temp, m_tempbase, m_tempwarn);
+
+    string temp_c_string = to_string(m_temp);
+    string temp_f_string = to_string(temp_f);
+
+    // Add units if `units = true` in config
+    if(m_units) {
+      temp_c_string += "°C";
+      temp_f_string += "°F";
+    }
+
+    const auto replace_tokens = [&](label_t& label) {
+      label->reset_tokens();
+      label->replace_token("%temperature-f%", temp_f_string);
+      label->replace_token("%temperature-c%", temp_c_string);
+
+      // DEPRECATED: Will be removed in later release
+      label->replace_token("%temperature%", temp_c_string);
+    };
+
+    if (m_label[temp_state::NORMAL]) {
+      replace_tokens(m_label[temp_state::NORMAL]);
+    }
+    if (m_label[temp_state::WARN]) {
+      replace_tokens(m_label[temp_state::WARN]);
+    }
+
+    return true;
+  }
+
+  string temperature_module::get_format() const {
+    if (m_temp >= m_tempwarn) {
+      return FORMAT_WARN;
+    } else {
+      return DEFAULT_FORMAT;
+    }
+  }
+
+  bool temperature_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL) {
+      builder->node(m_label.at(temp_state::NORMAL));
+    } else if (tag == TAG_LABEL_WARN) {
+      builder->node(m_label.at(temp_state::WARN));
+    } else if (tag == TAG_RAMP) {
+      builder->node(m_ramp->get_by_percentage_with_borders(m_perc));
+    } else {
+      return false;
+    }
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/text.cpp b/src/modules/text.cpp
new file mode 100644 (file)
index 0000000..01882e7
--- /dev/null
@@ -0,0 +1,56 @@
+#include "modules/text.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<text_module>;
+
+  text_module::text_module(const bar_settings& bar, string name_) : static_module<text_module>(bar, move(name_)) {
+    m_formatter->add("content", "", {});
+
+    if (m_formatter->get("content")->value.empty()) {
+      throw module_error(name() + ".content is empty or undefined");
+    }
+  }
+
+  string text_module::get_format() const {
+    return "content";
+  }
+
+  string text_module::get_output() {
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapper
+    // with the cmd handlers
+    string output{module::get_output()};
+
+    auto click_left = m_conf.get(name(), "click-left", ""s);
+    auto click_middle = m_conf.get(name(), "click-middle", ""s);
+    auto click_right = m_conf.get(name(), "click-right", ""s);
+    auto scroll_up = m_conf.get(name(), "scroll-up", ""s);
+    auto scroll_down = m_conf.get(name(), "scroll-down", ""s);
+
+    if (!click_left.empty()) {
+      m_builder->action(mousebtn::LEFT, click_left);
+    }
+    if (!click_middle.empty()) {
+      m_builder->action(mousebtn::MIDDLE, click_middle);
+    }
+    if (!click_right.empty()) {
+      m_builder->action(mousebtn::RIGHT, click_right);
+    }
+    if (!scroll_up.empty()) {
+      m_builder->action(mousebtn::SCROLL_UP, scroll_up);
+    }
+    if (!scroll_down.empty()) {
+      m_builder->action(mousebtn::SCROLL_DOWN, scroll_down);
+    }
+
+    m_builder->append(output);
+
+    return m_builder->flush();
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/modules/xbacklight.cpp b/src/modules/xbacklight.cpp
new file mode 100644 (file)
index 0000000..901694c
--- /dev/null
@@ -0,0 +1,177 @@
+#include "modules/xbacklight.hpp"
+#include "drawtypes/label.hpp"
+#include "drawtypes/progressbar.hpp"
+#include "drawtypes/ramp.hpp"
+#include "utils/math.hpp"
+#include "x11/connection.hpp"
+#include "x11/winspec.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<xbacklight_module>;
+
+  /**
+   * Construct module
+   */
+  xbacklight_module::xbacklight_module(const bar_settings& bar, string name_)
+      : static_module<xbacklight_module>(bar, move(name_)), m_connection(connection::make()) {
+    auto output = m_conf.get(name(), "output", m_bar.monitor->name);
+
+    auto monitors = randr_util::get_monitors(m_connection, m_connection.root(), bar.monitor_strict, false);
+
+    m_output = randr_util::match_monitor(monitors, output, bar.monitor_exact);
+
+    // If we didn't get a match we stop the module
+    if (!m_output) {
+      throw module_error("No matching output found for \"" + output + "\", stopping module...");
+    }
+
+    // Get flag to check if we should add scroll handlers for changing value
+    m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+
+    // Query randr for the backlight max and min value
+    try {
+      auto& backlight = m_output->backlight;
+      randr_util::get_backlight_range(m_connection, m_output, backlight);
+      randr_util::get_backlight_value(m_connection, m_output, backlight);
+    } catch (const exception& err) {
+      m_log.err("%s: Could not get data (err: %s)", name(), err.what());
+      throw module_error("Not supported for \"" + m_output->name + "\"");
+    }
+
+    // Create window that will proxy all RandR notify events
+    // clang-format off
+    m_proxy = winspec(m_connection)
+      << cw_size(1, 1)
+      << cw_pos(-1, -1)
+      << cw_flush(true);
+    // clang-format on
+
+    // Connect with the event registry and make sure we get
+    // notified when a RandR output property gets modified
+    m_connection.select_input_checked(m_proxy, XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY);
+
+    // Add formats and elements
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL, TAG_BAR, TAG_RAMP});
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_label = load_optional_label(m_conf, name(), TAG_LABEL, "%percentage%%");
+    }
+    if (m_formatter->has(TAG_BAR)) {
+      m_progressbar = load_progressbar(m_bar, m_conf, name(), TAG_BAR);
+    }
+    if (m_formatter->has(TAG_RAMP)) {
+      m_ramp = load_ramp(m_conf, name(), TAG_RAMP);
+    }
+  }
+
+  /**
+   * Handler for XCB_RANDR_NOTIFY events
+   */
+  void xbacklight_module::handle(const evt::randr_notify& evt) {
+    if (evt->subCode != XCB_RANDR_NOTIFY_OUTPUT_PROPERTY) {
+      return;
+    } else if (evt->u.op.status != XCB_PROPERTY_NEW_VALUE) {
+      return;
+    } else if (evt->u.op.window != m_proxy) {
+      return;
+    } else if (evt->u.op.output != m_output->output) {
+      return;
+    } else if (evt->u.op.atom != m_output->backlight.atom) {
+      return;
+    } else {
+      update();
+    }
+  }
+
+  /**
+   * Query the RandR extension for the new values
+   */
+  void xbacklight_module::update() {
+    auto& bl = m_output->backlight;
+    randr_util::get_backlight_value(m_connection, m_output, bl);
+    m_percentage = math_util::nearest_5(math_util::percentage<double>(bl.val, bl.min, bl.max));
+
+    // Update label tokens
+    if (m_label) {
+      m_label->reset_tokens();
+      m_label->replace_token("%percentage%", to_string(m_percentage));
+    }
+
+    // Emit a broadcast notification so that
+    // the new data will be drawn to the bar
+    broadcast();
+  }
+
+  /**
+   * Generate the module output
+   */
+  string xbacklight_module::get_output() {
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapped
+    // with the cmd handlers
+    string output{module::get_output()};
+
+    if (m_scroll) {
+      m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_INC, "");
+      m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_DEC, "");
+    }
+
+    m_builder->append(output);
+
+    m_builder->action_close();
+    m_builder->action_close();
+
+    return m_builder->flush();
+  }
+
+  /**
+   * Output content as defined in the config
+   */
+  bool xbacklight_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_BAR) {
+      builder->node(m_progressbar->output(m_percentage));
+    } else if (tag == TAG_RAMP) {
+      builder->node(m_ramp->get_by_percentage(m_percentage));
+    } else if (tag == TAG_LABEL) {
+      builder->node(m_label);
+    } else {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Process scroll events by changing backlight value
+   */
+  bool xbacklight_module::input(const string& action, const string&) {
+    double value_mod{0.0};
+
+    if (action == EVENT_INC) {
+      value_mod = 5.0;
+      m_log.info("%s: Increasing value by %i%", name(), value_mod);
+    } else if (action == EVENT_DEC) {
+      value_mod = -5.0;
+      m_log.info("%s: Decreasing value by %i%", name(), -value_mod);
+    } else {
+      return false;
+    }
+
+    try {
+      int rounded = math_util::cap<double>(m_percentage + value_mod, 0.0, 100.0) + 0.5;
+      const int values[1]{math_util::percentage_to_value<int>(rounded, m_output->backlight.max)};
+
+      m_connection.change_output_property_checked(
+          m_output->output, m_output->backlight.atom, XCB_ATOM_INTEGER, 32, XCB_PROP_MODE_REPLACE, 1, values);
+    } catch (const exception& err) {
+      m_log.err("%s: %s", name(), err.what());
+    }
+
+    return true;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/xkeyboard.cpp b/src/modules/xkeyboard.cpp
new file mode 100644 (file)
index 0000000..cdceccb
--- /dev/null
@@ -0,0 +1,290 @@
+#include "modules/xkeyboard.hpp"
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/factory.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<xkeyboard_module>;
+
+  // clang-format off
+  static const keyboard::indicator::type INDICATOR_TYPES[] {
+    keyboard::indicator::type::CAPS_LOCK,
+    keyboard::indicator::type::NUM_LOCK,
+    keyboard::indicator::type::SCROLL_LOCK
+  };
+  // clang-format on
+
+  /**
+   * Construct module
+   */
+  xkeyboard_module::xkeyboard_module(const bar_settings& bar, string name_)
+      : static_module<xkeyboard_module>(bar, move(name_)), m_connection(connection::make()) {
+
+
+    // Setup extension
+    // clang-format off
+    m_connection.xkb().select_events_checked(XCB_XKB_ID_USE_CORE_KBD,
+      XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+      XCB_XKB_EVENT_TYPE_STATE_NOTIFY |
+      XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY, 0,
+      XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY |
+      XCB_XKB_EVENT_TYPE_STATE_NOTIFY |
+      XCB_XKB_EVENT_TYPE_INDICATOR_STATE_NOTIFY, 0, 0, nullptr);
+    // clang-format on
+
+    // Create keyboard object
+    query_keyboard();
+
+    // Load config values
+    m_blacklist = m_conf.get_list(name(), "blacklist", {});
+
+    // load layout icons
+    m_layout_icons = factory_util::shared<iconset>();
+    m_layout_icons->add(DEFAULT_LAYOUT_ICON, load_optional_label(m_conf, name(), DEFAULT_LAYOUT_ICON, ""s));
+
+    for (const auto& it : m_conf.get_list<string>(name(), "layout-icon", {})) {
+      auto vec = string_util::tokenize(it, ';');
+      if (vec.size() == 2) {
+        m_layout_icons->add(vec[0], factory_util::shared<label>(vec[1]));
+      }
+    }
+
+    // Add formats and elements
+    m_formatter->add(DEFAULT_FORMAT, FORMAT_DEFAULT, {TAG_LABEL_LAYOUT, TAG_LABEL_INDICATOR});
+
+    if (m_formatter->has(TAG_LABEL_LAYOUT)) {
+      m_layout = load_optional_label(m_conf, name(), TAG_LABEL_LAYOUT, "%layout%");
+    }
+
+    if (m_formatter->has(TAG_LABEL_INDICATOR)) {
+      m_conf.warn_deprecated(name(), "label-indicator", "label-indicator-on");
+      // load an empty label if 'label-indicator-off' is not explicitly specified so
+      // no existing user configs are broken (who expect nothing to be shown when indicator is off)
+      m_indicator_state_off = load_optional_label(m_conf, name(), "label-indicator-off"s, ""s);
+
+      if (m_conf.has(name(), "label-indicator-on"s)) {
+        m_indicator_state_on = load_optional_label(m_conf, name(), "label-indicator-on"s, "%name%"s);
+      } else {
+        // if 'label-indicator-on' is not explicitly specified, use 'label-indicator'
+        // as to not break existing user configs
+        m_indicator_state_on = load_optional_label(m_conf, name(), TAG_LABEL_INDICATOR, "%name%"s);
+      }
+
+      // load indicator icons
+      m_indicator_icons_off = factory_util::shared<iconset>();
+      m_indicator_icons_on = factory_util::shared<iconset>();
+
+      auto icon_pair = string_util::tokenize(m_conf.get(name(), DEFAULT_INDICATOR_ICON, ""s), ';');
+      if(icon_pair.size() == 2) {
+        m_indicator_icons_off->add(DEFAULT_INDICATOR_ICON, factory_util::shared<label>(icon_pair[0]));
+        m_indicator_icons_on->add(DEFAULT_INDICATOR_ICON, factory_util::shared<label>(icon_pair[1]));
+      } else {
+        m_indicator_icons_off->add(DEFAULT_INDICATOR_ICON, factory_util::shared<label>(""s));
+        m_indicator_icons_on->add(DEFAULT_INDICATOR_ICON, factory_util::shared<label>(""s));
+      }
+
+      for (const auto& it : m_conf.get_list<string>(name(), "indicator-icon", {})) {
+        auto icon_triple = string_util::tokenize(it, ';');
+        if (icon_triple.size() == 3) {
+          auto const indicator_str = string_util::lower(icon_triple[0]);
+          m_indicator_icons_off->add(indicator_str, factory_util::shared<label>(icon_triple[1]));
+          m_indicator_icons_on->add(indicator_str, factory_util::shared<label>(icon_triple[2]));
+        }
+      }
+
+      for (auto it : INDICATOR_TYPES) {
+        const auto& indicator_str = m_keyboard->indicator_name(it);
+        auto key_name = string_util::replace(string_util::lower(indicator_str), " "s, ""s);
+        const auto indicator_key_on = "label-indicator-on-"s + key_name;
+        const auto indicator_key_off = "label-indicator-off-"s + key_name;
+
+        if (m_conf.has(name(), indicator_key_on)) {
+          m_indicator_on_labels.emplace(it, load_label(m_conf, name(), indicator_key_on));
+        }
+        if (m_conf.has(name(), indicator_key_off)) {
+          m_indicator_off_labels.emplace(it, load_label(m_conf, name(), indicator_key_off));
+        }
+      }
+    }
+  }
+
+  /**
+   * Update labels with extension data
+   */
+  void xkeyboard_module::update() {
+    if (m_layout) {
+      m_layout->reset_tokens();
+      m_layout->replace_token("%name%", m_keyboard->group_name(m_keyboard->current()));
+
+      auto const current_layout = m_keyboard->layout_name(m_keyboard->current());
+      auto icon = m_layout_icons->get(current_layout, DEFAULT_LAYOUT_ICON);
+
+      m_layout->replace_token("%icon%", icon->get());
+      m_layout->replace_token("%layout%", current_layout);
+      m_layout->replace_token("%number%", to_string(m_keyboard->current()));
+    }
+
+    if (m_formatter->has(TAG_LABEL_INDICATOR)) {
+      m_indicators.clear();
+
+      for (auto it : INDICATOR_TYPES) {
+        const auto& indicator_str = m_keyboard->indicator_name(it);
+
+        if (blacklisted(indicator_str)) {
+          continue;
+        }
+
+        auto indicator_on = m_keyboard->on(it);
+        auto &indicator_labels = indicator_on ? m_indicator_on_labels : m_indicator_off_labels;
+        auto &indicator_icons = indicator_on ? m_indicator_icons_on : m_indicator_icons_off;
+        auto &indicator_state = indicator_on ? m_indicator_state_on : m_indicator_state_off;
+
+        label_t indicator;
+        if (indicator_labels.find(it) != indicator_labels.end()) {
+          indicator = indicator_labels[it]->clone();
+        } else {
+          indicator = indicator_state->clone();
+        }
+
+        auto icon = indicator_icons->get(string_util::lower(indicator_str), DEFAULT_INDICATOR_ICON);
+
+        indicator->replace_token("%name%", indicator_str);
+        indicator->replace_token("%icon%", icon->get());
+        m_indicators.emplace(it, move(indicator));
+      }
+    }
+
+    // Trigger redraw
+    broadcast();
+  }
+
+  /**
+   * Build module output and wrap it in a click handler use
+   * to cycle between configured layout groups
+   */
+  string xkeyboard_module::get_output() {
+    string output{module::get_output()};
+
+    if (m_keyboard && m_keyboard->size() > 1) {
+      m_builder->action(mousebtn::LEFT, *this, EVENT_SWITCH, "");
+      m_builder->append(output);
+      m_builder->action_close();
+    } else {
+      m_builder->append(output);
+    }
+
+    return m_builder->flush();
+  }
+
+  /**
+   * Map format tags to content
+   */
+  bool xkeyboard_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_LAYOUT) {
+      builder->node(m_layout);
+    } else if (tag == TAG_LABEL_INDICATOR && !m_indicators.empty()) {
+      size_t n{0};
+      for (auto&& indicator : m_indicators) {
+        if (n++) {
+          builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
+        }
+        builder->node(indicator.second);
+      }
+      return n > 0;
+    } else {
+      return false;
+    }
+
+    return true;
+  }
+
+  /**
+   * Handle input command
+   */
+  bool xkeyboard_module::input(const string& action, const string&) {
+    if (action != EVENT_SWITCH) {
+      return false;
+    }
+
+    size_t current_group = m_keyboard->current() + 1;
+
+    if (current_group >= m_keyboard->size()) {
+      current_group = 0;
+    }
+
+    xkb_util::switch_layout(m_connection, XCB_XKB_ID_USE_CORE_KBD, current_group);
+    m_keyboard->current(current_group);
+    m_connection.flush();
+
+    update();
+
+    return true;
+  }
+
+  /**
+   * Create keyboard object by querying current extension data
+   */
+  bool xkeyboard_module::query_keyboard() {
+    try {
+      auto layouts = xkb_util::get_layouts(m_connection, XCB_XKB_ID_USE_CORE_KBD);
+      auto indicators = xkb_util::get_indicators(m_connection, XCB_XKB_ID_USE_CORE_KBD);
+      auto current_group = xkb_util::get_current_group(m_connection, XCB_XKB_ID_USE_CORE_KBD);
+      m_keyboard = factory_util::unique<keyboard>(move(layouts), move(indicators), current_group);
+      return true;
+    } catch (const exception& err) {
+      throw module_error("Failed to query keyboard, err: " + string{err.what()});
+    }
+
+    return false;
+  }
+
+  /**
+   * Check if the indicator has been blacklisted by the user
+   */
+  bool xkeyboard_module::blacklisted(const string& indicator_name) {
+    for (auto&& i : m_blacklist) {
+      if (string_util::compare(i, indicator_name)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Handler for XCB_XKB_NEW_KEYBOARD_NOTIFY events
+   */
+  void xkeyboard_module::handle(const evt::xkb_new_keyboard_notify& evt) {
+    if (evt->changed & XCB_XKB_NKN_DETAIL_KEYCODES && m_xkb_newkb_notify.allow(evt->time)) {
+      query_keyboard();
+      update();
+    }
+  }
+
+  /**
+   * Handler for XCB_XKB_STATE_NOTIFY events
+   */
+  void xkeyboard_module::handle(const evt::xkb_state_notify& evt) {
+    if (m_keyboard && evt->changed & XCB_XKB_STATE_PART_GROUP_STATE && m_xkb_state_notify.allow(evt->time)) {
+      m_keyboard->current(evt->group);
+      update();
+    }
+  }
+
+  /**
+   * Handler for XCB_XKB_INDICATOR_STATE_NOTIFY events
+   */
+  void xkeyboard_module::handle(const evt::xkb_indicator_state_notify& evt) {
+    if (m_keyboard && m_xkb_indicator_notify.allow(evt->time)) {
+      m_keyboard->set(m_connection.xkb().get_state(XCB_XKB_ID_USE_CORE_KBD)->lockedMods);
+      update();
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/xwindow.cpp b/src/modules/xwindow.cpp
new file mode 100644 (file)
index 0000000..fbc23af
--- /dev/null
@@ -0,0 +1,143 @@
+#include "modules/xwindow.hpp"
+#include "drawtypes/label.hpp"
+#include "utils/factory.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+#include "modules/meta/base.inl"
+
+POLYBAR_NS
+
+namespace modules {
+  template class module<xwindow_module>;
+
+  /**
+   * Wrapper used to update the event mask of the
+   * currently active to enable title tracking
+   */
+  active_window::active_window(xcb_connection_t* conn, xcb_window_t win) : m_connection(conn), m_window(win) {
+    if (m_window != XCB_NONE) {
+      const unsigned int mask{XCB_EVENT_MASK_PROPERTY_CHANGE};
+      xcb_change_window_attributes(m_connection, m_window, XCB_CW_EVENT_MASK, &mask);
+    }
+  }
+
+  /**
+   * Deconstruct window object
+   */
+  active_window::~active_window() {
+    if (m_window != XCB_NONE) {
+      const unsigned int mask{XCB_EVENT_MASK_NO_EVENT};
+      xcb_change_window_attributes(m_connection, m_window, XCB_CW_EVENT_MASK, &mask);
+    }
+  }
+
+  /**
+   * Check if current window matches passed value
+   */
+  bool active_window::match(const xcb_window_t win) const {
+    return m_window == win;
+  }
+
+  /**
+   * Get the title by returning the first non-empty value of:
+   *  _NET_WM_NAME
+   *  _NET_WM_VISIBLE_NAME
+   */
+  string active_window::title() const {
+    string title;
+
+    if (!(title = ewmh_util::get_wm_name(m_window)).empty()) {
+      return title;
+    } else if (!(title = ewmh_util::get_visible_name(m_window)).empty()) {
+      return title;
+    } else if (!(title = icccm_util::get_wm_name(m_connection, m_window)).empty()) {
+      return title;
+    } else {
+      return "";
+    }
+  }
+
+  /**
+   * Construct module
+   */
+  xwindow_module::xwindow_module(const bar_settings& bar, string name_)
+      : static_module<xwindow_module>(bar, move(name_)), m_connection(connection::make()) {
+    // Initialize ewmh atoms
+    if ((ewmh_util::initialize()) == nullptr) {
+      throw module_error("Failed to initialize ewmh atoms");
+    }
+
+    // Check if the WM supports _NET_ACTIVE_WINDOW
+    if (!ewmh_util::supports(_NET_ACTIVE_WINDOW)) {
+      throw module_error("The WM does not list _NET_ACTIVE_WINDOW as a supported hint");
+    }
+
+    // Add formats and elements
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
+
+    if (m_formatter->has(TAG_LABEL)) {
+      m_statelabels.emplace(state::ACTIVE, load_optional_label(m_conf, name(), "label", "%title%"));
+      m_statelabels.emplace(state::EMPTY, load_optional_label(m_conf, name(), "label-empty", ""));
+    }
+  }
+
+  /**
+   * Handler for XCB_PROPERTY_NOTIFY events
+   */
+  void xwindow_module::handle(const evt::property_notify& evt) {
+    if (evt->atom == _NET_ACTIVE_WINDOW) {
+      update(true);
+    } else if (evt->atom == _NET_CURRENT_DESKTOP) {
+      update(true);
+    } else if (evt->atom == _NET_WM_VISIBLE_NAME) {
+      update();
+    } else if (evt->atom == _NET_WM_NAME) {
+      update();
+    } else {
+      return;
+    }
+
+    broadcast();
+  }
+
+  /**
+   * Update the currently active window and query its title
+   */
+  void xwindow_module::update(bool force) {
+    std::lock(m_buildlock, m_updatelock);
+    std::lock_guard<std::mutex> guard_a(m_buildlock, std::adopt_lock);
+    std::lock_guard<std::mutex> guard_b(m_updatelock, std::adopt_lock);
+
+    xcb_window_t win;
+
+    if (force) {
+      m_active.reset();
+    }
+
+    if (!m_active && (win = ewmh_util::get_active_window()) != XCB_NONE) {
+      m_active = make_unique<active_window>(m_connection, win);
+    }
+
+    if (m_active) {
+      m_label = m_statelabels.at(state::ACTIVE)->clone();
+      m_label->reset_tokens();
+      m_label->replace_token("%title%", m_active->title());
+    } else {
+      m_label = m_statelabels.at(state::EMPTY)->clone();
+    }
+  }
+
+  /**
+   * Output content as defined in the config
+   */
+  bool xwindow_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL && m_label && m_label.get()) {
+      builder->node(m_label);
+      return true;
+    }
+    return false;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/modules/xworkspaces.cpp b/src/modules/xworkspaces.cpp
new file mode 100644 (file)
index 0000000..f8b7b9b
--- /dev/null
@@ -0,0 +1,405 @@
+#include "modules/xworkspaces.hpp"
+
+#include <algorithm>
+#include <set>
+#include <utility>
+
+#include "drawtypes/iconset.hpp"
+#include "drawtypes/label.hpp"
+#include "modules/meta/base.inl"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace {
+  inline bool operator==(const position& a, const position& b) {
+    return a.x + a.y == b.x + b.y;
+  }
+}  // namespace
+
+/**
+ * Defines a lexicographical order on position
+ */
+bool operator<(const position& a, const position& b) {
+  return std::make_tuple(a.x, a.y) < std::make_tuple(b.x, b.y);
+}
+
+namespace modules {
+  template class module<xworkspaces_module>;
+
+  /**
+   * Construct module
+   */
+  xworkspaces_module::xworkspaces_module(const bar_settings& bar, string name_)
+      : static_module<xworkspaces_module>(bar, move(name_)), m_connection(connection::make()) {
+    // Load config values
+    m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
+    m_click = m_conf.get(name(), "enable-click", m_click);
+    m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
+
+    // Initialize ewmh atoms
+    if ((m_ewmh = ewmh_util::initialize()) == nullptr) {
+      throw module_error("Failed to initialize ewmh atoms");
+    }
+
+    // Check if the WM supports _NET_CURRENT_DESKTOP
+    if (!ewmh_util::supports(m_ewmh->_NET_CURRENT_DESKTOP)) {
+      throw module_error("The WM does not support _NET_CURRENT_DESKTOP, aborting...");
+    }
+
+    // Check if the WM supports _NET_DESKTOP_VIEWPORT
+    if (!(m_monitorsupport = ewmh_util::supports(m_ewmh->_NET_DESKTOP_VIEWPORT)) && m_pinworkspaces) {
+      throw module_error("The WM does not support _NET_DESKTOP_VIEWPORT (required when `pin-workspaces = true`)");
+    }
+
+    // Add formats and elements
+    m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE, TAG_LABEL_MONITOR});
+
+    if (m_formatter->has(TAG_LABEL_MONITOR)) {
+      m_monitorlabel = load_optional_label(m_conf, name(), "label-monitor", DEFAULT_LABEL_MONITOR);
+    }
+
+    if (m_formatter->has(TAG_LABEL_STATE)) {
+      // clang-format off
+      m_labels.insert(make_pair(
+          desktop_state::ACTIVE, load_optional_label(m_conf, name(), "label-active", DEFAULT_LABEL_STATE)));
+      m_labels.insert(make_pair(
+          desktop_state::OCCUPIED, load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL_STATE)));
+      m_labels.insert(make_pair(
+          desktop_state::URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL_STATE)));
+      m_labels.insert(make_pair(
+          desktop_state::EMPTY, load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL_STATE)));
+      // clang-format on
+    }
+
+    m_icons = factory_util::shared<iconset>();
+    m_icons->add(DEFAULT_ICON, factory_util::shared<label>(m_conf.get(name(), DEFAULT_ICON, ""s)));
+
+    for (const auto& workspace : m_conf.get_list<string>(name(), "icon", {})) {
+      auto vec = string_util::tokenize(workspace, ';');
+      if (vec.size() == 2) {
+        m_icons->add(vec[0], factory_util::shared<label>(vec[1]));
+      }
+    }
+
+    // Get list of monitors
+    m_monitors = randr_util::get_monitors(m_connection, m_connection.root(), false);
+
+    // Get desktop details
+    m_desktop_names = get_desktop_names();
+    m_current_desktop = ewmh_util::get_current_desktop();
+    m_current_desktop_name = m_desktop_names[m_current_desktop];
+
+    rebuild_desktops();
+
+    // Get _NET_CLIENT_LIST
+    rebuild_clientlist();
+    rebuild_desktop_states();
+  }
+
+  /**
+   * Handler for XCB_PROPERTY_NOTIFY events
+   */
+  void xworkspaces_module::handle(const evt::property_notify& evt) {
+    std::lock_guard<std::mutex> lock(m_workspace_mutex);
+
+    if (evt->atom == m_ewmh->_NET_CLIENT_LIST || evt->atom == m_ewmh->_NET_WM_DESKTOP) {
+      rebuild_clientlist();
+      rebuild_desktop_states();
+    } else if (evt->atom == m_ewmh->_NET_DESKTOP_NAMES || evt->atom == m_ewmh->_NET_NUMBER_OF_DESKTOPS) {
+      m_desktop_names = get_desktop_names();
+      rebuild_desktops();
+      rebuild_clientlist();
+      rebuild_desktop_states();
+    } else if (evt->atom == m_ewmh->_NET_CURRENT_DESKTOP) {
+      m_current_desktop = ewmh_util::get_current_desktop();
+      m_current_desktop_name = m_desktop_names[m_current_desktop];
+      rebuild_desktop_states();
+    } else if (evt->atom == WM_HINTS) {
+      if (icccm_util::get_wm_urgency(m_connection, evt->window)) {
+        set_desktop_urgent(evt->window);
+      }
+    } else {
+      return;
+    }
+
+    broadcast();
+  }
+
+  /**
+   * Rebuild the list of managed clients
+   */
+  void xworkspaces_module::rebuild_clientlist() {
+    vector<xcb_window_t> newclients = ewmh_util::get_client_list();
+    std::sort(newclients.begin(), newclients.end());
+
+    for (auto&& client : newclients) {
+      if (m_clients.count(client) == 0) {
+        // new client: listen for changes (wm_hint or desktop)
+        m_connection.ensure_event_mask(client, XCB_EVENT_MASK_PROPERTY_CHANGE);
+      }
+    }
+
+    // rebuild entire mapping of clients to desktops
+    m_clients.clear();
+    for (auto&& client : newclients) {
+      m_clients[client] = ewmh_util::get_desktop_from_window(client);
+    }
+  }
+
+  /**
+   * Rebuild the desktop tree
+   *
+   * This requires m_desktop_names to have an up-to-date value
+   */
+  void xworkspaces_module::rebuild_desktops() {
+    m_viewports.clear();
+
+    /*
+     * Stores the _NET_DESKTOP_VIEWPORT hint
+     *
+     * For WMs that don't support that hint, we store an empty vector
+     *
+     * If the length of the vector is less than _NET_NUMBER_OF_DESKTOPS
+     * all desktops which aren't explicitly assigned a postion will be
+     * assigned (0, 0)
+     *
+     * We use this to map workspaces to viewports, desktop i is at position
+     * ws_positions[i].
+     */
+    vector<position> ws_positions;
+    if (m_monitorsupport) {
+      ws_positions = ewmh_util::get_desktop_viewports();
+    }
+
+    /*
+     * Not all desktops were assigned a viewport, add (0, 0) for all missing
+     * desktops.
+     */
+    if (ws_positions.size() < m_desktop_names.size()) {
+      auto num_insert = m_desktop_names.size() - ws_positions.size();
+      ws_positions.reserve(num_insert);
+      std::fill_n(std::back_inserter(ws_positions), num_insert, position{0, 0});
+    }
+
+    /*
+     * The list of viewports is the set of unique positions in ws_positions
+     * Using a set automatically removes duplicates.
+     */
+    std::set<position> viewports(ws_positions.begin(), ws_positions.end());
+
+    for (auto&& viewport_pos : viewports) {
+      /*
+       * If pin-workspaces is set, we only add the viewport if it's in the
+       * monitor the bar is on.
+       * Generally viewport_pos is the same as the top-left coordinate of the
+       * monitor but we use `contains` here as a safety in case it isn't exactly.
+       */
+      if (!m_pinworkspaces || m_bar.monitor->contains(viewport_pos)) {
+        auto viewport = make_unique<struct viewport>();
+        viewport->state = viewport_state::UNFOCUSED;
+        viewport->pos = viewport_pos;
+
+        for (auto&& m : m_monitors) {
+          if (m->contains(viewport->pos)) {
+            viewport->name = m->name;
+            viewport->state = viewport_state::FOCUSED;
+          }
+        }
+
+        viewport->label = [&] {
+          label_t label;
+          if (m_monitorlabel) {
+            label = m_monitorlabel->clone();
+            label->reset_tokens();
+            label->replace_token("%name%", viewport->name);
+          }
+          return label;
+        }();
+
+        for (size_t i = 0; i < ws_positions.size(); i++) {
+          auto&& ws_pos = ws_positions[i];
+          if (ws_pos == viewport_pos) {
+            viewport->desktops.emplace_back(make_unique<struct desktop>(i, desktop_state::EMPTY, label_t{}));
+          }
+        }
+
+        m_viewports.emplace_back(move(viewport));
+      }
+    }
+  }
+
+  /**
+   * Update active state of current desktops
+   */
+  void xworkspaces_module::rebuild_desktop_states() {
+    std::set<unsigned int> occupied_desks;
+    for (auto&& c : m_clients) {
+      occupied_desks.insert(c.second);
+    }
+
+    for (auto&& v : m_viewports) {
+      for (auto&& d : v->desktops) {
+        if (d->index == m_current_desktop) {
+          d->state = desktop_state::ACTIVE;
+        } else if (occupied_desks.count(d->index) > 0) {
+          d->state = desktop_state::OCCUPIED;
+        } else {
+          d->state = desktop_state::EMPTY;
+        }
+
+        d->label = m_labels.at(d->state)->clone();
+        d->label->reset_tokens();
+        d->label->replace_token("%index%", to_string(d->index + 1));
+        d->label->replace_token("%name%", m_desktop_names[d->index]);
+        d->label->replace_token("%icon%", m_icons->get(m_desktop_names[d->index], DEFAULT_ICON)->get());
+      }
+    }
+  }
+
+  vector<string> xworkspaces_module::get_desktop_names() {
+    vector<string> names = ewmh_util::get_desktop_names();
+    unsigned int desktops_number = ewmh_util::get_number_of_desktops();
+    if (desktops_number == names.size()) {
+      return names;
+    } else if (desktops_number < names.size()) {
+      names.erase(names.begin() + desktops_number, names.end());
+      return names;
+    }
+    for (unsigned int i = names.size(); i < desktops_number; i++) {
+      names.insert(names.end(), to_string(i + 1));
+    }
+    return names;
+  }
+
+  /**
+   * Find window and set corresponding desktop to urgent
+   */
+  void xworkspaces_module::set_desktop_urgent(xcb_window_t window) {
+    auto desk = ewmh_util::get_desktop_from_window(window);
+    if (desk == m_current_desktop)
+      // ignore if current desktop is urgent
+      return;
+    for (auto&& v : m_viewports) {
+      for (auto&& d : v->desktops) {
+        if (d->index == desk && d->state != desktop_state::URGENT) {
+          d->state = desktop_state::URGENT;
+
+          d->label = m_labels.at(d->state)->clone();
+          d->label->reset_tokens();
+          d->label->replace_token("%index%", to_string(d->index + 1));
+          d->label->replace_token("%name%", m_desktop_names[d->index]);
+          d->label->replace_token("%icon%", m_icons->get(m_desktop_names[d->index], DEFAULT_ICON)->get());
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * Fetch and parse data
+   */
+  void xworkspaces_module::update() {}
+
+  /**
+   * Generate module output
+   */
+  string xworkspaces_module::get_output() {
+    std::unique_lock<std::mutex> lock(m_workspace_mutex);
+
+    // Get the module output early so that
+    // the format prefix/suffix also gets wrapped
+    // with the cmd handlers
+    string output;
+    for (m_index = 0; m_index < m_viewports.size(); m_index++) {
+      if (m_index > 0) {
+        m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
+      }
+      output += module::get_output();
+    }
+
+    if (m_scroll) {
+      m_builder->action(mousebtn::SCROLL_DOWN, *this, EVENT_PREV, "");
+      m_builder->action(mousebtn::SCROLL_UP, *this, EVENT_NEXT, "");
+    }
+
+    m_builder->append(output);
+
+    m_builder->action_close();
+    m_builder->action_close();
+
+    return m_builder->flush();
+  }
+
+  /**
+   * Output content as defined in the config
+   */
+  bool xworkspaces_module::build(builder* builder, const string& tag) const {
+    if (tag == TAG_LABEL_MONITOR) {
+      if (m_viewports[m_index]->state != viewport_state::NONE) {
+        builder->node(m_viewports[m_index]->label);
+        return true;
+      } else {
+        return false;
+      }
+    } else if (tag == TAG_LABEL_STATE) {
+      unsigned int added_states = 0;
+      for (auto&& desktop : m_viewports[m_index]->desktops) {
+        if (desktop->label.get()) {
+          if (m_click && desktop->state != desktop_state::ACTIVE) {
+            builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, to_string(desktop->index), desktop->label);
+          } else {
+            builder->node(desktop->label);
+          }
+          added_states++;
+        }
+      }
+      return added_states > 0;
+    } else {
+      return false;
+    }
+  }
+
+  /**
+   * Handle user input event
+   */
+  bool xworkspaces_module::input(const string& action, const string& data) {
+    std::lock_guard<std::mutex> lock(m_workspace_mutex);
+
+    vector<unsigned int> indexes;
+    for (auto&& viewport : m_viewports) {
+      for (auto&& desktop : viewport->desktops) {
+        indexes.emplace_back(desktop->index);
+      }
+    }
+
+    std::sort(indexes.begin(), indexes.end());
+
+    unsigned int new_desktop{0};
+    unsigned int current_desktop{ewmh_util::get_current_desktop()};
+
+    if (action == EVENT_FOCUS) {
+      new_desktop = std::strtoul(data.c_str(), nullptr, 10);
+    } else if (action == EVENT_NEXT) {
+      new_desktop = math_util::min<unsigned int>(indexes.back(), current_desktop + 1);
+      new_desktop = new_desktop == current_desktop ? indexes.front() : new_desktop;
+    } else if (action == EVENT_PREV) {
+      new_desktop = math_util::max<unsigned int>(indexes.front(), current_desktop - 1);
+      new_desktop = new_desktop == current_desktop ? indexes.back() : new_desktop;
+    }
+
+    if (new_desktop != current_desktop) {
+      m_log.info("%s: Requesting change to desktop #%u", name(), new_desktop);
+      ewmh_util::change_current_desktop(new_desktop);
+    } else {
+      m_log.info("%s: Ignoring change to current desktop", name());
+    }
+
+    return true;
+  }
+}  // namespace modules
+
+POLYBAR_NS_END
diff --git a/src/settings.cpp.cmake b/src/settings.cpp.cmake
new file mode 100644 (file)
index 0000000..eadf3bb
--- /dev/null
@@ -0,0 +1,62 @@
+#include "settings.hpp"
+
+const char* const APP_NAME{"@PROJECT_NAME@"};
+const char* const APP_VERSION{"@APP_VERSION@"};
+
+const int SINK_PRIORITY_BAR{1};
+const int SINK_PRIORITY_SCREEN{2};
+const int SINK_PRIORITY_TRAY{3};
+const int SINK_PRIORITY_MODULE{4};
+
+const char* const ALSA_SOUNDCARD{"@SETTING_ALSA_SOUNDCARD@"};
+const char* const BSPWM_SOCKET_PATH{"@SETTING_BSPWM_SOCKET_PATH@"};
+const char* const BSPWM_STATUS_PREFIX{"@SETTING_BSPWM_STATUS_PREFIX@"};
+const char* const CONNECTION_TEST_IP{"@SETTING_CONNECTION_TEST_IP@"};
+const char* const PATH_ADAPTER{"@SETTING_PATH_ADAPTER@"};
+const char* const PATH_BACKLIGHT{"@SETTING_PATH_BACKLIGHT@"};
+const char* const PATH_BATTERY{"@SETTING_PATH_BATTERY@"};
+const char* const PATH_CPU_INFO{"@SETTING_PATH_CPU_INFO@"};
+const char* const PATH_MEMORY_INFO{"@SETTING_PATH_MEMORY_INFO@"};
+const char* const PATH_MESSAGING_FIFO{"@SETTING_PATH_MESSAGING_FIFO@"};
+const char* const PATH_TEMPERATURE_INFO{"@SETTING_PATH_TEMPERATURE_INFO@"};
+const char* const WIRELESS_LIB{"@WIRELESS_LIB@"};
+
+bool version_details(const std::vector<std::string>& args) {
+  for (auto&& arg : args) {
+    if (arg.compare(0, 3, "-vv") == 0)
+      return true;
+  }
+  return false;
+}
+
+// clang-format off
+void print_build_info(bool extended) {
+  printf("%s %s\n\n", APP_NAME, APP_VERSION);
+  printf("Features: %calsa %ccurl %ci3 %cmpd %cnetwork(%s) %cpulseaudio %cxkeyboard\n",
+    (ENABLE_ALSA       ? '+' : '-'),
+    (ENABLE_CURL       ? '+' : '-'),
+    (ENABLE_I3         ? '+' : '-'),
+    (ENABLE_MPD        ? '+' : '-'),
+    (ENABLE_NETWORK    ? '+' : '-'),
+    WIRELESS_LIB,
+    (ENABLE_PULSEAUDIO ? '+' : '-'),
+    (ENABLE_XKEYBOARD  ? '+' : '-'));
+  if (extended) {
+    printf("\n");
+    printf("X extensions: %crandr (%cmonitors) %ccomposite %cxkb %cxrm %cxcursor\n",
+      (WITH_XRANDR            ? '+' : '-'),
+      (WITH_XRANDR_MONITORS   ? '+' : '-'),
+      (WITH_XCOMPOSITE        ? '+' : '-'),
+      (WITH_XKB               ? '+' : '-'),
+      (WITH_XRM               ? '+' : '-'),
+      (WITH_XCURSOR           ? '+' : '-'));
+    printf("\n");
+    printf("Build type: @CMAKE_BUILD_TYPE@\n");
+    printf("Compiler: @CMAKE_CXX_COMPILER@\n");
+    printf("Compiler flags: @CMAKE_CXX_FLAGS@ ${CMAKE_CXX_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}\n");
+    printf("Linker flags: @CMAKE_EXE_LINKER_FLAGS@ ${CMAKE_EXE_LINKER_FLAGS_${CMAKE_BUILD_TYPE_UPPER}}\n");
+  }
+}
+// clang-format on
+
+// vim:ft=cpp
diff --git a/src/utils/actions.cpp b/src/utils/actions.cpp
new file mode 100644 (file)
index 0000000..d10f693
--- /dev/null
@@ -0,0 +1,55 @@
+#include "utils/actions.hpp"
+
+#include <cassert>
+#include <stdexcept>
+
+#include "common.hpp"
+#include "modules/meta/base.hpp"
+
+POLYBAR_NS
+
+namespace actions_util {
+  string get_action_string(const modules::module_interface& module, string action, string data) {
+    string str = "#" + module.name_raw() + "." + action;
+    if (!data.empty()) {
+      str += "." + data;
+    }
+
+    return str;
+  }
+
+  std::tuple<string, string, string> parse_action_string(string action_str) {
+    assert(action_str.front() == '#');
+
+    action_str.erase(0, 1);
+
+    auto action_sep = action_str.find('.');
+
+    if (action_sep == string::npos) {
+      throw std::runtime_error("Missing separator between name and action");
+    }
+
+    auto module_name = action_str.substr(0, action_sep);
+
+    if (module_name.empty()) {
+      throw std::runtime_error("The module name must not be empty");
+    }
+
+    auto action = action_str.substr(action_sep + 1);
+    auto data_sep = action.find('.');
+    string data;
+
+    if (data_sep != string::npos) {
+      data = action.substr(data_sep + 1);
+      action.erase(data_sep);
+    }
+
+    if (action.empty()) {
+      throw std::runtime_error("The action name must not be empty");
+    }
+
+    return std::tuple<string, string, string>{module_name, action, data};
+  }
+}  // namespace actions_util
+
+POLYBAR_NS_END
diff --git a/src/utils/bspwm.cpp b/src/utils/bspwm.cpp
new file mode 100644 (file)
index 0000000..92fab3c
--- /dev/null
@@ -0,0 +1,151 @@
+#include <sys/un.h>
+
+#include "errors.hpp"
+#include "utils/bspwm.hpp"
+#include "utils/env.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace bspwm_util {
+  /**
+   * Get all bspwm root windows
+   */
+  vector<xcb_window_t> root_windows(connection& conn) {
+    vector<xcb_window_t> roots;
+    auto children = conn.query_tree(conn.screen()->root).children();
+
+    for (auto it = children.begin(); it != children.end(); it++) {
+      xcb_icccm_get_wm_class_reply_t reply{};
+      reply.class_name = reply.instance_name = nullptr;
+
+      if (xcb_icccm_get_wm_class_reply(conn, xcb_icccm_get_wm_class(conn, *it), &reply, nullptr)) {
+        if (string_util::compare("Bspwm", reply.class_name) && string_util::compare("root", reply.instance_name)) {
+          roots.emplace_back(*it);
+        }
+      }
+
+      if (reply.class_name != nullptr || reply.instance_name != nullptr) {
+        xcb_icccm_get_wm_class_reply_wipe(&reply);
+      }
+    }
+
+    return roots;
+  }
+
+  /**
+   * Restack given window relative to the bspwm root window
+   * for the given monitor.
+   *
+   * Fixes the issue with always-on-top window's
+   */
+  bool restack_to_root(connection& conn, const monitor_t& mon, const xcb_window_t win) {
+    for (auto&& root : root_windows(conn)) {
+      auto geom = conn.get_geometry(root);
+
+      if (mon->x != geom->x || mon->y != geom->y) {
+        continue;
+      }
+      if (mon->w != geom->width || mon->h != geom->height) {
+        continue;
+      }
+
+      const unsigned int value_mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
+      const unsigned int value_list[2]{root, XCB_STACK_MODE_ABOVE};
+
+      conn.configure_window_checked(win, value_mask, value_list);
+      conn.flush();
+
+      return true;
+    }
+
+    return false;
+  }
+
+  /**
+   * Get path to the bspwm socket by the following order
+   *
+   * 1. Value of environment variable BSPWM_SOCKET
+   * 2. Value built from the bspwm socket path template
+   * 3. Value of the macro BSPWM_SOCKET_PATH
+   */
+  string get_socket_path() {
+    string env_path;
+
+    if (!(env_path = env_util::get("BSPWM_SOCKET")).empty()) {
+      return env_path;
+    }
+
+    struct sockaddr_un sa {};
+    char* host = nullptr;
+    int dsp = 0;
+    int scr = 0;
+
+    if (xcb_parse_display(nullptr, &host, &dsp, &scr) == 0) {
+      return BSPWM_SOCKET_PATH;
+    }
+
+    snprintf(sa.sun_path, sizeof(sa.sun_path), "/tmp/bspwm%s_%i_%i-socket", host, dsp, scr);
+    free(host);
+
+    return sa.sun_path;
+  }
+
+  /**
+   * Generate a payload object with properly formatted data
+   * ready to be sent to the bspwm ipc controller
+   */
+  payload_t make_payload(const string& cmd) {
+    payload_t payload{new payload_t::element_type{}};
+    auto size = sizeof(payload->data);
+    int offset = 0;
+    int chars = 0;
+
+    for (auto&& word : string_util::split(cmd, ' ')) {
+      chars = snprintf(payload->data + offset, size - offset, "%s%c", word.c_str(), 0);
+      payload->len += chars;
+      offset += chars;
+    }
+
+    return payload;
+  }
+
+  /**
+   * Create an ipc socket connection
+   *
+   * Example usage:
+   * \code cpp
+   *   auto ipc = make_connection();
+   *   ipc->send(make_payload("desktop -f eDP-1:^1"));
+   * \endcode
+   */
+  connection_t make_connection() {
+    return socket_util::make_unix_connection(get_socket_path());
+  }
+
+  /**
+   * Create a connection and subscribe to events
+   * on the bspwm socket
+   *
+   * Example usage:
+   * \code cpp
+   *   auto ipc = make_subscriber();
+   *
+   *   while (!ipc->poll(POLLHUP, 0)) {
+   *     ssize_t bytes_received = 0;
+   *     auto data = ipc->receive(BUFSIZ-1, bytes_received, 0);
+   *     std::cout << data << std::endl;
+   *   }
+   * \endcode
+   */
+  connection_t make_subscriber() {
+    auto conn = make_connection();
+    auto payload = make_payload("subscribe report");
+    if (conn->send(payload->data, payload->len, 0) == 0) {
+      throw system_error("Failed to initialize subscriber");
+    }
+    return conn;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/color.cpp b/src/utils/color.cpp
new file mode 100644 (file)
index 0000000..3123e2e
--- /dev/null
@@ -0,0 +1,180 @@
+#include "utils/color.hpp"
+
+POLYBAR_NS
+
+/**
+ * Takes a hex string as input and brings it into a normalized form
+ *
+ * The input can either be only an alpha channel #AA
+ * or any of these forms: #RGB, #ARGB, #RRGGBB, #AARRGGBB
+ *
+ * Colors without alpha channel will get an alpha channel of FF
+ * The input does not have to start with '#'
+ *
+ * \returns Empty string for malformed input, either AA for the alpha only
+ * input or an 8 character string of the expanded form AARRGGBB
+ */
+static string normalize_hex(string hex) {
+  if (hex.length() == 0) {
+    return "";
+  }
+
+  // We remove the hash because it makes processing easier
+  if (hex[0] == '#') {
+    hex.erase(0, 1);
+  }
+
+  if (hex.length() == 2) {
+    // We only have an alpha channel
+    return hex;
+  }
+
+  if (hex.length() == 3) {
+    // RGB -> FRGB
+    hex.insert(0, 1, 'f');
+  }
+
+  if (hex.length() == 4) {
+    // ARGB -> AARRGGBB
+    hex = {hex[0], hex[0], hex[1], hex[1], hex[2], hex[2], hex[3], hex[3]};
+  }
+
+  if (hex.length() == 6) {
+    // RRGGBB -> FFRRGGBB
+    hex.insert(0, 2, 'f');
+  }
+
+  if (hex.length() != 8) {
+    return "";
+  }
+
+  return hex;
+}
+
+rgba::rgba() : m_value(0), m_type(NONE) {}
+rgba::rgba(uint32_t value, color_type type) : m_value(value), m_type(type) {}
+rgba::rgba(string hex) {
+  hex = normalize_hex(hex);
+
+  if (hex.length() == 0) {
+    m_value = 0;
+    m_type = NONE;
+  } else if (hex.length() == 2) {
+    m_value = std::strtoul(hex.c_str(), nullptr, 16) << 24;
+    m_type = ALPHA_ONLY;
+  } else {
+    m_value = std::strtoul(hex.c_str(), nullptr, 16);
+    m_type = ARGB;
+  }
+}
+
+rgba::operator string() const {
+  char s[10];
+  size_t len = snprintf(s, 10, "#%08x", m_value);
+  return string(s, len);
+}
+
+bool rgba::operator==(const rgba& other) const {
+  if (m_type != other.m_type) {
+    return false;
+  }
+
+  switch (m_type) {
+    case NONE:
+      return true;
+    case ARGB:
+      return m_value == other.m_value;
+    case ALPHA_ONLY:
+      return alpha_i() == other.alpha_i();
+    default:
+      return false;
+  }
+}
+
+rgba::operator uint32_t() const {
+  return m_value;
+}
+
+uint32_t rgba::value() const {
+  return this->m_value;
+}
+
+rgba::color_type rgba::type() const {
+  return m_type;
+}
+
+double rgba::alpha_d() const {
+  return alpha_i() / 255.0;
+}
+
+double rgba::red_d() const {
+  return red_i() / 255.0;
+}
+
+double rgba::green_d() const {
+  return green_i() / 255.0;
+}
+
+double rgba::blue_d() const {
+  return blue_i() / 255.0;
+}
+
+uint8_t rgba::alpha_i() const {
+  return (m_value >> 24) & 0xFF;
+}
+
+uint8_t rgba::red_i() const {
+  return (m_value >> 16) & 0xFF;
+}
+
+uint8_t rgba::green_i() const {
+  return (m_value >> 8) & 0xFF;
+}
+
+uint8_t rgba::blue_i() const {
+  return m_value & 0xFF;
+}
+
+bool rgba::has_color() const {
+  return m_type != NONE;
+}
+
+/**
+ * Applies the alpha channel of this color to the given color.
+ */
+rgba rgba::apply_alpha_to(rgba other) const {
+  uint32_t val = (other.value() & 0x00FFFFFF) | (((uint32_t)alpha_i()) << 24);
+  return rgba(val, m_type);
+}
+
+/**
+ * If this is an ALPHA_ONLY color, applies this alpha channel to the other
+ * color, otherwise just returns this.
+ *
+ * \returns the new color if this is ALPHA_ONLY or a copy of this otherwise.
+ */
+rgba rgba::try_apply_alpha_to(rgba other) const {
+  if (m_type == ALPHA_ONLY) {
+    return apply_alpha_to(other);
+  }
+
+  return *this;
+}
+
+string color_util::simplify_hex(string hex) {
+  // convert #ffrrggbb to #rrggbb
+  if (hex.length() == 9 && std::toupper(hex[1]) == 'F' && std::toupper(hex[2]) == 'F') {
+    hex.erase(1, 2);
+  }
+
+  // convert #rrggbb to #rgb
+  if (hex.length() == 7) {
+    if (hex[1] == hex[2] && hex[3] == hex[4] && hex[5] == hex[6]) {
+      hex = {'#', hex[1], hex[3], hex[5]};
+    }
+  }
+
+  return hex;
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/command.cpp b/src/utils/command.cpp
new file mode 100644 (file)
index 0000000..7e8d3a9
--- /dev/null
@@ -0,0 +1,222 @@
+#include "utils/command.hpp"
+
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <csignal>
+#include <cstdlib>
+#include <utility>
+#include <utils/string.hpp>
+
+#include "errors.hpp"
+#include "utils/io.hpp"
+#include "utils/process.hpp"
+
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+
+POLYBAR_NS
+
+command<output_policy::IGNORED>::command(const logger& logger, string cmd) : m_log(logger), m_cmd(move(cmd)) {}
+
+command<output_policy::IGNORED>::~command() {
+  if (is_running()) {
+    terminate();
+  }
+}
+
+/**
+ * Execute the command
+ */
+int command<output_policy::IGNORED>::exec(bool wait_for_completion) {
+  m_forkpid = process_util::spawn_async([m_cmd = m_cmd] { process_util::exec_sh(m_cmd.c_str()); });
+  if (wait_for_completion) {
+    auto status = wait();
+    m_forkpid = -1;
+    return status;
+  }
+
+  return EXIT_SUCCESS;
+}
+
+void command<output_policy::IGNORED>::terminate() {
+  if (is_running()) {
+    m_log.trace("command: Sending SIGTERM to running child process (%d)", m_forkpid);
+    killpg(m_forkpid, SIGTERM);
+    wait();
+  }
+  m_forkpid = -1;
+}
+
+/**
+ * Check if command is running
+ */
+bool command<output_policy::IGNORED>::is_running() {
+  return m_forkpid > 0 && process_util::wait_for_completion_nohang(m_forkpid, &m_forkstatus) > -1;
+}
+
+/**
+ * Wait for the child processs to finish
+ */
+int command<output_policy::IGNORED>::wait() {
+  do {
+    m_log.trace("command: Waiting for pid %d to finish...", m_forkpid);
+
+    process_util::wait_for_completion(m_forkpid, &m_forkstatus, WCONTINUED | WUNTRACED);
+
+    if (WIFEXITED(m_forkstatus) && m_forkstatus > 0) {
+      m_log.trace("command: Exited with failed status %d", WEXITSTATUS(m_forkstatus));
+    } else if (WIFEXITED(m_forkstatus)) {
+      m_log.trace("command: Exited with status %d", WEXITSTATUS(m_forkstatus));
+    } else if (WIFSIGNALED(m_forkstatus)) {
+      m_log.trace("command: killed by signal %d", WTERMSIG(m_forkstatus));
+    } else if (WIFSTOPPED(m_forkstatus)) {
+      m_log.trace("command: Stopped by signal %d", WSTOPSIG(m_forkstatus));
+    } else if (WIFCONTINUED(m_forkstatus)) {
+      m_log.trace("command: Continued");
+    } else {
+      break;
+    }
+  } while (!WIFEXITED(m_forkstatus) && !WIFSIGNALED(m_forkstatus));
+
+  return WEXITSTATUS(m_forkstatus);
+}
+
+/**
+ * Get command pid
+ */
+pid_t command<output_policy::IGNORED>::get_pid() {
+  return m_forkpid;
+}
+
+/**
+ * Get command exit status
+ */
+int command<output_policy::IGNORED>::get_exit_status() {
+  return WEXITSTATUS(m_forkstatus);
+}
+
+command<output_policy::REDIRECTED>::command(const polybar::logger& logger, std::string cmd)
+    : command<output_policy::IGNORED>(logger, move(cmd)) {
+  if (pipe(m_stdin) != 0) {
+    throw command_error("Failed to allocate input stream");
+  }
+  if (pipe(m_stdout) != 0) {
+    throw command_error("Failed to allocate output stream");
+  }
+}
+
+command<output_policy::REDIRECTED>::~command() {
+  if (m_stdin[PIPE_READ] > 0) {
+    close(m_stdin[PIPE_READ]);
+  }
+  if (m_stdin[PIPE_WRITE] > 0) {
+    close(m_stdin[PIPE_WRITE]);
+  }
+  if (m_stdout[PIPE_READ] > 0) {
+    close(m_stdout[PIPE_READ]);
+  }
+  if (m_stdout[PIPE_WRITE] > 0) {
+    close(m_stdout[PIPE_WRITE]);
+  }
+}
+
+/**
+ * Execute the command
+ */
+int command<output_policy::REDIRECTED>::exec(bool wait_for_completion) {
+  if ((m_forkpid = fork()) == -1) {
+    throw system_error("Failed to fork process");
+  }
+
+  if (process_util::in_forked_process(m_forkpid)) {
+    if (dup2(m_stdin[PIPE_READ], STDIN_FILENO) == -1) {
+      throw command_error("Failed to redirect stdin in child process");
+    }
+    if (dup2(m_stdout[PIPE_WRITE], STDOUT_FILENO) == -1) {
+      throw command_error("Failed to redirect stdout in child process");
+    }
+    if (dup2(m_stdout[PIPE_WRITE], STDERR_FILENO) == -1) {
+      throw command_error("Failed to redirect stderr in child process");
+    }
+
+    // Close file descriptors that won't be used by the child
+    if ((m_stdin[PIPE_READ] = close(m_stdin[PIPE_READ])) == -1) {
+      throw command_error("Failed to close fd");
+    }
+    if ((m_stdin[PIPE_WRITE] = close(m_stdin[PIPE_WRITE])) == -1) {
+      throw command_error("Failed to close fd");
+    }
+    if ((m_stdout[PIPE_READ] = close(m_stdout[PIPE_READ])) == -1) {
+      throw command_error("Failed to close fd");
+    }
+    if ((m_stdout[PIPE_WRITE] = close(m_stdout[PIPE_WRITE])) == -1) {
+      throw command_error("Failed to close fd");
+    }
+
+    setpgid(m_forkpid, 0);
+    process_util::exec_sh(m_cmd.c_str());
+  } else {
+    // Close file descriptors that won't be used by the parent
+    if ((m_stdin[PIPE_READ] = close(m_stdin[PIPE_READ])) == -1) {
+      throw command_error("Failed to close fd");
+    }
+    if ((m_stdout[PIPE_WRITE] = close(m_stdout[PIPE_WRITE])) == -1) {
+      throw command_error("Failed to close fd");
+    }
+
+    if (wait_for_completion) {
+      auto status = wait();
+      m_forkpid = -1;
+      return status;
+    }
+  }
+
+  return EXIT_SUCCESS;
+}
+
+/**
+ * Tail command output
+ *
+ * \note: This is a blocking call and will not
+ * end until the stream is closed
+ */
+void command<output_policy::REDIRECTED>::tail(callback<string> cb) {
+  io_util::tail(m_stdout[PIPE_READ], cb);
+}
+
+/**
+ * Write line to command input channel
+ */
+int command<output_policy::REDIRECTED>::writeline(string data) {
+  std::lock_guard<std::mutex> lck(m_pipelock);
+  return static_cast<int>(io_util::writeline(m_stdin[PIPE_WRITE], data));
+}
+
+/**
+ * Read a line from the commands output stream
+ */
+string command<output_policy::REDIRECTED>::readline() {
+  std::lock_guard<std::mutex> lck(m_pipelock);
+  return io_util::readline(m_stdout[PIPE_READ]);
+}
+
+/**
+ * Get command output channel
+ */
+int command<output_policy::REDIRECTED>::get_stdout(int c) {
+  return m_stdout[c];
+}
+
+/**
+ * Get command input channel
+ */
+int command<output_policy::REDIRECTED>::get_stdin(int c) {
+  return m_stdin[c];
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/concurrency.cpp b/src/utils/concurrency.cpp
new file mode 100644 (file)
index 0000000..ee320af
--- /dev/null
@@ -0,0 +1,31 @@
+#include "utils/concurrency.hpp"
+
+POLYBAR_NS
+
+bool spin_lock::no_backoff_strategy::operator()() {
+  return true;
+}
+bool spin_lock::yield_backoff_strategy::operator()() {
+  this_thread::yield();
+  return false;
+}
+void spin_lock::lock() noexcept {
+  lock(no_backoff_strategy{});
+}
+void spin_lock::unlock() noexcept {
+  m_locked.clear(std::memory_order_release);
+}
+
+namespace concurrency_util {
+  size_t thread_id(const thread::id id) {
+    static size_t idx{1_z};
+    static mutex_wrapper<map<thread::id, size_t>> ids;
+    std::lock_guard<decltype(ids)> lock(ids);
+    if (ids.find(id) == ids.end()) {
+      ids[id] = idx++;
+    }
+    return ids[id];
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/env.cpp b/src/utils/env.cpp
new file mode 100644 (file)
index 0000000..575ffb4
--- /dev/null
@@ -0,0 +1,22 @@
+#include <cstdlib>
+#include <cstring>
+#include <thread>
+#include <utility>
+
+#include "utils/env.hpp"
+
+POLYBAR_NS
+
+namespace env_util {
+  bool has(const string& var) {
+    const char* env{std::getenv(var.c_str())};
+    return env != nullptr && std::strlen(env) > 0;
+  }
+
+  string get(const string& var, string fallback) {
+    const char* value{std::getenv(var.c_str())};
+    return value != nullptr ? value : move(fallback);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/factory.cpp b/src/utils/factory.cpp
new file mode 100644 (file)
index 0000000..1ae2835
--- /dev/null
@@ -0,0 +1,8 @@
+#include "utils/factory.hpp"
+
+POLYBAR_NS
+
+factory_util::detail::null_deleter factory_util::null_deleter{};
+factory_util::detail::fd_deleter factory_util::fd_deleter{};
+
+POLYBAR_NS_END
diff --git a/src/utils/file.cpp b/src/utils/file.cpp
new file mode 100644 (file)
index 0000000..756d057
--- /dev/null
@@ -0,0 +1,338 @@
+#include "utils/file.hpp"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <glob.h>
+#include <sys/stat.h>
+
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include <streambuf>
+
+#include "errors.hpp"
+#include "utils/env.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+// implementation of file_ptr {{{
+
+file_ptr::file_ptr(const string& path, const string& mode) : m_path(string(path)), m_mode(string(mode)) {
+  m_ptr = fopen(m_path.c_str(), m_mode.c_str());
+}
+
+file_ptr::~file_ptr() {
+  if (m_ptr != nullptr) {
+    fclose(m_ptr);
+  }
+}
+
+file_ptr::operator bool() {
+  return static_cast<const file_ptr&>(*this);
+}
+file_ptr::operator bool() const {
+  return m_ptr != nullptr;
+}
+
+file_ptr::operator FILE*() {
+  return static_cast<const file_ptr&>(*this);
+}
+file_ptr::operator FILE*() const {
+  return m_ptr;
+}
+
+file_ptr::operator int() {
+  return static_cast<const file_ptr&>(*this);
+}
+file_ptr::operator int() const {
+  return fileno(*this);
+}
+
+// }}}
+// implementation of file_descriptor {{{
+
+file_descriptor::file_descriptor(const string& path, int flags) {
+  if ((m_fd = open(path.c_str(), flags)) == -1) {
+    throw system_error("Failed to open file descriptor");
+  }
+}
+
+file_descriptor::file_descriptor(int fd, bool autoclose) : m_fd(fd), m_autoclose(autoclose) {
+  if (m_fd != -1 && !*this) {
+    throw system_error("Given file descriptor (" + to_string(m_fd) + ") is not valid");
+  }
+}
+
+file_descriptor::~file_descriptor() {
+  if (m_autoclose) {
+    close();
+  }
+}
+
+file_descriptor& file_descriptor::operator=(const int fd) {
+  if (m_autoclose) {
+    close();
+  }
+  m_fd = fd;
+  return *this;
+}
+
+file_descriptor::operator int() {
+  return static_cast<const file_descriptor&>(*this);
+}
+file_descriptor::operator int() const {
+  return m_fd;
+}
+
+file_descriptor::operator bool() {
+  return static_cast<const file_descriptor&>(*this);
+}
+file_descriptor::operator bool() const {
+  errno = 0;  // reset since fcntl only changes it on error
+  if ((fcntl(m_fd, F_GETFD) == -1) || errno == EBADF) {
+    errno = EBADF;
+    return false;
+  }
+  return true;
+}
+
+void file_descriptor::close() {
+  if (m_fd != -1 && ::close(m_fd) == -1) {
+    throw system_error("Failed to close file descriptor");
+  }
+  m_fd = -1;
+}
+
+// }}}
+// implementation of file_streambuf {{{
+
+fd_streambuf::~fd_streambuf() {
+  close();
+}
+
+fd_streambuf::operator int() {
+  return static_cast<const fd_streambuf&>(*this);
+}
+fd_streambuf::operator int() const {
+  return m_fd;
+}
+
+void fd_streambuf::open(int fd) {
+  if (m_fd) {
+    close();
+  }
+  m_fd = fd;
+  setg(m_in, m_in, m_in);
+  setp(m_out, m_out + bufsize - 1);
+}
+
+void fd_streambuf::close() {
+  if (m_fd) {
+    sync();
+    m_fd = -1;
+  }
+}
+
+int fd_streambuf::sync() {
+  if (pbase() != pptr()) {
+    auto size = pptr() - pbase();
+    auto bytes = write(m_fd, m_out, size);
+    if (bytes > 0) {
+      std::copy(pbase() + bytes, pptr(), pbase());
+      setp(pbase(), epptr());
+      pbump(size - bytes);
+    }
+  }
+  return pptr() != epptr() ? 0 : -1;
+}
+
+int fd_streambuf::overflow(int c) {
+  if (!traits_type::eq_int_type(c, traits_type::eof())) {
+    *pptr() = traits_type::to_char_type(c);
+    pbump(1);
+  }
+  return sync() == -1 ? traits_type::eof() : traits_type::not_eof(c);
+}
+
+int fd_streambuf::underflow() {
+  if (gptr() == egptr()) {
+    std::streamsize pback(std::min(gptr() - eback(), std::ptrdiff_t(16 - sizeof(int))));
+    std::copy(egptr() - pback, egptr(), eback());
+    int bytes(read(m_fd, eback() + pback, bufsize));
+    setg(eback(), eback() + pback, eback() + pback + std::max(0, bytes));
+  }
+  return gptr() == egptr() ? traits_type::eof() : traits_type::to_int_type(*gptr());
+}
+
+// }}}
+
+namespace file_util {
+  /**
+   * Checks if the given file exist
+   *
+   * May also return false if the file status  cannot be read
+   *
+   * Sets errno when returning false
+   */
+  bool exists(const string& filename) {
+    struct stat buffer {};
+    return stat(filename.c_str(), &buffer) == 0;
+  }
+
+  /**
+   * Checks if the given path exists and is a file
+   */
+  bool is_file(const string& filename) {
+    struct stat buffer {};
+
+    if (stat(filename.c_str(), &buffer) != 0) {
+      return false;
+    }
+
+    return S_ISREG(buffer.st_mode);
+  }
+
+  /**
+   * Picks the first existing file out of given entries
+   */
+  string pick(const vector<string>& filenames) {
+    for (auto&& f : filenames) {
+      if (exists(f)) {
+        return f;
+      }
+    }
+    return "";
+  }
+
+  /**
+   * Gets the contents of the given file
+   */
+  string contents(const string& filename) {
+    try {
+      string contents;
+      string line;
+      std::ifstream in(filename, std::ifstream::in);
+      while (std::getline(in, line)) {
+        contents += line + '\n';
+      }
+      return contents;
+    } catch (const std::ifstream::failure& e) {
+      return "";
+    }
+  }
+
+  /**
+   * Writes the contents of the given file
+   */
+  void write_contents(const string& filename, const string& contents) {
+    std::ofstream out(filename, std::ofstream::out);
+    if (!(out << contents)) {
+      throw std::system_error(errno, std::system_category(), "failed to write to " + filename);
+    }
+  }
+
+  /**
+   * Checks if the given file is a named pipe
+   */
+  bool is_fifo(const string& filename) {
+    struct stat buffer {};
+    return stat(filename.c_str(), &buffer) == 0 && S_ISFIFO(buffer.st_mode);
+  }
+
+  /**
+   * Get glob results using given pattern
+   */
+  vector<string> glob(string pattern) {
+    glob_t result{};
+    vector<string> ret;
+
+    // Manually expand tilde to fix builds using versions of libc
+    // that doesn't provide the GLOB_TILDE flag (musl for example)
+    if (pattern[0] == '~') {
+      pattern.replace(0, 1, env_util::get("HOME"));
+    }
+
+    if (::glob(pattern.c_str(), 0, nullptr, &result) == 0) {
+      for (size_t i = 0_z; i < result.gl_pathc; ++i) {
+        ret.emplace_back(result.gl_pathv[i]);
+      }
+      globfree(&result);
+    }
+
+    return ret;
+  }
+
+  /**
+   * Path expansion
+   */
+  const string expand(const string& path) {
+    string ret;
+    /*
+     * This doesn't capture all cases for absolute paths but the other cases
+     * (tilde and env variable) have the initial '/' character in their
+     * expansion already and will thus not require adding '/' to the beginning.
+     */
+    bool is_absolute = path.size() > 0 && path.at(0) == '/';
+    vector<string> p_exploded = string_util::split(path, '/');
+    for (auto& section : p_exploded) {
+      switch (section[0]) {
+        case '$':
+          section = env_util::get(section.substr(1));
+          break;
+        case '~':
+          section = env_util::get("HOME");
+          break;
+      }
+    }
+    ret = string_util::join(p_exploded, "/");
+    // Don't add an initial slash for relative paths
+    if (ret[0] != '/' && is_absolute) {
+      ret.insert(0, 1, '/');
+    }
+    return ret;
+  }
+
+  /*
+   * Search for polybar config and returns the path if found
+   */
+  string get_config_path() {
+    string confpath;
+    if (env_util::has("XDG_CONFIG_HOME")) {
+      confpath = env_util::get("XDG_CONFIG_HOME") + "/polybar/config";
+      if (exists(confpath)) {
+        return confpath;
+      }
+    }
+    if (env_util::has("HOME")) {
+      confpath = env_util::get("HOME") + "/.config/polybar/config";
+      if (exists(confpath)) {
+        return confpath;
+      }
+    }
+    return "";
+  }
+
+  /**
+   * Return a list of file names in a directory.
+   */
+  vector<string> list_files(const string& dirname) {
+    vector<string> files;
+    DIR* dir;
+    if ((dir = opendir(dirname.c_str())) != NULL) {
+      struct dirent* ent;
+      while ((ent = readdir(dir)) != NULL) {
+        // Type can be unknown for filesystems that do not support d_type
+        if ((ent->d_type & DT_REG) ||
+            ((ent->d_type & DT_UNKNOWN) && strcmp(ent->d_name, ".") != 0 && strcmp(ent->d_name, "..") != 0)) {
+          files.push_back(ent->d_name);
+        }
+      }
+      closedir(dir);
+      return files;
+    }
+    throw system_error("Failed to open directory stream for " + dirname);
+  }
+}  // namespace file_util
+
+POLYBAR_NS_END
diff --git a/src/utils/http.cpp b/src/utils/http.cpp
new file mode 100644 (file)
index 0000000..fdfd7c6
--- /dev/null
@@ -0,0 +1,59 @@
+#include "utils/http.hpp"
+
+#include <curl/curl.h>
+#include <curl/easy.h>
+
+#include <sstream>
+
+#include "errors.hpp"
+#include "settings.hpp"
+
+POLYBAR_NS
+
+http_downloader::http_downloader(int connection_timeout) {
+  m_curl = curl_easy_init();
+  curl_easy_setopt(m_curl, CURLOPT_ACCEPT_ENCODING, "deflate");
+  curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, connection_timeout);
+  curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, true);
+  curl_easy_setopt(m_curl, CURLOPT_NOSIGNAL, true);
+  curl_easy_setopt(m_curl, CURLOPT_USERAGENT, ("polybar/" + string{APP_VERSION}).c_str());
+  curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, http_downloader::write);
+  curl_easy_setopt(m_curl, CURLOPT_FORBID_REUSE, true);
+}
+
+http_downloader::~http_downloader() {
+  curl_easy_cleanup(m_curl);
+}
+
+string http_downloader::get(const string& url, const string& user, const string& password) {
+  std::stringstream out{};
+  curl_easy_setopt(m_curl, CURLOPT_URL, url.c_str());
+  curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &out);
+  if (!user.empty()) {
+    curl_easy_setopt(m_curl, CURLOPT_USERNAME, user.c_str());
+  }
+  if (!password.empty()) {
+    curl_easy_setopt(m_curl, CURLOPT_PASSWORD, password.c_str());
+  }
+
+  auto res = curl_easy_perform(m_curl);
+  if (res != CURLE_OK) {
+    throw application_error(curl_easy_strerror(res), res);
+  }
+
+  return out.str();
+}
+
+long http_downloader::response_code() {
+  long code{0};
+  curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &code);
+  return code;
+}
+
+size_t http_downloader::write(void* p, size_t size, size_t bytes, void* stream) {
+  string data{static_cast<const char*>(p), size * bytes};
+  *(static_cast<std::stringstream*>(stream)) << data << '\n';
+  return size * bytes;
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/i3.cpp b/src/utils/i3.cpp
new file mode 100644 (file)
index 0000000..d78f2fb
--- /dev/null
@@ -0,0 +1,83 @@
+#include <xcb/xcb.h>
+#include <i3ipc++/ipc.hpp>
+
+#include "common.hpp"
+#include "settings.hpp"
+#include "utils/i3.hpp"
+#include "utils/socket.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+
+POLYBAR_NS
+
+namespace i3_util {
+  /**
+   * Get all workspaces for given output
+   */
+  vector<shared_ptr<workspace_t>> workspaces(const connection_t& conn, const string& output) {
+    vector<shared_ptr<workspace_t>> result;
+    for (auto&& ws : conn.get_workspaces()) {
+      if (output.empty() || ws->output == output) {
+        result.emplace_back(forward<decltype(ws)>(ws));
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Get currently focused workspace
+   */
+  shared_ptr<workspace_t> focused_workspace(const connection_t& conn) {
+    for (auto&& ws : conn.get_workspaces()) {
+      if (ws->focused) {
+        return ws;
+      }
+    }
+    return nullptr;
+  }
+
+  /**
+   * Get main root window
+   */
+  xcb_window_t root_window(connection& conn) {
+    auto children = conn.query_tree(conn.screen()->root).children();
+    const auto wm_name = [&](xcb_connection_t* conn, xcb_window_t win) -> string {
+      string title;
+      if (!(title = ewmh_util::get_wm_name(win)).empty()) {
+        return title;
+      } else if (!(title = icccm_util::get_wm_name(conn, win)).empty()) {
+        return title;
+      } else {
+        return "";
+      }
+    };
+
+    for (auto it = children.begin(); it != children.end(); it++) {
+      if (wm_name(conn, *it) == "i3") {
+        return *it;
+      }
+    }
+
+    return XCB_NONE;
+  }
+
+  /**
+   * Restack given window relative to the i3 root window
+   * defined for the given monitor
+   *
+   * Fixes the issue with always-on-top window's
+   */
+  bool restack_to_root(connection& conn, const xcb_window_t win) {
+    const unsigned int value_mask = XCB_CONFIG_WINDOW_SIBLING | XCB_CONFIG_WINDOW_STACK_MODE;
+    const unsigned int value_list[2]{root_window(conn), XCB_STACK_MODE_ABOVE};
+    if (value_list[0] != XCB_NONE) {
+      conn.configure_window_checked(win, value_mask, value_list);
+      return true;
+    }
+    return false;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/inotify.cpp b/src/utils/inotify.cpp
new file mode 100644 (file)
index 0000000..7c3b174
--- /dev/null
@@ -0,0 +1,120 @@
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/inotify.hpp"
+#include "utils/memory.hpp"
+
+POLYBAR_NS
+
+/**
+ * Construct inotify watch
+ */
+inotify_watch::inotify_watch(string path) : m_path(move(path)) {}
+
+/**
+ * Deconstruct inotify watch
+ */
+inotify_watch::~inotify_watch() {
+  if (m_wd != -1) {
+    inotify_rm_watch(m_fd, m_wd);
+  }
+  if (m_fd != -1) {
+    close(m_fd);
+  }
+}
+
+/**
+ * Attach inotify watch
+ */
+void inotify_watch::attach(int mask) {
+  if (m_fd == -1 && (m_fd = inotify_init()) == -1) {
+    throw system_error("Failed to allocate inotify fd");
+  }
+  if ((m_wd = inotify_add_watch(m_fd, m_path.c_str(), mask)) == -1) {
+    throw system_error("Failed to attach inotify watch");
+  }
+  m_mask |= mask;
+}
+
+/**
+ * Remove inotify watch
+ */
+void inotify_watch::remove(bool force) {
+  if (inotify_rm_watch(m_fd, m_wd) == -1 && !force) {
+    throw system_error("Failed to remove inotify watch");
+  }
+  m_wd = -1;
+  m_mask = 0;
+}
+
+/**
+ * Poll the inotify fd for events
+ *
+ * \brief A wait_ms of -1 blocks until an event is fired
+ */
+bool inotify_watch::poll(int wait_ms) const {
+  if (m_fd == -1) {
+    return false;
+  }
+
+  struct pollfd fds[1];
+  fds[0].fd = m_fd;
+  fds[0].events = POLLIN;
+
+  ::poll(fds, 1, wait_ms);
+
+  return fds[0].revents & POLLIN;
+}
+
+/**
+ * Get the latest inotify event
+ */
+unique_ptr<inotify_event> inotify_watch::get_event() const {
+  auto event = factory_util::unique<inotify_event>();
+
+  if (m_fd == -1 || m_wd == -1) {
+    return event;
+  }
+
+  char buffer[1024];
+  auto bytes = read(m_fd, buffer, 1024);
+  auto len = 0;
+
+  while (len < bytes) {
+    auto* e = reinterpret_cast<::inotify_event*>(&buffer[len]);
+
+    event->filename = e->len ? e->name : m_path;
+    event->wd = e->wd;
+    event->cookie = e->cookie;
+    event->is_dir = e->mask & IN_ISDIR;
+    event->mask |= e->mask;
+
+    len += sizeof(*e) + e->len;
+  }
+
+  return event;
+}
+
+/**
+ * Wait for matching event
+ */
+unique_ptr<inotify_event> inotify_watch::await_match() const {
+  auto event = get_event();
+  return event->mask & m_mask ? std::move(event) : nullptr;
+}
+
+/**
+ * Get watch file path
+ */
+const string inotify_watch::path() const {
+  return m_path;
+}
+
+/**
+ * Get the file descriptor associated with the watch
+ */
+int inotify_watch::get_file_descriptor() const {
+  return m_fd;
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/io.cpp b/src/utils/io.cpp
new file mode 100644 (file)
index 0000000..8e3dc01
--- /dev/null
@@ -0,0 +1,96 @@
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+#include <cstdio>
+#include <cstdlib>
+#include <iomanip>
+
+#include "errors.hpp"
+#include "utils/file.hpp"
+#include "utils/io.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace io_util {
+  string read(int read_fd, size_t bytes_to_read) {
+    fd_stream<std::istream> in(read_fd, false);
+    char buffer[BUFSIZ];
+    in.getline(buffer, bytes_to_read);
+    string out{buffer};
+    return out;
+  }
+
+  string readline(int read_fd) {
+    fd_stream<std::istream> in(read_fd, false);
+    string out;
+    std::getline(in, out);
+    return out;
+  }
+
+  size_t write(int write_fd, size_t bytes_to_write, const string& data) {
+    fd_stream<std::ostream> out(write_fd, false);
+    out.write(data.c_str(), bytes_to_write).flush();
+    return out.good() ? data.size() : 0;
+  }
+
+  size_t writeline(int write_fd, const string& data) {
+    fd_stream<std::ostream> out(write_fd, false);
+    if (data[data.size() - 1] != '\n') {
+      out << data << std::endl;
+    } else {
+      out << data << std::flush;
+    }
+    return out.good() ? data.size() : 0;
+  }
+
+  void tail(int read_fd, const function<void(string)>& callback) {
+    string line;
+    fd_stream<std::istream> in(read_fd, false);
+    while (std::getline(in, line)) {
+      callback(move(line));
+    }
+  }
+
+  void tail(int read_fd, int writeback_fd) {
+    tail(read_fd, [&](string data) { io_util::writeline(writeback_fd, data); });
+  }
+
+  bool poll(int fd, short int events, int timeout_ms) {
+    struct pollfd fds[1];
+    fds[0].fd = fd;
+    fds[0].events = events;
+    ::poll(fds, 1, timeout_ms);
+    return fds[0].revents & events;
+  }
+
+  bool poll_read(int fd, int timeout_ms) {
+    return poll(fd, POLLIN, timeout_ms);
+  }
+
+  bool poll_write(int fd, int timeout_ms) {
+    return poll(fd, POLLOUT, timeout_ms);
+  }
+
+  bool interrupt_read(int write_fd) {
+    return write(write_fd, 1, {'\n'}) > 0;
+  }
+
+  void set_block(int fd) {
+    int flags = fcntl(fd, F_GETFL, 0);
+    flags &= ~O_NONBLOCK;
+    if (fcntl(fd, F_SETFL, flags) == -1) {
+      throw system_error("Failed to unset O_NONBLOCK");
+    }
+  }
+
+  void set_nonblock(int fd) {
+    int flags = fcntl(fd, F_GETFL, 0);
+    flags |= O_NONBLOCK;
+    if (fcntl(fd, F_SETFL, flags) == -1) {
+      throw system_error("Failed to set O_NONBLOCK");
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/process.cpp b/src/utils/process.cpp
new file mode 100644 (file)
index 0000000..1127c60
--- /dev/null
@@ -0,0 +1,199 @@
+#include "utils/process.hpp"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/env.hpp"
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace process_util {
+  /**
+   * Check if currently in main process
+   */
+  bool in_parent_process(pid_t pid) {
+    return pid != -1 && pid != 0;
+  }
+
+  /**
+   * Check if currently in subprocess
+   */
+  bool in_forked_process(pid_t pid) {
+    return pid == 0;
+  }
+
+  /**
+   * Redirects all io fds (stdin, stdout, stderr) of the current process to /dev/null.
+   */
+  void redirect_stdio_to_dev_null() {
+    auto redirect = [](int fd_to_redirect) {
+      int fd = open("/dev/null", O_WRONLY);
+      if (fd < 0 || dup2(fd, fd_to_redirect) < 0) {
+        throw system_error("Failed to redirect process output");
+      }
+      close(fd);
+    };
+
+    redirect(STDIN_FILENO);
+    redirect(STDOUT_FILENO);
+    redirect(STDERR_FILENO);
+  }
+
+  /**
+   * Forks a child process and executes the given lambda function in it.
+   *
+   * Processes spawned this way need to be waited on by the caller.
+   */
+  pid_t spawn_async(std::function<void()> const& lambda) {
+    pid_t pid = fork();
+    switch (pid) {
+      case -1:
+        throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
+      case 0:
+        // Child
+        setsid();
+        umask(0);
+        redirect_stdio_to_dev_null();
+        lambda();
+        _Exit(0);
+        break;
+      default:
+        return pid;
+    }
+  }
+
+  /**
+   * Forks a child process and completely detaches it.
+   *
+   * In the child process, the given lambda function is executed.
+   * We fork twice so that the first forked process can exit and it's child is
+   * reparented to the init process.
+   *
+   * Ref: https://web.archive.org/web/20120914180018/http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
+   *
+   * Use this if you want to run a command and just forget about it.
+   *
+   * \returns The PID of the child process
+   */
+  void fork_detached(std::function<void()> const& lambda) {
+    pid_t pid = fork();
+    switch (pid) {
+      case -1:
+        throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
+      case 0:
+        // Child
+        setsid();
+
+        pid = fork();
+        switch (pid) {
+          case -1:
+            throw runtime_error("fork_detached: Unable to fork: " + string(strerror(errno)));
+          case 0:
+            // Child
+            umask(0);
+            redirect_stdio_to_dev_null();
+            lambda();
+            _Exit(0);
+        }
+
+        _Exit(0);
+      default:
+        /*
+         * The first fork immediately exits and we have to collect its exit
+         * status
+         */
+        wait(pid);
+    }
+  }
+
+  /**
+   * Execute command
+   */
+  void exec(char* cmd, char** args) {
+    if (cmd != nullptr) {
+      execvp(cmd, args);
+      throw system_error("execvp() failed");
+    }
+  }
+
+  /**
+   * Execute command using shell
+   */
+  void exec_sh(const char* cmd) {
+    if (cmd != nullptr) {
+      static const string shell{env_util::get("POLYBAR_SHELL", "/bin/sh")};
+      execlp(shell.c_str(), shell.c_str(), "-c", cmd, nullptr);
+      throw system_error("execlp() failed");
+    }
+  }
+
+  int wait(pid_t pid) {
+    int forkstatus;
+    do {
+      process_util::wait_for_completion(pid, &forkstatus, WCONTINUED | WUNTRACED);
+    } while (!WIFEXITED(forkstatus) && !WIFSIGNALED(forkstatus));
+
+    return WEXITSTATUS(forkstatus);
+  }
+
+  /**
+   * Wait for child process
+   */
+  pid_t wait_for_completion(pid_t process_id, int* status_addr, int waitflags) {
+    int saved_errno = errno;
+    auto retval = waitpid(process_id, status_addr, waitflags);
+    errno = saved_errno;
+    return retval;
+  }
+
+  /**
+   * Wait for child process
+   */
+  pid_t wait_for_completion(int* status_addr, int waitflags) {
+    return wait_for_completion(-1, status_addr, waitflags);
+  }
+
+  /**
+   * Non-blocking wait
+   *
+   * \see wait_for_completion
+   */
+  pid_t wait_for_completion_nohang(pid_t process_id, int* status) {
+    return wait_for_completion(process_id, status, WNOHANG);
+  }
+
+  /**
+   * Non-blocking wait
+   *
+   * \see wait_for_completion
+   */
+  pid_t wait_for_completion_nohang(int* status) {
+    return wait_for_completion_nohang(-1, status);
+  }
+
+  /**
+   * Non-blocking wait
+   *
+   * \see wait_for_completion
+   */
+  pid_t wait_for_completion_nohang() {
+    int status = 0;
+    return wait_for_completion_nohang(-1, &status);
+  }
+
+  /**
+   * Non-blocking wait call which returns pid of any child process
+   *
+   * \see wait_for_completion
+   */
+  bool notify_childprocess() {
+    return wait_for_completion_nohang() > 0;
+  }
+}  // namespace process_util
+
+POLYBAR_NS_END
diff --git a/src/utils/socket.cpp b/src/utils/socket.cpp
new file mode 100644 (file)
index 0000000..9d6e2e2
--- /dev/null
@@ -0,0 +1,120 @@
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include "errors.hpp"
+#include "utils/file.hpp"
+#include "utils/mixins.hpp"
+#include "utils/socket.hpp"
+
+POLYBAR_NS
+
+using std::snprintf;
+using std::strlen;
+
+namespace socket_util {
+  /**
+   * Constructor: establishing socket connection
+   */
+  unix_connection::unix_connection(string&& path) : m_socketpath(path) {
+    struct sockaddr_un socket_addr {};
+    socket_addr.sun_family = AF_UNIX;
+
+    if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+      throw system_error("Failed to open unix connection");
+    }
+
+    snprintf(socket_addr.sun_path, sizeof(socket_addr.sun_path), "%s", m_socketpath.c_str());
+    auto len = sizeof(socket_addr);
+
+    if (connect(m_fd, reinterpret_cast<struct sockaddr*>(&socket_addr), len) == -1) {
+      throw system_error("Failed to connect to socket");
+    }
+  }
+
+  /**
+   * Destructor: closes file descriptor
+   */
+  unix_connection::~unix_connection() noexcept {
+    if (m_fd != -1) {
+      close(m_fd);
+    }
+  }
+
+  /**
+   * Close reading end of connection
+   */
+  int unix_connection::disconnect() {
+    return shutdown(m_fd, SHUT_RD);
+  }
+
+  /**
+   * Transmit fixed size data
+   */
+  ssize_t unix_connection::send(const void* data, size_t len, int flags) {
+    ssize_t bytes_sent = 0;
+
+    if ((bytes_sent = ::send(m_fd, data, len, flags)) == -1) {
+      throw system_error("Failed to transmit data");
+    }
+
+    return bytes_sent;
+  }
+
+  /**
+   * Transmit string data
+   */
+  ssize_t unix_connection::send(const string& data, int flags) {
+    return send(data.c_str(), data.length(), flags);
+  }
+
+  /**
+   * Receive data
+   */
+  string unix_connection::receive(const ssize_t receive_bytes, ssize_t* bytes_received, int flags) {
+    char buffer[BUFSIZ];
+
+    if ((*bytes_received = ::recv(m_fd, buffer, receive_bytes, flags)) == -1) {
+      throw system_error("Failed to receive data");
+    } else {
+      buffer[*bytes_received] = 0;
+    }
+
+    return string{buffer};
+  }
+
+  /**
+   * \see receive
+   */
+  string unix_connection::receive(const ssize_t receive_bytes, int flags) {
+    ssize_t bytes{0};
+    return receive(receive_bytes, &bytes, flags);
+  }
+
+  /**
+   * Peek at the specified number of bytes
+   */
+  bool unix_connection::peek(const size_t peek_bytes) {
+    ssize_t bytes_seen{0};
+    receive(peek_bytes, &bytes_seen, MSG_PEEK);
+    return bytes_seen > 0;
+  }
+
+  /**
+   * Poll file descriptor for to check for available data
+   */
+  bool unix_connection::poll(short int events, int timeout_ms) {
+    struct pollfd fds[1];
+    fds[0].fd = m_fd;
+    fds[0].events = events;
+
+    if (::poll(fds, 1, timeout_ms) == -1) {
+      throw system_error("Failed to poll file descriptor");
+    }
+
+    return fds[0].revents & events;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/utils/string.cpp b/src/utils/string.cpp
new file mode 100644 (file)
index 0000000..9df04b8
--- /dev/null
@@ -0,0 +1,317 @@
+#include <algorithm>
+#include <iomanip>
+#include <sstream>
+#include <utility>
+
+#include "utils/string.hpp"
+
+POLYBAR_NS
+
+namespace string_util {
+  /**
+   * Check if haystack contains needle
+   */
+  bool contains(const string& haystack, const string& needle) {
+    return haystack.find(needle) != string::npos;
+  }
+
+  /**
+   * Convert string to uppercase
+   */
+  string upper(const string& s) {
+    string str(s);
+    for (auto& c : str) {
+      c = toupper(c);
+    }
+    return str;
+  }
+
+  /**
+   * Convert string to lowercase
+   */
+  string lower(const string& s) {
+    string str(s);
+    for (auto& c : str) {
+      c = tolower(c);
+    }
+    return str;
+  }
+
+  /**
+   * Test lower case equality
+   */
+  bool compare(const string& s1, const string& s2) {
+    return lower(s1) == lower(s2);
+  }
+
+  /**
+   * Replace first occurence of needle in haystack
+   */
+  string replace(const string& haystack, const string& needle, const string& replacement, size_t start, size_t end) {
+    string str(haystack);
+    string::size_type pos;
+
+    if (needle != replacement && (pos = str.find(needle, start)) != string::npos) {
+      if (end == string::npos || pos < end) {
+        str = str.replace(pos, needle.length(), replacement);
+      }
+    }
+
+    return str;
+  }
+
+  /**
+   * Replace all occurences of needle in haystack
+   */
+  string replace_all(
+      const string& haystack, const string& needle, const string& replacement, size_t start, size_t end) {
+    string result{haystack};
+    string::size_type pos;
+    while ((pos = result.find(needle, start)) != string::npos && pos < result.length() &&
+           (end == string::npos || pos + needle.length() <= end)) {
+      result.replace(pos, needle.length(), replacement);
+      start = pos + replacement.length();
+    }
+    return result;
+  }
+
+  /**
+   * Replace all consecutive occurrences of needle in haystack
+   */
+  string squeeze(const string& haystack, char needle) {
+    string result = haystack;
+    while (result.find({needle, needle}) != string::npos) {
+      result = replace_all(result, {needle, needle}, {needle});
+    }
+    return result;
+  }
+
+  /**
+   * Remove all occurrences of needle in haystack
+   */
+  string strip(const string& haystack, char needle) {
+    string str(haystack);
+    string::size_type pos;
+    while ((pos = str.find(needle)) != string::npos) {
+      str.erase(pos, 1);
+    }
+    return str;
+  }
+
+  /**
+   * Remove trailing newline
+   */
+  string strip_trailing_newline(const string& haystack) {
+    string str(haystack);
+    if (str[str.length() - 1] == '\n') {
+      str.erase(str.length() - 1, 1);
+    }
+    return str;
+  }
+
+  /**
+   * Trims all characters that match pred from the left
+   */
+  string ltrim(string value, function<bool(char)> pred) {
+    value.erase(value.begin(), find_if(value.begin(), value.end(), not1(pred)));
+    return value;
+  }
+
+  /**
+   * Trims all characters that match pred from the right
+   */
+  string rtrim(string value, function<bool(char)> pred) {
+    value.erase(find_if(value.rbegin(), value.rend(), not1(pred)).base(), value.end());
+    return value;
+  }
+
+  /**
+   * Trims all characters that match pred from both sides
+   */
+  string trim(string value, function<bool(char)> pred) {
+    return ltrim(rtrim(move(value), pred), pred);
+  }
+
+  /**
+   * Remove needle from the start of the string
+   */
+  string ltrim(string&& value, const char& needle) {
+    if (value.empty()) {
+      return "";
+    }
+    while (*value.begin() == needle) {
+      value.erase(0, 1);
+    }
+    return forward<string>(value);
+  }
+
+  /**
+   * Remove needle from the end of the string
+   */
+  string rtrim(string&& value, const char& needle) {
+    if (value.empty()) {
+      return "";
+    }
+    while (*(value.end() - 1) == needle) {
+      value.erase(value.length() - 1, 1);
+    }
+    return forward<string>(value);
+  }
+
+  /**
+   * Remove needle from the start and end of the string
+   */
+  string trim(string&& value, const char& needle) {
+    if (value.empty()) {
+      return "";
+    }
+    return rtrim(ltrim(forward<string>(value), needle), needle);
+  }
+
+  /**
+   * Counts the number of codepoints in a utf8 encoded string.
+   */
+  size_t char_len(const string& value) {
+    // utf-8 bytes of the form 10xxxxxx are continuation bytes, so we
+    // simply count the number of bytes not of this form.
+    //
+    // 0xc0 = 11000000
+    // 0x80 = 10000000
+    return std::count_if(value.begin(), value.end(), [](char c) { return (c & 0xc0) != 0x80; });
+  }
+
+  /**
+   * Truncates a utf8 string at len number of codepoints. This isn't 100%
+   * matching the user-perceived character count, but it should be close
+   * enough and avoids having to pull in something like ICU to count actual
+   * grapheme clusters.
+   */
+  string utf8_truncate(string&& value, size_t len) {
+    if (value.empty()) {
+      return "";
+    }
+
+    // utf-8 bytes of the form 10xxxxxx are continuation bytes, so we
+    // simply jump forward to bytes not of that form and truncate starting
+    // at that byte if we've counted too many codepoints
+    //
+    // 0xc0 = 11000000
+    // 0x80 = 10000000
+    auto it = value.begin();
+    auto end = value.end();
+    for (size_t i = 0; i < len; ++i) {
+      if (it == end)
+        break;
+      ++it;
+      it = std::find_if(it, end, [](char c) { return (c & 0xc0) != 0x80; });
+    }
+    value.erase(it, end);
+
+    return forward<string>(value);
+  }
+
+  /**
+   * Join all strings in vector into a single string separated by delim
+   */
+  string join(const vector<string>& strs, const string& delim) {
+    string str;
+    for (auto& s : strs) {
+      str += (str.empty() ? "" : delim) + s;
+    }
+    return str;
+  }
+
+  /**
+   * Explode string by delim, ignore empty tokens
+   */
+  vector<string> split(const string& s, char delim) {
+    std::string::size_type pos = 0;
+    std::vector<std::string> result;
+
+    while ((pos = s.find_first_not_of(delim, pos)) != std::string::npos) {
+      auto nextpos = s.find_first_of(delim, pos);
+      result.emplace_back(s.substr(pos, nextpos - pos));
+      pos = nextpos;
+    }
+
+    return result;
+  }
+
+  /**
+   * Explode string by delim, include empty tokens
+   */
+  std::vector<std::string> tokenize(const string& str, char delimiters) {
+    std::vector<std::string> tokens;
+    std::string::size_type lastPos = 0;
+    auto pos = str.find_first_of(delimiters, lastPos);
+
+    while (pos != std::string::npos && lastPos != std::string::npos) {
+      tokens.emplace_back(str.substr(lastPos, pos - lastPos));
+
+      lastPos = pos + 1;
+      pos = str.find_first_of(delimiters, lastPos);
+    }
+
+    tokens.emplace_back(str.substr(lastPos, pos - lastPos));
+    return tokens;
+  }
+
+  /**
+   * Find the nth occurrence of needle in haystack starting from pos
+   */
+  size_t find_nth(const string& haystack, size_t pos, const string& needle, size_t nth) {
+    size_t found_pos = haystack.find(needle, pos);
+    if (1 == nth || string::npos == found_pos) {
+      return found_pos;
+    }
+    return find_nth(haystack, found_pos + 1, needle, nth - 1);
+  }
+
+  /**
+   * Create a floating point string
+   */
+  string floating_point(double value, size_t precision, bool fixed, const string& locale) {
+    std::stringstream ss;
+    ss.imbue(!locale.empty() ? std::locale(locale.c_str()) : std::locale::classic());
+    ss << std::fixed << std::setprecision(precision) << value;
+    return fixed ? ss.str() : replace(ss.str(), ".00", "");
+  }
+
+  /**
+   * Create a MiB filesize string
+   */
+  string filesize_mib(unsigned long long kibibytes, size_t precision, const string& locale) {
+    return floating_point(kibibytes / 1024.0, precision, true, locale) + " MiB";
+  }
+
+  /**
+   * Create a GiB filesize string
+   */
+  string filesize_gib(unsigned long long kibibytes, size_t precision, const string& locale) {
+    return floating_point(kibibytes / 1024.0 / 1024.0, precision, true, locale) + " GiB";
+  }
+
+  /**
+   * Create a filesize string by converting given bytes to highest unit possible
+   */
+  string filesize(unsigned long long bytes, size_t precision, bool fixed, const string& locale) {
+    vector<string> suffixes{"TB", "GB", "MB", "KB"};
+    string suffix{"B"};
+    double value = bytes;
+    while (!suffixes.empty() && value >= 1024.0) {
+      suffix = suffixes.back();
+      suffixes.pop_back();
+      value /= 1024.0;
+    }
+    return floating_point(value, precision, fixed, locale) + " " + suffix;
+  }
+
+  /**
+   * Compute string hash
+   */
+  hash_type hash(const string& src) {
+    return std::hash<string>()(src);
+  }
+}  // namespace string_util
+
+POLYBAR_NS_END
diff --git a/src/utils/throttle.cpp b/src/utils/throttle.cpp
new file mode 100644 (file)
index 0000000..d1eaf50
--- /dev/null
@@ -0,0 +1,36 @@
+#include <thread>
+
+#include "utils/throttle.hpp"
+
+POLYBAR_NS
+
+namespace throttle_util {
+  namespace strategy {
+    /**
+     * Only pass events when there are slots available
+     */
+    bool try_once_or_leave_yolo::operator()(queue& q, limit l, timewindow /*unused*/) {
+      if (q.size() >= l) {
+        return false;
+      }
+      q.emplace_back(timepoint_clock::now());
+      return true;
+    }
+
+    /**
+     * If no slots are available, wait the required
+     * amount of time for a slot to become available
+     * then let the event pass
+     */
+    bool wait_patiently_by_the_door::operator()(queue& q, limit l, timewindow /*unused*/) {
+      auto now = timepoint_clock::now();
+      q.emplace_back(now);
+      if (q.size() >= l) {
+        std::this_thread::sleep_for(now - q.front());
+      }
+      return true;
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/atoms.cpp b/src/x11/atoms.cpp
new file mode 100644 (file)
index 0000000..fc87c76
--- /dev/null
@@ -0,0 +1,82 @@
+#include "x11/atoms.hpp"
+
+#include <xcb/xcb.h>
+#include <xcb/xcb_atom.h>
+
+xcb_atom_t _NET_SUPPORTED;
+xcb_atom_t _NET_CURRENT_DESKTOP;
+xcb_atom_t _NET_ACTIVE_WINDOW;
+xcb_atom_t _NET_WM_NAME;
+xcb_atom_t _NET_WM_DESKTOP;
+xcb_atom_t _NET_WM_VISIBLE_NAME;
+xcb_atom_t _NET_WM_WINDOW_TYPE;
+xcb_atom_t _NET_WM_WINDOW_TYPE_DOCK;
+xcb_atom_t _NET_WM_WINDOW_TYPE_NORMAL;
+xcb_atom_t _NET_WM_PID;
+xcb_atom_t _NET_WM_STATE;
+xcb_atom_t _NET_WM_STATE_STICKY;
+xcb_atom_t _NET_WM_STATE_SKIP_TASKBAR;
+xcb_atom_t _NET_WM_STATE_ABOVE;
+xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
+xcb_atom_t _NET_WM_STRUT;
+xcb_atom_t _NET_WM_STRUT_PARTIAL;
+xcb_atom_t WM_PROTOCOLS;
+xcb_atom_t WM_DELETE_WINDOW;
+xcb_atom_t _XEMBED;
+xcb_atom_t _XEMBED_INFO;
+xcb_atom_t MANAGER;
+xcb_atom_t WM_STATE;
+xcb_atom_t _NET_SYSTEM_TRAY_OPCODE;
+xcb_atom_t _NET_SYSTEM_TRAY_ORIENTATION;
+xcb_atom_t _NET_SYSTEM_TRAY_VISUAL;
+xcb_atom_t _NET_SYSTEM_TRAY_COLORS;
+xcb_atom_t WM_TAKE_FOCUS;
+xcb_atom_t Backlight;
+xcb_atom_t BACKLIGHT;
+xcb_atom_t _XROOTPMAP_ID;
+xcb_atom_t _XSETROOT_ID;
+xcb_atom_t ESETROOT_PMAP_ID;
+xcb_atom_t _COMPTON_SHADOW;
+xcb_atom_t _NET_WM_WINDOW_OPACITY;
+xcb_atom_t WM_HINTS;
+
+// clang-format off
+cached_atom ATOMS[36] = {
+  {"_NET_SUPPORTED", sizeof("_NET_SUPPORTED") - 1, &_NET_SUPPORTED},
+  {"_NET_CURRENT_DESKTOP", sizeof("_NET_CURRENT_DESKTOP") - 1, &_NET_CURRENT_DESKTOP},
+  {"_NET_ACTIVE_WINDOW", sizeof("_NET_ACTIVE_WINDOW") - 1, &_NET_ACTIVE_WINDOW},
+  {"_NET_WM_NAME", sizeof("_NET_WM_NAME") - 1, &_NET_WM_NAME},
+  {"_NET_WM_DESKTOP", sizeof("_NET_WM_DESKTOP") - 1, &_NET_WM_DESKTOP},
+  {"_NET_WM_VISIBLE_NAME", sizeof("_NET_WM_VISIBLE_NAME") - 1, &_NET_WM_VISIBLE_NAME},
+  {"_NET_WM_WINDOW_TYPE", sizeof("_NET_WM_WINDOW_TYPE") - 1, &_NET_WM_WINDOW_TYPE},
+  {"_NET_WM_WINDOW_TYPE_DOCK", sizeof("_NET_WM_WINDOW_TYPE_DOCK") - 1, &_NET_WM_WINDOW_TYPE_DOCK},
+  {"_NET_WM_WINDOW_TYPE_NORMAL", sizeof("_NET_WM_WINDOW_TYPE_NORMAL") - 1, &_NET_WM_WINDOW_TYPE_NORMAL},
+  {"_NET_WM_PID", sizeof("_NET_WM_PID") - 1, &_NET_WM_PID},
+  {"_NET_WM_STATE", sizeof("_NET_WM_STATE") - 1, &_NET_WM_STATE},
+  {"_NET_WM_STATE_STICKY", sizeof("_NET_WM_STATE_STICKY") - 1, &_NET_WM_STATE_STICKY},
+  {"_NET_WM_STATE_SKIP_TASKBAR", sizeof("_NET_WM_STATE_SKIP_TASKBAR") - 1, &_NET_WM_STATE_SKIP_TASKBAR},
+  {"_NET_WM_STATE_ABOVE", sizeof("_NET_WM_STATE_ABOVE") - 1, &_NET_WM_STATE_ABOVE},
+  {"_NET_WM_STATE_MAXIMIZED_VERT", sizeof("_NET_WM_STATE_MAXIMIZED_VERT") - 1, &_NET_WM_STATE_MAXIMIZED_VERT},
+  {"_NET_WM_STRUT", sizeof("_NET_WM_STRUT") - 1, &_NET_WM_STRUT},
+  {"_NET_WM_STRUT_PARTIAL", sizeof("_NET_WM_STRUT_PARTIAL") - 1, &_NET_WM_STRUT_PARTIAL},
+  {"WM_PROTOCOLS", sizeof("WM_PROTOCOLS") - 1, &WM_PROTOCOLS},
+  {"WM_DELETE_WINDOW", sizeof("WM_DELETE_WINDOW") - 1, &WM_DELETE_WINDOW},
+  {"_XEMBED", sizeof("_XEMBED") - 1, &_XEMBED},
+  {"_XEMBED_INFO", sizeof("_XEMBED_INFO") - 1, &_XEMBED_INFO},
+  {"MANAGER", sizeof("MANAGER") - 1, &MANAGER},
+  {"WM_STATE", sizeof("WM_STATE") - 1, &WM_STATE},
+  {"_NET_SYSTEM_TRAY_OPCODE", sizeof("_NET_SYSTEM_TRAY_OPCODE") - 1, &_NET_SYSTEM_TRAY_OPCODE},
+  {"_NET_SYSTEM_TRAY_ORIENTATION", sizeof("_NET_SYSTEM_TRAY_ORIENTATION") - 1, &_NET_SYSTEM_TRAY_ORIENTATION},
+  {"_NET_SYSTEM_TRAY_VISUAL", sizeof("_NET_SYSTEM_TRAY_VISUAL") - 1, &_NET_SYSTEM_TRAY_VISUAL},
+  {"_NET_SYSTEM_TRAY_COLORS", sizeof("_NET_SYSTEM_TRAY_COLORS") - 1, &_NET_SYSTEM_TRAY_COLORS},
+  {"WM_TAKE_FOCUS", sizeof("WM_TAKE_FOCUS") - 1, &WM_TAKE_FOCUS},
+  {"Backlight", sizeof("Backlight") - 1, &Backlight},
+  {"BACKLIGHT", sizeof("BACKLIGHT") - 1, &BACKLIGHT},
+  {"_XROOTPMAP_ID", sizeof("_XROOTPMAP_ID") - 1, &_XROOTPMAP_ID},
+  {"_XSETROOT_ID", sizeof("_XSETROOT_ID") - 1, &_XSETROOT_ID},
+  {"ESETROOT_PMAP_ID", sizeof("ESETROOT_PMAP_ID") - 1, &ESETROOT_PMAP_ID},
+  {"_COMPTON_SHADOW", sizeof("_COMPTON_SHADOW") - 1, &_COMPTON_SHADOW},
+  {"_NET_WM_WINDOW_OPACITY", sizeof("_NET_WM_WINDOW_OPACITY") - 1, &_NET_WM_WINDOW_OPACITY},
+  {"WM_HINTS", sizeof("WM_HINTS") - 1, &WM_HINTS},
+};
+// clang-format on
diff --git a/src/x11/background_manager.cpp b/src/x11/background_manager.cpp
new file mode 100644 (file)
index 0000000..e8e1064
--- /dev/null
@@ -0,0 +1,205 @@
+#include "cairo/surface.hpp"
+#include "cairo/context.hpp"
+#include "events/signal.hpp"
+#include "components/config.hpp"
+#include "components/logger.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/background_manager.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+
+POLYBAR_NS
+
+background_manager& background_manager::make() {
+  return *factory_util::singleton<background_manager>(connection::make(), signal_emitter::make(), logger::make());
+}
+
+background_manager::background_manager(
+    connection& conn, signal_emitter& sig, const logger& log)
+  : m_connection(conn)
+  , m_sig(sig)
+  , m_log(log) {
+  m_sig.attach(this);
+}
+
+background_manager::~background_manager() {
+  m_sig.detach(this);
+  free_resources();
+}
+
+std::shared_ptr<bg_slice> background_manager::observe(xcb_rectangle_t rect, xcb_window_t window) {
+  // allocate a slice
+  activate();
+  auto slice = std::shared_ptr<bg_slice>(new bg_slice(m_connection, m_log, rect, window, m_visual));
+
+  // make sure that we receive a notification when the background changes
+  if(!m_attached) {
+    m_connection.ensure_event_mask(m_connection.root(), XCB_EVENT_MASK_PROPERTY_CHANGE);
+    m_connection.flush();
+    m_connection.attach_sink(this, SINK_PRIORITY_SCREEN);
+    m_attached = true;
+  }
+
+  // if the slice is empty, don't add to slices
+  if (slice->m_rect.width == 0 || slice->m_rect.height == 0) {
+    return slice;
+  }
+
+  m_slices.push_back(slice);
+  fetch_root_pixmap();
+  return slice;
+}
+
+void background_manager::deactivate() {
+  if(m_attached) {
+    m_connection.detach_sink(this, SINK_PRIORITY_SCREEN);
+    m_attached = false;
+  }
+  free_resources();
+}
+
+
+void background_manager::activate() {
+  if(!m_visual) {
+    m_log.trace("background_manager: Finding root visual");
+    m_visual = m_connection.visual_type_for_id(m_connection.screen(), m_connection.screen()->root_visual);
+    m_log.trace("background_manager: Got root visual with depth %d", m_connection.screen()->root_depth);
+  }
+}
+
+void background_manager::free_resources() {
+  m_visual = nullptr;
+}
+
+void background_manager::fetch_root_pixmap() {
+  m_log.trace("background_manager: Fetching pixmap");
+
+  int pixmap_depth;
+  xcb_pixmap_t pixmap;
+  xcb_rectangle_t pixmap_geom;
+
+  try {
+    if (!m_connection.root_pixmap(&pixmap, &pixmap_depth, &pixmap_geom)) {
+      return m_log.warn("background_manager: Failed to get root pixmap, default to black (is there a wallpaper?)");
+    };
+    m_log.trace("background_manager: root pixmap (%d:%d) %dx%d+%d+%d", pixmap, pixmap_depth,
+                pixmap_geom.width, pixmap_geom.height, pixmap_geom.x, pixmap_geom.y);
+
+    if (pixmap_depth == 1 && pixmap_geom.width == 1 && pixmap_geom.height == 1) {
+      return m_log.err("background_manager: Cannot find root pixmap, try a different tool to set the desktop background");
+    }
+
+    for (auto it = m_slices.begin(); it != m_slices.end(); ) {
+      auto slice = it->lock();
+      if (!slice) {
+        it = m_slices.erase(it);
+        continue;
+      }
+
+      // fill the slice
+      auto translated = m_connection.translate_coordinates(slice->m_window, m_connection.screen()->root, slice->m_rect.x, slice->m_rect.y);
+      auto src_x = math_util::cap(translated->dst_x, pixmap_geom.x, int16_t(pixmap_geom.x + pixmap_geom.width));
+      auto src_y = math_util::cap(translated->dst_y, pixmap_geom.y, int16_t(pixmap_geom.y + pixmap_geom.height));
+      auto w = math_util::cap(slice->m_rect.width, uint16_t(0), uint16_t(pixmap_geom.width - (src_x - pixmap_geom.x)));
+      auto h = math_util::cap(slice->m_rect.height, uint16_t(0), uint16_t(pixmap_geom.height - (src_y - pixmap_geom.y)));
+      m_log.trace("background_manager: Copying from root pixmap (%d:%d) %dx%d+%d+%d", pixmap, pixmap_depth, w, h, src_x, src_y);
+      m_connection.copy_area_checked(pixmap, slice->m_pixmap, slice->m_gcontext, src_x, src_y, 0, 0, w, h);
+
+      it++;
+    }
+
+    // if there are no active slices, deactivate
+    if (m_slices.empty()) {
+      m_log.trace("background_manager: deactivating because there are no slices to observe");
+      deactivate();
+    }
+
+  } catch(const exception& err) {
+    m_log.err("background_manager: Failed to copy slice of root pixmap (%s)", err.what());
+    throw;
+  }
+
+}
+
+void background_manager::handle(const evt::property_notify& evt) {
+  // if there are no slices to observe, don't do anything
+  if(m_slices.empty()) {
+    return;
+  }
+
+  if (evt->atom == _XROOTPMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID) {
+    fetch_root_pixmap();
+    m_sig.emit(signals::ui::update_background());
+  }
+}
+
+bool background_manager::on(const signals::ui::update_geometry&) {
+  // if there are no slices to observe, don't do anything
+  if(m_slices.empty()) {
+    return false;
+  }
+
+  fetch_root_pixmap();
+  m_sig.emit(signals::ui::update_background());
+  return false;
+}
+
+
+bg_slice::bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window, xcb_visualtype_t* visual)
+  : m_connection(conn)
+  , m_rect(rect)
+  , m_window(window) {
+  try {
+    allocate_resources(log, visual);
+  } catch(...) {
+    free_resources();
+    throw;
+  }
+}
+
+bg_slice::~bg_slice() {
+  free_resources();
+}
+
+void bg_slice::allocate_resources(const logger& log, xcb_visualtype_t* visual) {
+  if(m_pixmap == XCB_NONE) {
+    log.trace("background_manager: Allocating pixmap");
+    m_pixmap = m_connection.generate_id();
+    m_connection.create_pixmap(m_connection.screen()->root_depth, m_pixmap, m_window, m_rect.width, m_rect.height);
+  }
+
+  if(m_gcontext == XCB_NONE) {
+    log.trace("background_manager: Allocating graphics context");
+    auto black_pixel = m_connection.screen()->black_pixel;
+    unsigned int mask = XCB_GC_GRAPHICS_EXPOSURES | XCB_GC_FOREGROUND | XCB_GC_BACKGROUND;
+    unsigned int value_list[3] = {black_pixel, black_pixel, 0};
+    m_gcontext = m_connection.generate_id();
+    m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list);
+  }
+
+  if(!m_surface) {
+    log.trace("background_manager: Allocating cairo surface");
+    m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, m_rect.width, m_rect.height);
+  }
+
+  // fill (to provide a default in the case that fetching the background fails)
+  xcb_rectangle_t rect{0, 0, m_rect.width, m_rect.height};
+  m_connection.poly_fill_rectangle(m_pixmap, m_gcontext, 1, &rect);
+}
+
+void bg_slice::free_resources() {
+  m_surface.release();
+
+  if(m_pixmap != XCB_NONE) {
+    m_connection.free_pixmap(m_pixmap);
+    m_pixmap = XCB_NONE;
+  }
+
+  if(m_gcontext != XCB_NONE) {
+    m_connection.free_gc(m_gcontext);
+    m_gcontext = XCB_NONE;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/connection.cpp b/src/x11/connection.cpp
new file mode 100644 (file)
index 0000000..cdb26d6
--- /dev/null
@@ -0,0 +1,246 @@
+#include <algorithm>
+#include <iomanip>
+
+#include "errors.hpp"
+#include "utils/factory.hpp"
+#include "utils/memory.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+/**
+ * Create instance
+ */
+connection::make_type connection::make(xcb_connection_t* conn, int default_screen) {
+  return static_cast<connection::make_type>(
+      *factory_util::singleton<std::remove_reference_t<connection::make_type>>(conn, default_screen));
+}
+
+connection::connection(xcb_connection_t* c, int default_screen) : base_type(c, default_screen) {
+  // Preload required xcb atoms {{{
+
+  vector<xcb_intern_atom_cookie_t> cookies(memory_util::countof(ATOMS));
+  xcb_intern_atom_reply_t* reply{nullptr};
+
+  for (size_t i = 0; i < cookies.size(); i++) {
+    cookies[i] = xcb_intern_atom_unchecked(*this, false, ATOMS[i].len, ATOMS[i].name);
+  }
+
+  for (size_t i = 0; i < cookies.size(); i++) {
+    if ((reply = xcb_intern_atom_reply(*this, cookies[i], nullptr)) != nullptr) {
+      *ATOMS[i].atom = reply->atom;
+    }
+
+    free(reply);
+  }
+
+// }}}
+// Query for X extensions {{{
+#if WITH_XRANDR
+  randr_util::query_extension(*this);
+#endif
+#if WITH_XCOMPOSITE
+  composite_util::query_extension(*this);
+#endif
+#if WITH_XKB
+  xkb_util::query_extension(*this);
+#endif
+  // }}}
+}
+
+connection::~connection() {
+  disconnect();
+}
+
+void connection::pack_values(unsigned int mask, const unsigned int* src, unsigned int* dest) {
+  for (; mask; mask >>= 1, src++) {
+    if (mask & 1) {
+      *dest++ = *src;
+    }
+  }
+}
+void connection::pack_values(unsigned int mask, const xcb_params_cw_t* src, unsigned int* dest) {
+  pack_values(mask, reinterpret_cast<const unsigned int*>(src), dest);
+}
+void connection::pack_values(unsigned int mask, const xcb_params_gc_t* src, unsigned int* dest) {
+  pack_values(mask, reinterpret_cast<const unsigned int*>(src), dest);
+}
+void connection::pack_values(unsigned int mask, const xcb_params_configure_window_t* src, unsigned int* dest) {
+  pack_values(mask, reinterpret_cast<const unsigned int*>(src), dest);
+}
+
+/**
+ * Create X window id string
+ */
+string connection::id(xcb_window_t w) const {
+  return sstream() << "0x" << std::hex << std::setw(7) << std::setfill('0') << w;
+}
+
+/**
+ * Get pointer to the default xcb screen
+ */
+xcb_screen_t* connection::screen(bool realloc) {
+  if (m_screen == nullptr || realloc) {
+    m_screen = screen_of_display(default_screen());
+  }
+  return m_screen;
+}
+
+/**
+ * Add given event to the event mask unless already added
+ */
+void connection::ensure_event_mask(xcb_window_t win, unsigned int event) {
+  auto attributes = get_window_attributes(win);
+  attributes->your_event_mask = attributes->your_event_mask | event;
+  change_window_attributes(win, XCB_CW_EVENT_MASK, &attributes->your_event_mask);
+}
+
+/**
+ * Clear event mask for the given window
+ */
+void connection::clear_event_mask(xcb_window_t win) {
+  unsigned int mask{XCB_EVENT_MASK_NO_EVENT};
+  change_window_attributes(win, XCB_CW_EVENT_MASK, &mask);
+}
+
+/**
+ * Creates an instance of shared_ptr<xcb_client_message_event_t>
+ */
+shared_ptr<xcb_client_message_event_t> connection::make_client_message(xcb_atom_t type, xcb_window_t target) const {
+  auto client_message = memory_util::make_malloc_ptr<xcb_client_message_event_t, 32_z>();
+
+  client_message->response_type = XCB_CLIENT_MESSAGE;
+  client_message->format = 32;
+  client_message->type = type;
+  client_message->window = target;
+
+  client_message->sequence = 0;
+  client_message->data.data32[0] = 0;
+  client_message->data.data32[1] = 0;
+  client_message->data.data32[2] = 0;
+  client_message->data.data32[3] = 0;
+  client_message->data.data32[4] = 0;
+
+  return client_message;
+}
+
+/**
+ * Send client message event
+ */
+void connection::send_client_message(const shared_ptr<xcb_client_message_event_t>& message, xcb_window_t target,
+    unsigned int event_mask, bool propagate) const {
+  send_event(propagate, target, event_mask, reinterpret_cast<const char*>(&*message));
+  flush();
+}
+
+/**
+ * Try to get a visual type for the given screen that
+ * matches the given depth
+ */
+xcb_visualtype_t* connection::visual_type(xcb_screen_t* screen, int match_depth) {
+  xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(screen);
+  if (depth_iter.data) {
+    for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
+      if (match_depth == 0 || match_depth == depth_iter.data->depth) {
+        for (auto it = xcb_depth_visuals_iterator(depth_iter.data); it.rem; xcb_visualtype_next(&it)) {
+          return it.data;
+        }
+      }
+    }
+    if (match_depth > 0) {
+      return visual_type(screen, 0);
+    }
+  }
+  return nullptr;
+}
+
+
+xcb_visualtype_t* connection::visual_type_for_id(xcb_screen_t* screen, xcb_visualid_t visual_id) {
+  xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(screen);
+  if (depth_iter.data) {
+    for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
+      for (auto it = xcb_depth_visuals_iterator(depth_iter.data); it.rem; xcb_visualtype_next(&it)) {
+        if(it.data->visual_id == visual_id) return it.data;
+      }
+    }
+  }
+  return nullptr;
+}
+
+/**
+ * Query root window pixmap
+ */
+bool connection::root_pixmap(xcb_pixmap_t* pixmap, int* depth, xcb_rectangle_t* rect) {
+  *pixmap = XCB_NONE; // default value if getting the root pixmap fails
+
+  /*
+   * We try multiple properties for the root pixmap here because I am not 100% sure
+   * if all programs set them the same way. We might be able to just use _XSETROOT_ID
+   * but keeping the other as fallback should not hurt (if it does, feel free to remove).
+   *
+   * see https://metacpan.org/pod/X11::Protocol::XSetRoot#ROOT-WINDOW-PROPERTIES for description of the properties
+   * the properties here are ordered by reliability:
+   *    _XSETROOT_ID: this is usually a dummy 1x1 pixmap only for resource managment, use only as last resort
+   *    ESETROOT_PMAP_ID: according to the link above, this should usually by equal to _XROOTPMAP_ID
+   *    _XROOTPMAP_ID: this appears to be the "correct" property to use? if available, use this
+   */
+  const xcb_atom_t pixmap_properties[3]{_XROOTPMAP_ID, ESETROOT_PMAP_ID, _XSETROOT_ID};
+  for (auto&& property : pixmap_properties) {
+    try {
+      auto prop = get_property(false, screen()->root, property, XCB_ATOM_PIXMAP, 0L, 1L);
+      if (prop->format == 32 && prop->value_len == 1) {
+        *pixmap = *prop.value<xcb_pixmap_t>().begin();
+      }
+
+      if (*pixmap) {
+        auto geom = get_geometry(*pixmap);
+        *depth = geom->depth;
+        rect->width = geom->width;
+        rect->height = geom->height;
+        rect->x = geom->x;
+        rect->y = geom->y;
+        return true;
+      }
+    } catch (const exception& err) {
+      *pixmap = XCB_NONE;
+      *rect = xcb_rectangle_t{0, 0, 0U, 0U};
+      continue;
+    }
+  }
+  return false;
+}
+
+/**
+ * Parse connection error
+ */
+string connection::error_str(int error_code) {
+  switch (error_code) {
+    case XCB_CONN_ERROR:
+      return "Socket, pipe or stream error";
+    case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
+      return "Unsupported extension";
+    case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
+      return "Not enough memory";
+    case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
+      return "Request length exceeded";
+    case XCB_CONN_CLOSED_PARSE_ERR:
+      return "Can't parse display string";
+    case XCB_CONN_CLOSED_INVALID_SCREEN:
+      return "Invalid screen";
+    case XCB_CONN_CLOSED_FDPASSING_FAILED:
+      return "Failed to pass FD";
+    default:
+      return "Unknown error";
+  }
+}
+
+/**
+ * Dispatch event through the registry
+ */
+void connection::dispatch_event(const shared_ptr<xcb_generic_event_t>& evt) const {
+  m_registry.dispatch(evt);
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/cursor.cpp b/src/x11/cursor.cpp
new file mode 100644 (file)
index 0000000..253b1ea
--- /dev/null
@@ -0,0 +1,27 @@
+#include "x11/cursor.hpp"
+
+POLYBAR_NS
+
+namespace cursor_util {
+  bool valid(string name) {
+    return (cursors.find(name) != cursors.end());
+  }
+
+  bool set_cursor(xcb_connection_t *c, xcb_screen_t *screen, xcb_window_t w, string name) {
+    xcb_cursor_t cursor = XCB_CURSOR_NONE;
+    xcb_cursor_context_t *ctx;
+
+    if (xcb_cursor_context_new(c, screen, &ctx) < 0) {
+      return false;
+    }
+    for (auto&& cursor_name : cursors.at(name)) {
+      cursor = xcb_cursor_load_cursor(ctx, cursor_name.c_str());
+      if (cursor != XCB_CURSOR_NONE)
+        break;
+    }
+    xcb_change_window_attributes(c, w, XCB_CW_CURSOR, &cursor);
+    xcb_cursor_context_free(ctx);
+    return true;
+  }
+}
+POLYBAR_NS_END
diff --git a/src/x11/ewmh.cpp b/src/x11/ewmh.cpp
new file mode 100644 (file)
index 0000000..143ad99
--- /dev/null
@@ -0,0 +1,185 @@
+#include <unistd.h>
+
+#include "components/types.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+
+POLYBAR_NS
+
+namespace ewmh_util {
+  ewmh_connection_t g_connection{nullptr};
+  ewmh_connection_t initialize() {
+    if (!g_connection) {
+      g_connection = memory_util::make_malloc_ptr<xcb_ewmh_connection_t>(
+          [=](xcb_ewmh_connection_t* c) { xcb_ewmh_connection_wipe(c); });
+      xcb_ewmh_init_atoms_replies(&*g_connection, xcb_ewmh_init_atoms(connection::make(), &*g_connection), nullptr);
+    }
+    return g_connection;
+  }
+
+  bool supports(xcb_atom_t atom, int screen) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_atoms_reply_t reply{};
+    if (xcb_ewmh_get_supported_reply(conn, xcb_ewmh_get_supported(conn, screen), &reply, nullptr)) {
+      for (size_t n = 0; n < reply.atoms_len; n++) {
+        if (reply.atoms[n] == atom) {
+          xcb_ewmh_get_atoms_reply_wipe(&reply);
+          return true;
+        }
+      }
+      xcb_ewmh_get_atoms_reply_wipe(&reply);
+    }
+    return false;
+  }
+
+  string get_wm_name(xcb_window_t win) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_utf8_strings_reply_t utf8_reply{};
+    if (xcb_ewmh_get_wm_name_reply(conn, xcb_ewmh_get_wm_name(conn, win), &utf8_reply, nullptr)) {
+      return get_reply_string(&utf8_reply);
+    }
+    return "";
+  }
+
+  string get_visible_name(xcb_window_t win) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_utf8_strings_reply_t utf8_reply{};
+    if (xcb_ewmh_get_wm_visible_name_reply(conn, xcb_ewmh_get_wm_visible_name(conn, win), &utf8_reply, nullptr)) {
+      return get_reply_string(&utf8_reply);
+    }
+    return "";
+  }
+
+  string get_icon_name(xcb_window_t win) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_utf8_strings_reply_t utf8_reply{};
+    if (xcb_ewmh_get_wm_icon_name_reply(conn, xcb_ewmh_get_wm_icon_name(conn, win), &utf8_reply, nullptr)) {
+      return get_reply_string(&utf8_reply);
+    }
+    return "";
+  }
+
+  string get_reply_string(xcb_ewmh_get_utf8_strings_reply_t* reply) {
+    string str;
+    if (reply) {
+      str = string(reply->strings, reply->strings_len);
+      xcb_ewmh_get_utf8_strings_reply_wipe(reply);
+    }
+    return str;
+  }
+
+  unsigned int get_current_desktop(int screen) {
+    auto conn = initialize().get();
+    unsigned int desktop = XCB_NONE;
+    xcb_ewmh_get_current_desktop_reply(conn, xcb_ewmh_get_current_desktop(conn, screen), &desktop, nullptr);
+    return desktop;
+  }
+
+  unsigned int get_number_of_desktops(int screen) {
+    auto conn = initialize().get();
+    unsigned int desktops = XCB_NONE;
+    xcb_ewmh_get_number_of_desktops_reply(conn, xcb_ewmh_get_number_of_desktops(conn, screen), &desktops, nullptr);
+    return desktops;
+  }
+
+  vector<position> get_desktop_viewports(int screen) {
+    auto conn = initialize().get();
+    vector<position> viewports;
+    xcb_ewmh_get_desktop_viewport_reply_t reply{};
+    if (xcb_ewmh_get_desktop_viewport_reply(conn, xcb_ewmh_get_desktop_viewport(conn, screen), &reply, nullptr)) {
+      for (size_t n = 0; n < reply.desktop_viewport_len; n++) {
+        viewports.emplace_back(position{
+            static_cast<short int>(reply.desktop_viewport[n].x), static_cast<short int>(reply.desktop_viewport[n].y)});
+      }
+    }
+    return viewports;
+  }
+
+  vector<string> get_desktop_names(int screen) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_utf8_strings_reply_t reply{};
+    if (xcb_ewmh_get_desktop_names_reply(conn, xcb_ewmh_get_desktop_names(conn, screen), &reply, nullptr)) {
+      return string_util::split(string(reply.strings, reply.strings_len), '\0');
+    }
+    return {};
+  }
+
+  xcb_window_t get_active_window(int screen) {
+    auto conn = initialize().get();
+    unsigned int win = XCB_NONE;
+    xcb_ewmh_get_active_window_reply(conn, xcb_ewmh_get_active_window(conn, screen), &win, nullptr);
+    return win;
+  }
+
+  void change_current_desktop(unsigned int desktop) {
+    auto conn = initialize().get();
+    xcb_ewmh_request_change_current_desktop(conn, 0, desktop, XCB_CURRENT_TIME);
+    xcb_flush(conn->connection);
+  }
+
+  unsigned int get_desktop_from_window(xcb_window_t window) {
+    auto conn = initialize().get();
+    unsigned int desktop = XCB_NONE;
+    xcb_ewmh_get_wm_desktop_reply(conn, xcb_ewmh_get_wm_desktop(conn, window), &desktop, nullptr);
+    return desktop;
+  }
+
+  void set_wm_window_type(xcb_window_t win, vector<xcb_atom_t> types) {
+    auto conn = initialize().get();
+    xcb_ewmh_set_wm_window_type(conn, win, types.size(), types.data());
+    xcb_flush(conn->connection);
+  }
+
+  void set_wm_state(xcb_window_t win, vector<xcb_atom_t> states) {
+    auto conn = initialize().get();
+    xcb_ewmh_set_wm_state(conn, win, states.size(), states.data());
+    xcb_flush(conn->connection);
+  }
+
+  vector<xcb_atom_t> get_wm_state(xcb_window_t win) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_atoms_reply_t reply;
+    if (xcb_ewmh_get_wm_state_reply(conn, xcb_ewmh_get_wm_state(conn, win), &reply, nullptr)) {
+      return {reply.atoms, reply.atoms + reply.atoms_len};
+    }
+    return {};
+  }
+
+  void set_wm_pid(xcb_window_t win) {
+    auto conn = initialize().get();
+    xcb_ewmh_set_wm_pid(conn, win, getpid());
+    xcb_flush(conn->connection);
+  }
+
+  void set_wm_pid(xcb_window_t win, unsigned int pid) {
+    auto conn = initialize().get();
+    xcb_ewmh_set_wm_pid(conn, win, pid);
+    xcb_flush(conn->connection);
+  }
+
+  void set_wm_desktop(xcb_window_t win, unsigned int desktop) {
+    auto conn = initialize().get();
+    xcb_ewmh_set_wm_desktop(conn, win, desktop);
+    xcb_flush(conn->connection);
+  }
+
+  void set_wm_window_opacity(xcb_window_t win, unsigned long int values) {
+    auto conn = initialize().get();
+    xcb_change_property(
+        conn->connection, XCB_PROP_MODE_REPLACE, win, _NET_WM_WINDOW_OPACITY, XCB_ATOM_CARDINAL, 32, 1, &values);
+    xcb_flush(conn->connection);
+  }
+
+  vector<xcb_window_t> get_client_list(int screen) {
+    auto conn = initialize().get();
+    xcb_ewmh_get_windows_reply_t reply;
+    if (xcb_ewmh_get_client_list_reply(conn, xcb_ewmh_get_client_list(conn, screen), &reply, nullptr)) {
+      return {reply.windows, reply.windows + reply.windows_len};
+    }
+    return {};
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/extensions/composite.cpp b/src/x11/extensions/composite.cpp
new file mode 100644 (file)
index 0000000..d008ee5
--- /dev/null
@@ -0,0 +1,20 @@
+#include "x11/extensions/composite.hpp"
+#include "errors.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+namespace composite_util {
+  /**
+   * Query for the XCOMPOSITE extension
+   */
+  void query_extension(connection& conn) {
+    conn.composite().query_version(XCB_COMPOSITE_MAJOR_VERSION, XCB_COMPOSITE_MINOR_VERSION);
+
+    if (!conn.extension<xpp::composite::extension>()->present) {
+      throw application_error("Missing X extension: Composite");
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/extensions/randr.cpp b/src/x11/extensions/randr.cpp
new file mode 100644 (file)
index 0000000..1baac9b
--- /dev/null
@@ -0,0 +1,267 @@
+#include "x11/extensions/randr.hpp"
+
+#include <algorithm>
+#include <utility>
+
+#include "components/types.hpp"
+#include "errors.hpp"
+#include "settings.hpp"
+#include "utils/string.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+
+POLYBAR_NS
+
+/**
+ * Workaround for the inconsistent naming of outputs between intel and nvidia
+ * drivers (xf86-video-intel drops the dash)
+ *
+ * If exact == false, dashes will be ignored while matching
+ */
+bool randr_output::match(const string& o, bool exact) const {
+  if (exact) {
+    return name == o;
+  } else {
+    return string_util::replace(name, "-", "") == string_util::replace(o, "-", "");
+  }
+}
+
+/**
+ * Match position
+ */
+bool randr_output::match(const position& p) const {
+  return p.x == x && p.y == y;
+}
+
+/**
+ * Checks if `this` contains the position p
+ */
+bool randr_output::contains(const position& p) const {
+  return x <= p.x && y <= p.y && (x + w) > p.x && (y + h) > p.y;
+}
+
+/**
+ * Checks if inner is contained within `this`
+ *
+ * Also returns true for outputs that occupy the exact same space
+ */
+bool randr_output::contains(const randr_output& inner) const {
+  return inner.x >= x && inner.x + inner.w <= x + w && inner.y >= y && inner.y + inner.h <= y + h;
+}
+
+/**
+ * Checks if the given output is the same as this
+ *
+ * Looks at xcb_randr_output_t, position, dimension, name and 'primary'
+ */
+bool randr_output::equals(const randr_output& o) const {
+  return o.output == output && o.x == x && o.y == y && o.w == w && o.h == h && o.primary == primary && o.name == name;
+}
+
+namespace randr_util {
+  /**
+   * XRandR version
+   */
+  static unsigned int g_major_version = 0;
+  static unsigned int g_minor_version = 0;
+
+  /**
+   * Query for the XRandR extension
+   */
+  void query_extension(connection& conn) {
+    auto ext = conn.randr().query_version(XCB_RANDR_MAJOR_VERSION, XCB_RANDR_MINOR_VERSION);
+
+    g_major_version = ext->major_version;
+    g_minor_version = ext->minor_version;
+
+    if (!conn.extension<xpp::randr::extension>()->present) {
+      throw application_error("Missing X extension: Randr");
+    }
+  }
+
+  /**
+   * Check for XRandR monitor support
+   */
+  bool check_monitor_support() {
+    return WITH_XRANDR_MONITORS && g_major_version >= 1 && g_minor_version >= 5;
+  }
+
+  /**
+   * Define monitor
+   */
+  monitor_t make_monitor(xcb_randr_output_t randr, string name, unsigned short int w, unsigned short int h, short int x,
+      short int y, bool primary) {
+    monitor_t mon{new monitor_t::element_type{}};
+    mon->output = randr;
+    mon->name = move(name);
+    mon->x = x;
+    mon->y = y;
+    mon->h = h;
+    mon->w = w;
+    mon->primary = primary;
+    return mon;
+  }
+
+  /**
+   * Create a list of all available randr outputs
+   */
+  vector<monitor_t> get_monitors(connection& conn, xcb_window_t root, bool connected_only, bool purge_clones) {
+    vector<monitor_t> monitors;
+
+#if WITH_XRANDR_MONITORS
+    if (check_monitor_support()) {
+      for (auto&& mon : conn.get_monitors(root, true).monitors()) {
+        try {
+          auto name = conn.get_atom_name(mon.name).name();
+          monitors.emplace_back(make_monitor(XCB_NONE, move(name), mon.width, mon.height, mon.x, mon.y, mon.primary));
+        } catch (const exception&) {
+          // silently ignore output
+        }
+      }
+    }
+#endif
+    auto primary_output = conn.get_output_primary(root).output();
+    string primary_name{};
+
+    if (primary_output != XCB_NONE) {
+      auto primary_info = conn.get_output_info(primary_output);
+      auto name_iter = primary_info.name();
+      primary_name = {name_iter.begin(), name_iter.end()};
+    }
+
+    for (auto&& output : conn.get_screen_resources(root).outputs()) {
+      try {
+        auto info = conn.get_output_info(output);
+        if (info->crtc == XCB_NONE) {
+          continue;
+        } else if (connected_only && info->connection != XCB_RANDR_CONNECTION_CONNECTED) {
+          continue;
+        }
+
+        auto name_iter = info.name();
+        string name{name_iter.begin(), name_iter.end()};
+
+#if WITH_XRANDR_MONITORS
+        if (check_monitor_support()) {
+          auto mon = std::find_if(
+              monitors.begin(), monitors.end(), [&name](const monitor_t& mon) { return mon->name == name; });
+          if (mon != monitors.end()) {
+            (*mon)->output = output;
+            continue;
+          }
+        }
+#endif
+
+        auto crtc = conn.get_crtc_info(info->crtc);
+        auto primary = (primary_name == name);
+        monitors.emplace_back(make_monitor(output, move(name), crtc->width, crtc->height, crtc->x, crtc->y, primary));
+      } catch (const exception&) {
+        // silently ignore output
+      }
+    }
+
+    if (purge_clones) {
+      for (auto& outer : monitors) {
+        if (outer->w == 0) {
+          continue;
+        }
+
+        for (auto& inner : monitors) {
+          if (outer == inner || inner->w == 0) {
+            continue;
+          } else if (check_monitor_support() && (inner->output == XCB_NONE || outer->output == XCB_NONE)) {
+            continue;
+          }
+
+          // If inner is contained in outer, inner is removed
+          // If both happen to be the same size and have the same coordinates,
+          // inner is still removed but it doesn't matter since both occupy the
+          // exact same space
+          if (outer->contains(*inner)) {
+            // Reset width so that the output gets removed afterwards
+            inner->w = 0;
+          }
+        }
+      }
+
+      // Remove all cloned monitors (monitors with 0 width)
+      monitors.erase(
+          std::remove_if(monitors.begin(), monitors.end(), [](const auto& monitor) { return monitor->w == 0; }),
+          monitors.end());
+    }
+
+    return monitors;
+  }
+
+  /**
+   * Searches for a monitor with name in monitors
+   *
+   * Does best-fit matching (if exact_match == true, this is also first-fit)
+   */
+  monitor_t match_monitor(vector<monitor_t> monitors, const string& name, bool exact_match) {
+    monitor_t result{};
+    for (auto&& monitor : monitors) {
+      // If we can do an exact match, we have found our result
+      if (monitor->match(name, true)) {
+        result = move(monitor);
+        break;
+      }
+
+      /*
+       * Non-exact matches are moved into the result but we continue searching
+       * through the list, maybe we can find an exact match
+       * Note: If exact_match == true, we don't need to run this because it
+       * would be the exact same check as above
+       */
+      if (!exact_match && monitor->match(name, false)) {
+        result = move(monitor);
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Get backlight value range for given output
+   */
+  void get_backlight_range(connection& conn, const monitor_t& mon, backlight_values& dst) {
+    auto atom = Backlight;
+    auto reply = conn.query_output_property(mon->output, atom);
+
+    dst.min = 0;
+    dst.max = 0;
+
+    if (!reply->range || reply->length != 2) {
+      atom = BACKLIGHT;
+      reply = conn.query_output_property(mon->output, atom);
+    }
+
+    if (!reply->range || reply->length != 2) {
+      return;
+    }
+
+    auto range = reply.valid_values().begin();
+    dst.min = static_cast<double>(*range++);
+    dst.max = static_cast<double>(*range);
+    dst.atom = atom;
+  }
+
+  /**
+   * Get backlight value for given output
+   */
+  void get_backlight_value(connection& conn, const monitor_t& mon, backlight_values& dst) {
+    dst.val = 0.0;
+
+    if (!dst.atom) {
+      return;
+    }
+
+    auto reply = conn.get_output_property(mon->output, dst.atom, XCB_ATOM_NONE, 0, 4, 0, 0);
+    if (reply->num_items == 1 && reply->format == 32 && reply->type == XCB_ATOM_INTEGER) {
+      int value = *reinterpret_cast<int*>(xcb_randr_get_output_property_data(reply.get().get()));
+      dst.val = static_cast<double>(value);
+    }
+  }
+}  // namespace randr_util
+
+POLYBAR_NS_END
diff --git a/src/x11/extensions/xkb.cpp b/src/x11/extensions/xkb.cpp
new file mode 100644 (file)
index 0000000..8695ce5
--- /dev/null
@@ -0,0 +1,217 @@
+#include "x11/extensions/xkb.hpp"
+
+#include "errors.hpp"
+#include "utils/string.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+
+POLYBAR_NS
+
+/**
+ * Get next layout index
+ */
+const keyboard::indicator& keyboard::get(const indicator::type& i) const {
+  return indicators.at(i);
+}
+
+/**
+ * Update indicator states
+ */
+void keyboard::set(unsigned int state) {
+  for (auto& i : indicators) {
+    i.second.enabled = state & i.second.mask;
+  }
+}
+
+/**
+ * Get state for the given class
+ */
+bool keyboard::on(const indicator::type& i) const {
+  return indicators.at(i).enabled;
+}
+
+/**
+ * Set current group number
+ */
+void keyboard::current(unsigned char group) {
+  current_group = group;
+}
+
+/**
+ * Get current group number
+ */
+unsigned char keyboard::current() const {
+  return current_group;
+}
+
+/**
+ * Get current group name
+ */
+const string keyboard::group_name(size_t index) const {
+  if (!layouts.empty() && index < layouts.size()) {
+    return layouts[index].group_name;
+  }
+  return "";
+}
+
+/**
+ * Get current layout name
+ */
+const string keyboard::layout_name(size_t index) const {
+  if (index >= layouts.size() || index >= layouts[index].symbols.size()) {
+    return "";
+  }
+  return layouts[index].symbols[index];
+}
+
+/**
+ * Get indicator name
+ */
+const string keyboard::indicator_name(const indicator::type& i) const {
+  return indicators.at(i).name;
+}
+
+size_t keyboard::size() const {
+  return layouts.size();
+}
+
+namespace xkb_util {
+  /**
+   * Query for the XKB extension
+   */
+  void query_extension(connection& conn) {
+    conn.xkb().use_extension(XCB_XKB_MAJOR_VERSION, XCB_XKB_MINOR_VERSION);
+
+    if (!conn.extension<xpp::xkb::extension>()->present) {
+      throw application_error("Missing X extension: XKb");
+    }
+  }
+
+  /**
+   * Get current group number
+   */
+  void switch_layout(connection& conn, xcb_xkb_device_spec_t device, unsigned char index) {
+    xcb_xkb_latch_lock_state(conn, device, 0, 0, true, index, 0, 0, 0);
+    xcb_flush(conn);
+  }
+
+  /**
+   * Get current group number
+   */
+  unsigned char get_current_group(connection& conn, xcb_xkb_device_spec_t device) {
+    unsigned char result{0};
+    auto reply = xcb_xkb_get_state_reply(conn, xcb_xkb_get_state(conn, device), nullptr);
+    if (reply != nullptr) {
+      result = reply->group;
+      free(reply);
+    }
+    return result;
+  }
+
+  /**
+   * Get keyboard layouts
+   */
+  vector<keyboard::layout> get_layouts(connection& conn, xcb_xkb_device_spec_t device) {
+    vector<keyboard::layout> results;
+
+    unsigned int mask{XCB_XKB_NAME_DETAIL_GROUP_NAMES | XCB_XKB_NAME_DETAIL_SYMBOLS};
+    auto reply = xcb_xkb_get_names_reply(conn, xcb_xkb_get_names(conn, device, mask), nullptr);
+
+    if (reply == nullptr) {
+      return results;
+    }
+
+    xcb_xkb_get_names_value_list_t values{};
+    void* buffer = xcb_xkb_get_names_value_list(reply);
+    xcb_xkb_get_names_value_list_unpack(buffer, reply->nTypes, reply->indicators, reply->virtualMods, reply->groupNames,
+        reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, &values);
+
+    using get_atom_name_reply = xpp::x::reply::checked::get_atom_name<connection&>;
+    vector<get_atom_name_reply> replies;
+    for (int i = 0; i < xcb_xkb_get_names_value_list_groups_length(reply, &values); i++) {
+      replies.emplace_back(xpp::x::get_atom_name(conn, values.groups[i]));
+    }
+
+    for (const auto& reply : replies) {
+      vector<string> sym_names;
+
+      for (auto&& sym : string_util::split(conn.get_atom_name(values.symbolsName).name(), '+')) {
+        if (!(sym = parse_layout_symbol(move(sym))).empty()) {
+          sym_names.emplace_back(move(sym));
+        }
+      }
+
+      results.emplace_back(keyboard::layout{static_cast<get_atom_name_reply>(reply).name(), sym_names});
+    }
+
+    free(reply);
+
+    return results;
+  }
+
+  /**
+   * Get keyboard indicators
+   */
+  map<keyboard::indicator::type, keyboard::indicator> get_indicators(connection& conn, xcb_xkb_device_spec_t device) {
+    map<keyboard::indicator::type, keyboard::indicator> results;
+
+    unsigned int mask{XCB_XKB_NAME_DETAIL_INDICATOR_NAMES};
+    auto reply = xcb_xkb_get_names_reply(conn, xcb_xkb_get_names(conn, device, mask), nullptr);
+
+    if (reply == nullptr) {
+      return results;
+    }
+
+    xcb_xkb_get_names_value_list_t values{};
+    void* buffer = xcb_xkb_get_names_value_list(reply);
+    xcb_xkb_get_names_value_list_unpack(buffer, reply->nTypes, reply->indicators, reply->virtualMods, reply->groupNames,
+        reply->nKeys, reply->nKeyAliases, reply->nRadioGroups, reply->which, &values);
+
+    using get_atom_name_reply = xpp::x::reply::checked::get_atom_name<connection&>;
+    map<xcb_atom_t, get_atom_name_reply> entries;
+    for (int i = 0; i < xcb_xkb_get_names_value_list_indicator_names_length(reply, &values); i++) {
+      entries.emplace(values.indicatorNames[i], xpp::x::get_atom_name(conn, values.indicatorNames[i]));
+    }
+
+    for (const auto& entry : entries) {
+      auto name = static_cast<get_atom_name_reply>(entry.second).name();
+      auto type = keyboard::indicator::type::NONE;
+
+      if (string_util::compare(name, "caps lock")) {
+        type = keyboard::indicator::type::CAPS_LOCK;
+      } else if (string_util::compare(name, "num lock")) {
+        type = keyboard::indicator::type::NUM_LOCK;
+      } else if (string_util::compare(name, "scroll lock")) {
+        type = keyboard::indicator::type::SCROLL_LOCK;
+      } else {
+        continue;
+      }
+
+      auto data = conn.xkb().get_named_indicator(device, 0, 0, entry.first);
+      auto mask = (*conn.xkb().get_indicator_map(device, 1 << data->ndx).maps().begin()).mods;
+      auto enabled = static_cast<bool>(data->on);
+
+      results.emplace(type, keyboard::indicator{entry.first, mask, name, enabled});
+    }
+
+    free(reply);
+
+    return results;
+  }
+
+  /**
+   * Parse symbol name and exclude entries blacklisted entries
+   */
+  string parse_layout_symbol(string&& name) {
+    size_t pos;
+    while ((pos = name.find_first_of({'(', ':'})) != string::npos) {
+      name.erase(pos);
+    }
+    if (string_util::contains(LAYOUT_SYMBOL_BLACKLIST, ";" + name + ";")) {
+      return "";
+    }
+    return move(name);
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/icccm.cpp b/src/x11/icccm.cpp
new file mode 100644 (file)
index 0000000..a6e292e
--- /dev/null
@@ -0,0 +1,43 @@
+#include "x11/icccm.hpp"
+#include "x11/atoms.hpp"
+
+POLYBAR_NS
+
+namespace icccm_util {
+  string get_wm_name(xcb_connection_t* c, xcb_window_t w) {
+    xcb_icccm_get_text_property_reply_t reply{};
+    if (xcb_icccm_get_wm_name_reply(c, xcb_icccm_get_wm_name(c, w), &reply, nullptr)) {
+      return get_reply_string(&reply);
+    }
+    return "";
+  }
+
+  string get_reply_string(xcb_icccm_get_text_property_reply_t* reply) {
+    string str;
+    if (reply) {
+      str = string(reply->name, reply->name_len);
+      xcb_icccm_get_text_property_reply_wipe(reply);
+    }
+    return str;
+  }
+
+  void set_wm_name(xcb_connection_t* c, xcb_window_t w, const char* wmname, size_t l, const char* wmclass, size_t l2) {
+    xcb_icccm_set_wm_name(c, w, XCB_ATOM_STRING, 8, l, wmname);
+    xcb_icccm_set_wm_class(c, w, l2, wmclass);
+  }
+
+  void set_wm_protocols(xcb_connection_t* c, xcb_window_t w, vector<xcb_atom_t> flags) {
+    xcb_icccm_set_wm_protocols(c, w, WM_PROTOCOLS, flags.size(), flags.data());
+  }
+
+  bool get_wm_urgency(xcb_connection_t* c, xcb_window_t w) {
+    xcb_icccm_wm_hints_t hints;
+    if (xcb_icccm_get_wm_hints_reply(c, xcb_icccm_get_wm_hints(c, w), &hints, NULL)) {
+      if(xcb_icccm_wm_hints_get_urgency(&hints) == XCB_ICCCM_WM_HINT_X_URGENCY)
+        return true;
+    }
+    return false;
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/registry.cpp b/src/x11/registry.cpp
new file mode 100644 (file)
index 0000000..ff703f9
--- /dev/null
@@ -0,0 +1,11 @@
+#include <xpp/event.hpp>
+
+#include "x11/connection.hpp"
+#include "x11/extensions/all.hpp"
+#include "x11/registry.hpp"
+
+POLYBAR_NS
+
+registry::registry(connection& conn) : xpp::event::registry<connection&, XPP_EXTENSION_LIST>(conn) {}
+
+POLYBAR_NS_END
diff --git a/src/x11/tray_client.cpp b/src/x11/tray_client.cpp
new file mode 100644 (file)
index 0000000..9075083
--- /dev/null
@@ -0,0 +1,121 @@
+#include <xcb/xcb.h>
+#include <xcb/xcb_aux.h>
+
+#include "utils/memory.hpp"
+#include "x11/connection.hpp"
+#include "x11/tray_client.hpp"
+#include "x11/xembed.hpp"
+
+POLYBAR_NS
+
+tray_client::tray_client(connection& conn, xcb_window_t win, unsigned int w, unsigned int h)
+    : m_connection(conn), m_window(win), m_width(w), m_height(h) {
+  m_xembed = memory_util::make_malloc_ptr<xembed_data>();
+  m_xembed->version = XEMBED_VERSION;
+  m_xembed->flags = XEMBED_MAPPED;
+}
+
+tray_client::~tray_client() {
+  xembed::unembed(m_connection, window(), m_connection.root());
+}
+
+unsigned int tray_client::width() const {
+  return m_width;
+}
+
+unsigned int tray_client::height() const {
+  return m_height;
+}
+
+void tray_client::clear_window() const {
+  try {
+    m_connection.clear_area_checked(1, window(), 0, 0, width(), height());
+  } catch (const xpp::x::error::window& err) {
+    // ignore
+  }
+}
+
+/**
+ * Match given window against client window
+ */
+bool tray_client::match(const xcb_window_t& win) const {
+  return win == m_window;
+}
+
+/**
+ * Get client window mapped state
+ */
+bool tray_client::mapped() const {
+  return m_mapped;
+}
+
+/**
+ * Set client window mapped state
+ */
+void tray_client::mapped(bool state) {
+  m_mapped = state;
+}
+
+/**
+ * Get client window
+ */
+xcb_window_t tray_client::window() const {
+  return m_window;
+}
+
+/**
+ * Get xembed data pointer
+ */
+xembed_data* tray_client::xembed() const {
+  return m_xembed.get();
+}
+
+/**
+ * Make sure that the window mapping state is correct
+ */
+void tray_client::ensure_state() const {
+  if (!mapped() && ((xembed()->flags & XEMBED_MAPPED) == XEMBED_MAPPED)) {
+    m_connection.map_window_checked(window());
+  } else if (mapped() && ((xembed()->flags & XEMBED_MAPPED) != XEMBED_MAPPED)) {
+    m_connection.unmap_window_checked(window());
+  }
+}
+
+/**
+ * Configure window size
+ */
+void tray_client::reconfigure(int x, int y) const {
+  unsigned int configure_mask = 0;
+  unsigned int configure_values[7];
+  xcb_params_configure_window_t configure_params{};
+
+  XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, width, m_width);
+  XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, height, m_height);
+  XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, x, x);
+  XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, y, y);
+
+  connection::pack_values(configure_mask, &configure_params, configure_values);
+  m_connection.configure_window_checked(window(), configure_mask, configure_values);
+}
+
+/**
+ * Respond to client resize requests
+ */
+void tray_client::configure_notify(int x, int y) const {
+  auto notify = memory_util::make_malloc_ptr<xcb_configure_notify_event_t, 32_z>();
+  notify->response_type = XCB_CONFIGURE_NOTIFY;
+  notify->event = m_window;
+  notify->window = m_window;
+  notify->override_redirect = false;
+  notify->above_sibling = 0;
+  notify->x = x;
+  notify->y = y;
+  notify->width = m_width;
+  notify->height = m_height;
+  notify->border_width = 0;
+
+  unsigned int mask{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+  m_connection.send_event_checked(false, m_window, mask, reinterpret_cast<const char*>(notify.get()));
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/tray_manager.cpp b/src/x11/tray_manager.cpp
new file mode 100644 (file)
index 0000000..5cf503d
--- /dev/null
@@ -0,0 +1,1148 @@
+#include "x11/tray_manager.hpp"
+
+#include <xcb/xcb_image.h>
+
+#include <thread>
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "components/config.hpp"
+#include "errors.hpp"
+#include "events/signal.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "utils/memory.hpp"
+#include "utils/process.hpp"
+#include "x11/background_manager.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+#include "x11/winspec.hpp"
+#include "x11/xembed.hpp"
+
+// ====================================================================================================
+//
+// TODO: 32-bit visual
+//
+// _NET_SYSTEM_TRAY_VISUAL visual_id VISUALID/32
+//
+// The property should be set by the tray manager to indicate the preferred visual for icon windows.
+//
+// To avoid ambiguity about the colormap to use this visual must either be the default visual for
+// the screen or it must be a TrueColor visual. If this property is set to a visual with an alpha
+// channel, the tray manager must use the Composite extension to composite the icon against the
+// background using PictOpOver.
+//
+// ====================================================================================================
+
+POLYBAR_NS
+
+/**
+ * Create instance
+ */
+tray_manager::make_type tray_manager::make() {
+  return factory_util::unique<tray_manager>(
+      connection::make(), signal_emitter::make(), logger::make(), background_manager::make());
+}
+
+tray_manager::tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back)
+    : m_connection(conn), m_sig(emitter), m_log(logger), m_background_manager(back) {
+  m_connection.attach_sink(this, SINK_PRIORITY_TRAY);
+}
+
+tray_manager::~tray_manager() {
+  if (m_delaythread.joinable()) {
+    m_delaythread.join();
+  }
+  m_connection.detach_sink(this, SINK_PRIORITY_TRAY);
+  deactivate();
+}
+
+void tray_manager::setup(const bar_settings& bar_opts) {
+  const config& conf = config::make();
+  auto bs = conf.section();
+  string position;
+
+  try {
+    position = conf.get(bs, "tray-position");
+  } catch (const key_error& err) {
+    return m_log.info("Disabling tray manager (reason: missing `tray-position`)");
+  }
+
+  if (position == "left") {
+    m_opts.align = alignment::LEFT;
+  } else if (position == "right") {
+    m_opts.align = alignment::RIGHT;
+  } else if (position == "center") {
+    m_opts.align = alignment::CENTER;
+  } else if (position != "none") {
+    return m_log.err("Disabling tray manager (reason: Invalid position \"" + position + "\")");
+  } else {
+    return;
+  }
+
+  m_opts.detached = conf.get(bs, "tray-detached", false);
+  m_opts.height = bar_opts.size.h;
+  m_opts.height -= bar_opts.borders.at(edge::BOTTOM).size;
+  m_opts.height -= bar_opts.borders.at(edge::TOP).size;
+  m_opts.height_fill = m_opts.height;
+
+  if (m_opts.height % 2 != 0) {
+    m_opts.height--;
+  }
+
+  auto maxsize = conf.get<unsigned int>(bs, "tray-maxsize", 16);
+  if (m_opts.height > maxsize) {
+    m_opts.spacing += (m_opts.height - maxsize) / 2;
+    m_opts.height = maxsize;
+  }
+
+  m_opts.width_max = bar_opts.size.w;
+  m_opts.width = m_opts.height;
+  m_opts.orig_y = bar_opts.pos.y + bar_opts.borders.at(edge::TOP).size;
+
+  // Apply user-defined scaling
+  auto scale = conf.get(bs, "tray-scale", 1.0);
+  m_opts.width *= scale;
+  m_opts.height_fill *= scale;
+
+  auto inner_area = bar_opts.inner_area(true);
+
+  switch (m_opts.align) {
+    case alignment::NONE:
+      break;
+    case alignment::LEFT:
+      m_opts.orig_x = inner_area.x;
+      break;
+    case alignment::CENTER:
+      m_opts.orig_x = inner_area.x + inner_area.width / 2 - m_opts.width / 2;
+      break;
+    case alignment::RIGHT:
+      m_opts.orig_x = inner_area.x + inner_area.width;
+      break;
+  }
+
+  if (conf.has(bs, "tray-transparent")) {
+    m_log.warn("tray-transparent is deprecated, the tray always uses pseudo-transparency. Please remove it.");
+  }
+
+  // Set user-defined background color
+  m_opts.background = conf.get(bs, "tray-background", bar_opts.background);
+
+  if (m_opts.background.alpha_i() != 255) {
+    m_log.trace("tray: enable transparency");
+    m_opts.transparent = true;
+  }
+
+  // Add user-defined padding
+  m_opts.spacing += conf.get<unsigned int>(bs, "tray-padding", 0);
+
+  // Add user-defiend offset
+  auto offset_x_def = conf.get(bs, "tray-offset-x", ""s);
+  auto offset_y_def = conf.get(bs, "tray-offset-y", ""s);
+
+  auto offset_x = strtol(offset_x_def.c_str(), nullptr, 10);
+  auto offset_y = strtol(offset_y_def.c_str(), nullptr, 10);
+
+  if (offset_x != 0 && offset_x_def.find('%') != string::npos) {
+    if (m_opts.detached) {
+      offset_x = math_util::signed_percentage_to_value<int>(offset_x, bar_opts.monitor->w);
+    } else {
+      offset_x = math_util::signed_percentage_to_value<int>(offset_x, inner_area.width);
+    }
+  }
+
+  if (offset_y != 0 && offset_y_def.find('%') != string::npos) {
+    if (m_opts.detached) {
+      offset_y = math_util::signed_percentage_to_value<int>(offset_y, bar_opts.monitor->h);
+    } else {
+      offset_y = math_util::signed_percentage_to_value<int>(offset_y, inner_area.height);
+    }
+  }
+
+  m_opts.orig_x += offset_x;
+  m_opts.orig_y += offset_y;
+  m_opts.rel_x = m_opts.orig_x - bar_opts.pos.x;
+  m_opts.rel_y = m_opts.orig_y - bar_opts.pos.y;
+
+  // Put the tray next to the bar in the window stack
+  m_opts.sibling = bar_opts.window;
+
+  // Activate the tray manager
+  query_atom();
+  activate();
+}
+
+/**
+ * Get the settings container
+ */
+const tray_settings tray_manager::settings() const {
+  return m_opts;
+}
+
+/**
+ * Activate systray management
+ */
+void tray_manager::activate() {
+  if (m_activated) {
+    return;
+  }
+
+  m_log.info("Activating tray manager");
+  m_activated = true;
+  m_opts.running = true;
+
+  m_sig.attach(this);
+
+  try {
+    create_window();
+    create_bg();
+    restack_window();
+    set_wm_hints();
+    set_tray_colors();
+  } catch (const exception& err) {
+    m_log.err(err.what());
+    m_log.err("Cannot activate tray manager... failed to setup window");
+    m_activated = false;
+    return;
+  }
+
+  // Attempt to get control of the systray selection then
+  // notify clients waiting for a manager.
+  acquire_selection();
+
+  if (!m_acquired_selection) {
+    deactivate();
+    return;
+  }
+
+  // Send delayed notification
+  if (!m_firstactivation) {
+    notify_clients();
+  } else {
+    notify_clients_delayed();
+  }
+
+  m_firstactivation = false;
+}
+
+/**
+ * Deactivate systray management
+ */
+void tray_manager::deactivate(bool clear_selection) {
+  if (!m_activated) {
+    return;
+  }
+
+  m_log.info("Deactivating tray manager");
+  m_activated = false;
+  m_opts.running = false;
+
+  m_sig.detach(this);
+
+  if (!m_connection.connection_has_error() && clear_selection && m_acquired_selection) {
+    m_log.trace("tray: Unset selection owner");
+    m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
+  }
+
+  m_log.trace("tray: Unembed clients");
+  m_clients.clear();
+
+  if (m_tray) {
+    m_log.trace("tray: Destroy window");
+    m_connection.destroy_window(m_tray);
+  }
+  m_context.release();
+  m_surface.release();
+  if (m_pixmap) {
+    m_connection.free_pixmap(m_pixmap);
+  }
+  if (m_gc) {
+    m_connection.free_gc(m_pixmap);
+  }
+
+  m_tray = 0;
+  m_pixmap = 0;
+  m_gc = 0;
+  m_prevwidth = 0;
+  m_prevheight = 0;
+  m_opts.configured_x = 0;
+  m_opts.configured_y = 0;
+  m_opts.configured_w = 0;
+  m_opts.configured_h = 0;
+  m_opts.configured_slots = 0;
+  m_acquired_selection = false;
+  m_mapped = false;
+
+  m_connection.flush();
+
+  m_sig.emit(signals::eventqueue::notify_forcechange{});
+}
+
+/**
+ * Reconfigure tray
+ */
+void tray_manager::reconfigure() {
+  if (!m_tray) {
+    return;
+  } else if (m_mtx.try_lock()) {
+    std::unique_lock<mutex> guard(m_mtx, std::adopt_lock);
+
+    try {
+      reconfigure_clients();
+    } catch (const exception& err) {
+      m_log.err("Failed to reconfigure tray clients (%s)", err.what());
+    }
+    try {
+      reconfigure_window();
+    } catch (const exception& err) {
+      m_log.err("Failed to reconfigure tray window (%s)", err.what());
+    }
+    try {
+      reconfigure_bg();
+    } catch (const exception& err) {
+      m_log.err("Failed to reconfigure tray background (%s)", err.what());
+    }
+
+    m_opts.configured_slots = mapped_clients();
+    guard.unlock();
+    refresh_window();
+    m_connection.flush();
+  }
+
+  m_sig.emit(signals::eventqueue::notify_forcechange{});
+}
+
+/**
+ * Reconfigure container window
+ */
+void tray_manager::reconfigure_window() {
+  m_log.trace("tray: Reconfigure window (mapped=%i, clients=%i)", static_cast<bool>(m_mapped), m_clients.size());
+
+  if (!m_tray) {
+    return;
+  }
+
+  auto clients = mapped_clients();
+  if (!clients && m_mapped) {
+    m_log.trace("tray: Reconfigure window / unmap");
+    m_connection.unmap_window_checked(m_tray);
+  } else if (clients && !m_mapped && !m_hidden) {
+    m_log.trace("tray: Reconfigure window / map");
+    m_connection.map_window_checked(m_tray);
+  }
+
+  auto width = calculate_w();
+  auto x = calculate_x(width);
+
+  if (m_opts.transparent) {
+    xcb_rectangle_t rect{0, 0, calculate_w(), calculate_h()};
+    m_bg_slice = m_background_manager.observe(rect, m_tray);
+  }
+
+  if (width > 0) {
+    m_log.trace("tray: New window values, width=%d, x=%d", width, x);
+
+    unsigned int mask = 0;
+    unsigned int values[7];
+    xcb_params_configure_window_t params{};
+
+    XCB_AUX_ADD_PARAM(&mask, &params, width, width);
+    XCB_AUX_ADD_PARAM(&mask, &params, x, x);
+    connection::pack_values(mask, &params, values);
+    m_connection.configure_window_checked(m_tray, mask, values);
+  }
+
+  m_opts.configured_w = width;
+  m_opts.configured_x = x;
+}
+
+/**
+ * Reconfigure clients
+ */
+void tray_manager::reconfigure_clients() {
+  m_log.trace("tray: Reconfigure clients");
+
+  int x = m_opts.spacing;
+
+  for (auto it = m_clients.rbegin(); it != m_clients.rend(); it++) {
+    auto client = *it;
+
+    try {
+      client->ensure_state();
+      client->reconfigure(x, calculate_client_y());
+
+      x += m_opts.width + m_opts.spacing;
+    } catch (const xpp::x::error::window& err) {
+      remove_client(client, false);
+    }
+  }
+}
+
+/**
+ * Reconfigure root pixmap
+ */
+void tray_manager::reconfigure_bg(bool realloc) {
+  if (!m_opts.transparent || m_clients.empty() || !m_mapped) {
+    return;
+  };
+
+  m_log.trace("tray: Reconfigure bg (realloc=%i)", realloc);
+
+  if (!m_context) {
+    return m_log.err("tray: no context for drawing the background");
+  }
+
+  cairo::surface* surface = m_bg_slice->get_surface();
+  if (!surface) {
+    return m_log.err("tray: no root surface");
+  }
+
+  m_context->clear();
+  *m_context << CAIRO_OPERATOR_SOURCE << *m_surface;
+  cairo_set_source_surface(*m_context, *surface, 0, 0);
+  m_context->paint();
+  *m_context << CAIRO_OPERATOR_OVER << m_opts.background;
+  m_context->paint();
+}
+
+/**
+ * Refresh the bar window by clearing it along with each client window
+ */
+void tray_manager::refresh_window() {
+  if (!m_activated || !m_mapped || !m_mtx.try_lock()) {
+    return;
+  }
+
+  std::lock_guard<mutex> lock(m_mtx, std::adopt_lock);
+
+  m_log.trace("tray: Refreshing window");
+
+  auto width = calculate_w();
+  auto height = calculate_h();
+
+  if (m_opts.transparent && !m_context) {
+    xcb_rectangle_t rect{0, 0, static_cast<uint16_t>(width), static_cast<uint16_t>(height)};
+    m_connection.poly_fill_rectangle(m_pixmap, m_gc, 1, &rect);
+  }
+
+  if (m_surface)
+    m_surface->flush();
+
+  m_connection.clear_area(0, m_tray, 0, 0, width, height);
+
+  for (auto&& client : m_clients) {
+    client->clear_window();
+  }
+
+  m_connection.flush();
+
+  if (!mapped_clients()) {
+    m_opts.configured_w = 0;
+  } else {
+    m_opts.configured_w = width;
+  }
+}
+
+/**
+ * Redraw window
+ */
+void tray_manager::redraw_window(bool realloc_bg) {
+  m_log.info("Redraw tray container (id=%s)", m_connection.id(m_tray));
+  reconfigure_bg(realloc_bg);
+  refresh_window();
+}
+
+/**
+ * Find the systray selection atom
+ */
+void tray_manager::query_atom() {
+  m_log.trace("tray: Find systray selection atom for the default screen");
+  string name{"_NET_SYSTEM_TRAY_S" + to_string(m_connection.default_screen())};
+  auto reply = m_connection.intern_atom(false, name.length(), name.c_str());
+  m_atom = reply.atom();
+}
+
+/**
+ * Create tray window
+ */
+void tray_manager::create_window() {
+  m_log.trace("tray: Create tray window");
+
+  // clang-format off
+  auto win = winspec(m_connection, m_tray)
+    << cw_size(calculate_w(), calculate_h())
+    << cw_pos(calculate_x(calculate_w()), calculate_y())
+    << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT)
+    << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED)
+    << cw_params_event_mask(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
+                           |XCB_EVENT_MASK_STRUCTURE_NOTIFY
+                           |XCB_EVENT_MASK_EXPOSURE)
+    << cw_params_override_redirect(true);
+  // clang-format on
+
+  if (!m_opts.transparent) {
+    win << cw_params_back_pixel(m_opts.background);
+    win << cw_params_border_pixel(m_opts.background);
+  }
+
+  m_tray = win << cw_flush(true);
+  m_log.info("Tray window: %s", m_connection.id(m_tray));
+
+  // activate the background manager if we have transparency
+  if (m_opts.transparent) {
+    xcb_rectangle_t rect{0, 0, calculate_w(), calculate_h()};
+    m_bg_slice = m_background_manager.observe(rect, m_tray);
+  }
+
+  const unsigned int shadow{0};
+  m_connection.change_property(XCB_PROP_MODE_REPLACE, m_tray, _COMPTON_SHADOW, XCB_ATOM_CARDINAL, 32, 1, &shadow);
+}
+
+/**
+ * Create tray window background components
+ */
+void tray_manager::create_bg(bool realloc) {
+  if (!m_opts.transparent) {
+    return;
+  }
+  if (!realloc && m_pixmap && m_gc && m_surface && m_context) {
+    return;
+  }
+  if (realloc && m_pixmap) {
+    m_connection.free_pixmap(m_pixmap);
+    m_pixmap = 0;
+  }
+  if (realloc && m_gc) {
+    m_connection.free_gc(m_gc);
+    m_gc = 0;
+  }
+
+  if (realloc && m_surface) {
+    m_surface.release();
+  }
+  if (realloc && m_context) {
+    m_context.release();
+  }
+
+  auto w = m_opts.width_max;
+  auto h = calculate_h();
+
+  if (!m_pixmap) {
+    try {
+      m_pixmap = m_connection.generate_id();
+      m_connection.create_pixmap_checked(m_connection.screen()->root_depth, m_pixmap, m_tray, w, h);
+    } catch (const exception& err) {
+      return m_log.err("Failed to create pixmap for tray background (err: %s)", err.what());
+    }
+  }
+
+  if (!m_gc) {
+    try {
+      xcb_params_gc_t params{};
+      unsigned int mask = 0;
+      unsigned int values[32];
+      XCB_AUX_ADD_PARAM(&mask, &params, graphics_exposures, 1);
+      connection::pack_values(mask, &params, values);
+      m_gc = m_connection.generate_id();
+      m_connection.create_gc_checked(m_gc, m_pixmap, mask, values);
+    } catch (const exception& err) {
+      return m_log.err("Failed to create gcontext for tray background (err: %s)", err.what());
+    }
+  }
+
+  if (!m_surface) {
+    xcb_visualtype_t* visual =
+        m_connection.visual_type_for_id(m_connection.screen(), m_connection.screen()->root_visual);
+    if (!visual) {
+      return m_log.err("Failed to get root visual for tray background");
+    }
+    m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, w, h);
+  }
+
+  if (!m_context) {
+    m_context = make_unique<cairo::context>(*m_surface, m_log);
+    m_context->clear();
+    *m_context << CAIRO_OPERATOR_SOURCE << m_opts.background;
+    m_context->paint();
+  }
+
+  try {
+    m_connection.change_window_attributes_checked(m_tray, XCB_CW_BACK_PIXMAP, &m_pixmap);
+  } catch (const exception& err) {
+    m_log.err("Failed to set tray window back pixmap (%s)", err.what());
+  }
+}
+
+/**
+ * Put tray window above the defined sibling in the window stack
+ */
+void tray_manager::restack_window() {
+  if (m_opts.sibling == XCB_NONE) {
+    return;
+  }
+
+  try {
+    m_log.trace("tray: Restacking tray window");
+
+    unsigned int mask = 0;
+    unsigned int values[7];
+    xcb_params_configure_window_t params{};
+
+    XCB_AUX_ADD_PARAM(&mask, &params, sibling, m_opts.sibling);
+    XCB_AUX_ADD_PARAM(&mask, &params, stack_mode, XCB_STACK_MODE_ABOVE);
+
+    connection::pack_values(mask, &params, values);
+    m_connection.configure_window_checked(m_tray, mask, values);
+  } catch (const exception& err) {
+    auto id = m_connection.id(m_opts.sibling);
+    m_log.err("tray: Failed to put tray above %s in the stack (%s)", id, err.what());
+  }
+}
+
+/**
+ * Set window WM hints
+ */
+void tray_manager::set_wm_hints() {
+  const unsigned int visual{m_connection.screen()->root_visual};
+  const unsigned int orientation{_NET_SYSTEM_TRAY_ORIENTATION_HORZ};
+
+  m_log.trace("bar: Set window WM_NAME / WM_CLASS");
+  icccm_util::set_wm_name(m_connection, m_tray, TRAY_WM_NAME, 19_z, TRAY_WM_CLASS, 12_z);
+
+  m_log.trace("tray: Set window WM_PROTOCOLS");
+  icccm_util::set_wm_protocols(m_connection, m_tray, {WM_DELETE_WINDOW, WM_TAKE_FOCUS});
+
+  m_log.trace("tray: Set window _NET_WM_WINDOW_TYPE");
+  ewmh_util::set_wm_window_type(m_tray, {_NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_NORMAL});
+
+  m_log.trace("tray: Set window _NET_WM_STATE");
+  ewmh_util::set_wm_state(m_tray, {_NET_WM_STATE_SKIP_TASKBAR});
+
+  m_log.trace("tray: Set window _NET_WM_PID");
+  ewmh_util::set_wm_pid(m_tray);
+
+  m_log.trace("tray: Set window _NET_SYSTEM_TRAY_VISUAL");
+  xcb_change_property(
+      m_connection, XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_VISUAL, XCB_ATOM_VISUALID, 32, 1, &visual);
+
+  m_log.trace("tray: Set window _NET_SYSTEM_TRAY_ORIENTATION");
+  xcb_change_property(m_connection, XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_ORIENTATION,
+      _NET_SYSTEM_TRAY_ORIENTATION, 32, 1, &orientation);
+}
+
+/**
+ * Set color atom used by clients when determing icon theme
+ */
+void tray_manager::set_tray_colors() {
+  m_log.trace("tray: Set _NET_SYSTEM_TRAY_COLORS to %x", m_opts.background);
+
+  auto r = m_opts.background.red_i();
+  auto g = m_opts.background.green_i();
+  auto b = m_opts.background.blue_i();
+
+  const unsigned int colors[12] = {
+      r, g, b,  // normal
+      r, g, b,  // error
+      r, g, b,  // warning
+      r, g, b,  // success
+  };
+
+  m_connection.change_property(
+      XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32, 12, colors);
+}
+
+/**
+ * Acquire the systray selection
+ */
+void tray_manager::acquire_selection() {
+  m_othermanager = XCB_NONE;
+  xcb_window_t owner;
+
+  try {
+    owner = m_connection.get_selection_owner(m_atom).owner<xcb_window_t>();
+  } catch (const exception& err) {
+    return;
+  }
+
+  if (owner == m_tray) {
+    m_log.trace("tray: Already managing the systray selection");
+    m_acquired_selection = true;
+  } else if ((m_othermanager = owner) != XCB_NONE) {
+    m_log.warn("Systray selection already managed (window=%s)", m_connection.id(owner));
+    track_selection_owner(m_othermanager);
+  } else {
+    m_log.trace("tray: Change selection owner to %s", m_connection.id(m_tray));
+    m_connection.set_selection_owner_checked(m_tray, m_atom, XCB_CURRENT_TIME);
+    if (m_connection.get_selection_owner_unchecked(m_atom)->owner != m_tray) {
+      throw application_error("Failed to get control of the systray selection");
+    }
+    m_acquired_selection = true;
+  }
+}
+
+/**
+ * Notify pending clients about the new systray MANAGER
+ */
+void tray_manager::notify_clients() {
+  if (m_activated) {
+    m_log.info("Notifying pending tray clients");
+    auto message = m_connection.make_client_message(MANAGER, m_connection.root());
+    message->data.data32[0] = XCB_CURRENT_TIME;
+    message->data.data32[1] = m_atom;
+    message->data.data32[2] = m_tray;
+    m_connection.send_client_message(message, m_connection.root());
+  }
+}
+
+/**
+ * Send delayed notification to pending clients
+ */
+void tray_manager::notify_clients_delayed() {
+  if (m_delaythread.joinable()) {
+    m_delaythread.join();
+  }
+  m_delaythread = thread([this]() {
+    this_thread::sleep_for(1s);
+    notify_clients();
+  });
+}
+
+/**
+ * Track changes to the given selection owner
+ * If it gets destroyed or goes away we can reactivate the tray_manager
+ */
+void tray_manager::track_selection_owner(xcb_window_t owner) {
+  if (owner != XCB_NONE) {
+    m_log.trace("tray: Listen for events on the new selection window");
+    const unsigned int mask{XCB_CW_EVENT_MASK};
+    const unsigned int values[]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+    m_connection.change_window_attributes(owner, mask, values);
+  }
+}
+
+/**
+ * Process client docking request
+ */
+void tray_manager::process_docking_request(xcb_window_t win) {
+  m_log.info("Processing docking request from %s", m_connection.id(win));
+
+  m_clients.emplace_back(factory_util::shared<tray_client>(m_connection, win, m_opts.width, m_opts.height));
+  auto& client = m_clients.back();
+
+  try {
+    m_log.trace("tray: Get client _XEMBED_INFO");
+    xembed::query(m_connection, win, client->xembed());
+  } catch (const application_error& err) {
+    m_log.err(err.what());
+  } catch (const xpp::x::error::window& err) {
+    m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
+    remove_client(win, true);
+    return;
+  }
+
+  try {
+    const unsigned int mask{XCB_CW_BACK_PIXMAP | XCB_CW_EVENT_MASK};
+    const unsigned int values[]{
+        XCB_BACK_PIXMAP_PARENT_RELATIVE, XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+
+    m_log.trace("tray: Update client window");
+    m_connection.change_window_attributes_checked(client->window(), mask, values);
+
+    m_log.trace("tray: Configure client size");
+    client->reconfigure(0, 0);
+
+    m_log.trace("tray: Add client window to the save set");
+    m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client->window());
+
+    m_log.trace("tray: Reparent client");
+    m_connection.reparent_window_checked(
+        client->window(), m_tray, calculate_client_x(client->window()), calculate_client_y());
+
+    m_log.trace("tray: Send embbeded notification to client");
+    xembed::notify_embedded(m_connection, client->window(), m_tray, client->xembed()->version);
+
+    if (client->xembed()->flags & XEMBED_MAPPED) {
+      m_log.trace("tray: Map client");
+      m_connection.map_window_checked(client->window());
+    }
+  } catch (const xpp::x::error::window& err) {
+    m_log.err("Failed to setup tray client, removing... (%s)", err.what());
+    remove_client(win, false);
+  }
+}
+
+/**
+ * Calculate x position of tray window
+ */
+int tray_manager::calculate_x(unsigned int width, bool abspos) const {
+  auto x = abspos ? m_opts.orig_x : m_opts.rel_x;
+  if (m_opts.align == alignment::RIGHT) {
+    x -= ((m_opts.width + m_opts.spacing) * m_clients.size() + m_opts.spacing);
+  } else if (m_opts.align == alignment::CENTER) {
+    x -= (width / 2) - (m_opts.width / 2);
+  }
+  return x;
+}
+
+/**
+ * Calculate y position of tray window
+ */
+int tray_manager::calculate_y(bool abspos) const {
+  return abspos ? m_opts.orig_y : m_opts.rel_y;
+}
+
+/**
+ * Calculate width of tray window
+ */
+unsigned short int tray_manager::calculate_w() const {
+  unsigned int width = m_opts.spacing;
+  unsigned int count{0};
+  for (auto&& client : m_clients) {
+    if (client->mapped()) {
+      count++;
+      width += m_opts.spacing + m_opts.width;
+    }
+  }
+  return count ? width : 0;
+}
+
+/**
+ * Calculate height of tray window
+ */
+unsigned short int tray_manager::calculate_h() const {
+  return m_opts.height_fill;
+}
+
+/**
+ * Calculate x position of client window
+ */
+int tray_manager::calculate_client_x(const xcb_window_t& win) {
+  for (unsigned int i = 0; i < m_clients.size(); i++) {
+    if (m_clients[i]->match(win)) {
+      return m_opts.spacing + m_opts.width * i;
+    }
+  }
+  return m_opts.spacing;
+}
+
+/**
+ * Calculate y position of client window
+ */
+int tray_manager::calculate_client_y() {
+  return (m_opts.height_fill - m_opts.height) / 2;
+}
+
+/**
+ * Check if the given window is embedded
+ */
+bool tray_manager::is_embedded(const xcb_window_t& win) const {
+  return m_clients.end() != std::find_if(m_clients.begin(), m_clients.end(),
+                                [win](shared_ptr<tray_client> client) { return client->match(win); });
+}
+
+/**
+ * Find tray client by window
+ */
+shared_ptr<tray_client> tray_manager::find_client(const xcb_window_t& win) const {
+  for (auto&& client : m_clients) {
+    if (client->match(win)) {
+      return shared_ptr<tray_client>{client.get(), factory_util::null_deleter};
+    }
+  }
+  return nullptr;
+}
+
+/**
+ * Remove tray client
+ */
+void tray_manager::remove_client(shared_ptr<tray_client>& client, bool reconfigure) {
+  remove_client(client->window(), reconfigure);
+}
+
+/**
+ * Remove tray client by window
+ */
+void tray_manager::remove_client(xcb_window_t win, bool reconfigure) {
+  m_clients.erase(std::remove_if(
+      m_clients.begin(), m_clients.end(), [win](shared_ptr<tray_client> client) { return client->match(win); }));
+
+  if (reconfigure) {
+    tray_manager::reconfigure();
+  }
+}
+
+/**
+ * Get number of mapped clients
+ */
+unsigned int tray_manager::mapped_clients() const {
+  unsigned int mapped_clients = 0;
+
+  for (auto&& client : m_clients) {
+    if (client->mapped()) {
+      mapped_clients++;
+    }
+  }
+
+  return mapped_clients;
+}
+
+/**
+ * Event callback : XCB_EXPOSE
+ */
+void tray_manager::handle(const evt::expose& evt) {
+  if (m_activated && !m_clients.empty() && evt->count == 0) {
+    redraw_window();
+  }
+}
+
+/**
+ * Event callback : XCB_VISIBILITY_NOTIFY
+ */
+void tray_manager::handle(const evt::visibility_notify& evt) {
+  if (m_activated && !m_clients.empty()) {
+    m_log.trace("tray: Received visibility_notify for %s", m_connection.id(evt->window));
+    reconfigure_window();
+  }
+}
+
+/**
+ * Event callback : XCB_CLIENT_MESSAGE
+ */
+void tray_manager::handle(const evt::client_message& evt) {
+  if (!m_activated) {
+    return;
+  } else if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_tray) {
+    m_log.notice("Received WM_DELETE");
+    m_tray = 0;
+    deactivate();
+  } else if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) {
+    m_log.trace("tray: Received client_message");
+
+    if (SYSTEM_TRAY_REQUEST_DOCK == evt->data.data32[1]) {
+      if (!is_embedded(evt->data.data32[2])) {
+        process_docking_request(evt->data.data32[2]);
+      } else {
+        auto win = evt->data.data32[2];
+        m_log.warn("Tray client %s already embedded, ignoring request...", m_connection.id(win));
+      }
+    }
+  }
+}
+
+/**
+ * Event callback : XCB_CONFIGURE_REQUEST
+ *
+ * Called when a tray client thinks he's part of the free world and
+ * wants to reconfigure its window. This is of course nothing we appreciate
+ * so we return an answer that'll put him in place.
+ */
+void tray_manager::handle(const evt::configure_request& evt) {
+  if (m_activated && is_embedded(evt->window)) {
+    try {
+      m_log.trace("tray: Client configure request %s", m_connection.id(evt->window));
+      find_client(evt->window)->configure_notify(calculate_client_x(evt->window), calculate_client_y());
+    } catch (const xpp::x::error::window& err) {
+      m_log.err("Failed to reconfigure tray client, removing... (%s)", err.what());
+      remove_client(evt->window);
+    }
+  }
+}
+
+/**
+ * \see tray_manager::handle(const evt::configure_request&);
+ */
+void tray_manager::handle(const evt::resize_request& evt) {
+  if (m_activated && is_embedded(evt->window)) {
+    try {
+      m_log.trace("tray: Received resize_request for client %s", m_connection.id(evt->window));
+      find_client(evt->window)->configure_notify(calculate_client_x(evt->window), calculate_client_y());
+    } catch (const xpp::x::error::window& err) {
+      m_log.err("Failed to reconfigure tray client, removing... (%s)", err.what());
+      remove_client(evt->window);
+    }
+  }
+}
+
+/**
+ * Event callback : XCB_SELECTION_CLEAR
+ */
+void tray_manager::handle(const evt::selection_clear& evt) {
+  if (!m_activated) {
+    return;
+  } else if (evt->selection != m_atom) {
+    return;
+  } else if (evt->owner != m_tray) {
+    return;
+  }
+
+  try {
+    m_log.warn("Lost systray selection, deactivating...");
+    m_othermanager = m_connection.get_selection_owner(m_atom).owner<xcb_window_t>();
+    track_selection_owner(m_othermanager);
+  } catch (const exception& err) {
+    m_log.err("Failed to get systray selection owner");
+    m_othermanager = XCB_NONE;
+  }
+
+  deactivate(false);
+}
+
+/**
+ * Event callback : XCB_PROPERTY_NOTIFY
+ */
+void tray_manager::handle(const evt::property_notify& evt) {
+  if (!m_activated) {
+    return;
+  } else if (evt->atom == _XROOTPMAP_ID) {
+    redraw_window(true);
+  } else if (evt->atom == _XSETROOT_ID) {
+    redraw_window(true);
+  } else if (evt->atom == ESETROOT_PMAP_ID) {
+    redraw_window(true);
+  } else if (evt->atom != _XEMBED_INFO) {
+    return;
+  }
+
+  auto client = find_client(evt->window);
+
+  if (!client) {
+    return;
+  }
+
+  m_log.trace("tray: _XEMBED_INFO: %s", m_connection.id(evt->window));
+
+  auto xd = client->xembed();
+  auto win = client->window();
+
+  if (evt->state == XCB_PROPERTY_NEW_VALUE) {
+    m_log.trace("tray: _XEMBED_INFO value has changed");
+  }
+
+  try {
+    m_log.trace("tray: Get client _XEMBED_INFO");
+    xembed::query(m_connection, win, xd);
+  } catch (const application_error& err) {
+    m_log.err(err.what());
+    return;
+  } catch (const xpp::x::error::window& err) {
+    m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
+    remove_client(win, true);
+    return;
+  }
+
+  m_log.trace("tray: _XEMBED_INFO[0]=%u _XEMBED_INFO[1]=%u", xd->version, xd->flags);
+
+  if ((client->xembed()->flags & XEMBED_MAPPED) & XEMBED_MAPPED) {
+    reconfigure();
+  }
+}
+
+/**
+ * Event callback : XCB_REPARENT_NOTIFY
+ */
+void tray_manager::handle(const evt::reparent_notify& evt) {
+  if (m_activated && is_embedded(evt->window) && evt->parent != m_tray) {
+    m_log.trace("tray: Received reparent_notify for client, remove...");
+    remove_client(evt->window);
+  }
+}
+
+/**
+ * Event callback : XCB_DESTROY_NOTIFY
+ */
+void tray_manager::handle(const evt::destroy_notify& evt) {
+  if (m_activated && evt->window == m_tray) {
+    deactivate();
+  } else if (!m_activated && evt->window == m_othermanager) {
+    m_log.info("Systray selection unmanaged... re-activating");
+    activate();
+  } else if (m_activated && is_embedded(evt->window)) {
+    m_log.trace("tray: Received destroy_notify for client, remove...");
+    remove_client(evt->window);
+    redraw_window();
+  }
+}
+
+/**
+ * Event callback : XCB_MAP_NOTIFY
+ */
+void tray_manager::handle(const evt::map_notify& evt) {
+  if (m_activated && evt->window == m_tray) {
+    m_log.trace("tray: Received map_notify");
+    m_log.trace("tray: Update container mapped flag");
+    m_mapped = true;
+    redraw_window();
+  } else if (is_embedded(evt->window)) {
+    m_log.trace("tray: Received map_notify");
+    m_log.trace("tray: Set client mapped");
+    find_client(evt->window)->mapped(true);
+    unsigned int clientcount{mapped_clients()};
+    if (clientcount > m_opts.configured_slots) {
+      reconfigure();
+    }
+    m_sig.emit(signals::ui_tray::mapped_clients{move(clientcount)});
+  }
+}
+
+/**
+ * Event callback : XCB_UNMAP_NOTIFY
+ */
+void tray_manager::handle(const evt::unmap_notify& evt) {
+  if (m_activated && evt->window == m_tray) {
+    m_log.trace("tray: Received unmap_notify");
+    m_log.trace("tray: Update container mapped flag");
+    m_mapped = false;
+  } else if (m_activated && is_embedded(evt->window)) {
+    m_log.trace("tray: Received unmap_notify");
+    m_log.trace("tray: Set client unmapped");
+    find_client(evt->window)->mapped(false);
+    m_sig.emit(signals::ui_tray::mapped_clients{mapped_clients()});
+  }
+}
+
+/**
+ * Signal handler connected to the bar window's visibility change signal.
+ * This is used as a fallback in case the window restacking fails. It will
+ * toggle the tray window whenever the visibility of the bar window changes.
+ */
+bool tray_manager::on(const signals::ui::visibility_change& evt) {
+  bool visible{evt.cast()};
+  unsigned int clients{mapped_clients()};
+
+  m_log.trace("tray: visibility_change (state=%i, activated=%i, mapped=%i, hidden=%i)", visible,
+      static_cast<bool>(m_activated), static_cast<bool>(m_mapped), static_cast<bool>(m_hidden));
+
+  m_hidden = !visible;
+
+  if (!m_activated) {
+    return false;
+  } else if (!m_hidden && !m_mapped && clients) {
+    m_connection.map_window(m_tray);
+  } else if ((!clients || m_hidden) && m_mapped) {
+    m_connection.unmap_window(m_tray);
+  } else if (m_mapped && !m_hidden && clients) {
+    redraw_window();
+  }
+
+  m_connection.flush();
+
+  return true;
+}
+
+bool tray_manager::on(const signals::ui::dim_window& evt) {
+  if (m_activated) {
+    ewmh_util::set_wm_window_opacity(m_tray, evt.cast() * 0xFFFFFFFF);
+  }
+  // let the event bubble
+  return false;
+}
+
+bool tray_manager::on(const signals::ui::update_background&) {
+  redraw_window(true);
+
+  return false;
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/window.cpp b/src/x11/window.cpp
new file mode 100644 (file)
index 0000000..35393dd
--- /dev/null
@@ -0,0 +1,70 @@
+#include "components/types.hpp"
+#include "utils/memory.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/extensions/randr.hpp"
+#include "x11/window.hpp"
+
+POLYBAR_NS
+
+/**
+ * Reconfigure the window geometry
+ */
+window window::reconfigure_geom(unsigned short int w, unsigned short int h, short int x, short int y) {
+  unsigned int mask{0};
+  unsigned int values[7]{0};
+
+  xcb_params_configure_window_t params{};
+  XCB_AUX_ADD_PARAM(&mask, &params, width, w);
+  XCB_AUX_ADD_PARAM(&mask, &params, height, h);
+  XCB_AUX_ADD_PARAM(&mask, &params, x, x);
+  XCB_AUX_ADD_PARAM(&mask, &params, y, y);
+
+  connection::pack_values(mask, &params, values);
+  configure_checked(mask, values);
+
+  return *this;
+}
+
+/**
+ * Reconfigure the window position
+ */
+window window::reconfigure_pos(short int x, short int y) {
+  unsigned int mask{0};
+  unsigned int values[2]{0};
+
+  xcb_params_configure_window_t params{};
+  XCB_AUX_ADD_PARAM(&mask, &params, x, x);
+  XCB_AUX_ADD_PARAM(&mask, &params, y, y);
+
+  connection::pack_values(mask, &params, values);
+  configure_checked(mask, values);
+
+  return *this;
+}
+
+/**
+ * Reconfigure the windows ewmh strut
+ */
+window window::reconfigure_struts(unsigned short int w, unsigned short int h, short int x, bool bottom) {
+  unsigned int none{0};
+  unsigned int values[12]{none};
+
+  if (bottom) {
+    values[static_cast<int>(strut::BOTTOM)] = h;
+    values[static_cast<int>(strut::BOTTOM_START_X)] = x;
+    values[static_cast<int>(strut::BOTTOM_END_X)] = x + w - 1;
+  } else {
+    values[static_cast<int>(strut::TOP)] = h;
+    values[static_cast<int>(strut::TOP_START_X)] = x;
+    values[static_cast<int>(strut::TOP_END_X)] = x + w - 1;
+  }
+
+  connection().change_property_checked(XCB_PROP_MODE_REPLACE, *this, _NET_WM_STRUT, XCB_ATOM_CARDINAL, 32, 4, values);
+  connection().change_property_checked(
+      XCB_PROP_MODE_REPLACE, *this, _NET_WM_STRUT_PARTIAL, XCB_ATOM_CARDINAL, 32, 12, values);
+
+  return *this;
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/winspec.cpp b/src/x11/winspec.cpp
new file mode 100644 (file)
index 0000000..fb92125
--- /dev/null
@@ -0,0 +1,138 @@
+#include "x11/connection.hpp"
+#include "x11/winspec.hpp"
+
+POLYBAR_NS
+
+winspec::winspec(connection& conn) : m_connection(conn) {}
+winspec::winspec(connection& conn, const xcb_window_t& window) : m_connection(conn), m_window(window) {}
+
+winspec::operator xcb_window_t() const {
+  return m_window;
+}
+winspec::operator xcb_rectangle_t() const {
+  return {m_x, m_y, m_width, m_height};
+}
+
+xcb_window_t winspec::operator<<(const cw_flush& f) {
+  unsigned int values[16]{0};
+
+  if (m_window == XCB_NONE) {
+    m_window = m_connection.generate_id();
+  }
+  if (m_parent == XCB_NONE) {
+    m_parent = m_connection.screen()->root;
+  }
+
+  if (m_width <= 0) {
+    m_width = 1;
+  }
+  if (m_height <= 0) {
+    m_height = 1;
+  }
+
+  connection::pack_values(m_mask, &m_params, values);
+
+  if (f.checked) {
+    m_connection.create_window_checked(
+        m_depth, m_window, m_parent, m_x, m_y, m_width, m_height, m_border, m_class, m_visual, m_mask, values);
+  } else {
+    m_connection.create_window(
+        m_depth, m_window, m_parent, m_x, m_y, m_width, m_height, m_border, m_class, m_visual, m_mask, values);
+  }
+
+  return m_window;
+}
+
+winspec& winspec::operator<<(const cw_size& size) {
+  m_width = size.w;
+  m_height = size.h;
+  return *this;
+}
+winspec& winspec::operator<<(const cw_pos& p) {
+  m_x = p.x;
+  m_y = p.y;
+  return *this;
+}
+winspec& winspec::operator<<(const cw_border& b) {
+  m_border = b.border_width;
+  return *this;
+}
+winspec& winspec::operator<<(const cw_class& c) {
+  m_class = c.class_;
+  return *this;
+}
+winspec& winspec::operator<<(const cw_parent& p) {
+  m_parent = p.parent;
+  return *this;
+}
+winspec& winspec::operator<<(const cw_depth& d) {
+  m_depth = d.depth;
+  return *this;
+}
+winspec& winspec::operator<<(const cw_visual& v) {
+  m_visual = v.visualid;
+  return *this;
+}
+
+winspec& winspec::operator<<(const cw_params_back_pixel& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, back_pixel, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_back_pixmap& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, back_pixmap, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_backing_pixel& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, backing_pixel, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_backing_planes& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, backing_planes, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_backing_store& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, backing_store, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_bit_gravity& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, bit_gravity, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_border_pixel& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, border_pixel, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_border_pixmap& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, border_pixmap, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_colormap& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, colormap, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_cursor& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, cursor, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_dont_propagate& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, dont_propagate, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_event_mask& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, event_mask, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_override_redirect& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, override_redirect, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_save_under& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, save_under, p.value);
+  return *this;
+}
+winspec& winspec::operator<<(const cw_params_win_gravity& p) {
+  XCB_AUX_ADD_PARAM(&m_mask, &m_params, win_gravity, p.value);
+  return *this;
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/xembed.cpp b/src/x11/xembed.cpp
new file mode 100644 (file)
index 0000000..b426d9c
--- /dev/null
@@ -0,0 +1,101 @@
+#include "x11/xembed.hpp"
+#include "errors.hpp"
+#include "x11/atoms.hpp"
+
+POLYBAR_NS
+
+namespace xembed {
+  /**
+   * Query _XEMBED_INFO for the given window
+   */
+  xembed_data* query(connection& conn, xcb_window_t win, xembed_data* data) {
+    auto info = conn.get_property(false, win, _XEMBED_INFO, XCB_GET_PROPERTY_TYPE_ANY, 0L, 2);
+
+    if (info->value_len == 0) {
+      throw application_error("Invalid _XEMBED_INFO for window " + conn.id(win));
+    }
+
+    std::vector<unsigned int> xembed_data{info.value<unsigned int>().begin(), info.value<unsigned int>().end()};
+
+    data->xembed = _XEMBED;
+    data->xembed_info = _XEMBED_INFO;
+
+    data->time = XCB_CURRENT_TIME;
+    data->flags = xembed_data[1];
+    data->version = xembed_data[0];
+
+    return data;
+  }
+
+  /**
+   * Send _XEMBED messages
+   */
+  void send_message(connection& conn, xcb_window_t target, long message, long d1, long d2, long d3) {
+    auto msg = conn.make_client_message(_XEMBED, target);
+    msg->data.data32[0] = XCB_CURRENT_TIME;
+    msg->data.data32[1] = message;
+    msg->data.data32[2] = d1;
+    msg->data.data32[3] = d2;
+    msg->data.data32[4] = d3;
+    conn.send_client_message(msg, target);
+  }
+
+  /**
+   * Send window focus event
+   */
+  void send_focus_event(connection& conn, xcb_window_t target) {
+    auto msg = conn.make_client_message(WM_PROTOCOLS, target);
+    msg->data.data32[0] = WM_TAKE_FOCUS;
+    msg->data.data32[1] = XCB_CURRENT_TIME;
+    conn.send_client_message(msg, target);
+  }
+
+  /**
+   * Acknowledge window embedding
+   */
+  void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, long version) {
+    send_message(conn, win, XEMBED_EMBEDDED_NOTIFY, 0, embedder, version);
+  }
+
+  /**
+   * Send window activate notification
+   */
+  void notify_activated(connection& conn, xcb_window_t win) {
+    send_message(conn, win, XEMBED_WINDOW_ACTIVATE, 0, 0, 0);
+  }
+
+  /**
+   * Send window deactivate notification
+   */
+  void notify_deactivated(connection& conn, xcb_window_t win) {
+    send_message(conn, win, XEMBED_WINDOW_DEACTIVATE, 0, 0, 0);
+  }
+
+  /**
+   * Send window focused notification
+   */
+  void notify_focused(connection& conn, xcb_window_t win, long focus_type) {
+    send_message(conn, win, XEMBED_FOCUS_IN, focus_type, 0, 0);
+  }
+
+  /**
+   * Send window unfocused notification
+   */
+  void notify_unfocused(connection& conn, xcb_window_t win) {
+    send_message(conn, win, XEMBED_FOCUS_OUT, 0, 0, 0);
+  }
+
+  /**
+   * Unembed given window
+   */
+  void unembed(connection& conn, xcb_window_t win, xcb_window_t root) {
+    try {
+      conn.unmap_window_checked(win);
+      conn.reparent_window_checked(win, root, 0, 0);
+    } catch (const xpp::x::error::window& err) {
+      // invalid window
+    }
+  }
+}
+
+POLYBAR_NS_END
diff --git a/src/x11/xresources.cpp b/src/x11/xresources.cpp
new file mode 100644 (file)
index 0000000..49d4aa2
--- /dev/null
@@ -0,0 +1,15 @@
+#include "x11/xresources.hpp"
+
+POLYBAR_NS
+
+template <>
+string xresource_manager::convert(string&& value) const {
+  return forward<string>(value);
+}
+
+template <>
+double xresource_manager::convert(string&& value) const {
+  return std::strtod(value.c_str(), nullptr);
+}
+
+POLYBAR_NS_END
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
new file mode 100644 (file)
index 0000000..c90504b
--- /dev/null
@@ -0,0 +1,70 @@
+include_directories(${dirs})
+include_directories(${CMAKE_CURRENT_LIST_DIR})
+
+# Download and unpack googletest at configure time {{{
+configure_file(
+  CMakeLists.txt.in
+  ${CMAKE_BINARY_DIR}/googletest-download/CMakeLists.txt
+  )
+execute_process( COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
+  RESULT_VARIABLE result
+  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download)
+
+if(result)
+  message(FATAL_ERROR "CMake step for googletest failed: ${result}")
+endif()
+
+execute_process(COMMAND ${CMAKE_COMMAND} --build .
+  RESULT_VARIABLE result
+  WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/googletest-download )
+
+if(result)
+  message(FATAL_ERROR "Build step for googletest failed: ${result}")
+endif()
+
+# Add googletest directly to our build. This defines
+# the gtest, gtest_main, gmock and gmock_main targets.
+add_subdirectory(${CMAKE_BINARY_DIR}/googletest-src
+                 ${CMAKE_BINARY_DIR}/googletest-build
+                 EXCLUDE_FROM_ALL)
+
+# }}}
+
+# Compile all unit tests with 'make all_unit_tests'
+add_custom_target(all_unit_tests
+    COMMENT "Building all unit test")
+
+function(add_unit_test source_file)
+  string(REPLACE "/" "_" testname ${source_file})
+  set(name "unit_test.${testname}")
+
+  add_executable(${name} unit_tests/${source_file}.cpp)
+
+  # Link against gmock (this automatically links against gtest)
+  target_link_libraries(${name} poly gmock_main)
+  add_test(NAME ${name} COMMAND ${name})
+
+  add_dependencies(all_unit_tests ${name})
+endfunction()
+
+add_unit_test(utils/actions)
+add_unit_test(utils/color)
+add_unit_test(utils/command)
+add_unit_test(utils/math unit_tests)
+add_unit_test(utils/memory unit_tests)
+add_unit_test(utils/scope unit_tests)
+add_unit_test(utils/string unit_tests)
+add_unit_test(utils/file)
+add_unit_test(utils/process)
+add_unit_test(components/command_line)
+add_unit_test(components/bar)
+add_unit_test(components/parser)
+add_unit_test(components/config_parser)
+add_unit_test(drawtypes/label)
+add_unit_test(drawtypes/iconset)
+
+# Run make check to build and run all unit tests
+add_custom_target(check
+  COMMAND GTEST_COLOR=1 ctest --output-on-failure
+  DEPENDS all_unit_tests
+  )
diff --git a/tests/CMakeLists.txt.in b/tests/CMakeLists.txt.in
new file mode 100644 (file)
index 0000000..61cd29f
--- /dev/null
@@ -0,0 +1,15 @@
+cmake_minimum_required(VERSION 2.8.2)
+
+project(googletest-download NONE)
+
+include(ExternalProject)
+ExternalProject_Add(googletest
+  GIT_REPOSITORY    https://github.com/google/googletest.git
+  GIT_TAG           release-1.10.0
+  SOURCE_DIR        "${CMAKE_BINARY_DIR}/googletest-src"
+  BINARY_DIR        "${CMAKE_BINARY_DIR}/googletest-build"
+  CONFIGURE_COMMAND ""
+  BUILD_COMMAND     ""
+  INSTALL_COMMAND   ""
+  TEST_COMMAND      ""
+)
diff --git a/tests/common/test.hpp b/tests/common/test.hpp
new file mode 100644 (file)
index 0000000..e39c32b
--- /dev/null
@@ -0,0 +1,3 @@
+#pragma once
+
+#include "gtest/gtest.h"
diff --git a/tests/unit_tests/components/bar.cpp b/tests/unit_tests/components/bar.cpp
new file mode 100644 (file)
index 0000000..f00a3d5
--- /dev/null
@@ -0,0 +1,48 @@
+#include "common/test.hpp"
+#include "components/bar.hpp"
+
+using namespace polybar;
+
+
+
+/**
+ * \brief Class for parameterized tests on geom_format_to_pixels
+ *
+ * The first element in the tuple is the expected return value, the second
+ * value is the format string. The max value is always 1000
+ */
+class GeomFormatToPixelsTest :
+  public ::testing::Test,
+  public ::testing::WithParamInterface<pair<double, string>> {};
+
+vector<pair<double, string>> to_pixels_no_offset_list = {
+  {1000, "100%"},
+  {0, "0%"},
+  {1000, "150%"},
+  {100, "10%"},
+  {0, "0"},
+  {1234, "1234"},
+  {1.234, "1.234"},
+};
+
+vector<pair<double, string>> to_pixels_with_offset_list = {
+  {1000, "100%:-0"},
+  {1000, "100%:+0"},
+  {1010, "100%:+10"},
+  {990, "100%:-10"},
+  {10, "0%:+10"},
+  {1000, "99%:+10"},
+  {0, "1%:-100"},
+};
+
+INSTANTIATE_TEST_SUITE_P(NoOffset, GeomFormatToPixelsTest,
+    ::testing::ValuesIn(to_pixels_no_offset_list));
+
+INSTANTIATE_TEST_SUITE_P(WithOffset, GeomFormatToPixelsTest,
+    ::testing::ValuesIn(to_pixels_with_offset_list));
+
+TEST_P(GeomFormatToPixelsTest, correctness) {
+  double exp = GetParam().first;
+  std::string str = GetParam().second;
+  EXPECT_DOUBLE_EQ(exp, geom_format_to_pixels(str, 1000));
+}
diff --git a/tests/unit_tests/components/command_line.cpp b/tests/unit_tests/components/command_line.cpp
new file mode 100644 (file)
index 0000000..79873a7
--- /dev/null
@@ -0,0 +1,113 @@
+#include "common/test.hpp"
+#include "components/command_line.hpp"
+#include "utils/string.hpp"
+
+using namespace polybar;
+
+class CommandLine : public ::testing::Test {
+  protected:
+    virtual void SetUp() {
+      set_cli();
+    }
+
+    virtual void set_cli() {
+      cli = command_line::parser::make("cmd", get_opts());
+    }
+
+    command_line::options get_opts()  {
+      // clang-format off
+      return command_line::options {
+        command_line::option{"-f", "--flag", "Flag description"},
+        command_line::option{"-o", "--option", "Option description", "OPTION", {"foo", "bar", "baz"}},
+      };
+      // clang-format on
+    };
+
+    command_line::parser::make_type cli;
+};
+
+
+TEST_F(CommandLine, hasShort) {
+  cli->process_input(string_util::split("-f", ' '));
+  EXPECT_TRUE(cli->has("flag"));
+  EXPECT_FALSE(cli->has("option"));
+
+  set_cli();
+  cli->process_input(string_util::split("-f -o foo", ' '));
+  EXPECT_TRUE(cli->has("flag"));
+  EXPECT_TRUE(cli->has("option"));
+
+  set_cli();
+  cli->process_input(string_util::split("-o baz", ' '));
+  EXPECT_FALSE(cli->has("flag"));
+  EXPECT_TRUE(cli->has("option"));
+}
+
+TEST_F(CommandLine, hasLong) {
+  cli->process_input(string_util::split("--flag", ' '));
+  EXPECT_TRUE(cli->has("flag"));
+  EXPECT_FALSE(cli->has("option"));
+
+  set_cli();
+  cli->process_input(string_util::split("--flag --option=foo", ' '));
+  EXPECT_TRUE(cli->has("flag"));
+  EXPECT_TRUE(cli->has("option"));
+
+  set_cli();
+  cli->process_input(string_util::split("--option=foo --flag", ' '));
+  EXPECT_TRUE(cli->has("flag"));
+  EXPECT_TRUE(cli->has("option"));
+
+  set_cli();
+  cli->process_input(string_util::split("--option=baz", ' '));
+  EXPECT_FALSE(cli->has("flag"));
+  EXPECT_TRUE(cli->has("option"));
+}
+
+TEST_F(CommandLine, compare) {
+  cli->process_input(string_util::split("-o baz", ' '));
+  EXPECT_TRUE(cli->compare("option", "baz"));
+
+  set_cli();
+  cli->process_input(string_util::split("--option=foo", ' '));
+  EXPECT_TRUE(cli->compare("option", "foo"));
+}
+
+TEST_F(CommandLine, get) {
+  cli->process_input(string_util::split("--option=baz", ' '));
+  EXPECT_EQ("baz", cli->get("option"));
+
+  set_cli();
+  cli->process_input(string_util::split("--option=foo", ' '));
+  EXPECT_EQ("foo", cli->get("option"));
+}
+
+TEST_F(CommandLine, missingValue) {
+  auto input1 = string_util::split("--option", ' ');
+  auto input2 = string_util::split("-o", ' ');
+  auto input3 = string_util::split("--option baz", ' ');
+
+  EXPECT_THROW(cli->process_input(input1), command_line::value_error);
+  set_cli();
+  EXPECT_THROW(cli->process_input(input2), command_line::value_error);
+  set_cli();
+  EXPECT_THROW(cli->process_input(input3), command_line::value_error);
+}
+
+TEST_F(CommandLine, invalidValue) {
+  auto input1 = string_util::split("--option=invalid", ' ');
+  auto input2 = string_util::split("-o invalid_value", ' ');
+
+  EXPECT_THROW(cli->process_input(input1), command_line::value_error);
+  set_cli();
+  EXPECT_THROW(cli->process_input(input2), command_line::value_error);
+}
+
+TEST_F(CommandLine, unrecognized) {
+  auto input1 = string_util::split("-x", ' ');
+  auto input2 = string_util::split("--unrecognized", ' ');
+
+  EXPECT_THROW(cli->process_input(input1), command_line::argument_error);
+  set_cli();
+  EXPECT_THROW(cli->process_input(input2), command_line::argument_error);
+}
diff --git a/tests/unit_tests/components/config_parser.cpp b/tests/unit_tests/components/config_parser.cpp
new file mode 100644 (file)
index 0000000..5aa7c43
--- /dev/null
@@ -0,0 +1,238 @@
+#include "components/config_parser.hpp"
+
+#include "common/test.hpp"
+#include "components/logger.hpp"
+
+using namespace polybar;
+using namespace std;
+
+/**
+ * \brief Testing-only subclass of config_parser to change access level
+ */
+class TestableConfigParser : public config_parser {
+  using config_parser::config_parser;
+
+ public:
+  using config_parser::get_line_type;
+
+ public:
+  using config_parser::parse_key;
+
+ public:
+  using config_parser::parse_header;
+
+ public:
+  using config_parser::parse_line;
+
+ public:
+  using config_parser::m_files;
+};
+
+/**
+ * \brief Fixture class
+ */
+class ConfigParser : public ::testing::Test {
+ protected:
+  unique_ptr<TestableConfigParser> parser =
+      make_unique<TestableConfigParser>(logger(loglevel::NONE), "/dev/zero", "TEST");
+};
+
+// ParseLineTest {{{
+class ParseLineInValidTest : public ConfigParser, public ::testing::WithParamInterface<string> {};
+
+class ParseLineHeaderTest : public ConfigParser, public ::testing::WithParamInterface<pair<string, string>> {};
+
+class ParseLineKeyTest : public ConfigParser,
+                         public ::testing::WithParamInterface<pair<pair<string, string>, string>> {};
+
+vector<string> parse_line_invalid_list = {
+    " # comment",
+    "; comment",
+    "\t#",
+    "",
+    " ",
+    "\t ",
+};
+
+vector<pair<string, string>> parse_line_header_list = {
+    {"section", "\t[section]"},
+    {"section", "\t[section]  "},
+    {"bar/name", "\t[bar/name]  "},
+};
+
+vector<pair<pair<string, string>, string>> parse_line_key_list = {
+    {{"key", "value"}, " key = value"},
+    {{"key", ""}, " key\t = \"\""},
+    {{"key", "\""}, " key\t = \"\"\""},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseLineInValidTest, ::testing::ValuesIn(parse_line_invalid_list));
+
+TEST_P(ParseLineInValidTest, correctness) {
+  line_t line = parser->parse_line(GetParam());
+
+  EXPECT_FALSE(line.useful);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseLineHeaderTest, ::testing::ValuesIn(parse_line_header_list));
+
+TEST_P(ParseLineHeaderTest, correctness) {
+  line_t line = parser->parse_line(GetParam().second);
+
+  EXPECT_TRUE(line.useful);
+
+  EXPECT_TRUE(line.is_header);
+  EXPECT_EQ(GetParam().first, line.header);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseLineKeyTest, ::testing::ValuesIn(parse_line_key_list));
+
+TEST_P(ParseLineKeyTest, correctness) {
+  line_t line = parser->parse_line(GetParam().second);
+
+  EXPECT_TRUE(line.useful);
+
+  EXPECT_FALSE(line.is_header);
+  EXPECT_EQ(GetParam().first.first, line.key);
+  EXPECT_EQ(GetParam().first.second, line.value);
+}
+
+TEST_F(ParseLineInValidTest, throwsSyntaxError) {
+  EXPECT_THROW(parser->parse_line("unknown"), syntax_error);
+  EXPECT_THROW(parser->parse_line("\ufeff"), syntax_error);
+}
+// }}}
+
+// GetLineTypeTest {{{
+
+/**
+ * \brief Class for parameterized tests on get_line_type
+ *
+ * Parameters are pairs of the expected line type and a string that should be
+ * detected as that line type
+ */
+class GetLineTypeTest : public ConfigParser, public ::testing::WithParamInterface<pair<line_type, string>> {};
+
+/**
+ * \brief Helper function generate GetLineTypeTest parameter values
+ */
+vector<pair<line_type, string>> line_type_transform(vector<string>&& in, line_type type) {
+  vector<pair<line_type, string>> out;
+
+  out.reserve(in.size());
+  for (const auto& i : in) {
+    out.emplace_back(type, i);
+  }
+
+  return out;
+}
+
+/**
+ * \brief Parameter values for GetLineTypeTest
+ */
+auto line_type_key = line_type_transform({"a = b", "  a =b", " a\t =\t \t b", "a = "}, line_type::KEY);
+auto line_type_header = line_type_transform({"[section]", "[section]", "[section/sub]"}, line_type::HEADER);
+auto line_type_comment = line_type_transform({";abc", "#abc", ";", "#"}, line_type::COMMENT);
+auto line_type_empty = line_type_transform({""}, line_type::EMPTY);
+auto line_type_unknown = line_type_transform({"|a", " |a", "a"}, line_type::UNKNOWN);
+
+/**
+ * Instantiate GetLineTypeTest for the different line types
+ */
+INSTANTIATE_TEST_SUITE_P(LineTypeKey, GetLineTypeTest, ::testing::ValuesIn(line_type_key));
+INSTANTIATE_TEST_SUITE_P(LineTypeHeader, GetLineTypeTest, ::testing::ValuesIn(line_type_header));
+INSTANTIATE_TEST_SUITE_P(LineTypeComment, GetLineTypeTest, ::testing::ValuesIn(line_type_comment));
+INSTANTIATE_TEST_SUITE_P(LineTypeEmpty, GetLineTypeTest, ::testing::ValuesIn(line_type_empty));
+INSTANTIATE_TEST_SUITE_P(LineTypeUnknown, GetLineTypeTest, ::testing::ValuesIn(line_type_unknown));
+
+/**
+ * \brief Parameterized test for get_line_type
+ */
+TEST_P(GetLineTypeTest, correctness) {
+  EXPECT_EQ(GetParam().first, parser->get_line_type(GetParam().second));
+}
+
+// }}}
+
+// ParseKeyTest {{{
+
+/**
+ * \brief Class for parameterized tests on parse_key
+ *
+ * The first element of the pair is the expected key-value pair and the second
+ * element is the string to be parsed, has to be trimmed and valid.
+ */
+class ParseKeyTest : public ConfigParser, public ::testing::WithParamInterface<pair<pair<string, string>, string>> {};
+
+vector<pair<pair<string, string>, string>> parse_key_list = {
+    {{"key", "value"}, "key = value"},
+    {{"key", "value"}, "key=value"},
+    {{"key", "value"}, "key =\"value\""},
+    {{"key", "value"}, "key\t=\t \"value\""},
+    {{"key", "\"value"}, "key = \"value"},
+    {{"key", "value\""}, "key = value\""},
+    {{"key", "= value"}, "key == value"},
+    {{"key", ""}, "key ="},
+    {{"key", ""}, R"(key ="")"},
+    {{"key", "\"\""}, R"(key ="""")"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseKeyTest, ::testing::ValuesIn(parse_key_list));
+
+/**
+ * Parameterized test for parse_key with valid line
+ */
+TEST_P(ParseKeyTest, correctness) {
+  EXPECT_EQ(GetParam().first, parser->parse_key(GetParam().second));
+}
+
+/**
+ * Tests if exception is thrown for invalid key line
+ */
+TEST_F(ParseKeyTest, throwsSyntaxError) {
+  EXPECT_THROW(parser->parse_key("= empty name"), syntax_error);
+  EXPECT_THROW(parser->parse_key("forbidden char = value"), syntax_error);
+  EXPECT_THROW(parser->parse_key("forbidden\tchar = value"), syntax_error);
+}
+// }}}
+
+// ParseHeaderTest {{{
+
+/**
+ * \brief Class for parameterized tests on parse_key
+ *
+ * The first element of the pair is the expected key-value pair and the second
+ * element is the string to be parsed, has to be trimmed and valid
+ */
+class ParseHeaderTest : public ConfigParser, public ::testing::WithParamInterface<pair<string, string>> {};
+
+vector<pair<string, string>> parse_header_list = {
+    {"section", "[section]"},
+    {"bar/name", "[bar/name]"},
+    {"with_underscore", "[with_underscore]"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseHeaderTest, ::testing::ValuesIn(parse_header_list));
+
+/**
+ * Parameterized test for parse_header with valid line
+ */
+TEST_P(ParseHeaderTest, correctness) {
+  EXPECT_EQ(GetParam().first, parser->parse_header(GetParam().second));
+}
+
+/**
+ * Tests if exception is thrown for invalid header line
+ */
+TEST_F(ParseHeaderTest, throwsSyntaxError) {
+  EXPECT_THROW(parser->parse_header("[]"), syntax_error);
+  EXPECT_THROW(parser->parse_header("[no_end"), syntax_error);
+  EXPECT_THROW(parser->parse_header("[forbidden char]"), syntax_error);
+  EXPECT_THROW(parser->parse_header("[forbidden\tchar]"), syntax_error);
+
+  // Reserved names
+  EXPECT_THROW(parser->parse_header("[self]"), syntax_error);
+  EXPECT_THROW(parser->parse_header("[BAR]"), syntax_error);
+  EXPECT_THROW(parser->parse_header("[root]"), syntax_error);
+}
+// }}}
diff --git a/tests/unit_tests/components/parser.cpp b/tests/unit_tests/components/parser.cpp
new file mode 100644 (file)
index 0000000..493e688
--- /dev/null
@@ -0,0 +1,37 @@
+#include "common/test.hpp"
+#include "events/signal_emitter.hpp"
+#include "components/parser.hpp"
+
+using namespace polybar;
+
+class TestableParser : public parser {
+  using parser::parser;
+  public: using parser::parse_action_cmd;
+};
+
+class Parser : public ::testing::Test {
+  protected:
+    TestableParser m_parser{signal_emitter::make()};
+};
+/**
+ * The first element of the pair is the expected return text, the second element
+ * is the input to parse_action_cmd
+ */
+class ParseActionCmd :
+  public Parser,
+  public ::testing::WithParamInterface<pair<string, string>> {};
+
+vector<pair<string, string>> parse_action_cmd_list = {
+  {"abc", ":abc:\\abc"},
+  {"abc\\:", ":abc\\::\\abc"},
+  {"\\:\\:\\:", ":\\:\\:\\::\\abc"},
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseActionCmd,
+    ::testing::ValuesIn(parse_action_cmd_list));
+
+TEST_P(ParseActionCmd, correctness) {
+  auto input = GetParam().second;
+  auto result = m_parser.parse_action_cmd(std::move(input));
+  EXPECT_EQ(GetParam().first, result);
+}
diff --git a/tests/unit_tests/drawtypes/iconset.cpp b/tests/unit_tests/drawtypes/iconset.cpp
new file mode 100644 (file)
index 0000000..8cc53d2
--- /dev/null
@@ -0,0 +1,18 @@
+#include "drawtypes/iconset.hpp"
+
+#include "common/test.hpp"
+
+using namespace std;
+using namespace polybar;
+using namespace polybar::drawtypes;
+
+TEST(IconSet, fuzzyMatchExactMatchFirst) {
+  iconset_t icons = make_shared<iconset>();
+
+  icons->add("1", make_shared<label>("1"));
+  icons->add("10", make_shared<label>("10"));
+
+  label_t ret = icons->get("10", "", true);
+
+  EXPECT_EQ("10", ret->get());
+}
diff --git a/tests/unit_tests/drawtypes/label.cpp b/tests/unit_tests/drawtypes/label.cpp
new file mode 100644 (file)
index 0000000..edc053d
--- /dev/null
@@ -0,0 +1,109 @@
+#include "drawtypes/label.hpp"
+
+#include <memory>
+#include <tuple>
+
+#include "common.hpp"
+#include "common/test.hpp"
+#include "components/types.hpp"
+
+using namespace polybar;
+using namespace std;
+using namespace polybar::drawtypes;
+
+using GetTestParamLabel = tuple<string, bool, int, int, alignment>;
+using GetTestParam = pair<string, GetTestParamLabel>;
+/**
+ * \brief Class for parameterized tests label::get
+ *
+ * The first element of the pair is the expected returned text, the second
+ * element is a 5-tuple (text, ellipsis, minlen, maxlen, alignment)
+ */
+class GetTest : public ::testing::Test, public ::testing::WithParamInterface<GetTestParam> {};
+
+vector<GetTestParam> get_list = {
+    {"...", make_tuple("abcd", true, 0, 3, alignment::RIGHT)},
+    {"abc", make_tuple("abc", true, 0, 3, alignment::RIGHT)},
+    {"abc", make_tuple("abcdefgh", false, 0, 3, alignment::RIGHT)},
+    {"a...", make_tuple("abcdefgh", true, 0, 4, alignment::RIGHT)},
+    {"abcd...", make_tuple("abcdefgh", true, 0, 7, alignment::RIGHT)},
+    {"abcdefgh", make_tuple("abcdefgh", true, 0, 8, alignment::RIGHT)},
+};
+INSTANTIATE_TEST_SUITE_P(Inst, GetTest, ::testing::ValuesIn(get_list));
+
+// No alignment needed
+vector<GetTestParam> get_no_align_list = {
+    {"abc", make_tuple("abc", true, 3, 0, alignment::LEFT)},
+    {"abc", make_tuple("abc", true, 2, 0, alignment::CENTER)},
+    {"abc", make_tuple("abc", true, 1, 0, alignment::RIGHT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(NoAlignment, GetTest, ::testing::ValuesIn(get_no_align_list));
+
+// Left alignment
+vector<GetTestParam> get_left_align_list = {
+    {"a ", make_tuple("a", true, 2, 0, alignment::LEFT)},
+    {"a  ", make_tuple("a", true, 3, 0, alignment::LEFT)},
+    {"abcde     ", make_tuple("abcde", true, 10, 0, alignment::LEFT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(LeftAlignment, GetTest, ::testing::ValuesIn(get_left_align_list));
+
+// Center alignment
+vector<GetTestParam> get_center_align_list = {
+    {" a ", make_tuple("a", true, 3, 0, alignment::CENTER)},
+    {"  a  ", make_tuple("a", true, 5, 0, alignment::CENTER)},
+    {"   abcd   ", make_tuple("abcd", true, 10, 0, alignment::CENTER)},
+};
+
+INSTANTIATE_TEST_SUITE_P(CenterAlignment, GetTest, ::testing::ValuesIn(get_center_align_list));
+
+// Right alignment
+vector<GetTestParam> get_right_align_list = {
+    {"  a", make_tuple("a", true, 3, 0, alignment::RIGHT)},
+    {" abc", make_tuple("abc", true, 4, 0, alignment::RIGHT)},
+    {"       abc", make_tuple("abc", true, 10, 0, alignment::RIGHT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(RightAlignment, GetTest, ::testing::ValuesIn(get_right_align_list));
+
+vector<GetTestParam> get_min_max_list = {
+    {"a ", make_tuple("a", true, 2, 2, alignment::CENTER)},
+    {"abc ", make_tuple("abc", true, 4, 4, alignment::CENTER)},
+    {"abc", make_tuple("abcd", false, 3, 3, alignment::RIGHT)},
+    {"...", make_tuple("abcd", true, 1, 3, alignment::RIGHT)},
+    {" ", make_tuple("", true, 1, 3, alignment::RIGHT)},
+    {" a", make_tuple("a", true, 2, 3, alignment::RIGHT)},
+    {"...", make_tuple("....", true, 2, 3, alignment::RIGHT)},
+    {"...", make_tuple("....", false, 2, 3, alignment::RIGHT)},
+    {"abc...", make_tuple("abcdefg", true, 6, 6, alignment::RIGHT)},
+};
+
+INSTANTIATE_TEST_SUITE_P(MinMax, GetTest, ::testing::ValuesIn(get_min_max_list));
+
+unique_ptr<label> create_alignment_test_label(GetTestParamLabel params) {
+  /* It's simpler in this case to create a label with all the constructor defaults and then set the relevant
+   * fields individually.
+   */
+  auto test_label = make_unique<label>(get<0>(params));
+  test_label->m_ellipsis = get<1>(params);
+  test_label->m_minlen = get<2>(params);
+  test_label->m_maxlen = get<3>(params);
+  test_label->m_alignment = get<4>(params);
+  return test_label;
+}
+
+TEST_P(GetTest, correctness) {
+  auto m_label = create_alignment_test_label(GetParam().second);
+
+  auto expected = GetParam().first;
+  auto actual = m_label->get();
+  EXPECT_EQ(expected, actual);
+}
+
+TEST_P(GetTest, soundness) {
+  auto m_label = create_alignment_test_label(GetParam().second);
+  auto actual = m_label->get();
+  EXPECT_TRUE(m_label->m_maxlen == 0 || actual.length() <= m_label->m_maxlen) << "Returned text is longer than maxlen";
+  EXPECT_GE(actual.length(), m_label->m_minlen) << "Returned text is shorter than minlen";
+}
diff --git a/tests/unit_tests/utils/actions.cpp b/tests/unit_tests/utils/actions.cpp
new file mode 100644 (file)
index 0000000..d93fc36
--- /dev/null
@@ -0,0 +1,51 @@
+#include "utils/actions.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+using namespace actions_util;
+
+template<typename T1, typename T2, typename T3>
+using triple = std::tuple<T1, T2, T3>;
+
+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", ""}},
+};
+
+TEST_P(ParseActionStringTest, correctness) {
+  auto action_string = GetParam().first;
+  auto exp = GetParam().second;
+
+  auto res = parse_action_string(action_string);
+
+  EXPECT_EQ(res, exp);
+}
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseActionStringTest, ::testing::ValuesIn(parse_action_string_list));
+
+class ParseActionStringThrowTest : public ::testing::TestWithParam<string> {};
+
+vector<string> parse_action_string_throw_list = {
+  "#",
+  "#.",
+  "#..",
+  "#handler..",
+  "#.action.",
+  "#.action.data",
+  "#..data",
+  "#.data",
+};
+
+INSTANTIATE_TEST_SUITE_P(Inst, ParseActionStringThrowTest, ::testing::ValuesIn(parse_action_string_throw_list));
+
+TEST_P(ParseActionStringThrowTest, correctness) {
+  EXPECT_THROW(parse_action_string(GetParam()), std::runtime_error);
+}
diff --git a/tests/unit_tests/utils/color.cpp b/tests/unit_tests/utils/color.cpp
new file mode 100644 (file)
index 0000000..29c0e3f
--- /dev/null
@@ -0,0 +1,125 @@
+#include "utils/color.hpp"
+
+#include "common/test.hpp"
+
+using namespace polybar;
+
+TEST(Rgba, constructor) {
+  EXPECT_FALSE(rgba("invalid").has_color());
+
+  EXPECT_FALSE(rgba("#f").has_color());
+
+  EXPECT_EQ(rgba::ALPHA_ONLY, rgba{"#12"}.type());
+
+  EXPECT_EQ(0xff000000, rgba{"#ff"}.value());
+
+  EXPECT_EQ(0xffffffff, rgba{"#fff"}.value());
+
+  EXPECT_EQ(0xFF889900, rgba{"#890"}.value());
+
+  EXPECT_EQ(0xaa889900, rgba{"#a890"}.value());
+
+  EXPECT_EQ(0x55888777, rgba{"#55888777"}.value());
+
+  EXPECT_EQ(0x88aaaaaa, rgba{"#88aaaaaa"}.value());
+
+  EXPECT_EQ(0x00aaaaaa, rgba{"#00aaaaaa"}.value());
+
+  EXPECT_EQ(0x00FFFFFF, rgba{"#00FFFFFF"}.value());
+}
+
+TEST(Rgba, parse) {
+  EXPECT_EQ(0xffffffff, rgba{"#fff"}.value());
+  EXPECT_EQ(0xffffffff, rgba{"fff"}.value());
+  EXPECT_EQ(0xff112233, rgba{"#123"}.value());
+  EXPECT_EQ(0xff112233, rgba{"123"}.value());
+  EXPECT_EQ(0xff888888, rgba{"#888888"}.value());
+  EXPECT_EQ(0xff888888, rgba{"888888"}.value());
+  EXPECT_EQ(0x00aa00aa, rgba{"#00aa00aa"}.value());
+  EXPECT_EQ(0x00aa00aa, rgba{"00aa00aa"}.value());
+  EXPECT_EQ(0x11223344, rgba{"#1234"}.value());
+  EXPECT_EQ(0x11223344, rgba{"1234"}.value());
+  EXPECT_EQ(0xaa000000, rgba{"#aa"}.value());
+  EXPECT_EQ(0xaa000000, rgba{"aa"}.value());
+}
+
+TEST(Rgba, string) {
+  EXPECT_EQ("#11223344", static_cast<string>(rgba{"#1234"}));
+  EXPECT_EQ("#12000000", static_cast<string>(rgba{"#12"}));
+}
+
+TEST(Rgba, eq) {
+  rgba v(0x12, rgba::NONE);
+
+  EXPECT_TRUE(v == rgba(0, rgba::NONE));
+  EXPECT_TRUE(v == rgba(0x11, rgba::NONE));
+  EXPECT_FALSE(v == rgba{0x123456});
+
+  v = rgba{0xCC123456};
+
+  EXPECT_TRUE(v == rgba{0xCC123456});
+  EXPECT_FALSE(v == rgba(0xCC123456, rgba::NONE));
+
+  v = rgba{"#aa"};
+
+  EXPECT_TRUE(v == rgba(0xaa000000, rgba::ALPHA_ONLY));
+  EXPECT_FALSE(v == rgba(0xaa000000, rgba::ARGB));
+  EXPECT_FALSE(v == rgba(0xab000000, rgba::ALPHA_ONLY));
+}
+
+TEST(Rgba, hasColor) {
+  rgba v{"#"};
+
+  EXPECT_FALSE(v.has_color());
+
+  v = rgba{"#ff"};
+
+  EXPECT_TRUE(v.has_color());
+
+  v = rgba{"#cc123456"};
+
+  EXPECT_TRUE(v.has_color());
+
+  v = rgba(0x1243, rgba::NONE);
+
+  EXPECT_FALSE(v.has_color());
+}
+
+TEST(Rgba, channel) {
+  rgba v{0xCC123456};
+  EXPECT_EQ(0xCC, v.alpha_i());
+  EXPECT_EQ(0x12, v.red_i());
+  EXPECT_EQ(0x34, v.green_i());
+  EXPECT_EQ(0x56, v.blue_i());
+
+  EXPECT_EQ(0xCC / 255.0, rgba{0xCC112233}.alpha_d());
+  EXPECT_EQ(0x99 / 255.0, rgba{0x88449933}.green_d());
+}
+
+TEST(Rgba, applyAlphaTo) {
+  rgba v{0xAA000000, rgba::ALPHA_ONLY};
+  rgba modified = v.apply_alpha_to(rgba{0xCC123456, rgba::ALPHA_ONLY});
+  EXPECT_EQ(0xAA123456, modified.value());
+
+  v = rgba{0xCC999999};
+  modified = v.apply_alpha_to(rgba{0x00123456});
+  EXPECT_EQ(0xCC123456, modified.value());
+}
+
+TEST(Rgba, tryApplyAlphaTo) {
+  rgba v{0xAA000000, rgba::ALPHA_ONLY};
+  rgba modified = v.try_apply_alpha_to(rgba{0xCC123456, rgba::ALPHA_ONLY});
+  EXPECT_EQ(0xAA123456, modified.value());
+
+  v = rgba{0xCC999999};
+  modified = v.try_apply_alpha_to(rgba{0x00123456});
+  EXPECT_EQ(0xCC999999, modified.value());
+}
+
+TEST(ColorUtil, simplify) {
+  EXPECT_EQ("#111", color_util::simplify_hex("#FF111111"));
+  EXPECT_EQ("#234", color_util::simplify_hex("#ff223344"));
+  EXPECT_EQ("#ee223344", color_util::simplify_hex("#ee223344"));
+  EXPECT_EQ("#234567", color_util::simplify_hex("#ff234567"));
+  EXPECT_EQ("#00223344", color_util::simplify_hex("#00223344"));
+}
diff --git a/tests/unit_tests/utils/command.cpp b/tests/unit_tests/utils/command.cpp
new file mode 100644 (file)
index 0000000..2283515
--- /dev/null
@@ -0,0 +1,57 @@
+#include "utils/command.hpp"
+#include "common/test.hpp"
+
+#include <unistd.h>
+
+using namespace polybar;
+
+TEST(Command, status) {
+  // Test for command<output_policy::IGNORED>::exec(bool);
+  {
+    auto cmd = command_util::make_command<output_policy::IGNORED>("echo polybar > /dev/null");
+    int status = cmd->exec();
+
+    EXPECT_EQ(status, EXIT_SUCCESS);
+  }
+
+  // Test for command<output_policy::REDIRECTED>::exec(bool);
+  {
+    auto cmd = command_util::make_command<output_policy::REDIRECTED>("echo polybar");
+    int status = cmd->exec();
+
+    EXPECT_EQ(status, EXIT_SUCCESS);
+  }
+}
+
+TEST(Command, status_async) {
+  {
+    auto cmd = command_util::make_command<output_policy::IGNORED>("echo polybar > /dev/null");
+    EXPECT_EQ(cmd->exec(false), EXIT_SUCCESS);
+
+    cmd->wait();
+
+    EXPECT_FALSE(cmd->is_running());
+    EXPECT_EQ(cmd->get_exit_status(), EXIT_SUCCESS);
+  }
+}
+
+TEST(Command, output) {
+  auto cmd = command_util::make_command<output_policy::REDIRECTED>("echo polybar");
+  string str;
+  cmd->exec(false);
+  cmd->tail([&str](string&& string) { str = string; });
+  cmd->wait();
+
+  EXPECT_EQ(str, "polybar");
+}
+
+TEST(Command, readline) {
+  auto cmd = command_util::make_command<output_policy::REDIRECTED>("read text;echo $text");
+
+  string str;
+  cmd->exec(false);
+  cmd->writeline("polybar");
+  cmd->tail([&str](string&& string) { str = string; });
+
+  EXPECT_EQ(str, "polybar");
+}
diff --git a/tests/unit_tests/utils/file.cpp b/tests/unit_tests/utils/file.cpp
new file mode 100644 (file)
index 0000000..1c303dc
--- /dev/null
@@ -0,0 +1,16 @@
+#include <iomanip>
+#include <iostream>
+
+#include "common/test.hpp"
+#include "utils/command.hpp"
+#include "utils/file.hpp"
+
+using namespace polybar;
+
+TEST(File, expand) {
+  auto cmd = command_util::make_command<output_policy::REDIRECTED>("echo $HOME");
+  cmd->exec();
+  cmd->tail([](string home) {
+      EXPECT_EQ(home + "/test", file_util::expand("~/test"));
+      });
+}
diff --git a/tests/unit_tests/utils/math.cpp b/tests/unit_tests/utils/math.cpp
new file mode 100644 (file)
index 0000000..c02d8b0
--- /dev/null
@@ -0,0 +1,85 @@
+#include "common/test.hpp"
+#include "utils/math.hpp"
+
+using namespace polybar;
+
+TEST(Math, min) {
+  EXPECT_EQ(2, math_util::min<int>(2, 5));
+  EXPECT_EQ(-50, math_util::min<int>(-8, -50));
+  EXPECT_EQ(0, math_util::min<unsigned char>(0, -5));
+}
+
+TEST(Math, max) {
+  EXPECT_EQ(5, math_util::max<int>(2, 5));
+  EXPECT_EQ(-8, math_util::max<int>(-8, -50));
+  EXPECT_EQ(251, math_util::max<unsigned char>(0, (1 << 8) - 5));
+}
+
+TEST(Math, cap) {
+  EXPECT_EQ(8, math_util::cap<int>(8, 0, 10));
+  EXPECT_EQ(0, math_util::cap<int>(-8, 0, 10));
+  EXPECT_EQ(10, math_util::cap<int>(15, 0, 10));
+  EXPECT_EQ(20.5f, math_util::cap<float>(20.5f, 0.0f, 30.0f));
+  EXPECT_EQ(1.0f, math_util::cap<float>(1.0f, 0.0f, 2.0f));
+  EXPECT_EQ(-2.0f, math_util::cap<float>(-2.0f, -5.0f, 5.0f));
+  EXPECT_EQ(0, math_util::cap<float>(1.0f, 0.0f, 0.0f));
+}
+
+TEST(Math, unbounded_percentage) {
+  EXPECT_EQ(101.0f, (math_util::unbounded_percentage<float, float>(101.0f, 0.0f, 100.0f)));
+  EXPECT_EQ(102, (math_util::unbounded_percentage<float, int>(101.5f, 0.0f, 100.0f)));
+  EXPECT_EQ(110.0f, (math_util::unbounded_percentage<float, float>(12.0f, -10.0f, 10.0f)));
+  EXPECT_EQ(150.0f, (math_util::unbounded_percentage<float, float>(11.5f, 10.0f, 11.0f)));
+  EXPECT_EQ(-50.0f, (math_util::unbounded_percentage<float, float>(-50.0f, 0.0f, 100.0f)));
+  EXPECT_EQ(-50.0f, (math_util::unbounded_percentage<float, float>(9.5f, 10.0f, 11.0f)));
+}
+
+TEST(Math, percentage) {
+  EXPECT_EQ(55.0f, (math_util::percentage<float, float>(5.5f, 0.0f, 10.0f)));
+  EXPECT_EQ(56, (math_util::percentage<float, int>(5.55f, 0.0f, 10.0f)));
+  EXPECT_EQ(43.75f, (math_util::percentage<float, float>(5.25f, 0.0f, 12.0f)));
+  EXPECT_EQ(41, (math_util::percentage<int, int>(5, 0, 12)));
+  EXPECT_EQ(20.5f, (math_util::percentage<float, float>(20.5f, 0.0f, 100.0f)));
+  EXPECT_EQ(70.0f, (math_util::percentage<float, float>(4.5f, 1.0f, 6.0f)));
+  EXPECT_EQ(21, (math_util::percentage<float, int>(20.5f, 0.0f, 100.0f)));
+  EXPECT_EQ(50, (math_util::percentage<int, int>(4, 2, 6)));
+  EXPECT_EQ(50, (math_util::percentage<int, int>(0, -10, 10)));
+  EXPECT_EQ(0, (math_util::percentage<int, int>(-10, -10, 10)));
+  EXPECT_EQ(100, (math_util::percentage<int, int>(10, -10, 10)));
+  EXPECT_EQ(10, (math_util::percentage(10, 0, 100)));
+}
+
+TEST(Math, percentageToValue) {
+  EXPECT_EQ(3, math_util::percentage_to_value(50, 5));
+  EXPECT_EQ(2.5f, (math_util::percentage_to_value<int, float>(50, 5)));
+  EXPECT_EQ(0, math_util::percentage_to_value(0, 5));
+  EXPECT_EQ(1, math_util::percentage_to_value(10, 5));
+  EXPECT_EQ(1, math_util::percentage_to_value(20, 5));
+  EXPECT_EQ(2, math_util::percentage_to_value(30, 5));
+  EXPECT_EQ(2, math_util::percentage_to_value(40, 5));
+  EXPECT_EQ(3, math_util::percentage_to_value(50, 5));
+  EXPECT_EQ(5, math_util::percentage_to_value(100, 5));
+  EXPECT_EQ(5, math_util::percentage_to_value(200, 5));
+  EXPECT_EQ(0, math_util::percentage_to_value(-30, 5));
+}
+
+TEST(Math, rangedPercentageToValue) {
+  EXPECT_EQ(250, math_util::percentage_to_value(50, 200, 300));
+  EXPECT_EQ(3, math_util::percentage_to_value(50, 1, 5));
+}
+
+TEST(Math, roundToNearest10) {
+  EXPECT_EQ(50, math_util::nearest_10(52));
+  EXPECT_EQ(10, math_util::nearest_10(9.1));
+  EXPECT_EQ(100, math_util::nearest_10(95.0));
+  EXPECT_EQ(90, math_util::nearest_10(94.9));
+}
+
+TEST(Math, roundToNearest5) {
+  EXPECT_EQ(50, math_util::nearest_5(52));
+  EXPECT_EQ(10, math_util::nearest_5(9.1));
+  EXPECT_EQ(95, math_util::nearest_5(95.0));
+  EXPECT_EQ(95, math_util::nearest_5(94.9));
+  EXPECT_EQ(0, math_util::nearest_5(1));
+  EXPECT_EQ(100, math_util::nearest_5(99.99));
+}
diff --git a/tests/unit_tests/utils/memory.cpp b/tests/unit_tests/utils/memory.cpp
new file mode 100644 (file)
index 0000000..5c50f8b
--- /dev/null
@@ -0,0 +1,23 @@
+#include "common/test.hpp"
+#include "utils/memory.hpp"
+
+using namespace polybar;
+
+struct mytype {
+  int x, y, z;
+};
+
+TEST(Memory, makeMallocPtr) {
+  auto ptr = memory_util::make_malloc_ptr<mytype>();
+  EXPECT_EQ(sizeof(mytype*), sizeof(ptr.get()));
+  ptr.reset();
+  EXPECT_EQ(nullptr, ptr.get());
+}
+
+TEST(Memory, countof) {
+  mytype A[3]{{}, {}, {}};
+  mytype B[8]{{}, {}, {}, {}, {}, {}, {}, {}};
+
+  EXPECT_EQ(memory_util::countof(A), size_t{3});
+  EXPECT_EQ(memory_util::countof(B), size_t{8});
+}
diff --git a/tests/unit_tests/utils/process.cpp b/tests/unit_tests/utils/process.cpp
new file mode 100644 (file)
index 0000000..64ff21f
--- /dev/null
@@ -0,0 +1,34 @@
+#include "utils/process.hpp"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <thread>
+
+#include "common/test.hpp"
+
+using namespace polybar;
+using namespace process_util;
+
+TEST(SpawnAsync, is_async) {
+  pid_t pid = spawn_async([] { exec_sh("sleep 0.1"); });
+  int status;
+
+  pid_t res = process_util::wait_for_completion_nohang(pid, &status);
+
+  ASSERT_NE(res, -1);
+
+  EXPECT_FALSE(WIFEXITED(status));
+}
+
+TEST(SpawnAsync, exit_code) {
+  pid_t pid = spawn_async([] { exec_sh("exit 42"); });
+  int status = 0;
+  pid_t res = waitpid(pid, &status, 0);
+
+  EXPECT_EQ(res, pid);
+
+  EXPECT_EQ(WEXITSTATUS(status), 42);
+}
diff --git a/tests/unit_tests/utils/scope.cpp b/tests/unit_tests/utils/scope.cpp
new file mode 100644 (file)
index 0000000..69ab87d
--- /dev/null
@@ -0,0 +1,19 @@
+#include "common/test.hpp"
+#include "utils/scope.hpp"
+
+using namespace polybar;
+
+TEST(Scope, onExit) {
+  auto flag = false;
+  {
+    EXPECT_FALSE(flag);
+    auto handler = scope_util::make_exit_handler<>([&] { flag = true; });
+    EXPECT_FALSE(flag);
+    {
+      auto handler = scope_util::make_exit_handler<>([&] { flag = true; });
+    }
+    EXPECT_TRUE(flag);
+    flag = false;
+  }
+  EXPECT_TRUE(flag);
+}
diff --git a/tests/unit_tests/utils/string.cpp b/tests/unit_tests/utils/string.cpp
new file mode 100644 (file)
index 0000000..84c16d1
--- /dev/null
@@ -0,0 +1,150 @@
+#include "utils/string.hpp"
+#include "common/test.hpp"
+
+using namespace polybar;
+
+TEST(String, upper) {
+  EXPECT_EQ("FOO", string_util::upper("FOO"));
+  EXPECT_EQ("FOO", string_util::upper("FoO"));
+  EXPECT_EQ("FOO", string_util::upper("FOo"));
+  EXPECT_EQ("FOO", string_util::upper("Foo"));
+}
+
+TEST(String, lower) {
+  EXPECT_EQ("bar", string_util::lower("BAR"));
+}
+
+TEST(String, compare) {
+  EXPECT_TRUE(string_util::compare("foo", "foo"));
+  EXPECT_TRUE(string_util::compare("foo", "Foo"));
+  EXPECT_FALSE(string_util::compare("foo", "bar"));
+}
+
+TEST(String, replace) {
+  EXPECT_EQ("a.c", string_util::replace("abc", "b", "."));
+  EXPECT_EQ("a.a", string_util::replace("aaa", "a", ".", 1, 2));
+  EXPECT_EQ(".aa", string_util::replace("aaa", "a", ".", 0, 2));
+  EXPECT_EQ("Foo bxr baz", string_util::replace("Foo bar baz", "a", "x"));
+  EXPECT_EQ("foxoobar", string_util::replace("foooobar", "o", "x", 2, 3));
+  EXPECT_EQ("foooobar", string_util::replace("foooobar", "o", "x", 0, 1));
+}
+
+TEST(String, replaceAll) {
+  EXPECT_EQ("Foo bxr bxzx", string_util::replace_all("Foo bar baza", "a", "x"));
+  EXPECT_EQ("hoohoohoo", string_util::replace_all("hehehe", "he", "hoo"));
+  EXPECT_EQ("hoohehe", string_util::replace_all("hehehe", "he", "hoo", 0, 2));
+  EXPECT_EQ("hehehoo", string_util::replace_all("hehehe", "he", "hoo", 4));
+  EXPECT_EQ("hehehe", string_util::replace_all("hehehe", "he", "hoo", 0, 1));
+  EXPECT_EQ("113113113", string_util::replace_all("131313", "3", "13"));
+}
+
+TEST(String, squeeze) {
+  EXPECT_EQ("Squeze", string_util::squeeze("Squeeeeeze", 'e'));
+  EXPECT_EQ("bar baz foobar", string_util::squeeze("bar  baz   foobar", ' '));
+}
+
+TEST(String, strip) {
+  EXPECT_EQ("Strp", string_util::strip("Striip", 'i'));
+  EXPECT_EQ("test\n", string_util::strip_trailing_newline("test\n\n"));
+}
+
+TEST(String, trim) {
+  EXPECT_EQ("x x", string_util::trim("  x x "));
+  EXPECT_EQ("testxx", string_util::ltrim("xxtestxx", 'x'));
+  EXPECT_EQ("xxtest", string_util::rtrim("xxtestxx", 'x'));
+  EXPECT_EQ("test", string_util::trim("xxtestxx", 'x'));
+}
+
+TEST(String, trimPredicate) {
+  EXPECT_EQ("x\t x", string_util::trim("\t  x\t x   ", isspace));
+  EXPECT_EQ("x\t x", string_util::trim("x\t x   ", isspace));
+}
+
+TEST(String, join) {
+  EXPECT_EQ("A, B, C", string_util::join({"A", "B", "C"}, ", "));
+}
+
+TEST(String, split) {
+  {
+    vector<string> strings = string_util::split("A,B,C", ',');
+    EXPECT_EQ(3, strings.size());
+    EXPECT_EQ("A", strings[0]);
+    EXPECT_EQ("B", strings[1]);
+    EXPECT_EQ("C", strings[2]);
+  }
+
+  {
+    vector<string> strings = string_util::split(",A,,B,,C,", ',');
+    EXPECT_EQ(3, strings.size());
+    EXPECT_EQ("A", strings[0]);
+    EXPECT_EQ("B", strings[1]);
+    EXPECT_EQ("C", strings[2]);
+  }
+}
+
+TEST(String, tokenize) {
+  {
+    vector<string> strings = string_util::tokenize("A,B,C", ',');
+    EXPECT_EQ(3, strings.size());
+    EXPECT_EQ("A", strings[0]);
+    EXPECT_EQ("B", strings[1]);
+    EXPECT_EQ("C", strings[2]);
+  }
+
+  {
+    using namespace std::string_literals;
+    vector<string> strings = string_util::tokenize(",A,,B,,C,", ',');
+    vector<string> result{""s, "A"s, ""s, "B"s, ""s, "C"s, ""s};
+
+    EXPECT_TRUE(strings == result);
+  }
+}
+
+TEST(String, findNth) {
+  EXPECT_EQ(0, string_util::find_nth("foobarfoobar", 0, "f", 1));
+  EXPECT_EQ(6, string_util::find_nth("foobarfoobar", 0, "f", 2));
+  EXPECT_EQ(7, string_util::find_nth("foobarfoobar", 0, "o", 3));
+}
+
+TEST(String, hash) {
+  unsigned long hashA1{string_util::hash("foo")};
+  unsigned long hashA2{string_util::hash("foo")};
+  unsigned long hashB1{string_util::hash("Foo")};
+  unsigned long hashB2{string_util::hash("Bar")};
+  EXPECT_EQ(hashA2, hashA1);
+  EXPECT_NE(hashB1, hashA1);
+  EXPECT_NE(hashB2, hashA1);
+  EXPECT_NE(hashB2, hashB1);
+}
+
+TEST(String, floatingPoint) {
+  EXPECT_EQ("1.26", string_util::floating_point(1.2599, 2));
+  EXPECT_EQ("2", string_util::floating_point(1.7, 0));
+  EXPECT_EQ("1.7770000000", string_util::floating_point(1.777, 10));
+}
+
+TEST(String, filesize) {
+  EXPECT_EQ("3.000 MiB", string_util::filesize_mib(3 * 1024, 3));
+  EXPECT_EQ("3.195 MiB", string_util::filesize_mib(3 * 1024 + 200, 3));
+  EXPECT_EQ("3 MiB", string_util::filesize_mib(3 * 1024 + 400));
+  EXPECT_EQ("4 MiB", string_util::filesize_mib(3 * 1024 + 800));
+  EXPECT_EQ("3.195 GiB", string_util::filesize_gib(3 * 1024 * 1024 + 200 * 1024, 3));
+  EXPECT_EQ("3 GiB", string_util::filesize_gib(3 * 1024 * 1024 + 400 * 1024));
+  EXPECT_EQ("4 GiB", string_util::filesize_gib(3 * 1024 * 1024 + 800 * 1024));
+  EXPECT_EQ("3 B", string_util::filesize(3));
+  EXPECT_EQ("3 KB", string_util::filesize(3 * 1024));
+  EXPECT_EQ("3 MB", string_util::filesize(3 * 1024 * 1024));
+  EXPECT_EQ("3 GB", string_util::filesize((unsigned long long)3 * 1024 * 1024 * 1024));
+  EXPECT_EQ("3 TB", string_util::filesize((unsigned long long)3 * 1024 * 1024 * 1024 * 1024));
+}
+
+TEST(String, operators) {
+  string foo = "foobar";
+  EXPECT_EQ("foo", foo - "bar");
+  string baz = "bazbaz";
+  EXPECT_EQ("bazbaz", baz - "ba");
+  EXPECT_EQ("bazbaz", baz - "baZ");
+  EXPECT_EQ("bazbaz", baz - "bazbz");
+  string aaa = "aaa";
+  EXPECT_EQ("aaa", aaa - "aaaaa");
+}
diff --git a/version.txt b/version.txt
new file mode 100644 (file)
index 0000000..207bcec
--- /dev/null
@@ -0,0 +1,4 @@
+# Polybar version information
+# Update this on every release
+# This is used to create the version string if a git repo is not available
+3.5.5