bfa9b5d53e
* Adding case to ignore T@ in bspwm * Adding entry to changelog for issue #2371
506 lines
17 KiB
C++
506 lines
17 KiB
C++
#include "modules/bspwm.hpp"
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include "drawtypes/iconset.hpp"
|
|
#include "drawtypes/label.hpp"
|
|
#include "modules/meta/base.inl"
|
|
#include "utils/factory.hpp"
|
|
#include "utils/file.hpp"
|
|
#include "utils/string.hpp"
|
|
|
|
POLYBAR_NS
|
|
|
|
namespace {
|
|
using bspwm_state = modules::bspwm_module::state;
|
|
|
|
unsigned int make_mask(bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
|
|
unsigned int mask{0U};
|
|
if (static_cast<unsigned int>(s1)) {
|
|
mask |= 1U << (static_cast<unsigned int>(s1) - 1U);
|
|
}
|
|
if (static_cast<unsigned int>(s2)) {
|
|
mask |= 1U << (static_cast<unsigned int>(s2) - 1U);
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
unsigned int check_mask(unsigned int base, bspwm_state s1, bspwm_state s2 = bspwm_state::NONE) {
|
|
unsigned int mask{0U};
|
|
if (static_cast<unsigned int>(s1)) {
|
|
mask |= 1U << (static_cast<unsigned int>(s1) - 1U);
|
|
}
|
|
if (static_cast<unsigned int>(s2)) {
|
|
mask |= 1U << (static_cast<unsigned int>(s2) - 1U);
|
|
}
|
|
return (base & mask) == mask;
|
|
}
|
|
} // namespace
|
|
|
|
namespace modules {
|
|
template class module<bspwm_module>;
|
|
|
|
bspwm_module::bspwm_module(const bar_settings& bar, string name_) : event_module<bspwm_module>(bar, move(name_)) {
|
|
m_router->register_action_with_data(EVENT_FOCUS, &bspwm_module::action_focus);
|
|
m_router->register_action(EVENT_NEXT, &bspwm_module::action_next);
|
|
m_router->register_action(EVENT_PREV, &bspwm_module::action_prev);
|
|
|
|
auto socket_path = bspwm_util::get_socket_path();
|
|
|
|
if (!file_util::exists(socket_path)) {
|
|
throw module_error("Could not find socket: " + (socket_path.empty() ? "<empty>" : socket_path));
|
|
}
|
|
|
|
// Create ipc subscriber
|
|
m_subscriber = bspwm_util::make_subscriber();
|
|
|
|
// Load configuration values
|
|
m_pinworkspaces = m_conf.get(name(), "pin-workspaces", m_pinworkspaces);
|
|
m_click = m_conf.get(name(), "enable-click", m_click);
|
|
m_scroll = m_conf.get(name(), "enable-scroll", m_scroll);
|
|
m_occscroll = m_conf.get(name(), "occupied-scroll", m_occscroll);
|
|
m_revscroll = m_conf.get(name(), "reverse-scroll", m_revscroll);
|
|
m_inlinemode = m_conf.get(name(), "inline-mode", m_inlinemode);
|
|
m_fuzzy_match = m_conf.get(name(), "fuzzy-match", m_fuzzy_match);
|
|
|
|
// Add formats and create components
|
|
m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE}, {TAG_LABEL_MONITOR, TAG_LABEL_MODE});
|
|
|
|
if (m_formatter->has(TAG_LABEL_MONITOR)) {
|
|
m_monitorlabel = load_optional_label(m_conf, name(), "label-monitor", DEFAULT_MONITOR_LABEL);
|
|
}
|
|
|
|
if (m_formatter->has(TAG_LABEL_STATE)) {
|
|
// XXX: Warn about deprecated parameters
|
|
m_conf.warn_deprecated(name(), "label-dimmed-active", "label-dimmed-focused");
|
|
|
|
// clang-format off
|
|
try {
|
|
m_statelabels.emplace(make_mask(state::FOCUSED), load_label(m_conf, name(), "label-active", DEFAULT_LABEL));
|
|
m_conf.warn_deprecated(name(), "label-active", "label-focused and label-dimmed-focused");
|
|
} catch (const key_error& err) {
|
|
m_statelabels.emplace(make_mask(state::FOCUSED), load_optional_label(m_conf, name(), "label-focused", DEFAULT_LABEL));
|
|
}
|
|
|
|
m_statelabels.emplace(make_mask(state::OCCUPIED),
|
|
load_optional_label(m_conf, name(), "label-occupied", DEFAULT_LABEL));
|
|
m_statelabels.emplace(make_mask(state::URGENT),
|
|
load_optional_label(m_conf, name(), "label-urgent", DEFAULT_LABEL));
|
|
m_statelabels.emplace(make_mask(state::EMPTY),
|
|
load_optional_label(m_conf, name(), "label-empty", DEFAULT_LABEL));
|
|
m_statelabels.emplace(make_mask(state::DIMMED),
|
|
load_optional_label(m_conf, name(), "label-dimmed"));
|
|
|
|
vector<pair<state, string>> focused_overrides{
|
|
{state::OCCUPIED, "label-focused-occupied"},
|
|
{state::URGENT, "label-focused-urgent"},
|
|
{state::EMPTY, "label-focused-empty"}};
|
|
|
|
for (auto&& os : focused_overrides) {
|
|
unsigned int mask{make_mask(state::FOCUSED, os.first)};
|
|
try {
|
|
m_statelabels.emplace(mask, load_label(m_conf, name(), os.second));
|
|
} catch (const key_error& err) {
|
|
m_statelabels.emplace(mask, m_statelabels.at(make_mask(state::FOCUSED))->clone());
|
|
}
|
|
}
|
|
|
|
vector<pair<state, string>> dimmed_overrides{
|
|
{state::FOCUSED, "label-dimmed-focused"},
|
|
{state::OCCUPIED, "label-dimmed-occupied"},
|
|
{state::URGENT, "label-dimmed-urgent"},
|
|
{state::EMPTY, "label-dimmed-empty"}};
|
|
|
|
for (auto&& os : dimmed_overrides) {
|
|
m_statelabels.emplace(make_mask(state::DIMMED, os.first),
|
|
load_optional_label(m_conf, name(), os.second, m_statelabels.at(make_mask(os.first))->get()));
|
|
}
|
|
// clang-format on
|
|
}
|
|
|
|
if (m_formatter->has(TAG_LABEL_MODE)) {
|
|
m_modelabels.emplace(mode::LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle"));
|
|
m_modelabels.emplace(mode::LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"));
|
|
m_modelabels.emplace(mode::STATE_FULLSCREEN, load_optional_label(m_conf, name(), "label-fullscreen"));
|
|
m_modelabels.emplace(mode::STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating"));
|
|
m_modelabels.emplace(mode::STATE_PSEUDOTILED, load_optional_label(m_conf, name(), "label-pseudotiled"));
|
|
m_modelabels.emplace(mode::NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked"));
|
|
m_modelabels.emplace(mode::NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky"));
|
|
m_modelabels.emplace(mode::NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private"));
|
|
m_modelabels.emplace(mode::NODE_MARKED, load_optional_label(m_conf, name(), "label-marked"));
|
|
}
|
|
|
|
m_labelseparator = load_optional_label(m_conf, name(), "label-separator", "");
|
|
|
|
m_icons = factory_util::shared<iconset>();
|
|
m_icons->add(DEFAULT_ICON, factory_util::shared<label>(m_conf.get(name(), DEFAULT_ICON, ""s)));
|
|
|
|
for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
|
|
auto vec = string_util::tokenize(workspace, ';');
|
|
if (vec.size() == 2) {
|
|
m_icons->add(vec[0], factory_util::shared<label>(vec[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
void bspwm_module::stop() {
|
|
if (m_subscriber) {
|
|
m_log.info("%s: Disconnecting from socket", name());
|
|
m_subscriber->disconnect();
|
|
}
|
|
event_module::stop();
|
|
}
|
|
|
|
bool bspwm_module::has_event() {
|
|
if (m_subscriber->poll(POLLHUP, 0)) {
|
|
m_log.notice("%s: Reconnecting to socket...", name());
|
|
m_subscriber = bspwm_util::make_subscriber();
|
|
}
|
|
return m_subscriber->peek(1);
|
|
}
|
|
|
|
bool bspwm_module::update() {
|
|
if (!m_subscriber) {
|
|
return false;
|
|
}
|
|
|
|
string data{m_subscriber->receive(BUFSIZ)};
|
|
bool result = false;
|
|
|
|
for (auto&& status_line : string_util::split(data, '\n')) {
|
|
// Need to return true if ANY of the handle_status calls
|
|
// return true
|
|
result = this->handle_status(status_line) || result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
bool bspwm_module::handle_status(string& data) {
|
|
if (data.empty()) {
|
|
return false;
|
|
}
|
|
|
|
size_t prefix_len{strlen(BSPWM_STATUS_PREFIX)};
|
|
if (data.compare(0, prefix_len, BSPWM_STATUS_PREFIX) != 0) {
|
|
m_log.err("%s: Unknown status '%s'", name(), data);
|
|
return false;
|
|
}
|
|
|
|
string_util::hash_type hash;
|
|
if ((hash = string_util::hash(data)) == m_hash) {
|
|
return false;
|
|
}
|
|
|
|
m_hash = hash;
|
|
|
|
size_t pos;
|
|
|
|
// Extract the string for the defined monitor
|
|
if (m_pinworkspaces) {
|
|
const auto needle_active = ":M" + m_bar.monitor->name + ":";
|
|
const auto needle_inactive = ":m" + m_bar.monitor->name + ":";
|
|
|
|
if ((pos = data.find(BSPWM_STATUS_PREFIX)) != string::npos) {
|
|
data = data.replace(pos, prefix_len, ":");
|
|
}
|
|
if ((pos = data.find(needle_active)) != string::npos) {
|
|
data.erase(0, pos + 1);
|
|
}
|
|
if ((pos = data.find(needle_inactive)) != string::npos) {
|
|
data.erase(0, pos + 1);
|
|
}
|
|
if ((pos = data.find(":m", 1)) != string::npos) {
|
|
data.erase(pos);
|
|
}
|
|
if ((pos = data.find(":M", 1)) != string::npos) {
|
|
data.erase(pos);
|
|
}
|
|
} else if ((pos = data.find(BSPWM_STATUS_PREFIX)) != string::npos) {
|
|
data = data.replace(pos, prefix_len, ":");
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
m_log.info("%s: Parsing socket data: %s", name(), data);
|
|
|
|
m_monitors.clear();
|
|
|
|
size_t workspace_n{0U};
|
|
|
|
for (auto&& tag : string_util::split(data, ':')) {
|
|
auto value = tag.substr(1);
|
|
auto mode_flag = mode::NONE;
|
|
unsigned int workspace_mask{0U};
|
|
|
|
if (tag[0] == 'm' || tag[0] == 'M') {
|
|
m_monitors.emplace_back(factory_util::unique<bspwm_monitor>());
|
|
m_monitors.back()->name = value;
|
|
|
|
if (m_monitorlabel) {
|
|
m_monitors.back()->label = m_monitorlabel->clone();
|
|
m_monitors.back()->label->replace_token("%name%", value);
|
|
}
|
|
}
|
|
|
|
switch (tag[0]) {
|
|
case 'm':
|
|
m_monitors.back()->focused = false;
|
|
break;
|
|
case 'M':
|
|
m_monitors.back()->focused = true;
|
|
break;
|
|
case 'F':
|
|
workspace_mask = make_mask(state::FOCUSED, state::EMPTY);
|
|
break;
|
|
case 'O':
|
|
workspace_mask = make_mask(state::FOCUSED, state::OCCUPIED);
|
|
break;
|
|
case 'U':
|
|
workspace_mask = make_mask(state::FOCUSED, state::URGENT);
|
|
break;
|
|
case 'f':
|
|
workspace_mask = make_mask(state::EMPTY);
|
|
break;
|
|
case 'o':
|
|
workspace_mask = make_mask(state::OCCUPIED);
|
|
break;
|
|
case 'u':
|
|
workspace_mask = make_mask(state::URGENT);
|
|
break;
|
|
case 'L':
|
|
switch (value[0]) {
|
|
case 0:
|
|
break;
|
|
case 'M':
|
|
mode_flag = mode::LAYOUT_MONOCLE;
|
|
break;
|
|
case 'T':
|
|
mode_flag = mode::LAYOUT_TILED;
|
|
break;
|
|
default:
|
|
m_log.warn("%s: Undefined L => '%s'", name(), value);
|
|
}
|
|
break;
|
|
|
|
case 'T':
|
|
switch (value[0]) {
|
|
case 0:
|
|
break;
|
|
case '@':
|
|
break;
|
|
case 'T':
|
|
break;
|
|
case '=':
|
|
mode_flag = mode::STATE_FULLSCREEN;
|
|
break;
|
|
case 'F':
|
|
mode_flag = mode::STATE_FLOATING;
|
|
break;
|
|
case 'P':
|
|
mode_flag = mode::STATE_PSEUDOTILED;
|
|
break;
|
|
default:
|
|
m_log.warn("%s: Undefined T => '%s'", name(), value);
|
|
}
|
|
break;
|
|
|
|
case 'G':
|
|
if (!m_monitors.back()->focused) {
|
|
break;
|
|
}
|
|
|
|
for (size_t i = 0U; i < value.length(); i++) {
|
|
switch (value[i]) {
|
|
case 0:
|
|
break;
|
|
case 'L':
|
|
mode_flag = mode::NODE_LOCKED;
|
|
break;
|
|
case 'S':
|
|
mode_flag = mode::NODE_STICKY;
|
|
break;
|
|
case 'P':
|
|
mode_flag = mode::NODE_PRIVATE;
|
|
break;
|
|
case 'M':
|
|
mode_flag = mode::NODE_MARKED;
|
|
break;
|
|
default:
|
|
m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1));
|
|
}
|
|
|
|
if (mode_flag != mode::NONE && !m_modelabels.empty()) {
|
|
m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
|
|
}
|
|
}
|
|
continue;
|
|
|
|
default:
|
|
m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1));
|
|
continue;
|
|
}
|
|
|
|
if (!m_monitors.back()) {
|
|
m_log.warn("%s: No monitor created", name());
|
|
continue;
|
|
}
|
|
|
|
if (workspace_mask && m_formatter->has(TAG_LABEL_STATE)) {
|
|
auto icon = m_icons->get(value, DEFAULT_ICON, m_fuzzy_match);
|
|
auto label = m_statelabels.at(workspace_mask)->clone();
|
|
|
|
if (!m_monitors.back()->focused) {
|
|
if (m_statelabels[make_mask(state::DIMMED)]) {
|
|
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED)]);
|
|
}
|
|
if (workspace_mask & make_mask(state::EMPTY)) {
|
|
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::EMPTY)]);
|
|
}
|
|
if (workspace_mask & make_mask(state::OCCUPIED)) {
|
|
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::OCCUPIED)]);
|
|
}
|
|
if (workspace_mask & make_mask(state::FOCUSED)) {
|
|
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::FOCUSED)]);
|
|
}
|
|
if (workspace_mask & make_mask(state::URGENT)) {
|
|
label->replace_defined_values(m_statelabels[make_mask(state::DIMMED, state::URGENT)]);
|
|
}
|
|
}
|
|
|
|
label->reset_tokens();
|
|
label->replace_token("%name%", value);
|
|
label->replace_token("%icon%", icon->get());
|
|
label->replace_token("%index%", to_string(++workspace_n));
|
|
|
|
m_monitors.back()->workspaces.emplace_back(workspace_mask, move(label));
|
|
}
|
|
|
|
if (mode_flag != mode::NONE && !m_modelabels.empty()) {
|
|
m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
string bspwm_module::get_output() {
|
|
string output;
|
|
for (m_index = 0U; m_index < m_monitors.size(); m_index++) {
|
|
if (m_index > 0) {
|
|
m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing);
|
|
}
|
|
output += this->event_module::get_output();
|
|
}
|
|
return output;
|
|
}
|
|
|
|
bool bspwm_module::build(builder* builder, const string& tag) const {
|
|
if (tag == TAG_LABEL_MONITOR) {
|
|
builder->node(m_monitors[m_index]->label);
|
|
return true;
|
|
} else if (tag == TAG_LABEL_STATE && !m_monitors[m_index]->workspaces.empty()) {
|
|
size_t workspace_n{0U};
|
|
|
|
if (m_scroll) {
|
|
builder->action(mousebtn::SCROLL_DOWN, *this, m_revscroll ? EVENT_NEXT : EVENT_PREV, "");
|
|
builder->action(mousebtn::SCROLL_UP, *this, m_revscroll ? EVENT_PREV : EVENT_NEXT, "");
|
|
}
|
|
|
|
for (auto&& ws : m_monitors[m_index]->workspaces) {
|
|
if (ws.second.get()) {
|
|
if (workspace_n != 0 && *m_labelseparator) {
|
|
builder->node(m_labelseparator);
|
|
}
|
|
|
|
workspace_n++;
|
|
|
|
if (m_click) {
|
|
builder->action(mousebtn::LEFT, *this, EVENT_FOCUS, sstream() << m_index << "+" << workspace_n, ws.second);
|
|
} else {
|
|
builder->node(ws.second);
|
|
}
|
|
|
|
if (m_inlinemode && m_monitors[m_index]->focused && check_mask(ws.first, bspwm_state::FOCUSED)) {
|
|
for (auto&& mode : m_monitors[m_index]->modes) {
|
|
builder->node(mode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_scroll) {
|
|
builder->action_close();
|
|
builder->action_close();
|
|
}
|
|
|
|
return workspace_n > 0;
|
|
} else if (tag == TAG_LABEL_MODE && !m_inlinemode && m_monitors[m_index]->focused &&
|
|
!m_monitors[m_index]->modes.empty()) {
|
|
int modes_n = 0;
|
|
|
|
for (auto&& mode : m_monitors[m_index]->modes) {
|
|
if (mode && *mode) {
|
|
builder->node(mode);
|
|
modes_n++;
|
|
}
|
|
}
|
|
|
|
return modes_n > 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void bspwm_module::action_focus(const string& data) {
|
|
size_t separator{string_util::find_nth(data, 0, "+", 1)};
|
|
size_t monitor_n{std::strtoul(data.substr(0, separator).c_str(), nullptr, 10)};
|
|
string workspace_n{data.substr(separator + 1)};
|
|
|
|
if (monitor_n < m_monitors.size()) {
|
|
send_command("desktop -f " + m_monitors[monitor_n]->name + ":^" + workspace_n,
|
|
"Sending desktop focus command to ipc handler");
|
|
} else {
|
|
m_log.err("%s: Invalid monitor index in command: %s", name(), data);
|
|
}
|
|
}
|
|
void bspwm_module::action_next() {
|
|
focus_direction(true);
|
|
}
|
|
|
|
void bspwm_module::action_prev() {
|
|
focus_direction(false);
|
|
}
|
|
|
|
void bspwm_module::focus_direction(bool next) {
|
|
string scrolldir = next ? "next" : "prev";
|
|
string modifier;
|
|
|
|
if (m_occscroll) {
|
|
modifier += ".occupied";
|
|
}
|
|
|
|
if (m_pinworkspaces) {
|
|
modifier += ".local";
|
|
for (const auto& mon : m_monitors) {
|
|
if (m_bar.monitor->match(mon->name, false) && !mon->focused) {
|
|
send_command("monitor -f " + mon->name, "Sending monitor focus command to ipc handler");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
send_command("desktop -f " + scrolldir + modifier, "Sending desktop " + scrolldir + " command to ipc handler");
|
|
}
|
|
|
|
void bspwm_module::send_command(const string& payload_cmd, const string& log_info) {
|
|
auto ipc = bspwm_util::make_connection();
|
|
auto payload = bspwm_util::make_payload(payload_cmd);
|
|
m_log.info("%s: %s", name(), log_info);
|
|
ipc->send(payload->data, payload->len, 0);
|
|
ipc->disconnect();
|
|
}
|
|
} // namespace modules
|
|
|
|
POLYBAR_NS_END
|