Add niri/workspaces, niri/window, niri/language
This commit is contained in:
249
src/modules/niri/backend.cpp
Normal file
249
src/modules/niri/backend.cpp
Normal file
@ -0,0 +1,249 @@
|
||||
#include "modules/niri/backend.hpp"
|
||||
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <ext/stdio_filebuf.h>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
int IPC::connectToSocket() {
|
||||
const char* socket_path = getenv("NIRI_SOCKET");
|
||||
|
||||
if (socket_path == nullptr) {
|
||||
spdlog::warn("Niri is not running, niri IPC will not be available.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
struct sockaddr_un addr;
|
||||
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
|
||||
if (socketfd == -1) {
|
||||
throw std::runtime_error("socketfd failed");
|
||||
}
|
||||
|
||||
addr.sun_family = AF_UNIX;
|
||||
|
||||
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
|
||||
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
|
||||
|
||||
int l = sizeof(struct sockaddr_un);
|
||||
|
||||
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
|
||||
close(socketfd);
|
||||
throw std::runtime_error("unable to connect");
|
||||
}
|
||||
|
||||
return socketfd;
|
||||
}
|
||||
|
||||
void IPC::startIPC() {
|
||||
// will start IPC and relay events to parseIPC
|
||||
|
||||
std::thread([&]() {
|
||||
int socketfd;
|
||||
try {
|
||||
socketfd = connectToSocket();
|
||||
} catch (std::exception &e) {
|
||||
spdlog::error("Niri IPC: failed to start, reason: {}", e.what());
|
||||
return;
|
||||
}
|
||||
if (socketfd == -1)
|
||||
return;
|
||||
|
||||
spdlog::info("Niri IPC starting");
|
||||
|
||||
__gnu_cxx::stdio_filebuf<char> filebuf(socketfd, std::ios::in | std::ios::out);
|
||||
std::iostream fs(&filebuf);
|
||||
fs << R"("EventStream")" << std::endl;
|
||||
|
||||
std::string line;
|
||||
std::getline(fs, line);
|
||||
if (line != R"({"Ok":"Handled"})") {
|
||||
spdlog::error("Niri IPC: failed to start event stream");
|
||||
return;
|
||||
}
|
||||
|
||||
while (std::getline(fs, line)) {
|
||||
spdlog::debug("Niri IPC: received {}", line);
|
||||
|
||||
try {
|
||||
parseIPC(line);
|
||||
} catch (std::exception& e) {
|
||||
spdlog::warn("Failed to parse IPC message: {}, reason: {}", line, e.what());
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void IPC::parseIPC(const std::string& line) {
|
||||
const auto ev = parser_.parse(line);
|
||||
const auto members = ev.getMemberNames();
|
||||
if (members.size() != 1)
|
||||
throw std::runtime_error("Event must have a single member");
|
||||
|
||||
{
|
||||
auto lock = lockData();
|
||||
|
||||
if (const auto &payload = ev["WorkspacesChanged"]) {
|
||||
workspaces_.clear();
|
||||
const auto &values = payload["workspaces"];
|
||||
std::copy(values.begin(), values.end(), std::back_inserter(workspaces_));
|
||||
|
||||
std::sort(workspaces_.begin(), workspaces_.end(),
|
||||
[](const auto &a, const auto &b) {
|
||||
const auto &aOutput = a["output"].asString();
|
||||
const auto &bOutput = b["output"].asString();
|
||||
const auto aIdx = a["idx"].asUInt();
|
||||
const auto bIdx = b["idx"].asUInt();
|
||||
if (aOutput == bOutput)
|
||||
return aIdx < bIdx;
|
||||
return aOutput < bOutput;
|
||||
});
|
||||
} else if (const auto& payload = ev["WorkspaceActivated"]) {
|
||||
const auto id = payload["id"].asUInt64();
|
||||
const auto focused = payload["focused"].asBool();
|
||||
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[id](const auto &ws) { return ws["id"].asUInt64() == id; });
|
||||
if (it != workspaces_.end()) {
|
||||
const auto &ws = *it;
|
||||
const auto &output = ws["output"].asString();
|
||||
for (auto &ws : workspaces_) {
|
||||
const auto got_activated = (ws["id"].asUInt64() == id);
|
||||
if (ws["output"] == output)
|
||||
ws["is_active"] = got_activated;
|
||||
|
||||
if (focused)
|
||||
ws["is_focused"] = got_activated;
|
||||
}
|
||||
} else {
|
||||
spdlog::error("Activated unknown workspace");
|
||||
}
|
||||
} else if (const auto& payload = ev["WorkspaceActiveWindowChanged"]) {
|
||||
const auto workspaceId = payload["workspace_id"].asUInt64();
|
||||
auto it = std::find_if(workspaces_.begin(), workspaces_.end(),
|
||||
[workspaceId](const auto &ws) { return ws["id"].asUInt64() == workspaceId; });
|
||||
if (it != workspaces_.end()) {
|
||||
auto &ws = *it;
|
||||
ws["active_window_id"] = payload["active_window_id"];
|
||||
} else {
|
||||
spdlog::error("Active window changed on unknown workspace");
|
||||
}
|
||||
} else if (const auto &payload = ev["KeyboardLayoutsChanged"]) {
|
||||
const auto &layouts = payload["keyboard_layouts"];
|
||||
const auto &names = layouts["names"];
|
||||
keyboardLayoutCurrent_ = layouts["current_idx"].asUInt();
|
||||
|
||||
keyboardLayoutNames_.clear();
|
||||
for (const auto &fullName : names)
|
||||
keyboardLayoutNames_.push_back(fullName.asString());
|
||||
} else if (const auto& payload = ev["KeyboardLayoutSwitched"]) {
|
||||
keyboardLayoutCurrent_ = payload["idx"].asUInt();
|
||||
} else if (const auto &payload = ev["WindowsChanged"]) {
|
||||
windows_.clear();
|
||||
const auto &values = payload["windows"];
|
||||
std::copy(values.begin(), values.end(), std::back_inserter(windows_));
|
||||
} else if (const auto &payload = ev["WindowOpenedOrChanged"]) {
|
||||
const auto &window = payload["window"];
|
||||
const auto id = window["id"].asUInt64();
|
||||
auto it = std::find_if(windows_.begin(), windows_.end(),
|
||||
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
||||
if (it == windows_.end()) {
|
||||
windows_.push_back(window);
|
||||
|
||||
if (window["is_focused"].asBool()) {
|
||||
for (auto &win : windows_) {
|
||||
win["is_focused"] = win["id"].asUInt64() == id;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
*it = window;
|
||||
}
|
||||
} else if (const auto &payload = ev["WindowClosed"]) {
|
||||
const auto id = payload["id"].asUInt64();
|
||||
auto it = std::find_if(windows_.begin(), windows_.end(),
|
||||
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
||||
if (it != windows_.end()) {
|
||||
windows_.erase(it);
|
||||
} else {
|
||||
spdlog::error("Unknown window closed");
|
||||
}
|
||||
} else if (const auto &payload = ev["WindowFocusChanged"]) {
|
||||
const auto focused = !payload["id"].isNull();
|
||||
const auto id = payload["id"].asUInt64();
|
||||
for (auto &win : windows_) {
|
||||
win["is_focused"] = focused && win["id"].asUInt64() == id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
|
||||
for (auto& [eventname, handler] : callbacks_) {
|
||||
if (eventname == members[0]) {
|
||||
handler->onEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {
|
||||
if (ev_handler == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
callbacks_.emplace_back(ev, ev_handler);
|
||||
}
|
||||
|
||||
void IPC::unregisterForIPC(EventHandler* ev_handler) {
|
||||
if (ev_handler == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock lock(callbackMutex_);
|
||||
|
||||
for (auto it = callbacks_.begin(); it != callbacks_.end();) {
|
||||
auto& [eventname, handler] = *it;
|
||||
if (handler == ev_handler) {
|
||||
it = callbacks_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Json::Value IPC::send(const Json::Value& request) {
|
||||
int socketfd = connectToSocket();
|
||||
if (socketfd == -1)
|
||||
throw std::runtime_error("Niri is not running");
|
||||
|
||||
__gnu_cxx::stdio_filebuf<char> filebuf(socketfd, std::ios::in | std::ios::out);
|
||||
std::iostream fs(&filebuf);
|
||||
|
||||
// Niri needs the request on a single line.
|
||||
Json::StreamWriterBuilder builder;
|
||||
builder["indentation"] = "";
|
||||
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
|
||||
writer->write(request, &fs);
|
||||
fs << std::endl;
|
||||
|
||||
Json::Value response;
|
||||
fs >> response;
|
||||
return response;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::hyprland
|
138
src/modules/niri/language.cpp
Normal file
138
src/modules/niri/language.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
#include "modules/niri/language.hpp"
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbregistry.h>
|
||||
|
||||
#include "util/string.hpp"
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
Language::Language(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
|
||||
label_.hide();
|
||||
|
||||
if (!gIPC)
|
||||
gIPC = std::make_unique<IPC>();
|
||||
|
||||
gIPC->registerForIPC("KeyboardLayoutsChanged", this);
|
||||
gIPC->registerForIPC("KeyboardLayoutSwitched", this);
|
||||
|
||||
updateFromIPC();
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Language::~Language() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
// wait for possible event handler to finish
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
}
|
||||
|
||||
void Language::updateFromIPC() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto ipcLock = gIPC->lockData();
|
||||
|
||||
layouts_.clear();
|
||||
for (const auto &fullName : gIPC->keyboardLayoutNames())
|
||||
layouts_.push_back(getLayout(fullName));
|
||||
|
||||
current_idx_ = gIPC->keyboardLayoutCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Language::doUpdate - update workspaces in UI thread.
|
||||
*
|
||||
* Note: some member fields are modified by both UI thread and event listener thread, use mutex_ to
|
||||
* protect these member fields, and lock should released before calling ALabel::update().
|
||||
*/
|
||||
void Language::doUpdate() {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
if (layouts_.size() <= current_idx_) {
|
||||
spdlog::error("niri language layout index out of bounds");
|
||||
label_.hide();
|
||||
return;
|
||||
}
|
||||
const auto &layout = layouts_[current_idx_];
|
||||
|
||||
spdlog::debug("niri language update with full name {}", layout.full_name);
|
||||
spdlog::debug("niri language update with short name {}", layout.short_name);
|
||||
spdlog::debug("niri language update with short description {}", layout.short_description);
|
||||
spdlog::debug("niri language update with variant {}", layout.variant);
|
||||
|
||||
std::string layoutName = std::string{};
|
||||
if (config_.isMember("format-" + layout.short_description + "-" + layout.variant)) {
|
||||
const auto propName = "format-" + layout.short_description + "-" + layout.variant;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else if (config_.isMember("format-" + layout.short_description)) {
|
||||
const auto propName = "format-" + layout.short_description;
|
||||
layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
|
||||
} else {
|
||||
layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg("long", layout.full_name),
|
||||
fmt::arg("short", layout.short_name),
|
||||
fmt::arg("shortDescription", layout.short_description),
|
||||
fmt::arg("variant", layout.variant)));
|
||||
}
|
||||
|
||||
spdlog::debug("niri language formatted layout name {}", layoutName);
|
||||
|
||||
if (!format_.empty()) {
|
||||
label_.show();
|
||||
label_.set_markup(layoutName);
|
||||
} else {
|
||||
label_.hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Language::update() {
|
||||
doUpdate();
|
||||
ALabel::update();
|
||||
}
|
||||
|
||||
void Language::onEvent(const Json::Value& ev) {
|
||||
if (ev["KeyboardLayoutsChanged"]) {
|
||||
updateFromIPC();
|
||||
} else if (ev["KeyboardLayoutSwitched"]) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto ipcLock = gIPC->lockData();
|
||||
current_idx_ = gIPC->keyboardLayoutCurrent();
|
||||
}
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Language::Layout Language::getLayout(const std::string &fullName) {
|
||||
auto* const context = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
|
||||
rxkb_context_parse_default_ruleset(context);
|
||||
|
||||
rxkb_layout* layout = rxkb_layout_first(context);
|
||||
while (layout != nullptr) {
|
||||
std::string nameOfLayout = rxkb_layout_get_description(layout);
|
||||
|
||||
if (nameOfLayout != fullName) {
|
||||
layout = rxkb_layout_next(layout);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto name = std::string(rxkb_layout_get_name(layout));
|
||||
const auto* variantPtr = rxkb_layout_get_variant(layout);
|
||||
std::string variant = variantPtr == nullptr ? "" : std::string(variantPtr);
|
||||
|
||||
const auto* descriptionPtr = rxkb_layout_get_brief(layout);
|
||||
std::string description = descriptionPtr == nullptr ? "" : std::string(descriptionPtr);
|
||||
|
||||
Layout info = Layout{nameOfLayout, name, variant, description};
|
||||
|
||||
rxkb_context_unref(context);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
rxkb_context_unref(context);
|
||||
|
||||
spdlog::debug("niri language didn't find matching layout for {}", fullName);
|
||||
|
||||
return Layout{"", "", "", ""};
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
121
src/modules/niri/window.cpp
Normal file
121
src/modules/niri/window.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include "modules/niri/window.hpp"
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "util/rewrite_string.hpp"
|
||||
#include "util/sanitize_str.hpp"
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
Window::Window(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: AAppIconLabel(config, "window", id, "{title}", 0, true), bar_(bar) {
|
||||
if (!gIPC)
|
||||
gIPC = std::make_unique<IPC>();
|
||||
|
||||
gIPC->registerForIPC("WindowsChanged", this);
|
||||
gIPC->registerForIPC("WindowOpenedOrChanged", this);
|
||||
gIPC->registerForIPC("WindowClosed", this);
|
||||
gIPC->registerForIPC("WindowFocusChanged", this);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
}
|
||||
|
||||
void Window::onEvent(const Json::Value &ev) {
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
void Window::doUpdate() {
|
||||
auto ipcLock = gIPC->lockData();
|
||||
|
||||
const auto &windows = gIPC->windows();
|
||||
const auto &workspaces = gIPC->workspaces();
|
||||
|
||||
const auto separateOutputs = config_["separate-outputs"].asBool();
|
||||
const auto ws_it = std::find_if(workspaces.cbegin(), workspaces.cend(),
|
||||
[&](const auto &ws) {
|
||||
if (separateOutputs) {
|
||||
return ws["is_active"].asBool() && ws["output"].asString() == bar_.output->name;
|
||||
}
|
||||
|
||||
return ws["is_focused"].asBool();
|
||||
});
|
||||
|
||||
std::vector<Json::Value>::const_iterator it;
|
||||
if (ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull()) {
|
||||
it = windows.cend();
|
||||
} else {
|
||||
const auto id = (*ws_it)["active_window_id"].asUInt64();
|
||||
it = std::find_if(windows.cbegin(), windows.cend(),
|
||||
[id](const auto &win) { return win["id"].asUInt64() == id; });
|
||||
}
|
||||
|
||||
setClass("empty", ws_it == workspaces.cend() || (*ws_it)["active_window_id"].isNull());
|
||||
|
||||
if (it != windows.cend()) {
|
||||
const auto &window = *it;
|
||||
|
||||
const auto title = window["title"].asString();
|
||||
const auto appId = window["app_id"].asString();
|
||||
const auto sanitizedTitle = waybar::util::sanitize_string(title);
|
||||
const auto sanitizedAppId = waybar::util::sanitize_string(appId);
|
||||
|
||||
label_.show();
|
||||
label_.set_markup(waybar::util::rewriteString(
|
||||
fmt::format(fmt::runtime(format_),
|
||||
fmt::arg("title", sanitizedTitle),
|
||||
fmt::arg("app_id", sanitizedAppId)),
|
||||
config_["rewrite"]));
|
||||
|
||||
updateAppIconName(appId, "");
|
||||
|
||||
if (tooltipEnabled())
|
||||
label_.set_tooltip_text(title);
|
||||
|
||||
const auto id = window["id"].asUInt64();
|
||||
const auto workspaceId = window["workspace_id"].asUInt64();
|
||||
const auto isSolo = std::none_of(windows.cbegin(), windows.cend(),
|
||||
[&](const auto &win) {
|
||||
return win["id"].asUInt64() != id && win["workspace_id"].asUInt64() == workspaceId;
|
||||
});
|
||||
setClass("solo", isSolo);
|
||||
if (!appId.empty())
|
||||
setClass(appId, isSolo);
|
||||
|
||||
if (oldAppId_ != appId) {
|
||||
if (!oldAppId_.empty())
|
||||
setClass(oldAppId_, false);
|
||||
oldAppId_ = appId;
|
||||
}
|
||||
} else {
|
||||
label_.hide();
|
||||
updateAppIconName("", "");
|
||||
setClass("solo", false);
|
||||
if (!oldAppId_.empty())
|
||||
setClass(oldAppId_, false);
|
||||
oldAppId_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::update() {
|
||||
doUpdate();
|
||||
AAppIconLabel::update();
|
||||
}
|
||||
|
||||
void Window::setClass(const std::string &className, bool enable) {
|
||||
auto styleContext = bar_.window.get_style_context();
|
||||
if (enable) {
|
||||
if (!styleContext->has_class(className)) {
|
||||
styleContext->add_class(className);
|
||||
}
|
||||
} else {
|
||||
styleContext->remove_class(className);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
204
src/modules/niri/workspaces.cpp
Normal file
204
src/modules/niri/workspaces.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
#include "modules/niri/workspaces.hpp"
|
||||
|
||||
#include <gtkmm/button.h>
|
||||
#include <gtkmm/label.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
namespace waybar::modules::niri {
|
||||
|
||||
Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
|
||||
: AModule(config, "workspaces", id, false, false),
|
||||
bar_(bar),
|
||||
box_(bar.orientation, 0) {
|
||||
box_.set_name("workspaces");
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
box_.get_style_context()->add_class(MODULE_CLASS);
|
||||
event_box_.add(box_);
|
||||
|
||||
if (!gIPC)
|
||||
gIPC = std::make_unique<IPC>();
|
||||
|
||||
gIPC->registerForIPC("WorkspacesChanged", this);
|
||||
gIPC->registerForIPC("WorkspaceActivated", this);
|
||||
gIPC->registerForIPC("WorkspaceActiveWindowChanged", this);
|
||||
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
Workspaces::~Workspaces() {
|
||||
gIPC->unregisterForIPC(this);
|
||||
}
|
||||
|
||||
void Workspaces::onEvent(const Json::Value &ev) {
|
||||
dp.emit();
|
||||
}
|
||||
|
||||
void Workspaces::doUpdate() {
|
||||
auto ipcLock = gIPC->lockData();
|
||||
|
||||
const auto alloutputs = config_["all-outputs"].asBool();
|
||||
std::vector<Json::Value> my_workspaces;
|
||||
const auto &workspaces = gIPC->workspaces();
|
||||
std::copy_if(workspaces.cbegin(), workspaces.cend(), std::back_inserter(my_workspaces),
|
||||
[&](const auto &ws) {
|
||||
if (alloutputs)
|
||||
return true;
|
||||
return ws["output"].asString() == bar_.output->name;
|
||||
});
|
||||
|
||||
// Remove buttons for removed workspaces.
|
||||
for (auto it = buttons_.begin(); it != buttons_.end(); ) {
|
||||
auto ws = std::find_if(my_workspaces.begin(), my_workspaces.end(),
|
||||
[it](const auto &ws) { return ws["id"].asUInt64() == it->first; });
|
||||
if (ws == my_workspaces.end()) {
|
||||
it = buttons_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// Add buttons for new workspaces, update existing ones.
|
||||
for (const auto &ws : my_workspaces) {
|
||||
auto bit = buttons_.find(ws["id"].asUInt64());
|
||||
auto &button = bit == buttons_.end() ? addButton(ws) : bit->second;
|
||||
auto style_context = button.get_style_context();
|
||||
|
||||
if (ws["is_focused"].asBool())
|
||||
style_context->add_class("focused");
|
||||
else
|
||||
style_context->remove_class("focused");
|
||||
|
||||
if (ws["is_active"].asBool())
|
||||
style_context->add_class("active");
|
||||
else
|
||||
style_context->remove_class("active");
|
||||
|
||||
if (ws["output"]) {
|
||||
if (ws["output"].asString() == bar_.output->name)
|
||||
style_context->add_class("current_output");
|
||||
else
|
||||
style_context->remove_class("current_output");
|
||||
} else {
|
||||
style_context->remove_class("current_output");
|
||||
}
|
||||
|
||||
if (ws["active_window_id"].isNull())
|
||||
style_context->add_class("empty");
|
||||
else
|
||||
style_context->remove_class("empty");
|
||||
|
||||
std::string name;
|
||||
if (ws["name"]) {
|
||||
name = ws["name"].asString();
|
||||
} else {
|
||||
name = std::to_string(ws["idx"].asUInt());
|
||||
}
|
||||
button.set_name("niri-workspace-" + name);
|
||||
|
||||
if (config_["format"].isString()) {
|
||||
auto format = config_["format"].asString();
|
||||
name = fmt::format(
|
||||
fmt::runtime(format),
|
||||
fmt::arg("icon", getIcon(name, ws)),
|
||||
fmt::arg("value", name),
|
||||
fmt::arg("name", ws["name"].asString()),
|
||||
fmt::arg("index", ws["idx"].asUInt()),
|
||||
fmt::arg("output", ws["output"].asString()));
|
||||
}
|
||||
if (!config_["disable-markup"].asBool()) {
|
||||
static_cast<Gtk::Label *>(button.get_children()[0])->set_markup(name);
|
||||
} else {
|
||||
button.set_label(name);
|
||||
}
|
||||
|
||||
if (config_["current-only"].asBool()) {
|
||||
const auto *property = alloutputs ? "is_focused" : "is_active";
|
||||
if (ws[property].asBool())
|
||||
button.show();
|
||||
else
|
||||
button.hide();
|
||||
} else {
|
||||
button.show();
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the button order.
|
||||
for (auto it = my_workspaces.cbegin(); it != my_workspaces.cend(); ++it) {
|
||||
const auto &ws = *it;
|
||||
|
||||
auto pos = ws["idx"].asUInt() - 1;
|
||||
if (alloutputs)
|
||||
pos = it - my_workspaces.cbegin();
|
||||
|
||||
auto &button = buttons_[ws["id"].asUInt64()];
|
||||
box_.reorder_child(button, pos);
|
||||
}
|
||||
}
|
||||
|
||||
void Workspaces::update() {
|
||||
doUpdate();
|
||||
AModule::update();
|
||||
}
|
||||
|
||||
Gtk::Button &Workspaces::addButton(const Json::Value &ws) {
|
||||
std::string name;
|
||||
if (ws["name"]) {
|
||||
name = ws["name"].asString();
|
||||
} else {
|
||||
name = std::to_string(ws["idx"].asUInt());
|
||||
}
|
||||
|
||||
auto pair = buttons_.emplace(ws["id"].asUInt64(), name);
|
||||
auto &&button = pair.first->second;
|
||||
box_.pack_start(button, false, false, 0);
|
||||
button.set_relief(Gtk::RELIEF_NONE);
|
||||
if (!config_["disable-click"].asBool()) {
|
||||
const auto id = ws["id"].asUInt64();
|
||||
button.signal_pressed().connect([=] {
|
||||
try {
|
||||
// {"Action":{"FocusWorkspace":{"reference":{"Id":1}}}}
|
||||
Json::Value request(Json::objectValue);
|
||||
auto &action = (request["Action"] = Json::Value(Json::objectValue));
|
||||
auto &focusWorkspace = (action["FocusWorkspace"] = Json::Value(Json::objectValue));
|
||||
auto &reference = (focusWorkspace["reference"] = Json::Value(Json::objectValue));
|
||||
reference["Id"] = id;
|
||||
|
||||
IPC::send(request);
|
||||
} catch (const std::exception &e) {
|
||||
spdlog::error("Error switching workspace: {}", e.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
std::string Workspaces::getIcon(const std::string &value, const Json::Value &ws) {
|
||||
const auto &icons = config_["format-icons"];
|
||||
if (!icons)
|
||||
return value;
|
||||
|
||||
if (ws["is_focused"].asBool() && icons["focused"])
|
||||
return icons["focused"].asString();
|
||||
|
||||
if (ws["is_active"].asBool() && icons["active"])
|
||||
return icons["active"].asString();
|
||||
|
||||
if (ws["name"]) {
|
||||
const auto &name = ws["name"].asString();
|
||||
if (icons[name])
|
||||
return icons[name].asString();
|
||||
}
|
||||
|
||||
const auto idx = ws["idx"].asString();
|
||||
if (icons[idx])
|
||||
return icons[idx].asString();
|
||||
|
||||
if (icons["default"])
|
||||
return icons["default"].asString();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace waybar::modules::niri
|
Reference in New Issue
Block a user