diff --git a/include/factory.hpp b/include/factory.hpp index a75f3dd..06cd4d9 100644 --- a/include/factory.hpp +++ b/include/factory.hpp @@ -24,6 +24,7 @@ #ifdef HAVE_HYPRLAND #include "modules/hyprland/backend.hpp" #include "modules/hyprland/window.hpp" +#include "modules/hyprland/language.hpp" #endif #if defined(__linux__) && !defined(NO_FILESYSTEM) #include "modules/battery.hpp" diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp new file mode 100644 index 0000000..9e7193e --- /dev/null +++ b/include/modules/hyprland/language.hpp @@ -0,0 +1,29 @@ +#include + +#include "ALabel.hpp" +#include "bar.hpp" +#include "modules/hyprland/backend.hpp" +#include "util/json.hpp" + +namespace waybar::modules::hyprland { + +class Language : public waybar::ALabel { +public: + Language(const std::string&, const waybar::Bar&, const Json::Value&); + ~Language() = default; + + auto update() -> void; + +private: + void onEvent(const std::string&); + + void initLanguage(); + std::string getShortFrom(const std::string&); + + std::mutex mutex_; + const Bar& bar_; + util::JsonParser parser_; + std::string layoutName_; +}; + +} \ No newline at end of file diff --git a/man/waybar-hyprland-language.5.scd b/man/waybar-hyprland-language.5.scd new file mode 100644 index 0000000..cb16995 --- /dev/null +++ b/man/waybar-hyprland-language.5.scd @@ -0,0 +1,43 @@ +waybar-hyprland-language(5) + +# NAME + +waybar - hyprland language module + +# DESCRIPTION + +The *language* module displays the currently selected language. + +# CONFIGURATION + +Addressed by *hyprland/language* + +*format*: ++ + typeof: string ++ + default: {} ++ + The format, how information should be displayed. On {} the currently selected language is displayed. + +*format-* ++ + typeof: string++ + Provide an alternative name to display per language where is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below. + +*keyboard-name*: ++ + typeof: string ++ + Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "AT Translated set..." is recommended. + + + +# EXAMPLES + +``` +"hyprland/language": { + "format": "Lang: {}" + "format-us": "AMERICA, HELL YEAH!" // For American English + "format-tr": "As bayrakları" // For Turkish + "keyboard-name": "AT Translated Set 2 keyboard" +} +``` + +# STYLE + +- *#language* diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd index 7de1028..07a0de8 100644 --- a/man/waybar-pulseaudio.5.scd +++ b/man/waybar-pulseaudio.5.scd @@ -96,6 +96,11 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu default: true ++ Option to disable tooltip on hover. +*max-volume*: ++ + typeof: integer ++ + default: 100 ++ + The maximum volume that can be set, in percentage. + # FORMAT REPLACEMENTS *{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name. diff --git a/meson.build b/meson.build index 0a48e97..420606a 100644 --- a/meson.build +++ b/meson.build @@ -206,6 +206,7 @@ if true add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp') src_files += 'src/modules/hyprland/backend.cpp' src_files += 'src/modules/hyprland/window.cpp' + src_files += 'src/modules/hyprland/language.cpp' endif if libnl.found() and libnlgen.found() diff --git a/src/factory.cpp b/src/factory.cpp index c53e26a..13b7803 100644 --- a/src/factory.cpp +++ b/src/factory.cpp @@ -61,6 +61,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const { if (ref == "hyprland/window") { return new waybar::modules::hyprland::Window(id, bar_, config_[name]); } + if (ref == "hyprland/language") { + return new waybar::modules::hyprland::Language(id, bar_, config_[name]); + } #endif if (ref == "idle_inhibitor") { return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp new file mode 100644 index 0000000..5463508 --- /dev/null +++ b/src/modules/hyprland/language.cpp @@ -0,0 +1,131 @@ +#include "modules/hyprland/language.hpp" + +#include +#include +#include + +#include "modules/hyprland/backend.hpp" + +namespace waybar::modules::hyprland { + +Language::Language(const std::string& id, const Bar& bar, const Json::Value& config) + : ALabel(config, "language", id, "{}", 0, true), bar_(bar) { + modulesReady = true; + + if (!gIPC.get()) { + gIPC = std::make_unique(); + } + + // get the active layout when open + initLanguage(); + + label_.hide(); + ALabel::update(); + + // register for hyprland ipc + gIPC->registerForIPC("activelayout", [&](const std::string& ev) { this->onEvent(ev); }); +} + +auto Language::update() -> void { + std::lock_guard lg(mutex_); + + if (!format_.empty()) { + label_.show(); + label_.set_markup(layoutName_); + } else { + label_.hide(); + } + + ALabel::update(); +} + +void Language::onEvent(const std::string& ev) { + std::lock_guard lg(mutex_); + auto layoutName = ev.substr(ev.find_last_of(',') + 1); + auto keebName = ev.substr(0, ev.find_last_of(',')); + keebName = keebName.substr(keebName.find_first_of('>') + 2); + + if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString()) + return; // ignore + + auto replaceAll = [](std::string str, const std::string& from, + const std::string& to) -> std::string { + size_t start_pos = 0; + while ((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + return str; + }; + + const auto BRIEFNAME = getShortFrom(layoutName); + + if (config_.isMember("format-" + BRIEFNAME)) { + const auto PROPNAME = "format-" + BRIEFNAME; + layoutName = fmt::format(format_, config_[PROPNAME].asString()); + } else { + layoutName = fmt::format(format_, layoutName); + } + + layoutName = replaceAll(layoutName, "&", "&"); + + if (layoutName == layoutName_) return; + + layoutName_ = layoutName; + + spdlog::debug("hyprland language onevent with {}", layoutName); + + dp.emit(); +} + +void Language::initLanguage() { + const auto INPUTDEVICES = gIPC->getSocket1Reply("devices"); + + if (!config_.isMember("keyboard-name")) return; + + const auto KEEBNAME = config_["keyboard-name"].asString(); + + try { + auto searcher = INPUTDEVICES.substr(INPUTDEVICES.find(KEEBNAME) + KEEBNAME.length()); + searcher = searcher.substr(searcher.find("Keyboard at")); + searcher = searcher.substr(searcher.find("keymap:") + 7); + searcher = searcher.substr(0, searcher.find_first_of("\n\t")); + + layoutName_ = searcher; + + spdlog::debug("hyprland language initLanguage found {}", layoutName_); + + dp.emit(); + + } catch (std::exception& e) { + spdlog::error("hyprland language initLanguage failed with {}", e.what()); + } +} + +std::string Language::getShortFrom(const std::string& fullName) { + const auto CONTEXT = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES); + rxkb_context_parse_default_ruleset(CONTEXT); + + std::string foundName = ""; + rxkb_layout* layout = rxkb_layout_first(CONTEXT); + while (layout) { + std::string nameOfLayout = rxkb_layout_get_description(layout); + + if (nameOfLayout != fullName) { + layout = rxkb_layout_next(layout); + continue; + } + + std::string briefName = rxkb_layout_get_brief(layout); + + rxkb_context_unref(CONTEXT); + + return briefName; + } + + rxkb_context_unref(CONTEXT); + + return ""; +} + +} // namespace waybar::modules::hyprland \ No newline at end of file diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp index f6ff959..24b858d 100644 --- a/src/modules/pulseaudio.cpp +++ b/src/modules/pulseaudio.cpp @@ -90,12 +90,16 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) { double volume_tick = static_cast(PA_VOLUME_NORM) / 100; pa_volume_t change = volume_tick; pa_cvolume pa_volume = pa_volume_; + int max_volume = 100; // isDouble returns true for integers as well, just in case if (config_["scroll-step"].isDouble()) { change = round(config_["scroll-step"].asDouble() * volume_tick); } + if (config_["max-volume"].isInt()) { + max_volume = std::min(0, config_["max-volume"].asInt()); + } if (dir == SCROLL_DIR::UP) { - if (volume_ + 1 <= 100) { + if (volume_ + 1 <= max_volume) { pa_cvolume_inc(&pa_volume, change); } } else if (dir == SCROLL_DIR::DOWN) {