Skip to content
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

Closed
wants to merge 10 commits into from
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -44,6 +44,8 @@ $ waybar
```
gtkmm3
jsoncpp
pugixml
libinput
libsigc++
fmt
wayland
Expand Down Expand Up @@ -81,6 +83,7 @@ sudo apt install \
libgtkmm-3.0-dev \
libinput-dev \
libjsoncpp-dev \
libpugixml-dev \
libmpdclient-dev \
libnl-3-dev \
libnl-genl-3-dev \
Expand Down
1 change: 1 addition & 0 deletions include/factory.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
#if defined(__linux__) && !defined(NO_FILESYSTEM)
#include "modules/battery.hpp"
Expand Down
2 changes: 2 additions & 0 deletions include/modules/sway/ipc/ipc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ enum ipc_command_type {
IPC_EVENT_BINDING = ((1 << 31) | 5),
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
IPC_EVENT_TICK = ((1 << 31) | 7),
IPC_EVENT_BAR_STATE_UPDATE = ((1 << 31) | 0x14),
IPC_EVENT_INPUT = ((1 << 31) | 0x15),
};
61 changes: 61 additions & 0 deletions include/modules/sway/layout.hpp
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";

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?

Copy link
Contributor Author

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 ?

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 with pkg-config xkeyboard-config --variable xkb_base.

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_?

Copy link
Contributor Author

@BlueGone BlueGone May 22, 2020

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.


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
82 changes: 82 additions & 0 deletions man/waybar-sway-layout-5.scd
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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*
1 change: 1 addition & 0 deletions man/waybar.5.scd
Original file line number Diff line number Diff line change
Expand Up @@ -196,5 +196,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-sway-mode(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
- *waybar-sway-layout(5)*
- *waybar-temperature(5)*
- *waybar-tray(5)*
7 changes: 6 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Copy link
Contributor

@jbeich jbeich May 26, 2020

Choose a reason for hiding this comment

The 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'))
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -207,6 +210,8 @@ executable(
spdlog,
sigcpp,
jsoncpp,
pugixml,
libinput,
wayland_cursor,
gtkmm,
dbusmenu_gtk,
Expand Down
7 changes: 6 additions & 1 deletion resources/config
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,6 +26,11 @@
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"sway/layout": {
"tooltip-format": "{long}",
"format": "{short} ",
"keyboard_id": "1:1:AT_Translated_Set_2_keyboard"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format-disconnected": "Disconnected ",
Expand Down
1 change: 1 addition & 0 deletions resources/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ window#waybar.chromium {
#tray,
#mode,
#idle_inhibitor,
#layout,
#mpd {
padding: 0 10px;
margin: 0 4px;
Expand Down
3 changes: 3 additions & 0 deletions src/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
132 changes: 132 additions & 0 deletions src/modules/sway/layout.cpp
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