From ca25b5685c6eb6ee947406b52f7e7796cf7acbcb Mon Sep 17 00:00:00 2001
From: patrick96
Date: Mon, 31 Oct 2022 23:05:23 +0100
Subject: [PATCH] tray: Add back legacy tray implementation
Is used for trays defined through tray-position and nothing else.
---
include/components/bar.hpp | 6 +-
include/x11/legacy_tray_manager.hpp | 236 +++++
src/CMakeLists.txt | 1 +
src/components/bar.cpp | 34 +-
src/x11/legacy_tray_manager.cpp | 1292 +++++++++++++++++++++++++++
5 files changed, 1549 insertions(+), 20 deletions(-)
create mode 100644 include/x11/legacy_tray_manager.hpp
create mode 100644 src/x11/legacy_tray_manager.cpp
diff --git a/include/components/bar.hpp b/include/components/bar.hpp
index 3e5f3f1a..43e9b2f5 100644
--- a/include/components/bar.hpp
+++ b/include/components/bar.hpp
@@ -23,10 +23,12 @@ class connection;
class logger;
class renderer;
class screen;
+namespace legacy_tray {
class tray_manager;
+}
namespace tags {
- class dispatch;
+class dispatch;
}
// }}}
@@ -92,7 +94,7 @@ class bar : public xpp::event::sink m_screen;
- unique_ptr m_tray;
+ unique_ptr m_tray;
unique_ptr m_renderer;
unique_ptr m_dispatch;
unique_ptr m_action_ctxt;
diff --git a/include/x11/legacy_tray_manager.hpp b/include/x11/legacy_tray_manager.hpp
new file mode 100644
index 00000000..996dcece
--- /dev/null
+++ b/include/x11/legacy_tray_manager.hpp
@@ -0,0 +1,236 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "common.hpp"
+#include "components/logger.hpp"
+#include "components/types.hpp"
+#include "events/signal_fwd.hpp"
+#include "events/signal_receiver.hpp"
+#include "utils/concurrency.hpp"
+#include "x11/atoms.hpp"
+#include "x11/connection.hpp"
+#include "x11/xembed.hpp"
+
+#define _NET_SYSTEM_TRAY_ORIENTATION_HORZ 0
+#define _NET_SYSTEM_TRAY_ORIENTATION_VERT 1
+
+#define SYSTEM_TRAY_REQUEST_DOCK 0
+#define SYSTEM_TRAY_BEGIN_MESSAGE 1
+#define SYSTEM_TRAY_CANCEL_MESSAGE 2
+
+#define TRAY_WM_NAME "Polybar tray window"
+#define TRAY_WM_CLASS "tray\0Polybar"
+
+POLYBAR_NS
+
+// fwd declarations
+class connection;
+class background_manager;
+class bg_slice;
+
+namespace legacy_tray {
+
+namespace chrono = std::chrono;
+using namespace std::chrono_literals;
+using std::atomic;
+
+enum class tray_postition { NONE = 0, LEFT, CENTER, RIGHT, MODULE };
+
+struct tray_settings {
+ tray_postition tray_position{tray_postition::NONE};
+ bool running{false};
+ int rel_x{0};
+ int rel_y{0};
+ int orig_x{0};
+ int orig_y{0};
+ int configured_x{0};
+ int configured_y{0};
+ unsigned int configured_w{0U};
+ unsigned int configured_h{0U};
+ unsigned int configured_slots{0U};
+ unsigned int width{0U};
+ unsigned int width_max{0U};
+ unsigned int height{0U};
+ unsigned int height_fill{0U};
+ unsigned int spacing{0U};
+ unsigned int sibling{0U};
+ rgba background{};
+ rgba foreground{};
+ bool transparent{false};
+ bool detached{false};
+};
+
+class tray_client {
+ public:
+ explicit tray_client(connection& conn, xcb_window_t win, unsigned int w, unsigned int h);
+ tray_client(const tray_client& c) = delete;
+ tray_client& operator=(tray_client& c) = delete;
+
+ ~tray_client();
+
+ unsigned int width() const;
+ unsigned int height() const;
+ void clear_window() const;
+
+ bool match(const xcb_window_t& win) const;
+ bool mapped() const;
+ void mapped(bool state);
+
+ xcb_window_t window() 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;
+ void configure_notify(int x, int y) const;
+
+ protected:
+ connection& m_connection;
+ xcb_window_t m_window{0};
+
+ /**
+ * 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;
+ unsigned int m_height;
+};
+
+class tray_manager
+ : public xpp::event::sink,
+ public signal_receiver,
+ public non_copyable_mixin,
+ public non_movable_mixin {
+ public:
+ using make_type = unique_ptr;
+ static make_type make(const bar_settings& settings);
+
+ explicit tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back,
+ const bar_settings& settings);
+
+ ~tray_manager();
+
+ const tray_settings settings() const;
+
+ void setup(const string& tray_module_name);
+ void activate();
+ void activate_delayed(chrono::duration delay = 1s);
+ void deactivate(bool clear_selection = true);
+ void reconfigure();
+
+ protected:
+ void reconfigure_window();
+ void reconfigure_clients();
+ void reconfigure_bg(bool realloc = false);
+ void refresh_window();
+ void redraw_window(bool realloc_bg = false);
+
+ void query_atom();
+ void create_window();
+ void create_bg(bool realloc = false);
+ void restack_window();
+ void set_wm_hints();
+ void set_tray_colors();
+
+ void acquire_selection();
+ void notify_clients();
+ void notify_clients_delayed();
+
+ void track_selection_owner(xcb_window_t owner);
+ void process_docking_request(xcb_window_t win);
+
+ int calculate_x(unsigned width) const;
+ int calculate_y(bool abspos = true) 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();
+
+ bool is_embedded(const xcb_window_t& win) const;
+ shared_ptr find_client(const xcb_window_t& win) const;
+ void remove_client(shared_ptr& client, bool reconfigure = true);
+ void remove_client(xcb_window_t win, bool reconfigure = true);
+ unsigned int mapped_clients() const;
+ bool change_visibility(bool visible);
+
+ void handle(const evt::expose& evt) override;
+ void handle(const evt::visibility_notify& evt) override;
+ void handle(const evt::client_message& evt) override;
+ void handle(const evt::configure_request& evt) override;
+ void handle(const evt::resize_request& evt) override;
+ void handle(const evt::selection_clear& evt) override;
+ void handle(const evt::property_notify& evt) override;
+ void handle(const evt::reparent_notify& evt) override;
+ void handle(const evt::destroy_notify& evt) override;
+ void handle(const evt::map_notify& evt) override;
+ void handle(const evt::unmap_notify& evt) override;
+
+ bool on(const signals::ui::visibility_change& evt) override;
+ bool on(const signals::ui::dim_window& evt) override;
+ bool on(const signals::ui::update_background& evt) override;
+ bool on(const signals::ui_tray::tray_pos_change& evt) override;
+ bool on(const signals::ui_tray::tray_visibility& evt) override;
+
+ private:
+ connection& m_connection;
+ signal_emitter& m_sig;
+ const logger& m_log;
+ background_manager& m_background_manager;
+ std::shared_ptr m_bg_slice;
+ vector> m_clients;
+
+ tray_settings m_opts{};
+
+ xcb_gcontext_t m_gc{0};
+ xcb_pixmap_t m_pixmap{0};
+ unique_ptr m_surface;
+ unique_ptr m_context;
+
+ unsigned int m_prevwidth{0U};
+ unsigned int m_prevheight{0U};
+
+ xcb_atom_t m_atom{0};
+ xcb_window_t m_tray{0};
+ xcb_window_t m_othermanager{0};
+
+ atomic m_activated{false};
+ atomic m_mapped{false};
+ atomic m_hidden{false};
+ atomic m_acquired_selection{false};
+
+ thread m_delaythread;
+
+ mutex m_mtx{};
+
+ bool m_firstactivation{true};
+
+ const bar_settings& m_bar_opts;
+};
+
+} // namespace legacy_tray
+
+POLYBAR_NS_END
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d879ae43..f68cb8df 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -132,6 +132,7 @@ set(POLY_SOURCES
${src_dir}/x11/extensions/randr.cpp
${src_dir}/x11/icccm.cpp
${src_dir}/x11/registry.cpp
+ ${src_dir}/x11/legacy_tray_manager.cpp
${src_dir}/x11/tray_client.cpp
${src_dir}/x11/tray_manager.cpp
${src_dir}/x11/window.cpp
diff --git a/src/components/bar.cpp b/src/components/bar.cpp
index 3a08e03f..4852c7e1 100644
--- a/src/components/bar.cpp
+++ b/src/components/bar.cpp
@@ -20,7 +20,7 @@
#include "x11/ewmh.hpp"
#include "x11/extensions/all.hpp"
#include "x11/icccm.hpp"
-#include "x11/tray_manager.hpp"
+#include "x11/legacy_tray_manager.hpp"
#if WITH_XCURSOR
#include "x11/cursor.hpp"
@@ -57,8 +57,6 @@ bar::make_type bar::make(loop& loop, bool only_initialize_values) {
/**
* Construct bar instance
- *
- * TODO: Break out all tray handling
*/
bar::bar(connection& conn, signal_emitter& emitter, const config& config, const logger& logger, loop& loop,
unique_ptr&& screen, unique_ptr&& dispatch, unique_ptr&& action_ctxt,
@@ -73,7 +71,7 @@ bar::bar(connection& conn, signal_emitter& emitter, const config& config, const
, m_action_ctxt(forward(action_ctxt)) {
string bs{m_conf.section()};
- // m_tray = tray_manager::make(m_opts);
+ m_tray = legacy_tray::tray_manager::make(m_opts);
// Get available RandR outputs
auto monitor_name = m_conf.get(bs, "monitor", ""s);
@@ -388,20 +386,16 @@ void bar::parse(string&& data, bool force) {
auto rect = m_opts.inner_area();
- // TODO use legacy tray implementation
-#if 0
- if (m_tray && !m_tray->settings().detached && m_tray->settings().num_mapped_clients > 0 &&
- m_tray->settings().tray_position != tray_postition::MODULE) {
+ if (m_tray && !m_tray->settings().detached && m_tray->settings().configured_slots) {
auto tray_pos = m_tray->settings().tray_position;
- auto traywidth = m_tray->settings().win_size.w;
- if (tray_pos == tray_postition::LEFT) {
+ auto traywidth = m_tray->settings().configured_w;
+ if (tray_pos == legacy_tray::tray_postition::LEFT) {
rect.x += traywidth;
rect.width -= traywidth;
- } else if (tray_pos == tray_postition::RIGHT) {
+ } else if (tray_pos == legacy_tray::tray_postition::RIGHT) {
rect.width -= traywidth;
}
}
-#endif
m_log.info("Redrawing bar window");
m_renderer->begin(rect);
@@ -840,10 +834,9 @@ void bar::handle(const evt::button_press& evt) {
*/
void bar::handle(const evt::expose& evt) {
if (evt->window == m_opts.x_data.window && evt->count == 0) {
- // TODO
- // if (m_tray->running()) {
- // broadcast_visibility();
- // }
+ if (m_tray && m_tray->settings().running) {
+ broadcast_visibility();
+ }
m_log.trace("bar: Received expose event");
m_renderer->flush();
@@ -880,7 +873,7 @@ void bar::handle(const evt::configure_notify& evt) {
m_sig.emit(signals::ui::update_geometry{});
}
-void bar::start(const string&) {
+void bar::start(const string& tray_module_name) {
m_log.trace("bar: Create renderer");
m_renderer = renderer::make(m_opts, *m_action_ctxt);
@@ -911,7 +904,12 @@ void bar::start(const string&) {
m_renderer->end();
m_log.trace("bar: Setup tray manager");
- // m_tray->setup(tray_module_name);
+ m_tray->setup(tray_module_name);
+
+ if (m_tray->settings().tray_position == legacy_tray::tray_postition::MODULE ||
+ m_tray->settings().tray_position == legacy_tray::tray_postition::NONE) {
+ m_tray.reset();
+ }
broadcast_visibility();
}
diff --git a/src/x11/legacy_tray_manager.cpp b/src/x11/legacy_tray_manager.cpp
new file mode 100644
index 00000000..14888270
--- /dev/null
+++ b/src/x11/legacy_tray_manager.cpp
@@ -0,0 +1,1292 @@
+#include "x11/legacy_tray_manager.hpp"
+
+#include
+#include
+#include
+
+#include
+
+#include "cairo/context.hpp"
+#include "cairo/surface.hpp"
+#include "components/config.hpp"
+#include "errors.hpp"
+#include "events/signal.hpp"
+#include "utils/color.hpp"
+#include "utils/factory.hpp"
+#include "utils/math.hpp"
+#include "utils/memory.hpp"
+#include "utils/process.hpp"
+#include "utils/units.hpp"
+#include "x11/background_manager.hpp"
+#include "x11/connection.hpp"
+#include "x11/ewmh.hpp"
+#include "x11/icccm.hpp"
+#include "x11/window.hpp"
+#include "x11/winspec.hpp"
+#include "x11/xembed.hpp"
+
+/**
+ * Legacy tray implementation that does not run as part of a module.
+ */
+
+POLYBAR_NS
+
+namespace legacy_tray {
+
+/**
+ * Create instance
+ */
+tray_manager::make_type tray_manager::make(const bar_settings& settings) {
+ return std::make_unique(
+ connection::make(), signal_emitter::make(), logger::make(), background_manager::make(), settings);
+}
+
+tray_manager::tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back,
+ const bar_settings& settings)
+ : m_connection(conn), m_sig(emitter), m_log(logger), m_background_manager(back), m_bar_opts(settings) {
+ m_connection.attach_sink(this, SINK_PRIORITY_TRAY);
+}
+
+tray_manager::~tray_manager() {
+ if (m_delaythread.joinable()) {
+ m_delaythread.join();
+ }
+ m_connection.detach_sink(this, SINK_PRIORITY_TRAY);
+ deactivate();
+}
+
+void tray_manager::setup(const string& tray_module_name) {
+ const config& conf = config::make();
+ auto bs = conf.section();
+ string position = conf.get(bs, "tray-position", "none"s);
+
+ if (!position.empty() && position != "none" && !tray_module_name.empty()) {
+ m_log.err(
+ "The tray position is manually defined (`tray-position`) and also set by the tray module (%s). `tray-position` "
+ "will be ignored",
+ tray_module_name);
+ }
+
+ if (!tray_module_name.empty()) {
+ m_opts.tray_position = tray_postition::MODULE;
+ return;
+ } else if (position == "left") {
+ m_opts.tray_position = tray_postition::LEFT;
+ } else if (position == "right") {
+ m_opts.tray_position = tray_postition::RIGHT;
+ } else if (position == "center") {
+ m_opts.tray_position = tray_postition::CENTER;
+ } else if (position != "none") {
+ return m_log.err("Disabling tray manager (reason: Invalid position \"" + position + "\")");
+ } else {
+ return;
+ }
+
+ m_opts.detached = conf.get(bs, "tray-detached", false);
+ m_opts.height = m_bar_opts.size.h;
+ m_opts.height -= m_bar_opts.borders.at(edge::BOTTOM).size;
+ m_opts.height -= m_bar_opts.borders.at(edge::TOP).size;
+ m_opts.height_fill = m_opts.height;
+
+ if (m_opts.height % 2 != 0) {
+ m_opts.height--;
+ }
+
+ auto maxsize = conf.get(bs, "tray-maxsize", 16);
+ if (m_opts.height > maxsize) {
+ m_opts.spacing += (m_opts.height - maxsize) / 2;
+ m_opts.height = maxsize;
+ }
+
+ m_opts.width_max = m_bar_opts.size.w;
+ m_opts.width = m_opts.height;
+ m_opts.orig_y = m_bar_opts.pos.y + m_bar_opts.borders.at(edge::TOP).size;
+
+ // Apply user-defined scaling
+ auto scale = conf.get(bs, "tray-scale", 1.0);
+ m_opts.width *= scale;
+ m_opts.height_fill *= scale;
+
+ auto inner_area = m_bar_opts.inner_area(true);
+
+ switch (m_opts.tray_position) {
+ case tray_postition::NONE:
+ break;
+ case tray_postition::LEFT:
+ m_opts.orig_x = inner_area.x;
+ break;
+ case tray_postition::CENTER:
+ m_opts.orig_x = inner_area.x + inner_area.width / 2 - m_opts.width / 2;
+ break;
+ case tray_postition::RIGHT:
+ m_opts.orig_x = inner_area.x + inner_area.width;
+ break;
+ case tray_postition::MODULE:
+ break;
+ }
+
+ if (conf.has(bs, "tray-transparent")) {
+ m_log.warn("tray-transparent is deprecated, the tray always uses pseudo-transparency. Please remove it.");
+ }
+
+ // Set user-defined foreground and background colors.
+ m_opts.background = conf.get(bs, "tray-background", m_bar_opts.background);
+ m_opts.foreground = conf.get(bs, "tray-foreground", m_bar_opts.foreground);
+
+ if (m_opts.background.alpha_i() != 255) {
+ m_log.trace("tray: enable transparency");
+ m_opts.transparent = true;
+ }
+
+ // Add user-defined padding
+ m_opts.spacing += conf.get(bs, "tray-padding", 0);
+
+ // Add user-defiend offset
+ auto offset_x = conf.get(bs, "tray-offset-x", percentage_with_offset{});
+ auto offset_y = conf.get(bs, "tray-offset-y", percentage_with_offset{});
+
+ int max_x;
+ int max_y;
+
+ if (m_opts.detached) {
+ max_x = m_bar_opts.monitor->w;
+ max_y = m_bar_opts.monitor->h;
+ } else {
+ max_x = inner_area.width;
+ max_y = inner_area.height;
+ }
+
+ m_opts.orig_x += units_utils::percentage_with_offset_to_pixel(offset_x, max_x, m_bar_opts.dpi_x);
+ m_opts.orig_y += units_utils::percentage_with_offset_to_pixel(offset_y, max_y, m_bar_opts.dpi_y);
+ ;
+ m_opts.rel_x = m_opts.orig_x - m_bar_opts.pos.x;
+ m_opts.rel_y = m_opts.orig_y - m_bar_opts.pos.y;
+
+ // Put the tray next to the bar in the window stack
+ m_opts.sibling = m_bar_opts.x_data.window;
+
+ // Activate the tray manager
+ query_atom();
+ activate();
+}
+
+/**
+ * Get the settings container
+ */
+const tray_settings tray_manager::settings() const {
+ return m_opts;
+}
+
+/**
+ * Activate systray management
+ */
+void tray_manager::activate() {
+ if (m_activated) {
+ return;
+ }
+
+ m_log.info("Activating tray manager");
+ m_activated = true;
+ m_opts.running = true;
+
+ m_sig.attach(this);
+
+ try {
+ create_window();
+ create_bg();
+ restack_window();
+ set_wm_hints();
+ set_tray_colors();
+ } catch (const exception& err) {
+ m_log.err(err.what());
+ m_log.err("Cannot activate tray manager... failed to setup window");
+ m_activated = false;
+ return;
+ }
+
+ // Attempt to get control of the systray selection then
+ // notify clients waiting for a manager.
+ acquire_selection();
+
+ if (!m_acquired_selection) {
+ deactivate();
+ return;
+ }
+
+ // Send delayed notification
+ if (!m_firstactivation) {
+ notify_clients();
+ } else {
+ notify_clients_delayed();
+ }
+
+ m_firstactivation = false;
+}
+
+/**
+ * Deactivate systray management
+ */
+void tray_manager::deactivate(bool clear_selection) {
+ if (!m_activated) {
+ return;
+ }
+
+ m_log.info("Deactivating tray manager");
+ m_activated = false;
+ m_opts.running = false;
+
+ m_sig.detach(this);
+
+ if (!m_connection.connection_has_error() && clear_selection && m_acquired_selection) {
+ m_log.trace("tray: Unset selection owner");
+ m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
+ }
+
+ m_log.trace("tray: Unembed clients");
+ m_clients.clear();
+
+ if (m_tray) {
+ m_log.trace("tray: Destroy window");
+ m_connection.destroy_window(m_tray);
+ }
+ m_context.reset();
+ m_surface.reset();
+ if (m_pixmap) {
+ m_connection.free_pixmap(m_pixmap);
+ }
+ if (m_gc) {
+ m_connection.free_gc(m_pixmap);
+ }
+
+ m_tray = 0;
+ m_pixmap = 0;
+ m_gc = 0;
+ m_prevwidth = 0;
+ m_prevheight = 0;
+ m_opts.configured_x = 0;
+ m_opts.configured_y = 0;
+ m_opts.configured_w = 0;
+ m_opts.configured_h = 0;
+ m_opts.configured_slots = 0;
+ m_acquired_selection = false;
+ m_mapped = false;
+
+ m_connection.flush();
+
+ m_sig.emit(signals::eventqueue::notify_forcechange{});
+}
+
+/**
+ * Reconfigure tray
+ */
+void tray_manager::reconfigure() {
+ if (!m_tray) {
+ return;
+ } else if (m_mtx.try_lock()) {
+ std::unique_lock guard(m_mtx, std::adopt_lock);
+
+ try {
+ reconfigure_clients();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray clients (%s)", err.what());
+ }
+ try {
+ reconfigure_window();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray window (%s)", err.what());
+ }
+ try {
+ reconfigure_bg();
+ } catch (const exception& err) {
+ m_log.err("Failed to reconfigure tray background (%s)", err.what());
+ }
+
+ m_opts.configured_slots = mapped_clients();
+ guard.unlock();
+ refresh_window();
+ m_connection.flush();
+ }
+
+ m_sig.emit(signals::eventqueue::notify_forcechange{});
+}
+
+/**
+ * Reconfigure container window
+ */
+void tray_manager::reconfigure_window() {
+ m_log.trace("tray: Reconfigure window (mapped=%i, clients=%i)", static_cast(m_mapped), m_clients.size());
+
+ if (!m_tray) {
+ return;
+ }
+
+ auto clients = mapped_clients();
+ if (!clients && m_mapped) {
+ m_log.trace("tray: Reconfigure window / unmap");
+ m_connection.unmap_window_checked(m_tray);
+ } else if (clients && !m_mapped && !m_hidden) {
+ m_log.trace("tray: Reconfigure window / map");
+ m_connection.map_window_checked(m_tray);
+ }
+
+ 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);
+
+ unsigned int mask = 0;
+ array values{};
+ xcb_params_configure_window_t params{};
+
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, width, width);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, x, x);
+ connection::pack_values(mask, ¶ms, values);
+ m_connection.configure_window_checked(m_tray, mask, values.data());
+ }
+
+ m_opts.configured_w = width;
+ m_opts.configured_x = x;
+}
+
+/**
+ * Reconfigure clients
+ */
+void tray_manager::reconfigure_clients() {
+ m_log.trace("tray: Reconfigure clients");
+
+ int x = m_opts.spacing;
+
+ for (auto it = m_clients.rbegin(); it != m_clients.rend(); it++) {
+ auto client = *it;
+
+ try {
+ client->ensure_state();
+ client->reconfigure(x, calculate_client_y());
+
+ x += m_opts.width + m_opts.spacing;
+ } catch (const xpp::x::error::window& err) {
+ remove_client(client, false);
+ }
+ }
+}
+
+/**
+ * Reconfigure root pixmap
+ */
+void tray_manager::reconfigure_bg(bool realloc) {
+ if (!m_opts.transparent || m_clients.empty() || !m_mapped) {
+ return;
+ };
+
+ m_log.trace("tray: Reconfigure bg (realloc=%i)", realloc);
+
+ if (!m_context) {
+ return m_log.err("tray: no context for drawing the background");
+ }
+
+ cairo::surface* surface = m_bg_slice->get_surface();
+ if (!surface) {
+ return;
+ }
+
+ m_context->clear();
+ *m_context << CAIRO_OPERATOR_SOURCE << *m_surface;
+ cairo_set_source_surface(*m_context, *surface, 0, 0);
+ m_context->paint();
+ *m_context << CAIRO_OPERATOR_OVER << m_opts.background;
+ m_context->paint();
+}
+
+/**
+ * Refresh the bar window by clearing it along with each client window
+ */
+void tray_manager::refresh_window() {
+ if (!m_activated || !m_mapped || !m_mtx.try_lock()) {
+ return;
+ }
+
+ std::lock_guard lock(m_mtx, std::adopt_lock);
+
+ m_log.trace("tray: Refreshing window");
+
+ auto width = calculate_w();
+ auto height = calculate_h();
+
+ if (m_opts.transparent && !m_context) {
+ xcb_rectangle_t rect{0, 0, static_cast(width), static_cast(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) {
+ try {
+ if (client->mapped()) {
+ 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_connection.flush();
+
+ if (!mapped_clients()) {
+ m_opts.configured_w = 0;
+ } else {
+ m_opts.configured_w = width;
+ }
+}
+
+/**
+ * Redraw window
+ */
+void tray_manager::redraw_window(bool realloc_bg) {
+ m_log.info("Redraw tray container (id=%s)", m_connection.id(m_tray));
+ reconfigure_bg(realloc_bg);
+ refresh_window();
+}
+
+/**
+ * Find the systray selection atom
+ */
+void tray_manager::query_atom() {
+ m_log.trace("tray: Find systray selection atom for the default screen");
+ string name{"_NET_SYSTEM_TRAY_S" + to_string(m_connection.default_screen())};
+ auto reply = m_connection.intern_atom(false, name.length(), name.c_str());
+ m_atom = reply.atom();
+}
+
+/**
+ * Create tray window
+ */
+void tray_manager::create_window() {
+ m_log.trace("tray: Create tray window");
+
+ // clang-format off
+ auto win = winspec(m_connection, m_tray)
+ << cw_size(calculate_w(), calculate_h())
+ << cw_pos(calculate_x(calculate_w()), calculate_y())
+ << 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_STRUCTURE_NOTIFY
+ |XCB_EVENT_MASK_EXPOSURE)
+ << cw_params_override_redirect(true);
+ // clang-format on
+
+ if (!m_opts.transparent) {
+ win << cw_params_back_pixel(m_opts.background);
+ win << cw_params_border_pixel(m_opts.background);
+ }
+
+ 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);
+}
+
+/**
+ * Create tray window background components
+ */
+void tray_manager::create_bg(bool realloc) {
+ if (!m_opts.transparent) {
+ return;
+ }
+ if (!realloc && m_pixmap && m_gc && m_surface && m_context) {
+ return;
+ }
+ if (realloc && m_pixmap) {
+ m_connection.free_pixmap(m_pixmap);
+ m_pixmap = 0;
+ }
+ if (realloc && m_gc) {
+ m_connection.free_gc(m_gc);
+ m_gc = 0;
+ }
+
+ if (realloc && m_surface) {
+ m_surface.reset();
+ }
+ if (realloc && m_context) {
+ m_context.reset();
+ }
+
+ auto w = m_opts.width_max;
+ auto h = calculate_h();
+
+ if (!m_pixmap) {
+ try {
+ m_pixmap = m_connection.generate_id();
+ m_connection.create_pixmap_checked(m_connection.screen()->root_depth, m_pixmap, m_tray, w, h);
+ } catch (const exception& err) {
+ return m_log.err("Failed to create pixmap for tray background (err: %s)", err.what());
+ }
+ }
+
+ if (!m_gc) {
+ try {
+ xcb_params_gc_t params{};
+ unsigned int mask = 0;
+ array values{};
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, graphics_exposures, 1);
+ connection::pack_values(mask, ¶ms, values);
+ m_gc = m_connection.generate_id();
+ m_connection.create_gc_checked(m_gc, m_pixmap, mask, values.data());
+ } catch (const exception& err) {
+ return m_log.err("Failed to create gcontext for tray background (err: %s)", err.what());
+ }
+ }
+
+ if (!m_surface) {
+ xcb_visualtype_t* visual = m_connection.visual_type_for_id(m_connection.screen()->root_visual);
+ if (!visual) {
+ return m_log.err("Failed to get root visual for tray background");
+ }
+ m_surface = make_unique(m_connection, m_pixmap, visual, w, h);
+ }
+
+ if (!m_context) {
+ m_context = make_unique(*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) {
+ m_log.err("Failed to set tray window back pixmap (%s)", err.what());
+ }
+}
+
+/**
+ * Put tray window above the defined sibling in the window stack
+ */
+void tray_manager::restack_window() {
+ if (m_opts.sibling == XCB_NONE) {
+ return;
+ }
+
+ try {
+ m_log.trace("tray: Restacking tray window");
+
+ unsigned int mask = 0;
+ array values{};
+ xcb_params_configure_window_t params{};
+
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, sibling, m_opts.sibling);
+ XCB_AUX_ADD_PARAM(&mask, ¶ms, stack_mode, XCB_STACK_MODE_ABOVE);
+
+ connection::pack_values(mask, ¶ms, values);
+ m_connection.configure_window_checked(m_tray, mask, values.data());
+ } catch (const exception& err) {
+ auto id = m_connection.id(m_opts.sibling);
+ m_log.err("tray: Failed to put tray above %s in the stack (%s)", id, err.what());
+ }
+}
+
+/**
+ * Set window WM hints
+ */
+void tray_manager::set_wm_hints() {
+ const unsigned int visual{m_connection.screen()->root_visual};
+ const unsigned int orientation{_NET_SYSTEM_TRAY_ORIENTATION_HORZ};
+
+ m_log.trace("bar: Set window WM_NAME / WM_CLASS");
+ icccm_util::set_wm_name(m_connection, m_tray, TRAY_WM_NAME, 19_z, TRAY_WM_CLASS, 12_z);
+
+ m_log.trace("tray: Set window WM_PROTOCOLS");
+ icccm_util::set_wm_protocols(m_connection, m_tray, {WM_DELETE_WINDOW, WM_TAKE_FOCUS});
+
+ m_log.trace("tray: Set window _NET_WM_WINDOW_TYPE");
+ ewmh_util::set_wm_window_type(m_tray, {_NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_NORMAL});
+
+ m_log.trace("tray: Set window _NET_WM_STATE");
+ ewmh_util::set_wm_state(m_tray, {_NET_WM_STATE_SKIP_TASKBAR});
+
+ m_log.trace("tray: Set window _NET_WM_PID");
+ ewmh_util::set_wm_pid(m_tray);
+
+ m_log.trace("tray: Set window _NET_SYSTEM_TRAY_VISUAL");
+ xcb_change_property(
+ m_connection, XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_VISUAL, XCB_ATOM_VISUALID, 32, 1, &visual);
+
+ m_log.trace("tray: Set window _NET_SYSTEM_TRAY_ORIENTATION");
+ xcb_change_property(m_connection, XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_ORIENTATION,
+ _NET_SYSTEM_TRAY_ORIENTATION, 32, 1, &orientation);
+}
+
+/**
+ * Set color atom used by clients when determing icon theme
+ */
+void tray_manager::set_tray_colors() {
+ m_log.trace("tray: Set _NET_SYSTEM_TRAY_COLORS to %x", m_opts.foreground);
+
+ auto r = m_opts.foreground.red_i();
+ auto g = m_opts.foreground.green_i();
+ auto b = m_opts.foreground.blue_i();
+
+ const uint16_t r16 = (r << 8) | r;
+ const uint16_t g16 = (g << 8) | g;
+ const uint16_t b16 = (b << 8) | b;
+
+ const uint32_t colors[12] = {
+ r16, g16, b16, // normal
+ r16, g16, b16, // error
+ r16, g16, b16, // warning
+ r16, g16, b16, // success
+ };
+
+ m_connection.change_property(
+ XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32, 12, colors);
+}
+
+/**
+ * Acquire the systray selection
+ */
+void tray_manager::acquire_selection() {
+ m_othermanager = XCB_NONE;
+ xcb_window_t owner;
+
+ try {
+ owner = m_connection.get_selection_owner(m_atom).owner();
+ } catch (const exception& err) {
+ return;
+ }
+
+ if (owner == m_tray) {
+ m_log.trace("tray: Already managing the systray selection");
+ m_acquired_selection = true;
+ } else if ((m_othermanager = owner) != XCB_NONE) {
+ m_log.warn("Systray selection already managed (window=%s)", m_connection.id(owner));
+ track_selection_owner(m_othermanager);
+ } else {
+ m_log.trace("tray: Change selection owner to %s", m_connection.id(m_tray));
+ m_connection.set_selection_owner_checked(m_tray, m_atom, XCB_CURRENT_TIME);
+ if (m_connection.get_selection_owner_unchecked(m_atom)->owner != m_tray) {
+ throw application_error("Failed to get control of the systray selection");
+ }
+ m_acquired_selection = true;
+ }
+}
+
+/**
+ * Notify pending clients about the new systray MANAGER
+ */
+void tray_manager::notify_clients() {
+ if (m_activated) {
+ m_log.info("Notifying pending tray clients");
+ auto message = m_connection.make_client_message(MANAGER, m_connection.root());
+ message.data.data32[0] = XCB_CURRENT_TIME;
+ message.data.data32[1] = m_atom;
+ message.data.data32[2] = m_tray;
+ m_connection.send_client_message(message, m_connection.root());
+ }
+}
+
+/**
+ * Send delayed notification to pending clients
+ */
+void tray_manager::notify_clients_delayed() {
+ if (m_delaythread.joinable()) {
+ m_delaythread.join();
+ }
+ m_delaythread = thread([this]() {
+ this_thread::sleep_for(1s);
+ notify_clients();
+ });
+}
+
+/**
+ * Track changes to the given selection owner
+ * If it gets destroyed or goes away we can reactivate the tray_manager
+ */
+void tray_manager::track_selection_owner(xcb_window_t owner) {
+ if (owner != XCB_NONE) {
+ m_log.trace("tray: Listen for events on the new selection window");
+ const unsigned int mask{XCB_CW_EVENT_MASK};
+ const unsigned int values[]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+ m_connection.change_window_attributes(owner, mask, values);
+ }
+}
+
+/**
+ * Process client docking request
+ */
+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));
+
+ m_clients.emplace_back(std::make_shared(m_connection, win, m_opts.width, m_opts.height));
+ auto& client = m_clients.back();
+
+ try {
+ 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};
+
+ m_log.trace("tray: Update client window");
+ m_connection.change_window_attributes_checked(client->window(), mask, values);
+
+ m_log.trace("tray: Configure client size");
+ client->reconfigure(0, 0);
+
+ m_log.trace("tray: Add client window to the save set");
+ m_connection.change_save_set_checked(XCB_SET_MODE_INSERT, client->window());
+
+ m_log.trace("tray: Reparent client");
+ 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->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());
+ }
+
+ } catch (const std::exception& err) {
+ m_log.err("Failed to setup tray client removing... (%s)", err.what());
+ remove_client(win, false);
+ }
+}
+
+/**
+ * Calculate x position of tray window
+ */
+int tray_manager::calculate_x(unsigned int width) const {
+ auto x = m_opts.orig_x;
+ if (m_opts.tray_position == tray_postition::RIGHT) {
+ x -= ((m_opts.width + m_opts.spacing) * m_clients.size() + m_opts.spacing);
+ } else if (m_opts.tray_position == tray_postition::CENTER) {
+ x -= (width / 2) - (m_opts.width / 2);
+ }
+ return x;
+}
+
+/**
+ * Calculate y position of tray window
+ */
+int tray_manager::calculate_y(bool abspos) const {
+ return abspos ? m_opts.orig_y : m_opts.rel_y;
+}
+
+/**
+ * Calculate width of tray window
+ */
+unsigned short int tray_manager::calculate_w() const {
+ unsigned int width = m_opts.spacing;
+ unsigned int count{0};
+ for (auto&& client : m_clients) {
+ if (client->mapped()) {
+ count++;
+ width += m_opts.spacing + m_opts.width;
+ }
+ }
+ return count ? width : 0;
+}
+
+/**
+ * Calculate height of tray window
+ */
+unsigned short int tray_manager::calculate_h() const {
+ return m_opts.height_fill;
+}
+
+/**
+ * Calculate x position of client window
+ */
+int tray_manager::calculate_client_x(const xcb_window_t& win) {
+ for (unsigned int i = 0; i < m_clients.size(); i++) {
+ if (m_clients[i]->match(win)) {
+ return m_opts.spacing + m_opts.width * i;
+ }
+ }
+ return m_opts.spacing;
+}
+
+/**
+ * Calculate y position of client window
+ */
+int tray_manager::calculate_client_y() {
+ return (m_opts.height_fill - m_opts.height) / 2;
+}
+
+/**
+ * Check if the given window is embedded
+ */
+bool tray_manager::is_embedded(const xcb_window_t& win) const {
+ return m_clients.end() != std::find_if(m_clients.begin(), m_clients.end(),
+ [win](shared_ptr client) { return client->match(win); });
+}
+
+/**
+ * Find tray client by window
+ */
+shared_ptr tray_manager::find_client(const xcb_window_t& win) const {
+ for (auto&& client : m_clients) {
+ if (client->match(win)) {
+ return client;
+ }
+ }
+ return nullptr;
+}
+
+/**
+ * Remove tray client
+ */
+void tray_manager::remove_client(shared_ptr& client, bool reconfigure) {
+ remove_client(client->window(), reconfigure);
+}
+
+/**
+ * Remove tray client by window
+ */
+void tray_manager::remove_client(xcb_window_t win, bool reconfigure) {
+ m_clients.erase(std::remove_if(
+ m_clients.begin(), m_clients.end(), [win](shared_ptr client) { return client->match(win); }));
+
+ if (reconfigure) {
+ tray_manager::reconfigure();
+ }
+}
+
+/**
+ * Get number of mapped clients
+ */
+unsigned int tray_manager::mapped_clients() const {
+ unsigned int mapped_clients = 0;
+
+ for (auto&& client : m_clients) {
+ if (client->mapped()) {
+ mapped_clients++;
+ }
+ }
+
+ return mapped_clients;
+}
+
+bool tray_manager::change_visibility(bool visible) {
+ unsigned int clients{mapped_clients()};
+
+ m_log.trace("tray: visibility_change (state=%i, activated=%i, mapped=%i, hidden=%i)", visible,
+ static_cast(m_activated), static_cast(m_mapped), static_cast(m_hidden));
+
+ m_hidden = !visible;
+
+ if (!m_activated) {
+ return false;
+ } else if (!m_hidden && !m_mapped && clients) {
+ m_connection.map_window(m_tray);
+ } else if ((!clients || m_hidden) && m_mapped) {
+ m_connection.unmap_window(m_tray);
+ } else if (m_mapped && !m_hidden && clients) {
+ redraw_window();
+ }
+
+ m_connection.flush();
+
+ return true;
+}
+
+/**
+ * Event callback : XCB_EXPOSE
+ */
+void tray_manager::handle(const evt::expose& evt) {
+ if (m_activated && !m_clients.empty() && evt->count == 0) {
+ redraw_window();
+ }
+}
+
+/**
+ * Event callback : XCB_VISIBILITY_NOTIFY
+ */
+void tray_manager::handle(const evt::visibility_notify& evt) {
+ if (m_activated && !m_clients.empty()) {
+ m_log.trace("tray: Received visibility_notify for %s", m_connection.id(evt->window));
+ reconfigure_window();
+ }
+}
+
+/**
+ * Event callback : XCB_CLIENT_MESSAGE
+ */
+void tray_manager::handle(const evt::client_message& evt) {
+ if (!m_activated) {
+ return;
+ } else if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_tray) {
+ m_log.notice("Received WM_DELETE");
+ m_tray = 0;
+ deactivate();
+ } else if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) {
+ m_log.trace("tray: Received client_message");
+
+ if (SYSTEM_TRAY_REQUEST_DOCK == evt->data.data32[1]) {
+ if (!is_embedded(evt->data.data32[2])) {
+ process_docking_request(evt->data.data32[2]);
+ } else {
+ auto win = evt->data.data32[2];
+ m_log.warn("Tray client %s already embedded, ignoring request...", m_connection.id(win));
+ }
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_CONFIGURE_REQUEST
+ *
+ * Called when a tray client thinks he's part of the free world and
+ * wants to reconfigure its window. This is of course nothing we appreciate
+ * so we return an answer that'll put him in place.
+ */
+void tray_manager::handle(const evt::configure_request& evt) {
+ if (m_activated && is_embedded(evt->window)) {
+ try {
+ m_log.trace("tray: Client configure request %s", m_connection.id(evt->window));
+ find_client(evt->window)->configure_notify(calculate_client_x(evt->window), calculate_client_y());
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure tray client, removing... (%s)", err.what());
+ remove_client(evt->window);
+ }
+ }
+}
+
+/**
+ * @see tray_manager::handle(const evt::configure_request&);
+ */
+void tray_manager::handle(const evt::resize_request& evt) {
+ if (m_activated && is_embedded(evt->window)) {
+ try {
+ m_log.trace("tray: Received resize_request for client %s", m_connection.id(evt->window));
+ find_client(evt->window)->configure_notify(calculate_client_x(evt->window), calculate_client_y());
+ } catch (const xpp::x::error::window& err) {
+ m_log.err("Failed to reconfigure tray client, removing... (%s)", err.what());
+ remove_client(evt->window);
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_SELECTION_CLEAR
+ */
+void tray_manager::handle(const evt::selection_clear& evt) {
+ if (!m_activated) {
+ return;
+ } else if (evt->selection != m_atom) {
+ return;
+ } else if (evt->owner != m_tray) {
+ return;
+ }
+
+ try {
+ m_log.warn("Lost systray selection, deactivating...");
+ m_othermanager = m_connection.get_selection_owner(m_atom).owner();
+ track_selection_owner(m_othermanager);
+ } catch (const exception& err) {
+ m_log.err("Failed to get systray selection owner");
+ m_othermanager = XCB_NONE;
+ }
+
+ deactivate(false);
+}
+
+/**
+ * Event callback : XCB_PROPERTY_NOTIFY
+ */
+void tray_manager::handle(const evt::property_notify& evt) {
+ if (!m_activated) {
+ return;
+ }
+
+ // React an wallpaper change, if bar has transparency
+ if (m_opts.transparent &&
+ (evt->atom == _XROOTPMAP_ID || evt->atom == _XSETROOT_ID || evt->atom == ESETROOT_PMAP_ID)) {
+ redraw_window(true);
+ return;
+ }
+
+ if (evt->atom != _XEMBED_INFO) {
+ return;
+ }
+
+ auto client = find_client(evt->window);
+
+ if (!client) {
+ return;
+ }
+
+ 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");
+ }
+
+ try {
+ 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: 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->get_xembed().is_mapped()) {
+ reconfigure();
+ }
+}
+
+/**
+ * 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) {
+ m_log.trace("tray: Received reparent_notify for client, remove...");
+ remove_client(evt->window);
+ }
+}
+
+/**
+ * Event callback : XCB_DESTROY_NOTIFY
+ */
+void tray_manager::handle(const evt::destroy_notify& evt) {
+ if (m_activated && evt->window == m_tray) {
+ deactivate();
+ } else if (!m_activated && evt->window == m_othermanager) {
+ m_log.info("Systray selection unmanaged... re-activating");
+ activate();
+ } else if (m_activated && is_embedded(evt->window)) {
+ m_log.trace("tray: Received destroy_notify for client, remove...");
+ remove_client(evt->window);
+ redraw_window();
+ }
+}
+
+/**
+ * Event callback : XCB_MAP_NOTIFY
+ */
+void tray_manager::handle(const evt::map_notify& evt) {
+ if (m_activated && evt->window == m_tray) {
+ m_log.trace("tray: Received map_notify");
+ m_log.trace("tray: Update container mapped flag");
+ m_mapped = true;
+ redraw_window();
+ } else if (is_embedded(evt->window)) {
+ m_log.trace("tray: Received map_notify");
+ m_log.trace("tray: Set client mapped");
+ find_client(evt->window)->mapped(true);
+ unsigned int clientcount{mapped_clients()};
+ if (clientcount > m_opts.configured_slots) {
+ reconfigure();
+ }
+ }
+}
+
+/**
+ * Event callback : XCB_UNMAP_NOTIFY
+ */
+void tray_manager::handle(const evt::unmap_notify& evt) {
+ if (m_activated && evt->window == m_tray) {
+ m_log.trace("tray: Received unmap_notify");
+ m_log.trace("tray: Update container mapped flag");
+ m_mapped = false;
+ } else if (m_activated && is_embedded(evt->window)) {
+ m_log.trace("tray: Received unmap_notify");
+ m_log.trace("tray: Set client unmapped");
+ find_client(evt->window)->mapped(false);
+ }
+}
+
+/**
+ * Signal handler connected to the bar window's visibility change signal.
+ * This is used as a fallback in case the window restacking fails. It will
+ * toggle the tray window whenever the visibility of the bar window changes.
+ */
+bool tray_manager::on(const signals::ui::visibility_change& evt) {
+ return change_visibility(evt.cast());
+}
+
+bool tray_manager::on(const signals::ui::dim_window& evt) {
+ if (m_activated) {
+ ewmh_util::set_wm_window_opacity(m_tray, evt.cast() * 0xFFFFFFFF);
+ }
+ // let the event bubble
+ return false;
+}
+
+bool tray_manager::on(const signals::ui::update_background&) {
+ redraw_window(true);
+
+ return false;
+}
+
+bool tray_manager::on(const signals::ui_tray::tray_pos_change& evt) {
+ m_opts.orig_x =
+ m_bar_opts.inner_area(true).x + std::max(0, std::min(evt.cast(), (int)(m_bar_opts.size.w - calculate_w())));
+
+ reconfigure_window();
+
+ return true;
+}
+
+bool tray_manager::on(const signals::ui_tray::tray_visibility& evt) {
+ if (evt.cast() == m_hidden && m_opts.tray_position == tray_postition::MODULE) {
+ return change_visibility(evt.cast());
+ } else {
+ return true;
+ }
+}
+
+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) {}
+
+tray_client::~tray_client() {
+ xembed::unembed(m_connection, window(), m_connection.root());
+}
+
+unsigned int tray_client::width() const {
+ return m_width;
+}
+
+unsigned int tray_client::height() const {
+ return m_height;
+}
+
+void tray_client::clear_window() const {
+ m_connection.clear_area_checked(1, window(), 0, 0, width(), height());
+}
+
+/**
+ * Match given window against client window
+ */
+bool tray_client::match(const xcb_window_t& win) const {
+ return win == m_window;
+}
+
+/**
+ * Get client window mapped state
+ */
+bool tray_client::mapped() const {
+ return m_mapped;
+}
+
+/**
+ * Set client window mapped state
+ */
+void tray_client::mapped(bool state) {
+ m_mapped = state;
+}
+
+/**
+ * Get client window
+ */
+xcb_window_t tray_client::window() const {
+ return m_window;
+}
+
+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 {
+ 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() && !should_be_mapped) {
+ m_connection.unmap_window_checked(window());
+ }
+}
+
+/**
+ * Configure window size
+ */
+void tray_client::reconfigure(int x, int y) const {
+ unsigned int configure_mask = 0;
+ array configure_values{};
+ xcb_params_configure_window_t configure_params{};
+
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, width, m_width);
+ XCB_AUX_ADD_PARAM(&configure_mask, &configure_params, height, m_height);
+ 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());
+}
+
+/**
+ * Respond to client resize requests
+ */
+void tray_client::configure_notify(int x, int y) const {
+ xcb_configure_notify_event_t notify;
+ notify.response_type = XCB_CONFIGURE_NOTIFY;
+ notify.event = m_window;
+ notify.window = m_window;
+ notify.override_redirect = false;
+ notify.above_sibling = 0;
+ notify.x = x;
+ notify.y = y;
+ notify.width = m_width;
+ notify.height = m_height;
+ notify.border_width = 0;
+
+ unsigned int mask{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
+ m_connection.send_event_checked(false, m_window, mask, reinterpret_cast(¬ify));
+}
+
+} // namespace legacy_tray
+
+POLYBAR_NS_END