From 5e1886a31272b8b71cef661ac0f8c3de95a5321f Mon Sep 17 00:00:00 2001 From: Michael Carlberg Date: Thu, 19 Jan 2017 05:38:42 +0100 Subject: [PATCH] wip(refactor): Cairo drawing --- .ycm_extra_conf.py | 2 +- cmake/build/core.cmake | 2 - cmake/build/options.cmake | 12 +- cmake/build/summary.cmake | 91 ++-- include/cairo/context.hpp | 258 ++++++++++ include/cairo/font.hpp | 283 +++++++++++ include/cairo/fwd.hpp | 15 + include/cairo/surface.hpp | 50 ++ include/cairo/types.hpp | 46 ++ include/cairo/utils.hpp | 142 ++++++ include/components/bar.hpp | 7 +- include/components/controller.hpp | 8 +- include/components/logger.hpp | 2 +- include/components/parser.hpp | 4 - include/components/renderer.hpp | 130 +++-- include/components/types.hpp | 8 +- include/events/signal.hpp | 11 +- include/events/signal_fwd.hpp | 5 +- include/events/types.hpp | 3 + include/settings.hpp.cmake | 6 +- include/utils/cache.hpp | 7 +- include/utils/color.hpp | 76 ++- include/utils/math.hpp | 8 +- include/x11/color.hpp | 41 -- include/x11/fonts.hpp | 95 ---- src/CMakeLists.txt | 16 +- src/components/bar.cpp | 116 +++-- src/components/config.cpp | 14 +- src/components/controller.cpp | 22 +- src/components/logger.cpp | 5 +- src/components/parser.cpp | 56 +-- src/components/renderer.cpp | 774 ++++++++++++------------------ src/x11/color.cpp | 70 --- src/x11/draw.cpp | 2 +- src/x11/fonts.cpp | 307 ------------ src/x11/tray_manager.cpp | 3 +- 36 files changed, 1435 insertions(+), 1262 deletions(-) create mode 100644 include/cairo/context.hpp create mode 100644 include/cairo/font.hpp create mode 100644 include/cairo/fwd.hpp create mode 100644 include/cairo/surface.hpp create mode 100644 include/cairo/types.hpp create mode 100644 include/cairo/utils.hpp delete mode 100644 include/x11/color.hpp delete mode 100644 include/x11/fonts.hpp delete mode 100644 src/x11/color.cpp delete mode 100644 src/x11/fonts.cpp diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index aa6a7c06..95562fbd 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -52,8 +52,8 @@ flags.append('-I'+ DirectoryOfThisScript() +'/lib/concurrentqueue/include') flags.append('-I'+ DirectoryOfThisScript() +'/lib/i3ipcpp/include') flags.append('-I'+ DirectoryOfThisScript() +'/lib/xpp/include') flags.append('-I'+ DirectoryOfThisScript() +'/tests') -flags.append('-I/usr/include/freetype2') flags.append('-I/usr/include') +flags.append('-I/usr/include/freetype2') def MakeRelativePathsInFlagsAbsolute( flags, working_directory ): if not working_directory: diff --git a/cmake/build/core.cmake b/cmake/build/core.cmake index 0056c36c..57449abe 100644 --- a/cmake/build/core.cmake +++ b/cmake/build/core.cmake @@ -56,8 +56,6 @@ if(CXXLIB_CLANG) elseif(CXXLIB_GCC) message_colored(STATUS "Linking against libstdc++" 32) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lstdc++") -else() - message_colored(STATUS "No preferred c++lib specified... linking against system default" 33) endif() if(ENABLE_CCACHE) diff --git a/cmake/build/options.cmake b/cmake/build/options.cmake index b57f1b19..24e70ae6 100644 --- a/cmake/build/options.cmake +++ b/cmake/build/options.cmake @@ -50,13 +50,13 @@ option(CXXLIB_GCC "Link against stdlibc++" OFF) option(BUILD_IPC_MSG "Build ipc messager" ON) option(BUILD_TESTS "Build testsuite" OFF) -option(DEBUG_LOGGER "Enable extra debug logging" OFF) -option(DEBUG_LOGGER_TRACE "Enable verbose trace logs" OFF) -option(DEBUG_HINTS "Enable hints rendering" OFF) -option(DEBUG_WHITESPACE "Enable whitespace debug" OFF) -if(NOT DEBUG_LOGGER AND DEBUG_LOGGER_TRACE) - set(DEBUG_LOGGER_TRACE OFF) +if(DEBUG) + option(DEBUG_LOGGER "Debug logging" OFF) + option(DEBUG_LOGGER_VERBOSE "Debug logging (verbose)" OFF) + option(DEBUG_HINTS "Debug clickable areas" OFF) + option(DEBUG_WHITESPACE "Debug whitespace" OFF) + option(DEBUG_FONTCONFIG "Debug fontconfig" OFF) endif() option(ENABLE_CCACHE "Enable ccache support" OFF) diff --git a/cmake/build/summary.cmake b/cmake/build/summary.cmake index 82e053f4..73ffc83d 100644 --- a/cmake/build/summary.cmake +++ b/cmake/build/summary.cmake @@ -11,69 +11,72 @@ function(colored_option message_level text var color_on color_off) endif() endfunction() -message(STATUS "--------------------------") +message(STATUS " Build:") if(CMAKE_BUILD_TYPE) - message_colored(STATUS " Build type: ${CMAKE_BUILD_TYPE}" "32;1") + message_colored(STATUS " Type: ${CMAKE_BUILD_TYPE}" "37;2") else() - message_colored(STATUS " Build type: NONE" "33;1") + message_colored(STATUS " Type: NONE" "33;1") endif() -message(STATUS " Compiler C: ${CMAKE_C_COMPILER}") -message(STATUS " Compiler C++: ${CMAKE_CXX_COMPILER}") -message(STATUS " Compiler flags: ${CMAKE_CXX_FLAGS}") string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") - message(STATUS " debug flags: ${CMAKE_CXX_FLAGS_DEBUG}") if(NOT DEFINED ${DEBUG_LOGGER}) set(DEBUG_LOGGER ON) endif() if(NOT DEFINED ${ENABLE_CCACHE}) set(ENABLE_CCACHE ON) endif() + message_colored(STATUS " CC: ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_DEBUG}" "37;2") + message_colored(STATUS " CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_DEBUG}" "37;2") + message_colored(STATUS " LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS}${CMAKE_EXE_LINKER_FLAGS_DEBUG}" "37;2") elseif(CMAKE_BUILD_TYPE_LOWER STREQUAL "release") - message(STATUS " release: ${CMAKE_CXX_FLAGS_RELEASE}") + message_colored(STATUS " CC: ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELEASE}" "37;2") + message_colored(STATUS " CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELEASE}" "37;2") + message_colored(STATUS " LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS}${CMAKE_EXE_LINKER_FLAGS_RELEASE}" "37;2") elseif(CMAKE_BUILD_TYPE_LOWER STREQUAL "sanitize") - message(STATUS " sanitize: ${CMAKE_CXX_FLAGS_SANITIZE}") + message_colored(STATUS " CC: ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_SANITIZE}" "37;2") + message_colored(STATUS " CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_SANITIZE}" "37;2") + message_colored(STATUS " LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS}${CMAKE_EXE_LINKER_FLAGS_SANITIZE}" "37;2") elseif(CMAKE_BUILD_TYPE_LOWER STREQUAL "minsizerel") - message(STATUS " minsizerel: ${CMAKE_CXX_FLAGS_MINSIZEREL}") + message_colored(STATUS " CC: ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_MINSIZEREL}" "37;2") + message_colored(STATUS " CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_MINSIZEREL}" "37;2") + message_colored(STATUS " LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS}${CMAKE_EXE_LINKER_FLAGS_MINSIZEREL}" "37;2") elseif(CMAKE_BUILD_TYPE_LOWER STREQUAL "relwithdebinfo") - message(STATUS " relwithdebinfo: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}") + message_colored(STATUS " CC: ${CMAKE_C_COMPILER} ${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_RELWITHDEBINFO}" "37;2") + message_colored(STATUS " CXX: ${CMAKE_CXX_COMPILER} ${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" "37;2") + message_colored(STATUS " LD: ${CMAKE_LINKER} ${CMAKE_EXE_LINKER_FLAGS}${CMAKE_EXE_LINKER_FLAGS_RELWITHDEBINFO}" "37;2") endif() if(CMAKE_EXE_LINKER_FLAGS) - message(STATUS " Linker flags: ${CMAKE_EXE_LINKER_FLAGS}") + message_colored(STATUS " LD: ${CMAKE_EXE_LINKER_FLAGS}" "37;2") endif() -if(CXXLIB_CLANG) - message(STATUS " C++ library: libc++") -elseif(CXXLIB_GCC) - message(STATUS " C++ library: libstdc++") -else() - message(STATUS " C++ library: system default") -endif() +message(STATUS " Targets:") +colored_option(STATUS " polybar-msg" BUILD_IPC_MSG "32;1" "37;2") +colored_option(STATUS " testsuite" BUILD_TESTS "32;1" "37;2") -message(STATUS "--------------------------") -colored_option(STATUS " Build polybar-msg ${BUILD_IPC_MSG}" BUILD_IPC_MSG "32;1" "37;2") -colored_option(STATUS " Build testsuite ${BUILD_TESTS}" BUILD_TESTS "32;1" "37;2") -colored_option(STATUS " Debug logging ${DEBUG_LOGGER}" DEBUG_LOGGER "32;1" "37;2") -colored_option(STATUS " + Verbose tracing ${DEBUG_LOGGER_TRACE}" DEBUG_LOGGER_TRACE "32;1" "37;2") -colored_option(STATUS " Draw debug hints ${DEBUG_HINTS}" DEBUG_HINTS "32;1" "37;2") -colored_option(STATUS " Draw whitespace ${DEBUG_WHITESPACE}" DEBUG_WHITESPACE "32;1" "37;2") -colored_option(STATUS " Enable ccache ${ENABLE_CCACHE}" ENABLE_CCACHE "32;1" "37;2") -message(STATUS "--------------------------") -colored_option(STATUS " Enable alsa ${ENABLE_ALSA}" ENABLE_ALSA "32;1" "37;2") -colored_option(STATUS " Enable curl ${ENABLE_CURL}" ENABLE_CURL "32;1" "37;2") -colored_option(STATUS " Enable i3 ${ENABLE_I3}" ENABLE_I3 "32;1" "37;2") -colored_option(STATUS " Enable mpd ${ENABLE_MPD}" ENABLE_MPD "32;1" "37;2") -colored_option(STATUS " Enable network ${ENABLE_NETWORK}" ENABLE_NETWORK "32;1" "37;2") -message(STATUS "--------------------------") -colored_option(STATUS " XRANDR support ${WITH_XRANDR}" WITH_XRANDR "32;1" "37;2") -colored_option(STATUS " + XRandR monitors ${ENABLE_XRANDR_MONITORS}" ENABLE_XRANDR_MONITORS "32;1" "37;2") -colored_option(STATUS " XRENDER support ${WITH_XRENDER}" WITH_XRENDER "32;1" "37;2") -colored_option(STATUS " XDAMAGE support ${WITH_XDAMAGE}" WITH_XDAMAGE "32;1" "37;2") -colored_option(STATUS " XSYNC support ${WITH_XSYNC}" WITH_XSYNC "32;1" "37;2") -colored_option(STATUS " XCOMPOSITE support ${WITH_XCOMPOSITE}" WITH_XCOMPOSITE "32;1" "37;2") -colored_option(STATUS " XKB support ${WITH_XKB}" WITH_XKB "32;1" "37;2") -message(STATUS "--------------------------") -colored_option(STATUS " xcb-util-xrm ${WITH_XRM}" WITH_XRM "32;1" "37;2") -message(STATUS "--------------------------") +message(STATUS " Module supprt:") +colored_option(STATUS " alsa" ENABLE_ALSA "32;1" "37;2") +colored_option(STATUS " curl" ENABLE_CURL "32;1" "37;2") +colored_option(STATUS " i3" ENABLE_I3 "32;1" "37;2") +colored_option(STATUS " mpd" ENABLE_MPD "32;1" "37;2") +colored_option(STATUS " network" ENABLE_NETWORK "32;1" "37;2") +message(STATUS " X extensions:") +colored_option(STATUS " XRandR" WITH_XRANDR "32;1" "37;2") +colored_option(STATUS " XRandR (enable monitors)" ENABLE_XRANDR_MONITORS "32;1" "37;2") +colored_option(STATUS " XRender" WITH_XRENDER "32;1" "37;2") +colored_option(STATUS " XDamage" WITH_XDAMAGE "32;1" "37;2") +colored_option(STATUS " XSync" WITH_XSYNC "32;1" "37;2") +colored_option(STATUS " XComposite" WITH_XCOMPOSITE "32;1" "37;2") +colored_option(STATUS " Xkb" WITH_XKB "32;1" "37;2") +colored_option(STATUS " Xrm" WITH_XRM "32;1" "37;2") + +if(CMAKE_BUILD_TYPE_LOWER STREQUAL "debug") + message(STATUS " Debug options:") + colored_option(STATUS " Trace logging" DEBUG_LOGGER "32;1" "37;2") + colored_option(STATUS " Trace logging (verbose)" DEBUG_LOGGER_VERBOSE "32;1" "37;2") + colored_option(STATUS " Draw clickable areas" DEBUG_HINTS "32;1" "37;2") + colored_option(STATUS " Print fc-match details" DEBUG_FONTCONFIG "32;1" "37;2") + colored_option(STATUS " Enable window shading" DEBUG_SHADED "32;1" "37;2") + message(STATUS "--------------------------") +endif() diff --git a/include/cairo/context.hpp b/include/cairo/context.hpp new file mode 100644 index 00000000..8a5a1467 --- /dev/null +++ b/include/cairo/context.hpp @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include + +#include "cairo/font.hpp" +#include "cairo/surface.hpp" +#include "cairo/types.hpp" +#include "cairo/utils.hpp" +#include "common.hpp" +#include "components/logger.hpp" +#include "errors.hpp" +#include "utils/color.hpp" +#include "utils/string.hpp" + +POLYBAR_NS + +namespace cairo { + 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 uint32_t& c) { + // clang-format off + cairo_set_source_rgba(m_c, + color_util::red_channel(c) / 255.0, + color_util::green_channel(c) / 255.0, + color_util::blue_channel(c) / 255.0, + color_util::alpha_channel(c) / 255.0); + // clang-format on + return *this; + } + + context& operator<<(const abspos& p) { + 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.r, f.g, f.b, f.a); + 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 linear_gradient& l) { + if (l.steps.size() >= 2) { + auto pattern = cairo_pattern_create_linear(l.x0, l.y0, l.x1, l.y1); + *this << pattern; + auto stops = l.steps.size(); + 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_util::red_channel(color) / 255.0, + color_util::green_channel(color) / 255.0, + color_util::blue_channel(color) / 255.0, + color_util::alpha_channel(color) / 255.0); + // clang-format on + offset += step; + } + cairo_pattern_destroy(pattern); + } + return *this; + } + + context& operator<<(const textblock& t) { + // Sort the fontlist so that the preferred font is tested first + auto& fns = m_fonts; + std::sort(fns.begin(), fns.end(), [&](const unique_ptr& a, const unique_ptr&) { + if (t.fontindex > 0 && std::distance(fns.begin(), std::find(fns.begin(), fns.end(), a)) == t.fontindex - 1) { + return -1; + } else { + return 0; + } + }); + + string utf8 = string(t.contents); + unicode_charlist chars; + utils::utf8_to_ucs4((const uint8_t*)utf8.c_str(), chars); + + while (!chars.empty()) { + auto remaining = chars.size(); + for (auto&& f : fns) { + auto matches = f->match(chars); + if (!matches) { + continue; + } + + string subset; + auto end = chars.begin(); + while (matches-- && end != chars.end()) { + subset += utf8.substr(end->offset, end->length); + end++; + } + + f->use(m_c); + + cairo_text_extents_t extents; + f->textwidth(utf8, &extents); + + save(); // encapsulate the y pos update + *this << relpos{0.0, extents.height / 2.0 - f->extents().descent + f->offset()}; + f->render(subset); + restore(); + + *this << relpos{extents.width, 0.0}; + + chars.erase(chars.begin(), end); + break; + } + + if (chars.empty()) { + break; + } else if (remaining != chars.size()) { + continue; + } + + char unicode[5]{'\0'}; + utils::ucs4_to_utf8(unicode, chars.begin()->codepoint); + m_log.warn("Dropping unmatched character %s (U+%04x)", unicode, chars.begin()->codepoint); + 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<<(unique_ptr&& f) { + m_fonts.emplace_back(forward(f)); + return *this; + } + + context& save(bool save_point = true) { + if (save_point) { + m_points.emplace_front(make_pair(0.0, 0.0)); + position(&m_points.front().first, &m_points.front().second); + } + cairo_save(m_c); + return *this; + } + + context& restore(bool restore_point = true) { + if (restore_point) { + *this << abspos{m_points.front().first, m_points.front().first}; + m_points.pop_front(); + } + cairo_restore(m_c); + 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() { + cairo_fill(m_c); + return *this; + } + + context& clip(const rect& r) { + *this << r; + cairo_clip(m_c); + return *this; + } + + context& reset_clip() { + cairo_reset_clip(m_c); + return *this; + } + + context& position(double* x, double* y) { + *x = 0.0; + *y = 0.0; + if (cairo_has_current_point(m_c)) { + 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 = ((int)*x + 0.5); + *y = ((int)*y + 0.5); + return *this; + } + + protected: + cairo_t* m_c; + const logger& m_log; + vector> m_fonts; + std::deque> m_points; + }; +} + +POLYBAR_NS_END diff --git a/include/cairo/font.hpp b/include/cairo/font.hpp new file mode 100644 index 00000000..9f75a624 --- /dev/null +++ b/include/cairo/font.hpp @@ -0,0 +1,283 @@ +#pragma once + +#include +#include +#include +#include + +#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 { + namespace details { + /** + * @brief Global pointer to the Freetype library handler + */ + static FT_Library g_ftlib; + + /** + * @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) : m_font(font) { + m_face = cairo_ft_scaled_font_lock_face(m_font); + } + ~ft_face_lock() { + cairo_ft_scaled_font_unlock_face(m_font); + } + + operator FT_Face() const { + return m_face; + } + + 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() : codepoint(0), offset(0), length(0) {} + + unsigned long codepoint; + int offset; + int length; + }; + using unicode_charlist = std::list; + + /** + * @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() const = 0; + + virtual void use(cairo_t* c) { + cairo_set_font_face(c, cairo_font_face_reference(m_font_face)); + } + + virtual size_t match(unicode_charlist& charlist) = 0; + virtual size_t render(const string& text) = 0; + virtual void textwidth(const string& text, cairo_text_extents_t* extents) = 0; + + cairo_font_extents_t extents() const { + return m_extents; + } + + 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) : font(cairo, offset), m_pattern(pattern) { + cairo_matrix_t fm; + cairo_matrix_t ctm; + cairo_matrix_init_scale(&fm, size(), size()); + 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(cairo_scaled_font_reference(m_scaled)); + auto face = static_cast(*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; + } + } + + ~font_fc() override { + if (m_scaled != nullptr) { + cairo_scaled_font_destroy(m_scaled); + } + if (m_pattern != nullptr) { + FcPatternDestroy(m_pattern); + } + } + + string name() const override { + return property("family"); + } + + string file() const override { + return property("file"); + } + + double offset() const override { + return m_offset; + } + + double size() const override { + bool scalable; + double px; + property(FC_SCALABLE, &scalable); + if (scalable) { + property(FC_SIZE, &px); + } else { + property(FC_PIXEL_SIZE, &px); + px = static_cast(px + 0.5); + } + return px; + } + + void use(cairo_t* c) override { + cairo_set_scaled_font(c, cairo_scaled_font_reference(m_scaled)); + } + + size_t match(unicode_charlist& charlist) override { + auto lock = make_unique(cairo_scaled_font_reference(m_scaled)); + auto face = static_cast(*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) override { + double x, y; + cairo_get_current_point(m_cairo, &x, &y); + + 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(cairo_scaled_font_reference(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_status()" << 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) { + utf8 = text.substr(0, bytes); + cairo_scaled_font_text_to_glyphs( + m_scaled, x, y, utf8.c_str(), utf8.size(), &glyphs, &nglyphs, &clusters, &nclusters, &cf); + cairo_show_text_glyphs(m_cairo, utf8.c_str(), utf8.size(), glyphs, nglyphs, clusters, nclusters, cf); + } + + return bytes; + } + + void textwidth(const string& text, cairo_text_extents_t* extents) override { + cairo_scaled_font_text_extents(cairo_scaled_font_reference(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(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 + */ + decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset) { + static bool fc_init{false}; + if (!fc_init && !(fc_init = FcInit())) { + throw application_error("Could not load fontconfig"); + } else if (FT_Init_FreeType(&details::g_ftlib) != FT_Err_Ok) { + throw application_error("Could not load FreeType"); + } + + static auto fc_cleanup = scope_util::make_exit_handler([] { + FT_Done_FreeType(details::g_ftlib); + FcFini(); + }); + + auto pattern = FcNameParse((FcChar8*)fontname.c_str()); + 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_unique(cairo, match, offset); + } +} + +POLYBAR_NS_END diff --git a/include/cairo/fwd.hpp b/include/cairo/fwd.hpp new file mode 100644 index 00000000..1730f9da --- /dev/null +++ b/include/cairo/fwd.hpp @@ -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 index 00000000..51ffa13a --- /dev/null +++ b/include/cairo/surface.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include "cairo/types.hpp" +#include "common.hpp" +#include "utils/color.hpp" + +POLYBAR_NS + +namespace cairo { + 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; + } + + surface& flush() { + cairo_surface_flush(m_s); + return *this; + } + + surface& dirty() { + cairo_surface_mark_dirty(m_s); + return *this; + } + + surface& dirty(const rect& r) { + cairo_surface_mark_dirty_rectangle(m_s, r.x, r.y, r.w, r.h); + return *this; + } + + protected: + cairo_surface_t* m_s; + }; + + 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 {} + }; +} + +POLYBAR_NS_END diff --git a/include/cairo/types.hpp b/include/cairo/types.hpp new file mode 100644 index 00000000..72ebcc9a --- /dev/null +++ b/include/cairo/types.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "common.hpp" + +POLYBAR_NS + +namespace cairo { + struct abspos { + double x; + double y; + }; + 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 linear_gradient { + double x0; + double y0; + double x1; + double y1; + vector steps; + }; + + struct textblock { + string contents; + uint8_t fontindex; + }; +} + +POLYBAR_NS_END diff --git a/include/cairo/utils.hpp b/include/cairo/utils.hpp new file mode 100644 index 00000000..6d4a353a --- /dev/null +++ b/include/cairo/utils.hpp @@ -0,0 +1,142 @@ +#pragma once + +#include +#include + +#include "cairo/font.hpp" +#include "common.hpp" +#include "utils/string.hpp" + +POLYBAR_NS + +namespace cairo { + namespace utils { + /** + * @see + */ + cairo_operator_t str2operator(const string& mode, cairo_operator_t fallback) { + if (mode.empty()) { + return fallback; + } + static std::map 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, uint32_t 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/include/components/bar.hpp b/include/components/bar.hpp index 9cab5fd9..040f9c10 100644 --- a/include/components/bar.hpp +++ b/include/components/bar.hpp @@ -4,11 +4,11 @@ #include #include "common.hpp" -#include "settings.hpp" #include "components/types.hpp" #include "errors.hpp" #include "events/signal_fwd.hpp" #include "events/signal_receiver.hpp" +#include "settings.hpp" #include "x11/events.hpp" #include "x11/types.hpp" #include "x11/window.hpp" @@ -22,15 +22,14 @@ class logger; class parser; class renderer; class screen; -class signal_emitter; class taskqueue; class tray_manager; // }}} class bar : public xpp::event::sink, - public signal_receiver { + public signal_receiver { public: using make_type = unique_ptr; static make_type make(bool only_initialize_values = false); diff --git a/include/components/controller.hpp b/include/components/controller.hpp index 0e32797a..d166c9b8 100644 --- a/include/components/controller.hpp +++ b/include/components/controller.hpp @@ -36,7 +36,7 @@ using modulemap_t = std::map>; class controller : public signal_receiver { + signals::ipc::command, signals::ipc::hook, signals::ui::ready, signals::ui::button_press> { public: using make_type = unique_ptr; static make_type make(unique_ptr&& ipc, unique_ptr&& config_watch); @@ -61,6 +61,7 @@ class controller : public signal_receiver, 2> m_queuefd{}; + /** + * @brief State flag + */ + std::atomic m_process_events{false}; + /** * @brief Controls weather the output gets printed to stdout */ diff --git a/include/components/logger.hpp b/include/components/logger.hpp index 1c83b32b..ee550624 100644 --- a/include/components/logger.hpp +++ b/include/components/logger.hpp @@ -41,7 +41,7 @@ class logger { void trace(string message, Args... args) const { output(loglevel::TRACE, message, args...); } -#ifdef DEBUG_LOGGER_TRACE +#ifdef DEBUG_LOGGER_VERBOSE template void trace_x(string message, Args... args) const { output(loglevel::TRACE, message, args...); diff --git a/include/components/parser.hpp b/include/components/parser.hpp index 09c71656..c5690df3 100644 --- a/include/components/parser.hpp +++ b/include/components/parser.hpp @@ -17,10 +17,6 @@ DEFINE_CHILD_ERROR(unclosed_actionblocks, parser_error); class parser { public: - struct packet { - uint16_t data[128]{0U}; - size_t length{0}; - }; using make_type = unique_ptr; static make_type make(); diff --git a/include/components/renderer.hpp b/include/components/renderer.hpp index 52a348be..a8bd8c09 100644 --- a/include/components/renderer.hpp +++ b/include/components/renderer.hpp @@ -1,94 +1,71 @@ #pragma once +#include +#include + +#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/fonts.hpp" #include "x11/types.hpp" POLYBAR_NS -// fwd +// fwd {{{ class connection; -class font_manager; +class config; class logger; -class signal_emitter; +// }}} -using namespace signals::parser; using std::map; class renderer - : public signal_receiver { + : public signal_receiver { public: - enum class gc : uint8_t { BG, FG, OL, UL, BT, BB, BL, BR }; - using make_type = unique_ptr; - static make_type make(const bar_settings& bar, vector&& fonts); + static make_type make(const bar_settings& bar); - explicit renderer(connection& conn, signal_emitter& emitter, const logger& logger, - unique_ptr font_manager, const bar_settings& bar, const vector& fonts); + explicit renderer( + connection& conn, signal_emitter& emitter, const config&, const logger& logger, const bar_settings& bar); ~renderer(); - renderer(const renderer& o) = delete; - renderer& operator=(const renderer& o) = delete; - xcb_window_t window() const; + const vector actions() const; void begin(); void end(); - void flush(bool clear); + void flush(); void reserve_space(edge side, uint16_t w); - - void set_background(const uint32_t color); - void set_foreground(const uint32_t color); - void set_underline(const uint32_t color); - void set_overline(const uint32_t color); - void set_fontindex(const uint8_t font); - void set_alignment(const alignment align); - void set_attribute(const attribute attr, const bool state); - void toggle_attribute(const attribute attr); - bool check_attribute(const attribute attr); - void fill_background(); - void fill_overline(int16_t x, uint16_t w); - void fill_underline(int16_t x, uint16_t w); - void fill_shift(const int16_t px); - - void draw_textstring(const uint16_t* text, size_t len); - - void begin_action(const mousebtn btn, const string& cmd); - void end_action(const mousebtn btn); - const vector get_actions(); + void fill_overline(double x, double w); + void fill_underline(double x, double w); + void fill_borders(); + void draw_text(const string& contents); protected: - int16_t shift_content(int16_t x, const int16_t shift_x); - int16_t shift_content(const int16_t shift_x); + void adjust_clickable_areas(double width); + void highlight_clickable_areas(); - bool on(const change_background& evt); - bool on(const change_foreground& evt); - bool on(const change_underline& evt); - bool on(const change_overline& evt); - bool on(const change_font& evt); - bool on(const change_alignment& evt); - bool on(const offset_pixel& evt); - bool on(const attribute_set& evt); - bool on(const attribute_unset& evt); - bool on(const attribute_toggle& evt); - bool on(const action_begin& evt); - bool on(const action_end& evt); - bool on(const write_text_ascii& evt); - bool on(const write_text_unicode& evt); - bool on(const write_text_string& evt); - -#ifdef DEBUG_HINTS - vector m_debughints; - void debug_hints(); -#endif + 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::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); protected: struct reserve_area { @@ -99,34 +76,45 @@ class renderer private: connection& m_connection; signal_emitter& m_sig; + const config& m_conf; const logger& m_log; - unique_ptr m_fontmanager; - const bar_settings& m_bar; xcb_rectangle_t m_rect{0, 0, 0U, 0U}; - xcb_rectangle_t m_cleared{0, 0, 0U, 0U}; reserve_area m_cleararea{}; uint8_t m_depth{32}; xcb_window_t m_window; xcb_colormap_t m_colormap; xcb_visualtype_t* m_visual; - // xcb_gcontext_t m_gcontext; + xcb_gcontext_t m_gcontext; xcb_pixmap_t m_pixmap; - map m_gcontexts; - map m_pixmaps; vector m_actions; // bool m_autosize{false}; - uint16_t m_currentx{0U}; - alignment m_alignment{alignment::NONE}; - map m_colors; - uint8_t m_attributes{0U}; - uint8_t m_fontindex{0}; - xcb_font_t m_gcfont{XCB_NONE}; + unique_ptr m_context; + unique_ptr m_surface; + + cairo_operator_t m_compositing_background; + cairo_operator_t m_compositing_foreground; + cairo_operator_t m_compositing_overline; + cairo_operator_t m_compositing_underline; + cairo_operator_t m_compositing_borders; + + alignment m_alignment{alignment::NONE}; + std::bitset<2> m_attributes; + + uint8_t m_fontindex; + + uint32_t m_color_background; + uint32_t m_color_foreground; + uint32_t m_color_overline; + uint32_t m_color_underline; + + double m_x{0.0}; + double m_y{0.0}; }; POLYBAR_NS_END diff --git a/include/components/types.hpp b/include/components/types.hpp index e8055317..28f6a1c7 100644 --- a/include/components/types.hpp +++ b/include/components/types.hpp @@ -9,9 +9,10 @@ POLYBAR_NS -// fwd +// fwd {{{ struct randr_output; using monitor_t = shared_ptr; +// }}} struct enum_hash { template @@ -135,8 +136,9 @@ struct bar_settings { side_values module_margin{0U, 0U}; edge_values strut{0U, 0U, 0U, 0U}; - uint32_t background{0xFFFFFFFF}; - uint32_t foreground{0xFF000000}; + uint32_t background{0xFF000000}; + uint32_t foreground{0xFFFFFFFF}; + vector background_steps; line_settings underline{}; line_settings overline{}; diff --git a/include/events/signal.hpp b/include/events/signal.hpp index 824e32ce..4138ddcd 100644 --- a/include/events/signal.hpp +++ b/include/events/signal.hpp @@ -90,6 +90,9 @@ namespace signals { } namespace ui { + struct ready : public detail::base_signal { + using base_type::base_type; + }; struct tick : public detail::base_signal { using base_type::base_type; }; @@ -153,13 +156,7 @@ namespace signals { struct action_end : public detail::value_signal { using base_type::base_type; }; - struct write_text_ascii : public detail::value_signal { - using base_type::base_type; - }; - struct write_text_unicode : public detail::value_signal { - using base_type::base_type; - }; - struct write_text_string : public detail::value_signal { + struct text : public detail::value_signal { using base_type::base_type; }; } diff --git a/include/events/signal_fwd.hpp b/include/events/signal_fwd.hpp index 7b4f6b07..796fc45f 100644 --- a/include/events/signal_fwd.hpp +++ b/include/events/signal_fwd.hpp @@ -28,6 +28,7 @@ namespace signals { struct action; } namespace ui { + struct ready; struct tick; struct button_press; struct visibility_change; @@ -51,9 +52,7 @@ namespace signals { struct attribute_toggle; struct action_begin; struct action_end; - struct write_text_ascii; - struct write_text_unicode; - struct write_text_string; + struct text; } } diff --git a/include/events/types.hpp b/include/events/types.hpp index 944e2559..2908cfe2 100644 --- a/include/events/types.hpp +++ b/include/events/types.hpp @@ -23,6 +23,9 @@ namespace { inline bool operator==(uint8_t id, event_type type) { return id == static_cast(type); } + inline bool operator!=(uint8_t id, event_type type) { + return !(id == static_cast(type)); + } /** * Create QUIT event diff --git a/include/settings.hpp.cmake b/include/settings.hpp.cmake index 113f7b5b..8aa2016f 100644 --- a/include/settings.hpp.cmake +++ b/include/settings.hpp.cmake @@ -41,10 +41,14 @@ #cmakedefine XPP_EXTENSION_LIST @XPP_EXTENSION_LIST@ +#if DEBUG #cmakedefine DEBUG_LOGGER -#cmakedefine DEBUG_LOGGER_TRACE +#cmakedefine DEBUG_LOGGER_VERBOSE #cmakedefine DEBUG_HINTS #cmakedefine DEBUG_WHITESPACE +#cmakedefine DEBUG_SHADED +#cmakedefine DEBUG_FONTCONFIG +#endif static const size_t EVENT_SIZE = 64; diff --git a/include/utils/cache.hpp b/include/utils/cache.hpp index 566b916a..b878a5a2 100644 --- a/include/utils/cache.hpp +++ b/include/utils/cache.hpp @@ -13,6 +13,11 @@ class cache { using map_type = std::unordered_map>; using safe_map_type = mutex_wrapper; + bool check(const KeyType& key) { + std::lock_guard guard(m_cache); + return m_cache.find(key) == m_cache.end(); + } + template shared_ptr object(const KeyType& key, MakeArgs&&... make_args) { std::lock_guard guard(m_cache); @@ -27,6 +32,4 @@ class cache { safe_map_type m_cache; }; -namespace cache_util {} - POLYBAR_NS_END diff --git a/include/utils/color.hpp b/include/utils/color.hpp index 72090adf..f39741e2 100644 --- a/include/utils/color.hpp +++ b/include/utils/color.hpp @@ -1,12 +1,15 @@ #pragma once -#include - #include "common.hpp" -#include "utils/string.hpp" +#include "utils/cache.hpp" POLYBAR_NS +static cache g_cache_hex; +static cache g_cache_colors; + +struct rgba; + namespace color_util { template T alpha_channel(const uint32_t value) { @@ -55,21 +58,27 @@ namespace color_util { template string hex(uint32_t color) { - char s[12]; - size_t len = 0; + string hex; - uint8_t a = alpha_channel(color); - uint8_t r = red_channel(color); - uint8_t g = green_channel(color); - uint8_t b = blue_channel(color); + if (!g_cache_hex.check(color)) { + char s[12]; + size_t len = 0; - if (std::is_same::value) { - len = snprintf(s, sizeof(s), "#%02x%02x%02x%02x", a, r, g, b); - } else if (std::is_same::value) { - len = snprintf(s, sizeof(s), "#%02x%02x%02x", r, g, b); + uint8_t a = alpha_channel(color); + uint8_t r = red_channel(color); + uint8_t g = green_channel(color); + uint8_t b = blue_channel(color); + + if (std::is_same::value) { + len = snprintf(s, sizeof(s), "#%02x%02x%02x%02x", a, r, g, b); + } else if (std::is_same::value) { + len = snprintf(s, sizeof(s), "#%02x%02x%02x", r, g, b); + } + + hex = string(s, len); } - return string{s, 0, len}; + return *g_cache_hex.object(color, hex); } inline string parse_hex(string hex) { @@ -107,4 +116,43 @@ namespace color_util { } } +struct rgb { + double r; + double g; + double b; + + // clang-format off + explicit rgb(double r, double g, double b) : r(r), g(g), b(b) {} + explicit rgb(uint32_t color) : rgb( + color_util::red_channel(color_util::premultiply_alpha(color) / 255.0), + color_util::green_channel(color_util::premultiply_alpha(color) / 255.0), + color_util::blue_channel(color_util::premultiply_alpha(color) / 255.0)) {} + // clang-format on +}; + +struct rgba { + double r; + double g; + double b; + double a; + + // clang-format off + explicit rgba(double r, double g, double b, double a) : r(r), g(g), b(b), a(a) {} + explicit rgba(uint32_t color) : rgba( + color_util::red_channel(color) / 255.0, + color_util::green_channel(color) / 255.0, + color_util::blue_channel(color) / 255.0, + color_util::alpha_channel(color) / 255.0) {} + // clang-format on + + operator uint32_t() { + // clang-format off + return static_cast(a * 255) << 24 + | static_cast(r * 255) << 16 + | static_cast(g * 255) << 8 + | static_cast(b * 255); + // clang-format on + } +}; + POLYBAR_NS_END diff --git a/include/utils/math.hpp b/include/utils/math.hpp index c249748d..93b28474 100644 --- a/include/utils/math.hpp +++ b/include/utils/math.hpp @@ -12,7 +12,7 @@ namespace math_util { */ template ValueType min(ValueType one, ValueType two) { - return one < two ? one : two; + return one < two ? one : two; } /** @@ -20,7 +20,7 @@ namespace math_util { */ template ValueType max(ValueType one, ValueType two) { - return one > two ? one : two; + return one > two ? one : two; } /** @@ -87,6 +87,10 @@ namespace math_util { ReturnType nearest_5(double value) { return static_cast(static_cast(value / 5.0 + 0.5) * 5.0); } + + inline int ceil(double value, int step = 1) { + return static_cast((value * 10 + step * 10 - 1) / (step * 10)) * step; + } } POLYBAR_NS_END diff --git a/include/x11/color.hpp b/include/x11/color.hpp deleted file mode 100644 index 40b9264c..00000000 --- a/include/x11/color.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include -#include - -#include "common.hpp" -#include "utils/color.hpp" -#include "utils/concurrency.hpp" - -POLYBAR_NS - -class color { - public: - explicit color(string hex); - - string source() const; - - operator XRenderColor() const; - operator string() const; - - explicit operator uint32_t(); - operator uint32_t() const; - - static const color& parse(string input, const color& fallback); - static const color& parse(string input); - - protected: - uint32_t m_value; - uint32_t m_color; - string m_source; -}; - -extern mutex_wrapper> g_colorstore; - -extern const color& g_colorempty; -extern const color& g_colorblack; -extern const color& g_colorwhite; - -POLYBAR_NS_END diff --git a/include/x11/fonts.hpp b/include/x11/fonts.hpp deleted file mode 100644 index fbf32bee..00000000 --- a/include/x11/fonts.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#include -#include FT_FREETYPE_H -#include FT_OUTLINE_H -#include FT_GLYPH_H - -#include -#include -#include - -#include "common.hpp" -#include "x11/color.hpp" -#include "x11/types.hpp" - -POLYBAR_NS - -using std::map; -using std::unordered_map; - -// fwd -class connection; -class logger; - -struct font_ref { - explicit font_ref() = default; - font_ref(const font_ref& o) = delete; - font_ref& operator=(const font_ref& o) = delete; - XftFont* xft{nullptr}; - xcb_font_t ptr{XCB_NONE}; - int offset_y{0}; - int ascent{0}; - int descent{0}; - int height{0}; - int width{0}; - uint16_t char_max{0}; - uint16_t char_min{0}; - vector width_lut{}; - unordered_map glyph_widths{}; - - static struct _deleter { void operator()(font_ref* font); } deleter; -}; - -class font_manager { - public: - using make_type = unique_ptr; - static make_type make(); - - explicit font_manager(connection& conn, const logger& logger); - ~font_manager(); - - font_manager(const font_manager& o) = delete; - font_manager& operator=(const font_manager& o) = delete; - - void set_visual(Visual* v); - - void cleanup(); - bool load(const string& name, uint8_t fontindex = 0, int8_t offset_y = 0); - void fontindex(uint8_t index); - shared_ptr match_char(const uint16_t chr); - uint8_t glyph_width(const shared_ptr& font, const uint16_t chr); - void drawtext(const shared_ptr& font, xcb_pixmap_t pm, xcb_gcontext_t gc, int16_t x, int16_t y, - const uint16_t* chars, size_t num_chars); - - void allocate_color(uint32_t color); - void allocate_color(XRenderColor color); - - protected: - bool open_xcb_font(const shared_ptr& font, string fontname); - - uint8_t glyph_width_xft(const shared_ptr& font, const uint16_t chr); - uint8_t glyph_width_xcb(const shared_ptr& font, const uint16_t chr); - - bool has_glyph_xft(const shared_ptr& font, const uint16_t chr); - bool has_glyph_xcb(const shared_ptr& font, const uint16_t chr); - - void xcb_poly_text_16(xcb_drawable_t d, xcb_gcontext_t gc, int16_t x, int16_t y, uint8_t len, uint16_t* str); - - private: - connection& m_connection; - const logger& m_logger; - - Display* m_display{nullptr}; - Visual* m_visual{nullptr}; - Colormap m_colormap; - - map> m_fonts{}; - uint8_t m_fontindex{0}; - - XftDraw* m_xftdraw{nullptr}; - XftColor m_xftcolor{}; - bool m_xftcolor_allocated{false}; -}; - -POLYBAR_NS_END diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 455873a0..be1c4a76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,20 +8,21 @@ list(REMOVE_ITEM SOURCES ipc.cpp) # Locate dependencies {{{ set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) -find_package(X11 REQUIRED COMPONENTS Xft Xutil) -find_package(X11_XCB REQUIRED) find_package(PkgConfig) -pkg_check_modules(FONTCONFIG REQUIRED fontconfig) +find_package(Threads REQUIRED) +find_package(X11_XCB REQUIRED) +pkg_check_modules(CAIRO REQUIRED cairo-fc) +# pkg_check_modules(PANGOCAIRO REQUIRED pangocairo) set(APP_LIBRARIES ${APP_LIBRARIES} ${CMAKE_THREAD_LIBS_INIT}) -set(APP_LIBRARIES ${APP_LIBRARIES} ${X11_Xft_LIB}) -#set(APP_LIBRARIES ${APP_LIBRARIES} ${FONTCONFIG_LIBRARIES}) +# set(APP_LIBRARIES ${APP_LIBRARIES} ${PANGOCAIRO_LIBRARIES}) +set(APP_LIBRARIES ${APP_LIBRARIES} ${CAIRO_LIBRARIES}) -set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${FONTCONFIG_INCLUDE_DIRS}) set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/include) set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${PROJECT_SOURCE_DIR}/lib/concurrentqueue/include) set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) +# set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${PANGOCAIRO_INCLUDE_DIRS}) +set(APP_INCLUDE_DIRS ${APP_INCLUDE_DIRS} ${CAIRO_INCLUDE_DIRS}) # xpp library set(XCB_PROTOS xproto) @@ -147,7 +148,6 @@ target_link_libraries(${PROJECT_NAME} Threads::Threads) target_compile_options(${PROJECT_NAME} PUBLIC $<$:$<$:-flto>> - ${X11_Xft_DEFINITIONS} ${X11_XCB_DEFINITIONS} ${XCB_DEFINITIONS}) diff --git a/src/components/bar.cpp b/src/components/bar.cpp index 287664df..290ce556 100644 --- a/src/components/bar.cpp +++ b/src/components/bar.cpp @@ -182,18 +182,32 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const const auto parse_or_throw = [&](string key, string def) { try { - return color::parse(m_conf.get(bs, key, def)); + return color_util::parse(m_conf.get(bs, key, def)); } catch (const exception& err) { throw application_error(sstream() << "Failed to set " << key << " (reason: " << err.what() << ")"); } }; - // Load foreground/background - m_opts.background = parse_or_throw("background", color_util::hex(m_opts.background)); + // Load background + for (auto&& step : m_conf.get_list(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("background", color_util::hex(m_opts.background)); + } + + // Load foreground m_opts.foreground = parse_or_throw("foreground", color_util::hex(m_opts.foreground)); - // Load over-/underline color and size (warn about deprecated params if used) - auto line_color = parse_or_throw("line-color", "#f00"s); + // Load over-/underline + auto line_color = m_conf.get(bs, "line-color", "#f00"s); auto line_size = m_conf.get(bs, "line-size", 0); m_opts.overline.size = m_conf.get(bs, "overline-size", line_size); @@ -202,8 +216,8 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.underline.color = parse_or_throw("underline-color", line_color); // Load border settings + auto border_color = m_conf.get(bs, "border-color", ""s); auto border_size = m_conf.get(bs, "border-size", 0); - auto border_color = m_conf.get(bs, "border-color", "#00000000"s); m_opts.borders.emplace(edge::TOP, border_settings{}); m_opts.borders[edge::TOP].size = m_conf.deprecated(bs, "border-top", "border-top-size", border_size); @@ -253,8 +267,8 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const throw application_error("Resulting bar height is out of bounds (" + to_string(m_opts.size.h) + ")"); } - m_opts.size.w = math_util::cap(m_opts.size.w, 0, m_opts.monitor->w); - m_opts.size.h = math_util::cap(m_opts.size.h, 0, m_opts.monitor->h); + // m_opts.size.w = math_util::cap(m_opts.size.w, 0, m_opts.monitor->w); + // m_opts.size.h = math_util::cap(m_opts.size.h, 0, m_opts.monitor->h); m_opts.center.y = m_opts.size.h; m_opts.center.y -= m_opts.borders[edge::BOTTOM].size; @@ -266,41 +280,12 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const m_opts.center.x /= 2; m_opts.center.x += m_opts.borders[edge::LEFT].size; - m_log.trace("bar: Create renderer"); - auto fonts = m_conf.get_list(m_conf.section(), "font", {}); - m_renderer = renderer::make(m_opts, move(fonts)); + m_log.info("Bar geometry: %ix%i+%i+%i", m_opts.size.w, m_opts.size.h, m_opts.pos.x, m_opts.pos.y); - m_log.trace("bar: Attaching sink to registry"); + m_log.trace("bar: Attach X event sink"); m_connection.attach_sink(this, SINK_PRIORITY_BAR); - m_log.info("Bar geometry: %ix%i+%i+%i", m_opts.size.w, m_opts.size.h, m_opts.pos.x, m_opts.pos.y); - 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); - } - - m_log.info("Bar window: %s", m_connection.id(m_opts.window)); - restack_window(); - - m_log.trace("bar: Reconfigure window"); - reconfigure_struts(); - reconfigure_wm_hints(); - - m_log.trace("bar: Map window"); - m_connection.map_window_checked(m_opts.window); - - // Reconfigure window position after mapping (required by Openbox) - // Required by Openbox - reconfigure_pos(); - - m_log.trace("bar: Drawing empty bar"); - m_renderer->begin(); - m_renderer->fill_background(); - m_renderer->end(); - + m_log.trace("bar: Attach signal receiver"); m_sig.attach(this); } @@ -354,8 +339,6 @@ void bar::parse(string&& data, bool force) { } } - m_renderer->fill_background(); - try { m_parser->parse(settings(), data); } catch (const parser_error& err) { @@ -365,7 +348,7 @@ void bar::parse(string&& data, bool force) { m_renderer->end(); const auto check_dblclicks = [&]() -> bool { - for (auto&& action : m_renderer->get_actions()) { + for (auto&& action : m_renderer->actions()) { if (static_cast(action.button) >= static_cast(mousebtn::DOUBLE_LEFT)) { return true; } @@ -510,7 +493,7 @@ void bar::handle(const evt::destroy_notify& evt) { * _NET_WM_WINDOW_OPACITY atom value */ void bar::handle(const evt::enter_notify&) { -#if DEBUG +#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; @@ -534,7 +517,7 @@ void bar::handle(const evt::enter_notify&) { * _NET_WM_WINDOW_OPACITY atom value */ void bar::handle(const evt::leave_notify&) { -#if DEBUG +#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; @@ -571,7 +554,7 @@ void bar::handle(const evt::button_press& evt) { m_buttonpress_pos = evt->event_x; const auto deferred_fn = [&](size_t) { - for (auto&& action : m_renderer->get_actions()) { + for (auto&& action : m_renderer->actions()) { 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}}); @@ -626,7 +609,7 @@ void bar::handle(const evt::expose& evt) { } m_log.trace("bar: Received expose event"); - m_renderer->flush(false); + m_renderer->flush(); } } @@ -643,7 +626,7 @@ void bar::handle(const evt::expose& evt) { * pseudo-transparent background when it changes */ void bar::handle(const evt::property_notify& evt) { -#ifdef DEBUG_LOGGER_TRACE +#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 @@ -654,9 +637,42 @@ void bar::handle(const evt::property_notify& evt) { } 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); + } + + m_log.info("Bar window: %s", m_connection.id(m_opts.window)); + restack_window(); + + m_log.trace("bar: Reconfigure window"); + reconfigure_struts(); + reconfigure_wm_hints(); + + m_log.trace("bar: Map window"); + m_connection.map_window_checked(m_opts.window); + + // Reconfigure window position after mapping (required by Openbox) + // Required by Openbox + reconfigure_pos(); + + m_log.trace("bar: Draw empty bar"); + m_renderer->begin(); + 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(m_opts)); + broadcast_visibility(); + return true; } @@ -677,7 +693,7 @@ bool bar::on(const signals::ui::unshade_window&) { m_sig.emit(signals::ui::tick{}); } if (!remaining) { - m_renderer->flush(false); + m_renderer->flush(); } if (m_opts.dimmed) { m_opts.dimmed = false; @@ -716,7 +732,7 @@ bool bar::on(const signals::ui::shade_window&) { m_sig.emit(signals::ui::tick{}); } if (!remaining) { - m_renderer->flush(false); + m_renderer->flush(); } if (!m_opts.dimmed) { m_opts.dimmed = true; diff --git a/src/components/config.cpp b/src/components/config.cpp index b8ed8a12..0274fa7d 100644 --- a/src/components/config.cpp +++ b/src/components/config.cpp @@ -2,13 +2,12 @@ #include #include "components/config.hpp" +#include "utils/color.hpp" #include "utils/env.hpp" #include "utils/factory.hpp" #include "utils/file.hpp" #include "utils/math.hpp" #include "utils/string.hpp" -#include "x11/color.hpp" -#include "x11/xresources.hpp" POLYBAR_NS @@ -276,8 +275,15 @@ chrono::duration config::convert(string&& value) const { } template <> -color config::convert(string&& value) const { - return color{forward(value)}; +rgba config::convert(string&& value) const { + auto color = color_util::parse(value, 0); + // clang-format off + return rgba{ + color_util::red_channel(color) / 255.0, + color_util::green_channel(color) / 255.0, + color_util::blue_channel(color) / 255.0, + color_util::alpha_channel(color) / 255.0}; + // clang-format on } POLYBAR_NS_END diff --git a/src/components/controller.cpp b/src/components/controller.cpp index 08907273..1342f7b9 100644 --- a/src/components/controller.cpp +++ b/src/components/controller.cpp @@ -148,11 +148,8 @@ controller::~controller() { */ bool controller::run(bool writeback) { m_log.info("Starting application"); - assert(!m_connection.connection_has_error()); - m_writeback = writeback; - m_sig.attach(this); size_t started_modules{0}; @@ -183,10 +180,7 @@ bool controller::run(bool writeback) { throw application_error("No modules started"); } - m_sig.emit(signals::eventqueue::start{}); - m_connection.flush(); - m_event_thread = thread(&controller::process_eventqueue, this); read_events(); @@ -205,6 +199,9 @@ bool controller::run(bool writeback) { * Enqueue event */ bool controller::enqueue(event&& evt) { + if (!m_process_events && evt.type != event_type::QUIT) { + return false; + } if (!m_queue.enqueue(forward(evt))) { m_log.warn("Failed to enqueue event"); return false; @@ -330,8 +327,7 @@ void controller::read_events() { */ void controller::process_eventqueue() { m_log.info("Eventqueue worker (thread-id=%lu)", this_thread::get_id()); - - enqueue(make_update_evt(true)); + m_sig.emit(signals::eventqueue::start{}); while (!g_terminate) { event evt{}; @@ -556,6 +552,16 @@ bool controller::on(const signals::eventqueue::check_state&) { return true; } +/** + * Process ui ready event + */ +bool controller::on(const signals::ui::ready&) { + m_process_events = true; + enqueue(make_update_evt(true)); + // let the event bubble + return false; +} + /** * Process ui button press event */ diff --git a/src/components/logger.cpp b/src/components/logger.cpp index 01da660a..bb601a4f 100644 --- a/src/components/logger.cpp +++ b/src/components/logger.cpp @@ -2,6 +2,7 @@ #include "components/logger.hpp" #include "errors.hpp" +#include "settings.hpp" #include "utils/concurrency.hpp" #include "utils/factory.hpp" #include "utils/string.hpp" @@ -60,9 +61,9 @@ logger::logger(loglevel level) : m_level(level) { * Set output verbosity */ void logger::verbosity(loglevel&& level) { -#ifndef DEBUG +#ifndef DEBUG_LOGGER if (level == loglevel::TRACE) { - throw application_error("Trace logging is only enabled for debug builds..."); + throw application_error("Trace logging is not enabled..."); } #endif m_level = forward(level); diff --git a/src/components/parser.cpp b/src/components/parser.cpp index d78b8301..4e855085 100644 --- a/src/components/parser.cpp +++ b/src/components/parser.cpp @@ -173,60 +173,8 @@ size_t parser::text(string&& data) { } #endif - const uint8_t* utf{reinterpret_cast(&data[0])}; - - // clang-format off - if (utf[0] < 0x80) { - size_t pos{0}; - - // grab consecutive ascii chars - while (utf[pos] && utf[pos] < 0x80) { - packet pkt{}; - size_t limit{memory_util::countof(pkt.data)}; - size_t len{0}; - - while (len + 1 < limit && utf[pos] && utf[pos] < 0x80) { - pkt.data[len++] = utf[pos++]; - } - - if (!len) { - break; - } - - pkt.length = len; - m_sig.emit(write_text_string{move(pkt)}); - } - - if (pos > 0) { - return pos; - } - - } else if ((utf[0] & 0xe0) == 0xc0) { // 2 byte utf-8 sequence - m_sig.emit(write_text_unicode{static_cast(((utf[0] & 0x1f) << 6) | (utf[1] & 0x3f))}); - return 2; - } else if ((utf[0] & 0xf0) == 0xe0) { // 3 byte utf-8 sequence - m_sig.emit(write_text_unicode{static_cast(((utf[0] & 0x0f) << 12) | ((utf[1] & 0x3f) << 6) | (utf[2] & 0x3f))}); - return 3; - } else if ((utf[0] & 0xf8) == 0xf0) { // 4 byte utf-8 sequence - // m_sig.emit(write_text_unicode{((utf[0] & 0x07) << 18) | ((utf[1] & 0x3f) << 12) | ((utf[2] & 0x3f) << 6) | (utf[3] & 0x3f)}); - m_sig.emit(write_text_unicode{static_cast(0xfffd)}); - return 4; - } else if ((utf[0] & 0xfc) == 0xf8) { // 5 byte utf-8 sequence - // m_sig.emit(write_text_unicode{((utf[0] & 0x03) << 24) | ((utf[1] & 0x3f) << 18) | ((utf[2] & 0x3f) << 12) | ((utf[3] & 0x3f) << 6) | (utf[4] & 0x3f)}); - m_sig.emit(write_text_unicode{static_cast(0xfffd)}); - return 5; - } else if ((utf[0] & 0xfe) == 0xfc) { // 6 byte utf-8 sequence - // m_sig.emit(write_text_unicode{((utf[0] & 0x01) << 30) | ((utf[1] & 0x3f) << 24) | ((utf[2] & 0x3f) << 18) | ((utf[3] & 0x3f) << 12) | ((utf[4] & 0x3f) << 6) | (utf[5] & 0x3f)}); - m_sig.emit(write_text_unicode{static_cast(0xfffd)}); - return 6; - } - // clang-format on - - if (utf[0] < 0x80) { - m_sig.emit(write_text_ascii{utf[0]}); - } - - return 1; + m_sig.emit(signals::parser::text{forward(data)}); + return data.size(); } /** diff --git a/src/components/renderer.cpp b/src/components/renderer.cpp index 4c89fd0e..8359f2dc 100644 --- a/src/components/renderer.cpp +++ b/src/components/renderer.cpp @@ -1,11 +1,19 @@ #include "components/renderer.hpp" +#include "cairo/context.hpp" +#include "cairo/font.hpp" +#include "cairo/surface.hpp" +#include "cairo/types.hpp" +#include "cairo/utils.hpp" +#include "components/config.hpp" #include "components/logger.hpp" #include "errors.hpp" #include "events/signal.hpp" -#include "events/signal_emitter.hpp" #include "events/signal_receiver.hpp" +#include "utils/color.hpp" #include "utils/factory.hpp" #include "utils/file.hpp" +#include "utils/math.hpp" +#include "utils/string.hpp" #include "x11/atoms.hpp" #include "x11/connection.hpp" #include "x11/draw.hpp" @@ -18,53 +26,50 @@ POLYBAR_NS /** * Create instance */ -renderer::make_type renderer::make(const bar_settings& bar, vector&& fonts) { +renderer::make_type renderer::make(const bar_settings& bar) { // clang-format off return factory_util::unique( connection::make(), signal_emitter::make(), + config::make(), logger::make(), - font_manager::make(), - forward(bar), - forward(fonts)); + forward(bar)); // clang-format on } /** * Construct renderer instance */ -renderer::renderer(connection& conn, signal_emitter& emitter, const logger& logger, - unique_ptr font_manager, const bar_settings& bar, const vector& fonts) +renderer::renderer( + connection& conn, signal_emitter& sig, const config& conf, const logger& logger, const bar_settings& bar) : m_connection(conn) - , m_sig(emitter) + , m_sig(sig) + , m_conf(conf) , m_log(logger) - , m_fontmanager(forward(font_manager)) , m_bar(forward(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(), 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, aborting..."); - throw application_error("No matching 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 visual found..."); + throw application_error("No matching TrueColor"); } - - m_depth = 24; - - m_fontmanager->set_visual(m_connection.visual(m_depth)); } 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_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"); { @@ -88,73 +93,61 @@ renderer::renderer(connection& conn, signal_emitter& emitter, const logger& logg } m_log.trace("renderer: Allocate window pixmap"); - m_pixmap = m_connection.generate_id(); - m_connection.create_pixmap(m_depth, m_pixmap, m_window, m_rect.width, m_rect.height); - - m_log.trace("renderer: Allocate graphic contexts"); { - // clang-format off - vector colors { - m_bar.background, - m_bar.foreground, - m_bar.overline.color, - m_bar.underline.color, - m_bar.borders.at(edge::TOP).color, - m_bar.borders.at(edge::BOTTOM).color, - m_bar.borders.at(edge::LEFT).color, - m_bar.borders.at(edge::RIGHT).color, - }; - // clang-format on + m_pixmap = m_connection.generate_id(); + m_connection.create_pixmap(m_depth, m_pixmap, m_window, m_bar.size.w, m_bar.size.h); + } - for (int i = 0; i < 8; i++) { - uint32_t mask{0}; - uint32_t value_list[32]{0}; + m_log.trace("renderer: Allocate graphic context"); + { + uint32_t mask{0}; + uint32_t value_list[32]{0}; + xcb_params_gc_t params{}; + XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, m_bar.foreground); + XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0); + connection::pack_values(mask, ¶ms, value_list); + m_gcontext = m_connection.generate_id(); + m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list); + } - xcb_params_gc_t params{}; - XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, colors[i]); - XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0); - connection::pack_values(mask, ¶ms, value_list); - - m_colors.emplace(gc(i), colors[i]); - m_gcontexts.emplace(gc(i), m_connection.generate_id()); - m_connection.create_gc(m_gcontexts.at(gc(i)), m_pixmap, mask, value_list); - } + m_log.trace("renderer: Allocate cairo components"); + { + m_surface = make_unique(m_connection, m_pixmap, m_visual, m_bar.size.w, m_bar.size.h); + m_context = make_unique(*m_surface.get(), m_log); } m_log.trace("renderer: Load fonts"); { - auto fonts_loaded = false; - auto fontindex = 0; - + auto fonts = m_conf.get_list(m_conf.section(), "font", {}); if (fonts.empty()) { m_log.warn("No fonts specified, using fallback font \"fixed\""); + fonts.emplace_back("fixed"); } + auto fonts_loaded = false; for (const auto& f : fonts) { - fontindex++; vector fd{string_util::split(f, ';')}; - string pattern{fd[0]}; - int offset{0}; - - if (fd.size() > 1) { - offset = std::stoi(fd[1], nullptr, 10); - } - - if (m_fontmanager->load(pattern, fontindex, offset)) { - fonts_loaded = true; - } else { - m_log.warn("Unable to load font '%s'", fd[0]); - } + auto font = cairo::make_font(*m_context, string(fd[0]), fd.size() > 1 ? std::atoi(fd[1].c_str()) : 0); + m_log.info("Loaded font \"%s\" (name=%s, file=%s)", fd[0], font->name(), font->file()); + *m_context << move(font); + fonts_loaded = true; } - if (!fonts_loaded && !fonts.empty()) { - m_log.warn("Unable to load fonts, using fallback font \"fixed\""); - } - if (!fonts_loaded && !m_fontmanager->load("fixed")) { + if (!fonts_loaded) { throw application_error("Unable to load fonts"); } - m_fontmanager->allocate_color(m_bar.foreground); } + + m_compositing_background = + cairo::utils::str2operator(m_conf.get("settings", "compositing-background", ""s), CAIRO_OPERATOR_SOURCE); + m_compositing_foreground = + cairo::utils::str2operator(m_conf.get("settings", "compositing-foreground", ""s), CAIRO_OPERATOR_OVER); + m_compositing_overline = + cairo::utils::str2operator(m_conf.get("settings", "compositing-overline", ""s), CAIRO_OPERATOR_OVER); + m_compositing_underline = + cairo::utils::str2operator(m_conf.get("settings", "compositing-underline", ""s), CAIRO_OPERATOR_OVER); + m_compositing_borders = + cairo::utils::str2operator(m_conf.get("settings", "compositing-border", ""s), CAIRO_OPERATOR_SOURCE); } /** @@ -162,10 +155,6 @@ renderer::renderer(connection& conn, signal_emitter& emitter, const logger& logg */ renderer::~renderer() { m_sig.detach(this); - - if (m_window != XCB_NONE) { - m_connection.destroy_window(m_window); - } } /** @@ -175,17 +164,51 @@ xcb_window_t renderer::window() const { return m_window; } +/** + * Get completed action blocks + */ +const vector renderer::actions() const { + return m_actions; +} + /** * Begin render routine */ void renderer::begin() { m_log.trace_x("renderer: begin"); - m_rect = m_bar.inner_area(); - m_alignment = alignment::NONE; - m_currentx = 0; - m_attributes = 0; + // Reset state m_actions.clear(); + m_attributes.reset(); + m_alignment = alignment::NONE; + m_rect = m_bar.inner_area(); + m_x = 0.0; + + // Reset colors + m_color_background = 0; + m_color_foreground = m_bar.foreground; + m_color_underline = m_bar.underline.color; + m_color_overline = m_bar.overline.color; + + // Clear canvas + m_context->save(); + *m_context << CAIRO_OPERATOR_SOURCE; + *m_context << rgba{0.0, 0.0, 0.0, 0.0}; + m_context->paint(); + m_context->restore(); + + m_context->save(); + + fill_background(); + fill_borders(); + + // clang-format off + m_context->clip(cairo::rect{ + static_cast(m_rect.x), + static_cast(m_rect.y), + static_cast(m_rect.width), + static_cast(m_rect.height)}); + // clang-format on } /** @@ -194,101 +217,39 @@ void renderer::begin() { void renderer::end() { m_log.trace_x("renderer: end"); - m_fontmanager->cleanup(); + highlight_clickable_areas(); -#ifdef DEBUG_HINTS - debug_hints(); -#endif + m_context->restore(); + m_surface->flush(); - flush(false); + flush(); } /** * Flush pixmap contents onto the target window */ -void renderer::flush(bool clear) { - const xcb_rectangle_t& r = m_rect; +void renderer::flush() { + m_log.trace_x("renderer: flush"); - xcb_rectangle_t top{0, 0, 0U, 0U}; - top.x += m_bar.borders.at(edge::LEFT).size; - top.width += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size; - top.height += m_bar.borders.at(edge::TOP).size; - - xcb_rectangle_t bottom{0, 0, 0U, 0U}; - bottom.x += m_bar.borders.at(edge::LEFT).size; - bottom.y += m_bar.size.h - m_bar.borders.at(edge::BOTTOM).size; - bottom.width += m_bar.size.w - m_bar.borders.at(edge::LEFT).size - m_bar.borders.at(edge::RIGHT).size; - bottom.height += m_bar.borders.at(edge::BOTTOM).size; - - xcb_rectangle_t left{0, 0, 0U, 0U}; - left.width += m_bar.borders.at(edge::LEFT).size; - left.height += m_bar.size.h; - - xcb_rectangle_t right{0, 0, 0U, 0U}; - right.x += m_bar.size.w - m_bar.borders.at(edge::RIGHT).size; - right.width += m_bar.borders.at(edge::RIGHT).size; - right.height += m_bar.size.h; - - // Calculate the area that was reserved so that we - // can clear any previous content drawn at the same location - xcb_rectangle_t clear_area{r.x, r.y, r.width, r.height}; - - if (m_cleararea.size && m_cleararea.side == edge::RIGHT) { - clear_area.x += r.width; - clear_area.y = top.height; - clear_area.width = m_cleararea.size; - } else if (m_cleararea.size && m_cleararea.side == edge::LEFT) { - clear_area.x = left.width; - clear_area.y = top.height; - clear_area.width = m_cleararea.size; - } else if (m_cleararea.size && m_cleararea.side == edge::TOP) { - clear_area.height = m_cleararea.size; - } else if (m_cleararea.size && m_cleararea.side == edge::BOTTOM) { - clear_area.y += r.height - m_cleararea.size; - clear_area.height = m_cleararea.size; - } - - if (clear_area != m_cleared && clear_area != 0) { - m_log.trace("renderer: clearing area %dx%d+%d+%d", clear_area.width, clear_area.height, clear_area.x, clear_area.y); - m_connection.clear_area(0, m_window, clear_area.x, clear_area.y, clear_area.width, clear_area.height); - m_cleared = clear_area; - } - -#if DEBUG +#ifdef DEBUG_SHADED if (m_bar.shaded && m_bar.origin == edge::TOP) { - m_log.trace("renderer: copy pixmap (shaded=1, clear=%i, geom=%dx%d+%d+%d)", clear, r.width, r.height, r.x, r.y); + 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 = r.height - m_bar.shade_size.h - r.y - geom->height; - auto x2 = r.x; - auto y2 = r.y; - auto w = r.width; - auto h = r.height - m_bar.shade_size.h + geom->height; - m_connection.copy_area(m_pixmap, m_window, m_gcontexts.at(gc::FG), x1, y1, x2, y2, w, h); + 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 - m_log.trace("renderer: copy pixmap (clear=%i, geom=%dx%d+%d+%d)", clear, r.width, r.height, r.x, r.y); - m_connection.copy_area(m_pixmap, m_window, m_gcontexts.at(gc::FG), 0, 0, r.x, r.y, r.width, r.height); - - m_log.trace_x("renderer: draw top border (%lupx, %08x)", top.height, m_bar.borders.at(edge::TOP).color); - draw_util::fill(m_connection, m_window, m_gcontexts.at(gc::BT), top); - - m_log.trace_x("renderer: draw bottom border (%lupx, %08x)", bottom.height, m_bar.borders.at(edge::BOTTOM).color); - draw_util::fill(m_connection, m_window, m_gcontexts.at(gc::BB), bottom); - - m_log.trace_x("renderer: draw left border (%lupx, %08x)", left.width, m_bar.borders.at(edge::LEFT).color); - draw_util::fill(m_connection, m_window, m_gcontexts.at(gc::BL), left); - - m_log.trace_x("renderer: draw right border (%lupx, %08x)", right.width, m_bar.borders.at(edge::RIGHT).color); - draw_util::fill(m_connection, m_window, m_gcontexts.at(gc::BR), right); - - if (clear) { - m_connection.clear_area(false, m_pixmap, 0, 0, r.width, r.height); - } - + m_log.trace_x("renderer: copy pixmap (geom=%dx%d+%d+%d)", m_rect.width, m_rect.height, m_rect.x, m_rect.y); + 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(); } @@ -327,391 +288,296 @@ void renderer::reserve_space(edge side, uint16_t w) { } } -/** - * Check if given attribute is enabled - */ -bool renderer::check_attribute(const attribute attr) { - return (m_attributes >> static_cast(attr)) & 1U; -} - /** * Fill background color */ void renderer::fill_background() { - m_log.trace_x("renderer: fill_background"); - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BG), 0, 0, m_rect.width, m_rect.height); + m_context->save(); + *m_context << m_compositing_background; + + 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(int16_t x, uint16_t w) { - if (!check_attribute(attribute::OVERLINE)) { - return m_log.trace_x("renderer: not filling overline (flag unset)"); - } else if (!m_bar.overline.size) { - return m_log.trace_x("renderer: not filling overline (size=0)"); +void renderer::fill_overline(double x, double w) { + if (m_bar.overline.size && m_attributes.test(static_cast(attribute::OVERLINE))) { + m_log.trace_x("renderer: overline(x=%i, w=%i)", x, w); + m_context->save(); + *m_context << m_compositing_overline; + *m_context << m_color_overline; + *m_context << cairo::rect{x, static_cast(m_rect.y), w, static_cast(m_bar.overline.size)}; + m_context->fill(); + m_context->restore(); } - m_log.trace_x("renderer: fill_overline(%i, #%08x)", m_bar.overline.size, m_colors[gc::OL]); - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::OL), x, 0, w, m_bar.overline.size); } /** * Fill underline color */ -void renderer::fill_underline(int16_t x, uint16_t w) { - if (!check_attribute(attribute::UNDERLINE)) { - return m_log.trace_x("renderer: not filling underline (flag unset)"); - } else if (!m_bar.underline.size) { - return m_log.trace_x("renderer: not filling underline (size=0)"); - } - m_log.trace_x("renderer: fill_underline(%i, #%08x)", m_bar.underline.size, m_colors[gc::UL]); - int16_t y{static_cast(m_rect.height - m_bar.underline.size)}; - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::UL), x, y, w, m_bar.underline.size); -} - -/** - * @see shift_content - */ -void renderer::fill_shift(const int16_t px) { - shift_content(px); -} - -/** - * Draw consecutive character glyphs - */ -void renderer::draw_textstring(const uint16_t* text, size_t len) { - m_log.trace_x("renderer: draw_textstring(\"%s\")", text); - - for (size_t n = 0; n < len; n++) { - vector chars{text[n]}; - shared_ptr font{m_fontmanager->match_char(chars[0])}; - uint8_t width{static_cast(m_fontmanager->glyph_width(font, chars[0]) * chars.size())}; - - if (!font) { - m_log.warn("Could not find glyph for %i", chars[0]); - continue; - } else if (!width) { - m_log.warn("Could not determine glyph width for %i", chars[0]); - continue; - } - - while (n + 1 < len && text[n + 1] == chars[0]) { - chars.emplace_back(text[n++]); - } - - width *= chars.size(); - auto x = shift_content(width); - auto y = m_rect.height / 2 + font->height / 2 - font->descent + font->offset_y; - - if (font->ptr != XCB_NONE && m_gcfont != font->ptr) { - const uint32_t v[1]{font->ptr}; - m_connection.change_gc(m_gcontexts.at(gc::FG), XCB_GC_FONT, v); - m_gcfont = font->ptr; - } - - m_fontmanager->drawtext(font, m_pixmap, m_gcontexts.at(gc::FG), x, y, chars.data(), chars.size()); - - fill_underline(x, width); - fill_overline(x, width); +void renderer::fill_underline(double x, double w) { + if (m_bar.underline.size && m_attributes.test(static_cast(attribute::UNDERLINE))) { + m_log.trace_x("renderer: underline(x=%i, w=%i)", x, w); + m_context->save(); + *m_context << m_compositing_underline; + *m_context << m_color_underline; + *m_context << cairo::rect{x, static_cast(m_rect.y + m_rect.height - m_bar.underline.size), w, + static_cast(m_bar.underline.size)}; + m_context->fill(); + m_context->restore(); } } /** - * Get completed action blocks + * Fill border colors */ -const vector renderer::get_actions() { - return m_actions; +void renderer::fill_borders() { + m_context->save(); + *m_context << m_compositing_borders; + + 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::TOP).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(); + + 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(); + + 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(); + + 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(); } /** - * Shift pixmap content by given value + * Draw text contents */ -int16_t renderer::shift_content(int16_t x, const int16_t shift_x) { - if (x > m_rect.width) { - return m_rect.width; - } else if (x < 0) { - return 0; +void renderer::draw_text(const string& contents) { + m_log.trace_x("renderer: text(%s)", contents.c_str()); + + cairo_text_extents_t extents; + cairo_text_extents(*m_context, contents.c_str(), &extents); + + if (!extents.width) { + return; } - m_log.trace_x("renderer: shift_content(%i)", shift_x); + cairo::abspos origin{static_cast(m_rect.x), static_cast(m_rect.y)}; - int16_t base_x{0}; - double delta{0.0}; - - switch (m_alignment) { - case alignment::NONE: - break; - case alignment::LEFT: - break; - case alignment::CENTER: - base_x = static_cast(m_rect.width / 2); - m_connection.copy_area(m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), base_x - x / 2, 0, base_x - (x + shift_x) / 2, - 0, x, m_rect.height); - x = base_x - (x + shift_x) / 2 + x; - delta = static_cast(shift_x) / 2; - break; - case alignment::RIGHT: - base_x = static_cast(m_rect.width - x); - m_connection.copy_area( - m_pixmap, m_pixmap, m_gcontexts.at(gc::FG), base_x, 0, base_x - shift_x, 0, x, m_rect.height); - x = m_rect.width - shift_x; - delta = static_cast(shift_x); - break; + if (m_alignment == alignment::CENTER) { + origin.x += m_rect.width / 2.0 - extents.width / 2.0; + adjust_clickable_areas(extents.width / 2.0); + } else if (m_alignment == alignment::RIGHT) { + origin.x += m_rect.width - extents.width; + adjust_clickable_areas(extents.width); + } else { + origin.x += m_x; } - draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BG), x, 0, m_rect.width - x, m_rect.height); + // if (m_color_background && m_color_background != m_bar.background) { + // m_context->save(); + // *m_context << m_color_background; + // *m_context << m_compositing_background; + // *m_context << cairo::rect{origin.x, origin.y, extents.width, static_cast(m_rect.height)}; + // m_context->fill(); + // m_context->restore(); + // } - // Translate pos of clickable areas - if (m_alignment != alignment::LEFT) { - for (auto&& action : m_actions) { - if (action.active || action.align != m_alignment) { - continue; - } + origin.y += m_rect.height / 2.0; + + m_context->save(); + *m_context << origin; + *m_context << m_compositing_foreground; + *m_context << m_color_foreground; + *m_context << cairo::textblock{contents, m_fontindex}; + m_context->position(&m_x, &m_y); + m_context->restore(); + + // if (m_alignment == alignment::CENTER) { + // m_x += extents.width / 2.0; + // } else { + // m_x += extents.width; + // } + + // fill_underline(origin.x, m_x - origin.x); + // fill_overline(origin.x, m_x - origin.x); +} + +/** + * Move clickable areas position by given delta + */ +void renderer::adjust_clickable_areas(double delta) { + for (auto&& action : m_actions) { + if (!action.active && action.align == m_alignment) { action.start_x -= delta; action.end_x -= delta; } } - - m_currentx += shift_x; - - return x; } -/** - * @see shift_content - */ -int16_t renderer::shift_content(const int16_t shift_x) { - return shift_content(m_currentx, shift_x); -} - -#ifdef DEBUG_HINTS /** * Draw boxes at the location of each created action block */ -void renderer::debug_hints() { - uint16_t border_width{1}; - map hint_num{{ - // clang-format off - {alignment::LEFT, 0}, - {alignment::CENTER, 0}, - {alignment::RIGHT, 0}, - // clang-format on - }}; - - m_debughints.clear(); - +void renderer::highlight_clickable_areas() { +#ifdef DEBUG_HINTS + map hint_num{}; for (auto&& action : m_actions) { - if (action.active) { - continue; + if (!action.active) { + uint8_t n = hint_num.find(action.align)->second++; + double x = action.start_x + n * DEBUG_HINTS_OFFSET_X; + double y = m_bar.pos.y + m_rect.y + n * DEBUG_HINTS_OFFSET_Y; + double w = action.width(); + double h = m_rect.height; + + m_context->save(); + *m_context << CAIRO_OPERATOR_OVERLAY << (n % 2 ? 0x55FF0000 : 0x5500FF00); + *m_context << cairo::rect{x, y, w, h}; + m_context->fill(); + m_context->restore(); } - - xcb_window_t hintwin{m_connection.generate_id()}; - m_debughints.emplace_back(hintwin); - - uint8_t num{static_cast(hint_num.find(action.align)->second++)}; - - // clang-format off - winspec(m_connection, hintwin) - << cw_size(action.width() - border_width * 2, m_rect.height - border_width * 2) - << cw_pos(action.start_x + num * DEBUG_HINTS_OFFSET_X, m_bar.pos.y + m_rect.y + num * DEBUG_HINTS_OFFSET_Y) - << cw_border(border_width) - << cw_depth(m_depth) - << cw_visual(m_visual->visual_id) - << cw_params_colormap(m_colormap) - << cw_params_back_pixel(0) - << cw_params_border_pixel(num % 2 ? 0xFFFF0000 : 0xFF00FF00) - << cw_params_override_redirect(true) - << cw_flush() - ; - // clang-format on - - const uint32_t shadow{0}; - m_connection.change_property(XCB_PROP_MODE_REPLACE, hintwin, _COMPTON_SHADOW, XCB_ATOM_CARDINAL, 32, 1, &shadow); - m_connection.map_window(hintwin); } -} #endif +} -bool renderer::on(const change_background& evt) { +bool renderer::on(const signals::parser::change_background& evt) { const uint32_t color{evt.cast()}; - - if (m_colors[gc::BG] == color) { - m_log.trace_x("renderer: ignoring unchanged background color(#%08x)", color); - } else { - m_log.trace_x("renderer: set_background(#%08x)", color); - m_connection.change_gc(m_gcontexts.at(gc::BG), XCB_GC_FOREGROUND, &color); - m_colors[gc::BG] = color; - shift_content(0); + if (color != m_color_background) { + m_color_background = color; } - return true; } -bool renderer::on(const change_foreground& evt) { +bool renderer::on(const signals::parser::change_foreground& evt) { const uint32_t color{evt.cast()}; - - if (m_colors[gc::FG] == color) { - m_log.trace_x("renderer: ignoring unchanged foreground color(#%08x)", color); - } else { - m_log.trace_x("renderer: set_foreground(#%08x)", color); - m_connection.change_gc(m_gcontexts.at(gc::FG), XCB_GC_FOREGROUND, &color); - m_fontmanager->allocate_color(color); - m_colors[gc::FG] = color; + if (color != m_color_foreground) { + m_color_foreground = color; } - return true; } -bool renderer::on(const change_underline& evt) { +bool renderer::on(const signals::parser::change_underline& evt) { const uint32_t color{evt.cast()}; - - if (m_colors[gc::UL] == color) { - m_log.trace_x("renderer: ignoring unchanged underline color(#%08x)", color); - } else { - m_log.trace_x("renderer: set_underline(#%08x)", color); - m_connection.change_gc(m_gcontexts.at(gc::UL), XCB_GC_FOREGROUND, &color); - m_colors[gc::UL] = color; + if (color != m_color_underline) { + m_color_underline = color; } - return true; } -bool renderer::on(const change_overline& evt) { +bool renderer::on(const signals::parser::change_overline& evt) { const uint32_t color{evt.cast()}; - - if (m_colors[gc::OL] == color) { - m_log.trace_x("renderer: ignoring unchanged overline color(#%08x)", color); - } else { - m_log.trace_x("renderer: set_overline(#%08x)", color); - m_connection.change_gc(m_gcontexts.at(gc::OL), XCB_GC_FOREGROUND, &color); - m_colors[gc::OL] = color; + if (color != m_color_overline) { + m_color_overline = color; } - return true; } -bool renderer::on(const change_font& evt) { - const uint8_t font{evt.cast()}; - - if (m_fontindex == font) { - m_log.trace_x("renderer: ignoring unchanged font index(%i)", static_cast(font)); - } else { - m_log.trace_x("renderer: fontindex(%i)", static_cast(font)); - m_fontmanager->fontindex(font); - m_fontindex = font; - } - +bool renderer::on(const signals::parser::change_font& evt) { + m_fontindex = evt.cast(); return true; } -bool renderer::on(const change_alignment& evt) { +bool renderer::on(const signals::parser::change_alignment& evt) { auto align = static_cast(evt.cast()); - - if (align == m_alignment) { - m_log.trace_x("renderer: ignoring unchanged alignment(%i)", static_cast(align)); - } else { - m_log.trace_x("renderer: set_alignment(%i)", static_cast(align)); + if (align != m_alignment) { m_alignment = align; - m_currentx = 0; + m_x = 0.0; } - return true; } -bool renderer::on(const offset_pixel& evt) { - shift_content(evt.cast()); +bool renderer::on(const signals::parser::offset_pixel& evt) { + m_x += evt.cast(); return true; } -bool renderer::on(const attribute_set& evt) { - m_log.trace_x("renderer: attribute_set(%i, %i)", static_cast(evt.cast()), true); - m_attributes |= 1U << static_cast(evt.cast()); +bool renderer::on(const signals::parser::attribute_set& evt) { + m_attributes.set(static_cast(evt.cast()), true); return true; } -bool renderer::on(const attribute_unset& evt) { - m_log.trace_x("renderer: attribute_unset(%i, %i)", static_cast(evt.cast()), true); - m_attributes &= ~(1U << static_cast(evt.cast())); +bool renderer::on(const signals::parser::attribute_unset& evt) { + m_attributes.set(static_cast(evt.cast()), false); return true; } -bool renderer::on(const attribute_toggle& evt) { - m_log.trace_x("renderer: attribute_toggle(%i)", static_cast(evt.cast())); - m_attributes ^= 1U << static_cast(evt.cast()); +bool renderer::on(const signals::parser::attribute_toggle& evt) { + m_attributes.flip(static_cast(evt.cast())); return true; } -bool renderer::on(const action_begin& evt) { - auto a = static_cast(evt.cast()); - action_block action{}; - action.button = a.button; - action.align = m_alignment; - action.start_x = m_currentx; - action.command = string_util::replace_all(a.command, ":", "\\:"); - action.active = true; - - if (action.button == mousebtn::NONE) { - action.button = mousebtn::LEFT; - } - - m_log.trace_x("renderer: action_begin(%i, %s)", static_cast(a.button), a.command.c_str()); - m_actions.emplace_back(action); - +bool renderer::on(const signals::parser::action_begin& evt) { + (void) evt; + // auto a = evt.cast(); + // action_block action{}; + // action.button = a.button == mousebtn::NONE ? mousebtn::LEFT : a.button; + // action.align = m_alignment; + // action.start_x = m_x; + // action.command = string_util::replace_all(a.command, ":", "\\:"); + // action.active = true; + // m_actions.emplace_back(action); return true; } -bool renderer::on(const action_end& evt) { - auto btn = static_cast(evt.cast()); - int16_t clickable_width{0}; - - for (auto action = m_actions.rbegin(); action != m_actions.rend(); action++) { - if (!action->active || action->align != m_alignment || action->button != btn) { - continue; - } - - action->active = false; - - switch (action->align) { - case alignment::NONE: - break; - case alignment::LEFT: - action->end_x = m_currentx; - break; - case alignment::CENTER: - clickable_width = m_currentx - action->start_x; - action->start_x = m_rect.width / 2 - clickable_width / 2 + action->start_x / 2; - action->end_x = action->start_x + clickable_width; - break; - case alignment::RIGHT: - action->start_x = m_rect.width - m_currentx + action->start_x; - action->end_x = m_rect.width; - break; - } - - action->start_x += m_rect.x; - action->end_x += m_rect.x; - - m_log.trace_x("renderer: action_end(%i, %s, %i)", static_cast(btn), action->command, action->width()); - } - +bool renderer::on(const signals::parser::action_end& evt) { + (void) evt; + // auto btn = evt.cast(); + // int16_t clickable_width = 0; + // for (auto action = m_actions.rbegin(); action != m_actions.rend(); action++) { + // if (action->active && action->align == m_alignment && action->button == btn) { + // switch (action->align) { + // case alignment::NONE: + // break; + // case alignment::LEFT: + // action->end_x = m_x; + // break; + // case alignment::CENTER: + // clickable_width = m_x - action->start_x; + // action->start_x = m_rect.width / 2 - clickable_width / 2 + action->start_x / 2; + // action->end_x = action->start_x + clickable_width; + // break; + // case alignment::RIGHT: + // action->start_x = m_rect.width - m_x + action->start_x; + // action->end_x = m_rect.width; + // break; + // } + // action->start_x += m_rect.x; + // action->end_x += m_rect.x; + // action->active = false; + // } + // } return true; } -bool renderer::on(const write_text_ascii& evt) { - const uint16_t data[1]{evt.cast()}; - draw_textstring(data, 1); - return true; -} - -bool renderer::on(const write_text_unicode& evt) { - const uint16_t data[1]{evt.cast()}; - draw_textstring(data, 1); - return true; -} - -bool renderer::on(const write_text_string& evt) { - auto pkt = evt.cast(); - draw_textstring(pkt.data, pkt.length); +bool renderer::on(const signals::parser::text& evt) { + auto text = evt.cast(); + draw_text(text); return true; } diff --git a/src/x11/color.cpp b/src/x11/color.cpp deleted file mode 100644 index 94adc29d..00000000 --- a/src/x11/color.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include -#include - -#include "errors.hpp" -#include "utils/color.hpp" -#include "utils/string.hpp" -#include "x11/color.hpp" - -POLYBAR_NS - -mutex_wrapper> g_colorstore; - -const color& g_colorempty{"#00000000"}; -const color& g_colorblack{"#ff000000"}; -const color& g_colorwhite{"#ffffffff"}; - -color::color(string hex) : m_source(hex) { - if (hex.empty()) { - throw application_error("Cannot create color from empty hex"); - } - - m_value = std::strtoul(&hex[1], nullptr, 16); - m_color = color_util::premultiply_alpha(m_value); -} - -string color::source() const { - return m_source; -} - -color::operator XRenderColor() const { - XRenderColor x{}; - x.red = color_util::red_channel(m_color); - x.green = color_util::green_channel(m_color); - x.blue = color_util::blue_channel(m_color); - x.alpha = color_util::alpha_channel(m_color); - return x; -} - -color::operator string() const { - return color_util::hex(m_color); -} - -color::operator uint32_t() { - return static_cast(*this); -} - -color::operator uint32_t() const { - return m_color; -} - -const color& color::parse(string input, const color& fallback) { - if (input.empty()) { - throw application_error("Cannot parse empty color"); - } else if ((input = color_util::parse_hex(move(input))).empty()) { - return fallback; - } - - std::lock_guard guard(g_colorstore); - auto it = g_colorstore.find(input); - if (it == g_colorstore.end()) { - it = g_colorstore.emplace_hint(it, make_pair(input, color{input})); - } - return it->second; -} - -const color& color::parse(string input) { - return parse(move(input), g_colorempty); -} - -POLYBAR_NS_END diff --git a/src/x11/draw.cpp b/src/x11/draw.cpp index 4118f3d8..de645a73 100644 --- a/src/x11/draw.cpp +++ b/src/x11/draw.cpp @@ -1,5 +1,5 @@ #include "x11/draw.hpp" -#include "x11/connection.hpp" +#include "utils/color.hpp" POLYBAR_NS diff --git a/src/x11/fonts.cpp b/src/x11/fonts.cpp deleted file mode 100644 index c6cfab47..00000000 --- a/src/x11/fonts.cpp +++ /dev/null @@ -1,307 +0,0 @@ -#include "x11/fonts.hpp" -#include "components/logger.hpp" -#include "errors.hpp" -#include "utils/color.hpp" -#include "utils/factory.hpp" -#include "utils/memory.hpp" -#include "x11/connection.hpp" -#include "x11/draw.hpp" - -POLYBAR_NS - -void font_ref::_deleter::operator()(font_ref* font) { - font->glyph_widths.clear(); - font->width_lut.clear(); - - if (font->xft != nullptr || font->ptr != XCB_NONE) { - auto& conn = connection::make(); - if (font->xft != nullptr) { - XftFontClose(conn, font->xft); - } - if (font->ptr != XCB_NONE) { - xcb_close_font(conn, font->ptr); - } - } - delete font; -} - -/** - * Create instance - */ -font_manager::make_type font_manager::make() { - return factory_util::unique(connection::make(), logger::make()); -} - -font_manager::font_manager(connection& conn, const logger& logger) - : m_connection(conn) - , m_logger(logger) - , m_display(m_connection) - , m_visual(m_connection.visual()) - , m_colormap(XDefaultColormap(m_display, m_connection.default_screen())) {} - -font_manager::~font_manager() { - cleanup(); - if (m_display) { - if (m_xftcolor_allocated) { - XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor); - } - XFreeColormap(m_display, m_colormap); - } -} - -void font_manager::set_visual(Visual* v) { - m_visual = v; -} - -void font_manager::cleanup() { - if (m_xftdraw != nullptr) { - XftDrawDestroy(m_xftdraw); - m_xftdraw = nullptr; - } -} - -bool font_manager::load(const string& name, uint8_t fontindex, int8_t offset_y) { - if (fontindex > 0 && m_fonts.find(fontindex) != m_fonts.end()) { - m_logger.warn("A font with index '%i' has already been loaded, skip...", fontindex); - return false; - } else if (fontindex == 0) { - fontindex = m_fonts.size(); - m_logger.trace("font_manager: Assign font '%s' to index '%u'", name, fontindex); - } else { - m_logger.trace("font_manager: Add font '%s' to index '%u'", name, fontindex); - } - - shared_ptr font{new font_ref{}, font_ref::deleter}; - - font->offset_y = offset_y; - - if (open_xcb_font(font, name)) { - m_logger.info("Loaded font (xlfd=%s)", name); - } else if (font->ptr != XCB_NONE) { - m_connection.close_font_checked(font->ptr); - font->ptr = XCB_NONE; - } - - if (font->ptr == XCB_NONE && - (font->xft = XftFontOpenName(m_display, m_connection.default_screen(), name.c_str())) != nullptr) { - font->ascent = font->xft->ascent; - font->descent = font->xft->descent; - font->height = font->ascent + font->descent; - - if (font->xft->pattern != nullptr) { - // XftChar8* file; - // XftPatternGetString(font->xft->pattern, "file", 0, &file); - // m_logger.info("Loaded font (pattern=%s, file=%s)", name, file); - m_logger.info("Loaded font (pattern=%s)", name); - } else { - m_logger.info("Loaded font (pattern=%s)", name); - } - } - - if (font->ptr == XCB_NONE && font->xft == nullptr) { - return false; - } - - m_fonts.emplace(make_pair(fontindex, move(font))); - - int max_height{0}; - - for (auto& iter : m_fonts) { - if (iter.second->height > max_height) { - max_height = iter.second->height; - } - } - - for (auto& iter : m_fonts) { - iter.second->height = max_height; - } - - return true; -} - -void font_manager::fontindex(uint8_t index) { - if ((m_fontindex = index) > 0) { - for (auto&& font : m_fonts) { - if (font.first == index) { - m_fontindex = index; - break; - } - } - } -} - -shared_ptr font_manager::match_char(const uint16_t chr) { - if (!m_fonts.empty()) { - if (m_fontindex > 0 && static_cast(m_fontindex) <= m_fonts.size()) { - auto iter = m_fonts.find(m_fontindex); - if (iter != m_fonts.end() && iter->second) { - if (has_glyph_xft(iter->second, chr)) { - return iter->second; - } else if (has_glyph_xcb(iter->second, chr)) { - return iter->second; - } - } - } - for (auto&& font : m_fonts) { - if (font.second && has_glyph_xft(font.second, chr)) { - return font.second; - } else if (font.second && has_glyph_xcb(font.second, chr)) { - return font.second; - } - } - } - - return {}; -} - -uint8_t font_manager::glyph_width(const shared_ptr& font, const uint16_t chr) { - if (font && font->xft != nullptr) { - return glyph_width_xft(move(font), chr); - } else if (font && font->ptr != XCB_NONE) { - return glyph_width_xcb(move(font), chr); - } else { - return 0; - } -} - -void font_manager::drawtext(const shared_ptr& font, xcb_pixmap_t pm, xcb_gcontext_t gc, int16_t x, int16_t y, - const uint16_t* chars, size_t num_chars) { - if (m_xftdraw == nullptr) { - m_xftdraw = XftDrawCreate(m_display, pm, m_visual, m_colormap); - } - if (font->xft != nullptr) { - XftDrawString16(m_xftdraw, &m_xftcolor, font->xft, x, y, chars, num_chars); - } else if (font->ptr != XCB_NONE) { - vector ucs(num_chars); - for (size_t i = 0; i < num_chars; i++) { - ucs[i] = (chars[i] >> 8) | (chars[i] << 8); - } - xcb_poly_text_16(pm, gc, x, y, num_chars, ucs.data()); - } -} - -void font_manager::allocate_color(uint32_t color) { - // clang-format off - XRenderColor x{ - color_util::red_channel(color), - color_util::green_channel(color), - color_util::blue_channel(color), - color_util::alpha_channel(color)}; - // clang-format on - allocate_color(x); -} - -void font_manager::allocate_color(XRenderColor color) { - if (m_xftcolor_allocated) { - XftColorFree(m_display, m_visual, m_colormap, &m_xftcolor); - } - - if (!(m_xftcolor_allocated = XftColorAllocValue(m_display, m_visual, m_colormap, &color, &m_xftcolor))) { - m_logger.err("Failed to allocate color"); - } -} - -bool font_manager::open_xcb_font(const shared_ptr& font, string fontname) { - try { - uint32_t font_id{m_connection.generate_id()}; - m_connection.open_font_checked(font_id, fontname); - - m_logger.trace("Found X font '%s'", fontname); - font->ptr = font_id; - - auto query = m_connection.query_font(font_id); - if (query->char_infos_len == 0) { - m_logger.warn("X font '%s' does not contain any characters... (Verify the XLFD string)", fontname); - return false; - } - - font->descent = query->font_descent; - font->height = query->font_ascent + query->font_descent; - font->width = query->max_bounds.character_width; - font->char_max = query->max_byte1 << 8 | query->max_char_or_byte2; - font->char_min = query->min_byte1 << 8 | query->min_char_or_byte2; - - auto chars = query.char_infos(); - for (auto it = chars.begin(); it != chars.end(); it++) { - font->width_lut.emplace_back(forward(*it)); - } - return true; - } catch (const std::exception& e) { - m_logger.trace("font_manager: Could not find X font '%s' (what: %s)", fontname, e.what()); - } - - return false; -} - -uint8_t font_manager::glyph_width_xft(const shared_ptr& font, const uint16_t chr) { - auto it = font->glyph_widths.find(chr); - if (it != font->glyph_widths.end()) { - return it->second; - } - - XGlyphInfo extents{}; - FT_UInt glyph{XftCharIndex(m_display, font->xft, static_cast(chr))}; - - XftFontLoadGlyphs(m_display, font->xft, FcFalse, &glyph, 1); - XftGlyphExtents(m_display, font->xft, &glyph, 1, &extents); - XftFontUnloadGlyphs(m_display, font->xft, &glyph, 1); - - font->glyph_widths.emplace_hint(it, chr, extents.xOff); //.emplace_back(chr, extents.xOff); - - return extents.xOff; -} - -uint8_t font_manager::glyph_width_xcb(const shared_ptr& font, const uint16_t chr) { - if (!font || font->ptr == XCB_NONE) { - return 0; - } else if (static_cast(chr - font->char_min) < font->width_lut.size()) { - return font->width_lut[chr - font->char_min].character_width; - } else { - return font->width; - } -} - -bool font_manager::has_glyph_xft(const shared_ptr& font, const uint16_t chr) { - if (!font || font->xft == nullptr) { - return false; - } else if (XftCharExists(m_display, font->xft, static_cast(chr)) == FcFalse) { - return false; - } else { - return true; - } -} - -bool font_manager::has_glyph_xcb(const shared_ptr& font, const uint16_t chr) { - if (font->ptr == XCB_NONE) { - return false; - } else if (chr < font->char_min || chr > font->char_max) { - return false; - } else if (static_cast(chr - font->char_min) >= font->width_lut.size()) { - return false; - } else if (font->width_lut[chr - font->char_min].character_width == 0) { - return false; - } else { - return true; - } -} - -void font_manager::xcb_poly_text_16( - xcb_drawable_t d, xcb_gcontext_t gc, int16_t x, int16_t y, uint8_t len, uint16_t* str) { - static const xcb_protocol_request_t xcb_req = {5, nullptr, XCB_POLY_TEXT_16, 1}; - xcb_poly_text_16_request_t req{XCB_POLY_TEXT_16, 0, len, d, gc, x, y}; - uint8_t xcb_lendelta[2]{len, 0}; - struct iovec xcb_parts[7]{}; - xcb_parts[2].iov_base = reinterpret_cast(&req); - xcb_parts[2].iov_len = sizeof(req); - xcb_parts[3].iov_base = nullptr; - xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3; - xcb_parts[4].iov_base = xcb_lendelta; - xcb_parts[4].iov_len = sizeof(xcb_lendelta); - xcb_parts[5].iov_base = reinterpret_cast(str); - xcb_parts[5].iov_len = len * sizeof(int16_t); - xcb_parts[6].iov_base = nullptr; - xcb_parts[6].iov_len = -(xcb_parts[4].iov_len + xcb_parts[5].iov_len) & 3; - xcb_send_request(m_connection, 0, xcb_parts + 2, &xcb_req); -} - -POLYBAR_NS_END diff --git a/src/x11/tray_manager.cpp b/src/x11/tray_manager.cpp index 1c9ed0e8..47fc463a 100644 --- a/src/x11/tray_manager.cpp +++ b/src/x11/tray_manager.cpp @@ -12,7 +12,6 @@ #include "utils/memory.hpp" #include "utils/process.hpp" #include "x11/atoms.hpp" -#include "x11/color.hpp" #include "x11/connection.hpp" #include "x11/draw.hpp" #include "x11/graphics.hpp" @@ -133,7 +132,7 @@ void tray_manager::setup(const bar_settings& bar_opts) { } if (!bg.empty()) { - m_opts.background = color::parse(bg, g_colorempty); + m_opts.background = color_util::parse(bg); } else { m_opts.background = bar_opts.background; }