Merge branch 'master' into keyboard_state

This commit is contained in:
Alex 2021-07-23 15:59:08 +02:00 committed by GitHub
commit 445ad22580
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 387 additions and 109 deletions

View File

@ -3,5 +3,5 @@
FROM debian:sid FROM debian:sid
RUN apt-get update && \ RUN apt-get update && \
apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon && \ apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \
apt-get clean apt-get clean

View File

@ -3,5 +3,7 @@
FROM opensuse/tumbleweed:latest FROM opensuse/tumbleweed:latest
RUN zypper -n up && \ RUN zypper -n up && \
zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \
zypper -n refresh && \
zypper -n install -t pattern devel_C_C++ && \ zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel scdoc libxkbcommon zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc

View File

@ -11,8 +11,16 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h> #include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h> #include <sigc++/trackable.h>
#include <set>
#include <string_view>
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
struct ToolTip {
Glib::ustring icon_name;
Glib::ustring text;
};
class Item : public sigc::trackable { class Item : public sigc::trackable {
public: public:
Item(const std::string&, const std::string&, const Json::Value&); Item(const std::string&, const std::string&, const Json::Value&);
@ -27,10 +35,8 @@ class Item : public sigc::trackable {
Gtk::EventBox event_box; Gtk::EventBox event_box;
std::string category; std::string category;
std::string id; std::string id;
std::string status;
std::string title; std::string title;
int32_t window_id;
std::string icon_name; std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme; Glib::RefPtr<Gtk::IconTheme> icon_theme;
@ -39,6 +45,7 @@ class Item : public sigc::trackable {
std::string attention_movie_name; std::string attention_movie_name;
std::string icon_theme_path; std::string icon_theme_path;
std::string menu; std::string menu;
ToolTip tooltip;
DbusmenuGtkMenu* dbus_menu = nullptr; DbusmenuGtkMenu* dbus_menu = nullptr;
Gtk::Menu* gtk_menu = nullptr; Gtk::Menu* gtk_menu = nullptr;
/** /**
@ -51,6 +58,7 @@ class Item : public sigc::trackable {
private: private:
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result); void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value);
void getUpdatedProperties(); void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result); void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
@ -62,10 +70,18 @@ class Item : public sigc::trackable {
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
void makeMenu(); void makeMenu();
bool handleClick(GdkEventButton* const& /*ev*/); bool handleClick(GdkEventButton* const& /*ev*/);
bool handleScroll(GdkEventScroll* const&);
// smooth scrolling threshold
gdouble scroll_threshold_ = 0;
gdouble distance_scrolled_x_ = 0;
gdouble distance_scrolled_y_ = 0;
// visibility of items with Status == Passive
bool show_passive_ = false;
Glib::RefPtr<Gio::DBus::Proxy> proxy_; Glib::RefPtr<Gio::DBus::Proxy> proxy_;
Glib::RefPtr<Gio::Cancellable> cancellable_; Glib::RefPtr<Gio::Cancellable> cancellable_;
bool update_pending_; std::set<std::string_view> update_pending_;
}; };
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@ -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),
}; };

View File

@ -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,13 +21,40 @@ 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&);
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;
std::string lang_; Layout layout_;
util::JsonParser parser_; std::map<std::string, Layout> layouts_map_;
std::mutex mutex_; XKBContext xkb_context_;
Ipc ipc_; bool is_variant_displayed;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
}; };
} // namespace waybar::modules::sway } // namespace waybar::modules::sway

15
include/util/string.hpp Normal file
View File

@ -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)); }

View File

@ -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}",
} }
``` ```

View File

@ -16,6 +16,15 @@ Addressed by *tray*
typeof: integer ++ typeof: integer ++
Defines the size of the tray icons. Defines the size of the tray icons.
*show-passive-items*: ++
typeof: bool ++
default: false ++
Defines visibility of the tray icons with *Passive* status.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*spacing*: ++ *spacing*: ++
typeof: integer ++ typeof: integer ++
Defines the spacing between the tray icons. Defines the spacing between the tray icons.
@ -37,3 +46,6 @@ Addressed by *tray*
# STYLE # STYLE
- *#tray* - *#tray*
- *#tray > .passive*
- *#tray > .active*
- *#tray > .needs-attention*

View File

@ -96,6 +96,7 @@ libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev')) libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev')) libevdev = dependency('libevdev', required: get_option('libevdev'))
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()
@ -279,7 +280,8 @@ executable(
libevdev, libevdev,
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,

View File

@ -195,6 +195,15 @@ label:focus {
background-color: #2980b9; background-color: #2980b9;
} }
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
#idle_inhibitor { #idle_inhibitor {
background-color: #2d3436; background-color: #2d3436;
} }

View File

@ -1,7 +1,12 @@
#include "modules/sni/item.hpp" #include "modules/sni/item.hpp"
#include <gdkmm/general.h>
#include <glibmm/main.h> #include <glibmm/main.h>
#include <gtkmm/tooltip.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <fstream> #include <fstream>
#include <map>
template <> template <>
struct fmt::formatter<Glib::ustring> : formatter<std::string> { struct fmt::formatter<Glib::ustring> : formatter<std::string> {
@ -39,14 +44,22 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
object_path(op), object_path(op),
icon_size(16), icon_size(16),
effective_icon_size(0), effective_icon_size(0),
icon_theme(Gtk::IconTheme::create()), icon_theme(Gtk::IconTheme::create()) {
update_pending_(false) {
if (config["icon-size"].isUInt()) { if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt(); icon_size = config["icon-size"].asUInt();
} }
if (config["smooth-scrolling-threshold"].isNumeric()) {
scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble();
}
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
event_box.add(image); event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK); event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
// initial visibility
event_box.set_visible(show_passive_);
cancellable_ = Gio::Cancellable::create(); cancellable_ = Gio::Cancellable::create();
@ -73,12 +86,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal)); this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) { if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
return; return;
} }
this->updateImage(); this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) { } catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
@ -88,10 +100,24 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
} }
template <typename T> template <typename T>
T get_variant(Glib::VariantBase& value) { T get_variant(const Glib::VariantBase& value) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get(); return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
} }
template <>
ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
ToolTip result;
// Unwrap (sa(iiay)ss)
auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);
result.icon_name = get_variant<Glib::ustring>(container.get_child(0));
result.text = get_variant<Glib::ustring>(container.get_child(2));
auto description = get_variant<Glib::ustring>(container.get_child(3));
if (!description.empty()) {
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
}
return result;
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try { try {
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
@ -102,10 +128,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
id = get_variant<std::string>(value); id = get_variant<std::string>(value);
} else if (name == "Title") { } else if (name == "Title") {
title = get_variant<std::string>(value); title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
event_box.set_tooltip_markup(title);
}
} else if (name == "Status") { } else if (name == "Status") {
status = get_variant<std::string>(value); setStatus(get_variant<Glib::ustring>(value));
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
} else if (name == "IconName") { } else if (name == "IconName") {
icon_name = get_variant<std::string>(value); icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") { } else if (name == "IconPixmap") {
@ -121,7 +148,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "AttentionMovieName") { } else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value); attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") { } else if (name == "ToolTip") {
// TODO: tooltip tooltip = get_variant<ToolTip>(value);
if (!tooltip.text.empty()) {
event_box.set_tooltip_markup(tooltip.text);
}
} else if (name == "IconThemePath") { } else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value); icon_theme_path = get_variant<std::string>(value);
if (!icon_theme_path.empty()) { if (!icon_theme_path.empty()) {
@ -148,9 +178,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} }
} }
void Item::getUpdatedProperties() { void Item::setStatus(const Glib::ustring& value) {
update_pending_ = false; Glib::ustring lower = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) {
style->remove_class(class_name);
}
if (lower.compare("needsattention") == 0) {
// convert status to dash-case for CSS
lower = "needs-attention";
}
style->add_class(lower);
}
void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple( auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)}); {Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
proxy_->call("org.freedesktop.DBus.Properties.GetAll", proxy_->call("org.freedesktop.DBus.Properties.GetAll",
@ -167,33 +210,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
auto properties = properties_variant.get(); auto properties = properties_variant.get();
for (const auto& [name, value] : properties) { for (const auto& [name, value] : properties) {
Glib::VariantBase old_value; if (update_pending_.count(name.raw())) {
proxy_->get_cached_property(old_value, name);
if (!old_value || !value.equal(old_value)) {
proxy_->set_cached_property(name, value);
setProperty(name, const_cast<Glib::VariantBase&>(value)); setProperty(name, const_cast<Glib::VariantBase&>(value));
} }
} }
this->updateImage(); this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) { } catch (const Glib::Error& err) {
spdlog::warn("Failed to update properties: {}", err.what()); spdlog::warn("Failed to update properties: {}", err.what());
} catch (const std::exception& err) { } catch (const std::exception& err) {
spdlog::warn("Failed to update properties: {}", err.what()); spdlog::warn("Failed to update properties: {}", err.what());
} }
update_pending_.clear();
} }
/**
* Mapping from a signal name to a set of possibly changed properties.
* Commented signals are not handled by the tray module at the moment.
*/
static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
{"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}},
// {"XAyatanaNewLabel", {"XAyatanaLabel"}},
};
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) { const Glib::VariantContainerBase& arguments) {
spdlog::trace("Tray item '{}' got signal {}", id, signal_name); spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) { auto changed = signal2props.find(signal_name.raw());
/* Debounce signals and schedule update of all properties. if (changed != signal2props.end()) {
* Based on behavior of Plasma dataengine for StatusNotifierItem. if (update_pending_.empty()) {
*/ /* Debounce signals and schedule update of all properties.
update_pending_ = true; * Based on behavior of Plasma dataengine for StatusNotifierItem.
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), */
UPDATE_DEBOUNCE_TIME); Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
}
update_pending_.insert(changed->second.begin(), changed->second.end());
} }
} }
@ -252,33 +310,42 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
} }
void Item::updateImage() { void Item::updateImage() {
auto scale_factor = image.get_scale_factor();
auto scaled_icon_size = icon_size * scale_factor;
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU);
image.set_pixel_size(icon_size); image.set_pixel_size(scaled_icon_size);
if (!icon_name.empty()) { if (!icon_name.empty()) {
try { try {
// Try to find icons specified by path and filename // Try to find icons specified by path and filename
std::ifstream temp(icon_name); std::ifstream temp(icon_name);
if (temp.is_open()) { if (temp.is_open()) {
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name); auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) { if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for // An icon specified by path and filename may be the wrong size for
// the tray // the tray
// Keep the aspect ratio and scale to make the height equal to icon_size // Keep the aspect ratio and scale to make the height equal to scaled_icon_size
// If people have non square icons, assume they want it to grow in width not height // If people have non square icons, assume they want it to grow in width not height
int width = icon_size * pixbuf->get_width() / pixbuf->get_height(); int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR); pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
image.set(surface);
} }
} else { } else {
image.set(getIconByName(icon_name, icon_size)); auto icon_by_name = getIconByName(icon_name, scaled_icon_size);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(icon_by_name, 0, image.get_window());
image.set(surface);
} }
} catch (Glib::Error& e) { } catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what())); spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
} }
} else if (icon_pixmap) { } else if (icon_pixmap) {
// An icon extracted may be the wrong size for the tray // An icon extracted may be the wrong size for the tray
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR); icon_pixmap = icon_pixmap->scale_simple(icon_size, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
auto surface = Gdk::Cairo::create_surface_from_pixbuf(icon_pixmap, 0, image.get_window());
image.set(icon_pixmap); image.set(icon_pixmap);
} }
} }
@ -360,4 +427,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
return false; return false;
} }
bool Item::handleScroll(GdkEventScroll* const& ev) {
int dx = 0, dy = 0;
switch (ev->direction) {
case GDK_SCROLL_UP:
dy = -1;
break;
case GDK_SCROLL_DOWN:
dy = 1;
break;
case GDK_SCROLL_LEFT:
dx = -1;
break;
case GDK_SCROLL_RIGHT:
dx = 1;
break;
case GDK_SCROLL_SMOOTH:
distance_scrolled_x_ += ev->delta_x;
distance_scrolled_y_ += ev->delta_y;
// check against the configured threshold and ensure that the absolute value >= 1
if (distance_scrolled_x_ > scroll_threshold_) {
dx = (int)lround(std::max(distance_scrolled_x_, 1.0));
distance_scrolled_x_ = 0;
} else if (distance_scrolled_x_ < -scroll_threshold_) {
dx = (int)lround(std::min(distance_scrolled_x_, -1.0));
distance_scrolled_x_ = 0;
}
if (distance_scrolled_y_ > scroll_threshold_) {
dy = (int)lround(std::max(distance_scrolled_y_, 1.0));
distance_scrolled_y_ = 0;
} else if (distance_scrolled_y_ < -scroll_threshold_) {
dy = (int)lround(std::min(distance_scrolled_y_, -1.0));
distance_scrolled_y_ = 0;
}
break;
}
if (dx != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create("horizontal")});
proxy_->call("Scroll", parameters);
}
if (dy != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create("vertical")});
proxy_->call("Scroll", parameters);
}
return true;
}
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@ -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 {
auto payload = parser_.parse(res.payload); std::lock_guard<std::mutex> lock(mutex_);
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK auto payload = parser_.parse(res.payload);
Json::Value::ArrayIndex maxId = 0, max = 0; std::vector<std::string> used_layouts;
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) { // Display current layout of a device with a maximum count of layouts, expecting that all will
if(payload[i]["xkb_layout_names"].size() > max) { // be OK
max = payload[i]["xkb_layout_names"].size(); Json::ArrayIndex max_id = 0, max = 0;
maxId = i; for (Json::ArrayIndex i = 0; i < payload.size(); 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)));
if (tooltipEnabled()) { label_.set_markup(display_layout);
label_.set_tooltip_text(lang_); if (tooltipEnabled()) {
} 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