Use individual wrapper window for each tray client

This commit is contained in:
patrick96 2022-03-07 15:12:42 +01:00
parent 9544130b9c
commit 3711e999ba
No known key found for this signature in database
GPG Key ID: 521E5E03AEBCA1A7
8 changed files with 174 additions and 86 deletions

View File

@ -34,11 +34,15 @@ class logger {
explicit logger(loglevel level);
const logger& operator=(const logger&) const {
return *this;
}
static loglevel parse_verbosity(const string& name, loglevel fallback = loglevel::NONE);
void verbosity(loglevel level);
#ifdef DEBUG_LOGGER // {{{
#ifdef DEBUG_LOGGER // {{{
template <typename... Args>
void trace(const string& message, Args&&... args) const {
output(loglevel::TRACE, message, std::forward<Args>(args)...);
@ -57,7 +61,7 @@ class logger {
void trace(Args&&...) const {}
template <typename... Args>
void trace_x(Args&&...) const {}
#endif // }}}
#endif // }}}
/**
* Output an info message
@ -118,21 +122,21 @@ class logger {
return;
}
#if defined(__clang__) // {{{
#if defined(__clang__) // {{{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-security"
#elif defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-security"
#endif // }}}
#endif // }}}
dprintf(m_fd, (m_prefixes.at(level) + format + m_suffixes.at(level) + "\n").c_str(), convert(values)...);
#if defined(__clang__) // {{{
#if defined(__clang__) // {{{
#pragma clang diagnostic pop
#elif defined(__GNUC__)
#pragma GCC diagnostic pop
#endif // }}}
#endif // }}}
}
private:

View File

@ -13,7 +13,7 @@ class connection;
class tray_client : public non_copyable_mixin {
public:
explicit tray_client(connection& conn, xcb_window_t win, size s);
explicit tray_client(const logger& log, connection& conn, xcb_window_t tray, xcb_window_t win, size s);
~tray_client();
tray_client(tray_client&&);
@ -27,7 +27,8 @@ class tray_client : public non_copyable_mixin {
bool mapped() const;
void mapped(bool state);
xcb_window_t window() const;
xcb_window_t embedder() const;
xcb_window_t client() const;
void query_xembed();
bool is_xembed_supported() const;
@ -38,8 +39,27 @@ class tray_client : public non_copyable_mixin {
void configure_notify(int x, int y) const;
protected:
const logger& m_log;
connection& m_connection;
xcb_window_t m_window{XCB_NONE};
/**
* Embedder window.
*
* The docking client window is reparented to this window.
* This window is itself a child of the main tray window.
*
* This class owns this window and is responsible for creating/destroying it.
*/
xcb_window_t m_wrapper{XCB_NONE};
/**
* Client window.
*
* The window itself is owned by the application providing it.
* This class is responsible for correctly mapping and reparenting it in accordance with the XEMBED protocol.
*/
xcb_window_t m_client{XCB_NONE};
/**
* Whether the client window supports XEMBED.
@ -50,9 +70,12 @@ class tray_client : public non_copyable_mixin {
/**
* _XEMBED_INFO of the client window
*
* Only valid if m_xembed_supported == true
*/
xembed::info m_xembed;
// TODO
bool m_mapped{false};
size m_size;

View File

@ -69,7 +69,7 @@ struct tray_settings {
/**
* Number of clients currently mapped.
*/
int num_clients{0};
int num_mapped_clients{0};
// This is the width of the bar window
// TODO directly read from bar_settings

View File

@ -5,26 +5,26 @@
POLYBAR_NS
#define XEMBED_VERSION 0
#define XEMBED_MAPPED (1 << 0)
static constexpr uint32_t XEMBED_VERSION = 0;
static constexpr uint32_t XEMBED_MAPPED = (1 << 0);
#define XEMBED_EMBEDDED_NOTIFY 0
#define XEMBED_WINDOW_ACTIVATE 1
#define XEMBED_WINDOW_DEACTIVATE 2
#define XEMBED_REQUEST_FOCUS 3
#define XEMBED_FOCUS_IN 3
#define XEMBED_FOCUS_OUT 4
#define XEMBED_FOCUS_NEXT 5
#define XEMBED_FOCUS_PREV 6
static constexpr uint32_t XEMBED_EMBEDDED_NOTIFY = 0;
static constexpr uint32_t XEMBED_WINDOW_ACTIVATE = 1;
static constexpr uint32_t XEMBED_WINDOW_DEACTIVATE = 2;
static constexpr uint32_t XEMBED_REQUEST_FOCUS = 3;
static constexpr uint32_t XEMBED_FOCUS_IN = 3;
static constexpr uint32_t XEMBED_FOCUS_OUT = 4;
static constexpr uint32_t XEMBED_FOCUS_NEXT = 5;
static constexpr uint32_t XEMBED_FOCUS_PREV = 6;
#define XEMBED_FOCUS_CURRENT 0
#define XEMBED_FOCUS_FIRST 1
#define XEMBED_FOCUS_LAST 1
static constexpr uint32_t XEMBED_FOCUS_CURRENT = 0;
static constexpr uint32_t XEMBED_FOCUS_FIRST = 1;
static constexpr uint32_t XEMBED_FOCUS_LAST = 2;
/**
* Max XEMBED version supported.
*/
#define XEMBED_MAX_VERSION UINT32_C(0)
static constexpr uint32_t XEMBED_MAX_VERSION = 0;
/**
* Implementation of parts of the XEMBED spec (as much as needed to get the tray working).
@ -34,28 +34,28 @@ POLYBAR_NS
namespace xembed {
class info {
public:
void set(uint32_t* data);
public:
void set(uint32_t* data);
uint32_t get_version() const;
uint32_t get_flags() const;
uint32_t get_version() const;
uint32_t get_flags() const;
bool is_mapped() const;
bool is_mapped() const;
protected:
uint32_t version;
uint32_t flags;
protected:
uint32_t version;
uint32_t flags;
};
bool query(connection& conn, xcb_window_t win, info& data);
void send_message(connection& conn, xcb_window_t target, long message, long d1, long d2, long d3);
void send_message(connection& conn, xcb_window_t target, uint32_t message, uint32_t d1, uint32_t d2, uint32_t d3);
void send_focus_event(connection& conn, xcb_window_t target);
void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, uint32_t version);
void notify_activated(connection& conn, xcb_window_t win);
void notify_deactivated(connection& conn, xcb_window_t win);
void notify_focused(connection& conn, xcb_window_t win, long focus_type);
void notify_focused(connection& conn, xcb_window_t win, uint32_t focus_type);
void notify_unfocused(connection& conn, xcb_window_t win);
void unembed(connection& conn, xcb_window_t win, xcb_window_t root);
} // namespace xembed
} // namespace xembed
POLYBAR_NS_END

View File

@ -61,8 +61,8 @@ bar::make_type bar::make(loop& loop, bool only_initialize_values) {
* TODO: Break out all tray handling
*/
bar::bar(connection& conn, signal_emitter& emitter, const config& config, const logger& logger, loop& loop,
unique_ptr<screen>&& screen, unique_ptr<tags::dispatch>&& dispatch,
unique_ptr<tags::action_context>&& action_ctxt, bool only_initialize_values)
unique_ptr<screen>&& screen, unique_ptr<tags::dispatch>&& dispatch, unique_ptr<tags::action_context>&& action_ctxt,
bool only_initialize_values)
: m_connection(conn)
, m_sig(emitter)
, m_conf(config)
@ -393,7 +393,7 @@ void bar::parse(string&& data, bool force) {
auto rect = m_opts.inner_area();
if (m_tray && !m_tray->settings().detached && m_tray->settings().num_clients > 0 && !m_tray->settings().adaptive) {
if (m_tray && !m_tray->settings().detached && m_tray->settings().num_mapped_clients > 0 && !m_tray->settings().adaptive) {
auto trayalign = m_tray->settings().align;
auto traywidth = m_tray->settings().win_size.w;
if (trayalign == alignment::LEFT) {

View File

@ -5,24 +5,61 @@
#include "utils/memory.hpp"
#include "x11/connection.hpp"
#include "x11/winspec.hpp"
POLYBAR_NS
tray_client::tray_client(connection& conn, xcb_window_t win, size s) : m_connection(conn), m_window(win), m_size(s) {}
// TODO create wrapper window
tray_client::tray_client(const logger& log, connection& conn, xcb_window_t tray, xcb_window_t win, size s)
: m_log(log), m_connection(conn), m_client(win), m_size(s) {
auto geom = conn.get_geometry(win);
auto attrs = conn.get_window_attributes(win);
int depth = geom->depth;
xcb_visualid_t visual = attrs->visual;
m_log.trace("tray(%s): depth: %u, width: %u, height: %u, visual: 0x%x", conn.id(win), depth, geom->width, geom->height, visual);
// clang-format off
m_wrapper = winspec(conn)
<< cw_size(s.h, s.w)
<< cw_pos(0, 0)
// TODO fix BadMatch error for redshift-gtk window
// << cw_depth(depth)
// << cw_visual(visual)
<< cw_parent(tray)
// TODO add proper pixmap
<< cw_params_back_pixmap(XCB_PIXMAP_NONE)
// << cw_class(XCB_WINDOW_CLASS_INPUT_OUTPUT)
<< cw_params_backing_store(XCB_BACKING_STORE_WHEN_MAPPED)
<< cw_params_event_mask(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
| XCB_EVENT_MASK_PROPERTY_CHANGE
| XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_EXPOSURE)
// << cw_flush(false);
// TODO Make unchecked
<< cw_flush(true);
// clang-format on
}
tray_client::~tray_client() {
if (m_window != XCB_NONE) {
xembed::unembed(m_connection, m_window, m_connection.root());
if (m_client != XCB_NONE) {
xembed::unembed(m_connection, m_client, m_connection.root());
}
if (m_wrapper != XCB_NONE) {
m_connection.destroy_window(m_wrapper);
}
}
tray_client::tray_client(tray_client&& c) : m_connection(c.m_connection), m_size(c.m_size) {
std::swap(m_window, c.m_window);
tray_client::tray_client(tray_client&& c) : m_log(c.m_log), m_connection(c.m_connection), m_size(c.m_size) {
std::swap(m_wrapper, c.m_wrapper);
std::swap(m_client, c.m_client);
}
tray_client& tray_client::operator=(tray_client&& c) {
m_log = c.m_log;
m_connection = c.m_connection;
std::swap(m_window, c.m_window);
std::swap(m_wrapper, c.m_wrapper);
std::swap(m_client, c.m_client);
std::swap(m_size, c.m_size);
return *this;
}
@ -36,14 +73,14 @@ unsigned int tray_client::height() const {
}
void tray_client::clear_window() const {
m_connection.clear_area_checked(1, window(), 0, 0, width(), height());
m_connection.clear_area_checked(1, client(), 0, 0, width(), height());
}
/**
* Match given window against client window
* Is this the client for the given client window
*/
bool tray_client::match(const xcb_window_t& win) const {
return win == m_window;
return win == m_client;
}
/**
@ -60,15 +97,16 @@ void tray_client::mapped(bool state) {
m_mapped = state;
}
/**
* Get client window
*/
xcb_window_t tray_client::window() const {
return m_window;
xcb_window_t tray_client::embedder() const {
return m_wrapper;
}
xcb_window_t tray_client::client() const {
return m_client;
}
void tray_client::query_xembed() {
m_xembed_supported = xembed::query(m_connection, m_window, m_xembed);
m_xembed_supported = xembed::query(m_connection, m_client, m_xembed);
}
bool tray_client::is_xembed_supported() const {
@ -83,6 +121,7 @@ const xembed::info& tray_client::get_xembed() const {
* Make sure that the window mapping state is correct
*/
void tray_client::ensure_state() const {
// TODO correctly map/unmap wrapper
bool should_be_mapped = true;
if (is_xembed_supported()) {
@ -90,9 +129,11 @@ void tray_client::ensure_state() const {
}
if (!mapped() && should_be_mapped) {
m_connection.map_window_checked(window());
m_connection.map_window_checked(embedder());
m_connection.map_window_checked(client());
} else if (mapped() && !should_be_mapped) {
m_connection.unmap_window_checked(window());
m_connection.unmap_window_checked(embedder());
m_connection.unmap_window_checked(client());
}
}
@ -100,6 +141,9 @@ void tray_client::ensure_state() const {
* Configure window size
*/
void tray_client::reconfigure(int x, int y) const {
m_log.trace("tray(%s): moving to (%d, %d)", m_connection.id(client()), x, y);
// TODO correctly reconfigure wrapper + client
uint32_t configure_mask = 0;
std::array<uint32_t, 32> configure_values{};
xcb_params_configure_window_t configure_params{};
@ -109,17 +153,26 @@ void tray_client::reconfigure(int x, int y) const {
XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, x, x);
XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, y, y);
connection::pack_values(configure_mask, &configure_params, configure_values);
m_connection.configure_window_checked(window(), configure_mask, configure_values.data());
m_connection.configure_window_checked(embedder(), configure_mask, configure_values.data());
configure_mask = 0;
XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, width, m_size.w);
XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, height, m_size.h);
XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, x, 0);
XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, y, 0);
connection::pack_values(configure_mask, &configure_params, configure_values);
m_connection.configure_window_checked(client(), configure_mask, configure_values.data());
}
/**
* Respond to client resize requests
* Respond to client resize/move requests
*/
void tray_client::configure_notify(int x, int y) const {
// TODO remove x and y position. The position will always be (0,0)
xcb_configure_notify_event_t notify;
notify.response_type = XCB_CONFIGURE_NOTIFY;
notify.event = m_window;
notify.window = m_window;
notify.event = m_client;
notify.window = m_client;
notify.override_redirect = false;
notify.above_sibling = 0;
notify.x = x;
@ -129,7 +182,7 @@ void tray_client::configure_notify(int x, int y) const {
notify.border_width = 0;
unsigned int mask{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
m_connection.send_event_checked(false, m_window, mask, reinterpret_cast<const char*>(&notify));
m_connection.send_event_checked(false, m_client, mask, reinterpret_cast<const char*>(&notify));
}
POLYBAR_NS_END

View File

@ -265,7 +265,7 @@ void tray_manager::deactivate(bool clear_selection) {
}
m_opts.win_size.w = 0;
m_opts.num_clients = 0;
m_opts.num_mapped_clients = 0;
m_acquired_selection = false;
m_mapped = false;
@ -299,7 +299,7 @@ void tray_manager::reconfigure() {
m_log.err("Failed to reconfigure tray background (%s)", err.what());
}
m_opts.num_clients = mapped_clients();
m_opts.num_mapped_clients = mapped_clients();
guard.unlock();
refresh_window();
m_connection.flush();
@ -432,8 +432,8 @@ void tray_manager::refresh_window() {
client.clear_window();
}
} catch (const std::exception& e) {
m_log.err("Failed to clear tray client %s '%s' (%s)", m_connection.id(client.window()),
ewmh_util::get_wm_name(client.window()), e.what());
m_log.err("Failed to clear tray client %s '%s' (%s)", ewmh_util::get_wm_name(client.client()),
m_connection.id(client.client()), e.what());
}
}
@ -480,7 +480,8 @@ void tray_manager::create_window() {
<< cw_params_event_mask(XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT
|XCB_EVENT_MASK_STRUCTURE_NOTIFY
|XCB_EVENT_MASK_EXPOSURE)
<< cw_params_override_redirect(true)
// TODO
// << cw_params_override_redirect(true)
<< cw_parent(m_opts.bar_window);
// clang-format on
@ -699,10 +700,7 @@ void tray_manager::track_selection_owner(xcb_window_t owner) {
void tray_manager::process_docking_request(xcb_window_t win) {
m_log.info("Processing docking request from '%s' (%s)", ewmh_util::get_wm_name(win), m_connection.id(win));
tray_client client(m_connection, win, m_opts.client_size);
auto geom = m_connection.get_geometry(win);
m_log.trace("tray: depth: %u, width: %u, height: %u", geom->depth, geom->width, geom->height);
tray_client client(m_log, m_connection, m_tray, win, m_opts.client_size);
try {
client.query_xembed();
@ -722,29 +720,31 @@ void tray_manager::process_docking_request(xcb_window_t win) {
const uint32_t value = XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY;
m_log.trace("tray: Update client window");
m_connection.change_window_attributes_checked(client.window(), mask, &value);
m_connection.change_window_attributes_checked(client.client(), mask, &value);
m_log.trace("tray: Configure client size");
client.reconfigure(0, 0);
// TODO properly support tray icon backgrounds
auto p = XCB_BACK_PIXMAP_NONE;
m_connection.change_window_attributes_checked(client.window(), XCB_CW_BACK_PIXMAP, &p);
// TODO put this into tray_client class
m_log.trace("tray: Reparent client");
m_connection.reparent_window_checked(
client.window(), m_tray, calculate_client_x(client.window()), calculate_client_y());
client.client(), client.embedder(), calculate_client_x(client.client()), calculate_client_y());
m_log.trace("tray: Add client window to the save set");
m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client.window());
// TODO move this into tray_client
m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client.client());
if (client.is_xembed_supported()) {
m_log.trace("tray: Send embbeded notification to client");
xembed::notify_embedded(m_connection, client.window(), m_tray, client.get_xembed().get_version());
// TODO move this into tray_client
xembed::notify_embedded(m_connection, client.client(), client.embedder(), client.get_xembed().get_version());
}
if (!client.is_xembed_supported() || client.get_xembed().is_mapped()) {
m_log.trace("tray: Map client");
m_connection.map_window_checked(client.window());
// TODO move this into tray_client
m_connection.map_window_checked(client.client());
m_connection.map_window_checked(client.embedder());
}
} catch (const std::exception& err) {
m_log.err("Failed to setup tray client '%s' (%s) removing... (%s)", ewmh_util::get_wm_name(win),
@ -840,7 +840,7 @@ tray_client* tray_manager::find_client(const xcb_window_t& win) {
* Remove tray client
*/
void tray_manager::remove_client(const tray_client& client, bool reconfigure) {
remove_client(client.window(), reconfigure);
remove_client(client.client(), reconfigure);
}
/**
@ -993,8 +993,6 @@ void tray_manager::handle(const evt::property_notify& evt) {
m_log.trace("tray: _XEMBED_INFO: %s", m_connection.id(evt->window));
auto win = client->window();
if (evt->state == XCB_PROPERTY_NEW_VALUE) {
m_log.trace("tray: _XEMBED_INFO value has changed");
}
@ -1003,7 +1001,7 @@ void tray_manager::handle(const evt::property_notify& evt) {
client->query_xembed();
} catch (const xpp::x::error::window& err) {
m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
remove_client(win, true);
remove_client(*client, true);
return;
}
@ -1019,9 +1017,19 @@ void tray_manager::handle(const evt::property_notify& evt) {
* Event callback : XCB_REPARENT_NOTIFY
*/
void tray_manager::handle(const evt::reparent_notify& evt) {
if (m_activated && is_embedded(evt->window) && evt->parent != m_tray) {
if (!m_activated) {
return;
}
auto client = find_client(evt->window);
if (!client) {
return;
}
if (evt->parent != client->embedder()) {
m_log.trace("tray: Received reparent_notify for client, remove...");
remove_client(evt->window);
remove_client(*client);
}
}
@ -1054,7 +1062,7 @@ void tray_manager::handle(const evt::map_notify& evt) {
m_log.trace("tray: Received map_notify");
m_log.trace("tray: Set client mapped");
find_client(evt->window)->mapped(true);
if (mapped_clients() > m_opts.num_clients) {
if (mapped_clients() > m_opts.num_mapped_clients) {
reconfigure();
}
}

View File

@ -48,7 +48,7 @@ namespace xembed {
/**
* Send _XEMBED messages
*/
void send_message(connection& conn, xcb_window_t target, long message, long d1, long d2, long d3) {
void send_message(connection& conn, xcb_window_t target, uint32_t message, uint32_t d1, uint32_t d2, uint32_t d3) {
auto msg = conn.make_client_message(_XEMBED, target);
msg.data.data32[0] = XCB_CURRENT_TIME;
msg.data.data32[1] = message;
@ -92,7 +92,7 @@ namespace xembed {
/**
* Send window focused notification
*/
void notify_focused(connection& conn, xcb_window_t win, long focus_type) {
void notify_focused(connection& conn, xcb_window_t win, uint32_t focus_type) {
send_message(conn, win, XEMBED_FOCUS_IN, focus_type, 0, 0);
}