2016-11-02 15:22:45 -04:00
|
|
|
#include <sys/socket.h>
|
|
|
|
|
2016-11-19 09:49:03 -05:00
|
|
|
#include "drawtypes/iconset.hpp"
|
|
|
|
#include "drawtypes/label.hpp"
|
2016-11-30 12:23:11 -05:00
|
|
|
#include "modules/i3.hpp"
|
|
|
|
#include "utils/file.hpp"
|
2016-11-19 09:49:03 -05:00
|
|
|
|
2016-11-20 17:04:31 -05:00
|
|
|
#include "modules/meta/base.inl"
|
|
|
|
#include "modules/meta/event_module.inl"
|
|
|
|
|
2016-11-19 00:22:44 -05:00
|
|
|
POLYBAR_NS
|
2016-11-02 15:22:45 -04:00
|
|
|
|
|
|
|
namespace modules {
|
2016-11-20 17:04:31 -05:00
|
|
|
template class module<i3_module>;
|
|
|
|
template class event_module<i3_module>;
|
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
i3_module::workspace::operator bool() {
|
2016-11-19 09:49:03 -05:00
|
|
|
return label && *label;
|
|
|
|
}
|
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
void i3_module::setup() {
|
|
|
|
auto socket_path = i3ipc::get_socketpath();
|
|
|
|
|
|
|
|
if (!file_util::exists(socket_path)) {
|
|
|
|
throw module_error("Could not find socket: " + (socket_path.empty() ? "<empty>" : socket_path));
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ipc = make_unique<i3ipc::connection>();
|
|
|
|
|
2016-11-11 17:57:12 -05:00
|
|
|
// Load configuration values
|
|
|
|
GET_CONFIG_VALUE(name(), m_click, "enable-click");
|
|
|
|
GET_CONFIG_VALUE(name(), m_scroll, "enable-scroll");
|
2016-12-13 18:46:33 -05:00
|
|
|
GET_CONFIG_VALUE(name(), m_wrap, "wrapping-scroll");
|
2016-11-02 15:22:45 -04:00
|
|
|
GET_CONFIG_VALUE(name(), m_indexsort, "index-sort");
|
|
|
|
GET_CONFIG_VALUE(name(), m_pinworkspaces, "pin-workspaces");
|
|
|
|
GET_CONFIG_VALUE(name(), m_strip_wsnumbers, "strip-wsnumbers");
|
|
|
|
GET_CONFIG_VALUE(name(), m_wsname_maxlen, "wsname-maxlen");
|
|
|
|
|
2016-11-11 17:57:12 -05:00
|
|
|
// Add formats and create components
|
2016-11-30 12:23:11 -05:00
|
|
|
m_formatter->add(DEFAULT_FORMAT, DEFAULT_TAGS, {TAG_LABEL_STATE, TAG_LABEL_MODE});
|
2016-11-02 15:22:45 -04:00
|
|
|
|
|
|
|
if (m_formatter->has(TAG_LABEL_STATE)) {
|
2016-11-11 17:57:12 -05:00
|
|
|
m_statelabels.insert(
|
2016-11-30 12:23:11 -05:00
|
|
|
make_pair(state::FOCUSED, load_optional_label(m_conf, name(), "label-focused", DEFAULT_WS_LABEL)));
|
|
|
|
m_statelabels.insert(
|
|
|
|
make_pair(state::UNFOCUSED, load_optional_label(m_conf, name(), "label-unfocused", DEFAULT_WS_LABEL)));
|
|
|
|
m_statelabels.insert(
|
|
|
|
make_pair(state::VISIBLE, load_optional_label(m_conf, name(), "label-visible", DEFAULT_WS_LABEL)));
|
|
|
|
m_statelabels.insert(
|
|
|
|
make_pair(state::URGENT, load_optional_label(m_conf, name(), "label-urgent", DEFAULT_WS_LABEL)));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_formatter->has(TAG_LABEL_MODE)) {
|
|
|
|
m_modelabel = load_optional_label(m_conf, name(), "label-mode", "%mode%");
|
2016-11-02 15:22:45 -04:00
|
|
|
}
|
|
|
|
|
2016-11-25 07:55:15 -05:00
|
|
|
m_icons = make_shared<iconset>();
|
|
|
|
m_icons->add(DEFAULT_WS_ICON, make_shared<label>(m_conf.get<string>(name(), DEFAULT_WS_ICON, "")));
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-25 07:55:15 -05:00
|
|
|
for (const auto& workspace : m_conf.get_list<string>(name(), "ws-icon", {})) {
|
2016-11-02 15:22:45 -04:00
|
|
|
auto vec = string_util::split(workspace, ';');
|
2016-11-25 07:55:15 -05:00
|
|
|
if (vec.size() == 2) {
|
|
|
|
m_icons->add(vec[0], make_shared<label>(vec[1]));
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2016-11-30 12:23:11 -05:00
|
|
|
if (m_modelabel) {
|
|
|
|
m_ipc->on_mode_event = [this](const i3ipc::mode_t& mode) {
|
|
|
|
m_modeactive = (mode.change != DEFAULT_MODE);
|
|
|
|
if (m_modeactive) {
|
|
|
|
m_modelabel->reset_tokens();
|
|
|
|
m_modelabel->replace_token("%mode%", mode.change);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
m_ipc->subscribe(i3ipc::ET_WORKSPACE | i3ipc::ET_MODE);
|
|
|
|
} catch (const exception& err) {
|
2016-11-02 15:22:45 -04:00
|
|
|
throw module_error(err.what());
|
|
|
|
}
|
2016-11-30 12:23:11 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
void i3_module::stop() {
|
2016-11-02 15:22:45 -04:00
|
|
|
try {
|
2016-11-30 12:23:11 -05:00
|
|
|
if (m_ipc) {
|
|
|
|
m_log.info("%s: Disconnecting from socket", name());
|
|
|
|
shutdown(m_ipc->get_event_socket_fd(), SHUT_RDWR);
|
|
|
|
shutdown(m_ipc->get_main_socket_fd(), SHUT_RDWR);
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
} catch (...) {
|
|
|
|
}
|
|
|
|
|
|
|
|
event_module::stop();
|
2016-11-30 12:23:11 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
bool i3_module::has_event() {
|
|
|
|
try {
|
|
|
|
m_ipc->handle_event();
|
|
|
|
return true;
|
|
|
|
} catch (const exception& err) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
bool i3_module::update() {
|
2016-11-02 15:22:45 -04:00
|
|
|
m_workspaces.clear();
|
|
|
|
i3_util::connection_t ipc;
|
|
|
|
|
|
|
|
try {
|
|
|
|
auto workspaces = ipc.get_workspaces();
|
|
|
|
vector<shared_ptr<i3ipc::workspace_t>> sorted = workspaces;
|
|
|
|
string focused_output;
|
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
for (auto&& ws : workspaces) {
|
|
|
|
if (ws->focused) {
|
|
|
|
focused_output = ws->output;
|
2016-11-02 15:22:45 -04:00
|
|
|
break;
|
|
|
|
}
|
2016-11-25 07:55:15 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
|
|
|
if (m_indexsort) {
|
|
|
|
using ws_t = shared_ptr<i3ipc::workspace_t>;
|
|
|
|
// clang-format off
|
|
|
|
sort(sorted.begin(), sorted.end(), [](ws_t ws1, ws_t ws2){
|
|
|
|
return ws1->num < ws2->num;
|
|
|
|
});
|
|
|
|
// clang-format on
|
|
|
|
}
|
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
for (auto&& ws : sorted) {
|
|
|
|
if (m_pinworkspaces && ws->output != m_bar.monitor->name) {
|
2016-11-02 15:22:45 -04:00
|
|
|
continue;
|
2016-11-25 07:55:15 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 21:35:54 -05:00
|
|
|
auto ws_state = state::NONE;
|
2016-11-30 12:23:11 -05:00
|
|
|
if (ws->focused) {
|
2016-11-30 21:35:54 -05:00
|
|
|
ws_state = state::FOCUSED;
|
2016-11-30 12:23:11 -05:00
|
|
|
} else if (ws->urgent) {
|
2016-11-30 21:35:54 -05:00
|
|
|
ws_state = state::URGENT;
|
2016-11-30 12:23:11 -05:00
|
|
|
} else if (!ws->visible || (ws->visible && ws->output != focused_output)) {
|
2016-11-30 21:35:54 -05:00
|
|
|
ws_state = state::UNFOCUSED;
|
2016-11-25 07:55:15 -05:00
|
|
|
} else {
|
2016-11-30 21:35:54 -05:00
|
|
|
ws_state = state::VISIBLE;
|
2016-11-25 07:55:15 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
string wsname{ws->name};
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-03 10:43:45 -04:00
|
|
|
// Remove workspace numbers "0:"
|
2016-11-25 07:55:15 -05:00
|
|
|
if (m_strip_wsnumbers) {
|
2016-11-03 10:43:45 -04:00
|
|
|
wsname.erase(0, string_util::find_nth(wsname, 0, ":", 1) + 1);
|
2016-11-25 07:55:15 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-03 10:43:45 -04:00
|
|
|
// Trim leading and trailing whitespace
|
|
|
|
wsname = string_util::trim(wsname, ' ');
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-03 10:43:45 -04:00
|
|
|
// Cap at configured max length
|
2016-11-25 07:55:15 -05:00
|
|
|
if (m_wsname_maxlen > 0 && wsname.length() > m_wsname_maxlen) {
|
2016-11-02 15:22:45 -04:00
|
|
|
wsname.erase(m_wsname_maxlen);
|
2016-11-25 07:55:15 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
auto icon = m_icons->get(ws->name, DEFAULT_WS_ICON);
|
2016-11-30 21:35:54 -05:00
|
|
|
auto label = m_statelabels.find(ws_state)->second->clone();
|
2016-11-02 15:22:45 -04:00
|
|
|
|
|
|
|
label->reset_tokens();
|
2016-11-30 12:23:11 -05:00
|
|
|
label->replace_token("%output%", ws->output);
|
2016-11-02 15:22:45 -04:00
|
|
|
label->replace_token("%name%", wsname);
|
|
|
|
label->replace_token("%icon%", icon->get());
|
2016-11-30 12:23:11 -05:00
|
|
|
label->replace_token("%index%", to_string(ws->num));
|
2016-11-30 21:35:54 -05:00
|
|
|
m_workspaces.emplace_back(make_unique<workspace>(ws->num, ws_state, move(label)));
|
2016-11-02 15:22:45 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2016-11-30 12:23:11 -05:00
|
|
|
} catch (const exception& err) {
|
2016-11-02 15:22:45 -04:00
|
|
|
m_log.err("%s: %s", name(), err.what());
|
|
|
|
return false;
|
|
|
|
}
|
2016-11-30 12:23:11 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
bool i3_module::build(builder* builder, const string& tag) const {
|
|
|
|
if (tag == TAG_LABEL_MODE && m_modeactive) {
|
|
|
|
builder->node(m_modelabel);
|
|
|
|
} else if (tag == TAG_LABEL_STATE && !m_workspaces.empty()) {
|
|
|
|
if (m_scroll) {
|
|
|
|
builder->cmd(mousebtn::SCROLL_DOWN, EVENT_SCROLL_DOWN);
|
|
|
|
builder->cmd(mousebtn::SCROLL_UP, EVENT_SCROLL_UP);
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
for (auto&& ws : m_workspaces) {
|
|
|
|
if (m_click) {
|
|
|
|
builder->cmd(mousebtn::LEFT, string{EVENT_CLICK} + to_string(ws->index));
|
|
|
|
builder->node(ws->label);
|
|
|
|
builder->cmd_close();
|
|
|
|
} else {
|
|
|
|
builder->node(ws->label);
|
|
|
|
}
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
if (m_scroll) {
|
|
|
|
builder->cmd_close();
|
2016-11-24 13:24:47 -05:00
|
|
|
builder->cmd_close();
|
2016-11-11 17:57:12 -05:00
|
|
|
}
|
2016-11-30 12:23:11 -05:00
|
|
|
} else {
|
|
|
|
return false;
|
2016-11-11 17:57:12 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2016-11-30 12:23:11 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
|
2016-11-30 12:23:11 -05:00
|
|
|
bool i3_module::handle_event(string cmd) {
|
2016-11-02 15:22:45 -04:00
|
|
|
try {
|
2016-12-13 18:46:33 -05:00
|
|
|
|
2016-11-02 15:22:45 -04:00
|
|
|
if (cmd.compare(0, strlen(EVENT_CLICK), EVENT_CLICK) == 0) {
|
2016-12-13 18:46:33 -05:00
|
|
|
const i3_util::connection_t conn{};
|
|
|
|
const string workspace_num{cmd.substr(strlen(EVENT_CLICK))};
|
|
|
|
|
|
|
|
if (i3_util::focused_workspace(conn)->num != atoi(workspace_num.c_str())) {
|
|
|
|
m_log.info("%s: Sending workspace focus command to ipc handler", name());
|
|
|
|
conn.send_command("workspace number " + workspace_num);
|
|
|
|
} else {
|
|
|
|
m_log.warn("%s: Ignoring workspace focus command (reason: workspace already focused)", name());
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
} else if (cmd.compare(0, strlen(EVENT_SCROLL_DOWN), EVENT_SCROLL_DOWN) == 0) {
|
2016-12-13 18:46:33 -05:00
|
|
|
const i3_util::connection_t conn{};
|
|
|
|
|
|
|
|
if (m_wrap || conn.get_workspaces().back()->num != i3_util::focused_workspace(conn)->num) {
|
|
|
|
m_log.info("%s: Sending workspace next command to ipc handler", name());
|
|
|
|
conn.send_command("workspace next_on_output");
|
|
|
|
} else {
|
|
|
|
m_log.warn("%s: Ignoring workspace next command (reason: `wrap = false`)", name());
|
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
} else if (cmd.compare(0, strlen(EVENT_SCROLL_UP), EVENT_SCROLL_UP) == 0) {
|
2016-12-13 18:46:33 -05:00
|
|
|
const i3_util::connection_t conn{};
|
|
|
|
|
|
|
|
if (m_wrap || conn.get_workspaces().front()->num != i3_util::focused_workspace(conn)->num) {
|
|
|
|
m_log.info("%s: Sending workspace prev command to ipc handler", name());
|
|
|
|
conn.send_command("workspace prev_on_output");
|
|
|
|
} else {
|
|
|
|
m_log.warn("%s: Ignoring workspace prev command (reason: `wrapping-scroll = false`)", name());
|
|
|
|
}
|
2016-12-05 07:17:09 -05:00
|
|
|
} else {
|
|
|
|
return false;
|
2016-11-02 15:22:45 -04:00
|
|
|
}
|
2016-11-30 12:23:11 -05:00
|
|
|
} catch (const exception& err) {
|
2016-11-02 15:22:45 -04:00
|
|
|
m_log.err("%s: %s", name(), err.what());
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2016-11-30 12:23:11 -05:00
|
|
|
}
|
2016-11-02 15:22:45 -04:00
|
|
|
}
|
|
|
|
|
2016-11-19 00:22:44 -05:00
|
|
|
POLYBAR_NS_END
|