Merge pull request #1159 from Anakael/pr/anakael/sway-language-impr
[sway/language] Improve sway/languagepull/1024/head^2
commit
3f3f2d9c2c
|
@ -29,4 +29,8 @@ enum ipc_command_type {
|
||||||
IPC_EVENT_BINDING = ((1 << 31) | 5),
|
IPC_EVENT_BINDING = ((1 << 31) | 5),
|
||||||
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
|
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
|
||||||
IPC_EVENT_TICK = ((1 << 31) | 7),
|
IPC_EVENT_TICK = ((1 << 31) | 7),
|
||||||
|
|
||||||
|
// sway-specific event types
|
||||||
|
IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20),
|
||||||
|
IPC_EVENT_INPUT = ((1<<31) | 21),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
#include <xkbcommon/xkbregistry.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "ALabel.hpp"
|
#include "ALabel.hpp"
|
||||||
#include "bar.hpp"
|
#include "bar.hpp"
|
||||||
#include "client.hpp"
|
#include "client.hpp"
|
||||||
|
@ -16,10 +21,37 @@ class Language : public ALabel, public sigc::trackable {
|
||||||
auto update() -> void;
|
auto update() -> void;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct Layout {
|
||||||
|
std::string full_name;
|
||||||
|
std::string short_name;
|
||||||
|
std::string variant;
|
||||||
|
};
|
||||||
|
|
||||||
|
class XKBContext {
|
||||||
|
public:
|
||||||
|
XKBContext();
|
||||||
|
~XKBContext();
|
||||||
|
auto next_layout() -> Layout*;
|
||||||
|
private:
|
||||||
|
rxkb_context* context_ = nullptr;
|
||||||
|
rxkb_layout* xkb_layout_ = nullptr;
|
||||||
|
Layout* layout_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
void onEvent(const struct Ipc::ipc_response&);
|
void onEvent(const struct Ipc::ipc_response&);
|
||||||
void onCmd(const struct Ipc::ipc_response&);
|
void onCmd(const struct Ipc::ipc_response&);
|
||||||
|
|
||||||
std::string lang_;
|
auto set_current_layout(std::string current_layout) -> void;
|
||||||
|
auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;
|
||||||
|
|
||||||
|
const static std::string XKB_LAYOUT_NAMES_KEY;
|
||||||
|
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
|
||||||
|
|
||||||
|
Layout layout_;
|
||||||
|
std::map<std::string, Layout> layouts_map_;
|
||||||
|
XKBContext xkb_context_;
|
||||||
|
bool is_variant_displayed;
|
||||||
|
|
||||||
util::JsonParser parser_;
|
util::JsonParser parser_;
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
Ipc ipc_;
|
Ipc ipc_;
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
const std::string WHITESPACE = " \n\r\t\f\v";
|
||||||
|
|
||||||
|
std::string ltrim(const std::string s) {
|
||||||
|
size_t begin = s.find_first_not_of(WHITESPACE);
|
||||||
|
return (begin == std::string::npos) ? "" : s.substr(begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rtrim(const std::string s) {
|
||||||
|
size_t end = s.find_last_not_of(WHITESPACE);
|
||||||
|
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string trim(const std::string& s) { return rtrim(ltrim(s)); }
|
|
@ -15,63 +15,30 @@ Addressed by *sway/language*
|
||||||
*format*: ++
|
*format*: ++
|
||||||
typeof: string ++
|
typeof: string ++
|
||||||
default: {} ++
|
default: {} ++
|
||||||
The format, how information should be displayed. On {} data gets inserted.
|
The format, how layout should be displayed.
|
||||||
|
|
||||||
*rotate*: ++
|
|
||||||
typeof: integer ++
|
|
||||||
Positive value to rotate the text label.
|
|
||||||
|
|
||||||
*max-length*: ++
|
|
||||||
typeof: integer ++
|
|
||||||
The maximum length in character the module should display.
|
|
||||||
|
|
||||||
*min-length*: ++
|
|
||||||
typeof: integer ++
|
|
||||||
The minimum length in characters the module should take up.
|
|
||||||
|
|
||||||
*align*: ++
|
|
||||||
typeof: float ++
|
|
||||||
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
|
|
||||||
|
|
||||||
*on-click*: ++
|
|
||||||
typeof: string ++
|
|
||||||
Command to execute when clicked on the module.
|
|
||||||
|
|
||||||
*on-click-middle*: ++
|
|
||||||
typeof: string ++
|
|
||||||
Command to execute when middle-clicked on the module using mousewheel.
|
|
||||||
|
|
||||||
*on-click-right*: ++
|
|
||||||
typeof: string ++
|
|
||||||
Command to execute when you right clicked on the module.
|
|
||||||
|
|
||||||
*on-update*: ++
|
|
||||||
typeof: string ++
|
|
||||||
Command to execute when the module is updated.
|
|
||||||
|
|
||||||
*on-scroll-up*: ++
|
|
||||||
typeof: string ++
|
|
||||||
Command to execute when scrolling up on the module.
|
|
||||||
|
|
||||||
*on-scroll-down*: ++
|
|
||||||
typeof: string ++
|
|
||||||
Command to execute when scrolling down on the module.
|
|
||||||
|
|
||||||
*smooth-scrolling-threshold*: ++
|
|
||||||
typeof: double ++
|
|
||||||
Threshold to be used when scrolling.
|
|
||||||
|
|
||||||
*tooltip*: ++
|
*tooltip*: ++
|
||||||
typeof: bool ++
|
typeof: bool ++
|
||||||
default: true ++
|
default: true ++
|
||||||
Option to disable tooltip on hover.
|
Option to disable tooltip on hover.
|
||||||
|
|
||||||
|
# FORMAT REPLACEMENTS
|
||||||
|
|
||||||
|
*{short}*: Short name of layout (e.g. "en"). Equals to {}.
|
||||||
|
|
||||||
|
*{long}*: Long name of layout (e.g. "English (Dvorak)").
|
||||||
|
|
||||||
|
*{variant}*: Variant of layout (e.g. "Dvorak").
|
||||||
|
|
||||||
# EXAMPLES
|
# EXAMPLES
|
||||||
|
|
||||||
```
|
```
|
||||||
"sway/language": {
|
"sway/language": {
|
||||||
"format": "{}",
|
"format": "{}",
|
||||||
"max-length": 50
|
},
|
||||||
|
|
||||||
|
"sway/language": {
|
||||||
|
"format": "{short} {variant}",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,7 @@ libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
|
||||||
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
||||||
libudev = dependency('libudev', required: get_option('libudev'))
|
libudev = dependency('libudev', required: get_option('libudev'))
|
||||||
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
|
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
|
||||||
|
xkbregistry = dependency('xkbregistry')
|
||||||
|
|
||||||
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
|
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
|
||||||
if libsndio.found()
|
if libsndio.found()
|
||||||
|
@ -272,7 +273,8 @@ executable(
|
||||||
libmpdclient,
|
libmpdclient,
|
||||||
gtk_layer_shell,
|
gtk_layer_shell,
|
||||||
libsndio,
|
libsndio,
|
||||||
tz_dep
|
tz_dep,
|
||||||
|
xkbregistry
|
||||||
],
|
],
|
||||||
include_directories: [include_directories('include')],
|
include_directories: [include_directories('include')],
|
||||||
install: true,
|
install: true,
|
||||||
|
|
|
@ -1,10 +1,24 @@
|
||||||
#include "modules/sway/language.hpp"
|
#include "modules/sway/language.hpp"
|
||||||
|
|
||||||
|
#include <fmt/core.h>
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <xkbcommon/xkbregistry.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "modules/sway/ipc/ipc.hpp"
|
||||||
|
#include "util/string.hpp"
|
||||||
|
|
||||||
namespace waybar::modules::sway {
|
namespace waybar::modules::sway {
|
||||||
|
|
||||||
|
const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
|
||||||
|
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
|
||||||
|
|
||||||
Language::Language(const std::string& id, const Json::Value& config)
|
Language::Language(const std::string& id, const Json::Value& config)
|
||||||
: ALabel(config, "language", id, "{}", 0, true) {
|
: ALabel(config, "language", id, "{}", 0, true) {
|
||||||
|
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
||||||
ipc_.subscribe(R"(["input"])");
|
ipc_.subscribe(R"(["input"])");
|
||||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
||||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
||||||
|
@ -21,18 +35,31 @@ Language::Language(const std::string& id, const Json::Value& config)
|
||||||
}
|
}
|
||||||
|
|
||||||
void Language::onCmd(const struct Ipc::ipc_response& res) {
|
void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||||
|
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto payload = parser_.parse(res.payload);
|
auto payload = parser_.parse(res.payload);
|
||||||
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK
|
std::vector<std::string> used_layouts;
|
||||||
Json::Value::ArrayIndex maxId = 0, max = 0;
|
// Display current layout of a device with a maximum count of layouts, expecting that all will
|
||||||
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) {
|
// be OK
|
||||||
if(payload[i]["xkb_layout_names"].size() > max) {
|
Json::ArrayIndex max_id = 0, max = 0;
|
||||||
max = payload[i]["xkb_layout_names"].size();
|
for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
|
||||||
maxId = i;
|
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
|
||||||
|
if (size > max) {
|
||||||
|
max = size;
|
||||||
|
max_id = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2);
|
|
||||||
lang_ = Glib::Markup::escape_text(layout_name);
|
for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
|
||||||
|
used_layouts.push_back(layout.asString());
|
||||||
|
}
|
||||||
|
|
||||||
|
init_layouts_map(used_layouts);
|
||||||
|
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||||
dp.emit();
|
dp.emit();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
spdlog::error("Language: {}", e.what());
|
spdlog::error("Language: {}", e.what());
|
||||||
|
@ -40,14 +67,15 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Language::onEvent(const struct Ipc::ipc_response& res) {
|
void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||||
|
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto payload = parser_.parse(res.payload)["input"];
|
auto payload = parser_.parse(res.payload)["input"];
|
||||||
if (payload["type"].asString() == "keyboard") {
|
if (payload["type"].asString() == "keyboard") {
|
||||||
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2);
|
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||||
if (!layout_name.empty()) {
|
|
||||||
lang_ = Glib::Markup::escape_text(layout_name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dp.emit();
|
dp.emit();
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
|
@ -56,17 +84,93 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Language::update() -> void {
|
auto Language::update() -> void {
|
||||||
if (lang_.empty()) {
|
auto display_layout = trim(fmt::format(format_,
|
||||||
event_box_.hide();
|
fmt::arg("short", layout_.short_name),
|
||||||
} else {
|
fmt::arg("long", layout_.full_name),
|
||||||
label_.set_markup(fmt::format(format_, lang_));
|
fmt::arg("variant", layout_.variant)));
|
||||||
|
label_.set_markup(display_layout);
|
||||||
if (tooltipEnabled()) {
|
if (tooltipEnabled()) {
|
||||||
label_.set_tooltip_text(lang_);
|
label_.set_tooltip_markup(display_layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
event_box_.show();
|
event_box_.show();
|
||||||
}
|
|
||||||
// Call parent update
|
// Call parent update
|
||||||
ALabel::update();
|
ALabel::update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto Language::set_current_layout(std::string current_layout) -> void {
|
||||||
|
layout_ = layouts_map_[current_layout];
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
|
||||||
|
std::map<std::string, std::vector<Layout*>> found_by_short_names;
|
||||||
|
auto layout = xkb_context_.next_layout();
|
||||||
|
for (; layout != nullptr; layout = xkb_context_.next_layout()) {
|
||||||
|
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
|
||||||
|
used_layouts.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_variant_displayed) {
|
||||||
|
auto short_name = layout->short_name;
|
||||||
|
if (found_by_short_names.count(short_name) > 0) {
|
||||||
|
found_by_short_names[short_name].push_back(layout);
|
||||||
|
} else {
|
||||||
|
found_by_short_names[short_name] = {layout};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
layouts_map_.emplace(layout->full_name, *layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_variant_displayed || found_by_short_names.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, int> short_name_to_number_map;
|
||||||
|
for (const auto& used_layout_name : used_layouts) {
|
||||||
|
auto used_layout = &layouts_map_.find(used_layout_name)->second;
|
||||||
|
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
|
||||||
|
spdlog::info("SIZE: " + std::to_string(layouts_with_same_name_list.size()));
|
||||||
|
if (layouts_with_same_name_list.size() < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
|
||||||
|
short_name_to_number_map[used_layout->short_name] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
used_layout->short_name =
|
||||||
|
used_layout->short_name + std::to_string(short_name_to_number_map[used_layout->short_name]++);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Language::XKBContext::XKBContext() {
|
||||||
|
context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
|
||||||
|
rxkb_context_include_path_append_default(context_);
|
||||||
|
rxkb_context_parse_default_ruleset(context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Language::XKBContext::next_layout() -> Layout* {
|
||||||
|
if (xkb_layout_ == nullptr) {
|
||||||
|
xkb_layout_ = rxkb_layout_first(context_);
|
||||||
|
} else {
|
||||||
|
xkb_layout_ = rxkb_layout_next(xkb_layout_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xkb_layout_ == nullptr) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto description = std::string(rxkb_layout_get_description(xkb_layout_));
|
||||||
|
auto name = std::string(rxkb_layout_get_name(xkb_layout_));
|
||||||
|
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
|
||||||
|
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
|
||||||
|
|
||||||
|
layout_ = new Layout{description, name, variant};
|
||||||
|
return layout_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Language::XKBContext::~XKBContext() { rxkb_context_unref(context_); }
|
||||||
} // namespace waybar::modules::sway
|
} // namespace waybar::modules::sway
|
||||||
|
|
Loading…
Reference in New Issue