feat(render): support pseudo-transparency

This adds pseudo-transparency for the background of the bar and the
background of the systray.
This commit is contained in:
Benno Fünfstück 2017-05-23 21:32:34 +02:00 committed by Patrick Ziegler
parent 654c667698
commit 3de914abca
15 changed files with 315 additions and 86 deletions

View File

@ -243,7 +243,7 @@ namespace cairo {
/**
* Match and create font from given fontconfig pattern
*/
decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset, double dpi_x, double dpi_y) {
inline decltype(auto) make_font(cairo_t* cairo, string&& fontname, double offset, double dpi_x, double dpi_y) {
static bool fc_init{false};
if (!fc_init && !(fc_init = FcInit())) {
throw application_error("Could not load fontconfig");

View File

@ -35,7 +35,8 @@ using modulemap_t = std::map<alignment, vector<module_t>>;
class controller : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eventqueue::exit_terminate, signals::eventqueue::exit_reload,
signals::eventqueue::notify_change, signals::eventqueue::notify_forcechange, signals::eventqueue::check_state, signals::ipc::action,
signals::ipc::command, signals::ipc::hook, signals::ui::ready, signals::ui::button_press> {
signals::ipc::command, signals::ipc::hook, signals::ui::ready, signals::ui::button_press,
signals::ui::update_background> {
public:
using make_type = unique_ptr<controller>;
static make_type make(unique_ptr<ipc>&& ipc, unique_ptr<inotify_watch>&& config_watch);
@ -65,6 +66,7 @@ class controller : public signal_receiver<SIGN_PRIORITY_CONTROLLER, signals::eve
bool on(const signals::ipc::action& evt);
bool on(const signals::ipc::command& evt);
bool on(const signals::ipc::hook& evt);
bool on(const signals::ui::update_background& evt);
private:
connection& m_connection;

View File

@ -49,7 +49,7 @@ class ipc {
const logger& m_log;
string m_path{};
unique_ptr<file_descriptor> m_fd{};
unique_ptr<file_descriptor> m_fd;
};
POLYBAR_NS_END

View File

@ -17,6 +17,7 @@ POLYBAR_NS
class connection;
class config;
class logger;
class background_manager;
// }}}
using std::map;
@ -39,7 +40,7 @@ class renderer
static make_type make(const bar_settings& bar);
explicit renderer(
connection& conn, signal_emitter& sig, const config&, const logger& logger, const bar_settings& bar);
connection& conn, signal_emitter& sig, const config&, const logger& logger, const bar_settings& bar, background_manager& background_manager);
~renderer();
xcb_window_t window() const;
@ -95,6 +96,7 @@ class renderer
const config& m_conf;
const logger& m_log;
const bar_settings& m_bar;
background_manager& m_background;
int m_depth{32};
xcb_window_t m_window;

View File

@ -120,6 +120,9 @@ namespace signals {
struct request_snapshot : public detail::value_signal<request_snapshot, string> {
using base_type::base_type;
};
struct update_background : public detail::base_signal<update_background> {
using base_type::base_type;
};
}
namespace ui_tray {

View File

@ -38,6 +38,7 @@ namespace signals {
struct shade_window;
struct unshade_window;
struct request_snapshot;
struct update_background;
}
namespace ui_tray {
struct mapped_clients;

View File

@ -0,0 +1,52 @@
#pragma once
#include "common.hpp"
#include "events/signal_fwd.hpp"
#include "x11/extensions/fwd.hpp"
#include "x11/types.hpp"
POLYBAR_NS
class logger;
namespace cairo {
class surface;
class xcb_surface;
}
class background_manager : public xpp::event::sink<evt::property_notify> {
public:
using make_type = background_manager&;
static make_type make();
explicit background_manager(connection& conn, signal_emitter& sig, const logger& log);
~background_manager();
void activate(xcb_window_t window, xcb_rectangle_t rect);
void deactivate();
cairo::surface* get_surface() const;
void handle(const evt::property_notify& evt);
private:
connection& m_connection;
signal_emitter& m_sig;
const logger& m_log;
xcb_window_t m_window;
xcb_rectangle_t m_rect{0, 0, 0U, 0U};
xcb_visualtype_t* m_visual = nullptr;
xcb_gcontext_t m_gcontext = XCB_NONE;
xcb_pixmap_t m_pixmap = XCB_NONE;
unique_ptr<cairo::xcb_surface> m_surface;
bool m_attached = false;
void allocate_resources();
void free_resources();
void fetch_root_pixmap();
};
POLYBAR_NS_END

View File

@ -126,6 +126,7 @@ class connection : public detail::connection_base<connection&, XPP_EXTENSION_LIS
unsigned int event_mask = 0xFFFFFF, bool propagate = false) const;
xcb_visualtype_t* visual_type(xcb_screen_t* screen, int match_depth = 32);
xcb_visualtype_t* visual_type_for_id(xcb_screen_t* screen, xcb_visualid_t visual_id);
bool root_pixmap(xcb_pixmap_t* pixmap, int* depth, xcb_rectangle_t* rect);

View File

@ -2,6 +2,8 @@
#include <chrono>
#include "cairo/context.hpp"
#include "cairo/surface.hpp"
#include "common.hpp"
#include "components/logger.hpp"
#include "components/types.hpp"
@ -32,6 +34,7 @@ using namespace std::chrono_literals;
// fwd declarations
class connection;
struct xembed_data;
class background_manager;
struct tray_settings {
tray_settings() = default;
@ -61,12 +64,12 @@ class tray_manager
: public xpp::event::sink<evt::expose, evt::visibility_notify, evt::client_message, evt::configure_request,
evt::resize_request, evt::selection_clear, evt::property_notify, evt::reparent_notify, evt::destroy_notify,
evt::map_notify, evt::unmap_notify>,
public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::visibility_change, signals::ui::dim_window> {
public signal_receiver<SIGN_PRIORITY_TRAY, signals::ui::visibility_change, signals::ui::dim_window, signals::ui::update_background> {
public:
using make_type = unique_ptr<tray_manager>;
static make_type make();
explicit tray_manager(connection& conn, signal_emitter& emitter, const logger& logger);
explicit tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back);
~tray_manager();
@ -127,21 +130,21 @@ class tray_manager
bool on(const signals::ui::visibility_change& evt);
bool on(const signals::ui::dim_window& evt);
bool on(const signals::ui::update_background& evt);
private:
connection& m_connection;
signal_emitter& m_sig;
const logger& m_log;
background_manager& m_background;
vector<shared_ptr<tray_client>> m_clients;
tray_settings m_opts{};
xcb_gcontext_t m_gc{0};
xcb_pixmap_t m_pixmap{0};
xcb_pixmap_t m_rootpixmap{0};
int m_rootpixmap_depth{0};
xcb_rectangle_t m_rootpixmap_geom{0, 0, 0U, 0U};
unique_ptr<cairo::surface> m_surface;
unique_ptr<cairo::context> m_context;
unsigned int m_prevwidth{0U};
unsigned int m_prevheight{0U};

View File

@ -686,4 +686,10 @@ bool controller::on(const signals::ipc::hook& evt) {
return true;
}
bool controller::on(const signals::ui::update_background&) {
enqueue(make_update_evt(true));
return false;
}
POLYBAR_NS_END

View File

@ -77,7 +77,7 @@ void parser::codeblock(string&& data, const bar_settings& bar) {
switch (tag) {
case 'B':
m_sig.emit(change_background{parse_color(value, bar.background)});
m_sig.emit(change_background{parse_color(value, 0x0)});
break;
case 'F':

View File

@ -7,6 +7,7 @@
#include "utils/file.hpp"
#include "utils/math.hpp"
#include "x11/atoms.hpp"
#include "x11/background_manager.hpp"
#include "x11/connection.hpp"
#include "x11/extensions/all.hpp"
#include "x11/winspec.hpp"
@ -25,7 +26,8 @@ renderer::make_type renderer::make(const bar_settings& bar) {
signal_emitter::make(),
config::make(),
logger::make(),
forward<decltype(bar)>(bar));
forward<decltype(bar)>(bar),
background_manager::make());
// clang-format on
}
@ -33,13 +35,15 @@ renderer::make_type renderer::make(const bar_settings& bar) {
* Construct renderer instance
*/
renderer::renderer(
connection& conn, signal_emitter& sig, const config& conf, const logger& logger, const bar_settings& bar)
connection& conn, signal_emitter& sig, const config& conf, const logger& logger, const bar_settings& bar, background_manager& background)
: m_connection(conn)
, m_sig(sig)
, m_conf(conf)
, m_log(logger)
, m_bar(forward<const bar_settings&>(bar))
, m_background(background)
, m_rect(m_bar.inner_area()) {
m_sig.attach(this);
m_log.trace("renderer: Get TrueColor visual");
{
@ -160,6 +164,9 @@ renderer::renderer(
m_log.info("Loaded font \"%s\" (name=%s, offset=%i, file=%s)", pattern, font->name(), offset, font->file());
*m_context << move(font);
}
m_log.trace("Activate root background manager");
m_background.activate(m_window, m_bar.inner_area(true));
}
m_comp_bg = m_conf.get<cairo_operator_t>("settings", "compositing-background", m_comp_bg);
@ -205,7 +212,7 @@ void renderer::begin(xcb_rectangle_t rect) {
m_align = alignment::NONE;
// Reset colors
m_bg = m_bar.background;
m_bg = 0x0;
m_fg = m_bar.foreground;
m_ul = m_bar.underline.color;
m_ol = m_bar.overline.color;
@ -214,6 +221,11 @@ void renderer::begin(xcb_rectangle_t rect) {
m_context->save();
m_context->clear();
// Draw the background on the new layer to make up for
// the areas not covered by the alignment blocks
fill_background();
// Create corner mask
if (m_bar.radius && m_cornermask == nullptr) {
m_context->save();
@ -261,10 +273,6 @@ void renderer::end() {
// so that it can be masked with the corner pattern
m_context->push();
// Draw the background on the new layer to make up for
// the areas not covered by the alignment blocks
fill_background();
for (auto&& b : m_blocks) {
flush(b.first);
}
@ -495,6 +503,14 @@ void renderer::fill_background() {
m_context->save();
*m_context << m_comp_bg;
auto root_bg = m_background.get_surface();
if(root_bg != nullptr) {
m_log.trace_x("renderer: root background");
*m_context << *root_bg;
m_context->paint();
*m_context << CAIRO_OPERATOR_OVER;
}
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};
@ -718,8 +734,6 @@ bool renderer::on(const signals::parser::change_alignment& evt) {
m_blocks[m_align].y = 0.0;
m_context->push();
m_log.trace_x("renderer: push(%i)", static_cast<int>(m_align));
fill_background();
}
return true;
}

View File

@ -0,0 +1,143 @@
#include "cairo/surface.hpp"
#include "events/signal.hpp"
#include "components/logger.hpp"
#include "x11/atoms.hpp"
#include "x11/connection.hpp"
#include "x11/background_manager.hpp"
#include "utils/factory.hpp"
#include "utils/math.hpp"
POLYBAR_NS
background_manager& background_manager::make() {
return *factory_util::singleton<background_manager>(connection::make(), signal_emitter::make(), logger::make());
}
background_manager::background_manager(
connection& conn, signal_emitter& sig, const logger& log)
: m_connection(conn)
, m_sig(sig)
, m_log(log) {
}
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();
// 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_window = window;
m_rect = rect;
fetch_root_pixmap();
}
void background_manager::deactivate() {
free_resources();
}
void background_manager::allocate_resources() {
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<cairo::xcb_surface>(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;
xcb_pixmap_t pixmap;
xcb_rectangle_t pixmap_geom;
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(m_rect.x, pixmap_geom.x, int16_t(pixmap_geom.x + pixmap_geom.width));
auto src_y = math_util::cap(m_rect.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);
m_log.trace("background_manager: Copying from root pixmap (%d) %dx%d+%dx%d", pixmap, w, h, src_x, src_y);
try {
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();
return;
}
}
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 (evt->atom == _XROOTMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID) {
fetch_root_pixmap();
m_sig.emit(signals::ui::update_background());
}
}
POLYBAR_NS_END

View File

@ -165,6 +165,19 @@ xcb_visualtype_t* connection::visual_type(xcb_screen_t* screen, int match_depth)
return nullptr;
}
xcb_visualtype_t* connection::visual_type_for_id(xcb_screen_t* screen, xcb_visualid_t visual_id) {
xcb_depth_iterator_t depth_iter = xcb_screen_allowed_depths_iterator(screen);
if (depth_iter.data) {
for (; depth_iter.rem; xcb_depth_next(&depth_iter)) {
for (auto it = xcb_depth_visuals_iterator(depth_iter.data); it.rem; xcb_visualtype_next(&it)) {
if(it.data->visual_id == visual_id) return it.data;
}
}
}
return nullptr;
}
/**
* Query root window pixmap
*/

View File

@ -1,6 +1,8 @@
#include <xcb/xcb_image.h>
#include <thread>
#include "cairo/context.hpp"
#include "cairo/surface.hpp"
#include "components/config.hpp"
#include "errors.hpp"
#include "events/signal.hpp"
@ -9,6 +11,7 @@
#include "utils/math.hpp"
#include "utils/memory.hpp"
#include "utils/process.hpp"
#include "x11/background_manager.hpp"
#include "x11/ewmh.hpp"
#include "x11/icccm.hpp"
#include "x11/tray_manager.hpp"
@ -37,11 +40,11 @@ POLYBAR_NS
* Create instance
*/
tray_manager::make_type tray_manager::make() {
return factory_util::unique<tray_manager>(connection::make(), signal_emitter::make(), logger::make());
return factory_util::unique<tray_manager>(connection::make(), signal_emitter::make(), logger::make(), background_manager::make());
}
tray_manager::tray_manager(connection& conn, signal_emitter& emitter, const logger& logger)
: m_connection(conn), m_sig(emitter), m_log(logger) {
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.attach_sink(this, SINK_PRIORITY_TRAY);
}
@ -131,9 +134,9 @@ void tray_manager::setup(const bar_settings& bar_opts) {
m_opts.background = bar_opts.background;
}
if (color_util::alpha_channel(m_opts.background) == 0) {
if (color_util::alpha_channel(m_opts.background) != 1) {
m_opts.transparent = true;
m_opts.background = 0;
//m_opts.background = 0;
}
}
@ -257,6 +260,8 @@ void tray_manager::deactivate(bool clear_selection) {
m_log.trace("tray: Destroy window");
m_connection.destroy_window(m_tray);
}
m_context.release();
m_surface.release();
if (m_pixmap) {
m_connection.free_pixmap(m_pixmap);
}
@ -267,7 +272,6 @@ void tray_manager::deactivate(bool clear_selection) {
m_tray = 0;
m_pixmap = 0;
m_gc = 0;
m_rootpixmap = 0;
m_prevwidth = 0;
m_prevheight = 0;
m_opts.configured_x = 0;
@ -384,75 +388,30 @@ void tray_manager::reconfigure_clients() {
void tray_manager::reconfigure_bg(bool realloc) {
if (!m_opts.transparent || m_clients.empty() || !m_mapped) {
return;
} else if (!m_rootpixmap) {
realloc = true;
}
auto w = calculate_w();
auto h = calculate_h();
if ((!w || (w == m_prevwidth && h == m_prevheight)) && !realloc) {
return;
}
};
m_log.trace("tray: Reconfigure bg (realloc=%i)", realloc);
if (realloc && !m_connection.root_pixmap(&m_rootpixmap, &m_rootpixmap_depth, &m_rootpixmap_geom)) {
return m_log.err("Failed to get root pixmap for tray background (realloc=%i)", realloc);
} else if (realloc) {
// clang-format off
m_log.info("Tray root pixmap (rootpmap=%s, geom=%dx%d+%d+%d, tray=%s, pmap=%s, gc=%s)",
m_connection.id(m_rootpixmap),
m_rootpixmap_geom.width,
m_rootpixmap_geom.height,
m_rootpixmap_geom.x,
m_rootpixmap_geom.y,
m_connection.id(m_tray),
m_connection.id(m_pixmap),
m_connection.id(m_gc));
// clang-format on
}
m_prevwidth = w;
m_prevheight = h;
auto w = calculate_w();
auto x = calculate_x(w);
auto y = calculate_y();
auto px = math_util::max(0, m_rootpixmap_geom.x + x);
auto py = math_util::max(0, m_rootpixmap_geom.y + y);
// Make sure we don't try to copy void content
if (px + w > m_rootpixmap_geom.width) {
w -= px + w - m_rootpixmap_geom.width;
}
if (py + h > m_rootpixmap_geom.height) {
h -= py + h - m_rootpixmap_geom.height;
if(!m_context) {
return m_log.err("tray: no context for drawing the background");
}
if (realloc) {
vector<unsigned char> image_data;
unsigned char image_depth;
try {
auto image_reply = m_connection.get_image(XCB_IMAGE_FORMAT_Z_PIXMAP, m_rootpixmap, px, py, w, h, XCB_COPY_PLANE);
image_depth = image_reply->depth;
std::back_insert_iterator<decltype(image_data)> back_it(image_data);
std::copy(image_reply.data().begin(), image_reply.data().end(), back_it);
} catch (const exception& err) {
m_log.err("Failed to get slice of root pixmap (%s)", err.what());
return;
}
try {
m_connection.put_image_checked(
XCB_IMAGE_FORMAT_Z_PIXMAP, m_pixmap, m_gc, w, h, 0, 0, 0, image_depth, image_data.size(), image_data.data());
} catch (const exception& err) {
m_log.err("Failed to store slice of root pixmap (%s)", err.what());
return;
}
cairo::surface* surface = m_background.get_surface();
if(!surface) {
return m_log.err("tray: no root surface");
}
m_connection.copy_area_checked(m_rootpixmap, m_pixmap, m_gc, px, py, 0, 0, w, h);
m_context->clear();
*m_context << CAIRO_OPERATOR_SOURCE << *m_surface;
cairo_set_source_surface(*m_context, *surface, -x, -y);
m_context->paint();
*m_context << CAIRO_OPERATOR_OVER << m_opts.background;
m_context->paint();
}
/**
@ -470,11 +429,13 @@ void tray_manager::refresh_window() {
auto width = calculate_w();
auto height = calculate_h();
if (m_opts.transparent && !m_rootpixmap) {
if (m_opts.transparent && !m_context) {
xcb_rectangle_t rect{0, 0, static_cast<uint16_t>(width), static_cast<uint16_t>(height)};
m_connection.poly_fill_rectangle(m_pixmap, m_gc, 1, &rect);
}
if(m_surface) m_surface->flush();
m_connection.clear_area(0, m_tray, 0, 0, width, height);
for (auto&& client : m_clients) {
@ -546,7 +507,7 @@ void tray_manager::create_bg(bool realloc) {
if (!m_opts.transparent) {
return;
}
if (!realloc && m_pixmap && m_gc && m_rootpixmap) {
if (!realloc && m_pixmap && m_gc && m_surface && m_context) {
return;
}
if (realloc && m_pixmap) {
@ -558,6 +519,13 @@ void tray_manager::create_bg(bool realloc) {
m_gc = 0;
}
if(realloc && m_surface) {
m_surface.release();
}
if(realloc && m_context) {
m_context.release();
}
auto w = m_opts.width_max;
auto h = calculate_h();
@ -584,6 +552,21 @@ void tray_manager::create_bg(bool realloc) {
}
}
if(!m_surface) {
xcb_visualtype_t* visual = m_connection.visual_type_for_id(m_connection.screen(), m_connection.screen()->root_visual);
if(!visual) {
return m_log.err("Failed to get root visual for tray background");
}
m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, w, h);
}
if(!m_context) {
m_context = make_unique<cairo::context>(*m_surface, m_log);
m_context->clear();
*m_context << CAIRO_OPERATOR_SOURCE << m_opts.background;
m_context->paint();
}
try {
m_connection.change_window_attributes_checked(m_tray, XCB_CW_BACK_PIXMAP, &m_pixmap);
} catch (const exception& err) {
@ -1155,4 +1138,10 @@ bool tray_manager::on(const signals::ui::dim_window& evt) {
return false;
}
bool tray_manager::on(const signals::ui::update_background&) {
redraw_window(true);
return false;
}
POLYBAR_NS_END