diff --git a/include/components/renderer.hpp b/include/components/renderer.hpp index 0366cb8a..42c6a1d8 100644 --- a/include/components/renderer.hpp +++ b/include/components/renderer.hpp @@ -2,6 +2,7 @@ #include #include +#include #include "cairo/fwd.hpp" #include "common.hpp" @@ -18,6 +19,7 @@ class connection; class config; class logger; class background_manager; +class bg_slice; // }}} using std::map; @@ -96,7 +98,7 @@ class renderer const config& m_conf; const logger& m_log; const bar_settings& m_bar; - background_manager& m_background; + std::shared_ptr m_background; int m_depth{32}; xcb_window_t m_window; diff --git a/include/x11/background_manager.hpp b/include/x11/background_manager.hpp index 85f72ab0..b887d591 100644 --- a/include/x11/background_manager.hpp +++ b/include/x11/background_manager.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include + #include "common.hpp" #include "events/signal_fwd.hpp" #include "events/signal_receiver.hpp" @@ -16,6 +19,44 @@ namespace cairo { class xcb_surface; } +class bg_slice { + public: + ~bg_slice(); + // copying bg_slices is not allowed + bg_slice(const bg_slice&) = delete; + bg_slice& operator=(const bg_slice&) = delete; + + /** + * Get the current desktop background at the location of this slice. + * The returned pointer is only valid as long as the slice itself is alive. + * + * This function is fast, since the current desktop background is cached. + */ + cairo::surface* get_surface() const { + return m_surface.get(); + } + + private: + bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window, xcb_visualtype_t* visual); + + // standard components + connection& m_connection; + + // area covered by this slice + xcb_rectangle_t m_rect{0, 0, 0U, 0U}; + xcb_window_t m_window; + + // cache for the root window background at this slice's position + xcb_pixmap_t m_pixmap{XCB_NONE}; + unique_ptr m_surface; + xcb_gcontext_t m_gcontext{XCB_NONE}; + + void allocate_resources(const logger& log, xcb_visualtype_t* visual); + void free_resources(); + + friend class background_manager; +}; + /** * \brief Class to keep track of the desktop background used to support pseudo-transparency * @@ -42,48 +83,35 @@ class background_manager : public signal_receiver observe(xcb_rectangle_t rect, xcb_window_t window); void handle(const evt::property_notify& evt); bool on(const signals::ui::update_geometry&); private: + void activate(); + void deactivate(); + // references to standard components connection& m_connection; signal_emitter& m_sig; const logger& m_log; - // these are set by activate - xcb_window_t m_window; - xcb_rectangle_t m_rect{0, 0, 0U, 0U}; + // list of slices that need to be filled with the desktop background + std::vector> m_slices; // required values for fetching the root window's background xcb_visualtype_t* m_visual{nullptr}; - xcb_gcontext_t m_gcontext{XCB_NONE}; - xcb_pixmap_t m_pixmap{XCB_NONE}; - unique_ptr m_surface; // true if we are currently attached as a listener for desktop background changes bool m_attached{false}; diff --git a/include/x11/tray_manager.hpp b/include/x11/tray_manager.hpp index cd17c484..6da0d9bd 100644 --- a/include/x11/tray_manager.hpp +++ b/include/x11/tray_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "cairo/context.hpp" #include "cairo/surface.hpp" @@ -35,6 +36,7 @@ using namespace std::chrono_literals; class connection; struct xembed_data; class background_manager; +class bg_slice; struct tray_settings { tray_settings() = default; @@ -106,8 +108,8 @@ class tray_manager int calculate_x(unsigned width, bool abspos = true) const; int calculate_y(bool abspos = true) const; - unsigned int calculate_w() const; - unsigned int calculate_h() const; + unsigned short int calculate_w() const; + unsigned short int calculate_h() const; int calculate_client_x(const xcb_window_t& win); int calculate_client_y(); @@ -138,7 +140,8 @@ class tray_manager connection& m_connection; signal_emitter& m_sig; const logger& m_log; - background_manager& m_background; + background_manager& m_background_manager; + std::shared_ptr m_bg_slice; vector> m_clients; tray_settings m_opts{}; diff --git a/src/components/renderer.cpp b/src/components/renderer.cpp index fb937107..4e55b6e2 100644 --- a/src/components/renderer.cpp +++ b/src/components/renderer.cpp @@ -41,7 +41,6 @@ renderer::renderer( , m_conf(conf) , m_log(logger) , m_bar(forward(bar)) - , m_background(background) , m_rect(m_bar.inner_area()) { m_sig.attach(this); @@ -166,10 +165,12 @@ renderer::renderer( } m_log.trace("Activate root background manager"); - m_background.activate(m_window, m_bar.outer_area(false)); } m_pseudo_transparency = m_conf.get("settings", "pseudo-transparency", m_pseudo_transparency); + if (m_pseudo_transparency) { + m_background = background.observe(m_bar.outer_area(false), m_window); + } m_comp_bg = m_conf.get("settings", "compositing-background", m_comp_bg); m_comp_fg = m_conf.get("settings", "compositing-foreground", m_comp_fg); @@ -308,7 +309,7 @@ void renderer::end() { cairo_pattern_t* barcontents{}; m_context->pop(&barcontents); // corresponding push is in renderer::begin - auto root_bg = m_background.get_surface(); + auto root_bg = m_background->get_surface(); if (root_bg != nullptr) { m_log.trace_x("renderer: root background"); *m_context << *root_bg; diff --git a/src/x11/background_manager.cpp b/src/x11/background_manager.cpp index a8885825..bd291443 100644 --- a/src/x11/background_manager.cpp +++ b/src/x11/background_manager.cpp @@ -28,29 +28,27 @@ background_manager::~background_manager() { free_resources(); } -cairo::surface* background_manager::get_surface() const { - return m_surface.get(); -} - -void background_manager::activate(xcb_window_t window, xcb_rectangle_t rect) { - // ensure that we start from a clean state - // - // the size of the pixmap may need to be changed, etc. - // so the easiest way is to just re-allocate everything. - // it may be possible to be more clever here, but activate is - // not supposed to be called often so this shouldn't be a problem. - free_resources(); +std::shared_ptr background_manager::observe(xcb_rectangle_t rect, xcb_window_t window) { + // allocate a slice + activate(); + auto slice = std::shared_ptr(new bg_slice(m_connection, m_log, rect, window, m_visual)); // make sure that we receive a notification when the background changes if(!m_attached) { m_connection.ensure_event_mask(m_connection.root(), XCB_EVENT_MASK_PROPERTY_CHANGE); m_connection.flush(); m_connection.attach_sink(this, SINK_PRIORITY_SCREEN); + m_attached = true; } - m_window = window; - m_rect = rect; + // if the slice is empty, don't add to slices + if (slice->m_rect.width == 0 || slice->m_rect.height == 0) { + return slice; + } + + m_slices.push_back(slice); fetch_root_pixmap(); + return slice; } void background_manager::deactivate() { @@ -59,61 +57,22 @@ void background_manager::deactivate() { m_attached = false; } free_resources(); - m_rect = xcb_rectangle_t{0, 0, 0, 0}; } -void background_manager::allocate_resources() { +void background_manager::activate() { if(!m_visual) { m_log.trace("background_manager: Finding root visual"); m_visual = m_connection.visual_type_for_id(m_connection.screen(), m_connection.screen()->root_visual); m_log.trace("background_manager: Got root visual with depth %d", m_connection.screen()->root_depth); } - - if(m_pixmap == XCB_NONE) { - m_log.trace("background_manager: Allocating pixmap"); - m_pixmap = m_connection.generate_id(); - m_connection.create_pixmap(m_connection.screen()->root_depth, m_pixmap, m_window, m_rect.width, m_rect.height); - } - - if(m_gcontext == XCB_NONE) { - m_log.trace("background_manager: Allocating graphics context"); - unsigned int mask = XCB_GC_GRAPHICS_EXPOSURES; - unsigned int value_list[1] = {0}; - m_gcontext = m_connection.generate_id(); - m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list); - } - - if(!m_surface) { - m_log.trace("background_manager: Allocating cairo surface"); - m_surface = make_unique(m_connection, m_pixmap, m_visual, m_rect.width, m_rect.height); - } - - if(m_attached) { - m_connection.detach_sink(this, SINK_PRIORITY_SCREEN); - m_attached = false; - } - } void background_manager::free_resources() { - m_surface.release(); m_visual = nullptr; - - if(m_pixmap != XCB_NONE) { - m_connection.free_pixmap(m_pixmap); - m_pixmap = XCB_NONE; - } - - if(m_gcontext != XCB_NONE) { - m_connection.free_gc(m_gcontext); - m_gcontext = XCB_NONE; - } } void background_manager::fetch_root_pixmap() { - allocate_resources(); - m_log.trace("background_manager: Fetching pixmap"); int pixmap_depth; @@ -121,30 +80,46 @@ void background_manager::fetch_root_pixmap() { xcb_rectangle_t pixmap_geom; try { - auto translated = m_connection.translate_coordinates(m_window, m_connection.screen()->root, m_rect.x, m_rect.y); if (!m_connection.root_pixmap(&pixmap, &pixmap_depth, &pixmap_geom)) { free_resources(); return m_log.err("background_manager: Failed to get root pixmap for background (realloc=%i)", realloc); }; - auto src_x = math_util::cap(translated->dst_x, pixmap_geom.x, int16_t(pixmap_geom.x + pixmap_geom.width)); - auto src_y = math_util::cap(translated->dst_y, pixmap_geom.y, int16_t(pixmap_geom.y + pixmap_geom.height)); - auto h = math_util::min(m_rect.height, pixmap_geom.height); - auto w = math_util::min(m_rect.width, pixmap_geom.width); + for (auto it = m_slices.begin(); it != m_slices.end(); ) { + auto slice = it->lock(); + if (!slice) { + it = m_slices.erase(it); + continue; + } + + // fill the slice + auto translated = m_connection.translate_coordinates(slice->m_window, m_connection.screen()->root, slice->m_rect.x, slice->m_rect.y); + auto src_x = math_util::cap(translated->dst_x, pixmap_geom.x, int16_t(pixmap_geom.x + pixmap_geom.width)); + auto src_y = math_util::cap(translated->dst_y, pixmap_geom.y, int16_t(pixmap_geom.y + pixmap_geom.height)); + auto w = math_util::cap(slice->m_rect.width, uint16_t(0), uint16_t(pixmap_geom.width - (src_x - pixmap_geom.x))); + auto h = math_util::cap(slice->m_rect.height, uint16_t(0), uint16_t(pixmap_geom.height - (src_y - pixmap_geom.y))); + m_log.trace("background_manager: Copying from root pixmap (%d) %dx%d+%dx%d", pixmap, w, h, src_x, src_y); + m_connection.copy_area_checked(pixmap, slice->m_pixmap, slice->m_gcontext, src_x, src_y, 0, 0, w, h); + + it++; + } + + // if there are no active slices, deactivate + if (m_slices.empty()) { + m_log.trace("background_manager: deactivating because there are no slices to observe"); + deactivate(); + } - m_log.trace("background_manager: Copying from root pixmap (%d) %dx%d+%dx%d", pixmap, w, h, src_x, src_y); - m_connection.copy_area_checked(pixmap, m_pixmap, m_gcontext, src_x, src_y, 0, 0, w, h); } catch(const exception& err) { m_log.err("background_manager: Failed to copy slice of root pixmap (%s)", err.what()); - free_resources(); throw; } } void background_manager::handle(const evt::property_notify& evt) { - // if region that we should observe is empty, don't do anything - if(m_rect.width == 0 || m_rect.height == 0) return; + // if there are no slices to observe, don't do anything + if(m_slices.empty()) return; if (evt->atom == _XROOTMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID) { fetch_root_pixmap(); @@ -158,4 +133,56 @@ bool background_manager::on(const signals::ui::update_geometry&) { return false; } + +bg_slice::bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window, xcb_visualtype_t* visual) + : m_connection(conn) + , m_rect(rect) + , m_window(window) { + try { + allocate_resources(log, visual); + } catch(...) { + free_resources(); + throw; + } +} + +bg_slice::~bg_slice() { + free_resources(); +} + +void bg_slice::allocate_resources(const logger& log, xcb_visualtype_t* visual) { + if(m_pixmap == XCB_NONE) { + log.trace("background_manager: Allocating pixmap"); + m_pixmap = m_connection.generate_id(); + m_connection.create_pixmap(m_connection.screen()->root_depth, m_pixmap, m_window, m_rect.width, m_rect.height); + } + + if(m_gcontext == XCB_NONE) { + log.trace("background_manager: Allocating graphics context"); + unsigned int mask = XCB_GC_GRAPHICS_EXPOSURES; + unsigned int value_list[1] = {0}; + m_gcontext = m_connection.generate_id(); + m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list); + } + + if(!m_surface) { + log.trace("background_manager: Allocating cairo surface"); + m_surface = make_unique(m_connection, m_pixmap, visual, m_rect.width, m_rect.height); + } +} + +void bg_slice::free_resources() { + m_surface.release(); + + if(m_pixmap != XCB_NONE) { + m_connection.free_pixmap(m_pixmap); + m_pixmap = XCB_NONE; + } + + if(m_gcontext != XCB_NONE) { + m_connection.free_gc(m_gcontext); + m_gcontext = XCB_NONE; + } +} + POLYBAR_NS_END diff --git a/src/x11/tray_manager.cpp b/src/x11/tray_manager.cpp index e07bf014..e08c1c4a 100644 --- a/src/x11/tray_manager.cpp +++ b/src/x11/tray_manager.cpp @@ -44,7 +44,7 @@ tray_manager::make_type tray_manager::make() { } tray_manager::tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back) - : m_connection(conn), m_sig(emitter), m_log(logger), m_background(back) { + : m_connection(conn), m_sig(emitter), m_log(logger), m_background_manager(back) { m_connection.attach_sink(this, SINK_PRIORITY_TRAY); } @@ -338,6 +338,11 @@ void tray_manager::reconfigure_window() { auto width = calculate_w(); auto x = calculate_x(width); + if (m_opts.transparent) { + xcb_rectangle_t rect{0, 0, calculate_w(), calculate_h()}; + m_bg_slice = m_background_manager.observe(rect, m_tray); + } + if (width > 0) { m_log.trace("tray: New window values, width=%d, x=%d", width, x); @@ -388,22 +393,18 @@ void tray_manager::reconfigure_bg(bool realloc) { m_log.trace("tray: Reconfigure bg (realloc=%i)", realloc); - auto w = calculate_w(); - auto x = calculate_x(w, false); - auto y = calculate_y(false); - if(!m_context) { return m_log.err("tray: no context for drawing the background"); } - cairo::surface* surface = m_background.get_surface(); + cairo::surface* surface = m_bg_slice->get_surface(); if(!surface) { return m_log.err("tray: no root surface"); } m_context->clear(); *m_context << CAIRO_OPERATOR_SOURCE << *m_surface; - cairo_set_source_surface(*m_context, *surface, -x, -y); + cairo_set_source_surface(*m_context, *surface, 0, 0); m_context->paint(); *m_context << CAIRO_OPERATOR_OVER << m_opts.background; m_context->paint(); @@ -491,6 +492,12 @@ void tray_manager::create_window() { m_tray = win << cw_flush(true); m_log.info("Tray window: %s", m_connection.id(m_tray)); + // activate the background manager if we have transparency + if (m_opts.transparent) { + xcb_rectangle_t rect{0, 0, calculate_w(), calculate_h()}; + m_bg_slice = m_background_manager.observe(rect, m_tray); + } + const unsigned int shadow{0}; m_connection.change_property(XCB_PROP_MODE_REPLACE, m_tray, _COMPTON_SHADOW, XCB_ATOM_CARDINAL, 32, 1, &shadow); } @@ -790,7 +797,7 @@ int tray_manager::calculate_y(bool abspos) const { /** * Calculate width of tray window */ -unsigned int tray_manager::calculate_w() const { +unsigned short int tray_manager::calculate_w() const { unsigned int width = m_opts.spacing; unsigned int count{0}; for (auto&& client : m_clients) { @@ -805,7 +812,7 @@ unsigned int tray_manager::calculate_w() const { /** * Calculate height of tray window */ -unsigned int tray_manager::calculate_h() const { +unsigned short int tray_manager::calculate_h() const { return m_opts.height_fill; }