#include "x11/background_manager.hpp" #include #include "cairo/context.hpp" #include "cairo/surface.hpp" #include "components/config.hpp" #include "components/logger.hpp" #include "events/signal.hpp" #include "utils/factory.hpp" #include "utils/math.hpp" #include "x11/atoms.hpp" #include "x11/connection.hpp" POLYBAR_NS background_manager& background_manager::make() { return *factory_util::singleton(connection::make(), signal_emitter::make(), logger::make()); } background_manager::background_manager(connection& conn, signal_emitter& sig, const logger& log) : m_connection(conn), m_sig(sig), m_log(log) { m_sig.attach(this); } background_manager::~background_manager() { m_sig.detach(this); free_resources(); } std::shared_ptr background_manager::observe(xcb_rectangle_t rect, xcb_window_t window) { // allocate a slice auto slice = std::shared_ptr(new bg_slice(m_connection, m_log, rect, window)); // if the slice is empty, don't add to slices if (slice->m_rect.width == 0 || slice->m_rect.height == 0) { return slice; } activate(); m_slices.push_back(slice); update_slice(*slice); return slice; } void background_manager::activate() { attach(); } void background_manager::deactivate() { detach(); free_resources(); } /** * Attaches a listener to listen for background changes on the root window. */ void background_manager::attach() { if (!m_attached) { // make sure that we receive a notification when the background changes 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; } } /** * Stops listening for background changes */ void background_manager::detach() { if (m_attached) { m_connection.detach_sink(this, SINK_PRIORITY_SCREEN); m_attached = false; } } void background_manager::free_resources() { clear_pixmap(); } /** * Changes required when the background may have changed. * * 1. Delete cached pixmaps * 2. Update all slices (loads new pixmaps on demand) * 3. Notify about new background */ void background_manager::on_background_change() { clear_pixmap(); m_pixmap_load_failed = false; for (auto it = m_slices.begin(); it != m_slices.end();) { auto slice = it->lock(); if (!slice) { it = m_slices.erase(it); continue; } update_slice(*slice); 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(); return; } m_sig.emit(signals::ui::update_background()); } void background_manager::update_slice(bg_slice& slice) { ensure_pixmap(); if (has_pixmap()) { slice.copy(m_pixmap, m_pixmap_depth, m_pixmap_geom, m_visual); } else { slice.clear(); } } void background_manager::handle(const evt::property_notify& evt) { if (evt->atom == _XROOTPMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID) { m_log.trace("background_manager: root pixmap change"); on_background_change(); } } bool background_manager::on(const signals::ui::update_geometry&) { m_log.trace("background_manager: update_geometry"); on_background_change(); return false; } bool background_manager::has_pixmap() const { return m_pixmap != XCB_NONE; } void background_manager::ensure_pixmap() { // Only try to load the root pixmap if we haven't already loaded it and the previous load didn't fail. if (!has_pixmap() && !m_pixmap_load_failed) { load_pixmap(); m_pixmap_load_failed = !has_pixmap(); } } void background_manager::load_pixmap() { int old_depth = m_pixmap_depth; clear_pixmap(); try { if (!m_connection.root_pixmap(&m_pixmap, &m_pixmap_depth, &m_pixmap_geom)) { m_log.warn("background_manager: Failed to get root pixmap, default to black (is there a wallpaper?)"); return; } } catch (const exception& err) { m_log.err("background_manager: Failed to get root pixmap, default to black (%s)", err.what()); clear_pixmap(); return; } m_log.trace("background_manager: root pixmap (0x%x:%d) %dx%d+%d+%d", m_pixmap, m_pixmap_depth, m_pixmap_geom.width, m_pixmap_geom.height, m_pixmap_geom.x, m_pixmap_geom.y); if (m_pixmap_depth == 1 && m_pixmap_geom.width == 1 && m_pixmap_geom.height == 1) { m_log.err("background_manager: Cannot find root pixmap, try a different tool to set the desktop background"); clear_pixmap(); return; } // Only reload visual if depth changed if (old_depth != m_pixmap_depth) { m_visual = m_connection.visual_type(XCB_VISUAL_CLASS_TRUE_COLOR, m_pixmap_depth); if (!m_visual) { m_log.err("background_manager: Cannot find true color visual for root pixmap (depth: %d)", m_pixmap_depth); clear_pixmap(); return; } } } void background_manager::clear_pixmap() { if (has_pixmap()) { m_pixmap = XCB_NONE; m_pixmap_depth = 0; m_pixmap_geom = {0, 0, 0, 0}; m_visual = nullptr; } } bg_slice::bg_slice(connection& conn, const logger& log, xcb_rectangle_t rect, xcb_window_t window) : m_connection(conn), m_log(log), m_rect(rect), m_window(window) {} bg_slice::~bg_slice() { free_resources(); } /** * 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* bg_slice::get_surface() const { return m_surface.get(); } void bg_slice::clear() { ensure_resources(0, nullptr); } void bg_slice::copy(xcb_pixmap_t root_pixmap, int depth, xcb_rectangle_t geom, xcb_visualtype_t* visual) { assert(root_pixmap); assert(depth > 0); ensure_resources(depth, visual); assert(m_pixmap); // fill the slice auto translated = m_connection.translate_coordinates(m_window, m_connection.screen()->root, m_rect.x, m_rect.y); // Coordinates of the slice in the root pixmap auto src_x = math_util::cap(translated->dst_x, geom.x, int16_t(geom.x + geom.width)); auto src_y = math_util::cap(translated->dst_y, geom.y, int16_t(geom.y + geom.height)); auto w = math_util::cap(m_rect.width, uint16_t(0), uint16_t(geom.width - (src_x - geom.x))); auto h = math_util::cap(m_rect.height, uint16_t(0), uint16_t(geom.height - (src_y - geom.y))); m_log.trace( "background_manager: Copying from root pixmap (0x%x:%d) %dx%d+%d+%d", root_pixmap, depth, w, h, src_x, src_y); m_connection.copy_area_checked(root_pixmap, m_pixmap, m_gcontext, src_x, src_y, 0, 0, w, h); } void bg_slice::ensure_resources(int depth, xcb_visualtype_t* visual) { if (m_depth != depth) { m_depth = depth; free_resources(); if (depth != 0) { allocate_resources(visual); } } } void bg_slice::allocate_resources(xcb_visualtype_t* visual) { m_log.trace("background_manager: Allocating 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("background_manager: Allocating graphics context"); auto black_pixel = m_connection.screen()->black_pixel; uint32_t mask = 0; xcb_params_gc_t params{}; std::array value_list{}; XCB_AUX_ADD_PARAM(&mask, ¶ms, foreground, black_pixel); XCB_AUX_ADD_PARAM(&mask, ¶ms, background, black_pixel); XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 0); m_connection.pack_values(mask, ¶ms, value_list); m_gcontext = m_connection.generate_id(); m_connection.create_gc(m_gcontext, m_pixmap, mask, value_list.data()); m_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.reset(); 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