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 <label-monitor>

- Treat <label-mode> as a normal format tag
This commit is contained in:
Michael Carlberg 2016-11-03 17:54:26 +01:00
parent 12a64bd3d6
commit 5f21d7d440
2 changed files with 161 additions and 95 deletions

View File

@ -8,13 +8,15 @@
LEMONBUDDY_NS LEMONBUDDY_NS
namespace modules { namespace modules {
enum class bspwm_flag { enum class state_ws {
WORKSPACE_NONE, WORKSPACE_NONE,
WORKSPACE_ACTIVE, WORKSPACE_ACTIVE,
WORKSPACE_URGENT, WORKSPACE_URGENT,
WORKSPACE_EMPTY, WORKSPACE_EMPTY,
WORKSPACE_OCCUPIED, WORKSPACE_OCCUPIED,
WORKSPACE_DIMMED, // used when the monitor is out of focus WORKSPACE_DIMMED, // used when the monitor is out of focus
};
enum class state_mode {
MODE_NONE, MODE_NONE,
MODE_LAYOUT_MONOCLE, MODE_LAYOUT_MONOCLE,
MODE_LAYOUT_TILED, MODE_LAYOUT_TILED,
@ -25,20 +27,14 @@ namespace modules {
MODE_NODE_PRIVATE MODE_NODE_PRIVATE
}; };
struct bspwm_workspace { struct bspwm_monitor {
bspwm_flag flag; vector<pair<state_ws, label_t>> workspaces;
vector<label_t> modes;
label_t label; label_t label;
string name;
bspwm_workspace(bspwm_flag flag, label_t&& label) bool focused = false;
: flag(flag), label(forward<decltype(label)>(label)) {}
operator bool() {
return label && *label;
}
}; };
using bspwm_workspace_t = unique_ptr<bspwm_workspace>;
class bspwm_module : public event_module<bspwm_module> { class bspwm_module : public event_module<bspwm_module> {
public: public:
using event_module::event_module; using event_module::event_module;
@ -47,6 +43,7 @@ namespace modules {
void stop(); void stop();
bool has_event(); bool has_event();
bool update(); bool update();
string get_output();
bool build(builder* builder, string tag) const; bool build(builder* builder, string tag) const;
bool handle_event(string cmd); bool handle_event(string cmd);
bool receive_events() const; bool receive_events() const;
@ -54,19 +51,26 @@ namespace modules {
private: private:
static constexpr auto DEFAULT_WS_ICON = "ws-icon-default"; static constexpr auto DEFAULT_WS_ICON = "ws-icon-default";
static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%"; static constexpr auto DEFAULT_WS_LABEL = "%icon% %name%";
static constexpr auto DEFAULT_MONITOR_LABEL = "%name%";
static constexpr auto TAG_LABEL_MONITOR = "<label-monitor>";
static constexpr auto TAG_LABEL_STATE = "<label-state>"; static constexpr auto TAG_LABEL_STATE = "<label-state>";
static constexpr auto TAG_LABEL_MODE = "<label-mode>"; static constexpr auto TAG_LABEL_MODE = "<label-mode>";
static constexpr auto EVENT_CLICK = "bwm"; static constexpr auto EVENT_CLICK = "bwm";
bspwm_util::connection_t m_subscriber; bspwm_util::connection_t m_subscriber;
map<bspwm_flag, label_t> m_modelabels; vector<unique_ptr<bspwm_monitor>> m_monitors;
map<bspwm_flag, label_t> m_statelabels;
vector<bspwm_workspace_t> m_workspaces; map<state_mode, label_t> m_modelabels;
vector<label_t> m_modes; map<state_ws, label_t> m_statelabels;
label_t m_monitorlabel;
iconset_t m_icons; iconset_t m_icons;
bool m_pinworkspaces = true;
string m_monitor; string m_monitor;
unsigned long m_hash; unsigned long m_hash;
// used while formatting output
size_t m_index = 0;
}; };
} }

View File

@ -7,40 +7,49 @@ LEMONBUDDY_NS
namespace modules { namespace modules {
void bspwm_module::setup() { void bspwm_module::setup() {
m_monitor = m_bar.monitor->name; 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 {{{ // 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)) { 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))); 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))); 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))); 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))); load_optional_label(m_conf, name(), "label-empty", DEFAULT_WS_LABEL)));
m_statelabels.insert(make_pair( 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)) { if (m_formatter->has(TAG_LABEL_MODE)) {
m_modelabels.insert(make_pair( 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( m_modelabels.insert(make_pair(
bspwm_flag::MODE_LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled"))); state_mode::MODE_LAYOUT_TILED, load_optional_label(m_conf, name(), "label-tiled")));
m_modelabels.insert(make_pair(bspwm_flag::MODE_STATE_FULLSCREEN, m_modelabels.insert(make_pair(state_mode::MODE_STATE_FULLSCREEN,
load_optional_label(m_conf, name(), "label-fullscreen"))); load_optional_label(m_conf, name(), "label-fullscreen")));
m_modelabels.insert(make_pair( 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( 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( 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( 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()}; m_icons = iconset_t{new iconset()};
@ -114,68 +123,83 @@ namespace modules {
m_hash = hash; m_hash = hash;
// Extract the string for the defined monitor // Extract the string for the defined monitor
const auto needle_active = ":M" + m_monitor + ":"; if (m_pinworkspaces) {
const auto needle_inactive = ":m" + m_monitor + ":"; 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(), ":"); data = data.replace(pos, prefix.length(), ":");
if ((pos = data.find(needle_active)) != std::string::npos) } else {
data.erase(0, pos + 1); return false;
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);
m_modes.clear();
m_workspaces.clear();
bool monitor_focused = true;
int workspace_n = 0; int workspace_n = 0;
m_monitors.clear();
for (auto&& tag : string_util::split(data, ':')) { for (auto&& tag : string_util::split(data, ':')) {
if (tag.empty()) if (tag.empty()) {
continue; continue;
}
auto value = tag.size() > 0 ? tag.substr(1) : ""; auto value = tag.size() > 0 ? tag.substr(1) : "";
auto workspace_flag = bspwm_flag::WORKSPACE_NONE; auto workspace_flag = state_ws::WORKSPACE_NONE;
auto mode_flag = bspwm_flag::MODE_NONE; auto mode_flag = state_mode::MODE_NONE;
if (tag[0] == 'm' || tag[0] == 'M') {
m_monitors.emplace_back(make_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]) { switch (tag[0]) {
case 'm': case 'm':
monitor_focused = false; m_monitors.back()->focused = false;
break; break;
case 'M': case 'M':
monitor_focused = true; m_monitors.back()->focused = true;
break; break;
case 'F': case 'F':
workspace_flag = bspwm_flag::WORKSPACE_ACTIVE; workspace_flag = state_ws::WORKSPACE_ACTIVE;
break; break;
case 'O': case 'O':
workspace_flag = bspwm_flag::WORKSPACE_ACTIVE; workspace_flag = state_ws::WORKSPACE_ACTIVE;
break; break;
case 'o': case 'o':
workspace_flag = bspwm_flag::WORKSPACE_OCCUPIED; workspace_flag = state_ws::WORKSPACE_OCCUPIED;
break; break;
case 'U': case 'U':
workspace_flag = bspwm_flag::WORKSPACE_URGENT; workspace_flag = state_ws::WORKSPACE_URGENT;
break; break;
case 'u': case 'u':
workspace_flag = bspwm_flag::WORKSPACE_URGENT; workspace_flag = state_ws::WORKSPACE_URGENT;
break; break;
case 'f': case 'f':
workspace_flag = bspwm_flag::WORKSPACE_EMPTY; workspace_flag = state_ws::WORKSPACE_EMPTY;
break; break;
case 'L': case 'L':
switch (value[0]) { switch (value[0]) {
case 0: case 0:
break; break;
case 'M': case 'M':
mode_flag = bspwm_flag::MODE_LAYOUT_MONOCLE; mode_flag = state_mode::MODE_LAYOUT_MONOCLE;
break; break;
case 'T': case 'T':
mode_flag = bspwm_flag::MODE_LAYOUT_TILED; mode_flag = state_mode::MODE_LAYOUT_TILED;
break; break;
default: default:
m_log.warn("%s: Undefined L => '%s'", name(), value); m_log.warn("%s: Undefined L => '%s'", name(), value);
@ -189,10 +213,10 @@ namespace modules {
case 'T': case 'T':
break; break;
case '=': case '=':
mode_flag = bspwm_flag::MODE_STATE_FULLSCREEN; mode_flag = state_mode::MODE_STATE_FULLSCREEN;
break; break;
case 'F': case 'F':
mode_flag = bspwm_flag::MODE_STATE_FLOATING; mode_flag = state_mode::MODE_STATE_FLOATING;
break; break;
default: default:
m_log.warn("%s: Undefined T => '%s'", name(), value); m_log.warn("%s: Undefined T => '%s'", name(), value);
@ -200,25 +224,30 @@ namespace modules {
break; break;
case 'G': case 'G':
if (!m_monitors.back()->focused) {
break;
}
for (int i = 0; i < (int)value.length(); i++) { for (int i = 0; i < (int)value.length(); i++) {
switch (value[i]) { switch (value[i]) {
case 0: case 0:
break; break;
case 'L': case 'L':
mode_flag = bspwm_flag::MODE_NODE_LOCKED; mode_flag = state_mode::MODE_NODE_LOCKED;
break; break;
case 'S': case 'S':
mode_flag = bspwm_flag::MODE_NODE_STICKY; mode_flag = state_mode::MODE_NODE_STICKY;
break; break;
case 'P': case 'P':
mode_flag = bspwm_flag::MODE_NODE_PRIVATE; mode_flag = state_mode::MODE_NODE_PRIVATE;
break; break;
default: default:
m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1)); m_log.warn("%s: Undefined G => '%s'", name(), value.substr(i, 1));
} }
if (mode_flag != bspwm_flag::MODE_NONE && !m_modelabels.empty()) if (mode_flag != state_mode::MODE_NONE && !m_modelabels.empty()) {
m_modes.emplace_back(m_modelabels.find(mode_flag)->second->clone()); m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
}
} }
continue; continue;
@ -226,67 +255,100 @@ namespace modules {
m_log.warn("%s: Undefined tag => '%s'", name(), tag.substr(0, 1)); 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 icon = m_icons->get(value, DEFAULT_WS_ICON);
auto label = m_statelabels.find(workspace_flag)->second->clone(); auto label = m_statelabels.find(workspace_flag)->second->clone();
if (!monitor_focused) if (!m_monitors.back()->focused) {
label->replace_defined_values(m_statelabels.find(bspwm_flag::WORKSPACE_DIMMED)->second); label->replace_defined_values(m_statelabels.find(state_ws::WORKSPACE_DIMMED)->second);
}
label->reset_tokens(); label->reset_tokens();
label->replace_token("%name%", value); label->replace_token("%name%", value);
label->replace_token("%icon%", icon->get()); label->replace_token("%icon%", icon->get());
label->replace_token("%index%", to_string(++workspace_n)); label->replace_token("%index%", to_string(++workspace_n));
m_workspaces.emplace_back(make_unique<bspwm_workspace>(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()) if (mode_flag != state_mode::MODE_NONE && !m_modelabels.empty()) {
m_modes.emplace_back(m_modelabels.find(mode_flag)->second->clone()); m_monitors.back()->modes.emplace_back(m_modelabels.find(mode_flag)->second->clone());
}
} }
if (!monitor_focused)
m_modes.clear();
return true; 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 { bool bspwm_module::build(builder* builder, string tag) const {
if (tag != TAG_LABEL_STATE) if (tag == TAG_LABEL_MONITOR) {
return false; 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) { builder->cmd(mousebtn::LEFT, event);
if (!ws.get()->label->get().empty()) builder->node(ws.second);
builder->cmd(mousebtn::LEFT, string(EVENT_CLICK) + to_string(++workspace_n)); builder->cmd_close(true);
}
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);
} }
if (!ws.get()->label->get().empty()) return workspace_n > 0;
builder->cmd_close(true); } 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) { 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; return false;
}
try { try {
auto ipc = bspwm_util::make_connection(); cmd.erase(0, strlen(EVENT_CLICK));
auto payload = bspwm_util::make_payload(
"desktop -f " + m_monitor + ":^" + cmd.substr(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); if (monitor_n < m_monitors.size()) {
ipc->disconnect(); 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) { } catch (const system_error& err) {
m_log.err("%s: %s", name(), err.what()); m_log.err("%s: %s", name(), err.what());
} }