-
-
Notifications
You must be signed in to change notification settings - Fork 721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Keyboard layout module #659
Changes from all commits
c7d8c49
faa1fb4
b75e5d3
23e6650
571549a
baab219
d0bf5c6
e2cb561
de0c9ef
2caad48
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
#pragma once | ||
|
||
#ifdef FILESYSTEM_EXPERIMENTAL | ||
#include <experimental/filesystem> | ||
#else | ||
#include <filesystem> | ||
#endif | ||
#include <fmt/format.h> | ||
#include <unordered_map> | ||
#include <tuple> | ||
#include <fstream> | ||
#include <regex> | ||
#include "ALabel.hpp" | ||
#include "bar.hpp" | ||
#include "client.hpp" | ||
#include "modules/sway/ipc/client.hpp" | ||
#include "util/json.hpp" | ||
#include "util/sleeper_thread.hpp" | ||
#include <pugixml.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: | ||
|
||
using ShortNames = std::tuple<std::string, std::string>; | ||
using MemoizedShortNames = std::unordered_map< | ||
std::string, | ||
std::tuple<std::string, std::string> | ||
>; | ||
|
||
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(); | ||
ShortNames getShortNames(); | ||
ShortNames fromFileGetShortNames(); | ||
|
||
std::string layout_; | ||
util::JsonParser parser_; | ||
MemoizedShortNames memoizedShortNames_; | ||
|
||
std::string keyboard_id_; | ||
|
||
util::SleeperThread thread_; | ||
Ipc ipc_; | ||
}; | ||
|
||
} // namespace waybar::modules::sway |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
waybar-sway-layout(5) | ||
|
||
# NAME | ||
|
||
waybar - sway layout module | ||
|
||
# DESCRIPTION | ||
|
||
The *layout* module displays the current keyboard layout of Sway | ||
|
||
# CONFIGURATION | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure about the different fields here, I picked them from waybar-disk(5). Are they herited from ALabel ? |
||
|
||
Addressed by *sway/layout* | ||
|
||
*format*: ++ | ||
typeof: string ++ | ||
default: "{}" ++ | ||
The format, how information 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. | ||
|
||
*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-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*: ++ | ||
typeof: bool ++ | ||
default: true ++ | ||
Option to disable tooltip on hover. | ||
|
||
*tooltip-format*: ++ | ||
typeof: string ++ | ||
default: "{long}" ++ | ||
The format of the information displayed in the tooltip. | ||
|
||
# FORMAT REPLACEMENTS | ||
|
||
*{long}*: The complete layout and variant indication (e.g "English (US)") | ||
|
||
*{short}*: The layout short name (e.g "en") | ||
|
||
*{variant}*: The variant short name (e.g "us") | ||
|
||
# EXAMPLES | ||
|
||
``` | ||
"sway/layout": { | ||
"tooltip-format": "{long}", | ||
"format": "{short} ({variant})", | ||
"keyboard_id": "1:1:AT_Translated_Set_2_keyboard" | ||
} | ||
``` | ||
|
||
# STYLE | ||
|
||
- *#layout* |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -82,6 +82,8 @@ gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) | |
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) | ||
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) | ||
jsoncpp = dependency('jsoncpp') | ||
pugixml = dependency('pugixml') | ||
BlueGone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
libinput = dependency('libinput') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? I don't see libinput references in the code (builds fine without). See also 74db69d. |
||
sigcpp = dependency('sigc++-2.0') | ||
libepoll = dependency('epoll-shim', required: false) | ||
libnl = dependency('libnl-3.0', required: get_option('libnl')) | ||
|
@@ -156,7 +158,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 | ||
|
||
|
@@ -207,6 +210,8 @@ executable( | |
spdlog, | ||
sigcpp, | ||
jsoncpp, | ||
pugixml, | ||
libinput, | ||
wayland_cursor, | ||
gtkmm, | ||
dbusmenu_gtk, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
#include "modules/sway/layout.hpp" | ||
#include <spdlog/spdlog.h> | ||
|
||
namespace waybar::modules::sway { | ||
|
||
Layout::Layout(const std::string& id, const Json::Value& config) | ||
: ALabel(config, "layout", id, "{}", 0) { | ||
|
||
keyboard_id_ = config_["keyboard_id"].isString() | ||
? config_["keyboard_id"].asString() | ||
: "1:1:AT_Translated_Set_2_keyboard"; | ||
|
||
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); | ||
// Launch worker | ||
worker(); | ||
} | ||
|
||
void Layout::onEvent(const struct Ipc::ipc_response& res) { | ||
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) { | ||
return; | ||
} | ||
|
||
auto payload = parser_.parse(res.payload); | ||
|
||
if ((payload["change"] != "xkb_layout" || payload["change"] != "xkb_keymap" ) && | ||
payload["input"]["identifier"] == keyboard_id_) { | ||
layout_ = payload["input"]["xkb_active_layout_name"].asString(); | ||
dp.emit(); | ||
} | ||
} | ||
|
||
void Layout::onCmd(const struct Ipc::ipc_response &res) { | ||
if (res.type != IPC_GET_INPUTS) { | ||
return; | ||
} | ||
|
||
auto payload = parser_.parse(res.payload); | ||
|
||
for (auto keyboard : payload) { | ||
if (keyboard["identifier"] == keyboard_id_) { | ||
layout_ = keyboard["xkb_active_layout_name"].asString(); | ||
dp.emit(); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
void Layout::worker() { | ||
thread_ = [this] { | ||
try { | ||
ipc_.handleEvent(); | ||
} catch (const std::exception& e) { | ||
spdlog::error("Layout: {}", e.what()); | ||
} | ||
}; | ||
} | ||
|
||
auto Layout::update() -> void { | ||
auto short_names = getShortNames(); | ||
auto short_description = std::get<0>(short_names); | ||
auto short_variant = std::get<1>(short_names); | ||
|
||
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(); | ||
} | ||
} | ||
|
||
Layout::ShortNames Layout::getShortNames() { | ||
try { | ||
return memoizedShortNames_.at(layout_); | ||
} catch (std::out_of_range &e) { | ||
spdlog::debug("Layout: Getting short names from XKB file for {}.", layout_); | ||
auto shortNames = fromFileGetShortNames(); | ||
memoizedShortNames_[layout_] = shortNames; | ||
return shortNames; | ||
} | ||
} | ||
|
||
Layout::ShortNames Layout::fromFileGetShortNames() { | ||
std::string short_description; | ||
std::string short_variant; | ||
|
||
pugi::xml_document doc; | ||
doc.load_file(xbk_file_.c_str()); | ||
|
||
for (auto xkb_layout_xpath : doc.select_nodes("/xkbConfigRegistry/layoutList/layout")) { | ||
auto xkb_layout_config = xkb_layout_xpath.node().child("configItem"); | ||
|
||
if (xkb_layout_config.child_value("description") == layout_) { | ||
return std::make_tuple( | ||
xkb_layout_config.child_value("shortDescription"), | ||
xkb_layout_config.child_value("name") | ||
); | ||
} | ||
|
||
for (auto xkb_variant_xpath : xkb_layout_xpath.node().select_nodes("variantList/variant")) { | ||
auto xkb_variant_config = xkb_variant_xpath.node().child("configItem"); | ||
|
||
if (xkb_variant_config.child_value("description") == layout_) { | ||
return std::make_tuple( | ||
xkb_layout_config.child_value("shortDescription"), | ||
xkb_variant_config.child_value("name") | ||
); | ||
} | ||
} | ||
} | ||
|
||
spdlog::warn("Layout: Could not find layout \"{}\" in XKB file.", layout_); | ||
return std::make_tuple("N/A", "N/A"); | ||
} | ||
|
||
} // namespace waybar::modules::sway |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be
xkb_file_
?Also, kind of hardcoded, isn't it? May this file be at some different path? Like under another prefix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This path is defined and standart by https://linux.die.net/man/7/xkeyboard-config . I fairly assume there will be an issue only with nixos. Are you using nixos, and if so, have you some suggestions for a workaround ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can get the path to the
/usr/share/X11/xkb
dir withpkg-config xkeyboard-config --variable xkb_base
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, is it intentional or typo
xkb_file_
->xbk_file_
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The typo is not intended. I will fix it, thanks for the feedback.
I suppose it is possible for meson.build to generate a macro which contains the path
XKB_BASE
. I don't know how to use meson.build properly, I'll explore a bit, I'll keep you informed.