tray: Properly handle missing _XEMBED_INFO

The icon not supporting XEMBED is not fatal, we just treat is as always
mapped.

Fixes #2479
Fixes #2442
This commit is contained in:
patrick96 2021-09-08 18:13:21 +02:00 committed by Patrick Ziegler
parent eb9c192d04
commit 6136c08d42
7 changed files with 120 additions and 55 deletions

View File

@ -9,6 +9,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Fixed
- The tray mistakenly removed tray icons that did not support XEMBED
([`#2479`](https://github.com/polybar/polybar/issues/2479),
[`#2442`](https://github.com/polybar/polybar/issues/2442))
## [3.5.6] - 2021-05-24
### Build
- Support building documentation on sphinx 4.0 ([`#2424`](https://github.com/polybar/polybar/issues/2424))

View File

@ -4,12 +4,12 @@
#include "common.hpp"
#include "utils/concurrency.hpp"
#include "x11/xembed.hpp"
POLYBAR_NS
// fwd declarations
class connection;
struct xembed_data;
class tray_client {
public:
@ -28,7 +28,10 @@ class tray_client {
void mapped(bool state);
xcb_window_t window() const;
xembed_data* xembed() const;
void query_xembed();
bool is_xembed_supported() const;
const xembed::info& get_xembed() const;
void ensure_state() const;
void reconfigure(int x, int y) const;
@ -38,7 +41,18 @@ class tray_client {
connection& m_connection;
xcb_window_t m_window{0};
shared_ptr<xembed_data> m_xembed;
/**
* Whether the client window supports XEMBED.
*
* A tray client can still work when it doesn't support XEMBED.
*/
bool m_xembed_supported{false};
/**
* _XEMBED_INFO of the client window
*/
xembed::info m_xembed;
bool m_mapped{false};
unsigned int m_width;

View File

@ -34,7 +34,6 @@ using namespace std::chrono_literals;
// fwd declarations
class connection;
struct xembed_data;
class background_manager;
class bg_slice;

View File

@ -21,24 +21,41 @@ POLYBAR_NS
#define XEMBED_FOCUS_FIRST 1
#define XEMBED_FOCUS_LAST 1
struct xembed_data {
unsigned long version;
unsigned long flags;
xcb_timestamp_t time;
xcb_atom_t xembed;
xcb_atom_t xembed_info;
/**
* Max XEMBED version supported.
*/
#define XEMBED_MAX_VERSION UINT32_C(0)
/**
* Implementation of parts of the XEMBED spec (as much as needed to get the tray working).
*
* Ref: https://specifications.freedesktop.org/xembed-spec/xembed-spec-latest.html
*/
namespace xembed {
class info {
public:
void set(uint32_t* data);
uint32_t get_version() const;
uint32_t get_flags() const;
bool is_mapped() const;
protected:
uint32_t version;
uint32_t flags;
};
namespace xembed {
xembed_data* query(connection& conn, xcb_window_t win, xembed_data* data);
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_focus_event(connection& conn, xcb_window_t target);
void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, long version);
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_unfocused(connection& conn, xcb_window_t win);
void unembed(connection& conn, xcb_window_t win, xcb_window_t root);
}
} // namespace xembed
POLYBAR_NS_END

View File

@ -5,16 +5,11 @@
#include "utils/memory.hpp"
#include "x11/connection.hpp"
#include "x11/xembed.hpp"
POLYBAR_NS
tray_client::tray_client(connection& conn, xcb_window_t win, unsigned int w, unsigned int h)
: m_connection(conn), m_window(win), m_width(w), m_height(h) {
m_xembed = memory_util::make_malloc_ptr<xembed_data>();
m_xembed->version = XEMBED_VERSION;
m_xembed->flags = XEMBED_MAPPED;
}
: m_connection(conn), m_window(win), m_width(w), m_height(h) {}
tray_client::~tray_client() {
xembed::unembed(m_connection, window(), m_connection.root());
@ -60,20 +55,31 @@ xcb_window_t tray_client::window() const {
return m_window;
}
/**
* Get xembed data pointer
*/
xembed_data* tray_client::xembed() const {
return m_xembed.get();
void tray_client::query_xembed() {
m_xembed_supported = xembed::query(m_connection, m_window, m_xembed);
}
bool tray_client::is_xembed_supported() const {
return m_xembed_supported;
}
const xembed::info& tray_client::get_xembed() const {
return m_xembed;
}
/**
* Make sure that the window mapping state is correct
*/
void tray_client::ensure_state() const {
if (!mapped() && ((xembed()->flags & XEMBED_MAPPED) == XEMBED_MAPPED)) {
bool should_be_mapped = true;
if (is_xembed_supported()) {
should_be_mapped = m_xembed.is_mapped();
}
if (!mapped() && should_be_mapped) {
m_connection.map_window_checked(window());
} else if (mapped() && ((xembed()->flags & XEMBED_MAPPED) != XEMBED_MAPPED)) {
} else if (mapped() && !should_be_mapped) {
m_connection.unmap_window_checked(window());
}
}

View File

@ -21,6 +21,12 @@
#include "x11/winspec.hpp"
#include "x11/xembed.hpp"
/*
* Tray implementation according to the System Tray Protocol.
*
* Ref: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
*/
// ====================================================================================================
//
// TODO: 32-bit visual
@ -740,14 +746,20 @@ void tray_manager::process_docking_request(xcb_window_t win) {
auto& client = m_clients.back();
try {
m_log.trace("tray: Get client _XEMBED_INFO");
xembed::query(m_connection, win, client->xembed());
} catch (const std::exception& err) {
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);
return;
}
m_log.trace("tray: xembed = %s", client->is_xembed_supported() ? "true" : "false");
if (client->is_xembed_supported()) {
m_log.trace("tray: version = 0x%x, flags = 0x%x, XEMBED_MAPPED = %s", client->get_xembed().get_version(),
client->get_xembed().get_flags(), client->get_xembed().is_mapped() ? "true" : "false");
}
try {
const unsigned int mask = XCB_CW_EVENT_MASK;
const unsigned int values[]{XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
@ -765,10 +777,12 @@ void tray_manager::process_docking_request(xcb_window_t win) {
m_connection.reparent_window_checked(
client->window(), m_tray, calculate_client_x(client->window()), calculate_client_y());
if (client->is_xembed_supported()) {
m_log.trace("tray: Send embbeded notification to client");
xembed::notify_embedded(m_connection, client->window(), m_tray, client->xembed()->version);
xembed::notify_embedded(m_connection, client->window(), m_tray, client->get_xembed().get_version());
}
if (client->xembed()->flags & XEMBED_MAPPED) {
if (!client->is_xembed_supported() || client->get_xembed().is_mapped()) {
m_log.trace("tray: Map client");
m_connection.map_window_checked(client->window());
}
@ -1019,7 +1033,6 @@ void tray_manager::handle(const evt::property_notify& evt) {
m_log.trace("tray: _XEMBED_INFO: %s", m_connection.id(evt->window));
auto xd = client->xembed();
auto win = client->window();
if (evt->state == XCB_PROPERTY_NEW_VALUE) {
@ -1027,20 +1040,17 @@ void tray_manager::handle(const evt::property_notify& evt) {
}
try {
m_log.trace("tray: Get client _XEMBED_INFO");
xembed::query(m_connection, win, xd);
} catch (const application_error& err) {
m_log.err(err.what());
return;
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);
return;
}
m_log.trace("tray: _XEMBED_INFO[0]=%u _XEMBED_INFO[1]=%u", xd->version, xd->flags);
m_log.trace("tray: version = 0x%x, flags = 0x%x, XEMBED_MAPPED = %s", client->get_xembed().get_version(),
client->get_xembed().get_flags(), client->get_xembed().is_mapped() ? "true" : "false");
if ((client->xembed()->flags & XEMBED_MAPPED) & XEMBED_MAPPED) {
if (client->get_xembed().is_mapped()) {
reconfigure();
}
}

View File

@ -5,26 +5,40 @@
POLYBAR_NS
namespace xembed {
/**
* Query _XEMBED_INFO for the given window
*/
xembed_data* query(connection& conn, xcb_window_t win, xembed_data* data) {
auto info = conn.get_property(false, win, _XEMBED_INFO, XCB_GET_PROPERTY_TYPE_ANY, 0L, 2);
if (info->value_len == 0) {
throw application_error("Invalid _XEMBED_INFO for window " + conn.id(win));
void info::set(uint32_t* data) {
version = data[0];
flags = data[1];
}
std::vector<unsigned int> xembed_data{info.value<unsigned int>().begin(), info.value<unsigned int>().end()};
uint32_t info::get_version() const {
return version;
}
data->xembed = _XEMBED;
data->xembed_info = _XEMBED_INFO;
uint32_t info::get_flags() const {
return flags;
}
data->time = XCB_CURRENT_TIME;
data->flags = xembed_data[1];
data->version = xembed_data[0];
bool info::is_mapped() const {
return (flags & XEMBED_MAPPED) == XEMBED_MAPPED;
}
return data;
/**
* Query _XEMBED_INFO for the given window
*
* \return Whether valid XEMBED info was found
*/
bool query(connection& conn, xcb_window_t win, info& data) {
auto info = conn.get_property(false, win, _XEMBED_INFO, _XEMBED_INFO, 0L, 2);
if (info->value_len == 0) {
return false;
}
std::vector<uint32_t> xembed_data{info.value<uint32_t>().begin(), info.value<uint32_t>().end()};
data.set(xembed_data.data());
return true;
}
/**
@ -53,8 +67,8 @@ namespace xembed {
/**
* Acknowledge window embedding
*/
void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, long version) {
send_message(conn, win, XEMBED_EMBEDDED_NOTIFY, 0, embedder, version);
void notify_embedded(connection& conn, xcb_window_t win, xcb_window_t embedder, uint32_t version) {
send_message(conn, win, XEMBED_EMBEDDED_NOTIFY, 0, embedder, std::min(version, XEMBED_MAX_VERSION));
}
/**