#include "components/renderer.hpp" #include "components/logger.hpp" #include "errors.hpp" #include "events/signal.hpp" #include "events/signal_emitter.hpp" #include "events/signal_receiver.hpp" #include "x11/connection.hpp" #include "x11/draw.hpp" #include "x11/extensions/all.hpp" #include "x11/generic.hpp" #include "x11/winspec.hpp" POLYBAR_NS /** * Create instance */ renderer::make_type renderer::make(const bar_settings& bar, vector&& fonts) { // clang-format off return factory_util::unique( connection::make(), signal_emitter::make(), logger::make(), font_manager::make(), forward(bar), forward(fonts)); // 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) : m_connection(conn) , m_sig(emitter) , 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(), 24)) == nullptr) { m_log.err("No 24-bit TrueColor visual found, aborting..."); throw application_error("No matching TrueColor visual found..."); } if (m_visual == nullptr) { throw application_error("No matching TrueColor visual found..."); } 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_log.trace("renderer: Allocate output window"); { // clang-format off m_window = winspec(m_connection) << cw_size(m_bar.size) << cw_pos(m_bar.pos) << cw_depth(m_depth) << cw_visual(m_visual->visual_id) << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT) << cw_params_back_pixel(0) << cw_params_border_pixel(0) << cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED) << cw_params_colormap(m_colormap) << cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE |XCB_EVENT_MASK_EXPOSURE |XCB_EVENT_MASK_BUTTON_PRESS) << cw_params_override_redirect(m_bar.override_redirect) << cw_flush(true); // clang-format on } m_log.trace("renderer: Allocate window 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 for (int i = 0; i < 8; i++) { uint32_t mask{0}; uint32_t value_list[32]{0}; 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: Load fonts"); { auto fonts_loaded = false; auto fontindex = 0; if (fonts.empty()) { m_log.warn("No fonts specified, using fallback font \"fixed\""); } 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]); } } if (!fonts_loaded && !fonts.empty()) { m_log.warn("Unable to load fonts, using fallback font \"fixed\""); } if (!fonts_loaded && !m_fontmanager->load("fixed")) { throw application_error("Unable to load fonts"); } m_fontmanager->allocate_color(m_bar.foreground); } } /** * Deconstruct instance */ renderer::~renderer() { m_sig.detach(this); if (m_window != XCB_NONE) { m_connection.destroy_window(m_window); } } /** * Get output window */ xcb_window_t renderer::window() const { return m_window; } /** * 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; m_actions.clear(); } /** * End render routine */ void renderer::end() { m_log.trace_x("renderer: end"); m_fontmanager->cleanup(); #ifdef DEBUG_HINTS debug_hints(); #endif flush(false); } /** * Flush pixmap contents onto the target window */ void renderer::flush(bool clear) { const xcb_rectangle_t& r = m_rect; 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 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); 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); 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_connection.flush(); } /** * Reserve space at given edge */ void renderer::reserve_space(edge side, uint16_t w) { m_log.trace_x("renderer: reserve_space(%i, %i)", static_cast(side), w); m_cleararea.side = side; m_cleararea.size = w; switch (side) { case edge::NONE: break; case edge::TOP: m_rect.y += w; m_rect.height -= w; break; case edge::BOTTOM: m_rect.height -= w; break; case edge::LEFT: m_rect.x += w; m_rect.width -= w; break; case edge::RIGHT: m_rect.width -= w; break; case edge::ALL: m_rect.x += w; m_rect.y += w; m_rect.width -= w * 2; m_rect.height -= w * 2; break; } } /** * 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); } /** * 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)"); } 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); } } /** * Get completed action blocks */ const vector renderer::get_actions() { return m_actions; } /** * Shift pixmap content by given value */ 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; } m_log.trace_x("renderer: shift_content(%i)", shift_x); 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; } draw_util::fill(m_connection, m_pixmap, m_gcontexts.at(gc::BG), x, 0, m_rect.width - x, m_rect.height); // Translate pos of clickable areas if (m_alignment != alignment::LEFT) { for (auto&& action : m_actions) { if (action.active || action.align != m_alignment) { continue; } 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(); for (auto&& action : m_actions) { if (action.active) { continue; } 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) { const uint32_t color{*evt()}; 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); } return true; } bool renderer::on(const change_foreground& evt) { const uint32_t color{*evt()}; 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; } return true; } bool renderer::on(const change_underline& evt) { const uint32_t color{*evt()}; 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; } return true; } bool renderer::on(const change_overline& evt) { const uint32_t color{*evt()}; 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; } return true; } bool renderer::on(const change_font& evt) { const uint8_t font{*evt()}; 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; } return true; } bool renderer::on(const change_alignment& evt) { auto align = static_cast(*evt()); 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)); m_alignment = align; m_currentx = 0; } return true; } bool renderer::on(const offset_pixel& evt) { shift_content(*evt()); return true; } bool renderer::on(const attribute_set& evt) { m_log.trace_x("renderer: attribute_set(%i, %i)", static_cast(*evt()), true); m_attributes |= 1U << static_cast(*evt()); return true; } bool renderer::on(const attribute_unset& evt) { m_log.trace_x("renderer: attribute_unset(%i, %i)", static_cast(*evt()), true); m_attributes &= ~(1U << static_cast(*evt())); return true; } bool renderer::on(const attribute_toggle& evt) { m_log.trace_x("renderer: attribute_toggle(%i)", static_cast(*evt())); m_attributes ^= 1U << static_cast(*evt()); return true; } bool renderer::on(const action_begin& evt) { auto a = static_cast(*evt()); 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); return true; } bool renderer::on(const action_end& evt) { auto btn = static_cast(*evt()); 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()); } return true; } bool renderer::on(const write_text_ascii& evt) { const uint16_t data[1]{*evt()}; draw_textstring(data, 1); return true; } bool renderer::on(const write_text_unicode& evt) { const uint16_t data[1]{*evt()}; draw_textstring(data, 1); return true; } bool renderer::on(const write_text_string& evt) { auto pkt = static_cast(*evt()); draw_textstring(pkt.data, pkt.length); return true; } POLYBAR_NS_END