parent
9f4638f42c
commit
b25fa46ac2
5 changed files with 328 additions and 15 deletions
68
include/modules/xworkspaces.hpp
Normal file
68
include/modules/xworkspaces.hpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <bitset>
|
||||||
|
|
||||||
|
#include "components/config.hpp"
|
||||||
|
#include "modules/meta/static_module.hpp"
|
||||||
|
#include "x11/events.hpp"
|
||||||
|
#include "x11/ewmh.hpp"
|
||||||
|
#include "x11/icccm.hpp"
|
||||||
|
#include "x11/window.hpp"
|
||||||
|
|
||||||
|
POLYBAR_NS
|
||||||
|
|
||||||
|
class connection;
|
||||||
|
|
||||||
|
namespace modules {
|
||||||
|
enum class desktop_state {
|
||||||
|
NONE,
|
||||||
|
ACTIVE,
|
||||||
|
URGENT,
|
||||||
|
EMPTY,
|
||||||
|
OCCUPIED,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct desktop {
|
||||||
|
explicit desktop(string&& name, desktop_state state) : name(name), state(state) {}
|
||||||
|
string name;
|
||||||
|
desktop_state state{desktop_state::NONE};
|
||||||
|
label_t label;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module used to display EWMH desktops
|
||||||
|
*/
|
||||||
|
class xworkspaces_module : public static_module<xworkspaces_module>, public xpp::event::sink<evt::property_notify> {
|
||||||
|
public:
|
||||||
|
xworkspaces_module(const bar_settings& bar, const logger& logger, const config& config, string name);
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
void teardown();
|
||||||
|
void handle(const evt::property_notify& evt);
|
||||||
|
void update();
|
||||||
|
string get_output();
|
||||||
|
bool build(builder* builder, const string& tag) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void rebuild_desktops();
|
||||||
|
void set_current_desktop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr const char* DEFAULT_ICON{"icon-default"};
|
||||||
|
static constexpr const char* DEFAULT_LABEL{"%icon% %name%"};
|
||||||
|
|
||||||
|
static constexpr const char* TAG_LABEL{"<label>"};
|
||||||
|
|
||||||
|
connection& m_connection;
|
||||||
|
ewmh_connection_t m_ewmh;
|
||||||
|
event_timer m_throttle{0, 0};
|
||||||
|
|
||||||
|
vector<unique_ptr<desktop>> m_desktops;
|
||||||
|
map<desktop_state, label_t> m_labels;
|
||||||
|
iconset_t m_icons;
|
||||||
|
|
||||||
|
size_t m_index{0};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
POLYBAR_NS_END
|
|
@ -3,19 +3,27 @@
|
||||||
#include <xcb/xcb_ewmh.h>
|
#include <xcb/xcb_ewmh.h>
|
||||||
|
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
#include "x11/connection.hpp"
|
#include "utils/memory.hpp"
|
||||||
|
|
||||||
POLYBAR_NS
|
POLYBAR_NS
|
||||||
|
|
||||||
namespace ewmh_util {
|
using ewmh_connection_t = memory_util::malloc_ptr_t<xcb_ewmh_connection_t>;
|
||||||
bool setup(connection& conn, xcb_ewmh_connection_t* dst);
|
|
||||||
bool supports(xcb_ewmh_connection_t* ewmh, xcb_atom_t atom);
|
|
||||||
|
|
||||||
xcb_window_t get_active_window(xcb_ewmh_connection_t* conn);
|
namespace ewmh_util {
|
||||||
|
extern ewmh_connection_t g_ewmh_connection;
|
||||||
|
|
||||||
|
ewmh_connection_t initialize();
|
||||||
|
void dealloc();
|
||||||
|
|
||||||
|
bool supports(xcb_ewmh_connection_t* ewmh, xcb_atom_t atom, int screen = 0);
|
||||||
|
|
||||||
string get_visible_name(xcb_ewmh_connection_t* conn, xcb_window_t win);
|
string get_visible_name(xcb_ewmh_connection_t* conn, xcb_window_t win);
|
||||||
string get_icon_name(xcb_ewmh_connection_t* conn, xcb_window_t win);
|
string get_icon_name(xcb_ewmh_connection_t* conn, xcb_window_t win);
|
||||||
string get_reply_string(xcb_ewmh_get_utf8_strings_reply_t* reply);
|
string get_reply_string(xcb_ewmh_get_utf8_strings_reply_t* reply);
|
||||||
|
|
||||||
|
vector<string> get_desktop_names(xcb_ewmh_connection_t* conn, int screen = 0);
|
||||||
|
uint32_t get_current_desktop(xcb_ewmh_connection_t* conn, int screen = 0);
|
||||||
|
xcb_window_t get_active_window(xcb_ewmh_connection_t* conn, int screen = 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
POLYBAR_NS_END
|
POLYBAR_NS_END
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "modules/text.hpp"
|
#include "modules/text.hpp"
|
||||||
#include "modules/xbacklight.hpp"
|
#include "modules/xbacklight.hpp"
|
||||||
#include "modules/xwindow.hpp"
|
#include "modules/xwindow.hpp"
|
||||||
|
#include "modules/xworkspaces.hpp"
|
||||||
#include "utils/process.hpp"
|
#include "utils/process.hpp"
|
||||||
#include "utils/string.hpp"
|
#include "utils/string.hpp"
|
||||||
|
|
||||||
|
@ -401,6 +402,8 @@ void controller::bootstrap_modules() {
|
||||||
module.reset(new xbacklight_module(bar, m_log, m_conf, module_name));
|
module.reset(new xbacklight_module(bar, m_log, m_conf, module_name));
|
||||||
} else if (type == "internal/xwindow") {
|
} else if (type == "internal/xwindow") {
|
||||||
module.reset(new xwindow_module(bar, m_log, m_conf, module_name));
|
module.reset(new xwindow_module(bar, m_log, m_conf, module_name));
|
||||||
|
} else if (type == "internal/xworkspaces") {
|
||||||
|
module.reset(new xworkspaces_module(bar, m_log, m_conf, module_name));
|
||||||
} else if (type == "custom/text") {
|
} else if (type == "custom/text") {
|
||||||
module.reset(new text_module(bar, m_log, m_conf, module_name));
|
module.reset(new text_module(bar, m_log, m_conf, module_name));
|
||||||
} else if (type == "custom/script") {
|
} else if (type == "custom/script") {
|
||||||
|
|
191
src/modules/xworkspaces.cpp
Normal file
191
src/modules/xworkspaces.cpp
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
#include "modules/xworkspaces.hpp"
|
||||||
|
#include "drawtypes/iconset.hpp"
|
||||||
|
#include "drawtypes/label.hpp"
|
||||||
|
#include "x11/atoms.hpp"
|
||||||
|
#include "x11/connection.hpp"
|
||||||
|
|
||||||
|
#include "modules/meta/base.inl"
|
||||||
|
#include "modules/meta/static_module.inl"
|
||||||
|
|
||||||
|
POLYBAR_NS
|
||||||
|
|
||||||
|
namespace modules {
|
||||||
|
template class module<xworkspaces_module>;
|
||||||
|
template class static_module<xworkspaces_module>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct module
|
||||||
|
*/
|
||||||
|
xworkspaces_module::xworkspaces_module(
|
||||||
|
const bar_settings& bar, const logger& logger, const config& config, string name)
|
||||||
|
: static_module<xworkspaces_module>(bar, logger, config, name)
|
||||||
|
, m_connection(configure_connection().create<connection&>()) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap the module
|
||||||
|
*/
|
||||||
|
void xworkspaces_module::setup() {
|
||||||
|
connection& conn{configure_connection().create<decltype(conn)>()};
|
||||||
|
|
||||||
|
// Initialize ewmh atoms
|
||||||
|
if ((m_ewmh = ewmh_util::initialize()) == nullptr) {
|
||||||
|
throw module_error("Failed to initialize ewmh atoms");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the WM supports _NET_CURRENT_DESKTOP
|
||||||
|
if (!ewmh_util::supports(m_ewmh.get(), _NET_CURRENT_DESKTOP)) {
|
||||||
|
throw module_error("The WM does not list _NET_CURRENT_DESKTOP as a supported hint");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add formats and elements
|
||||||
|
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL, {TAG_LABEL});
|
||||||
|
|
||||||
|
if (m_formatter->has(TAG_LABEL)) {
|
||||||
|
m_labels.insert(
|
||||||
|
make_pair(desktop_state::ACTIVE, load_optional_label(m_conf, name(), "label-active", DEFAULT_LABEL)));
|
||||||
|
m_labels.insert(
|
||||||
|
make_pair(desktop_state::OCCUPIED, load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL)));
|
||||||
|
m_labels.insert(
|
||||||
|
make_pair(desktop_state::URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL)));
|
||||||
|
m_labels.insert(
|
||||||
|
make_pair(desktop_state::EMPTY, load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL)));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_icons = make_shared<iconset>();
|
||||||
|
m_icons->add(DEFAULT_ICON, make_shared<label>(m_conf.get<string>(name(), DEFAULT_ICON, "")));
|
||||||
|
|
||||||
|
for (const auto& workspace : m_conf.get_list<string>(name(), "icon", {})) {
|
||||||
|
auto vec = string_util::split(workspace, ';');
|
||||||
|
if (vec.size() == 2) {
|
||||||
|
m_icons->add(vec[0], make_shared<label>(vec[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we get notified when root properties change
|
||||||
|
window{conn, conn.root()}.ensure_event_mask(XCB_EVENT_MASK_PROPERTY_CHANGE);
|
||||||
|
|
||||||
|
// Connect with the event registry
|
||||||
|
conn.attach_sink(this, 1);
|
||||||
|
|
||||||
|
// Get desktops
|
||||||
|
rebuild_desktops();
|
||||||
|
set_current_desktop();
|
||||||
|
|
||||||
|
// Trigger the initial draw event
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disconnect from the event registry
|
||||||
|
*/
|
||||||
|
void xworkspaces_module::teardown() {
|
||||||
|
connection& conn{configure_connection().create<decltype(conn)>()};
|
||||||
|
conn.detach_sink(this, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for XCB_PROPERTY_NOTIFY events
|
||||||
|
*/
|
||||||
|
void xworkspaces_module::handle(const evt::property_notify& evt) {
|
||||||
|
if (evt->atom == m_ewmh->_NET_DESKTOP_NAMES && !m_throttle.throttle(evt->time)) {
|
||||||
|
rebuild_desktops();
|
||||||
|
} else if (evt->atom == m_ewmh->_NET_CURRENT_DESKTOP && !m_throttle.throttle(evt->time)) {
|
||||||
|
set_current_desktop();
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
// m_log.err("%lu %s", evt->time, m_connection.get_atom_name(evt->atom).name());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the currently active window and query its title
|
||||||
|
*/
|
||||||
|
void xworkspaces_module::update() {
|
||||||
|
size_t desktop_index{0};
|
||||||
|
|
||||||
|
for (const auto& desktop : m_desktops) {
|
||||||
|
switch (desktop->state) {
|
||||||
|
case desktop_state::ACTIVE:
|
||||||
|
desktop->label = m_labels[desktop_state::ACTIVE]->clone();
|
||||||
|
break;
|
||||||
|
case desktop_state::EMPTY:
|
||||||
|
desktop->label = m_labels[desktop_state::EMPTY]->clone();
|
||||||
|
break;
|
||||||
|
case desktop_state::URGENT:
|
||||||
|
desktop->label = m_labels[desktop_state::URGENT]->clone();
|
||||||
|
break;
|
||||||
|
case desktop_state::OCCUPIED:
|
||||||
|
desktop->label = m_labels[desktop_state::URGENT]->clone();
|
||||||
|
break;
|
||||||
|
case desktop_state::NONE:
|
||||||
|
continue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
desktop->label->reset_tokens();
|
||||||
|
desktop->label->replace_token("%name%", desktop->name);
|
||||||
|
desktop->label->replace_token("%icon%", m_icons->get(desktop->name, DEFAULT_ICON)->get());
|
||||||
|
desktop->label->replace_token("%index%", to_string(++desktop_index));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit notification to trigger redraw
|
||||||
|
broadcast();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate module output
|
||||||
|
*/
|
||||||
|
string xworkspaces_module::get_output() {
|
||||||
|
string output;
|
||||||
|
for (m_index = 0; m_index < m_desktops.size(); m_index++) {
|
||||||
|
if (m_index > 0) {
|
||||||
|
m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
||||||
|
}
|
||||||
|
output += static_module::get_output();
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Output content as defined in the config
|
||||||
|
*/
|
||||||
|
bool xworkspaces_module::build(builder* builder, const string& tag) const {
|
||||||
|
if (tag == TAG_LABEL && m_desktops[m_index]->state != desktop_state::NONE) {
|
||||||
|
builder->node(m_desktops[m_index]->label);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild the list of desktops
|
||||||
|
*/
|
||||||
|
void xworkspaces_module::rebuild_desktops() {
|
||||||
|
m_desktops.clear();
|
||||||
|
|
||||||
|
for (auto&& name : ewmh_util::get_desktop_names(m_ewmh.get())) {
|
||||||
|
m_desktops.emplace_back(make_unique<desktop>(move(name), desktop_state::EMPTY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag the desktop that is currently active
|
||||||
|
*/
|
||||||
|
void xworkspaces_module::set_current_desktop() {
|
||||||
|
auto current = ewmh_util::get_current_desktop(m_ewmh.get());
|
||||||
|
|
||||||
|
for (auto&& desktop : m_desktops) {
|
||||||
|
desktop->state = desktop_state::EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_desktops.size() > current) {
|
||||||
|
m_desktops[current]->state = desktop_state::ACTIVE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
POLYBAR_NS_END
|
|
@ -1,19 +1,35 @@
|
||||||
#include "x11/ewmh.hpp"
|
#include "x11/ewmh.hpp"
|
||||||
|
#include "x11/xutils.hpp"
|
||||||
|
|
||||||
POLYBAR_NS
|
POLYBAR_NS
|
||||||
|
|
||||||
namespace ewmh_util {
|
namespace ewmh_util {
|
||||||
bool setup(connection& conn, xcb_ewmh_connection_t* dst) {
|
ewmh_connection_t g_ewmh_connection{nullptr};
|
||||||
return xcb_ewmh_init_atoms_replies(dst, xcb_ewmh_init_atoms(conn, dst), nullptr);
|
|
||||||
|
ewmh_connection_t initialize() {
|
||||||
|
if (!g_ewmh_connection) {
|
||||||
|
g_ewmh_connection = memory_util::make_malloc_ptr<xcb_ewmh_connection_t>();
|
||||||
|
auto* xconn = xutils::get_connection();
|
||||||
|
auto* conn = g_ewmh_connection.get();
|
||||||
|
xcb_ewmh_init_atoms_replies(conn, xcb_ewmh_init_atoms(xconn, conn), nullptr);
|
||||||
|
}
|
||||||
|
return g_ewmh_connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool supports(xcb_ewmh_connection_t* ewmh, xcb_atom_t atom) {
|
void dealloc() {
|
||||||
|
if (g_ewmh_connection) {
|
||||||
|
xcb_ewmh_connection_wipe(g_ewmh_connection.get());
|
||||||
|
g_ewmh_connection.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool supports(xcb_ewmh_connection_t* ewmh, xcb_atom_t atom, int screen) {
|
||||||
bool supports{false};
|
bool supports{false};
|
||||||
|
|
||||||
xcb_ewmh_get_atoms_reply_t reply;
|
xcb_ewmh_get_atoms_reply_t reply;
|
||||||
reply.atoms = nullptr;
|
reply.atoms = nullptr;
|
||||||
|
|
||||||
if (!xcb_ewmh_get_supported_reply(ewmh, xcb_ewmh_get_supported(ewmh, 0), &reply, nullptr)) {
|
if (!xcb_ewmh_get_supported_reply(ewmh, xcb_ewmh_get_supported(ewmh, screen), &reply, nullptr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,12 +47,6 @@ namespace ewmh_util {
|
||||||
return supports;
|
return supports;
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_window_t get_active_window(xcb_ewmh_connection_t* conn) {
|
|
||||||
xcb_window_t win{XCB_NONE};
|
|
||||||
xcb_ewmh_get_active_window_reply(conn, xcb_ewmh_get_active_window(conn, 0), &win, nullptr);
|
|
||||||
return win;
|
|
||||||
}
|
|
||||||
|
|
||||||
string get_visible_name(xcb_ewmh_connection_t* conn, xcb_window_t win) {
|
string get_visible_name(xcb_ewmh_connection_t* conn, xcb_window_t win) {
|
||||||
xcb_ewmh_get_utf8_strings_reply_t utf8_reply;
|
xcb_ewmh_get_utf8_strings_reply_t utf8_reply;
|
||||||
if (!xcb_ewmh_get_wm_visible_name_reply(conn, xcb_ewmh_get_wm_visible_name(conn, win), &utf8_reply, nullptr)) {
|
if (!xcb_ewmh_get_wm_visible_name_reply(conn, xcb_ewmh_get_wm_visible_name(conn, win), &utf8_reply, nullptr)) {
|
||||||
|
@ -62,6 +72,39 @@ namespace ewmh_util {
|
||||||
xcb_ewmh_get_utf8_strings_reply_wipe(reply);
|
xcb_ewmh_get_utf8_strings_reply_wipe(reply);
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t get_current_desktop(xcb_ewmh_connection_t* conn, int screen) {
|
||||||
|
uint32_t desktop{0};
|
||||||
|
if (xcb_ewmh_get_current_desktop_reply(conn, xcb_ewmh_get_current_desktop(conn, screen), &desktop, nullptr)) {
|
||||||
|
return desktop;
|
||||||
|
}
|
||||||
|
return XCB_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
vector<string> get_desktop_names(xcb_ewmh_connection_t* conn, int screen) {
|
||||||
|
xcb_ewmh_get_utf8_strings_reply_t reply;
|
||||||
|
xcb_ewmh_get_desktop_names_reply(conn, xcb_ewmh_get_desktop_names(conn, screen), &reply, nullptr);
|
||||||
|
|
||||||
|
vector<string> names;
|
||||||
|
char buffer[BUFSIZ];
|
||||||
|
|
||||||
|
for (size_t n = 0, len = 0; n < reply.strings_len; n++) {
|
||||||
|
if (reply.strings[n] == '\0') {
|
||||||
|
names.emplace_back(buffer, len);
|
||||||
|
len = 0;
|
||||||
|
} else {
|
||||||
|
buffer[len++] = reply.strings[n];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_window_t get_active_window(xcb_ewmh_connection_t* conn, int screen) {
|
||||||
|
xcb_window_t win{XCB_NONE};
|
||||||
|
xcb_ewmh_get_active_window_reply(conn, xcb_ewmh_get_active_window(conn, screen), &win, nullptr);
|
||||||
|
return win;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
POLYBAR_NS_END
|
POLYBAR_NS_END
|
||||||
|
|
Loading…
Reference in a new issue