From 5f21d7d44024d59869b926b8049710c281e86a37 Mon Sep 17 00:00:00 2001 From: Michael Carlberg Date: Thu, 3 Nov 2016 17:54:26 +0100 Subject: [PATCH] feat(bspwm): Support for multi monitors listing - All available workspaces can now be listed, grouped by monitor, by setting the module config `pin-workspaces` to false - Adds a new format tag - Treat as a normal format tag --- include/modules/bspwm.hpp | 36 ++++--- src/modules/bspwm.cpp | 220 ++++++++++++++++++++++++-------------- 2 files changed, 161 insertions(+), 95 deletions(-) diff --git a/include/modules/bspwm.hpp b/include/modules/bspwm.hpp index 2b3c6c97..5f725239 100644 --- a/include/modules/bspwm.hpp +++ b/include/modules/bspwm.hpp @@ -8,13 +8,15 @@ LEMONBUDDY_NS namespace modules { - enum class bspwm_flag { + enum class state_ws { WORKSPACE_NONE, WORKSPACE_ACTIVE, WORKSPACE_URGENT, WORKSPACE_EMPTY, WORKSPACE_OCCUPIED, WORKSPACE_DIMMED, // used when the monitor is out of focus + }; + enum class state_mode { MODE_NONE, MODE_LAYOUT_MONOCLE, MODE_LAYOUT_TILED, @@ -25,20 +27,14 @@ namespace modules { MODE_NODE_PRIVATE }; - struct bspwm_workspace { - bspwm_flag flag; + struct bspwm_monitor { + vector> workspaces; + vector modes; label_t label; - - bspwm_workspace(bspwm_flag flag, label_t&& label) - : flag(flag), label(forward(label)) {} - - operator bool() { - return label && *label; - } + string name; + bool focused = false; }; - using bspwm_workspace_t = unique_ptr; - class bspwm_module : public event_module { public: using event_module::event_module; @@ -47,6 +43,7 @@ namespace modules { void stop(); bool has_event(); bool update(); + string get_output(); bool build(builder* builder, string tag) const; bool handle_event(string cmd); bool receive_events() const; @@ -54,19 +51,26 @@ namespace modules { private: static constexpr auto DEFAULT_WS_ICON = "ws-icon-default"; static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%"; + static constexpr auto DEFAULT_MONITOR_LABEL = "%name%"; + static constexpr auto TAG_LABEL_MONITOR = ""; static constexpr auto TAG_LABEL_STATE = ""; static constexpr auto TAG_LABEL_MODE = ""; static constexpr auto EVENT_CLICK = "bwm"; bspwm_util::connection_t m_subscriber; - map m_modelabels; - map m_statelabels; - vector m_workspaces; - vector m_modes; + vector> m_monitors; + + map m_modelabels; + map m_statelabels; + label_t m_monitorlabel; iconset_t m_icons; + bool m_pinworkspaces = true; string m_monitor; unsigned long m_hash; + + // used while formatting output + size_t m_index = 0; }; } diff --git a/src/modules/bspwm.cpp b/src/modules/bspwm.cpp index dba49512..6915cc77 100644 --- a/src/modules/bspwm.cpp +++ b/src/modules/bspwm.cpp @@ -7,40 +7,49 @@ LEMONBUDDY_NS namespace modules { void bspwm_module::setup() { m_monitor = m_bar.monitor->name; - m_log.trace("%s: Primary monitor '%s'", name(), m_monitor); + // Load configuration values {{{ + + GET_CONFIG_VALUE(name(), m_pinworkspaces, "pin-workspaces"); + + // }}} // Add formats and create components {{{ - m_formatter->add(DEFAULT_FORMAT, TAG_LABEL_STATE, {TAG_LABEL_STATE}, {TAG_LABEL_MODE}); + 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)) { - m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_ACTIVE, + m_statelabels.insert(make_pair(state_ws::WORKSPACE_ACTIVE, load_optional_label(m_conf, name(), "label-active", DEFAULT_WS_LABEL))); - m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_OCCUPIED, + m_statelabels.insert(make_pair(state_ws::WORKSPACE_OCCUPIED, load_optional_label(m_conf, name(), "label-occupied", DEFAULT_WS_LABEL))); - m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_URGENT, + m_statelabels.insert(make_pair(state_ws::WORKSPACE_URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL))); - m_statelabels.insert(make_pair(bspwm_flag::WORKSPACE_EMPTY, + m_statelabels.insert(make_pair(state_ws::WORKSPACE_EMPTY, load_optional_label(m_conf, name(), "label-empty", DEFAULT_WS_LABEL))); m_statelabels.insert(make_pair( - bspwm_flag::WORKSPACE_DIMMED, load_optional_label(m_conf, name(), "label-dimmed"))); + state_ws::WORKSPACE_DIMMED, load_optional_label(m_conf, name(), "label-dimmed"))); } if (m_formatter->has(TAG_LABEL_MODE)) { m_modelabels.insert(make_pair( - bspwm_flag::MODE_LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle"))); + state_mode::MODE_LAYOUT_MONOCLE, load_optional_label(m_conf, name(), "label-monocle"))); m_modelabels.insert(make_pair( - bspwm_flag::MODE_LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"))); - m_modelabels.insert(make_pair(bspwm_flag::MODE_STATE_FULLSCREEN, + state_mode::MODE_LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"))); + m_modelabels.insert(make_pair(state_mode::MODE_STATE_FULLSCREEN, load_optional_label(m_conf, name(), "label-fullscreen"))); m_modelabels.insert(make_pair( - bspwm_flag::MODE_STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating"))); + state_mode::MODE_STATE_FLOATING, load_optional_label(m_conf, name(), "label-floating"))); m_modelabels.insert(make_pair( - bspwm_flag::MODE_NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked"))); + state_mode::MODE_NODE_LOCKED, load_optional_label(m_conf, name(), "label-locked"))); m_modelabels.insert(make_pair( - bspwm_flag::MODE_NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky"))); + state_mode::MODE_NODE_STICKY, load_optional_label(m_conf, name(), "label-sticky"))); m_modelabels.insert(make_pair( - bspwm_flag::MODE_NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private"))); + state_mode::MODE_NODE_PRIVATE, load_optional_label(m_conf, name(), "label-private"))); } m_icons = iconset_t{new iconset()}; @@ -114,68 +123,83 @@ namespace modules { m_hash = hash; // Extract the string for the defined monitor - const auto needle_active = ":M" + m_monitor + ":"; - const auto needle_inactive = ":m" + m_monitor + ":"; + if (m_pinworkspaces) { + const auto needle_active = ":M" + m_monitor + ":"; + const auto needle_inactive = ":m" + m_monitor + ":"; - if ((pos = data.find(prefix)) != std::string::npos) + if ((pos = data.find(prefix)) != std::string::npos) + data = data.replace(pos, prefix.length(), ":"); + if ((pos = data.find(needle_active)) != std::string::npos) + data.erase(0, pos + 1); + if ((pos = data.find(needle_inactive)) != std::string::npos) + data.erase(0, pos + 1); + if ((pos = data.find(":m", 1)) != std::string::npos) + data.erase(pos); + if ((pos = data.find(":M", 1)) != std::string::npos) + data.erase(pos); + } else if ((pos = data.find(prefix)) != std::string::npos) { data = data.replace(pos, prefix.length(), ":"); - if ((pos = data.find(needle_active)) != std::string::npos) - data.erase(0, pos + 1); - if ((pos = data.find(needle_inactive)) != std::string::npos) - data.erase(0, pos + 1); - if ((pos = data.find(":m", 1)) != std::string::npos) - data.erase(pos); - if ((pos = data.find(":M", 1)) != std::string::npos) - data.erase(pos); + } else { + return false; + } - m_modes.clear(); - m_workspaces.clear(); - - bool monitor_focused = true; int workspace_n = 0; + m_monitors.clear(); + for (auto&& tag : string_util::split(data, ':')) { - if (tag.empty()) + if (tag.empty()) { continue; + } auto value = tag.size() > 0 ? tag.substr(1) : ""; - auto workspace_flag = bspwm_flag::WORKSPACE_NONE; - auto mode_flag = bspwm_flag::MODE_NONE; + auto workspace_flag = state_ws::WORKSPACE_NONE; + auto mode_flag = state_mode::MODE_NONE; + + if (tag[0] == 'm' || tag[0] == 'M') { + m_monitors.emplace_back(make_unique()); + 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': - monitor_focused = false; + m_monitors.back()->focused = false; break; case 'M': - monitor_focused = true; + m_monitors.back()->focused = true; break; case 'F': - workspace_flag = bspwm_flag::WORKSPACE_ACTIVE; + workspace_flag = state_ws::WORKSPACE_ACTIVE; break; case 'O': - workspace_flag = bspwm_flag::WORKSPACE_ACTIVE; + workspace_flag = state_ws::WORKSPACE_ACTIVE; break; case 'o': - workspace_flag = bspwm_flag::WORKSPACE_OCCUPIED; + workspace_flag = state_ws::WORKSPACE_OCCUPIED; break; case 'U': - workspace_flag = bspwm_flag::WORKSPACE_URGENT; + workspace_flag = state_ws::WORKSPACE_URGENT; break; case 'u': - workspace_flag = bspwm_flag::WORKSPACE_URGENT; + workspace_flag = state_ws::WORKSPACE_URGENT; break; case 'f': - workspace_flag = bspwm_flag::WORKSPACE_EMPTY; + workspace_flag = state_ws::WORKSPACE_EMPTY; break; case 'L': switch (value[0]) { case 0: break; case 'M': - mode_flag = bspwm_flag::MODE_LAYOUT_MONOCLE; + mode_flag = state_mode::MODE_LAYOUT_MONOCLE; break; case 'T': - mode_flag = bspwm_flag::MODE_LAYOUT_TILED; + mode_flag = state_mode::MODE_LAYOUT_TILED; break; default: m_log.warn("%s: Undefined L => '%s'", name(), value); @@ -189,10 +213,10 @@ namespace modules { case 'T': break; case '=': - mode_flag = bspwm_flag::MODE_STATE_FULLSCREEN; + mode_flag = state_mode::MODE_STATE_FULLSCREEN; break; case 'F': - mode_flag = bspwm_flag::MODE_STATE_FLOATING; + mode_flag = state_mode::MODE_STATE_FLOATING; break; default: m_log.warn("%s: Undefined T => '%s'", name(), value); @@ -200,25 +224,30 @@ namespace modules { break; case 'G': + if (!m_monitors.back()->focused) { + break; + } + for (int i = 0; i < (int)value.length(); i++) { switch (value[i]) { case 0: break; case 'L': - mode_flag = bspwm_flag::MODE_NODE_LOCKED; + mode_flag = state_mode::MODE_NODE_LOCKED; break; case 'S': - mode_flag = bspwm_flag::MODE_NODE_STICKY; + mode_flag = state_mode::MODE_NODE_STICKY; break; case 'P': - mode_flag = bspwm_flag::MODE_NODE_PRIVATE; + mode_flag = state_mode::MODE_NODE_PRIVATE; break; default: m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1)); } - if (mode_flag != bspwm_flag::MODE_NONE && !m_modelabels.empty()) - m_modes.emplace_back(m_modelabels.find(mode_flag)->second->clone()); + if (mode_flag != state_mode::MODE_NONE && !m_modelabels.empty()) { + m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone()); + } } continue; @@ -226,67 +255,100 @@ namespace modules { m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1)); } - if (workspace_flag != bspwm_flag::WORKSPACE_NONE && m_formatter->has(TAG_LABEL_STATE)) { + if (workspace_flag != state_ws::WORKSPACE_NONE && m_formatter->has(TAG_LABEL_STATE)) { auto icon = m_icons->get(value, DEFAULT_WS_ICON); auto label = m_statelabels.find(workspace_flag)->second->clone(); - if (!monitor_focused) - label->replace_defined_values(m_statelabels.find(bspwm_flag::WORKSPACE_DIMMED)->second); + if (!m_monitors.back()->focused) { + label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED)->second); + } label->reset_tokens(); label->replace_token("%name%", value); label->replace_token("%icon%", icon->get()); label->replace_token("%index%", to_string(++workspace_n)); - m_workspaces.emplace_back(make_unique(workspace_flag, std::move(label))); + m_monitors.back()->workspaces.emplace_back(make_pair(workspace_flag, std::move(label))); } - if (mode_flag != bspwm_flag::MODE_NONE && !m_modelabels.empty()) - m_modes.emplace_back(m_modelabels.find(mode_flag)->second->clone()); + if (mode_flag != state_mode::MODE_NONE && !m_modelabels.empty()) { + m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone()); + } } - if (!monitor_focused) - m_modes.clear(); - return true; } + string bspwm_module::get_output() { + string output; + for (m_index = 0; m_index < m_monitors.size(); m_index++) { + if (m_index > 0) { + m_builder->space(m_formatter->get(DEFAULT_FORMAT)->spacing); + } + output += event_module::get_output(); + } + return output; + } + bool bspwm_module::build(builder* builder, string tag) const { - if (tag != TAG_LABEL_STATE) - return false; + if (tag == TAG_LABEL_MONITOR) { + builder->node(m_monitors[m_index]->label); + return true; + } else if (tag == TAG_LABEL_STATE) { + int workspace_n = 0; - int workspace_n = 0; + for (auto&& ws : m_monitors[m_index]->workspaces) { + if (ws.second.get()) { + string event{EVENT_CLICK}; + event += to_string(m_index); + event += "+"; + event += to_string(++workspace_n); - for (auto&& ws : m_workspaces) { - if (!ws.get()->label->get().empty()) - builder->cmd(mousebtn::LEFT, string(EVENT_CLICK) + to_string(++workspace_n)); - - builder->node(ws.get()->label); - - if (ws->flag == bspwm_flag::WORKSPACE_ACTIVE && m_formatter->has(TAG_LABEL_MODE)) { - for (auto&& mode : m_modes) builder->node(mode); + builder->cmd(mousebtn::LEFT, event); + builder->node(ws.second); + builder->cmd_close(true); + } } - if (!ws.get()->label->get().empty()) - builder->cmd_close(true); + return workspace_n > 0; + } else if (tag == TAG_LABEL_MODE && m_monitors[m_index]->focused) { + int modes_n = 0; + + for (auto&& mode : m_monitors[m_index]->modes) { + builder->node(mode); + modes_n++; + } + + return modes_n > 0; } - return true; + return false; } bool bspwm_module::handle_event(string cmd) { - if (cmd.find(EVENT_CLICK) == string::npos || cmd.length() <= strlen(EVENT_CLICK)) + if (cmd.find(EVENT_CLICK) != 0) { return false; + } try { - auto ipc = bspwm_util::make_connection(); - auto payload = bspwm_util::make_payload( - "desktop -f " + m_monitor + ":^" + cmd.substr(strlen(EVENT_CLICK))); + cmd.erase(0, strlen(EVENT_CLICK)); - m_log.info("%s: Sending desktop focus command to ipc handler", name()); + size_t separator = string_util::find_nth(cmd, 0, "+", 1); + size_t monitor_n = std::atoi(cmd.substr(0, separator).c_str()); + string workspace_n = cmd.substr(separator + 1); - ipc->send(payload->data, payload->len, 0); - ipc->disconnect(); + if (monitor_n < m_monitors.size()) { + auto ipc = bspwm_util::make_connection(); + auto payload = bspwm_util::make_payload( + "desktop -f " + m_monitors[monitor_n]->name + ":^" + workspace_n); + + m_log.info("%s: Sending desktop focus command to ipc handler", name()); + + ipc->send(payload->data, payload->len, 0); + ipc->disconnect(); + } else { + m_log.err("%s: Invalid monitor index in command: %s", name(), cmd); + } } catch (const system_error& err) { m_log.err("%s: %s", name(), err.what()); }