1
0
Fork 0
mirror of https://github.com/polybar/polybar.git synced 2024-10-27 05:23:39 -04:00
polybar/src/x11/tray_manager.cpp

1136 lines
31 KiB
C++
Raw Normal View History

#include "x11/tray_manager.hpp"
#include <xcb/xcb_image.h>
2016-11-20 17:04:31 -05:00
#include <thread>
#include "cairo/context.hpp"
#include "cairo/surface.hpp"
#include "components/config.hpp"
2016-11-25 07:55:15 -05:00
#include "errors.hpp"
#include "events/signal.hpp"
2016-11-02 15:22:45 -04:00
#include "utils/color.hpp"
2016-11-20 17:04:31 -05: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 15:08:57 -05:00
#include "utils/units.hpp"
#include "x11/background_manager.hpp"
2017-01-24 02:49:27 -05:00
#include "x11/ewmh.hpp"
#include "x11/icccm.hpp"
2016-11-02 15:22:45 -04:00
#include "x11/window.hpp"
2016-11-24 22:10:26 -05:00
#include "x11/winspec.hpp"
#include "x11/xembed.hpp"
2016-11-02 15:22:45 -04:00
/*
* Tray implementation according to the System Tray Protocol.
*
* Ref: https://specifications.freedesktop.org/systemtray-spec/systemtray-spec-latest.html
*/
2016-11-26 09:42:48 -05:00
// ====================================================================================================
//
// TODO: 32-bit visual
//
// _NET_SYSTEM_TRAY_VISUAL visual_id VISUALID/32
//
// The property should be set by the tray manager to indicate the preferred visual for icon windows.
//
// To avoid ambiguity about the colormap to use this visual must either be the default visual for
// the screen or it must be a TrueColor visual. If this property is set to a visual with an alpha
// channel, the tray manager must use the Composite extension to composite the icon against the
// background using PictOpOver.
//
// ====================================================================================================
2016-11-19 00:22:44 -05:00
POLYBAR_NS
2016-11-02 15:22:45 -04:00
2016-12-09 03:02:47 -05:00
/**
* Create instance
*/
2022-03-06 19:24:15 -05:00
tray_manager::make_type tray_manager::make(const bar_settings& bar_opts) {
return std::make_unique<tray_manager>(
2022-03-06 19:24:15 -05:00
connection::make(), signal_emitter::make(), logger::make(), background_manager::make(), bar_opts);
2016-12-09 03:02:47 -05:00
}
tray_manager::tray_manager(connection& conn, signal_emitter& emitter, const logger& logger, background_manager& back,
2022-03-06 19:24:15 -05:00
const bar_settings& bar_opts)
: m_connection(conn), m_sig(emitter), m_log(logger), m_background_manager(back), m_bar_opts(bar_opts) {
2016-12-14 05:34:09 -05:00
m_connection.attach_sink(this, SINK_PRIORITY_TRAY);
}
2016-11-02 15:22:45 -04:00
tray_manager::~tray_manager() {
if (m_delaythread.joinable()) {
m_delaythread.join();
}
2016-12-14 05:34:09 -05:00
m_connection.detach_sink(this, SINK_PRIORITY_TRAY);
deactivate();
}
void tray_manager::setup() {
const config& conf = config::make();
auto bs = conf.section();
string position;
try {
position = conf.get(bs, "tray-position");
} catch (const key_error& err) {
return m_log.info("Disabling tray manager (reason: missing `tray-position`)");
}
if (position == "left") {
m_opts.align = alignment::LEFT;
} else if (position == "right") {
m_opts.align = alignment::RIGHT;
} else if (position == "center") {
m_opts.align = alignment::CENTER;
} else if (position == "adaptive") {
m_opts.adaptive = true;
2017-01-27 07:33:39 -05:00
} else if (position != "none") {
return m_log.err("Disabling tray manager (reason: Invalid position \"" + position + "\")");
2017-01-27 07:33:39 -05:00
} else {
return;
}
2022-02-27 15:36:16 -05:00
auto inner_area = m_bar_opts.inner_area();
m_opts.win_size.w = 0;
m_opts.win_size.h = inner_area.height;
2016-11-02 15:22:45 -04:00
2022-02-27 15:36:16 -05:00
m_opts.detached = conf.get(bs, "tray-detached", false);
unsigned int client_height = inner_area.height;
2016-11-02 15:22:45 -04:00
2017-01-19 05:11:28 -05:00
auto maxsize = conf.get<unsigned int>(bs, "tray-maxsize", 16);
2022-02-27 15:36:16 -05:00
if (client_height > maxsize) {
m_opts.spacing += (client_height - maxsize) / 2;
client_height = maxsize;
}
2016-11-02 15:22:45 -04:00
m_opts.width_max = m_bar_opts.size.w;
2022-02-27 15:36:16 -05:00
m_opts.client_size = {client_height, client_height};
2016-11-02 15:22:45 -04:00
// Apply user-defined scaling
2017-01-19 05:11:28 -05:00
auto scale = conf.get(bs, "tray-scale", 1.0);
2022-02-27 15:36:16 -05:00
m_opts.client_size.w *= scale;
m_opts.win_size.h *= scale;
m_opts.pos.x = inner_area.x + [&]() -> int {
switch (m_opts.align) {
case alignment::LEFT:
return 0;
case alignment::CENTER:
return inner_area.width / 2 - m_opts.client_size.w / 2;
case alignment::RIGHT:
return inner_area.width;
default:
return 0;
}
}();
m_opts.pos.y = inner_area.y;
2016-11-02 15:22:45 -04:00
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);
2016-11-02 15:22:45 -04:00
if (m_opts.background.alpha_i() != 255) {
m_log.trace("tray: enable transparency");
m_opts.transparent = true;
}
2016-11-02 15:22:45 -04:00
// Add user-defined padding
2017-01-19 05:11:28 -05:00
m_opts.spacing += conf.get<unsigned int>(bs, "tray-padding", 0);
2016-11-25 07:55:15 -05:00
// Add user-defiend offset
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 15:08:57 -05:00
auto offset_x = conf.get(bs, "tray-offset-x", percentage_with_offset{});
auto offset_y = conf.get(bs, "tray-offset-y", percentage_with_offset{});
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 15:08:57 -05:00
int max_x;
int max_y;
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 15:08:57 -05:00
if (m_opts.detached) {
max_x = m_bar_opts.monitor->w;
max_y = m_bar_opts.monitor->h;
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 15:08:57 -05:00
} else {
max_x = inner_area.width;
max_y = inner_area.height;
}
2022-02-27 15:36:16 -05:00
m_opts.offset.x = units_utils::percentage_with_offset_to_pixel(offset_x, max_x, m_bar_opts.dpi_x);
m_opts.offset.y = units_utils::percentage_with_offset_to_pixel(offset_y, max_y, m_bar_opts.dpi_y);
m_opts.pos.x += m_opts.offset.x;
m_opts.pos.y += m_opts.offset.y;
2022-02-27 15:36:16 -05:00
m_opts.bar_window = m_bar_opts.window;
// Activate the tray manager
query_atom();
activate();
2016-11-02 15:22:45 -04:00
}
/**
* Get the settings container
*/
2016-12-03 14:26:29 -05:00
const tray_settings tray_manager::settings() const {
return m_opts;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Activate systray management
*/
2016-12-03 14:26:29 -05:00
void tray_manager::activate() {
2016-11-02 15:22:45 -04:00
if (m_activated) {
return;
}
2016-11-24 22:10:26 -05:00
m_log.info("Activating tray manager");
2016-11-02 15:22:45 -04:00
m_activated = true;
m_opts.running = true;
m_sig.attach(this);
2016-11-02 15:22:45 -04:00
try {
create_window();
create_bg();
set_wm_hints();
set_tray_colors();
} catch (const exception& err) {
m_log.err(err.what());
2016-11-24 22:10:26 -05:00
m_log.err("Cannot activate tray manager... failed to setup window");
m_activated = false;
return;
}
2016-11-02 15:22:45 -04:00
// Attempt to get control of the systray selection then
// notify clients waiting for a manager.
acquire_selection();
2016-12-21 17:22:02 -05:00
if (!m_acquired_selection) {
deactivate();
return;
}
2016-11-25 07:55:15 -05:00
// Send delayed notification
2016-12-14 05:34:09 -05:00
if (!m_firstactivation) {
notify_clients();
2016-12-21 17:22:02 -05:00
} else {
notify_clients_delayed();
2016-11-25 07:55:15 -05:00
}
2016-12-14 05:34:09 -05:00
m_firstactivation = false;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Deactivate systray management
*/
2016-12-03 14:26:29 -05:00
void tray_manager::deactivate(bool clear_selection) {
2016-11-02 15:22:45 -04:00
if (!m_activated) {
return;
}
2016-11-24 22:10:26 -05:00
m_log.info("Deactivating tray manager");
2016-11-02 15:22:45 -04:00
m_activated = false;
m_opts.running = false;
2016-11-02 15:22:45 -04:00
m_sig.detach(this);
2016-11-02 15:22:45 -04:00
2016-11-25 07:55:15 -05:00
if (!m_connection.connection_has_error() && clear_selection && m_acquired_selection) {
2016-11-24 22:10:26 -05:00
m_log.trace("tray: Unset selection owner");
m_connection.set_selection_owner(XCB_NONE, m_atom, XCB_CURRENT_TIME);
2016-11-02 15:22:45 -04:00
}
m_log.trace("tray: Unembed clients");
m_clients.clear();
if (m_tray) {
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Destroy window");
m_connection.destroy_window(m_tray);
2022-03-06 19:24:15 -05:00
m_tray = 0;
2016-11-02 15:22:45 -04:00
}
2022-03-06 19:24:15 -05:00
m_context.reset();
m_surface.reset();
2022-03-06 19:24:15 -05:00
if (m_pixmap) {
m_connection.free_pixmap(m_pixmap);
2022-03-06 19:24:15 -05:00
m_pixmap = 0;
}
2022-03-06 19:24:15 -05:00
if (m_gc) {
2022-02-27 14:30:23 -05:00
m_connection.free_gc(m_gc);
2022-03-06 19:24:15 -05:00
m_gc = 0;
}
2022-02-27 15:36:16 -05:00
m_opts.win_size.w = 0;
m_opts.num_clients = 0;
2016-11-25 07:55:15 -05:00
m_acquired_selection = false;
2016-12-03 14:26:29 -05:00
m_mapped = false;
2016-11-02 15:22:45 -04:00
m_connection.flush();
2017-01-25 17:33:26 -05:00
m_sig.emit(signals::eventqueue::notify_forcechange{});
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Reconfigure tray
*/
2016-12-03 14:26:29 -05:00
void tray_manager::reconfigure() {
2017-01-25 17:33:26 -05:00
if (!m_tray) {
2016-11-02 15:22:45 -04:00
return;
2017-01-25 17:33:26 -05:00
} else if (m_mtx.try_lock()) {
std::unique_lock<mutex> guard(m_mtx, std::adopt_lock);
2016-11-02 15:22:45 -04:00
2017-01-25 17:33:26 -05:00
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());
}
2022-02-27 15:36:16 -05:00
m_opts.num_clients = mapped_clients();
2017-01-25 17:33:26 -05:00
guard.unlock();
refresh_window();
m_connection.flush();
}
2016-11-02 15:22:45 -04:00
2017-01-25 17:33:26 -05:00
m_sig.emit(signals::eventqueue::notify_forcechange{});
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Reconfigure container window
*/
2016-12-03 14:26:29 -05:00
void tray_manager::reconfigure_window() {
m_log.trace("tray: Reconfigure window (mapped=%i, clients=%i)", static_cast<bool>(m_mapped), m_clients.size());
2016-12-03 17:01:21 -05:00
if (!m_tray) {
return;
}
2022-02-27 15:36:16 -05:00
bool has_clients = has_mapped_clients();
if (!has_clients && m_mapped) {
m_log.trace("tray: Reconfigure window / unmap");
m_connection.unmap_window_checked(m_tray);
2022-02-27 15:36:16 -05:00
} else if (has_clients && !m_mapped && !m_hidden) {
m_log.trace("tray: Reconfigure window / map");
m_connection.map_window_checked(m_tray);
}
2016-11-02 15:22:45 -04:00
auto width = calculate_w();
2022-02-27 15:36:16 -05:00
m_opts.win_size.w = width;
2016-11-02 15:22:45 -04:00
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) {
2022-02-27 15:36:16 -05:00
auto x = calculate_x(width);
m_log.trace("tray: New window values, width=%d, x=%d", width, x);
2022-03-06 15:51:07 -05:00
uint32_t mask = 0;
std::array<uint32_t, 32> values{};
2016-12-15 03:29:14 -05:00
xcb_params_configure_window_t params{};
XCB_AUX_ADD_PARAM(&mask, &params, width, width);
XCB_AUX_ADD_PARAM(&mask, &params, x, x);
connection::pack_values(mask, &params, values);
2022-03-06 15:51:07 -05:00
m_connection.configure_window_checked(m_tray, mask, values.data());
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Reconfigure clients
*/
2016-12-03 14:26:29 -05:00
void tray_manager::reconfigure_clients() {
m_log.trace("tray: Reconfigure clients");
2017-01-19 05:11:28 -05:00
int x = m_opts.spacing;
2016-11-02 15:22:45 -04:00
for (auto it = m_clients.rbegin(); it != m_clients.rend(); it++) {
auto client = *it;
try {
client->ensure_state();
client->reconfigure(x, calculate_client_y());
2022-02-27 15:36:16 -05:00
x += m_opts.client_size.w + m_opts.spacing;
2016-11-02 15:22:45 -04:00
} catch (const xpp::x::error::window& err) {
remove_client(client, false);
}
}
m_sig.emit(signals::ui_tray::tray_width_change{calculate_w()});
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Reconfigure root pixmap
*/
2022-03-06 17:43:06 -05:00
void tray_manager::reconfigure_bg() {
if (!m_opts.transparent || m_clients.empty() || !m_mapped) {
return;
};
2022-03-06 17:43:06 -05:00
m_log.trace("tray: Reconfigure bg");
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_log.err("tray: no root surface");
}
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();
2016-12-03 14:26:29 -05:00
}
/**
* Refresh the bar window by clearing it along with each client window
*/
2016-12-03 14:26:29 -05:00
void tray_manager::refresh_window() {
if (!m_activated || !m_mapped || !m_mtx.try_lock()) {
return;
}
2016-12-22 23:18:58 -05:00
std::lock_guard<mutex> 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) {
2017-01-24 02:01:04 -05:00
xcb_rectangle_t rect{0, 0, static_cast<uint16_t>(width), static_cast<uint16_t>(height)};
m_connection.poly_fill_rectangle(m_pixmap, m_gc, 1, &rect);
}
if (m_surface) {
m_surface->flush();
}
m_connection.clear_area(0, m_tray, 0, 0, width, height);
for (auto&& client : m_clients) {
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();
2022-02-27 15:36:16 -05:00
if (has_mapped_clients()) {
m_opts.win_size.w = width;
} else {
2022-02-27 15:36:16 -05:00
m_opts.win_size.w = 0;
}
2016-12-03 14:26:29 -05:00
}
/**
* Redraw window
*/
2022-03-06 17:43:06 -05:00
void tray_manager::redraw_window() {
2017-01-25 17:33:26 -05:00
m_log.info("Redraw tray container (id=%s)", m_connection.id(m_tray));
2022-03-06 17:43:06 -05:00
reconfigure_bg();
2016-12-03 14:26:29 -05:00
refresh_window();
}
2016-11-02 15:22:45 -04:00
/**
* Find the systray selection atom
*/
2016-12-03 14:26:29 -05:00
void tray_manager::query_atom() {
2016-11-02 15:22:45 -04: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 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Create tray window
*/
2016-12-03 14:26:29 -05:00
void tray_manager::create_window() {
2016-11-24 22:10:26 -05:00
m_log.trace("tray: Create tray window");
2016-11-02 15:22:45 -04:00
2016-11-25 02:42:31 -05:00
// clang-format off
2022-02-27 14:30:23 -05:00
auto win = winspec(m_connection)
2016-11-24 22:10:26 -05:00
<< 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)
<< cw_parent(m_opts.bar_window);
2016-11-25 02:42:31 -05:00
// clang-format on
2016-11-02 15:22:45 -04:00
if (!m_opts.transparent) {
2016-11-24 22:10:26 -05:00
win << cw_params_back_pixel(m_opts.background);
win << cw_params_border_pixel(m_opts.background);
}
m_tray = win << cw_flush(true);
2016-11-24 22:10:26 -05:00
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);
}
2017-01-19 05:11:28 -05:00
const unsigned int shadow{0};
m_connection.change_property(XCB_PROP_MODE_REPLACE, m_tray, _COMPTON_SHADOW, XCB_ATOM_CARDINAL, 32, 1, &shadow);
2016-12-03 14:26:29 -05:00
}
/**
* Create tray window background components
*/
2022-02-27 14:30:23 -05:00
void tray_manager::create_bg() {
if (!m_opts.transparent) {
return;
}
2022-02-27 14:30:23 -05:00
if (m_pixmap && m_gc && m_surface && m_context) {
return;
}
auto w = m_opts.width_max;
auto h = calculate_h();
2016-11-02 15:22:45 -04:00
2017-01-24 02:49:27 -05:00
if (!m_pixmap) {
try {
2022-03-06 19:24:15 -05:00
auto depth = m_connection.get_geometry(m_bar_opts.window)->depth;
2017-01-24 02:49:27 -05:00
m_pixmap = m_connection.generate_id();
2022-03-06 19:24:15 -05:00
m_connection.create_pixmap_checked(depth, m_pixmap, m_tray, w, h);
2017-01-24 02:49:27 -05:00
} 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{};
2022-03-06 15:51:07 -05:00
uint32_t mask = 0;
std::array<uint32_t, 32> values{};
2017-01-24 02:49:27 -05:00
XCB_AUX_ADD_PARAM(&mask, &params, graphics_exposures, 1);
connection::pack_values(mask, &params, values);
m_gc = m_connection.generate_id();
2022-03-06 15:51:07 -05:00
m_connection.create_gc_checked(m_gc, m_pixmap, mask, values.data());
2017-01-24 02:49:27 -05:00
} 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(), m_connection.screen()->root_visual);
if (!visual) {
return m_log.err("Failed to get root visual for tray background");
}
m_surface = make_unique<cairo::xcb_surface>(m_connection, m_pixmap, visual, w, h);
}
if (!m_context) {
m_context = make_unique<cairo::context>(*m_surface, m_log);
m_context->clear();
*m_context << CAIRO_OPERATOR_SOURCE << m_opts.background;
m_context->paint();
}
try {
2016-11-26 00:13:20 -05:00
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());
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Set window WM hints
*/
void tray_manager::set_wm_hints() {
2017-01-19 05:11:28 -05:00
const unsigned int visual{m_connection.screen()->root_visual};
const unsigned int orientation{_NET_SYSTEM_TRAY_ORIENTATION_HORZ};
2017-01-24 02:49:27 -05:00
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);
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Set window WM_PROTOCOLS");
2017-01-24 02:49:27 -05:00
icccm_util::set_wm_protocols(m_connection, m_tray, {WM_DELETE_WINDOW, WM_TAKE_FOCUS});
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Set window _NET_WM_WINDOW_TYPE");
2017-01-24 02:49:27 -05:00
ewmh_util::set_wm_window_type(m_tray, {_NET_WM_WINDOW_TYPE_DOCK, _NET_WM_WINDOW_TYPE_NORMAL});
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Set window _NET_WM_STATE");
2017-01-24 02:49:27 -05:00
ewmh_util::set_wm_state(m_tray, {_NET_WM_STATE_SKIP_TASKBAR});
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Set window _NET_WM_PID");
2017-01-24 02:49:27 -05:00
ewmh_util::set_wm_pid(m_tray);
2016-11-02 15:22:45 -04:00
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);
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* 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);
2016-11-02 15:22:45 -04: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 15:22:45 -04:00
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] = {
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 15:08:57 -05:00
r16, g16, b16, // normal
r16, g16, b16, // error
r16, g16, b16, // warning
r16, g16, b16, // success
2016-11-02 15:22:45 -04:00
};
m_connection.change_property(
XCB_PROP_MODE_REPLACE, m_tray, _NET_SYSTEM_TRAY_COLORS, XCB_ATOM_CARDINAL, 32, 12, colors);
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Acquire the systray selection
*/
2016-12-03 14:26:29 -05:00
void tray_manager::acquire_selection() {
2016-12-14 05:34:09 -05:00
m_othermanager = XCB_NONE;
xcb_window_t owner;
try {
owner = m_connection.get_selection_owner(m_atom).owner<xcb_window_t>();
} catch (const exception& err) {
return;
}
2016-11-02 15:22:45 -04:00
if (owner == m_tray) {
2016-12-14 05:34:09 -05:00
m_log.trace("tray: Already managing the systray selection");
m_acquired_selection = true;
2016-12-21 17:22:02 -05:00
} 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;
2016-11-25 07:55:15 -05:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Notify pending clients about the new systray MANAGER
*/
2016-12-03 14:26:29 -05:00
void tray_manager::notify_clients() {
2016-11-25 07:55:15 -05:00
if (m_activated) {
m_log.info("Notifying pending tray clients");
auto message = m_connection.make_client_message(MANAGER, m_connection.root());
2022-03-06 10:40:42 -05:00
message.data.data32[0] = XCB_CURRENT_TIME;
message.data.data32[1] = m_atom;
message.data.data32[2] = m_tray;
2016-11-25 07:55:15 -05:00
m_connection.send_client_message(message, m_connection.root());
}
2016-12-03 14:26:29 -05:00
}
2016-11-25 07:55:15 -05:00
/**
* Send delayed notification to pending clients
*/
2016-12-14 05:34:09 -05:00
void tray_manager::notify_clients_delayed() {
if (m_delaythread.joinable()) {
2016-11-25 07:55:15 -05:00
m_delaythread.join();
}
2016-12-14 05:34:09 -05:00
m_delaythread = thread([this]() {
this_thread::sleep_for(1s);
2016-11-25 07:55:15 -05:00
notify_clients();
2016-12-14 05:34:09 -05:00
});
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Track changes to the given selection owner
* If it gets destroyed or goes away we can reactivate the tray_manager
2016-11-02 15:22:45 -04:00
*/
2016-12-03 14:26:29 -05:00
void tray_manager::track_selection_owner(xcb_window_t owner) {
2016-12-14 05:34:09 -05:00
if (owner != XCB_NONE) {
2016-11-26 00:13:20 -05:00
m_log.trace("tray: Listen for events on the new selection window");
2017-01-19 05:11:28 -05:00
const unsigned int mask{XCB_CW_EVENT_MASK};
const unsigned int values[]{XCB_EVENT_MASK_STRUCTURE_NOTIFY};
2016-11-26 00:13:20 -05:00
m_connection.change_window_attributes(owner, mask, values);
2016-11-25 07:55:15 -05:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Process client docking request
*/
2016-12-03 14:26:29 -05:00
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));
2022-02-27 15:36:16 -05:00
m_clients.emplace_back(std::make_shared<tray_client>(m_connection, win, m_opts.client_size));
auto& client = m_clients.back();
2016-11-02 15:22:45 -04:00
try {
client->query_xembed();
} catch (const xpp::x::error::window& err) {
2017-03-15 18:52:59 -04:00
m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
remove_client(win, true);
2016-11-02 15:22:45 -04:00
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");
}
2016-11-02 15:22:45 -04:00
try {
const unsigned int mask = XCB_CW_EVENT_MASK;
const unsigned int values[]{XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_STRUCTURE_NOTIFY};
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Update client window");
m_connection.change_window_attributes_checked(client->window(), mask, values);
2016-11-02 15:22:45 -04:00
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());
// 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);
2016-11-02 15:22:45 -04:00
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());
}
2016-11-02 15:22:45 -04:00
if (!client->is_xembed_supported() || client->get_xembed().is_mapped()) {
2016-11-02 15:22:45 -04:00
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);
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Calculate x position of tray window
*/
int tray_manager::calculate_x(unsigned int width) const {
2022-02-27 15:36:16 -05:00
auto x = m_opts.pos.x;
2016-11-25 07:55:15 -05:00
if (m_opts.align == alignment::RIGHT) {
2022-02-27 15:36:16 -05:00
x -= ((m_opts.client_size.w + m_opts.spacing) * m_clients.size() + m_opts.spacing);
2016-11-25 07:55:15 -05:00
} else if (m_opts.align == alignment::CENTER) {
2022-02-27 15:36:16 -05:00
x -= (width / 2) - (m_opts.client_size.w / 2);
2016-11-25 07:55:15 -05:00
}
2016-11-02 15:22:45 -04:00
return x;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Calculate y position of tray window
*/
2022-02-27 14:30:23 -05:00
int tray_manager::calculate_y() const {
2022-02-27 15:36:16 -05:00
return m_opts.pos.y;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Calculate width of tray window
*/
unsigned short int tray_manager::calculate_w() const {
2017-01-19 05:11:28 -05:00
unsigned int width = m_opts.spacing;
2017-01-25 17:33:26 -05:00
unsigned int count{0};
2016-11-02 15:22:45 -04:00
for (auto&& client : m_clients) {
if (client->mapped()) {
count++;
2022-02-27 15:36:16 -05:00
width += m_opts.spacing + m_opts.client_size.w;
2016-11-02 15:22:45 -04:00
}
}
return count ? width : 0;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Calculate height of tray window
*/
unsigned short int tray_manager::calculate_h() const {
2022-02-27 15:36:16 -05:00
return m_opts.win_size.h;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Calculate x position of client window
*/
2017-01-19 05:11:28 -05:00
int tray_manager::calculate_client_x(const xcb_window_t& win) {
2017-01-25 17:33:26 -05:00
for (unsigned int i = 0; i < m_clients.size(); i++) {
2016-11-25 07:55:15 -05:00
if (m_clients[i]->match(win)) {
2022-02-27 15:36:16 -05:00
return m_opts.spacing + m_opts.client_size.w * i;
2016-11-25 07:55:15 -05:00
}
}
return m_opts.spacing;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Calculate y position of client window
*/
2017-01-19 05:11:28 -05:00
int tray_manager::calculate_client_y() {
2022-02-27 15:36:16 -05:00
return (m_opts.win_size.h - m_opts.client_size.h) / 2;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* 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<tray_client> client) { return client->match(win); });
}
2016-11-02 15:22:45 -04:00
/**
* Find tray client by window
*/
2016-12-03 14:26:29 -05:00
shared_ptr<tray_client> tray_manager::find_client(const xcb_window_t& win) const {
2016-11-25 07:55:15 -05:00
for (auto&& client : m_clients) {
2016-11-02 15:22:45 -04:00
if (client->match(win)) {
2022-03-06 11:44:48 -05:00
return client;
2016-11-02 15:22:45 -04:00
}
2016-11-25 07:55:15 -05:00
}
return nullptr;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Remove tray client
2016-11-02 15:22:45 -04:00
*/
2016-12-03 14:26:29 -05:00
void tray_manager::remove_client(shared_ptr<tray_client>& 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<tray_client> client) { return client->match(win); }));
2016-11-02 15:22:45 -04:00
if (reconfigure) {
tray_manager::reconfigure();
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Get number of mapped clients
*/
2022-02-27 15:36:16 -05:00
int tray_manager::mapped_clients() const {
int mapped_clients = 0;
2016-11-02 15:22:45 -04:00
for (auto&& client : m_clients) {
if (client->mapped()) {
mapped_clients++;
}
}
return mapped_clients;
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
2022-02-27 15:36:16 -05:00
bool tray_manager::has_mapped_clients() const {
for (auto&& client : m_clients) {
if (client->mapped()) {
return true;
}
}
return false;
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_EXPOSE
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::expose& evt) {
if (m_activated && !m_clients.empty() && evt->count == 0) {
redraw_window();
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_VISIBILITY_NOTIFY
*/
2016-12-03 14:26:29 -05:00
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();
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_CLIENT_MESSAGE
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::client_message& evt) {
2016-11-02 15:22:45 -04:00
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) {
2016-11-26 00:13:20 -05:00
m_log.trace("tray: Received client_message");
if (SYSTEM_TRAY_REQUEST_DOCK == evt->data.data32[1]) {
2022-02-27 14:30:23 -05:00
xcb_window_t win = evt->data.data32[2];
if (!is_embedded(win)) {
process_docking_request(win);
} else {
m_log.warn("Tray client %s already embedded, ignoring request...", m_connection.id(win));
}
2016-11-26 00:13:20 -05:00
}
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04: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.
*/
2016-12-03 14:26:29 -05:00
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);
}
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
2022-02-20 15:40:48 -05:00
* @see tray_manager::handle(const evt::configure_request&);
2016-11-02 15:22:45 -04:00
*/
2016-12-03 14:26:29 -05:00
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);
}
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_SELECTION_CLEAR
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::selection_clear& evt) {
if (!m_activated) {
2016-11-02 15:22:45 -04:00
return;
} else if (evt->selection != m_atom) {
2016-11-02 15:22:45 -04:00
return;
} else if (evt->owner != m_tray) {
2016-11-02 15:22:45 -04:00
return;
}
2016-11-02 15:22:45 -04:00
try {
m_log.warn("Lost systray selection, deactivating...");
2016-12-14 05:34:09 -05:00
m_othermanager = m_connection.get_selection_owner(m_atom).owner<xcb_window_t>();
2016-11-02 15:22:45 -04:00
track_selection_owner(m_othermanager);
} catch (const exception& err) {
2016-11-02 15:22:45 -04:00
m_log.err("Failed to get systray selection owner");
2016-12-14 05:34:09 -05:00
m_othermanager = XCB_NONE;
2016-11-02 15:22:45 -04:00
}
2016-11-24 22:10:26 -05:00
deactivate(false);
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_PROPERTY_NOTIFY
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::property_notify& evt) {
if (!m_activated) {
2016-11-02 15:22:45 -04:00
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)) {
2022-03-06 17:43:06 -05:00
redraw_window();
return;
}
if (evt->atom != _XEMBED_INFO) {
return;
}
2016-11-26 00:13:20 -05:00
auto client = find_client(evt->window);
2016-11-02 15:22:45 -04:00
if (!client) {
return;
}
2016-11-02 15:22:45 -04:00
m_log.trace("tray: _XEMBED_INFO: %s", m_connection.id(evt->window));
2016-11-02 15:22:45 -04:00
auto win = client->window();
2016-11-02 15:22:45 -04:00
if (evt->state == XCB_PROPERTY_NEW_VALUE) {
m_log.trace("tray: _XEMBED_INFO value has changed");
}
2016-11-02 15:22:45 -04:00
2017-03-15 18:52:59 -04:00
try {
client->query_xembed();
2017-03-15 18:52:59 -04:00
} catch (const xpp::x::error::window& err) {
m_log.err("Failed to query _XEMBED_INFO, removing client... (%s)", err.what());
remove_client(win, true);
2017-03-15 18:52:59 -04:00
return;
}
2016-11-02 15:22:45 -04:00
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();
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_REPARENT_NOTIFY
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::reparent_notify& evt) {
if (m_activated && is_embedded(evt->window) && evt->parent != m_tray) {
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Received reparent_notify for client, remove...");
remove_client(evt->window);
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_DESTROY_NOTIFY
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::destroy_notify& evt) {
2016-11-24 22:10:26 -05:00
if (m_activated && evt->window == m_tray) {
deactivate();
2016-12-14 05:34:09 -05:00
} else if (!m_activated && evt->window == m_othermanager) {
2016-12-21 17:22:02 -05:00
m_log.info("Systray selection unmanaged... re-activating");
2016-12-14 05:34:09 -05:00
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();
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_MAP_NOTIFY
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::map_notify& evt) {
if (m_activated && evt->window == m_tray) {
2016-12-03 14:26:29 -05:00
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);
2022-02-27 15:36:16 -05:00
if (mapped_clients() > m_opts.num_clients) {
2016-12-03 14:26:29 -05:00
reconfigure();
}
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* Event callback : XCB_UNMAP_NOTIFY
*/
2016-12-03 14:26:29 -05:00
void tray_manager::handle(const evt::unmap_notify& evt) {
if (m_activated && evt->window == m_tray) {
2016-11-02 15:22:45 -04:00
m_log.trace("tray: Received unmap_notify");
2016-12-03 14:26:29 -05:00
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);
2016-11-02 15:22:45 -04:00
}
2016-12-03 14:26:29 -05:00
}
2016-11-02 15:22:45 -04:00
/**
* 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.
*/
2017-01-25 17:33:26 -05:00
bool tray_manager::on(const signals::ui::visibility_change& evt) {
bool visible{evt.cast()};
2022-02-27 15:36:16 -05:00
bool has_clients = has_mapped_clients();
2016-12-15 15:00:17 -05:00
m_log.trace("tray: visibility_change (state=%i, activated=%i, mapped=%i, hidden=%i)", visible,
static_cast<bool>(m_activated), static_cast<bool>(m_mapped), static_cast<bool>(m_hidden));
m_hidden = !visible;
2016-12-15 15:00:17 -05:00
if (!m_activated) {
return false;
2022-02-27 15:36:16 -05:00
} else if (!m_hidden && !m_mapped && has_clients) {
m_connection.map_window(m_tray);
2022-02-27 15:36:16 -05:00
} else if ((!has_clients || m_hidden) && m_mapped) {
m_connection.unmap_window(m_tray);
2022-02-27 15:36:16 -05:00
} else if (m_mapped && !m_hidden && has_clients) {
redraw_window();
}
m_connection.flush();
return true;
}
2016-11-02 15:22:45 -04:00
2017-01-25 17:33:26 -05:00
bool tray_manager::on(const signals::ui::dim_window& evt) {
2016-12-16 01:10:45 -05:00
if (m_activated) {
2017-01-24 02:49:27 -05:00
ewmh_util::set_wm_window_opacity(m_tray, evt.cast() * 0xFFFFFFFF);
2016-12-16 01:10:45 -05:00
}
// let the event bubble
return false;
}
bool tray_manager::on(const signals::ui::update_background&) {
2022-03-06 17:43:06 -05:00
redraw_window();
return false;
}
bool tray_manager::on(const signals::ui_tray::tray_pos_change& evt) {
2022-02-27 15:36:16 -05:00
m_opts.pos.x = m_bar_opts.inner_area(true).x + evt.cast();
reconfigure_window();
return true;
}
2016-11-19 00:22:44 -05:00
POLYBAR_NS_END