polybar/src/x11/tray_manager.cpp

770 lines
20 KiB
C++
Raw Normal View History

#include "x11/tray_manager.hpp"
#include <xcb/xcb_image.h>
2016-11-20 22:04:31 +00:00
#include <thread>
2022-10-31 22:21:00 +00:00
#include <utility>
#include "cairo/context.hpp"
#include "cairo/surface.hpp"
#include "components/config.hpp"
2016-11-25 12:55:15 +00:00
#include "errors.hpp"
#include "events/signal.hpp"
2016-11-02 19:22:45 +00:00
#include "utils/color.hpp"
2016-11-20 22:04:31 +00:00
#include "utils/factory.hpp"
#include "utils/math.hpp"
#include "utils/memory.hpp"
#include "utils/process.hpp"
Add units support (POINT, PIXEL, SPACE) (#2578) * add units support (POINT, PIXEL, SPACE) for polybar - add a size_with_unit struct - add a geometry_format_values struct - move dpi initialisation from renderer.cpp to bar.cpp - add a string to size_with_unit converter - add point support (with pt) - add pixel support (with px) * Fix unit test compilation * clang-format * Better names The old names didn't really capture the purpose of the structs and function. space_type -> spacing_type space_size -> spacing_val size_type -> extent_type geometry -> extent_val geometry_format_values -> percentage_with_offset * Remove parse_size_with_unit No longer needed. The convert<spacing_val> function in config.cpp already does all the work for us and always setting the type to pixel was wrong. In addition, line-size should not be of type spacing_val but extent_val. * Cleanup I tried to address most of my comments on the old PR * Fix renderer width calculation We can't just blindly add the x difference to the width because for example the width should increase if x < width and the increase keeps x < width. Similarly, we can't just add the offset to the width. * Rename geom_format_to_pixels to percentage_with_offset_to_pixel * Cleanup * Apply suggested changes from Patrick on GitHub Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/bar.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/config.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * config: Use stod for parsing percentage * Use stof instead of strtof * units: Fix test edge cases * Remove unnecessary clang-format toggle * Use percentage_with_offset for margin-{top,bottom} * Support negative extent values * Rename unit to units and create a cpp file * Move percentage_with_offset_to_pixel unit test to units * Add unit tests for units_utils * Clarify when and how negative spacing/extent is allowed Negative spacing is never allowed and produces a config error. Extents allow negative values in theory, but only a few use-cases accept it. Only the extent value used for the `%{O}` tag and the offset value in percentage_with_offset can be negative. Everything else is capped below at 0. The final pixel value of percentage_with_offset also caps below at 0. * Fix parsing errors not being caught in config * Print a proper error message for uncaught exceptions * Cleanup module::get_output All changes preserve the existing semantics * Stop using remove_trailing_space in module::get_output Instead, we first check if the current tag is built, and only if it is, the spacing is prepended. * Remove unused imports * Restore old behavior If there are two tags and the second one isn't built (module::build returns false), the space in between them is removed. For example in the mpd module: format-online = <toggle> <label-song> foo If mpd is not running, the mpd module will return false when trying to build the `<label-song>` tag. If we don't remove the space between `<toggle>` and `<label-song>`, we end up with two spaces between `<toggle>` and `foo`. This change is to match the old behavior where at least one trailing space character was removed from the builder. * Add changelog entry * Remove unused setting * Use percentage with offset for tray-offset Co-authored-by: Jérôme BOULMIER <jerome.boulmier@outlook.fr> Co-authored-by: Joe Groocock <github@frebib.net>
2022-02-20 20:08:57 +00:00
#include "utils/units.hpp"
2017-01-24 07:49:27 +00:00
#include "x11/ewmh.hpp"
#include "x11/icccm.hpp"
2016-11-02 19:22:45 +00:00
#include "x11/window.hpp"
2016-11-25 03:10:26 +00:00
#include "x11/winspec.hpp"
#include "x11/xembed.hpp"
2016-11-02 19:22:45 +00:00
/*
* Tray implementation according to the System Tray Protocol.
*
* Ref: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
2022-09-03 19:33:22 +00:00
*
2023-03-23 21:38:42 +00:00
* This class manages embedded tray icons by placing them on the bar in the correct positions; the start position is
2022-09-03 19:33:22 +00:00
* requested by the renderer.
*
* The tray manager needs to trigger bar updates only when the size of the entire tray changes (e.g. when tray icons are
2023-03-23 21:38:42 +00:00
* added/removed). Everything else can be handled without an update.
*/
2016-11-19 05:22:44 +00:00
POLYBAR_NS
2016-11-02 19:22:45 +00:00
2022-10-31 22:21:00 +00:00
namespace tray {
manager::manager(
2022-09-03 19:33:22 +00:00
connection& conn, signal_emitter& emitter, const logger& logger, const bar_settings& bar_opts, on_update on_update)
2022-10-31 22:21:00 +00:00
: m_connection(conn), m_sig(emitter), m_log(logger), m_bar_opts(bar_opts), m_on_update(std::move(on_update)) {
2016-12-14 10:34:09 +00:00
m_connection.attach_sink(this, SINK_PRIORITY_TRAY);
}
2016-11-02 19:22:45 +00:00
2022-10-31 22:21:00 +00:00
manager::~manager() {
2016-12-14 10:34:09 +00:00
m_connection.detach_sink(this, SINK_PRIORITY_TRAY);
deactivate();
}
2022-10-31 22:21:00 +00:00
void manager::setup(const config& conf, const string& section_name) {
unsigned bar_height = m_bar_opts.inner_area().height;
// Spacing between icons
auto spacing = conf.get(section_name, "tray-spacing", ZERO_PX_EXTENT);
m_opts.spacing = units_utils::extent_to_pixel_nonnegative(spacing, m_bar_opts.dpi_x);
// Padding before and after each icon
auto padding = conf.get(section_name, "tray-padding", ZERO_PX_EXTENT);
m_opts.padding = units_utils::extent_to_pixel_nonnegative(padding, m_bar_opts.dpi_x);
2016-11-02 19:22:45 +00:00
auto size = conf.get(section_name, "tray-size", percentage_with_offset{66., ZERO_PX_EXTENT});
unsigned client_height = std::min(
bar_height, units_utils::percentage_with_offset_to_pixel_nonnegative(size, bar_height, m_bar_opts.dpi_y));
if (client_height == 0) {
m_log.warn("tray: tray-size has an effective value of 0px, you will not see any tray icons");
}
2016-11-02 19:22:45 +00:00
2022-08-28 12:49:09 +00:00
m_opts.client_size = {client_height, client_height};
2022-02-27 20:36:16 +00:00
// Set user-defined foreground and background colors.
m_opts.background = conf.get(section_name, "tray-background", m_bar_opts.background);
m_opts.foreground = conf.get(section_name, "tray-foreground", m_bar_opts.foreground);
2016-11-25 12:55:15 +00:00
m_opts.selection_owner = m_bar_opts.x_data.window;
m_log.info("tray: spacing=%upx padding=%upx size=%upx", m_opts.spacing, m_opts.padding, client_height);
2022-08-28 13:15:48 +00:00
if (m_bar_opts.x_data.window == XCB_NONE) {
m_log.err("tray: No bar window found, disabling tray");
return;
}
// Activate the tray manager
query_atom();
activate();
2016-11-02 19:22:45 +00:00
}
2022-10-31 22:21:00 +00:00
unsigned manager::get_width() const {
return m_tray_width;
}
2022-10-31 22:21:00 +00:00
bool manager::is_active() const {
2022-09-11 19:47:50 +00:00
return m_state == state::ACTIVE;
}
2022-10-31 22:21:00 +00:00
bool manager::is_inactive() const {
2022-09-11 19:47:50 +00:00
return m_state == state::INACTIVE;
}
2022-10-31 22:21:00 +00:00
bool manager::is_waiting() const {
2022-09-11 19:47:50 +00:00
return m_state == state::WAITING;
}
2022-10-31 22:21:00 +00:00
bool manager::is_visible() const {
2022-09-14 20:12:13 +00:00
return is_active() && !m_hidden;
}
2016-11-02 19:22:45 +00:00
/**
* Activate systray management
*/
2022-10-31 22:21:00 +00:00
void manager::activate() {
2022-09-11 19:47:50 +00:00
if (is_active()) {
2016-11-02 19:22:45 +00:00
return;
}
2022-09-14 19:53:02 +00:00
m_log.info("tray: Activating tray manager");
2016-11-02 19:22:45 +00:00
try {
set_tray_colors();
2022-09-19 19:23:22 +00:00
set_tray_orientation();
} catch (const exception& err) {
m_log.err(err.what());
2016-11-25 03:10:26 +00:00
m_log.err("Cannot activate tray manager... failed to setup window");
2022-09-11 19:47:50 +00:00
deactivate();
return;
}
2022-09-11 19:47:50 +00:00
// Attempt to get control of the systray selection
2022-09-14 19:53:02 +00:00
xcb_window_t other_owner = XCB_NONE;
2022-09-11 19:47:50 +00:00
if (!acquire_selection(other_owner)) {
// Transition to WAITING state
wait_for_selection(other_owner);
2016-12-21 22:22:02 +00:00
return;
}
2022-09-11 19:47:50 +00:00
m_sig.attach(this);
m_othermanager = XCB_NONE;
m_state = state::ACTIVE;
2022-10-16 19:31:24 +00:00
notify_clients();
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
2022-09-11 19:47:50 +00:00
/**
* Transitions tray manager to WAITING state
*
* @param other window id for current selection owner
*/
2022-10-31 22:21:00 +00:00
void manager::wait_for_selection(xcb_window_t other) {
2022-09-11 19:47:50 +00:00
if (is_waiting() || other == XCB_NONE) {
return;
}
m_log.info("tray: Waiting for systray selection (current owner: %s)", m_connection.id(other));
m_sig.detach(this);
m_othermanager = other;
track_selection_owner(other);
m_log.trace("tray: Unembed clients");
m_clients.clear();
m_connection.flush();
m_state = state::WAITING;
2023-03-25 18:55:30 +00:00
recalculate_width();
2022-09-11 19:47:50 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Deactivate systray management
*/
2022-10-31 22:21:00 +00:00
void manager::deactivate() {
2022-09-11 19:47:50 +00:00
if (is_inactive()) {
2016-11-02 19:22:45 +00:00
return;
}
2022-09-14 19:53:02 +00:00
m_log.info("tray: Deactivating tray manager");
2016-11-02 19:22:45 +00:00
m_sig.detach(this);
2016-11-02 19:22:45 +00:00
2022-09-11 19:47:50 +00:00
// Unset selection owner if we currently own the atom
if (!m_connection.connection_has_error() && is_active()) {
2016-11-25 03:10:26 +00:00
m_log.trace("tray: Unset selection owner");
m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
2016-11-02 19:22:45 +00:00
}
m_log.trace("tray: Unembed clients");
m_clients.clear();
m_connection.flush();
2022-09-11 19:47:50 +00:00
m_othermanager = XCB_NONE;
m_state = state::INACTIVE;
2023-03-25 18:55:30 +00:00
recalculate_width();
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Reconfigure tray
*/
2022-10-31 22:21:00 +00:00
void manager::reconfigure() {
if (!m_opts.selection_owner) {
2016-11-02 19:22:45 +00:00
return;
2022-08-28 12:56:56 +00:00
}
2016-11-02 19:22:45 +00:00
2022-08-28 12:56:56 +00:00
try {
reconfigure_clients();
} catch (const exception& err) {
m_log.err("Failed to reconfigure tray clients (%s)", err.what());
}
2016-11-02 19:22:45 +00:00
2022-08-28 12:56:56 +00:00
m_connection.flush();
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
2023-03-25 18:55:30 +00:00
* Calculates the total width of the tray and potentially runs the update hook.
2022-09-03 19:33:22 +00:00
*
2023-03-25 18:55:30 +00:00
* Should be called whenever the number of mapped clients changes
2016-11-02 19:22:45 +00:00
*/
2023-03-25 18:55:30 +00:00
void manager::recalculate_width() {
2022-09-14 19:53:02 +00:00
m_log.trace("tray: Reconfigure window (hidden=%i, clients=%i)", m_hidden, m_clients.size());
2022-09-03 19:33:22 +00:00
unsigned new_width = calculate_w();
if (m_tray_width != new_width) {
m_tray_width = new_width;
2022-09-14 19:53:02 +00:00
m_log.trace("tray: new width (width: %d, clients: %d)", m_tray_width, m_clients.size());
2022-09-03 19:33:22 +00:00
m_on_update();
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
2023-03-25 18:55:30 +00:00
* Reconfigure client positions and mapped state
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
void manager::reconfigure_clients() {
m_log.trace("tray: Reconfigure clients");
// X-position of the start of the tray area
2023-04-01 01:08:34 +00:00
int base_x = calculate_x();
2016-11-02 19:22:45 +00:00
// X-position of the end of the previous tray icon (including padding)
unsigned x = 0;
2022-09-24 11:20:02 +00:00
bool has_error = false;
2023-04-01 01:08:34 +00:00
unsigned count = 0;
2022-09-24 11:20:02 +00:00
for (auto& client : m_clients) {
2016-11-02 19:22:45 +00:00
try {
2022-09-24 11:20:02 +00:00
client->ensure_state();
if (client->mapped()) {
// Calculate start of tray icon
unsigned client_x = x + (count > 0 ? m_opts.spacing : 0) + m_opts.padding;
client->set_position(base_x + client_x, calculate_client_y());
// Add size and padding to get the end position of the icon
x = client_x + m_opts.client_size.w + m_opts.padding;
2023-04-01 01:08:34 +00:00
count++;
2022-09-24 11:20:02 +00:00
}
2016-11-02 19:22:45 +00:00
} catch (const xpp::x::error::window& err) {
2022-09-24 11:20:02 +00:00
m_log.err("Failed to reconfigure %s, removing ... (%s)", client->name(), err.what());
client.reset();
has_error = true;
2016-11-02 19:22:45 +00:00
}
}
2022-09-24 11:20:02 +00:00
if (has_error) {
clean_clients();
}
2023-03-25 18:55:30 +00:00
// Some clients may have been (un)mapped or even removed
recalculate_width();
// The final x position should match the width of the entire tray
assert(x == m_tray_width);
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Redraw client windows.
*/
2022-10-31 22:21:00 +00:00
void manager::redraw_clients() {
2022-09-14 20:12:13 +00:00
if (!is_visible()) {
return;
}
2022-08-28 12:56:56 +00:00
m_log.trace("tray: Refreshing clients");
for (auto& client : m_clients) {
try {
2022-09-24 11:20:02 +00:00
if (client->mapped()) {
client->update_bg();
}
} catch (const std::exception& e) {
2022-09-24 11:20:02 +00:00
m_log.err("tray: Failed to clear %s (%s)", client->name(), e.what());
}
}
m_connection.flush();
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Find the systray selection atom
*/
2022-10-31 22:21:00 +00:00
void manager::query_atom() {
2016-11-02 19:22:45 +00:00
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();
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Set _NET_SYSTEM_TRAY_COLORS atom used by clients when determing icon theme
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
void manager::set_tray_colors() {
2022-10-09 16:07:01 +00:00
m_log.trace("tray: Set _NET_SYSTEM_TRAY_COLORS to 0x%08x", m_opts.foreground);
2016-11-02 19:22:45 +00:00
auto r = m_opts.foreground.red_i();
auto g = m_opts.foreground.green_i();
auto b = m_opts.foreground.blue_i();
2016-11-02 19:22:45 +00:00
const uint16_t r16 = (r << 8) | r;
const uint16_t g16 = (g << 8) | g;
const uint16_t b16 = (b << 8) | b;
2022-10-31 22:21:00 +00:00
const array<uint32_t, 12> colors = {
Add units support (POINT, PIXEL, SPACE) (#2578) * add units support (POINT, PIXEL, SPACE) for polybar - add a size_with_unit struct - add a geometry_format_values struct - move dpi initialisation from renderer.cpp to bar.cpp - add a string to size_with_unit converter - add point support (with pt) - add pixel support (with px) * Fix unit test compilation * clang-format * Better names The old names didn't really capture the purpose of the structs and function. space_type -> spacing_type space_size -> spacing_val size_type -> extent_type geometry -> extent_val geometry_format_values -> percentage_with_offset * Remove parse_size_with_unit No longer needed. The convert<spacing_val> function in config.cpp already does all the work for us and always setting the type to pixel was wrong. In addition, line-size should not be of type spacing_val but extent_val. * Cleanup I tried to address most of my comments on the old PR * Fix renderer width calculation We can't just blindly add the x difference to the width because for example the width should increase if x < width and the increase keeps x < width. Similarly, we can't just add the offset to the width. * Rename geom_format_to_pixels to percentage_with_offset_to_pixel * Cleanup * Apply suggested changes from Patrick on GitHub Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/bar.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/config.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * Update src/components/builder.cpp Co-authored-by: Patrick Ziegler <p.ziegler96@gmail.com> * config: Use stod for parsing percentage * Use stof instead of strtof * units: Fix test edge cases * Remove unnecessary clang-format toggle * Use percentage_with_offset for margin-{top,bottom} * Support negative extent values * Rename unit to units and create a cpp file * Move percentage_with_offset_to_pixel unit test to units * Add unit tests for units_utils * Clarify when and how negative spacing/extent is allowed Negative spacing is never allowed and produces a config error. Extents allow negative values in theory, but only a few use-cases accept it. Only the extent value used for the `%{O}` tag and the offset value in percentage_with_offset can be negative. Everything else is capped below at 0. The final pixel value of percentage_with_offset also caps below at 0. * Fix parsing errors not being caught in config * Print a proper error message for uncaught exceptions * Cleanup module::get_output All changes preserve the existing semantics * Stop using remove_trailing_space in module::get_output Instead, we first check if the current tag is built, and only if it is, the spacing is prepended. * Remove unused imports * Restore old behavior If there are two tags and the second one isn't built (module::build returns false), the space in between them is removed. For example in the mpd module: format-online = <toggle> <label-song> foo If mpd is not running, the mpd module will return false when trying to build the `<label-song>` tag. If we don't remove the space between `<toggle>` and `<label-song>`, we end up with two spaces between `<toggle>` and `foo`. This change is to match the old behavior where at least one trailing space character was removed from the builder. * Add changelog entry * Remove unused setting * Use percentage with offset for tray-offset Co-authored-by: Jérôme BOULMIER <jerome.boulmier@outlook.fr> Co-authored-by: Joe Groocock <github@frebib.net>
2022-02-20 20:08:57 +00:00
r16, g16, b16, // normal
r16, g16, b16, // error
r16, g16, b16, // warning
r16, g16, b16, // success
2016-11-02 19:22:45 +00:00
};
2022-10-31 22:21:00 +00:00
m_connection.change_property(XCB_PROP_MODE_REPLACE, m_opts.selection_owner, _NET_SYSTEM_TRAY_COLORS,
XCB_ATOM_CARDINAL, 32, colors.size(), colors.data());
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
2022-09-19 19:23:22 +00:00
/**
* Set the _NET_SYSTEM_TRAY_ORIENTATION atom
*/
2022-10-31 22:21:00 +00:00
void manager::set_tray_orientation() {
2022-09-19 19:23:22 +00:00
const uint32_t orientation = _NET_SYSTEM_TRAY_ORIENTATION_HORZ;
m_log.trace("tray: Set _NET_SYSTEM_TRAY_ORIENTATION to 0x%x", orientation);
m_connection.change_property_checked(XCB_PROP_MODE_REPLACE, m_opts.selection_owner, _NET_SYSTEM_TRAY_ORIENTATION,
XCB_ATOM_CARDINAL, 32, 1, &orientation);
}
2016-11-02 19:22:45 +00:00
/**
* Acquire the systray selection
2022-09-11 19:47:50 +00:00
*
* @param other_owner is set to the current owner if the function fails
* @returns Whether we acquired the selection
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
bool manager::acquire_selection(xcb_window_t& other_owner) {
2022-09-11 19:47:50 +00:00
other_owner = XCB_NONE;
xcb_window_t owner = m_connection.get_selection_owner(m_atom).owner();
2016-11-02 19:22:45 +00:00
if (owner == m_opts.selection_owner) {
2016-12-14 10:34:09 +00:00
m_log.trace("tray: Already managing the systray selection");
2022-09-11 19:47:50 +00:00
return true;
} else if (owner == XCB_NONE) {
m_log.trace("tray: Change selection owner to %s", m_connection.id(m_opts.selection_owner));
m_connection.set_selection_owner_checked(m_opts.selection_owner, m_atom, XCB_CURRENT_TIME);
if (m_connection.get_selection_owner_unchecked(m_atom)->owner != m_opts.selection_owner) {
2016-12-21 22:22:02 +00:00
throw application_error("Failed to get control of the systray selection");
}
2022-09-11 19:47:50 +00:00
return true;
} else {
other_owner = owner;
m_log.warn("Systray selection already managed (window=%s)", m_connection.id(owner));
return false;
2016-11-25 12:55:15 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Notify pending clients about the new systray MANAGER
*/
2022-10-31 22:21:00 +00:00
void manager::notify_clients() {
2022-09-11 19:47:50 +00:00
if (is_active()) {
2022-09-14 19:53:02 +00:00
m_log.info("tray: Notifying pending tray clients");
2016-11-25 12:55:15 +00:00
auto message = m_connection.make_client_message(MANAGER, m_connection.root());
2022-03-06 15:40:42 +00:00
message.data.data32[0] = XCB_CURRENT_TIME;
message.data.data32[1] = m_atom;
message.data.data32[2] = m_opts.selection_owner;
2016-11-25 12:55:15 +00:00
m_connection.send_client_message(message, m_connection.root());
}
2016-12-03 19:26:29 +00:00
}
2016-11-25 12:55:15 +00:00
2016-11-02 19:22:45 +00:00
/**
* Track changes to the given selection owner
* If it gets destroyed or goes away we can reactivate the tray_manager
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
void manager::track_selection_owner(xcb_window_t owner) {
2016-12-14 10:34:09 +00:00
if (owner != XCB_NONE) {
2022-10-31 22:21:00 +00:00
const uint32_t mask{XCB_CW_EVENT_MASK};
const uint32_t value{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
m_connection.change_window_attributes(owner, mask, &value);
2016-11-25 12:55:15 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Process client docking request
*/
2022-10-31 22:21:00 +00:00
void manager::process_docking_request(xcb_window_t win) {
2022-09-14 19:53:02 +00:00
m_log.info("tray: Processing docking request from '%s' (%s)", ewmh_util::get_wm_name(win), m_connection.id(win));
2016-11-02 19:22:45 +00:00
try {
2023-03-25 18:55:30 +00:00
auto cl =
make_unique<client>(m_log, m_connection, m_opts.selection_owner, win, m_opts.client_size, m_opts.background);
2016-11-02 19:22:45 +00:00
try {
2022-10-31 22:21:00 +00:00
cl->query_xembed();
} catch (const xpp::x::error::window& err) {
2022-10-31 22:21:00 +00:00
m_log.err("Failed to query _XEMBED_INFO, removing %s ... (%s)", cl->name(), err.what());
return;
}
2022-10-31 22:21:00 +00:00
cl->update_client_attributes();
2022-10-31 22:21:00 +00:00
cl->reparent();
2016-11-02 19:22:45 +00:00
2022-10-31 22:21:00 +00:00
cl->add_to_save_set();
2016-11-02 19:22:45 +00:00
cl->hidden(m_hidden);
2022-10-31 22:21:00 +00:00
cl->ensure_state();
2022-10-31 22:21:00 +00:00
cl->notify_xembed();
2022-10-09 16:07:01 +00:00
2022-10-31 22:21:00 +00:00
m_clients.emplace_back(std::move(cl));
} catch (const std::exception& err) {
m_log.err("tray: Failed to setup tray client '%s' (%s) removing... (%s)", ewmh_util::get_wm_name(win),
m_connection.id(win), err.what());
return;
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
2023-04-01 01:08:34 +00:00
/**
* Final x-position of the tray window relative to the very top-left bar window.
*/
2022-10-31 22:21:00 +00:00
int manager::calculate_x() const {
2022-08-28 12:49:09 +00:00
return m_bar_opts.inner_area(false).x + m_pos.x;
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
2023-03-25 18:55:30 +00:00
/**
* Calculates the entire width taken up by the tray area in pixels
*
* This many pixels need to be reserved on the bar in order to draw the tray.
*/
2022-10-31 22:21:00 +00:00
unsigned manager::calculate_w() const {
2023-04-01 01:08:34 +00:00
unsigned count =
std::count_if(m_clients.begin(), m_clients.end(), [](const auto& client) { return client->mapped(); });
if (count > 0) {
return (count - 1) * m_opts.spacing + count * (2 * m_opts.padding + m_opts.client_size.w);
2023-04-01 01:08:34 +00:00
} else {
return 0;
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Calculate y position of client window to vertically center it in the inner area of the bar.
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
int manager::calculate_client_y() {
return m_bar_opts.inner_area(false).y + (m_bar_opts.inner_area(false).height - m_opts.client_size.h) / 2;
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Check if the given window is embedded.
*
* The given window ID can be the ID of the wrapper or the embedded window
*/
2022-10-31 22:21:00 +00:00
bool manager::is_embedded(const xcb_window_t& win) {
return find_client(win) != nullptr;
}
2016-11-02 19:22:45 +00:00
/**
* Find tray client object from the wrapper or embedded window
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
client* manager::find_client(const xcb_window_t& win) {
auto client = std::find_if(m_clients.begin(), m_clients.end(),
2022-09-24 11:20:02 +00:00
[win](const auto& client) { return client->match(win) || client->embedder() == win; });
if (client == m_clients.end()) {
return nullptr;
} else {
2022-09-24 11:20:02 +00:00
return client->get();
2016-11-25 12:55:15 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Remove tray client
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
void manager::remove_client(const client& c) {
remove_client(c.client_window());
}
/**
* Remove tray client by window
*/
2022-10-31 22:21:00 +00:00
void manager::remove_client(xcb_window_t win) {
2022-09-14 20:12:13 +00:00
auto old_size = m_clients.size();
m_clients.erase(
2022-09-24 11:20:02 +00:00
std::remove_if(m_clients.begin(), m_clients.end(), [win](const auto& client) { return client->match(win); }));
2016-11-02 19:22:45 +00:00
2022-09-14 20:12:13 +00:00
if (old_size != m_clients.size()) {
2023-03-25 18:55:30 +00:00
reconfigure();
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
2022-09-24 11:20:02 +00:00
/**
* Remove all null pointers from client list.
*
* Removing clients is often done in two steps:
* 1. When removing a client during iteration, the unique_ptr is reset.
* 2. Afterwards all null pointers are removed from the list.
*/
2022-10-31 22:21:00 +00:00
void manager::clean_clients() {
2022-09-24 11:20:02 +00:00
m_clients.erase(
std::remove_if(m_clients.begin(), m_clients.end(), [](const auto& client) { return client.get() == nullptr; }));
}
2022-10-31 22:21:00 +00:00
bool manager::change_visibility(bool visible) {
if (m_hidden == !visible) {
return false;
}
2022-09-14 19:53:02 +00:00
m_log.trace("tray: visibility_change (new_state)", visible ? "visible" : "hidden");
m_hidden = !visible;
for (auto& client : m_clients) {
2022-09-24 11:20:02 +00:00
client->hidden(m_hidden);
client->ensure_state();
}
if (!m_hidden) {
redraw_clients();
}
m_connection.flush();
return true;
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_EXPOSE
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::expose& evt) {
2022-09-11 19:47:50 +00:00
if (is_active() && !m_clients.empty() && evt->count == 0) {
redraw_clients();
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_CLIENT_MESSAGE
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::client_message& evt) {
2022-09-11 19:47:50 +00:00
if (!is_active()) {
2016-11-02 19:22:45 +00:00
return;
2022-09-11 19:47:50 +00:00
}
// Our selection owner window was deleted
if (evt->type == WM_PROTOCOLS && evt->data.data32[0] == WM_DELETE_WINDOW && evt->window == m_opts.selection_owner) {
2022-09-14 19:53:02 +00:00
m_log.notice("Received WM_DELETE for selection owner");
deactivate();
} else if (evt->type == _NET_SYSTEM_TRAY_OPCODE && evt->format == 32) {
2016-11-26 05:13:20 +00:00
m_log.trace("tray: Received client_message");
2022-09-11 19:47:50 +00:00
// Docking request
2016-11-26 05:13:20 +00:00
if (SYSTEM_TRAY_REQUEST_DOCK == evt->data.data32[1]) {
2022-02-27 19:30:23 +00:00
xcb_window_t win = evt->data.data32[2];
2022-09-11 19:47:50 +00:00
if (is_embedded(win)) {
m_log.warn("Tray client %s already embedded, ignoring request...", m_connection.id(win));
2022-09-11 19:47:50 +00:00
} else {
process_docking_request(win);
}
2016-11-26 05:13:20 +00:00
}
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* 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.
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::configure_request& evt) {
2022-09-11 19:47:50 +00:00
if (is_active() && is_embedded(evt->window)) {
2022-09-14 19:53:02 +00:00
auto client = find_client(evt->window);
try {
2022-09-14 19:53:02 +00:00
m_log.trace("%s: Client configure request", client->name());
client->configure_notify();
} catch (const xpp::x::error::window& err) {
2022-09-14 19:53:02 +00:00
m_log.err("Failed to reconfigure %s, removing... (%s)", client->name(), err.what());
remove_client(evt->window);
}
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
2022-10-31 22:21:00 +00:00
* @see manager::handle(const evt::configure_request&);
2016-11-02 19:22:45 +00:00
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::resize_request& evt) {
2022-09-11 19:47:50 +00:00
if (is_active() && is_embedded(evt->window)) {
2022-09-14 19:53:02 +00:00
auto client = find_client(evt->window);
try {
2022-09-14 19:53:02 +00:00
m_log.trace("%s: Client resize request", client->name());
client->configure_notify();
} catch (const xpp::x::error::window& err) {
2022-09-14 19:53:02 +00:00
m_log.err("Failed to reconfigure %s, removing... (%s)", client->name(), err.what());
remove_client(evt->window);
}
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_SELECTION_CLEAR
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::selection_clear& evt) {
2022-09-11 19:47:50 +00:00
if (is_inactive()) {
2016-11-02 19:22:45 +00:00
return;
} else if (evt->selection != m_atom) {
2016-11-02 19:22:45 +00:00
return;
} else if (evt->owner != m_opts.selection_owner) {
2016-11-02 19:22:45 +00:00
return;
}
2016-11-02 19:22:45 +00:00
2022-09-11 19:47:50 +00:00
m_log.warn("Lost systray selection, deactivating...");
wait_for_selection(m_connection.get_selection_owner(m_atom).owner());
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_PROPERTY_NOTIFY
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::property_notify& evt) {
2022-09-11 19:47:50 +00:00
if (!is_active()) {
2016-11-02 19:22:45 +00:00
return;
}
if (evt->atom != _XEMBED_INFO) {
return;
}
2016-11-26 05:13:20 +00:00
auto client = find_client(evt->window);
2016-11-02 19:22:45 +00:00
if (!client) {
return;
}
2016-11-02 19:22:45 +00:00
2022-09-19 19:39:11 +00:00
m_log.trace("%s: _XEMBED_INFO", client->name());
2016-11-02 19:22:45 +00:00
if (evt->state == XCB_PROPERTY_NEW_VALUE) {
m_log.trace("tray: _XEMBED_INFO value has changed");
}
2016-11-02 19:22:45 +00:00
2017-03-15 22:52:59 +00:00
try {
client->query_xembed();
2017-03-15 22:52:59 +00:00
} catch (const xpp::x::error::window& err) {
2022-09-14 19:53:02 +00:00
m_log.err("Failed to query _XEMBED_INFO, removing %s ... (%s)", client->name(), err.what());
2022-09-14 20:12:13 +00:00
remove_client(*client);
2017-03-15 22:52:59 +00:00
return;
}
2016-11-02 19:22:45 +00:00
2022-09-14 20:12:13 +00:00
client->ensure_state();
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_REPARENT_NOTIFY
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::reparent_notify& evt) {
2022-09-11 19:47:50 +00:00
if (!is_active()) {
return;
}
auto client = find_client(evt->window);
if (!client) {
return;
}
2022-09-11 19:47:50 +00:00
// Tray client was reparented to another window
if (evt->parent != client->embedder()) {
2022-09-14 19:53:02 +00:00
m_log.info("%s: Received reparent_notify for client, remove...", client->name());
remove_client(*client);
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_DESTROY_NOTIFY
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::destroy_notify& evt) {
2022-09-11 19:47:50 +00:00
if (is_waiting() && evt->window == m_othermanager) {
2016-12-21 22:22:02 +00:00
m_log.info("Systray selection unmanaged... re-activating");
2016-12-14 10:34:09 +00:00
activate();
2022-09-11 19:47:50 +00:00
} else if (is_active() && is_embedded(evt->window)) {
m_log.info("tray: Received destroy_notify for client, remove...");
remove_client(evt->window);
redraw_clients();
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_MAP_NOTIFY
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::map_notify& evt) {
2022-09-11 19:47:50 +00:00
if (is_active() && evt->window == m_opts.selection_owner) {
2022-09-14 19:53:02 +00:00
m_log.trace("tray: Received map_notify for selection owner");
redraw_clients();
} else if (is_embedded(evt->window)) {
2022-08-28 12:49:09 +00:00
auto client = find_client(evt->window);
2022-09-14 20:12:13 +00:00
// If we received a notification on the wrapped window, we don't want to do anything.
if (client->embedder() != evt->window) {
return;
}
2022-09-14 19:53:02 +00:00
m_log.trace("%s: Received map_notify", client->name());
2022-08-28 12:49:09 +00:00
if (!client->mapped()) {
client->mapped(true);
2016-12-03 19:26:29 +00:00
reconfigure();
}
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
/**
* Event callback : XCB_UNMAP_NOTIFY
*/
2022-10-31 22:21:00 +00:00
void manager::handle(const evt::unmap_notify& evt) {
2022-09-11 19:47:50 +00:00
if (is_active() && is_embedded(evt->window)) {
2022-08-28 12:49:09 +00:00
auto client = find_client(evt->window);
2022-09-14 20:12:13 +00:00
// If we received a notification on the wrapped window, we don't want to do anything.
if (client->embedder() != evt->window) {
return;
}
2022-09-14 19:53:02 +00:00
m_log.trace("%s: Received unmap_notify", client->name());
2022-08-28 12:49:09 +00:00
if (client->mapped()) {
client->mapped(false);
reconfigure();
}
2016-11-02 19:22:45 +00:00
}
2016-12-03 19:26:29 +00:00
}
2016-11-02 19:22:45 +00:00
2022-10-31 22:21:00 +00:00
bool manager::on(const signals::ui::update_background&) {
redraw_clients();
return false;
}
2022-10-31 22:21:00 +00:00
bool manager::on(const signals::ui_tray::tray_pos_change& evt) {
2022-08-28 12:49:09 +00:00
int new_x = std::max(0, std::min(evt.cast(), (int)(m_bar_opts.size.w - m_tray_width)));
2022-08-28 12:49:09 +00:00
if (new_x != m_pos.x) {
m_pos.x = new_x;
reconfigure();
}
return true;
}
2022-10-31 22:21:00 +00:00
} // namespace tray
2016-11-19 05:22:44 +00:00
POLYBAR_NS_END