Files
polybar/src/components/screen.cpp
patrick96 ecbe77bbda fix(monitor): Reload when any monitor changes
Before it would only reload if the size changed and even that was
reliable since the method relied on the order of the monitor list.

Now if the monitor list differs in any way (pos, dimension, primary,
output, name) a reload is issued
2020-04-21 23:59:01 +02:00

148 lines
4.1 KiB
C++

#include <csignal>
#include <algorithm>
#include <thread>
#include "components/config.hpp"
#include "components/logger.hpp"
#include "components/screen.hpp"
#include "components/types.hpp"
#include "events/signal.hpp"
#include "events/signal_emitter.hpp"
#include "x11/connection.hpp"
#include "x11/extensions/all.hpp"
#include "x11/registry.hpp"
#include "x11/types.hpp"
#include "x11/winspec.hpp"
POLYBAR_NS
using namespace signals::eventqueue;
/**
* Create instance
*/
screen::make_type screen::make() {
return factory_util::unique<screen>(connection::make(), signal_emitter::make(), logger::make(), config::make());
}
/**
* Construct screen instance
*/
screen::screen(connection& conn, signal_emitter& emitter, const logger& logger, const config& conf)
: m_connection(conn)
, m_sig(emitter)
, m_log(logger)
, m_conf(conf)
, m_root(conn.root())
, m_monitors(randr_util::get_monitors(m_connection, m_root, true, false))
, m_size({conn.screen()->width_in_pixels, conn.screen()->height_in_pixels}) {
// Check if the reloading has been disabled by the user
if (!m_conf.get("settings", "screenchange-reload", false)) {
return;
}
// clang-format off
m_proxy = winspec(m_connection)
<< cw_size(1U, 1U)
<< cw_pos(-1, -1)
<< cw_parent(m_root)
<< cw_params_override_redirect(true)
<< cw_params_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE)
<< cw_flush(true);
// clang-format on
// Update the root windows event mask
auto attributes = m_connection.get_window_attributes(m_root);
auto root_mask = attributes->your_event_mask;
attributes->your_event_mask = attributes->your_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;
m_connection.change_window_attributes(m_root, XCB_CW_EVENT_MASK, &attributes->your_event_mask);
// Receive randr events
m_connection.randr().select_input(m_proxy, XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE);
// Create window used as event proxy
m_connection.map_window(m_proxy);
m_connection.flush();
// Wait until the proxy window has been mapped
using evt = xcb_map_notify_event_t;
m_connection.wait_for_response<evt, XCB_MAP_NOTIFY>([&](const evt* evt) -> bool { return evt->window == m_proxy; });
// Restore the root windows event mask
m_connection.change_window_attributes(m_root, XCB_CW_EVENT_MASK, &root_mask);
// Finally attach the sink the process randr events
m_connection.attach_sink(this, SINK_PRIORITY_SCREEN);
}
/**
* Deconstruct screen instance
*/
screen::~screen() {
m_connection.detach_sink(this, SINK_PRIORITY_SCREEN);
if (m_proxy != XCB_NONE) {
m_connection.destroy_window(m_proxy);
}
}
/**
* Handle XCB_RANDR_SCREEN_CHANGE_NOTIFY events
*
* If any of the monitors have changed we raise USR1 to trigger a reload
*/
void screen::handle(const evt::randr_screen_change_notify& evt) {
if (m_sigraised || evt->request_window != m_proxy) {
return;
}
auto screen = m_connection.screen(true);
auto changed = false;
// We need to reload if the screen size changed as well
if (screen->width_in_pixels != m_size.w || screen->height_in_pixels != m_size.h) {
changed = true;
} else {
changed = have_monitors_changed();
}
if (changed) {
m_log.warn("randr_screen_change_notify (%ux%u)... reloading", evt->width, evt->height);
m_sig.emit(exit_reload{});
m_sigraised = true;
}
}
/**
* Checks if the stored monitor list is different from a newly fetched one
*
* Fetches the monitor list and compares it with the one stored
*/
bool screen::have_monitors_changed() const {
auto monitors = randr_util::get_monitors(m_connection, m_root, true, false);
if(monitors.size() != m_monitors.size()) {
return true;
}
for (auto m : m_monitors) {
auto it = std::find_if(monitors.begin(), monitors.end(),
[m] (auto& monitor) -> bool {
return m->equals(*monitor);
});
/*
* Every monitor in the stored list should also exist in the newly fetched
* list. If this holds then the two lists are equivalent since they have
* the same size
*/
if(it == monitors.end()) {
return true;
}
}
return false;
}
POLYBAR_NS_END