mirror of
https://github.com/rad4day/Waybar.git
synced 2023-12-21 10:22:59 +01:00
Merge branch 'master' into fix_power_calc
This commit is contained in:
commit
65166109c9
7
.github/workflows/freebsd.yml
vendored
7
.github/workflows/freebsd.yml
vendored
@ -8,15 +8,16 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Test in FreeBSD VM
|
||||
uses: vmactions/freebsd-vm@v0.0.9 # aka FreeBSD 12.2
|
||||
uses: vmactions/freebsd-vm@v0.1.4 # aka FreeBSD 12.2
|
||||
with:
|
||||
usesh: true
|
||||
prepare: |
|
||||
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
|
||||
sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
|
||||
pkg install -y git # subprojects/date
|
||||
pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio \
|
||||
libfmt libmpdclient libudev-devd meson pkgconf pulseaudio scdoc spdlog
|
||||
pkg install -y evdev-proto gtk-layer-shell gtkmm30 jsoncpp libdbusmenu \
|
||||
libevdev libfmt libmpdclient libudev-devd meson pkgconf pulseaudio \
|
||||
scdoc sndio spdlog
|
||||
run: |
|
||||
meson build -Dman-pages=enabled
|
||||
ninja -C build
|
||||
|
25
.github/workflows/linux.yml
vendored
Normal file
25
.github/workflows/linux.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: linux
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
- alpine
|
||||
- archlinux
|
||||
- debian
|
||||
- fedora
|
||||
- opensuse
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: alexays/waybar:${{ matrix.distro }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: configure
|
||||
run: meson -Dman-pages=enabled build
|
||||
- name: build
|
||||
run: ninja -C build
|
38
.travis.yml
38
.travis.yml
@ -1,38 +0,0 @@
|
||||
language: cpp
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
git:
|
||||
submodules: false
|
||||
|
||||
env:
|
||||
- distro: debian
|
||||
- distro: archlinux
|
||||
- distro: fedora
|
||||
- distro: alpine
|
||||
- distro: opensuse
|
||||
|
||||
before_install:
|
||||
- docker pull alexays/waybar:${distro}
|
||||
- find . -type f \( -name '*.cpp' -o -name '*.h' \) -print0 | xargs -r0 clang-format -i
|
||||
|
||||
script:
|
||||
- echo FROM alexays/waybar:${distro} > Dockerfile
|
||||
- echo ADD . /root >> Dockerfile
|
||||
- docker build -t waybar .
|
||||
- docker run waybar /bin/sh -c "cd /root && meson build -Dman-pages=enabled && ninja -C build"
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- os: freebsd
|
||||
compiler: clang
|
||||
env:
|
||||
before_install:
|
||||
- export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio
|
||||
- sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
|
||||
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio
|
||||
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
|
||||
script:
|
||||
- meson build -Dman-pages=enabled
|
||||
- ninja -C build
|
@ -2,4 +2,4 @@
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc
|
||||
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon
|
||||
|
@ -1,6 +1,6 @@
|
||||
# vim: ft=Dockerfile
|
||||
|
||||
FROM archlinux/base:latest
|
||||
FROM archlinux:base-devel
|
||||
|
||||
RUN pacman -Syu --noconfirm && \
|
||||
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm
|
||||
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon
|
||||
|
@ -3,5 +3,5 @@
|
||||
FROM debian:sid
|
||||
|
||||
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 && \
|
||||
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
|
||||
|
@ -1,7 +1,12 @@
|
||||
# vim: ft=Dockerfile
|
||||
|
||||
FROM fedora:32
|
||||
FROM fedora:latest
|
||||
|
||||
RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-devel pugixml-devel egl-wayland-devel mesa-libEGL-devel mesa-libGLES-devel mesa-libgbm-devel libxkbcommon-devel libudev-devel pixman-devel gtkmm30-devel jsoncpp-devel scdoc -y && \
|
||||
dnf group install "C Development Tools and Libraries" -y && \
|
||||
RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
|
||||
'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \
|
||||
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \
|
||||
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
|
||||
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
|
||||
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \
|
||||
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \
|
||||
dnf clean all -y
|
||||
|
@ -3,5 +3,7 @@
|
||||
FROM opensuse/tumbleweed:latest
|
||||
|
||||
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 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
|
||||
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
|
||||
|
@ -68,6 +68,7 @@ libappindicator-gtk3 [Tray module]
|
||||
libdbusmenu-gtk3 [Tray module]
|
||||
libmpdclient [MPD module]
|
||||
libsndio [sndio module]
|
||||
libevdev [KeyboardState module]
|
||||
```
|
||||
|
||||
**Build dependencies**
|
||||
@ -86,6 +87,7 @@ sudo apt install \
|
||||
clang-tidy \
|
||||
gobject-introspection \
|
||||
libdbusmenu-gtk3-dev \
|
||||
libevdev-dev \
|
||||
libfmt-dev \
|
||||
libgirepository1.0-dev \
|
||||
libgtk-3-dev \
|
||||
|
@ -14,7 +14,7 @@ class ALabel : public AModule {
|
||||
virtual ~ALabel() = default;
|
||||
virtual auto update() -> void;
|
||||
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
|
||||
virtual std::string getIcon(uint16_t, std::vector<std::string> &alts, uint16_t max = 0);
|
||||
virtual std::string getIcon(uint16_t, const std::vector<std::string> &alts, uint16_t max = 0);
|
||||
|
||||
protected:
|
||||
Gtk::Label label_;
|
||||
|
@ -38,7 +38,9 @@ class Client {
|
||||
const std::string getValidPath(const std::vector<std::string> &paths) const;
|
||||
void handleOutput(struct waybar_output &output);
|
||||
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
|
||||
auto setupConfig(const std::string &config_file) -> void;
|
||||
auto setupConfig(const std::string &config_file, int depth) -> void;
|
||||
auto resolveConfigIncludes(Json::Value &config, int depth) -> void;
|
||||
auto mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void;
|
||||
auto setupCss(const std::string &css_file) -> void;
|
||||
struct waybar_output & getOutput(void *);
|
||||
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
|
||||
|
@ -38,6 +38,9 @@
|
||||
#ifdef HAVE_LIBUDEV
|
||||
#include "modules/backlight.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
#include "modules/keyboard_state.hpp"
|
||||
#endif
|
||||
#ifdef HAVE_LIBPULSE
|
||||
#include "modules/pulseaudio.hpp"
|
||||
#endif
|
||||
|
47
include/modules/keyboard_state.hpp
Normal file
47
include/modules/keyboard_state.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <fmt/format.h>
|
||||
#if FMT_VERSION < 60000
|
||||
#include <fmt/time.h>
|
||||
#else
|
||||
#include <fmt/chrono.h>
|
||||
#endif
|
||||
#include "AModule.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "util/sleeper_thread.hpp"
|
||||
#include <gtkmm/label.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libevdev/libevdev.h>
|
||||
}
|
||||
|
||||
namespace waybar::modules {
|
||||
|
||||
class KeyboardState : public AModule {
|
||||
public:
|
||||
KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&);
|
||||
~KeyboardState();
|
||||
auto update() -> void;
|
||||
|
||||
private:
|
||||
static auto openDevice(const std::string&) -> std::pair<int, libevdev*>;
|
||||
|
||||
Gtk::Box box_;
|
||||
Gtk::Label numlock_label_;
|
||||
Gtk::Label capslock_label_;
|
||||
Gtk::Label scrolllock_label_;
|
||||
|
||||
std::string numlock_format_;
|
||||
std::string capslock_format_;
|
||||
std::string scrolllock_format_;
|
||||
const std::chrono::seconds interval_;
|
||||
std::string icon_locked_;
|
||||
std::string icon_unlocked_;
|
||||
|
||||
int fd_;
|
||||
libevdev* dev_;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules
|
@ -72,6 +72,7 @@ class Network : public ALabel {
|
||||
int32_t signal_strength_dbm_;
|
||||
uint8_t signal_strength_;
|
||||
uint32_t frequency_;
|
||||
uint32_t route_priority;
|
||||
|
||||
util::SleeperThread thread_;
|
||||
util::SleeperThread thread_timer_;
|
||||
|
@ -24,7 +24,7 @@ class Pulseaudio : public ALabel {
|
||||
static void volumeModifyCb(pa_context*, int, void*);
|
||||
|
||||
bool handleScroll(GdkEventScroll* e);
|
||||
const std::string getPortIcon() const;
|
||||
const std::vector<std::string> getPulseIcon() const;
|
||||
|
||||
pa_threaded_mainloop* mainloop_;
|
||||
pa_mainloop_api* mainloop_api_;
|
||||
@ -38,7 +38,8 @@ class Pulseaudio : public ALabel {
|
||||
std::string form_factor_;
|
||||
std::string desc_;
|
||||
std::string monitor_;
|
||||
std::string default_sink_name_;
|
||||
std::string current_sink_name_;
|
||||
bool current_sink_running_;
|
||||
// SOURCE
|
||||
uint32_t source_idx_{0};
|
||||
uint16_t source_volume_;
|
||||
|
@ -11,8 +11,16 @@
|
||||
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
|
||||
#include <sigc++/trackable.h>
|
||||
|
||||
#include <set>
|
||||
#include <string_view>
|
||||
|
||||
namespace waybar::modules::SNI {
|
||||
|
||||
struct ToolTip {
|
||||
Glib::ustring icon_name;
|
||||
Glib::ustring text;
|
||||
};
|
||||
|
||||
class Item : public sigc::trackable {
|
||||
public:
|
||||
Item(const std::string&, const std::string&, const Json::Value&);
|
||||
@ -27,10 +35,8 @@ class Item : public sigc::trackable {
|
||||
Gtk::EventBox event_box;
|
||||
std::string category;
|
||||
std::string id;
|
||||
std::string status;
|
||||
|
||||
std::string title;
|
||||
int32_t window_id;
|
||||
std::string icon_name;
|
||||
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
|
||||
Glib::RefPtr<Gtk::IconTheme> icon_theme;
|
||||
@ -39,6 +45,7 @@ class Item : public sigc::trackable {
|
||||
std::string attention_movie_name;
|
||||
std::string icon_theme_path;
|
||||
std::string menu;
|
||||
ToolTip tooltip;
|
||||
DbusmenuGtkMenu* dbus_menu = nullptr;
|
||||
Gtk::Menu* gtk_menu = nullptr;
|
||||
/**
|
||||
@ -51,6 +58,7 @@ class Item : public sigc::trackable {
|
||||
private:
|
||||
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
|
||||
void setStatus(const Glib::ustring& value);
|
||||
void getUpdatedProperties();
|
||||
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
|
||||
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);
|
||||
void makeMenu();
|
||||
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::Cancellable> cancellable_;
|
||||
bool update_pending_;
|
||||
std::set<std::string_view> update_pending_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::SNI
|
||||
|
@ -29,4 +29,8 @@ enum ipc_command_type {
|
||||
IPC_EVENT_BINDING = ((1 << 31) | 5),
|
||||
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
|
||||
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
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <xkbcommon/xkbregistry.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "ALabel.hpp"
|
||||
#include "bar.hpp"
|
||||
#include "client.hpp"
|
||||
@ -16,13 +21,41 @@ class Language : public ALabel, public sigc::trackable {
|
||||
auto update() -> void;
|
||||
|
||||
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 onCmd(const struct Ipc::ipc_response&);
|
||||
|
||||
std::string lang_;
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
Ipc ipc_;
|
||||
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::string tooltip_format_ = "";
|
||||
std::map<std::string, Layout> layouts_map_;
|
||||
XKBContext xkb_context_;
|
||||
bool is_variant_displayed;
|
||||
|
||||
util::JsonParser parser_;
|
||||
std::mutex mutex_;
|
||||
Ipc ipc_;
|
||||
};
|
||||
|
||||
} // namespace waybar::modules::sway
|
||||
|
@ -35,7 +35,11 @@ namespace fmt {
|
||||
// The rationale for ignoring it is that the only reason to specify
|
||||
// an alignment and a with is to get a fixed width bar, and ">" is
|
||||
// sufficient in this implementation.
|
||||
#if FMT_VERSION < 80000
|
||||
width = parse_nonnegative_int(it, end, ctx);
|
||||
#else
|
||||
width = detail::parse_nonnegative_int(it, end, -1);
|
||||
#endif
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
@ -8,6 +8,20 @@
|
||||
|
||||
namespace waybar::util {
|
||||
|
||||
/**
|
||||
* Defer pthread_cancel until the end of a current scope.
|
||||
*
|
||||
* Required to protect a scope where it's unsafe to raise `__forced_unwind` exception.
|
||||
* An example of these is a call of a method marked as `noexcept`; an attempt to cancel within such
|
||||
* a method may result in a `std::terminate` call.
|
||||
*/
|
||||
class CancellationGuard {
|
||||
int oldstate;
|
||||
public:
|
||||
CancellationGuard() { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); }
|
||||
~CancellationGuard() { pthread_setcancelstate(oldstate, &oldstate); }
|
||||
};
|
||||
|
||||
class SleeperThread {
|
||||
public:
|
||||
SleeperThread() = default;
|
||||
@ -33,14 +47,16 @@ class SleeperThread {
|
||||
bool isRunning() const { return do_run_; }
|
||||
|
||||
auto sleep_for(std::chrono::system_clock::duration dur) {
|
||||
std::unique_lock lk(mutex_);
|
||||
std::unique_lock lk(mutex_);
|
||||
CancellationGuard cancel_lock;
|
||||
return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; });
|
||||
}
|
||||
|
||||
auto sleep_until(
|
||||
std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration>
|
||||
time_point) {
|
||||
std::unique_lock lk(mutex_);
|
||||
std::unique_lock lk(mutex_);
|
||||
CancellationGuard cancel_lock;
|
||||
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
|
||||
}
|
||||
|
||||
|
15
include/util/string.hpp
Normal file
15
include/util/string.hpp
Normal 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)); }
|
80
man/waybar-keyboard-state.5.scd
Normal file
80
man/waybar-keyboard-state.5.scd
Normal file
@ -0,0 +1,80 @@
|
||||
waybar-keyboard-state(5)
|
||||
|
||||
# NAME
|
||||
|
||||
waybar - keyboard-state module
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock.
|
||||
|
||||
# CONFIGURATION
|
||||
|
||||
*interval*: ++
|
||||
typeof: integer ++
|
||||
default: 1 ++
|
||||
The interval, in seconds, to poll the keyboard state.
|
||||
|
||||
*format*: ++
|
||||
typeof: string|object ++
|
||||
default: {name} {icon} ++
|
||||
The format, how information should be displayed. If a string, the same format is used for all keyboard states. If an object, the fields "numlock", "capslock", and "scrolllock" each specify the format for the corresponding state. Any unspecified states use the default format.
|
||||
|
||||
*format-icons*: ++
|
||||
typeof: object ++
|
||||
default: {"locked": "locked", "unlocked": "unlocked"} ++
|
||||
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
|
||||
|
||||
*numlock*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Display the number lock state.
|
||||
|
||||
*capslock*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Display the caps lock state.
|
||||
|
||||
*scrolllock*: ++
|
||||
typeof: bool ++
|
||||
default: false ++
|
||||
Display the scroll lock state.
|
||||
|
||||
*device-path*: ++
|
||||
typeof: string ++
|
||||
default: chooses first valid input device ++
|
||||
Which libevdev input device to show the state of. Libevdev devices can be found in /dev/input. The device should support number lock, caps lock, and scroll lock events.
|
||||
|
||||
# FORMAT REPLACEMENTS
|
||||
|
||||
*{name}*: Caps, Num, or Scroll.
|
||||
|
||||
*{icon}*: Icon, as defined in *format-icons*.
|
||||
|
||||
# ICONS
|
||||
|
||||
The following *format-icons* can be set.
|
||||
|
||||
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
|
||||
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
|
||||
|
||||
# EXAMPLE:
|
||||
|
||||
```
|
||||
"keyboard-state": {
|
||||
"numlock": true,
|
||||
"capslock": true,
|
||||
"format": "{name} {icon}",
|
||||
"format-icons": {
|
||||
"locked": "",
|
||||
"unlocked": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# STYLE
|
||||
|
||||
- *#keyboard-state*
|
||||
- *#keyboard-state label*
|
||||
- *#keyboard-state label.locked*
|
||||
|
@ -109,6 +109,9 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
|
||||
# ICONS:
|
||||
|
||||
The following strings for *format-icons* are supported.
|
||||
|
||||
- the device name
|
||||
|
||||
If they are found in the current PulseAudio port name, the corresponding icons will be selected.
|
||||
|
||||
- *default* (Shown, when no other port is found)
|
||||
@ -131,6 +134,7 @@ If they are found in the current PulseAudio port name, the corresponding icons w
|
||||
"format-bluetooth": "{volume}% {icon}",
|
||||
"format-muted": "",
|
||||
"format-icons": {
|
||||
"alsa_output.pci-0000_00_1f.3.analog-stereo": "",
|
||||
"headphones": "",
|
||||
"handsfree": "",
|
||||
"headset": "",
|
||||
|
@ -15,63 +15,35 @@ Addressed by *sway/language*
|
||||
*format*: ++
|
||||
typeof: string ++
|
||||
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*: ++
|
||||
*tooltip-format*: ++
|
||||
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.
|
||||
default: {} ++
|
||||
The format, how layout should be displayed in tooltip.
|
||||
|
||||
*tooltip*: ++
|
||||
typeof: bool ++
|
||||
default: true ++
|
||||
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
|
||||
|
||||
```
|
||||
"sway/language": {
|
||||
"format": "{}",
|
||||
"max-length": 50
|
||||
},
|
||||
|
||||
"sway/language": {
|
||||
"format": "{short} {variant}",
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -16,6 +16,15 @@ Addressed by *tray*
|
||||
typeof: integer ++
|
||||
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*: ++
|
||||
typeof: integer ++
|
||||
Defines the spacing between the tray icons.
|
||||
@ -37,3 +46,6 @@ Addressed by *tray*
|
||||
# STYLE
|
||||
|
||||
- *#tray*
|
||||
- *#tray > .passive*
|
||||
- *#tray > .active*
|
||||
- *#tray > .needs-attention*
|
||||
|
@ -85,6 +85,11 @@ Also a minimal example configuration can be found on the at the bottom of this m
|
||||
Option to disable the use of gtk-layer-shell for popups.
|
||||
Only functional if compiled with gtk-layer-shell support.
|
||||
|
||||
*include* ++
|
||||
typeof: string|array ++
|
||||
Paths to additional configuration files. In case of duplicate options, the including file's value takes precedence. Make sure to avoid circular imports.
|
||||
For a multi-bar config, specify at least an empty object for each bar also in every file being included.
|
||||
|
||||
# MODULE FORMAT
|
||||
|
||||
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
|
||||
@ -203,6 +208,7 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
|
||||
- *waybar-custom(5)*
|
||||
- *waybar-disk(5)*
|
||||
- *waybar-idle-inhibitor(5)*
|
||||
- *waybar-keyboard-state(5)*
|
||||
- *waybar-memory(5)*
|
||||
- *waybar-mpd(5)*
|
||||
- *waybar-network(5)*
|
||||
|
12
meson.build
12
meson.build
@ -94,7 +94,9 @@ libnl = dependency('libnl-3.0', required: get_option('libnl'))
|
||||
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
|
||||
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
|
||||
libudev = dependency('libudev', required: get_option('libudev'))
|
||||
libevdev = dependency('libevdev', required: get_option('libevdev'))
|
||||
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
|
||||
xkbregistry = dependency('xkbregistry')
|
||||
|
||||
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
|
||||
if libsndio.found()
|
||||
@ -215,6 +217,11 @@ if libudev.found() and (is_linux or libepoll.found())
|
||||
src_files += 'src/modules/backlight.cpp'
|
||||
endif
|
||||
|
||||
if libevdev.found() and (is_linux or libepoll.found())
|
||||
add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')
|
||||
src_files += 'src/modules/keyboard_state.cpp'
|
||||
endif
|
||||
|
||||
if libmpdclient.found()
|
||||
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
|
||||
src_files += 'src/modules/mpd/mpd.cpp'
|
||||
@ -270,9 +277,11 @@ executable(
|
||||
libudev,
|
||||
libepoll,
|
||||
libmpdclient,
|
||||
libevdev,
|
||||
gtk_layer_shell,
|
||||
libsndio,
|
||||
tz_dep
|
||||
tz_dep,
|
||||
xkbregistry
|
||||
],
|
||||
include_directories: [include_directories('include')],
|
||||
install: true,
|
||||
@ -310,6 +319,7 @@ if scdoc.found()
|
||||
'waybar-custom.5.scd',
|
||||
'waybar-disk.5.scd',
|
||||
'waybar-idle-inhibitor.5.scd',
|
||||
'waybar-keyboard-state.5.scd',
|
||||
'waybar-memory.5.scd',
|
||||
'waybar-mpd.5.scd',
|
||||
'waybar-network.5.scd',
|
||||
|
@ -1,6 +1,7 @@
|
||||
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.')
|
||||
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
|
||||
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
|
||||
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')
|
||||
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
|
||||
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
|
||||
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
|
||||
|
@ -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", "sway/language", "battery", "battery#bat2", "clock", "tray"],
|
||||
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"],
|
||||
// Modules configuration
|
||||
// "sway/workspaces": {
|
||||
// "disable-scroll": true,
|
||||
@ -23,6 +23,15 @@
|
||||
// "default": ""
|
||||
// }
|
||||
// },
|
||||
"keyboard-state": {
|
||||
"numlock": true,
|
||||
"capslock": true,
|
||||
"format": "{name} {icon}",
|
||||
"format-icons": {
|
||||
"locked": "",
|
||||
"unlocked": ""
|
||||
}
|
||||
},
|
||||
"sway/mode": {
|
||||
"format": "<span style=\"italic\">{}</span>"
|
||||
},
|
||||
@ -145,3 +154,4 @@
|
||||
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -195,6 +195,15 @@ label:focus {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
#tray > .passive {
|
||||
-gtk-icon-effect: dim;
|
||||
}
|
||||
|
||||
#tray > .needs-attention {
|
||||
-gtk-icon-effect: highlight;
|
||||
background-color: #eb4d4b;
|
||||
}
|
||||
|
||||
#idle_inhibitor {
|
||||
background-color: #2d3436;
|
||||
}
|
||||
@ -228,3 +237,19 @@ label:focus {
|
||||
margin: 0 5px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#keyboard-state {
|
||||
background: #97e1ad;
|
||||
color: #000000;
|
||||
padding: 0 0px;
|
||||
margin: 0 5px;
|
||||
min-width: 16px;
|
||||
}
|
||||
|
||||
#keyboard-state > label {
|
||||
padding: 0 5px;
|
||||
}
|
||||
|
||||
#keyboard-state > label.locked {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) {
|
||||
std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& alts, uint16_t max) {
|
||||
auto format_icons = config_["format-icons"];
|
||||
if (format_icons.isObject()) {
|
||||
std::string _alt = "default";
|
||||
|
@ -234,14 +234,62 @@ std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
|
||||
return {config_file, css_file};
|
||||
}
|
||||
|
||||
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
|
||||
auto waybar::Client::setupConfig(const std::string &config_file, int depth) -> void {
|
||||
if (depth > 100) {
|
||||
throw std::runtime_error("Aborting due to likely recursive include in config files");
|
||||
}
|
||||
std::ifstream file(config_file);
|
||||
if (!file.is_open()) {
|
||||
throw std::runtime_error("Can't open config file");
|
||||
}
|
||||
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
|
||||
util::JsonParser parser;
|
||||
config_ = parser.parse(str);
|
||||
Json::Value tmp_config_ = parser.parse(str);
|
||||
if (tmp_config_.isArray()) {
|
||||
for (auto &config_part : tmp_config_) {
|
||||
resolveConfigIncludes(config_part, depth);
|
||||
}
|
||||
} else {
|
||||
resolveConfigIncludes(tmp_config_, depth);
|
||||
}
|
||||
mergeConfig(config_, tmp_config_);
|
||||
}
|
||||
|
||||
auto waybar::Client::resolveConfigIncludes(Json::Value &config, int depth) -> void {
|
||||
Json::Value includes = config["include"];
|
||||
if (includes.isArray()) {
|
||||
for (const auto &include : includes) {
|
||||
spdlog::info("Including resource file: {}", include.asString());
|
||||
setupConfig(getValidPath({include.asString()}), ++depth);
|
||||
}
|
||||
} else if (includes.isString()) {
|
||||
spdlog::info("Including resource file: {}", includes.asString());
|
||||
setupConfig(getValidPath({includes.asString()}), ++depth);
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::Client::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) -> void {
|
||||
if (!a_config_) {
|
||||
// For the first config
|
||||
a_config_ = b_config_;
|
||||
} else if (a_config_.isObject() && b_config_.isObject()) {
|
||||
for (const auto &key : b_config_.getMemberNames()) {
|
||||
if (a_config_[key].isObject() && b_config_[key].isObject()) {
|
||||
mergeConfig(a_config_[key], b_config_[key]);
|
||||
} else {
|
||||
a_config_[key] = b_config_[key];
|
||||
}
|
||||
}
|
||||
} else if (a_config_.isArray() && b_config_.isArray()) {
|
||||
// This can happen only on the top-level array of a multi-bar config
|
||||
for (Json::Value::ArrayIndex i = 0; i < b_config_.size(); i++) {
|
||||
if (a_config_[i].isObject() && b_config_[i].isObject()) {
|
||||
mergeConfig(a_config_[i], b_config_[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::Client::setupCss(const std::string &css_file) -> void {
|
||||
@ -320,7 +368,7 @@ int waybar::Client::main(int argc, char *argv[]) {
|
||||
}
|
||||
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
|
||||
auto [config_file, css_file] = getConfigs(config, style);
|
||||
setupConfig(config_file);
|
||||
setupConfig(config_file, 0);
|
||||
setupCss(css_file);
|
||||
bindInterfaces();
|
||||
gtk_app->hold();
|
||||
|
@ -70,6 +70,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
|
||||
return new waybar::modules::Backlight(id, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LIBEVDEV
|
||||
if (ref == "keyboard-state") {
|
||||
return new waybar::modules::KeyboardState(id, bar_, config_[name]);
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LIBPULSE
|
||||
if (ref == "pulseaudio") {
|
||||
return new waybar::modules::Pulseaudio(id, config_[name]);
|
||||
|
@ -196,6 +196,9 @@ template <>
|
||||
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
|
||||
template <typename FormatContext>
|
||||
auto format(const waybar_time& t, FormatContext& ctx) {
|
||||
#if FMT_VERSION >= 80000
|
||||
auto& tm_format = specs;
|
||||
#endif
|
||||
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
|
||||
}
|
||||
};
|
||||
|
152
src/modules/keyboard_state.cpp
Normal file
152
src/modules/keyboard_state.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
#include "modules/keyboard_state.hpp"
|
||||
#include <filesystem>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
extern "C" {
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
}
|
||||
|
||||
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
|
||||
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
|
||||
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
|
||||
numlock_label_(""),
|
||||
capslock_label_(""),
|
||||
numlock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
: config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString()
|
||||
: "{name} {icon}"),
|
||||
capslock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
: config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString()
|
||||
: "{name} {icon}"),
|
||||
scrolllock_format_(config_["format"].isString() ? config_["format"].asString()
|
||||
: config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString()
|
||||
: "{name} {icon}"),
|
||||
interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
|
||||
icon_locked_(config_["format-icons"]["locked"].isString()
|
||||
? config_["format-icons"]["locked"].asString()
|
||||
: "locked"),
|
||||
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
|
||||
? config_["format-icons"]["unlocked"].asString()
|
||||
: "unlocked"),
|
||||
fd_(0),
|
||||
dev_(nullptr) {
|
||||
box_.set_name("keyboard-state");
|
||||
if (config_["numlock"].asBool()) {
|
||||
box_.pack_end(numlock_label_, false, false, 0);
|
||||
}
|
||||
if (config_["capslock"].asBool()) {
|
||||
box_.pack_end(capslock_label_, false, false, 0);
|
||||
}
|
||||
if (config_["scrolllock"].asBool()) {
|
||||
box_.pack_end(scrolllock_label_, false, false, 0);
|
||||
}
|
||||
if (!id.empty()) {
|
||||
box_.get_style_context()->add_class(id);
|
||||
}
|
||||
event_box_.add(box_);
|
||||
|
||||
if (config_["device-path"].isString()) {
|
||||
std::string dev_path = config_["device-path"].asString();
|
||||
std::tie(fd_, dev_) = openDevice(dev_path);
|
||||
} else {
|
||||
DIR* dev_dir = opendir("/dev/input");
|
||||
if (dev_dir == nullptr) {
|
||||
throw std::runtime_error("Failed to open /dev/input");
|
||||
}
|
||||
dirent *ep;
|
||||
while ((ep = readdir(dev_dir))) {
|
||||
if (ep->d_type != DT_CHR) continue;
|
||||
std::string dev_path = std::string("/dev/input/") + ep->d_name;
|
||||
try {
|
||||
std::tie(fd_, dev_) = openDevice(dev_path);
|
||||
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev_), dev_path);
|
||||
break;
|
||||
} catch (const std::runtime_error& e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (dev_ == nullptr) {
|
||||
throw std::runtime_error("Failed to find keyboard device");
|
||||
}
|
||||
}
|
||||
|
||||
thread_ = [this] {
|
||||
dp.emit();
|
||||
thread_.sleep_for(interval_);
|
||||
};
|
||||
}
|
||||
|
||||
waybar::modules::KeyboardState::~KeyboardState() {
|
||||
libevdev_free(dev_);
|
||||
int err = close(fd_);
|
||||
if (err < 0) {
|
||||
// Not much we can do, so ignore it.
|
||||
}
|
||||
}
|
||||
|
||||
auto waybar::modules::KeyboardState::openDevice(const std::string& path) -> std::pair<int, libevdev*> {
|
||||
int fd = open(path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
|
||||
if (fd < 0) {
|
||||
throw std::runtime_error("Can't open " + path);
|
||||
}
|
||||
|
||||
libevdev* dev;
|
||||
int err = libevdev_new_from_fd(fd, &dev);
|
||||
if (err < 0) {
|
||||
throw std::runtime_error("Can't create libevdev device");
|
||||
}
|
||||
if (!libevdev_has_event_type(dev, EV_LED)) {
|
||||
throw std::runtime_error("Device doesn't support LED events");
|
||||
}
|
||||
if (!libevdev_has_event_code(dev, EV_LED, LED_NUML)
|
||||
|| !libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
|
||||
|| !libevdev_has_event_code(dev, EV_LED, LED_SCROLLL)) {
|
||||
throw std::runtime_error("Device doesn't support num lock, caps lock, or scroll lock events");
|
||||
}
|
||||
|
||||
return std::make_pair(fd, dev);
|
||||
}
|
||||
|
||||
auto waybar::modules::KeyboardState::update() -> void {
|
||||
int err = LIBEVDEV_READ_STATUS_SUCCESS;
|
||||
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
|
||||
input_event ev;
|
||||
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
|
||||
while (err == LIBEVDEV_READ_STATUS_SYNC) {
|
||||
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
|
||||
}
|
||||
}
|
||||
if (err != -EAGAIN) {
|
||||
throw std::runtime_error("Failed to sync evdev device");
|
||||
}
|
||||
|
||||
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
|
||||
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
|
||||
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
|
||||
|
||||
struct {
|
||||
bool state;
|
||||
Gtk::Label& label;
|
||||
const std::string& format;
|
||||
const char* name;
|
||||
} label_states[] = {
|
||||
{(bool) numl, numlock_label_, numlock_format_, "Num"},
|
||||
{(bool) capsl, capslock_label_, capslock_format_, "Caps"},
|
||||
{(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"},
|
||||
};
|
||||
for (auto& label_state : label_states) {
|
||||
std::string text;
|
||||
text = fmt::format(label_state.format,
|
||||
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
|
||||
fmt::arg("name", label_state.name));
|
||||
label_state.label.set_markup(text);
|
||||
if (label_state.state) {
|
||||
label_state.label.get_style_context()->add_class("locked");
|
||||
} else {
|
||||
label_state.label.get_style_context()->remove_class("locked");
|
||||
}
|
||||
}
|
||||
|
||||
AModule::update();
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#include "modules/network.hpp"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <linux/if.h>
|
||||
#include <fstream>
|
||||
#include <cassert>
|
||||
#include <optional>
|
||||
@ -18,6 +19,7 @@ constexpr const char *NETSTAT_FILE =
|
||||
constexpr std::string_view BANDWIDTH_CATEGORY = "IpExt";
|
||||
constexpr std::string_view BANDWIDTH_DOWN_TOTAL_KEY = "InOctets";
|
||||
constexpr std::string_view BANDWIDTH_UP_TOTAL_KEY = "OutOctets";
|
||||
constexpr const char *DEFAULT_FORMAT = "{ifname}";
|
||||
|
||||
std::ifstream netstat(NETSTAT_FILE);
|
||||
std::optional<unsigned long long> read_netstat(std::string_view category, std::string_view key) {
|
||||
@ -81,7 +83,7 @@ std::optional<unsigned long long> read_netstat(std::string_view category, std::s
|
||||
} // namespace
|
||||
|
||||
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
|
||||
: ALabel(config, "network", id, "{ifname}", 60),
|
||||
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
|
||||
ifid_(-1),
|
||||
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
|
||||
efd_(-1),
|
||||
@ -322,6 +324,10 @@ auto waybar::modules::Network::update() -> void {
|
||||
}
|
||||
if (config_["format-" + state].isString()) {
|
||||
default_format_ = config_["format-" + state].asString();
|
||||
} else if (config_["format"].isString()) {
|
||||
default_format_ = config_["format"].asString();
|
||||
} else {
|
||||
default_format_ = DEFAULT_FORMAT;
|
||||
}
|
||||
if (config_["tooltip-format-" + state].isString()) {
|
||||
tooltip_format = config_["tooltip-format-" + state].asString();
|
||||
@ -400,6 +406,7 @@ bool waybar::modules::Network::checkInterface(std::string name) {
|
||||
|
||||
void waybar::modules::Network::clearIface() {
|
||||
ifid_ = -1;
|
||||
ifname_.clear();
|
||||
essid_.clear();
|
||||
ipaddr_.clear();
|
||||
netmask_.clear();
|
||||
@ -431,6 +438,20 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
// Check if the interface goes "down" and if we want to detect the
|
||||
// external interface.
|
||||
if (net->ifid_ != -1 && !(ifi->ifi_flags & IFF_UP)
|
||||
&& !net->config_["interface"].isString()) {
|
||||
// The current interface is now down, all the routes associated with
|
||||
// it have been deleted, so start looking for a new default route.
|
||||
spdlog::debug("network: if{} down", net->ifid_);
|
||||
net->clearIface();
|
||||
net->dp.emit();
|
||||
net->want_route_dump_ = true;
|
||||
net->askForStateDump();
|
||||
return NL_OK;
|
||||
}
|
||||
|
||||
for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) {
|
||||
switch (ifla->rta_type) {
|
||||
case IFLA_IFNAME:
|
||||
@ -572,11 +593,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
bool has_gateway = false;
|
||||
bool has_destination = false;
|
||||
int temp_idx = -1;
|
||||
|
||||
/* If we found the correct answer, skip parsing the attributes. */
|
||||
if (!is_del_event && net->ifid_ != -1) {
|
||||
return NL_OK;
|
||||
}
|
||||
uint32_t priority = 0;
|
||||
|
||||
/* Find the message(s) concerting the main routing table, each message
|
||||
* corresponds to a single routing table entry.
|
||||
@ -620,52 +637,59 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
|
||||
/* The output interface index. */
|
||||
temp_idx = *static_cast<int *>(RTA_DATA(attr));
|
||||
break;
|
||||
case RTA_PRIORITY:
|
||||
priority = *(uint32_t*)RTA_DATA(attr);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* If this is the default route, and we know the interface index,
|
||||
* we can stop parsing this message.
|
||||
*/
|
||||
if (has_gateway && !has_destination && temp_idx != -1) {
|
||||
if (!is_del_event) {
|
||||
net->ifid_ = temp_idx;
|
||||
// Check if we have a default route.
|
||||
if (has_gateway && !has_destination && temp_idx != -1) {
|
||||
// Check if this is the first default route we see, or if this new
|
||||
// route have a higher priority.
|
||||
if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) {
|
||||
// Clear if's state for the case were there is a higher priority
|
||||
// route on a different interface.
|
||||
net->clearIface();
|
||||
net->ifid_ = temp_idx;
|
||||
net->route_priority = priority;
|
||||
|
||||
spdlog::debug("network: new default route via if{}", temp_idx);
|
||||
spdlog::debug("network: new default route via if{} metric {}", temp_idx, priority);
|
||||
|
||||
/* Ask ifname associated with temp_idx as well as carrier status */
|
||||
struct ifinfomsg ifinfo_hdr = {
|
||||
.ifi_family = AF_UNSPEC,
|
||||
.ifi_index = temp_idx,
|
||||
};
|
||||
int err;
|
||||
err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST,
|
||||
&ifinfo_hdr, sizeof (ifinfo_hdr));
|
||||
if (err < 0) {
|
||||
spdlog::error("network: failed to ask link info: {}", err);
|
||||
/* Ask for a dump of all links instead */
|
||||
net->want_link_dump_ = true;
|
||||
}
|
||||
|
||||
/* Also ask for the address. Asking for a addresses of a specific
|
||||
* interface doesn't seems to work so ask for a dump of all
|
||||
* addresses. */
|
||||
net->want_addr_dump_ = true;
|
||||
net->askForStateDump();
|
||||
net->thread_timer_.wake_up();
|
||||
} else if (is_del_event && temp_idx == net->ifid_) {
|
||||
spdlog::debug("network: default route deleted {}/if{}",
|
||||
net->ifname_, temp_idx);
|
||||
|
||||
net->ifname_.clear();
|
||||
net->clearIface();
|
||||
net->dp.emit();
|
||||
/* Ask for a dump of all routes in case another one is already
|
||||
* setup. If there's none, there'll be an event with new one
|
||||
* later. */
|
||||
net->want_route_dump_ = true;
|
||||
net->askForStateDump();
|
||||
/* Ask ifname associated with temp_idx as well as carrier status */
|
||||
struct ifinfomsg ifinfo_hdr = {
|
||||
.ifi_family = AF_UNSPEC,
|
||||
.ifi_index = temp_idx,
|
||||
};
|
||||
int err;
|
||||
err = nl_send_simple(net->ev_sock_, RTM_GETLINK, NLM_F_REQUEST,
|
||||
&ifinfo_hdr, sizeof (ifinfo_hdr));
|
||||
if (err < 0) {
|
||||
spdlog::error("network: failed to ask link info: {}", err);
|
||||
/* Ask for a dump of all links instead */
|
||||
net->want_link_dump_ = true;
|
||||
}
|
||||
|
||||
/* Also ask for the address. Asking for a addresses of a specific
|
||||
* interface doesn't seems to work so ask for a dump of all
|
||||
* addresses. */
|
||||
net->want_addr_dump_ = true;
|
||||
net->askForStateDump();
|
||||
net->thread_timer_.wake_up();
|
||||
} else if (is_del_event && temp_idx == net->ifid_
|
||||
&& net->route_priority == priority) {
|
||||
spdlog::debug("network: default route deleted {}/if{} metric {}",
|
||||
net->ifname_, temp_idx, priority);
|
||||
|
||||
net->clearIface();
|
||||
net->dp.emit();
|
||||
/* Ask for a dump of all routes in case another one is already
|
||||
* setup. If there's none, there'll be an event with new one
|
||||
* later. */
|
||||
net->want_route_dump_ = true;
|
||||
net->askForStateDump();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -151,8 +151,24 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p
|
||||
*/
|
||||
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
|
||||
int /*eol*/, void *data) {
|
||||
if (i == nullptr)
|
||||
return;
|
||||
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
if (i != nullptr && pa->default_sink_name_ == i->name) {
|
||||
if (pa->current_sink_name_ == i->name) {
|
||||
if (i->state != PA_SINK_RUNNING) {
|
||||
pa->current_sink_running_ = false;
|
||||
} else {
|
||||
pa->current_sink_running_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
|
||||
pa->current_sink_name_ = i->name;
|
||||
pa->current_sink_running_ = true;
|
||||
}
|
||||
|
||||
if (pa->current_sink_name_ == i->name) {
|
||||
pa->pa_volume_ = i->volume;
|
||||
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
|
||||
pa->sink_idx_ = i->index;
|
||||
@ -175,11 +191,11 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
|
||||
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
|
||||
void *data) {
|
||||
auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
|
||||
pa->default_sink_name_ = i->default_sink_name;
|
||||
pa->current_sink_name_ = i->default_sink_name;
|
||||
pa->default_source_name_ = i->default_source_name;
|
||||
|
||||
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data);
|
||||
pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data);
|
||||
pa_context_get_sink_info_list(context, sinkInfoCb, data);
|
||||
pa_context_get_source_info_list(context, sourceInfoCb, data);
|
||||
}
|
||||
|
||||
static const std::array<std::string, 9> ports = {
|
||||
@ -194,15 +210,17 @@ static const std::array<std::string, 9> ports = {
|
||||
"phone",
|
||||
};
|
||||
|
||||
const std::string waybar::modules::Pulseaudio::getPortIcon() const {
|
||||
const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
|
||||
std::vector<std::string> res = {default_source_name_};
|
||||
std::string nameLC = port_name_ + form_factor_;
|
||||
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
|
||||
for (auto const &port : ports) {
|
||||
if (nameLC.find(port) != std::string::npos) {
|
||||
return port;
|
||||
res.push_back(port);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return port_name_;
|
||||
return res;
|
||||
}
|
||||
|
||||
auto waybar::modules::Pulseaudio::update() -> void {
|
||||
@ -252,7 +270,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
fmt::arg("format_source", format_source),
|
||||
fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_),
|
||||
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
|
||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
||||
getState(volume_);
|
||||
|
||||
if (tooltipEnabled()) {
|
||||
@ -267,7 +285,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
|
||||
fmt::arg("format_source", format_source),
|
||||
fmt::arg("source_volume", source_volume_),
|
||||
fmt::arg("source_desc", source_desc_),
|
||||
fmt::arg("icon", getIcon(volume_, getPortIcon()))));
|
||||
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
|
||||
} else {
|
||||
label_.set_tooltip_text(desc_);
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
#include "modules/sni/item.hpp"
|
||||
|
||||
#include <gdkmm/general.h>
|
||||
#include <glibmm/main.h>
|
||||
#include <gtkmm/tooltip.h>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
|
||||
template <>
|
||||
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),
|
||||
icon_size(16),
|
||||
effective_icon_size(0),
|
||||
icon_theme(Gtk::IconTheme::create()),
|
||||
update_pending_(false) {
|
||||
icon_theme(Gtk::IconTheme::create()) {
|
||||
if (config["icon-size"].isUInt()) {
|
||||
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_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_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
|
||||
// initial visibility
|
||||
event_box.set_visible(show_passive_);
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
this->updateImage();
|
||||
// this->event_box.set_tooltip_text(this->title);
|
||||
|
||||
} catch (const Glib::Error& err) {
|
||||
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>
|
||||
T get_variant(Glib::VariantBase& value) {
|
||||
T get_variant(const Glib::VariantBase& value) {
|
||||
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) {
|
||||
try {
|
||||
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);
|
||||
} else if (name == "Title") {
|
||||
title = get_variant<std::string>(value);
|
||||
if (tooltip.text.empty()) {
|
||||
event_box.set_tooltip_markup(title);
|
||||
}
|
||||
} else if (name == "Status") {
|
||||
status = get_variant<std::string>(value);
|
||||
} else if (name == "WindowId") {
|
||||
window_id = get_variant<int32_t>(value);
|
||||
setStatus(get_variant<Glib::ustring>(value));
|
||||
} else if (name == "IconName") {
|
||||
icon_name = get_variant<std::string>(value);
|
||||
} else if (name == "IconPixmap") {
|
||||
@ -121,7 +148,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
} else if (name == "AttentionMovieName") {
|
||||
attention_movie_name = get_variant<std::string>(value);
|
||||
} 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") {
|
||||
icon_theme_path = get_variant<std::string>(value);
|
||||
if (!icon_theme_path.empty()) {
|
||||
@ -148,9 +178,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
|
||||
}
|
||||
}
|
||||
|
||||
void Item::getUpdatedProperties() {
|
||||
update_pending_ = false;
|
||||
void Item::setStatus(const Glib::ustring& value) {
|
||||
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(
|
||||
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
|
||||
proxy_->call("org.freedesktop.DBus.Properties.GetAll",
|
||||
@ -167,33 +210,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
|
||||
auto properties = properties_variant.get();
|
||||
|
||||
for (const auto& [name, value] : properties) {
|
||||
Glib::VariantBase old_value;
|
||||
proxy_->get_cached_property(old_value, name);
|
||||
if (!old_value || !value.equal(old_value)) {
|
||||
proxy_->set_cached_property(name, value);
|
||||
if (update_pending_.count(name.raw())) {
|
||||
setProperty(name, const_cast<Glib::VariantBase&>(value));
|
||||
}
|
||||
}
|
||||
|
||||
this->updateImage();
|
||||
// this->event_box.set_tooltip_text(this->title);
|
||||
} catch (const Glib::Error& err) {
|
||||
spdlog::warn("Failed to update properties: {}", err.what());
|
||||
} catch (const std::exception& err) {
|
||||
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,
|
||||
const Glib::VariantContainerBase& arguments) {
|
||||
spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
|
||||
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) {
|
||||
/* Debounce signals and schedule update of all properties.
|
||||
* Based on behavior of Plasma dataengine for StatusNotifierItem.
|
||||
*/
|
||||
update_pending_ = true;
|
||||
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
|
||||
UPDATE_DEBOUNCE_TIME);
|
||||
auto changed = signal2props.find(signal_name.raw());
|
||||
if (changed != signal2props.end()) {
|
||||
if (update_pending_.empty()) {
|
||||
/* Debounce signals and schedule update of all properties.
|
||||
* Based on behavior of Plasma dataengine for StatusNotifierItem.
|
||||
*/
|
||||
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() {
|
||||
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_pixel_size(icon_size);
|
||||
image.set_pixel_size(scaled_icon_size);
|
||||
if (!icon_name.empty()) {
|
||||
try {
|
||||
// Try to find icons specified by path and filename
|
||||
std::ifstream temp(icon_name);
|
||||
if (temp.is_open()) {
|
||||
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name);
|
||||
|
||||
if (pixbuf->gobj() != nullptr) {
|
||||
// An icon specified by path and filename may be the wrong size for
|
||||
// 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
|
||||
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);
|
||||
image.set(pixbuf);
|
||||
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
|
||||
|
||||
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
|
||||
image.set(surface);
|
||||
}
|
||||
} 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) {
|
||||
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
|
||||
}
|
||||
} else if (icon_pixmap) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
@ -360,4 +427,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
|
||||
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
|
||||
|
@ -1,10 +1,28 @@
|
||||
#include "modules/sway/language.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <json/json.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 {
|
||||
|
||||
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)
|
||||
: ALabel(config, "language", id, "{}", 0, true) {
|
||||
is_variant_displayed = format_.find("{variant}") != std::string::npos;
|
||||
if (config.isMember("tooltip-format")) {
|
||||
tooltip_format_ = config["tooltip-format"].asString();
|
||||
}
|
||||
ipc_.subscribe(R"(["input"])");
|
||||
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
|
||||
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
|
||||
@ -21,18 +39,31 @@ Language::Language(const std::string& id, const Json::Value& config)
|
||||
}
|
||||
|
||||
void Language::onCmd(const struct Ipc::ipc_response& res) {
|
||||
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
auto payload = parser_.parse(res.payload);
|
||||
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK
|
||||
Json::Value::ArrayIndex maxId = 0, max = 0;
|
||||
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) {
|
||||
if(payload[i]["xkb_layout_names"].size() > max) {
|
||||
max = payload[i]["xkb_layout_names"].size();
|
||||
maxId = i;
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto payload = parser_.parse(res.payload);
|
||||
std::vector<std::string> used_layouts;
|
||||
// Display current layout of a device with a maximum count of layouts, expecting that all will
|
||||
// be OK
|
||||
Json::ArrayIndex max_id = 0, max = 0;
|
||||
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();
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Language: {}", e.what());
|
||||
@ -40,12 +71,15 @@ void Language::onCmd(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 {
|
||||
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") {
|
||||
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2);
|
||||
lang_ = Glib::Markup::escape_text(layout_name);
|
||||
set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
|
||||
}
|
||||
dp.emit();
|
||||
} catch (const std::exception& e) {
|
||||
@ -54,17 +88,102 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
|
||||
}
|
||||
|
||||
auto Language::update() -> void {
|
||||
if (lang_.empty()) {
|
||||
event_box_.hide();
|
||||
} else {
|
||||
label_.set_markup(fmt::format(format_, lang_));
|
||||
if (tooltipEnabled()) {
|
||||
label_.set_tooltip_text(lang_);
|
||||
}
|
||||
event_box_.show();
|
||||
auto display_layout = trim(fmt::format(format_,
|
||||
fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("long", layout_.full_name),
|
||||
fmt::arg("variant", layout_.variant)));
|
||||
label_.set_markup(display_layout);
|
||||
if (tooltipEnabled()) {
|
||||
if (tooltip_format_ != "") {
|
||||
auto tooltip_display_layout = trim(fmt::format(tooltip_format_,
|
||||
fmt::arg("short", layout_.short_name),
|
||||
fmt::arg("long", layout_.full_name),
|
||||
fmt::arg("variant", layout_.variant)));
|
||||
label_.set_tooltip_markup(tooltip_display_layout);
|
||||
|
||||
} else {
|
||||
label_.set_tooltip_markup(display_layout);
|
||||
}
|
||||
}
|
||||
|
||||
event_box_.show();
|
||||
|
||||
// Call parent 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
|
||||
|
Loading…
Reference in New Issue
Block a user