From 87b56a31cb10168949250fa3b906b656cddefe3b Mon Sep 17 00:00:00 2001 From: Matej Novota Date: Wed, 15 Jan 2020 00:56:14 +0100 Subject: [PATCH] Add module for keyboard layout Update list of modules in README Add layout class to sway modules update default config and style --- README.md | 2 +- include/factory.hpp | 1 + include/modules/sway/layout.hpp | 54 +++++++++++++++++ meson.build | 3 +- resources/config | 6 +- resources/style.css | 1 + src/factory.cpp | 3 + src/modules/sway/layout.cpp | 101 ++++++++++++++++++++++++++++++++ 8 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 include/modules/sway/layout.hpp create mode 100644 src/modules/sway/layout.cpp diff --git a/README.md b/README.md index d8b7545d..90fda51e 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ > *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* **Current features** -- Sway (Workspaces, Binding mode, Focused window name) +- Sway (Workspaces, Binding mode, Focused window name, Keyboard Layout) - Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Local time - Battery diff --git a/include/factory.hpp b/include/factory.hpp index 7d4d14e3..53839b56 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -6,6 +6,7 @@ #include "modules/sway/mode.hpp" #include "modules/sway/window.hpp" #include "modules/sway/workspaces.hpp" +#include "modules/sway/layout.hpp" #endif #ifndef NO_FILESYSTEM #include "modules/battery.hpp" diff --git a/include/modules/sway/layout.hpp b/include/modules/sway/layout.hpp new file mode 100644 index 00000000..68f7af7a --- /dev/null +++ b/include/modules/sway/layout.hpp @@ -0,0 +1,54 @@ +#pragma once + +#ifdef FILESYSTEM_EXPERIMENTAL +#include +#else +#include +#endif +#include +#include +#include +#include "ALabel.hpp" +#include "bar.hpp" +#include "client.hpp" +#include "modules/sway/ipc/client.hpp" +#include "util/json.hpp" +#include "util/sleeper_thread.hpp" + +namespace waybar::modules::sway { + +#ifdef FILESYSTEM_EXPERIMENTAL +namespace fs = std::experimental::filesystem; +#else +namespace fs = std::filesystem; +#endif + +class Layout : public ALabel, public sigc::trackable { + public: + Layout(const std::string&, const Json::Value&); + ~Layout() = default; + auto update() -> void; + + private: + static inline const fs::path xbk_file_ = "/usr/share/X11/xkb/rules/evdev.xml"; + + void onCmd(const struct Ipc::ipc_response&); + void onEvent(const struct Ipc::ipc_response&); + void worker(); + void shortName(); + inline std::string sanitize(const std::string& text) { + std::regex specialChars {R"([-[\]{}()*+?.,\^$|#\s])"}; + return std::regex_replace(text, specialChars, R"(\$&)"); + } + + std::string layout_; + std::string short_description_; + std::string short_variant_; + util::JsonParser parser_; + std::mutex mutex_; + + util::SleeperThread thread_; + Ipc ipc_; + }; + +} // namespace waybar::modules::sway diff --git a/meson.build b/meson.build index a099ad2b..eca02536 100644 --- a/meson.build +++ b/meson.build @@ -105,7 +105,8 @@ if true # find_program('sway', required : false).found() 'src/modules/sway/ipc/client.cpp', 'src/modules/sway/mode.cpp', 'src/modules/sway/window.cpp', - 'src/modules/sway/workspaces.cpp' + 'src/modules/sway/workspaces.cpp', + 'src/modules/sway/layout.cpp' ] endif diff --git a/resources/config b/resources/config index 4e392399..614c0d0f 100644 --- a/resources/config +++ b/resources/config @@ -6,7 +6,7 @@ // Choose the order of the modules "modules-left": ["sway/workspaces", "sway/mode", "custom/media"], "modules-center": ["sway/window"], - "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"], + "modules-right": ["sway/layout", "mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"], // Modules configuration // "sway/workspaces": { // "disable-scroll": true, @@ -26,6 +26,10 @@ "sway/mode": { "format": "{}" }, + "sway/layout": { + "tooltip-format": "{long}", + "format": "{short} " + }, "mpd": { "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ", "format-disconnected": "Disconnected ", diff --git a/resources/style.css b/resources/style.css index e21ae00e..0e988c90 100644 --- a/resources/style.css +++ b/resources/style.css @@ -77,6 +77,7 @@ window#waybar.chromium { #tray, #mode, #idle_inhibitor, +#layout, #mpd { padding: 0 10px; margin: 0 4px; diff --git a/src/factory.cpp b/src/factory.cpp index 8f7cea75..8f707bb7 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -22,6 +22,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "sway/window") { return new waybar::modules::sway::Window(id, bar_, config_[name]); } + if (ref == "sway/layout") { + return new waybar::modules::sway::Layout(id, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/sway/layout.cpp b/src/modules/sway/layout.cpp new file mode 100644 index 00000000..a6f5dc76 --- /dev/null +++ b/src/modules/sway/layout.cpp @@ -0,0 +1,101 @@ +#include "modules/sway/layout.hpp" +#include + +namespace waybar::modules::sway { + +Layout::Layout(const std::string& id, const Json::Value& config) + : ALabel(config, "layout", id, "{}", 0) { + ipc_.subscribe(R"(["input"])"); + ipc_.signal_event.connect(sigc::mem_fun(*this, &Layout::onEvent)); + ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Layout::onCmd)); + ipc_.sendCmd(IPC_GET_INPUTS); + shortName(); + // Launch worker + worker(); + dp.emit(); +} + +void Layout::onEvent(const struct Ipc::ipc_response& res) { + try { + std::lock_guard lock(mutex_); + auto payload = parser_.parse(res.payload); + if (payload["change"] == "xkb_layout") { + layout_ = payload["input"]["xkb_active_layout_name"].asString(); + } + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Layout: {}", e.what()); + } +} + +void Layout::onCmd(const struct Ipc::ipc_response &res) { + if (res.type == IPC_GET_INPUTS) { + try { + std::lock_guard lock(mutex_); + auto payload = parser_.parse(res.payload); + for (auto keyboard : payload) { + if (keyboard["identifier"] == "1:1:AT_Translated_Set_2_keyboard") { + layout_ = keyboard["xkb_active_layout_name"].asString(); + } + } + dp.emit(); + } catch (const std::exception& e) { + spdlog::error("Layout: {}", e.what()); + } + } +} + +void Layout::worker() { + thread_ = [this] { + try { + ipc_.handleEvent(); + } catch (const std::exception& e) { + spdlog::error("Layout: {}", e.what()); + } + }; +} + +auto Layout::update() -> void { + shortName(); + + if (layout_.empty()) { + event_box_.hide(); + } else { + label_.set_markup(fmt::format(format_, + fmt::arg("long", layout_), + fmt::arg("short", short_description_), + fmt::arg("variant", short_variant_))); + if (tooltipEnabled()) { + if (config_["tooltip-format"].isString()) { + auto tooltip_format = config_["tooltip-format"].asString(); + label_.set_tooltip_text(fmt::format(tooltip_format, + fmt::arg("long", layout_), + fmt::arg("short", short_description_), + fmt::arg("variant", short_variant_))); + } else { + label_.set_tooltip_text(layout_); + } + } + event_box_.show(); + } +} + +void Layout::shortName() { + std::string line; + std::ifstream file (xbk_file_); + std::regex e1 (".*(.*).*"); + std::regex e2 (".*(.*).*"); + + while (getline(file,line)) { + + if(std::regex_match(line, e1)) { + short_description_ = std::regex_replace(line, e1, "$1"); + } else if(std::regex_match(line, e2)) { + short_variant_ = std::regex_replace(line, e2, "$1"); + } else if(std::regex_match(line, std::regex(".*"+sanitize(layout_)+".*"))){ + break; + } + } +} + +} // namespace waybar::modules::sway