Merge branch 'master' of https://github.com/Alexays/Waybar into workspace-manager-implementation

This commit is contained in:
dmitry 2021-11-16 21:44:50 +03:00
commit 15761ef802
132 changed files with 5444 additions and 1481 deletions

24
.github/workflows/freebsd.yml vendored Normal file
View File

@ -0,0 +1,24 @@
name: freebsd
on: [ push, pull_request ]
jobs:
clang:
runs-on: macos-latest # until https://github.com/actions/runner/issues/385
steps:
- uses: actions/checkout@v2
- name: Test in FreeBSD VM
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 catch 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
meson test -C build --no-rebuild --print-errorlogs --suite waybar

27
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,27 @@
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
- name: test
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar

View File

@ -1,36 +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:
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
script:
- meson build -Dman-pages=enabled
- ninja -C build

View File

@ -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 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

View File

@ -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 --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

View File

@ -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 && \
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

View File

@ -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

View File

@ -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

View File

@ -16,5 +16,8 @@ install: build
run: build
./build/waybar
debug-run: build-debug
./build/waybar --log-level debug
clean:
rm -rf build

View File

@ -1,11 +1,11 @@
# Waybar [![Travis](https://travis-ci.org/Alexays/Waybar.svg?branch=master)](https://travis-ci.org/Alexays/Waybar) [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
# Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
**Current features**
#### Current features
- Sway (Workspaces, Binding mode, Focused window name)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time
@ -22,11 +22,21 @@
- Multiple output configuration
- And much more customizations
**Configuration and Styling**
#### Configuration and Styling
[See the wiki for more details](https://github.com/Alexays/Waybar/wiki).
**How to build**
### Installation
Waybar is available from a number of Linux distributions:
[![Packaging status](https://repology.org/badge/vertical-allrepos/waybar.svg)](https://repology.org/project/waybar/versions)
An Ubuntu PPA with more recent versions is available
[here](https://launchpad.net/~nschloe/+archive/ubuntu/waybar).
#### Building from source
```bash
$ git clone https://github.com/Alexays/Waybar
@ -57,6 +67,8 @@ libnl [Network module]
libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module]
libsndio [sndio module]
libevdev [KeyboardState module]
```
**Build dependencies**
@ -75,6 +87,7 @@ sudo apt install \
clang-tidy \
gobject-introspection \
libdbusmenu-gtk3-dev \
libevdev-dev \
libfmt-dev \
libgirepository1.0-dev \
libgtk-3-dev \

View File

@ -10,16 +10,15 @@ namespace waybar {
class ALabel : public AModule {
public:
ALabel(const Json::Value &, const std::string &, const std::string &, const std::string &format,
uint16_t interval = 0, bool ellipsize = false);
uint16_t interval = 0, bool ellipsize = false, bool enable_click = false, bool enable_scroll = false);
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_;
std::string format_;
std::string click_param;
const std::chrono::seconds interval_;
bool alt_ = false;
std::string default_format_;

View File

@ -7,9 +7,8 @@
#include <gtkmm/main.h>
#include <gtkmm/window.h>
#include <json/json.h>
#include "AModule.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar {
@ -18,65 +17,68 @@ class Factory;
struct waybar_output {
Glib::RefPtr<Gdk::Monitor> monitor;
std::string name;
std::string identifier;
std::unique_ptr<struct zxdg_output_v1, decltype(&zxdg_output_v1_destroy)> xdg_output = {
nullptr, &zxdg_output_v1_destroy};
};
enum class bar_layer : uint8_t {
BOTTOM,
TOP,
OVERLAY,
};
struct bar_margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
};
class BarSurface {
protected:
BarSurface() = default;
public:
virtual void setExclusiveZone(bool enable) = 0;
virtual void setLayer(bar_layer layer) = 0;
virtual void setMargins(const struct bar_margins &margins) = 0;
virtual void setPassThrough(bool enable) = 0;
virtual void setPosition(const std::string_view &position) = 0;
virtual void setSize(uint32_t width, uint32_t height) = 0;
virtual void commit(){};
virtual ~BarSurface() = default;
};
class Bar {
public:
Bar(struct waybar_output *w_output, const Json::Value &);
Bar(const Bar &) = delete;
~Bar() = default;
auto toggle() -> void;
void setVisible(bool visible);
void toggle();
void handleSignal(int);
struct waybar_output *output;
Json::Value config;
struct wl_surface * surface;
bool exclusive = true;
bool visible = true;
bool vertical = false;
Gtk::Window window;
private:
static constexpr const char *MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules";
static constexpr const char *MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules";
static constexpr const char *BAR_SIZE_MSG =
"Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char *SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
static void layerSurfaceHandleConfigure(void *, struct zwlr_layer_surface_v1 *, uint32_t,
uint32_t, uint32_t);
static void layerSurfaceHandleClosed(void *, struct zwlr_layer_surface_v1 *);
#ifdef HAVE_GTK_LAYER_SHELL
void initGtkLayerShell();
#endif
void onConfigure(GdkEventConfigure *ev);
void onRealize();
void onMap(GdkEventAny *ev);
void setExclusiveZone(uint32_t width, uint32_t height);
void setSurfaceSize(uint32_t width, uint32_t height);
void onMap(GdkEventAny *);
auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &);
void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name);
struct margins {
int top = 0;
int right = 0;
int bottom = 0;
int left = 0;
} margins_;
struct zwlr_layer_surface_v1 *layer_surface_;
// use gtk-layer-shell instead of handling layer surfaces directly
bool use_gls_ = false;
uint32_t width_ = 0;
uint32_t height_ = 1;
uint8_t anchor_;
std::unique_ptr<BarSurface> surface_impl_;
bar_layer layer_;
Gtk::Box left_;
Gtk::Box center_;
Gtk::Box right_;

View File

@ -3,10 +3,14 @@
#include <fmt/format.h>
#include <gdk/gdk.h>
#include <gdk/gdkwayland.h>
#include <unistd.h>
#include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp"
#include "config.hpp"
struct zwlr_layer_shell_v1;
struct zwp_idle_inhibitor_v1;
struct zwp_idle_inhibit_manager_v1;
namespace waybar {
@ -14,6 +18,7 @@ class Client {
public:
static Client *inst();
int main(int argc, char *argv[]);
void reset();
Glib::RefPtr<Gtk::Application> gtk_app;
Glib::RefPtr<Gdk::Display> gdk_display;
@ -23,28 +28,27 @@ class Client {
struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars;
Config config;
private:
Client() = default;
std::tuple<const std::string, const std::string> getConfigs(const std::string &config,
const std::string &style) const;
void bindInterfaces();
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 setupCss(const std::string &css_file) -> void;
struct waybar_output &getOutput(void *);
const std::string getStyle(const std::string &style);
void bindInterfaces();
void handleOutput(struct waybar_output &output);
auto setupCss(const std::string &css_file) -> void;
struct waybar_output & getOutput(void *);
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
static void handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version);
static void handleGlobalRemove(void *data, struct wl_registry *registry, uint32_t name);
static void handleOutputDone(void *, struct zxdg_output_v1 *);
static void handleOutputName(void *, struct zxdg_output_v1 *, const char *);
static void handleOutputDescription(void *, struct zxdg_output_v1 *, const char *);
void handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor);
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_;
std::list<struct waybar_output> outputs_;

39
include/config.hpp Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <json/json.h>
#include <optional>
#include <string>
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
namespace waybar {
class Config {
public:
static const std::vector<std::string> CONFIG_DIRS;
/* Try to find any of provided names in the supported set of config directories */
static std::optional<std::string> findConfigPath(
const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS);
Config() = default;
void load(const std::string &config);
Json::Value &getConfig() { return config_; }
std::vector<Json::Value> getOutputConfigs(const std::string &name, const std::string &identifier);
private:
void setupConfig(Json::Value &dst, const std::string &config_file, int depth);
void resolveConfigIncludes(Json::Value &config, int depth);
void mergeConfig(Json::Value &a_config_, Json::Value &b_config_);
std::string config_file_;
Json::Value config_;
};
} // namespace waybar

View File

@ -1,11 +1,16 @@
#pragma once
#include <json/json.h>
#ifdef HAVE_LIBDATE
#include "modules/clock.hpp"
#else
#include "modules/simpleclock.hpp"
#endif
#ifdef HAVE_SWAY
#include "modules/sway/mode.hpp"
#include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#include "modules/sway/language.hpp"
#endif
#ifdef HAVE_WLR
#include "modules/wlr/taskbar.hpp"
@ -34,17 +39,25 @@
#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
#ifdef HAVE_LIBMPDCLIENT
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#endif
#ifdef HAVE_LIBSNDIO
#include "modules/sndio.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/temperature.hpp"
#if defined(__linux__)
#include "modules/bluetooth.hpp"
# ifdef WANT_RFKILL
# include "modules/bluetooth.hpp"
# endif
#endif
namespace waybar {

View File

@ -31,19 +31,22 @@ class Battery : public ALabel {
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
void getBatteries();
void worker();
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, float, std::string> getInfos() const;
const std::string formatTimeRemaining(float hoursRemaining);
void refreshBatteries();
void worker();
const std::string getAdapterStatus(uint8_t capacity) const;
const std::tuple<uint8_t, float, std::string, float> getInfos();
const std::string formatTimeRemaining(float hoursRemaining);
std::vector<fs::path> batteries_;
int global_watch;
std::map<fs::path,int> batteries_;
fs::path adapter_;
int fd_;
std::vector<int> wds_;
int battery_watch_fd_;
int global_watch_fd_;
std::mutex battery_list_mutex_;
std::string old_status_;
util::SleeperThread thread_;
util::SleeperThread thread_battery_update_;
util::SleeperThread thread_timer_;
};

View File

@ -1,10 +1,6 @@
#pragma once
#include <fmt/format.h>
#include "ALabel.hpp"
#include <fmt/chrono.h>
#include "util/sleeper_thread.hpp"
#include "util/rfkill.hpp"
namespace waybar::modules {
@ -16,10 +12,6 @@ class Bluetooth : public ALabel {
auto update() -> void;
private:
std::string status_;
util::SleeperThread thread_;
util::SleeperThread intervall_thread_;
util::Rfkill rfkill_;
};

View File

@ -28,12 +28,16 @@ class Clock : public ALabel {
std::locale locale_;
const date::time_zone* time_zone_;
bool fixed_time_zone_;
date::year_month_day cached_calendar_ymd_;
int time_zone_idx_;
date::year_month_day cached_calendar_ymd_ = date::January/1/0;
std::string cached_calendar_text_;
bool handleScroll(GdkEventScroll* e);
auto calendar_text(const waybar_time& wtime) -> std::string;
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday;
bool setTimeZone(Json::Value zone_name);
};
} // namespace waybar::modules

View File

@ -1,7 +1,6 @@
#pragma once
#include <fmt/format.h>
#include <unistd.h>
#include <cstdint>
#include <fstream>
#include <numeric>
@ -20,9 +19,11 @@ class Cpu : public ALabel {
auto update() -> void;
private:
uint16_t getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
double getCpuLoad();
std::tuple<std::vector<uint16_t>, std::string> getCpuUsage();
std::tuple<float, float, float> getCpuFrequency();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<float> parseCpuFrequencies();
std::vector<std::tuple<size_t, size_t>> prev_times_;

View File

@ -22,6 +22,7 @@ class Custom : public ALabel {
void continuousWorker();
void parseOutputRaw();
void parseOutputJson();
void handleEvent();
bool handleScroll(GdkEventScroll* e);
bool handleToggle(GdkEventButton* const& e);

View File

@ -12,12 +12,13 @@ class IdleInhibitor : public ALabel {
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~IdleInhibitor();
auto update() -> void;
static std::list<waybar::AModule*> modules;
static bool status;
private:
bool handleToggle(GdkEventButton* const& e);
const Bar& bar_;
std::string status_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_;
int pid_;
};

View 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

View File

@ -1,74 +0,0 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD : public ALabel {
public:
MPD(const std::string&, const Json::Value&);
auto update() -> void;
private:
std::thread periodic_updater();
std::string getTag(mpd_tag_type type, unsigned idx = 0);
void setLabel();
std::string getStateIcon();
std::string getOptionIcon(std::string optionName, bool activated);
std::thread event_listener();
// Assumes `connection_lock_` is locked
void tryConnect();
// If checking errors on the main connection, make sure to lock it using
// `connection_lock_` before calling checkErrors
void checkErrors(mpd_connection* conn);
// Assumes `connection_lock_` is locked
void fetchState();
void waitForEvent();
bool handlePlayPause(GdkEventButton* const&);
bool stopped();
bool playing();
bool paused();
const std::string module_name_;
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
unsigned timeout_;
// We need a mutex here because we can trigger updates from multiple thread:
// the event based updates, the periodic updates needed for the elapsed time,
// and the click play/pause feature
std::mutex connection_lock_;
unique_connection connection_;
// The alternate connection will be used to wait for events: since it will
// be blocking (idle) we can't send commands via this connection
//
// No lock since only used in the event listener thread
unique_connection alternate_connection_;
// Protect them using the `connection_lock_`
unique_status status_;
mpd_state state_;
unique_song song_;
// To make sure the previous periodic_updater stops before creating a new one
std::mutex periodic_lock_;
};
} // namespace waybar::modules

View File

@ -0,0 +1,67 @@
#pragma once
#include <fmt/format.h>
#include <mpd/client.h>
#include <spdlog/spdlog.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
#include "modules/mpd/state.hpp"
namespace waybar::modules {
class MPD : public ALabel {
friend class detail::Context;
// State machine
detail::Context context_{this};
const std::string module_name_;
// Not using unique_ptr since we don't manage the pointer
// (It's either nullptr, or from the config)
const char* server_;
const unsigned port_;
const std::string password_;
unsigned timeout_;
detail::unique_connection connection_;
detail::unique_status status_;
mpd_state state_;
detail::unique_song song_;
public:
MPD(const std::string&, const Json::Value&);
virtual ~MPD() noexcept = default;
auto update() -> void;
private:
std::string getTag(mpd_tag_type type, unsigned idx = 0) const;
void setLabel();
std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const;
// GUI-side methods
bool handlePlayPause(GdkEventButton* const&);
void emit() { dp.emit(); }
// MPD-side, Non-GUI methods.
void tryConnect();
void checkErrors(mpd_connection* conn);
void fetchState();
void queryMPD();
inline bool stopped() const { return connection_ && state_ == MPD_STATE_STOP; }
inline bool playing() const { return connection_ && state_ == MPD_STATE_PLAY; }
inline bool paused() const { return connection_ && state_ == MPD_STATE_PAUSE; }
};
#if !defined(MPD_NOINLINE)
#include "modules/mpd/state.inl.hpp"
#endif
} // namespace waybar::modules

View File

@ -0,0 +1,217 @@
#pragma once
#include <mpd/client.h>
#include <fmt/format.h>
#include <spdlog/spdlog.h>
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
namespace waybar::modules {
class MPD;
} // namespace waybar::modules
namespace waybar::modules::detail {
using unique_connection = std::unique_ptr<mpd_connection, decltype(&mpd_connection_free)>;
using unique_status = std::unique_ptr<mpd_status, decltype(&mpd_status_free)>;
using unique_song = std::unique_ptr<mpd_song, decltype(&mpd_song_free)>;
class Context;
/// This state machine loosely follows a non-hierarchical, statechart
/// pattern, and includes ENTRY and EXIT actions.
///
/// The State class is the base class for all other states. The
/// entry and exit methods are automatically called when entering
/// into a new state and exiting from the current state. This
/// includes initially entering (Disconnected class) and exiting
/// Waybar.
///
/// The following nested "top-level" states are represented:
/// 1. Idle - await notification of MPD activity.
/// 2. All Non-Idle states:
/// 1. Playing - An active song is producing audio output.
/// 2. Paused - The current song is paused.
/// 3. Stopped - No song is actively playing.
/// 3. Disconnected - periodically attempt MPD (re-)connection.
///
/// NOTE: Since this statechart is non-hierarchical, the above
/// states are flattened into a set.
class State {
public:
virtual ~State() noexcept = default;
virtual void entry() noexcept { spdlog::debug("mpd: ignore entry action"); }
virtual void exit() noexcept { spdlog::debug("mpd: ignore exit action"); }
virtual void play() { spdlog::debug("mpd: ignore play state transition"); }
virtual void stop() { spdlog::debug("mpd: ignore stop state transition"); }
virtual void pause() { spdlog::debug("mpd: ignore pause state transition"); }
/// Request state update the GUI.
virtual void update() noexcept { spdlog::debug("mpd: ignoring update method request"); }
};
class Idle : public State {
Context* const ctx_;
sigc::connection idle_connection_;
public:
Idle(Context* const ctx) : ctx_{ctx} {}
virtual ~Idle() noexcept { this->exit(); };
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void stop() override;
void pause() override;
void update() noexcept override;
private:
Idle(const Idle&) = delete;
Idle& operator=(const Idle&) = delete;
bool on_io(Glib::IOCondition const&);
};
class Playing : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Playing(Context* const ctx) : ctx_{ctx} {}
virtual ~Playing() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void pause() override;
void stop() override;
void update() noexcept override;
private:
Playing(Playing const&) = delete;
Playing& operator=(Playing const&) = delete;
bool on_timer();
};
class Paused : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Paused(Context* const ctx) : ctx_{ctx} {}
virtual ~Paused() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void stop() override;
void update() noexcept override;
private:
Paused(Paused const&) = delete;
Paused& operator=(Paused const&) = delete;
bool on_timer();
};
class Stopped : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Stopped(Context* const ctx) : ctx_{ctx} {}
virtual ~Stopped() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void play() override;
void pause() override;
void update() noexcept override;
private:
Stopped(Stopped const&) = delete;
Stopped& operator=(Stopped const&) = delete;
bool on_timer();
};
class Disconnected : public State {
Context* const ctx_;
sigc::connection timer_connection_;
public:
Disconnected(Context* const ctx) : ctx_{ctx} {}
virtual ~Disconnected() noexcept { this->exit(); }
void entry() noexcept override;
void exit() noexcept override;
void update() noexcept override;
private:
Disconnected(Disconnected const&) = delete;
Disconnected& operator=(Disconnected const&) = delete;
void arm_timer(int interval) noexcept;
void disarm_timer() noexcept;
bool on_timer();
};
class Context {
std::unique_ptr<State> state_;
waybar::modules::MPD* mpd_module_;
friend class State;
friend class Playing;
friend class Paused;
friend class Stopped;
friend class Disconnected;
friend class Idle;
protected:
void setState(std::unique_ptr<State>&& new_state) noexcept {
if (state_.get() != nullptr) {
state_->exit();
}
state_ = std::move(new_state);
state_->entry();
}
bool is_connected() const;
bool is_playing() const;
bool is_paused() const;
bool is_stopped() const;
constexpr std::size_t interval() const;
void tryConnect() const;
void checkErrors(mpd_connection*) const;
void do_update();
void queryMPD() const;
void fetchState() const;
constexpr mpd_state state() const;
void emit() const;
[[nodiscard]] unique_connection& connection();
public:
explicit Context(waybar::modules::MPD* const mpd_module)
: state_{std::make_unique<Disconnected>(this)}, mpd_module_{mpd_module} {
state_->entry();
}
void play() { state_->play(); }
void stop() { state_->stop(); }
void pause() { state_->pause(); }
void update() noexcept { state_->update(); }
};
} // namespace waybar::modules::detail

View File

@ -0,0 +1,24 @@
#pragma once
namespace detail {
inline bool Context::is_connected() const { return mpd_module_->connection_ != nullptr; }
inline bool Context::is_playing() const { return mpd_module_->playing(); }
inline bool Context::is_paused() const { return mpd_module_->paused(); }
inline bool Context::is_stopped() const { return mpd_module_->stopped(); }
constexpr inline std::size_t Context::interval() const { return mpd_module_->interval_.count(); }
inline void Context::tryConnect() const { mpd_module_->tryConnect(); }
inline unique_connection& Context::connection() { return mpd_module_->connection_; }
constexpr inline mpd_state Context::state() const { return mpd_module_->state_; }
inline void Context::do_update() {
mpd_module_->setLabel();
}
inline void Context::checkErrors(mpd_connection* conn) const { mpd_module_->checkErrors(conn); }
inline void Context::queryMPD() const { mpd_module_->queryMPD(); }
inline void Context::fetchState() const { mpd_module_->fetchState(); }
inline void Context::emit() const { mpd_module_->emit(); }
} // namespace detail

View File

@ -2,16 +2,16 @@
#include <arpa/inet.h>
#include <fmt/format.h>
#include <ifaddrs.h>
#include <linux/nl80211.h>
#include <net/if.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>
#include <netlink/netlink.h>
#include <sys/epoll.h>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
namespace waybar::modules {
@ -26,23 +26,20 @@ class Network : public ALabel {
static const uint8_t EPOLL_MAX = 200;
static int handleEvents(struct nl_msg*, void*);
static int handleEventsDone(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*);
void askForStateDump(void);
void worker();
void createInfoSocket();
void createEventSocket();
int getExternalInterface(int skip_idx = -1) const;
void getInterfaceAddress();
int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const;
int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const;
void parseEssid(struct nlattr**);
void parseSignal(struct nlattr**);
void parseFreq(struct nlattr**);
bool associatedOrJoined(struct nlattr**);
bool checkInterface(struct ifinfomsg* rtif, std::string name);
int getPreferredIface(int skip_idx = -1, bool wait = true) const;
bool checkInterface(std::string name);
auto getInfo() -> void;
void checkNewInterface(struct ifinfomsg* rtif);
const std::string getNetworkState() const;
void clearIface();
bool wildcardMatch(const std::string& pattern, const std::string& text) const;
@ -52,27 +49,37 @@ class Network : public ALabel {
struct sockaddr_nl nladdr_ = {0};
struct nl_sock* sock_ = nullptr;
struct nl_sock* ev_sock_ = nullptr;
int efd_;
int ev_fd_;
int nl80211_id_;
std::mutex mutex_;
bool want_route_dump_;
bool want_link_dump_;
bool want_addr_dump_;
bool dump_in_progress_;
unsigned long long bandwidth_down_total_;
unsigned long long bandwidth_up_total_;
std::string state_;
std::string essid_;
bool carrier_;
std::string ifname_;
std::string ipaddr_;
std::string gwaddr_;
std::string netmask_;
int cidr_;
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
uint32_t frequency_;
uint32_t route_priority;
util::SleeperThread thread_;
util::SleeperThread thread_timer_;
util::SleeperThread thread_rfkill_;
#ifdef WANT_RFKILL
util::Rfkill rfkill_;
#endif
};
} // namespace waybar::modules

View File

@ -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_;

View File

@ -18,6 +18,7 @@ class Tags : public waybar::AModule {
// Handlers for wayland events
void handle_focused_tags(uint32_t tags);
void handle_view_tags(struct wl_array *tags);
void handle_urgent_tags(uint32_t tags);
struct zriver_status_manager_v1 *status_manager_;

View File

@ -0,0 +1,24 @@
#pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Clock : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;
auto update() -> void;
private:
util::SleeperThread thread_;
};
} // namespace waybar::modules

30
include/modules/sndio.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <sndio.h>
#include <vector>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Sndio : public ALabel {
public:
Sndio(const std::string&, const Json::Value&);
~Sndio();
auto update() -> void;
auto set_desc(struct sioctl_desc *, unsigned int) -> void;
auto put_val(unsigned int, unsigned int) -> void;
bool handleScroll(GdkEventScroll *);
bool handleToggle(GdkEventButton* const&);
private:
auto connect_to_sndio() -> void;
util::SleeperThread thread_;
struct sioctl_hdl *hdl_;
std::vector<struct pollfd> pfds_;
unsigned int addr_;
unsigned int volume_, old_volume_, maxval_;
bool muted_;
};
} // namespace waybar::modules

View File

@ -5,13 +5,15 @@
#include <glibmm/refptr.h>
#include <json/json.h>
#include <tuple>
#include "bar.hpp"
#include "modules/sni/item.hpp"
namespace waybar::modules::SNI {
class Host {
public:
Host(const std::size_t id, const Json::Value&, const std::function<void(std::unique_ptr<Item>&)>&,
Host(const std::size_t id, const Json::Value&, const Bar&,
const std::function<void(std::unique_ptr<Item>&)>&,
const std::function<void(std::unique_ptr<Item>&)>&);
~Host();
@ -36,6 +38,7 @@ class Host {
GCancellable* cancellable_ = nullptr;
SnWatcher* watcher_ = nullptr;
const Json::Value& config_;
const Bar& bar_;
const std::function<void(std::unique_ptr<Item>&)> on_add_;
const std::function<void(std::unique_ptr<Item>&)> on_remove_;
};

View File

@ -11,11 +11,21 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h>
#include <set>
#include <string_view>
#include "bar.hpp"
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&);
Item(const std::string&, const std::string&, const Json::Value&, const Bar&);
~Item() = default;
std::string bus_name;
@ -27,10 +37,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 +47,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;
/**
@ -49,8 +58,10 @@ class Item : public sigc::trackable {
bool item_is_menu = true;
private:
void onConfigure(GdkEventConfigure* ev);
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,
@ -58,14 +69,24 @@ class Item : public sigc::trackable {
void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
double getScaledIconSize();
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

View File

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

View File

@ -0,0 +1,69 @@
#pragma once
#include <fmt/format.h>
#include <xkbcommon/xkbregistry.h>
#include <map>
#include <string>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
namespace waybar::modules::sway {
class Language : public ALabel, public sigc::trackable {
public:
Language(const std::string& id, const Json::Value& config);
~Language() = default;
auto update() -> void;
private:
enum class DispayedShortFlag {
None = 0,
ShortName = 1,
ShortDescription = 1 << 1
};
struct Layout {
std::string full_name;
std::string short_name;
std::string variant;
std::string short_description;
};
class XKBContext {
public:
XKBContext();
~XKBContext();
auto next_layout() -> Layout*;
private:
rxkb_context* context_ = nullptr;
rxkb_layout* xkb_layout_ = nullptr;
Layout* layout_ = nullptr;
std::map<std::string, rxkb_layout*> base_layouts_by_name_;
};
void onEvent(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;
Layout layout_;
std::string tooltip_format_ = "";
std::map<std::string, Layout> layouts_map_;
bool is_variant_displayed;
std::byte displayed_short_flag = static_cast<std::byte>(DispayedShortFlag::None);
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
};
} // namespace waybar::modules::sway

View File

@ -22,6 +22,7 @@ class Window : public ALabel, public sigc::trackable {
std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes,
std::string& output);
void getTree();
std::string rewriteTitle(const std::string& title);
const Bar& bar_;
std::string window_;

View File

@ -20,7 +20,7 @@ class Workspaces : public AModule, public sigc::trackable {
auto update() -> void;
private:
static inline const std::string workspace_switch_cmd_ = "workspace --no-auto-back-and-forth \"{}\"";
static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\"";
static int convertWorkspaceNameToNum(std::string name);

View File

@ -8,6 +8,7 @@
#include <memory>
#include <string>
#include <vector>
#include <unordered_set>
#include <gdk/gdk.h>
@ -61,6 +62,7 @@ class Task
Gtk::Label text_before_;
Gtk::Label text_after_;
bool button_visible_;
bool ignored_;
bool with_icon_;
std::string format_before_;
@ -70,7 +72,7 @@ class Task
std::string title_;
std::string app_id_;
uint32_t state_;
uint32_t state_ = 0;
private:
std::string repr() const;
@ -132,6 +134,7 @@ class Taskbar : public waybar::AModule
std::vector<TaskPtr> tasks_;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
std::unordered_set<std::string> ignore_list_;
struct zwlr_foreign_toplevel_manager_v1 *manager_;
struct wl_seat *seat_;
@ -155,6 +158,7 @@ class Taskbar : public waybar::AModule
bool all_outputs() const;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const;
const std::unordered_set<std::string>& ignore_list() const;
};
} /* namespace waybar::modules::wlr */

View File

@ -5,8 +5,18 @@
#include <sys/wait.h>
#include <unistd.h>
#ifdef __linux__
#include <sys/prctl.h>
#endif
#ifdef __FreeBSD__
#include <sys/procctl.h>
#endif
#include <array>
extern std::mutex reap_mtx;
extern std::list<pid_t> reap;
namespace waybar::util::command {
struct res {
@ -32,10 +42,11 @@ inline std::string read(FILE* fp) {
inline int close(FILE* fp, pid_t pid) {
int stat = -1;
pid_t ret;
fclose(fp);
do {
waitpid(pid, &stat, WCONTINUED | WUNTRACED);
ret = waitpid(pid, &stat, WCONTINUED | WUNTRACED);
if (WIFEXITED(stat)) {
spdlog::debug("Cmd exited with code {}", WEXITSTATUS(stat));
@ -45,6 +56,8 @@ inline int close(FILE* fp, pid_t pid) {
spdlog::debug("Cmd stopped by {}", WSTOPSIG(stat));
} else if (WIFCONTINUED(stat)) {
spdlog::debug("Cmd continued");
} else if (ret == -1) {
spdlog::debug("waitpid failed: {}", strerror(errno));
} else {
break;
}
@ -65,6 +78,24 @@ inline FILE* open(const std::string& cmd, int& pid) {
}
if (!child_pid) {
int err;
sigset_t mask;
sigfillset(&mask);
// Reset sigmask
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in open failed: {}", strerror(err));
// Kill child if Waybar exits
int deathsig = SIGTERM;
#ifdef __linux__
if (prctl(PR_SET_PDEATHSIG, deathsig) != 0) {
spdlog::error("prctl(PR_SET_PDEATHSIG) in open failed: {}", strerror(errno));
}
#endif
#ifdef __FreeBSD__
if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, reinterpret_cast<void*>(&deathsig)) == -1) {
spdlog::error("procctl(PROC_PDEATHSIG_CTL) in open failed: {}", strerror(errno));
}
#endif
::close(fd[0]);
dup2(fd[1], 1);
setpgid(child_pid, child_pid);
@ -97,7 +128,7 @@ inline struct res execNoRead(const std::string& cmd) {
inline int32_t forkExec(const std::string& cmd) {
if (cmd == "") return -1;
int32_t pid = fork();
pid_t pid = fork();
if (pid < 0) {
spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno));
@ -106,12 +137,20 @@ inline int32_t forkExec(const std::string& cmd) {
// Child executes the command
if (!pid) {
int err;
sigset_t mask;
sigfillset(&mask);
// Reset sigmask
err = pthread_sigmask(SIG_UNBLOCK, &mask, nullptr);
if (err != 0) spdlog::error("pthread_sigmask in forkExec failed: {}", strerror(err));
setpgid(pid, pid);
signal(SIGCHLD, SIG_DFL);
execl("/bin/sh", "sh", "-c", cmd.c_str(), (char*)0);
exit(0);
} else {
signal(SIGCHLD, SIG_IGN);
reap_mtx.lock();
reap.push_back(pid);
reap_mtx.unlock();
spdlog::debug("Added child to reap list: {}", pid);
}
return pid;

View File

@ -23,7 +23,7 @@ namespace fmt {
constexpr auto parse(ParseContext& ctx) -> decltype (ctx.begin()) {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == ':') ++it;
if (*it == '>' || *it == '<' || *it == '=') {
if (it && (*it == '>' || *it == '<' || *it == '=')) {
spec = *it;
++it;
}
@ -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;
}

View File

@ -1,19 +1,26 @@
#pragma once
#include <glibmm/iochannel.h>
#include <linux/rfkill.h>
#include <sigc++/signal.h>
#include <sigc++/trackable.h>
namespace waybar::util {
class Rfkill {
class Rfkill : public sigc::trackable {
public:
Rfkill(enum rfkill_type rfkill_type);
~Rfkill() = default;
void waitForEvent();
~Rfkill();
bool getState() const;
sigc::signal<void(struct rfkill_event&)> on_update;
private:
enum rfkill_type rfkill_type_;
int state_ = 0;
bool state_ = false;
int fd_ = -1;
bool on_event(Glib::IOCondition cond);
};
} // namespace waybar::util

View File

@ -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
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

@ -0,0 +1,5 @@
#pragma once
#include <glibmm/ustring.h>
// calculate column width of ustring
int ustring_clen(const Glib::ustring &str);

View File

@ -24,6 +24,14 @@ The *backlight* module displays the current backlight level.
typeof: integer ++
The maximum length in characters 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.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.

View File

@ -20,7 +20,12 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*full-at*: ++
typeof: integer ++
Define the max percentage of the battery, useful for an old battery, e.g. 96
Define the max percentage of the battery, for when you've set the battery to stop charging at a lower level to save it. For example, if you've set the battery to stop at 80% that will become the new 100%.
*design-capacity*: ++
typeof: bool ++
default: false ++
Option to use the battery design capacity instead of it's current maximal capacity.
*interval*: ++
typeof: integer ++
@ -50,6 +55,14 @@ The *battery* module displays the current capacity and state (eg. charging) of y
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.
*rotate*: ++
typeof: integer++
Positive value to rotate the text label.
@ -91,6 +104,8 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*{capacity}*: Capacity in percentage
*{power}*: Power in watts
*{icon}*: Icon, as defined in *format-icons*.
*{time}*: Estimate of time until full or empty. Note that this is based on the power draw at the last refresh time, not an average.
@ -105,7 +120,7 @@ The two arguments are:
# CUSTOM FORMATS
The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected.
The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected.
*format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery.

View File

@ -12,11 +12,6 @@ The *bluetooth* module displays information about the status of the device's blu
Addressed by *bluetooth*
*interval*: ++
typeof: integer ++
default: 60 ++
The interval in which the bluetooth state gets updated.
*format*: ++
typeof: string ++
default: *{icon}* ++
@ -35,6 +30,14 @@ Addressed by *bluetooth*
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.
@ -80,12 +83,11 @@ Addressed by *bluetooth*
"bluetooth": {
"format": "{icon}",
"format-alt": "bluetooth: {status}",
"interval": 30,
"format-icons": {
"enabled": "",
"disabled": ""
},
"tooltip-format": "{status}"
"tooltip-format": "{}"
}
```

View File

@ -24,17 +24,36 @@ The *clock* module displays the current date and time.
*timezone*: ++
typeof: string ++
default: inferred local timezone ++
The timezone to display the time in, e.g. America/New_York.
The timezone to display the time in, e.g. America/New_York. ++
This field will be ignored if *timezones* field is set and have at least one value.
*timezones*: ++
typeof: list of strings ++
A list of timezones to use for time display, changed using the scroll wheel. ++
Use "" to represent the system's local timezone. Using %Z in the format or tooltip format is useful to track which time zone is currently displayed.
*locale*: ++
typeof: string ++
default: inferred from current locale ++
A locale to be used to display the time. Intended to render times in custom timezones with the proper language and format.
*today-format*: ++
typeof: string ++
default: <b><u>{}</u></b> ++
The format of today's date in the calendar.
*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.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.

View File

@ -20,10 +20,23 @@ The *cpu* module displays the current cpu utilization.
default: {usage}% ++
The format, how information should be displayed. On {} data gets inserted.
*format-icons*: ++
typeof: array/object ++
Based on the current usage, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*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.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -69,9 +82,23 @@ The *cpu* module displays the current cpu utilization.
*{load}*: Current cpu load.
*{usage}*: Current cpu usage.
*{usage}*: Current overall cpu usage.
# EXAMPLE
*{usage*{n}*}*: Current cpu core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
*{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz.
*{max_frequency}*: Current cpu max frequency (based on the core with the highest frequency) in GHz.
*{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz.
*{icon}*: Icon for overall cpu usage.
*{icon*{n}*}*: Icon for cpu core n usage. Use like {icon0}.
# EXAMPLES
Basic configuration:
```
"cpu": {
@ -81,6 +108,16 @@ The *cpu* module displays the current cpu utilization.
}
```
Cpu usage per core rendered as icons:
```
"cpu": {
"interval": 1,
"format": "{icon0}{icon1}{icon2}{icon3} {usage:>2}% ",
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
},
```
# STYLE
- *#cpu*

View File

@ -22,6 +22,12 @@ Addressed by *custom/<name>*
The path to a script, which determines if the script in *exec* should be executed.
*exec* will be executed if the exit code of *exec-if* equals 0.
*exec-on-event*: ++
typeof: bool ++
default: true ++
If an event command is set (e.g. *on-click* or *on-scroll-up*) then re-execute the script after
executing the event command.
*return-type*: ++
typeof: string ++
See *return-type*
@ -61,6 +67,14 @@ Addressed by *custom/<name>*
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.

View File

@ -31,10 +31,22 @@ Addressed by *disk*
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*.
*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.

View File

@ -27,6 +27,14 @@ screensaving, also known as "presentation mode".
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. A click also toggles the state

View 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*

View File

@ -22,6 +22,11 @@ Addressed by *memory*
default: {percentage}% ++
The format, how information should be displayed.
*format-icons*: ++
typeof: array/object ++
Based on the current percentage, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -34,6 +39,14 @@ Addressed by *memory*
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.

View File

@ -20,6 +20,10 @@ Addressed by *mpd*
typeof: integer ++
The port MPD listens to. If empty, use the default port.
*password*: ++
typeof: string ++
The password required to connect to the MPD server. If empty, no password is sent to MPD.
*interval*: ++
typeof: integer++
default: 5 ++
@ -69,6 +73,22 @@ Addressed by *mpd*
default: "MPD (disconnected)" ++
Tooltip information displayed when the MPD server can't be reached.
*artist-len*: ++
typeof: integer ++
Maximum length of the Artist tag.
*album-len*: ++
typeof: integer ++
Maximum length of the Album tag.
*album-artist-len*: ++
typeof: integer ++
Maximum length of the Album Artist tag.
*title-len*: ++
typeof: integer ++
Maximum length of the Title tag.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -77,6 +97,14 @@ Addressed by *mpd*
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.
@ -144,10 +172,16 @@ Addressed by *mpd*
*{date}*: The date of the current song
*{volume}*: The current volume in percent
*{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration)
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration)
*{songPosition}*: The position of the current song.
*{queueLength}*: The length of the current queue.
*{stateIcon}*: The icon corresponding the playing or paused status of the player (see *state-icons* option)
*{consumeIcon}*: The icon corresponding the "consume" option (see *consume-icons* option)

View File

@ -64,6 +64,14 @@ Addressed by *network*
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.
@ -123,6 +131,8 @@ Addressed by *network*
*{ipaddr}*: The first IP of the interface.
*{gwaddr}*: The default gateway for the interface
*{netmask}*: The subnetmask corresponding to the IP.
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation.

View File

@ -50,6 +50,14 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
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.
*scroll-step*: ++
typeof: float ++
default: 1.0 ++
@ -101,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)
@ -123,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": "",

View File

@ -15,7 +15,11 @@ Addressed by *river/tags*
*num-tags*: ++
typeof: uint ++
default: 9 ++
The number of tags that should be displayed.
The number of tags that should be displayed. Max 32.
*tag-labels*: ++
typeof: array ++
The label to display for each tag.
# EXAMPLE
@ -30,8 +34,10 @@ Addressed by *river/tags*
- *#tags button*
- *#tags button.occupied*
- *#tags button.focused*
- *#tags button.urgent*
Note that a tag can be both occupied and focused at the same time.
Note that occupied/focused/urgent status may overlap. That is, a tag may be
both occupied and focused at the same time.
# SEE ALSO

91
man/waybar-sndio.5.scd Normal file
View File

@ -0,0 +1,91 @@
waybar-sndio(5)
# NAME
waybar - sndio module
# DESCRIPTION
The *sndio* module displays the current volume reported by sndio(7).
Additionally, you can control the volume by scrolling *up* or *down* while the
cursor is over the module, and clicking on the module toggles mute.
# CONFIGURATION
*format*: ++
typeof: string ++
default: {volume}% ++
The format for how information should be displayed.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*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.
*scroll-step*: ++
typeof: int ++
default: 5 ++
The speed in which to change the volume when scrolling.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
This replaces the default behaviour of toggling mute.
*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.
This replaces the default behaviour of volume control.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
This replaces the default behaviour of volume control.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.
*{raw_value}*: Volume as value reported by sndio.
# EXAMPLES
```
"sndio": {
"format": "{raw_value} 🎜",
"scroll-step": 3
}
```
# STYLE
- *#sndio*
- *#sndio.muted*

View File

@ -13,7 +13,7 @@ apply a class when the value matches the declared state value.
Each class gets activated when the current capacity is equal or below the configured *<value>*.
- Also each state can have its own *format*.
Those con be configured via *format-<name>*.
Those can be configured via *format-<name>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
# EXAMPLE

View File

@ -0,0 +1,54 @@
waybar-sway-language(5)
# NAME
waybar - sway language module
# DESCRIPTION
The *language* module displays the current keyboard layout in Sway
# CONFIGURATION
Addressed by *sway/language*
*format*: ++
typeof: string ++
default: {} ++
The format, how layout should be displayed.
*tooltip-format*: ++
typeof: string ++
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. "us"). Equals to {}.
*{shortDescription}*: Short description of layout (e.g. "en").
*{long}*: Long name of layout (e.g. "English (Dvorak)").
*{variant}*: Variant of layout (e.g. "dvorak").
# EXAMPLES
```
"sway/language": {
"format": "{}",
},
"sway/language": {
"format": "{short} {variant}",
}
```
# STYLE
- *#language*

View File

@ -25,6 +25,14 @@ Addressed by *sway/mode*
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.

View File

@ -25,6 +25,14 @@ Addressed by *sway/window*
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.
@ -58,12 +66,32 @@ Addressed by *sway/window*
default: true ++
Option to disable tooltip on hover.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
# REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are
rewrite rules if the expression matches. Rules may contain references to
captures of the expression.
Regular expression and replacement follow ECMA-script rules.
If no expression matches, the title is left unchanged.
Invalid expressions (e.g., mismatched parentheses) are skipped.
# EXAMPLES
```
"sway/window": {
"format": "{}",
"max-length": 50
"max-length": 50,
"rewrite": {
"(.*) - Mozilla Firefox": "🌎 $1",
"(.*) - zsh": "> [$1]"
}
}
```

View File

@ -31,6 +31,11 @@ Addressed by *sway/workspaces*
default: false ++
If set to false, you can scroll to cycle through workspaces. If set to true this behaviour is disabled.
*disable-click*: ++
typeof: bool ++
default: false ++
If set to false, you can click to change workspace. If set to true this behaviour is disabled.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
@ -61,12 +66,16 @@ Addressed by *sway/workspaces*
Lists workspaces that should always be shown, even when non existent
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
typeof: string ++
Command to execute when the module is updated.
*numeric-first*: ++
typeof: bool ++
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
typeof: bool ++
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
*disable-auto-back-and-forth*: ++
typeof: bool ++
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
# FORMAT REPLACEMENTS
@ -134,3 +143,4 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
- *#workspaces button.urgent*
- *#workspaces button.persistent*
- *#workspaces button.current_output*
- *#workspaces button#sway-workspace-${name}*

View File

@ -50,6 +50,11 @@ Addressed by *temperature*
typeof: array ++
Based on the current temperature (Celsius) and *critical-threshold* if available, the corresponding icon gets selected. The order is *low* to *high*.
*tooltip-format*: ++
typeof: string ++
default: {temperatureC}°C ++
The format for the tooltip
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -58,6 +63,14 @@ Addressed by *temperature*
typeof: integer ++
The maximum length in characters 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 you clicked on the module.

View File

@ -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*

View File

@ -32,6 +32,11 @@ Addressed by *wlr/taskbar*
default: 16 ++
The size of the icon.
*markup*: ++
typeof: bool ++
default: false ++
If set to true, pango markup will be accepted in format and tooltip-format.
*tooltip*: ++
typeof: bool ++
default: true ++
@ -63,6 +68,10 @@ Addressed by *wlr/taskbar*
typeof: string ++
Command to execute when the module is updated.
*ignore-list*: ++
typeof: array ++
List of app_id to be invisible.
# FORMAT REPLACEMENTS
*{icon}*: The icon of the application.
@ -78,9 +87,10 @@ Addressed by *wlr/taskbar*
# CLICK ACTIONS
*activate*: Bring the application into foreground.
*minimize*: Minimize the application.
*maximize*: Maximize the application.
*fullscreen*: Set the application to fullscreen.
*minimize*: Toggle application's minimized state.
*minimize-raise*: Bring the application into foreground or toggle its minimized state.
*maximize*: Toggle application's maximized state.
*fullscreen*: Toggle application's fullscreen state.
*close*: Close the application.
# EXAMPLES
@ -92,7 +102,10 @@ Addressed by *wlr/taskbar*
"icon-theme": "Numix-Circle",
"tooltip-format": "{title}",
"on-click": "activate",
"on-click-middle": "close"
"on-click-middle": "close",
"ignore-list": [
"Alacritty"
]
}
```

View File

@ -14,6 +14,7 @@ Valid locations for this file are:
- *~/.config/waybar/config*
- *~/waybar/config*
- */etc/xdg/waybar/config*
- *@sysconfdir@/xdg/waybar/config*
A good starting point is the default configuration found at https://github.com/Alexays/Waybar/blob/master/resources/config
Also a minimal example configuration can be found on the at the bottom of this man page.
@ -63,16 +64,37 @@ Also a minimal example configuration can be found on the at the bottom of this m
typeof: integer ++
Margins value without units.
*spacing* ++
typeof: integer ++
Size of gaps in between of the different modules.
*name* ++
typeof: string ++
Optional name added as a CSS class, for styling multiple waybars.
*exclusive* ++
typeof: bool ++
default: *true* unless the layer is set to *overlay* ++
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
*passthrough* ++
typeof: bool ++
default: *false* unless the layer is set to *overlay* ++
Option to pass any pointer events to the window under the bar.
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
*gtk-layer-shell* ++
typeof: bool ++
default: true ++
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.
Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports.
For a multi-bar config, the include directive affects only current bar configuration object.
# MODULE FORMAT
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
@ -185,14 +207,19 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-backlight(5)*
- *waybar-battery(5)*
- *waybar-bluetooth(5)*
- *waybar-clock(5)*
- *waybar-cpu(5)*
- *waybar-custom(5)*
- *waybar-disk(5)*
- *waybar-idle-inhibitor(5)*
- *waybar-keyboard-state(5)*
- *waybar-memory(5)*
- *waybar-mpd(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-river-tags(5)*
- *waybar-states(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*

View File

@ -1,7 +1,8 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.2',
version: '0.9.8',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
'cpp_std=c++17',
'buildtype=release',
@ -79,7 +80,7 @@ is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.3.1'], fallback : ['spdlog', 'spdlog_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
@ -93,12 +94,31 @@ 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()
if not compiler.has_function('sioctl_open', prefix: '#include <sndio.h>', dependencies: libsndio)
if get_option('sndio').enabled()
error('libsndio is too old, required >=1.7.0')
else
warning('libsndio is too old, required >=1.7.0')
libsndio = dependency('', required: false)
endif
endif
endif
gtk_layer_shell = dependency('gtk-layer-shell-0',
required: get_option('gtk-layer-shell'),
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
systemd = dependency('systemd', required: get_option('systemd'))
tz_dep = dependency('date', default_options : [ 'use_system_tzdb=true' ], modules : [ 'date::date', 'date::date-tz' ], fallback: [ 'date', 'tz_dep' ])
tz_dep = dependency('date',
required: false,
default_options : [ 'use_system_tzdb=true' ],
modules : [ 'date::date', 'date::date-tz' ],
fallback: [ 'date', 'tz_dep' ])
prefix = get_option('prefix')
sysconfdir = get_option('sysconfdir')
@ -122,7 +142,6 @@ src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/ALabel.cpp',
'src/modules/clock.cpp',
'src/modules/custom.cpp',
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
@ -130,6 +149,8 @@ src_files = files(
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp',
'src/config.cpp',
'src/util/ustring_clen.cpp'
)
if is_linux
@ -137,12 +158,10 @@ if is_linux
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
src_files += files(
'src/modules/battery.cpp',
'src/modules/bluetooth.cpp',
'src/modules/cpu/common.cpp',
'src/modules/cpu/linux.cpp',
'src/modules/memory/common.cpp',
'src/modules/memory/linux.cpp',
'src/util/rfkill.cpp'
)
elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
add_project_arguments('-DHAVE_CPU_BSD', language: 'cpp')
@ -159,6 +178,7 @@ add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [
'src/modules/sway/ipc/client.cpp',
'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
]
@ -200,15 +220,43 @@ 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.cpp'
src_files += 'src/modules/mpd/mpd.cpp'
src_files += 'src/modules/mpd/state.cpp'
endif
if gtk_layer_shell.found()
add_project_arguments('-DHAVE_GTK_LAYER_SHELL', language: 'cpp')
endif
if libsndio.found()
add_project_arguments('-DHAVE_LIBSNDIO', language: 'cpp')
src_files += 'src/modules/sndio.cpp'
endif
if get_option('rfkill').enabled()
if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp')
src_files += files(
'src/modules/bluetooth.cpp',
'src/util/rfkill.cpp'
)
endif
endif
if tz_dep.found()
add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
src_files += 'src/modules/clock.cpp'
else
src_files += 'src/modules/simpleclock.cpp'
endif
subdir('protocol')
executable(
@ -232,8 +280,11 @@ executable(
libudev,
libepoll,
libmpdclient,
libevdev,
gtk_layer_shell,
tz_dep
libsndio,
tz_dep,
xkbregistry
],
include_directories: [include_directories('include')],
install: true,
@ -250,9 +301,20 @@ scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_opti
if scdoc.found()
scdoc_prog = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true)
sh = find_program('sh', native: true)
main_manpage = configure_file(
input: 'man/waybar.5.scd.in',
output: 'waybar.5.scd',
configuration: {
'sysconfdir': join_paths(prefix, sysconfdir)
}
)
main_manpage_path = join_paths(meson.build_root(), '@0@'.format(main_manpage))
mandir = get_option('mandir')
man_files = [
'waybar.5.scd',
main_manpage_path,
'waybar-backlight.5.scd',
'waybar-battery.5.scd',
'waybar-clock.5.scd',
@ -260,11 +322,13 @@ 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',
'waybar-pulseaudio.5.scd',
'waybar-river-tags.5.scd',
'waybar-sway-language.5.scd',
'waybar-sway-mode.5.scd',
'waybar-sway-window.5.scd',
'waybar-sway-workspaces.5.scd',
@ -274,16 +338,21 @@ if scdoc.found()
'waybar-wlr-taskbar.5.scd',
'waybar-wlr-workspaces.5.scd',
'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd',
]
foreach filename : man_files
topic = filename.split('.')[-3].split('/')[-1]
section = filename.split('.')[-2]
foreach file : man_files
path = '@0@'.format(file)
basename = path.split('/')[-1]
topic = basename.split('.')[-3]
section = basename.split('.')[-2]
output = '@0@.@1@'.format(topic, section)
custom_target(
output,
input: 'man/@0@'.format(filename),
# drops the 'man' if `path` is an absolute path
input: join_paths('man', path),
output: output,
command: [
sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc_prog.path(), output)
@ -294,6 +363,15 @@ if scdoc.found()
endforeach
endif
catch2 = dependency(
'catch2',
fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'),
)
if catch2.found()
subdir('test')
endif
clangtidy = find_program('clang-tidy', required: false)
if clangtidy.found()

View File

@ -1,9 +1,13 @@
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')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
option('mpd', type: 'feature', value: 'auto', description: 'Enable support for the Music Player Daemon')
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_status_unstable_v1">
<copyright>
Copyright 2020 Isaac Freund
Copyright 2020 The River Developers
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright>
<interface name="zriver_status_manager_v1" version="1">
<interface name="zriver_status_manager_v1" version="2">
<description summary="manage river status objects">
A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar.
@ -47,7 +47,7 @@
</request>
</interface>
<interface name="zriver_output_status_v1" version="1">
<interface name="zriver_output_status_v1" version="2">
<description summary="track output tags and focus">
This interface allows clients to receive information about the current
windowing state of an output.
@ -75,12 +75,21 @@
</description>
<arg name="tags" type="array" summary="array of 32-bit bitfields"/>
</event>
<event name="urgent_tags" since="2">
<description summary="tags of the output with an urgent view">
Sent once on binding the interface and again whenever the set of
tags with at least one urgent view changes.
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
</interface>
<interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat.
focus of a seat. Note that (un)focused_output events will only be sent
if the client has bound the relevant wl_output globals.
</description>
<request name="destroy" type="destructor">

View File

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_foreign_toplevel_manager_v1" version="2">
<interface name="zwlr_foreign_toplevel_manager_v1" version="3">
<description summary="list and control opened apps">
The purpose of this protocol is to enable the creation of taskbars
and docks by providing them with a list of opened applications and
@ -68,7 +68,7 @@
</event>
</interface>
<interface name="zwlr_foreign_toplevel_handle_v1" version="2">
<interface name="zwlr_foreign_toplevel_handle_v1" version="3">
<description summary="an opened toplevel">
A zwlr_foreign_toplevel_handle_v1 object represents an opened toplevel
window. Each app may have multiple opened toplevels.
@ -255,5 +255,16 @@
actually changes, this will be indicated by the state event.
</description>
</request>
<!-- Version 3 additions -->
<event name="parent" since="3">
<description summary="parent change">
This event is emitted whenever the parent of the toplevel changes.
No event is emitted when the parent handle is destroyed by the client.
</description>
<arg name="parent" type="object" interface="zwlr_foreign_toplevel_handle_v1" allow-null="true"/>
</event>
</interface>
</protocol>

View File

@ -25,7 +25,7 @@
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="1">
<interface name="zwlr_layer_shell_v1" version="3">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
@ -82,17 +82,27 @@
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="1">
<interface name="zwlr_layer_surface_v1" version="3">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (size, anchor, exclusive zone, margin, interactivity)
is double-buffered, and will be applied at the time wl_surface.commit of
the corresponding wl_surface is called.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
</description>
<request name="set_size">
@ -115,7 +125,7 @@
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthoginal edges are specified (e.g. 'top' and
and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
@ -127,20 +137,25 @@
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area of the surface
with other surfaces. The compositor's use of this information is
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to an
edge, rather than a corner. The zone is the number of surface-local
coordinates from the edge that are considered exclusive.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive excluzive zone. If set to -1, the surface
indicates that it would not like to be moved to accomodate for other
surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
@ -281,5 +296,16 @@
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface>
</protocol>

View File

@ -3,10 +3,11 @@
// "position": "bottom", // Waybar position (top|bottom|left|right)
"height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width
"spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "battery", "battery#bat2", "clock", "tray"],
"modules-right": ["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,11 +24,20 @@
// "default": ""
// }
// },
"keyboard-state": {
"numlock": true,
"capslock": true,
"format": "{name} {icon}",
"format-icons": {
"locked": "",
"unlocked": ""
}
},
"sway/mode": {
"format": "<span style=\"italic\">{}</span>"
},
"mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ",
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
"format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A",
@ -108,7 +118,8 @@
"network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname}: {ipaddr}/{cidr} ",
"format-ethernet": "{ipaddr}/{cidr} ",
"tooltip-format": "{ifname} via {gwaddr} ",
"format-linked": "{ifname} (No IP) ",
"format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}"
@ -145,3 +156,4 @@
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
}
}

View File

@ -79,7 +79,7 @@ def signal_handler(sig, frame):
def parse_arguments():
parser = argparse.ArgumentParser()
# Increase verbosity with every occurence of -v
# Increase verbosity with every occurrence of -v
parser.add_argument('-v', '--verbose', action='count', default=0)
# Define for which player we're listening

View File

@ -41,19 +41,19 @@ window#waybar.chromium {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
border-bottom: 3px solid transparent;
/* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
}
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
#workspaces button:hover {
background: rgba(0, 0, 0, 0.2);
box-shadow: inherit;
border-bottom: 3px solid #ffffff;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.focused {
background-color: #64727D;
border-bottom: 3px solid #ffffff;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.urgent {
@ -69,6 +69,7 @@ window#waybar.chromium {
#battery,
#cpu,
#memory,
#disk,
#temperature,
#backlight,
#network,
@ -79,10 +80,24 @@ window#waybar.chromium {
#idle_inhibitor,
#mpd {
padding: 0 10px;
margin: 0 4px;
color: #ffffff;
}
#window,
#workspaces {
margin: 0 4px;
}
/* If workspaces is the leftmost module, omit left margin */
.modules-left > widget:first-child > #workspaces {
margin-left: 0;
}
/* If workspaces is the rightmost module, omit right margin */
.modules-right > widget:last-child > #workspaces {
margin-right: 0;
}
#clock {
background-color: #64727D;
}
@ -92,7 +107,7 @@ window#waybar.chromium {
color: #000000;
}
#battery.charging {
#battery.charging, #battery.plugged {
color: #ffffff;
background-color: #26A65B;
}
@ -127,6 +142,10 @@ label:focus {
background-color: #9b59b6;
}
#disk {
background-color: #964B00;
}
#backlight {
background-color: #90b1b1;
}
@ -175,6 +194,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;
}
@ -200,3 +228,27 @@ label:focus {
#mpd.paused {
background-color: #51a37a;
}
#language {
background: #00b093;
color: #740864;
padding: 0 5px;
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);
}

View File

@ -1,13 +1,14 @@
[Unit]
Description=Highly customizable Wayland bar for Sway and Wlroots based compositors.
Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=wayland-session.target
After=wayland-session.target
PartOf=graphical-session.target
After=graphical-session.target
Requisite=graphical-session.target
[Service]
Type=dbus
BusName=fr.arouillard.waybar
ExecStart=@prefix@/bin/waybar
ExecReload=kill -SIGUSR2 $MAINPID
Restart=on-failure
[Install]
WantedBy=wayland-session.target
WantedBy=graphical-session.target

View File

@ -5,8 +5,9 @@
namespace waybar {
ALabel::ALabel(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize)
: AModule(config, name, id, config["format-alt"].isString()),
const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds(100000000)
@ -19,15 +20,36 @@ ALabel::ALabel(const Json::Value& config, const std::string& name, const std::st
}
event_box_.add(label_);
if (config_["max-length"].isUInt()) {
label_.set_max_width_chars(config_["max-length"].asUInt());
label_.set_max_width_chars(config_["max-length"].asInt());
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
} else if (ellipsize && label_.get_max_width_chars() == -1) {
label_.set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_.set_single_line_mode(true);
}
if (config_["rotate"].isUInt()) {
label_.set_angle(config["rotate"].asUInt());
if (config_["min-length"].isUInt()) {
label_.set_width_chars(config_["min-length"].asUInt());
}
uint rotate = 0;
if (config_["rotate"].isUInt()) {
rotate = config["rotate"].asUInt();
label_.set_angle(rotate);
}
if (config_["align"].isDouble()) {
auto align = config_["align"].asFloat();
if (rotate == 90 || rotate == 270) {
label_.set_yalign(align);
} else {
label_.set_xalign(align);
}
}
}
auto ALabel::update() -> void {
@ -45,8 +67,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
if (size) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
}
if (format_icons.isString()) {
return format_icons.asString();
@ -54,22 +78,24 @@ 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";
for (const auto& alt : alts) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt];
_alt = alt;
break;
} else {
format_icons = format_icons["default"];
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
if (size) {
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
}
if (format_icons.isString()) {
return format_icons.asString();

View File

@ -44,9 +44,9 @@ bool AModule::handleToggle(GdkEventButton* const& e) {
format = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && e->button == 3) {
format = config_["on-click-right"].asString();
} else if (config_["on-click-forward"].isString() && e->button == 8) {
} else if (config_["on-click-backward"].isString() && e->button == 8) {
format = config_["on-click-backward"].asString();
} else if (config_["on-click-backward"].isString() && e->button == 9) {
} else if (config_["on-click-forward"].isString() && e->button == 9) {
format = config_["on-click-forward"].asString();
}
if (!format.empty()) {

View File

@ -2,18 +2,396 @@
#include <gtk-layer-shell.h>
#endif
#include <spdlog/spdlog.h>
#include <type_traits>
#include "bar.hpp"
#include "client.hpp"
#include "factory.hpp"
#include <spdlog/spdlog.h>
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} is less than the minimum height: {} required by the modules";
static constexpr const char* MIN_WIDTH_MSG =
"Requested width: {} is less than the minimum width: {} required by the modules";
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char* SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that";
#ifdef HAVE_GTK_LAYER_SHELL
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_name_ = output.name;
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(window_.gobj());
gtk_layer_set_keyboard_interactivity(window.gobj(), FALSE);
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
gtk_layer_set_namespace(window_.gobj(), "waybar");
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
}
void setExclusiveZone(bool enable) override {
if (enable) {
gtk_layer_auto_exclusive_zone_enable(window_.gobj());
} else {
gtk_layer_set_exclusive_zone(window_.gobj(), 0);
}
}
void setMargins(const struct bar_margins& margins) override {
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, margins.left);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, margins.right);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, margins.top);
gtk_layer_set_margin(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, margins.bottom);
}
void setLayer(bar_layer value) override {
auto layer = GTK_LAYER_SHELL_LAYER_BOTTOM;
if (value == bar_layer::TOP) {
layer = GTK_LAYER_SHELL_LAYER_TOP;
} else if (value == bar_layer::OVERLAY) {
layer = GTK_LAYER_SHELL_LAYER_OVERLAY;
}
gtk_layer_set_layer(window_.gobj(), layer);
}
void setPassThrough(bool enable) override {
passthrough_ = enable;
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override {
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
vertical_ = false;
if (position == "bottom") {
unanchored = GTK_LAYER_SHELL_EDGE_TOP;
} else if (position == "left") {
unanchored = GTK_LAYER_SHELL_EDGE_RIGHT;
vertical_ = true;
} else if (position == "right") {
vertical_ = true;
unanchored = GTK_LAYER_SHELL_EDGE_LEFT;
}
for (auto edge : {GTK_LAYER_SHELL_EDGE_LEFT,
GTK_LAYER_SHELL_EDGE_RIGHT,
GTK_LAYER_SHELL_EDGE_TOP,
GTK_LAYER_SHELL_EDGE_BOTTOM}) {
gtk_layer_set_anchor(window_.gobj(), edge, unanchored != edge);
}
}
void setSize(uint32_t width, uint32_t height) override {
width_ = width;
height_ = height;
window_.set_size_request(width_, height_);
};
private:
Gtk::Window& window_;
std::string output_name_;
uint32_t width_;
uint32_t height_;
bool passthrough_ = false;
bool vertical_ = false;
void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); }
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
* Actual resizing and management of the exclusve zone is handled within the gtk-layer-shell
* code. This event handler only updates stored size of the window and prints some warnings.
*
* Note: forced resizing to a window smaller than required by GTK would not work with
* gtk-layer-shell.
*/
if (vertical_) {
if (width_ > 1 && ev->width > static_cast<int>(width_)) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
} else {
if (height_ > 1 && ev->height > static_cast<int>(height_)) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
}
width_ = ev->width;
height_ = ev->height;
spdlog::info(BAR_SIZE_MSG, width_, height_, output_name_);
}
};
#endif
struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
RawSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
output_ = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
output_name_ = output.name;
window.signal_realize().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &RawSurfaceImpl::onMap));
window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &RawSurfaceImpl::onConfigure));
if (window.get_realized()) {
onRealize();
}
}
void setExclusiveZone(bool enable) override {
exclusive_zone_ = enable;
if (layer_surface_) {
auto zone = 0;
if (enable) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
zone += width_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height_;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output_name_);
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_.get(), zone);
}
}
void setLayer(bar_layer layer) override {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
if (layer == bar_layer::TOP) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_TOP;
} else if (layer == bar_layer::OVERLAY) {
layer_ = ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY;
}
// updating already mapped window
if (layer_surface_) {
if (zwlr_layer_surface_v1_get_version(layer_surface_.get()) >=
ZWLR_LAYER_SURFACE_V1_SET_LAYER_SINCE_VERSION) {
zwlr_layer_surface_v1_set_layer(layer_surface_.get(), layer_);
} else {
spdlog::warn("Unable to change layer: layer-shell implementation is too old");
}
}
}
void setMargins(const struct bar_margins& margins) override {
margins_ = margins;
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
}
}
void setPassThrough(bool enable) override {
passthrough_ = enable;
/* GTK overwrites any region changes applied directly to the wl_surface,
* thus the same GTK region API as in the GLS impl has to be used. */
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (position == "bottom") {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (position == "left") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (position == "right") {
anchor_ = VERTICAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
}
// updating already mapped window
if (layer_surface_) {
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
}
}
void setSize(uint32_t width, uint32_t height) override {
configured_width_ = width_ = width;
configured_height_ = height_ = height;
// layer_shell.configure handler should update exclusive zone if size changes
window_.set_size_request(width, height);
};
void commit() override {
if (surface_) {
wl_surface_commit(surface_);
}
}
private:
constexpr static uint8_t VERTICAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
constexpr static uint8_t HORIZONTAL_ANCHOR =
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
template <auto fn>
using deleter_fn = std::integral_constant<decltype(fn), fn>;
using layer_surface_ptr =
std::unique_ptr<zwlr_layer_surface_v1, deleter_fn<zwlr_layer_surface_v1_destroy>>;
Gtk::Window& window_;
std::string output_name_;
uint32_t configured_width_ = 0;
uint32_t configured_height_ = 0;
uint32_t width_ = 0;
uint32_t height_ = 0;
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool exclusive_zone_ = true;
bool passthrough_ = false;
struct bar_margins margins_;
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
struct wl_output* output_ = nullptr; // owned by GTK
struct wl_surface* surface_ = nullptr; // owned by GTK
layer_surface_ptr layer_surface_;
void onRealize() {
auto gdk_window = window_.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void onMap(GdkEventAny* ev) {
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = onSurfaceConfigure,
.closed = onSurfaceClosed,
};
auto client = Client::inst();
auto gdk_window = window_.get_window()->gobj();
surface_ = gdk_wayland_window_get_wl_surface(gdk_window);
layer_surface_.reset(zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface_, output_, layer_, "waybar"));
zwlr_layer_surface_v1_add_listener(layer_surface_.get(), &layer_surface_listener, this);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_.get(), false);
zwlr_layer_surface_v1_set_anchor(layer_surface_.get(), anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_.get(), margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(exclusive_zone_);
setPassThrough(passthrough_);
commit();
wl_display_roundtrip(client->wl_display);
}
void onConfigure(GdkEventConfigure* ev) {
/*
* GTK wants new size for the window.
*
* Prefer configured size if it's non-default.
* If the size is not set and the window is smaller than requested by GTK, request resize from
* layer surface.
*/
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (configured_height_ > 1) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (configured_width_ > 1) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
commit();
}
}
void setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
*
* If the size for unanchored axis is not set (0), change request to 1 to avoid automatic
* assignment by the compositor.
*/
if ((anchor_ & VERTICAL_ANCHOR) == VERTICAL_ANCHOR) {
width = width > 0 ? width : 1;
if (height > 1) {
height += margins_.top + margins_.bottom;
}
} else {
height = height > 0 ? height : 1;
if (width > 1) {
width += margins_.right + margins_.left;
}
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output_name_);
zwlr_layer_surface_v1_set_size(layer_surface_.get(), width, height);
}
static void onSurfaceConfigure(void* data, struct zwlr_layer_surface_v1* surface, uint32_t serial,
uint32_t width, uint32_t height) {
auto o = static_cast<RawSurfaceImpl*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window_.set_size_request(o->width_, o->height_);
o->window_.resize(o->width_, o->height_);
o->setExclusiveZone(o->exclusive_zone_);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output_name_);
o->commit();
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
static void onSurfaceClosed(void* data, struct zwlr_layer_surface_v1* /* surface */) {
auto o = static_cast<RawSurfaceImpl*>(data);
o->layer_surface_.reset();
}
};
}; // namespace waybar
waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output),
config(w_config),
surface(nullptr),
window{Gtk::WindowType::WINDOW_TOPLEVEL},
layer_surface_(nullptr),
anchor_(ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP),
layer_{bar_layer::BOTTOM},
left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0),
@ -25,26 +403,30 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.get_style_context()->add_class(config["name"].asString());
window.get_style_context()->add_class(config["position"].asString());
if (config["position"] == "right" || config["position"] == "left") {
height_ = 0;
width_ = 1;
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
} else if (config["layer"] == "overlay") {
layer_ = bar_layer::OVERLAY;
}
height_ = config["height"].isUInt() ? config["height"].asUInt() : height_;
width_ = config["width"].isUInt() ? config["width"].asUInt() : width_;
if (config["position"] == "bottom") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
} else if (config["position"] == "left") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT;
} else if (config["position"] == "right") {
anchor_ = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
if (config["exclusive"].isBool()) {
exclusive = config["exclusive"].asBool();
} else if (layer_ == bar_layer::OVERLAY) {
// swaybar defaults: overlay mode does not reserve an exclusive zone
exclusive = false;
}
if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
} else if (anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT ||
anchor_ == ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT) {
anchor_ |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
bool passthrough = false;
if (config["passthrough"].isBool()) {
passthrough = config["passthrough"].asBool();
} else if (layer_ == bar_layer::OVERLAY) {
// swaybar defaults: overlay mode does not accept pointer events.
passthrough = true;
}
auto position = config["position"].asString();
if (position == "right" || position == "left") {
left_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
center_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
right_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
@ -52,6 +434,22 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
vertical = true;
}
left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right");
if (config["spacing"].isInt()) {
int spacing = config["spacing"].asInt();
left_.set_spacing(spacing);
center_.set_spacing(spacing);
right_.set_spacing(spacing);
}
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
struct bar_margins margins_;
if (config["margin-top"].isInt() || config["margin-right"].isInt() ||
config["margin-bottom"].isInt() || config["margin-left"].isInt()) {
margins_ = {
@ -98,170 +496,63 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
}
#ifdef HAVE_GTK_LAYER_SHELL
use_gls_ = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls_) {
initGtkLayerShell();
}
#endif
window.signal_realize().connect_notify(sigc::mem_fun(*this, &Bar::onRealize));
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Bar::onConfigure));
window.set_size_request(width_, height_);
setupWidgets();
if (window.get_realized()) {
onRealize();
}
window.show_all();
}
void waybar::Bar::onConfigure(GdkEventConfigure* ev) {
auto tmp_height = height_;
auto tmp_width = width_;
if (ev->height > static_cast<int>(height_)) {
// Default minimal value
if (height_ > 1) {
spdlog::warn(MIN_HEIGHT_MSG, height_, ev->height);
}
if (config["height"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Height");
} else {
tmp_height = ev->height;
}
}
if (ev->width > static_cast<int>(width_)) {
// Default minimal value
if (width_ > 1) {
spdlog::warn(MIN_WIDTH_MSG, width_, ev->width);
}
if (config["width"].isUInt()) {
spdlog::info(SIZE_DEFINED, "Width");
} else {
tmp_width = ev->width;
}
}
if (use_gls_) {
width_ = tmp_width;
height_ = tmp_height;
spdlog::debug("Set surface size {}x{} for output {}", width_, height_, output->name);
setExclusiveZone(tmp_width, tmp_height);
} else if (tmp_width != width_ || tmp_height != height_) {
setSurfaceSize(tmp_width, tmp_height);
}
}
#ifdef HAVE_GTK_LAYER_SHELL
void waybar::Bar::initGtkLayerShell() {
auto gtk_window = window.gobj();
// this has to be executed before GtkWindow.realize
gtk_layer_init_for_window(gtk_window);
gtk_layer_set_keyboard_interactivity(gtk_window, FALSE);
auto layer = config["layer"] == "top" ? GTK_LAYER_SHELL_LAYER_TOP : GTK_LAYER_SHELL_LAYER_BOTTOM;
gtk_layer_set_layer(gtk_window, layer);
gtk_layer_set_monitor(gtk_window, output->monitor->gobj());
gtk_layer_set_namespace(gtk_window, "waybar");
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_TOP, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP);
gtk_layer_set_anchor(
gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_LEFT, margins_.left);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_RIGHT, margins_.right);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_TOP, margins_.top);
gtk_layer_set_margin(gtk_window, GTK_LAYER_SHELL_EDGE_BOTTOM, margins_.bottom);
if (width_ > 1 && height_ > 1) {
/* configure events are not emitted if the bar is using initial size */
setExclusiveZone(width_, height_);
}
}
#endif
void waybar::Bar::onRealize() {
auto gdk_window = window.get_window()->gobj();
gdk_wayland_window_set_use_custom_surface(gdk_window);
}
void waybar::Bar::onMap(GdkEventAny* ev) {
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
if (use_gls_) {
return;
}
auto client = waybar::Client::inst();
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output->monitor->gobj());
auto layer =
config["layer"] == "top" ? ZWLR_LAYER_SHELL_V1_LAYER_TOP : ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
layer_surface_ = zwlr_layer_shell_v1_get_layer_surface(
client->layer_shell, surface, wl_output, layer, "waybar");
zwlr_layer_surface_v1_set_keyboard_interactivity(layer_surface_, false);
zwlr_layer_surface_v1_set_anchor(layer_surface_, anchor_);
zwlr_layer_surface_v1_set_margin(
layer_surface_, margins_.top, margins_.right, margins_.bottom, margins_.left);
setSurfaceSize(width_, height_);
setExclusiveZone(width_, height_);
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layerSurfaceHandleConfigure,
.closed = layerSurfaceHandleClosed,
};
zwlr_layer_surface_v1_add_listener(layer_surface_, &layer_surface_listener, this);
wl_surface_commit(surface);
wl_display_roundtrip(client->wl_display);
}
void waybar::Bar::setExclusiveZone(uint32_t width, uint32_t height) {
auto zone = 0;
if (visible) {
// exclusive zone already includes margin for anchored edge,
// only opposite margin should be added
if (vertical) {
zone += width;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT) ? margins_.right : margins_.left;
} else {
zone += height;
zone += (anchor_ & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP) ? margins_.bottom : margins_.top;
}
}
spdlog::debug("Set exclusive zone {} for output {}", zone, output->name);
#ifdef HAVE_GTK_LAYER_SHELL
if (use_gls_) {
gtk_layer_set_exclusive_zone(window.gobj(), zone);
bool use_gls = config["gtk-layer-shell"].isBool() ? config["gtk-layer-shell"].asBool() : true;
if (use_gls) {
surface_impl_ = std::make_unique<GLSSurfaceImpl>(window, *output);
} else
#endif
{
zwlr_layer_surface_v1_set_exclusive_zone(layer_surface_, zone);
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
}
surface_impl_->setLayer(layer_);
surface_impl_->setExclusiveZone(exclusive);
surface_impl_->setMargins(margins_);
surface_impl_->setPassThrough(passthrough);
surface_impl_->setPosition(position);
surface_impl_->setSize(width, height);
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
setupWidgets();
window.show_all();
if (spdlog::should_log(spdlog::level::debug)) {
// Unfortunately, this function isn't in the C++ bindings, so we have to call the C version.
char* gtk_tree = gtk_style_context_to_string(
window.get_style_context()->gobj(),
(GtkStyleContextPrintFlags)(GTK_STYLE_CONTEXT_PRINT_RECURSE |
GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE));
spdlog::debug("GTK widget tree:\n{}", gtk_tree);
g_free(gtk_tree);
}
}
void waybar::Bar::setSurfaceSize(uint32_t width, uint32_t height) {
/* If the client is anchored to two opposite edges, layer_surface.configure will return
* size without margins for the axis.
* layer_surface.set_size, however, expects size with margins for the anchored axis.
* This is not specified by wlr-layer-shell and based on actual behavior of sway.
void waybar::Bar::onMap(GdkEventAny*) {
/*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
*/
if (vertical && height > 1) {
height += margins_.top + margins_.bottom;
}
if (!vertical && width > 1) {
width += margins_.right + margins_.left;
}
spdlog::debug("Set surface size {}x{} for output {}", width, height, output->name);
zwlr_layer_surface_v1_set_size(layer_surface_, width, height);
auto gdk_window = window.get_window()->gobj();
surface = gdk_wayland_window_get_wl_surface(gdk_window);
}
void waybar::Bar::setVisible(bool value) {
visible = value;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
surface_impl_->setLayer(bar_layer::BOTTOM);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
surface_impl_->setLayer(layer_);
}
surface_impl_->setExclusiveZone(exclusive && visible);
surface_impl_->commit();
}
void waybar::Bar::toggle() { setVisible(!visible); }
// Converting string to button code rn as to avoid doing it later
void waybar::Bar::setupAltFormatKeyForModule(const std::string& module_name) {
if (config.isMember(module_name)) {
@ -323,48 +614,6 @@ void waybar::Bar::handleSignal(int signal) {
}
}
void waybar::Bar::layerSurfaceHandleConfigure(void* data, struct zwlr_layer_surface_v1* surface,
uint32_t serial, uint32_t width, uint32_t height) {
auto o = static_cast<waybar::Bar*>(data);
if (width != o->width_ || height != o->height_) {
o->width_ = width;
o->height_ = height;
o->window.set_size_request(o->width_, o->height_);
o->window.resize(o->width_, o->height_);
o->setExclusiveZone(width, height);
spdlog::info(BAR_SIZE_MSG,
o->width_ == 1 ? "auto" : std::to_string(o->width_),
o->height_ == 1 ? "auto" : std::to_string(o->height_),
o->output->name);
wl_surface_commit(o->surface);
}
zwlr_layer_surface_v1_ack_configure(surface, serial);
}
void waybar::Bar::layerSurfaceHandleClosed(void* data, struct zwlr_layer_surface_v1* /*surface*/) {
auto o = static_cast<waybar::Bar*>(data);
if (o->layer_surface_) {
zwlr_layer_surface_v1_destroy(o->layer_surface_);
o->layer_surface_ = nullptr;
}
o->modules_left_.clear();
o->modules_center_.clear();
o->modules_right_.clear();
}
auto waybar::Bar::toggle() -> void {
visible = !visible;
if (!visible) {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
}
setExclusiveZone(width_, height_);
wl_surface_commit(surface);
}
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
if (config[pos].isArray()) {
for (const auto& name : config[pos]) {

View File

@ -1,37 +1,25 @@
#include "client.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <fstream>
#include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp"
#include "util/json.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() {
static auto c = new Client();
return c;
}
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const {
wordexp_t p;
for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
}
return std::string();
}
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data);
if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
// limit version to a highest supported by the client protocol file
version = std::min<uint32_t>(version, zwlr_layer_shell_v1_interface.version);
client->layer_shell = static_cast<struct zwlr_layer_shell_v1 *>(
wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, version));
} else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0 &&
@ -53,9 +41,9 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
static const struct zxdg_output_v1_listener xdgOutputListener = {
.logical_position = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.logical_size = [](void *, struct zxdg_output_v1 *, int32_t, int32_t) {},
.done = [](void *, struct zxdg_output_v1 *) {},
.done = &handleOutputDone,
.name = &handleOutputName,
.description = [](void *, struct zxdg_output_v1 *, const char *) {},
.description = &handleOutputDescription,
};
// owned by output->monitor; no need to destroy
auto wl_output = gdk_wayland_monitor_get_wl_output(output.monitor->gobj());
@ -63,27 +51,6 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output);
}
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() && output_conf.asString() == output.name) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output_name = config["output"].asString();
if (!config_output_name.empty()) {
if (config_output_name.substr(0, 1) == "!") {
return config_output_name.substr(1) != output.name;
}
return config_output_name == output.name;
}
}
return true;
}
struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
auto it = std::find_if(
outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; });
@ -94,17 +61,36 @@ struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
}
std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, output)) {
configs.push_back(config);
return config.getOutputConfigs(output.name, output.identifier);
}
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
auto client = waybar::Client::inst();
try {
auto &output = client->getOutput(data);
/**
* Multiple .done events may arrive in batch. In this case libwayland would queue
* xdg_output.destroy and dispatch all pending events, triggering this callback several times
* for the same output. .done events can also arrive after that for a scale or position changes.
* We wouldn't want to draw a duplicate bar for each such event either.
*
* All the properties we care about are immutable so it's safe to delete the xdg_output object
* on the first event and use the ptr value to check that the callback was already invoked.
*/
if (output.xdg_output) {
output.xdg_output.reset();
spdlog::debug("Output detection done: {} ({})", output.name, output.identifier);
auto configs = client->getOutputConfigs(output);
if (!configs.empty()) {
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
}
}
}
} else if (isValidOutput(config_, output)) {
configs.push_back(config_);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
return configs;
}
void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 * /*xdg_output*/,
@ -113,22 +99,21 @@ void waybar::Client::handleOutputName(void * data, struct zxdg_output_v1 *
try {
auto &output = client->getOutput(data);
output.name = name;
spdlog::debug("Output detected: {} ({} {})",
name,
output.monitor->get_manufacturer(),
output.monitor->get_model());
auto configs = client->getOutputConfigs(output);
if (configs.empty()) {
output.xdg_output.reset();
} else {
wl_display_roundtrip(client->wl_display);
for (const auto &config : configs) {
client->bars.emplace_back(std::make_unique<Bar>(&output, config));
Glib::RefPtr<Gdk::Screen> screen = client->bars.back()->window.get_screen();
client->style_context_->add_provider_for_screen(
screen, client->css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
}
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
void waybar::Client::handleOutputDescription(void *data, struct zxdg_output_v1 * /*xdg_output*/,
const char *description) {
auto client = waybar::Client::inst();
try {
auto & output = client->getOutput(data);
const char *open_paren = strrchr(description, '(');
// Description format: "identifier (name)"
size_t identifier_length = open_paren - description;
output.identifier = std::string(description, identifier_length - 1);
} catch (const std::exception &e) {
std::cerr << e.what() << std::endl;
}
@ -142,6 +127,16 @@ void waybar::Client::handleMonitorAdded(Glib::RefPtr<Gdk::Monitor> monitor) {
void waybar::Client::handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor) {
spdlog::debug("Output removed: {} {}", monitor->get_manufacturer(), monitor->get_model());
/* This event can be triggered from wl_display_roundtrip called by GTK or our code.
* Defer destruction of bars for the output to the next iteration of the event loop to avoid
* deleting objects referenced by currently executed code.
*/
Glib::signal_idle().connect_once(
sigc::bind(sigc::mem_fun(*this, &Client::handleDeferredMonitorRemoval), monitor),
Glib::PRIORITY_HIGH_IDLE);
}
void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor) {
for (auto it = bars.begin(); it != bars.end();) {
if ((*it)->output->monitor == monitor) {
auto output_name = (*it)->output->name;
@ -156,40 +151,14 @@ void waybar::Client::handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor) {
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
}
std::tuple<const std::string, const std::string> waybar::Client::getConfigs(
const std::string &config, const std::string &style) const {
auto config_file = config.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/config",
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
: config;
auto css_file = style.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css",
})
: style;
if (css_file.empty() || config_file.empty()) {
throw std::runtime_error("Missing required resources files");
const std::string waybar::Client::getStyle(const std::string &style) {
auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
if (!css_file) {
throw std::runtime_error("Missing required resource files");
}
spdlog::info("Resources files: {}, {}", config_file, css_file);
return {config_file, css_file};
}
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
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);
}
spdlog::info("Using CSS file {}", css_file.value());
return css_file.value();
};
auto waybar::Client::setupCss(const std::string &css_file) -> void {
css_provider_ = Gtk::CssProvider::create();
@ -199,6 +168,9 @@ auto waybar::Client::setupCss(const std::string &css_file) -> void {
if (!css_provider_->load_from_path(css_file)) {
throw std::runtime_error("Can't open style file");
}
// there's always only one screen
style_context_->add_provider_for_screen(
Gdk::Screen::get_default(), css_provider_, GTK_STYLE_PROVIDER_PRIORITY_USER);
}
void waybar::Client::bindInterfaces() {
@ -225,14 +197,14 @@ void waybar::Client::bindInterfaces() {
int waybar::Client::main(int argc, char *argv[]) {
bool show_help = false;
bool show_version = false;
std::string config;
std::string style;
std::string config_opt;
std::string style_opt;
std::string bar_id;
std::string log_level;
auto cli = clara::detail::Help(show_help) |
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt(
log_level,
"trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") |
@ -253,7 +225,8 @@ int waybar::Client::main(int argc, char *argv[]) {
if (!log_level.empty()) {
spdlog::set_level(spdlog::level::from_str(log_level));
}
gtk_app = Gtk::Application::create(argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gtk_app = Gtk::Application::create(
argc, argv, "fr.arouillard.waybar", Gio::APPLICATION_HANDLES_COMMAND_LINE);
gdk_display = Gdk::Display::get_default();
if (!gdk_display) {
throw std::runtime_error("Can't find display");
@ -262,17 +235,16 @@ int waybar::Client::main(int argc, char *argv[]) {
throw std::runtime_error("Bar need to run under Wayland");
}
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style);
setupConfig(config_file);
config.load(config_opt);
auto css_file = getStyle(style_opt);
setupCss(css_file);
bindInterfaces();
gtk_app->hold();
gtk_app->run();
bars.clear();
zxdg_output_manager_v1_destroy(xdg_output_manager);
zwlr_layer_shell_v1_destroy(layer_shell);
zwp_idle_inhibit_manager_v1_destroy(idle_inhibit_manager);
wl_registry_destroy(registry);
wl_display_disconnect(wl_display);
return 0;
}
void waybar::Client::reset() {
gtk_app->quit();
}

153
src/config.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "config.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <wordexp.h>
#include <fstream>
#include <stdexcept>
#include "util/json.hpp"
namespace waybar {
const std::vector<std::string> Config::CONFIG_DIRS = {
"$XDG_CONFIG_HOME/waybar/",
"$HOME/.config/waybar/",
"$HOME/waybar/",
"/etc/xdg/waybar/",
SYSCONFDIR "/xdg/waybar/",
"./resources/",
};
std::optional<std::string> tryExpandPath(const std::string &path) {
wordexp_t p;
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
return std::nullopt;
}
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
const std::vector<std::string> &dirs) {
std::vector<std::string> paths;
for (const auto &dir : dirs) {
for (const auto &name : names) {
if (auto res = tryExpandPath(dir + name); res) {
return res;
}
}
}
return std::nullopt;
}
void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) {
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;
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(dst, tmp_config);
}
void Config::resolveConfigIncludes(Json::Value &config, int depth) {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth);
}
} else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString());
setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth);
}
}
void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) {
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()) {
// [] creates key with default value. Use `get` to avoid that.
if (a_config_.get(key, Json::Value::nullSingleton()).isObject() &&
b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else if (!a_config_.isMember(key)) {
// do not allow overriding value set by top or previously included config
a_config_[key] = b_config_[key];
} else {
spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
}
bool isValidOutput(const Json::Value &config, const std::string &name,
const std::string &identifier) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() &&
(output_conf.asString() == name || output_conf.asString() == identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != name && config_output.substr(1) != identifier;
}
return config_output == name || config_output == identifier;
}
}
return true;
}
void Config::load(const std::string &config) {
auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config;
if (!file) {
throw std::runtime_error("Missing required resource files");
}
config_file_ = file.value();
spdlog::info("Using configuration file {}", config_file_);
setupConfig(config_, config_file_, 0);
}
std::vector<Json::Value> Config::getOutputConfigs(const std::string &name,
const std::string &identifier) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, name, identifier)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, name, identifier)) {
configs.push_back(config_);
}
return configs;
}
} // namespace waybar

View File

@ -22,6 +22,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/window") {
return new waybar::modules::sway::Window(id, bar_, config_[name]);
}
if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]);
}
#endif
#ifdef HAVE_WLR
if (ref == "wlr/taskbar") {
@ -70,6 +73,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]);
@ -79,14 +87,21 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "mpd") {
return new waybar::modules::MPD(id, config_[name]);
}
#endif
#ifdef HAVE_LIBSNDIO
if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
#if defined(__linux__)
# ifdef WANT_RFKILL
if (ref == "bluetooth") {
return new waybar::modules::Bluetooth(id, config_[name]);
}
# endif
#endif
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]);

View File

@ -1,16 +1,89 @@
#include <csignal>
#include <list>
#include <mutex>
#include <sys/types.h>
#include <sys/wait.h>
#include <spdlog/spdlog.h>
#include "client.hpp"
std::mutex reap_mtx;
std::list<pid_t> reap;
volatile bool reload;
void* signalThread(void* args) {
int err, signum;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
while (true) {
err = sigwait(&mask, &signum);
if (err != 0) {
spdlog::error("sigwait failed: {}", strerror(errno));
continue;
}
switch (signum) {
case SIGCHLD:
spdlog::debug("Received SIGCHLD in signalThread");
if (!reap.empty()) {
reap_mtx.lock();
for (auto it = reap.begin(); it != reap.end(); ++it) {
if (waitpid(*it, nullptr, WNOHANG) == *it) {
spdlog::debug("Reaped child with PID: {}", *it);
it = reap.erase(it);
}
}
reap_mtx.unlock();
}
break;
default:
spdlog::debug("Received signal with number {}, but not handling",
signum);
break;
}
}
}
void startSignalThread(void) {
int err;
sigset_t mask;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
// Block SIGCHLD so it can be handled by the signal thread
// Any threads created by this one (the main thread) should not
// modify their signal mask to unblock SIGCHLD
err = pthread_sigmask(SIG_BLOCK, &mask, nullptr);
if (err != 0) {
spdlog::error("pthread_sigmask failed in startSignalThread: {}", strerror(err));
exit(1);
}
pthread_t thread_id;
err = pthread_create(&thread_id, nullptr, signalThread, nullptr);
if (err != 0) {
spdlog::error("pthread_create failed in startSignalThread: {}", strerror(err));
exit(1);
}
}
int main(int argc, char* argv[]) {
try {
auto client = waybar::Client::inst();
std::signal(SIGUSR1, [](int /*signal*/) {
for (auto& bar : waybar::Client::inst()->bars) {
bar->toggle();
}
});
std::signal(SIGUSR2, [](int /*signal*/) {
spdlog::info("Reloading...");
reload = true;
waybar::Client::inst()->reset();
});
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
@ -18,8 +91,14 @@ int main(int argc, char* argv[]) {
}
});
}
startSignalThread();
auto ret = 0;
do {
reload = false;
ret = client->main(argc, argv);
} while (reload);
auto ret = client->main(argc, argv);
delete client;
return ret;
} catch (const std::exception& e) {

View File

@ -173,7 +173,7 @@ auto waybar::modules::Backlight::update() -> void {
return;
}
const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max();
const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format(
format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
getState(percent);

View File

@ -4,46 +4,82 @@
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) {
getBatteries();
fd_ = inotify_init1(IN_CLOEXEC);
if (fd_ == -1) {
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
for (auto const& bat : batteries_) {
auto wd = inotify_add_watch(fd_, (bat / "uevent").c_str(), IN_ACCESS);
if (wd != -1) {
wds_.push_back(wd);
}
global_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (global_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries.");
}
// Watch the directory for any added or removed batteries
global_watch = inotify_add_watch(global_watch_fd_, data_dir_.c_str(), IN_CREATE | IN_DELETE);
if (global_watch < 0) {
throw std::runtime_error("Could not watch for battery plug/unplug");
}
refreshBatteries();
worker();
}
waybar::modules::Battery::~Battery() {
for (auto wd : wds_) {
inotify_rm_watch(fd_, wd);
std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
inotify_rm_watch(global_watch_fd_, global_watch);
}
close(fd_);
close(global_watch_fd_);
for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) {
auto watch_id = (*it).second;
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(it);
}
close(battery_watch_fd_);
}
void waybar::modules::Battery::worker() {
thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason
refreshBatteries();
dp.emit();
thread_timer_.sleep_for(interval_);
};
thread_ = [this] {
struct inotify_event event = {0};
int nbytes = read(fd_, &event, sizeof(event));
int nbytes = read(battery_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
// TODO: don't stop timer for now since there is some bugs :?
// thread_timer_.stop();
dp.emit();
};
thread_battery_update_ = [this] {
struct inotify_event event = {0};
int nbytes = read(global_watch_fd_, &event, sizeof(event));
if (nbytes != sizeof(event) || event.mask & IN_IGNORED) {
thread_.stop();
return;
}
refreshBatteries();
dp.emit();
};
}
void waybar::modules::Battery::getBatteries() {
void waybar::modules::Battery::refreshBatteries() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map;
for (auto const& bat : batteries_) {
check_map[bat.first] = false;
}
try {
for (auto& node : fs::directory_iterator(data_dir_)) {
if (!fs::is_directory(node)) {
@ -54,12 +90,22 @@ void waybar::modules::Battery::getBatteries() {
if (((bat_defined && dir_name == config_["bat"].asString()) || !bat_defined) &&
fs::exists(node.path() / "capacity") && fs::exists(node.path() / "uevent") &&
fs::exists(node.path() / "status") && fs::exists(node.path() / "type")) {
std::string type;
std::ifstream(node.path() / "type") >> type;
std::string type;
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")){
batteries_.push_back(node.path());
if (!type.compare("Battery")){
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
// We've found a new battery save it and start listening for events
auto event_path = (node.path() / "uevent");
auto wd = inotify_add_watch(battery_watch_fd_, event_path.c_str(), IN_ACCESS);
if (wd < 0) {
throw std::runtime_error("Could not watch events for " + node.path().string());
}
batteries_[node.path()] = wd;
}
}
}
auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
@ -76,36 +122,86 @@ void waybar::modules::Battery::getBatteries() {
}
throw std::runtime_error("No batteries.");
}
// Remove any batteries that are no longer present and unwatch them
for (auto const& check : check_map) {
if (!check.second) {
auto watch_id = batteries_[check.first];
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
}
batteries_.erase(check.first);
}
}
}
const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos() const {
// Unknown > Full > Not charging > Discharging > Charging
static bool status_gt(const std::string& a, const std::string& b) {
if (a == b) return false;
else if (a == "Unknown") return true;
else if (a == "Full" && b != "Unknown") return true;
else if (a == "Not charging" && b != "Unknown" && b != "Full") return true;
else if (a == "Discharging" && b != "Unknown" && b != "Full" && b != "Not charging") return true;
return false;
}
const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::getInfos() {
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
uint16_t total = 0;
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
uint32_t total_energy_full_design = 0;
std::string status = "Unknown";
for (auto const& bat : batteries_) {
uint16_t capacity;
for (auto const& item : batteries_) {
auto bat = item.first;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
uint32_t energy_full_design;
std::string _status;
std::ifstream(bat / "capacity") >> capacity;
std::ifstream(bat / "status") >> _status;
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now;
auto now_path = fs::exists(bat / "charge_now") ? "charge_now" : "energy_now";
std::ifstream(bat / now_path) >> energy_now;
auto full_path = fs::exists(bat / "charge_full") ? "charge_full" : "energy_full";
std::ifstream(bat / full_path) >> energy_full;
if (_status != "Unknown") {
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
if (fs::exists(bat / "current_now")) {
uint32_t voltage_now;
uint32_t current_now;
uint32_t charge_now;
uint32_t charge_full;
uint32_t charge_full_design;
std::ifstream(bat / "voltage_now") >> voltage_now;
std::ifstream(bat / "current_now") >> current_now;
std::ifstream(bat / "charge_full") >> charge_full;
std::ifstream(bat / "charge_full_design") >> charge_full_design;
if (fs::exists(bat / "charge_now"))
std::ifstream(bat / "charge_now") >> charge_now;
else {
// charge_now is missing on some systems, estimate using capacity.
uint32_t capacity;
std::ifstream(bat / "capacity") >> capacity;
charge_now = (capacity * charge_full) / 100;
}
power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000;
energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000;
energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000;
energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000;
} else {
std::ifstream(bat / "power_now") >> power_now;
std::ifstream(bat / "energy_now") >> energy_now;
std::ifstream(bat / "energy_full") >> energy_full;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) {
status = _status;
}
total += capacity;
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
total_energy_full_design += energy_full_design;
}
if (!adapter_.empty() && status == "Discharging") {
bool online;
@ -119,22 +215,40 @@ const std::tuple<uint8_t, float, std::string> waybar::modules::Battery::getInfos
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
if (time_remaining > 0.0f) {
// If we've turned positive it means the battery is past 100% and so
// just report that as no time remaining
time_remaining = 0.0f;
}
}
float capacity = ((float)total_energy * 100.0f / (float) total_energy_full);
// Handle design-capacity
if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) {
capacity = ((float)total_energy * 100.0f / (float) total_energy_full_design);
}
uint16_t capacity = total / batteries_.size();
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
if (full_at < 100) {
capacity = 100.f * capacity / full_at;
if (capacity > full_at) {
capacity = full_at;
}
}
}
return {capacity, time_remaining, status};
if (capacity > 100.f) {
// This can happen when the battery is calibrating and goes above 100%
// Handle it gracefully by clamping at 100%
capacity = 100.f;
}
uint8_t cap = round(capacity);
if (cap == 100 && status == "Charging") {
// If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done
status = "Full";
}
return {cap, time_remaining, status, total_power / 1e6};
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown"};
return {0, 0, "Unknown", 0};
}
}
@ -158,6 +272,10 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);
uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));
auto format = std::string("{H} h {M} min");
if (full_hours == 0 && minutes == 0) {
// Migh as well not show "0h 0min"
return "";
}
if (config_["format-time"].isString()) {
format = config_["format-time"].asString();
}
@ -165,26 +283,41 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
}
auto waybar::modules::Battery::update() -> void {
auto [capacity, time_remaining, status] = getInfos();
auto [capacity, time_remaining, status, power] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
}
if (tooltipEnabled()) {
std::string tooltip_text;
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text = time_to + ": " + formatTimeRemaining(time_remaining);
} else {
tooltip_text = status;
}
label_.set_tooltip_text(tooltip_text);
}
auto status_pretty = status;
// Transform to lowercase and replace space with dash
std::transform(status.begin(), status.end(), status.begin(), [](char ch) {
return ch == ' ' ? '-' : std::tolower(ch);
});
auto format = format_;
auto state = getState(capacity, true);
auto time_remaining_formatted = formatTimeRemaining(time_remaining);
if (tooltipEnabled()) {
std::string tooltip_text_default;
std::string tooltip_format = "{timeTo}";
if (time_remaining != 0) {
std::string time_to = std::string("Time to ") + ((time_remaining > 0) ? "empty" : "full");
tooltip_text_default = time_to + ": " + time_remaining_formatted;
} else {
tooltip_text_default = status_pretty;
}
if (!state.empty() && config_["tooltip-format-" + status + "-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + status + "-" + state].asString();
} else if (config_["tooltip-format-" + status].isString()) {
tooltip_format = config_["tooltip-format-" + status].asString();
} else if (!state.empty() && config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format,
fmt::arg("timeTo", tooltip_text_default),
fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
}
@ -204,8 +337,9 @@ auto waybar::modules::Battery::update() -> void {
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format,
fmt::arg("capacity", capacity),
fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", formatTimeRemaining(time_remaining))));
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();

View File

@ -1,45 +1,25 @@
#include "modules/bluetooth.hpp"
#include "util/rfkill.hpp"
#include <linux/rfkill.h>
#include <time.h>
#include <fmt/format.h>
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, "{icon}", 10),
status_("disabled"),
rfkill_{RFKILL_TYPE_BLUETOOTH} {
thread_ = [this] {
dp.emit();
rfkill_.waitForEvent();
};
intervall_thread_ = [this] {
auto now = std::chrono::system_clock::now();
auto timeout = std::chrono::floor<std::chrono::seconds>(now + interval_);
auto diff = std::chrono::seconds(timeout.time_since_epoch().count() % interval_.count());
thread_.sleep_until(timeout - diff);
dp.emit();
};
: ALabel(config, "bluetooth", id, "{icon}", 10), rfkill_{RFKILL_TYPE_BLUETOOTH} {
rfkill_.on_update.connect(sigc::hide(sigc::mem_fun(*this, &Bluetooth::update)));
}
auto waybar::modules::Bluetooth::update() -> void {
if (rfkill_.getState()) {
status_ = "disabled";
} else {
status_ = "enabled";
}
std::string status = rfkill_.getState() ? "disabled" : "enabled";
label_.set_markup(
fmt::format(
format_,
fmt::arg("status", status_),
fmt::arg("icon", getIcon(0, status_))));
fmt::format(format_, fmt::arg("status", status), fmt::arg("icon", getIcon(0, status))));
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, status_);
auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status));
label_.set_tooltip_text(tooltip_text);
} else {
label_.set_tooltip_text(status_);
label_.set_tooltip_text(status);
}
}
}

View File

@ -5,6 +5,7 @@
#include <sstream>
#include <type_traits>
#include "util/ustring_clen.hpp"
#ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h>
#include <locale.h>
@ -13,11 +14,15 @@
using waybar::modules::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60), fixed_time_zone_(false) {
if (config_["timezone"].isString()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018.");
time_zone_ = date::locate_zone(config_["timezone"].asString());
fixed_time_zone_ = true;
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) {
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
time_zone_idx_ = 0;
setTimeZone(config_["timezones"][time_zone_idx_]);
} else {
setTimeZone(config_["timezone"]);
}
if (fixed_time_zone_) {
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
}
if (config_["locale"].isString()) {
@ -71,6 +76,42 @@ auto waybar::modules::Clock::update() -> void {
ALabel::update();
}
bool waybar::modules::Clock::setTimeZone(Json::Value zone_name) {
if (!zone_name.isString() || zone_name.asString().empty()) {
fixed_time_zone_ = false;
return false;
}
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
return true;
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
// defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
return AModule::handleScroll(e);
}
auto dir = AModule::getScrollDir(e);
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (!config_["timezones"].isArray() || config_["timezones"].empty()) {
return true;
}
auto nr_zones = config_["timezones"].size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = time_zone_idx_ + 1;
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1;
}
setTimeZone(config_["timezones"][time_zone_idx_]);
update();
return true;
}
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
const auto ymd = date::year_month_day(daypoint);
@ -99,7 +140,12 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
os << '\n';
}
if (d == curr_day) {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
if (config_["today-format"].isString()) {
auto today_format = config_["today-format"].asString();
os << fmt::format(today_format, date::format("%e", d));
} else {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else {
os << date::format("%e", d);
}
@ -117,12 +163,14 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
do {
if (wd != first_dow) os << ' ';
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto wd_len = wd_ustring.length();
if (wd_len > 2) {
wd_ustring = wd_ustring.substr(0, 2);
wd_len = 2;
auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length();
while (clen > 2) {
wd_ustring = wd_ustring.substr(0, wd_len-1);
wd_len--;
clen = ustring_clen(wd_ustring);
}
const std::string pad(2 - wd_len, ' ');
const std::string pad(2 - clen, ' ');
os << pad << wd_ustring;
} while (++wd != first_dow);
os << "\n";
@ -156,6 +204,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));
}
};

View File

@ -2,8 +2,10 @@
#include <sys/types.h>
#include <sys/sysctl.h>
#include <spdlog/spdlog.h>
#include <cstdlib> // malloc
#include <unistd.h> // sysconf
#include <cmath> // NAN
#if defined(__NetBSD__) || defined(__OpenBSD__)
# include <sys/sched.h>
@ -95,3 +97,12 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
free(cp_time);
return cpuinfo;
}
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
static std::vector<float> frequencies;
if (frequencies.empty()) {
spdlog::warn("cpu/bsd: parseCpuFrequencies is not implemented, expect garbage in {*_frequency}");
frequencies.push_back(NAN);
}
return frequencies;
}

View File

@ -1,5 +1,14 @@
#include "modules/cpu.hpp"
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] {
@ -12,31 +21,60 @@ auto waybar::modules::Cpu::update() -> void {
// TODO: as creating dynamic fmt::arg arrays is buggy we have to calc both
auto cpu_load = getCpuLoad();
auto [cpu_usage, tooltip] = getCpuUsage();
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
if (tooltipEnabled()) {
label_.set_tooltip_text(tooltip);
}
label_.set_markup(fmt::format(format_, fmt::arg("load", cpu_load), fmt::arg("usage", cpu_usage)));
getState(cpu_usage);
auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
store.push_back(fmt::arg("max_frequency", max_frequency));
store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency));
for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i);
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
auto icon_format = fmt::format("icon{}", core_i);
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
}
label_.set_markup(fmt::vformat(format, store));
}
// Call parent update
ALabel::update();
}
uint16_t waybar::modules::Cpu::getCpuLoad() {
double waybar::modules::Cpu::getCpuLoad() {
double load[1];
if (getloadavg(load, 1) != -1) {
return load[0] * 100 / sysconf(_SC_NPROCESSORS_ONLN);
return load[0];
}
throw std::runtime_error("Can't get Cpu load");
}
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() {
if (prev_times_.empty()) {
prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip;
uint16_t usage = 0;
std::vector<uint16_t> usage;
for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i];
@ -44,12 +82,25 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp);
} else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
}
usage.push_back(tmp);
}
prev_times_ = curr_times;
return {usage, tooltip};
}
std::tuple<float, float, float> waybar::modules::Cpu::getCpuFrequency() {
std::vector<float> frequencies = parseCpuFrequencies();
auto [min, max] = std::minmax_element(std::begin(frequencies), std::end(frequencies));
float avg_frequency = std::accumulate(std::begin(frequencies), std::end(frequencies), 0.0) / frequencies.size();
// Round frequencies with double decimal precision to get GHz
float max_frequency = std::ceil(*max / 10.0) / 100.0;
float min_frequency = std::ceil(*min / 10.0) / 100.0;
avg_frequency = std::ceil(avg_frequency / 10.0) / 100.0;
return { max_frequency, min_frequency, avg_frequency };
}

View File

@ -1,3 +1,4 @@
#include <filesystem>
#include "modules/cpu.hpp"
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
@ -27,3 +28,50 @@ std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
}
return cpuinfo;
}
std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
const std::string file_path_ = "/proc/cpuinfo";
std::ifstream info(file_path_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + file_path_);
}
std::vector<float> frequencies;
std::string line;
while (getline(info, line)) {
if (line.substr(0, 7).compare("cpu MHz") != 0) {
continue;
}
std::string frequency_str = line.substr(line.find(":") + 2);
float frequency = std::strtol(frequency_str.c_str(), nullptr, 10);
frequencies.push_back(frequency);
}
info.close();
if (frequencies.size() <= 0) {
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
if (std::filesystem::exists(cpufreq_dir)) {
std::vector<std::string> frequency_files = {
"/cpuinfo_min_freq",
"/cpuinfo_max_freq"
};
for (auto& p: std::filesystem::directory_iterator(cpufreq_dir)) {
for (auto freq_file: frequency_files) {
std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) {
std::string freq_value;
std::ifstream freq(freq_file_path);
if (freq.is_open()) {
getline(freq, freq_value);
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
frequencies.push_back(frequency / 1000);
freq.close();
}
}
}
}
}
}
return frequencies;
}

View File

@ -50,7 +50,6 @@ void waybar::modules::Custom::continuousWorker() {
thread_ = [this, cmd] {
char* buff = nullptr;
size_t len = 0;
bool restart = false;
if (getline(&buff, &len, fp_) == -1) {
int exit_code = 1;
if (fp_) {
@ -63,8 +62,8 @@ void waybar::modules::Custom::continuousWorker() {
spdlog::error("{} stopped unexpectedly, is it endless?", name_);
}
if (config_["restart-interval"].isUInt()) {
restart = true;
pid_ = -1;
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
fp_ = util::command::open(cmd, pid_);
if (!fp_) {
throw std::runtime_error("Unable to open " + cmd);
@ -83,9 +82,6 @@ void waybar::modules::Custom::continuousWorker() {
output_ = {0, output};
dp.emit();
}
if (restart) {
thread_.sleep_for(std::chrono::seconds(config_["restart-interval"].asUInt()));
}
};
}
@ -95,15 +91,21 @@ void waybar::modules::Custom::refresh(int sig) {
}
}
void waybar::modules::Custom::handleEvent() {
if (!config_["exec-on-event"].isBool() || config_["exec-on-event"].asBool()) {
thread_.wake_up();
}
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = ALabel::handleScroll(e);
thread_.wake_up();
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = ALabel::handleToggle(e);
thread_.wake_up();
handleEvent();
return ret;
}
@ -129,9 +131,13 @@ auto waybar::modules::Custom::update() -> void {
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
label_.set_tooltip_markup(str);
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
label_.set_tooltip_markup(tooltip_);
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = label_.get_style_context()->list_classes();

View File

@ -47,16 +47,29 @@ auto waybar::modules::Disk::update() -> void {
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks;
auto format = format_;
auto state = getState(percentage_used);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
label_.set_markup(fmt::format(format
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
label_.set_markup(fmt::format(format_
, stats.f_bavail * 100 / stats.f_blocks
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
if (tooltipEnabled()) {
std::string tooltip_format = "{used} used out of {total} on {path} ({percentage_used}%)";
if (config_["tooltip-format"].isString()) {
@ -67,12 +80,11 @@ auto waybar::modules::Disk::update() -> void {
, fmt::arg("free", free)
, fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks)
, fmt::arg("used", used)
, fmt::arg("percentage_used", (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks)
, fmt::arg("percentage_used", percentage_used)
, fmt::arg("total", total)
, fmt::arg("path", path_)
));
}
event_box_.show();
// Call parent update
ALabel::update();
}

View File

@ -1,16 +1,28 @@
#include "modules/idle_inhibitor.hpp"
#include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/command.hpp"
std::list<waybar::AModule*> waybar::modules::IdleInhibitor::modules;
bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"),
bar_(bar),
status_("deactivated"),
idle_inhibitor_(nullptr),
pid_(-1) {
if (waybar::Client::inst()->idle_inhibit_manager == nullptr) {
throw std::runtime_error("idle-inhibit not available");
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
// Add this to the modules list
waybar::modules::IdleInhibitor::modules.push_back(this);
dp.emit();
}
@ -19,6 +31,10 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
// Remove this from the modules list
waybar::modules::IdleInhibitor::modules.remove(this);
if (pid_ != -1) {
kill(-pid_, 9);
pid_ = -1;
@ -26,11 +42,27 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
}
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
label_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
label_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
}
}
std::string status_text = status ? "activated" : "deactivated";
label_.set_markup(
fmt::format(format_, fmt::arg("status", status_), fmt::arg("icon", getIcon(0, status_))));
label_.get_style_context()->add_class(status_);
fmt::format(format_, fmt::arg("status", status_text), fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_);
label_.set_tooltip_text(status_text);
}
// Call parent update
ALabel::update();
@ -38,18 +70,16 @@ auto waybar::modules::IdleInhibitor::update() -> void {
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) {
label_.get_style_context()->remove_class(status_);
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
status_ = "deactivated";
} else {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
status_ = "activated";
status = !status;
// Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
if (module != this) {
module->update();
}
}
click_param = status_;
}
ALabel::handleToggle(e);
return true;
}

View 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();
}

View File

@ -15,11 +15,11 @@ auto waybar::modules::Memory::update() -> void {
unsigned long memfree;
if (meminfo_.count("MemAvailable")) {
// New kernels (3.4+) have an accurate available memory field.
memfree = meminfo_["MemAvailable"];
memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
} else {
// Old kernel; give a best-effort approximation of available memory.
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
meminfo_["SReclaimable"] - meminfo_["Shmem"];
meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"];
}
if (memtotal > 0 && memfree >= 0) {
@ -28,17 +28,39 @@ auto waybar::modules::Memory::update() -> void {
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
getState(used_ram_percentage);
label_.set_markup(fmt::format(format_,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
if (tooltipEnabled()) {
label_.set_tooltip_text(fmt::format("{:.{}f}Gb used", used_ram_gigabytes, 1));
auto format = format_;
auto state = getState(used_ram_percentage);
if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
}
if (format.empty()) {
event_box_.hide();
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format(format,
used_ram_percentage,
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
}
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage,
fmt::arg("total", total_ram_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
event_box_.show();
} else {
event_box_.hide();
}

View File

@ -1,8 +1,29 @@
#include "modules/memory.hpp"
static unsigned zfsArcSize() {
std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"};
if (zfs_arc_stats.is_open()) {
std::string name;
std::string type;
unsigned long data{0};
std::string line;
while (std::getline(zfs_arc_stats, line)) {
std::stringstream(line) >> name >> type >> data;
if (name == "size") {
return data / 1024; // convert to kB
}
}
}
return 0;
}
void waybar::modules::Memory::parseMeminfo() {
const std::string data_dir_ = "/proc/meminfo";
std::ifstream info(data_dir_);
std::ifstream info(data_dir_);
if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_);
}
@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() {
int64_t value = std::stol(line.substr(posDelim + 1));
meminfo_[name] = value;
}
meminfo_["zfs_size"] = zfsArcSize();
}

View File

@ -1,16 +1,23 @@
#include "modules/mpd.hpp"
#include "modules/mpd/mpd.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include <glibmm/ustring.h>
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
password_(config_["password"].empty() ? "" : config_["password"].asString()),
timeout_(config_["timeout"].isUInt() ? config_["timeout"].asUInt() * 1'000 : 30'000),
connection_(nullptr, &mpd_connection_free),
alternate_connection_(nullptr, &mpd_connection_free),
status_(nullptr, &mpd_status_free),
song_(nullptr, &mpd_song_free) {
if (!config_["port"].isNull() && !config_["port"].isUInt()) {
@ -28,71 +35,33 @@ waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
server_ = config["server"].asCString();
}
event_listener().detach();
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &MPD::handlePlayPause));
}
auto waybar::modules::MPD::update() -> void {
std::lock_guard guard(connection_lock_);
tryConnect();
if (connection_ != nullptr) {
try {
bool wasPlaying = playing();
if(!wasPlaying) {
// Wait until the periodic_updater has stopped
std::lock_guard periodic_guard(periodic_lock_);
}
fetchState();
if (!wasPlaying && playing()) {
periodic_updater().detach();
}
} catch (const std::exception& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
}
setLabel();
context_.update();
// Call parent update
ALabel::update();
}
std::thread waybar::modules::MPD::event_listener() {
return std::thread([this] {
void waybar::modules::MPD::queryMPD() {
if (connection_ != nullptr) {
spdlog::debug("{}: fetching state information", module_name_);
try {
if (connection_ == nullptr) {
// Retry periodically if no connection
dp.emit();
std::this_thread::sleep_for(interval_);
} else {
waitForEvent();
dp.emit();
}
} catch (const std::exception& e) {
if (strcmp(e.what(), "Connection to MPD closed") == 0) {
spdlog::debug("{}: {}", module_name_, e.what());
} else {
spdlog::warn("{}: {}", module_name_, e.what());
}
fetchState();
spdlog::debug("{}: fetch complete", module_name_);
} catch (std::exception const& e) {
spdlog::error("{}: {}", module_name_, e.what());
state_ = MPD_STATE_UNKNOWN;
}
});
dp.emit();
}
}
std::thread waybar::modules::MPD::periodic_updater() {
return std::thread([this] {
std::lock_guard guard(periodic_lock_);
while (connection_ != nullptr && playing()) {
dp.emit();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
});
}
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) {
std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const {
std::string result =
config_["unknown-tag"].isString() ? config_["unknown-tag"].asString() : "N/A";
const char* tag = mpd_song_get_tag(song_.get(), type, idx);
@ -129,8 +98,9 @@ void waybar::modules::MPD::setLabel() {
}
auto format = format_;
std::string artist, album_artist, album, title, date;
Glib::ustring artist, album_artist, album, title;
std::string date;
int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
@ -146,8 +116,8 @@ void waybar::modules::MPD::setLabel() {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else if (paused()) {
format =
config_["format-paused"].isString() ? config_["format-paused"].asString() : config_["format"].asString();
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
@ -159,6 +129,12 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get());
volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
volume = 0;
}
queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
}
@ -171,43 +147,62 @@ void waybar::modules::MPD::setLabel() {
std::string repeatIcon = getOptionIcon("repeat", repeatActivated);
bool singleActivated = mpd_status_get_single(status_.get());
std::string singleIcon = getOptionIcon("single", singleActivated);
if (config_["artist-len"].isInt()) artist = artist.substr(0, config_["artist-len"].asInt());
if (config_["album-artist-len"].isInt()) album_artist = album_artist.substr(0, config_["album-artist-len"].asInt());
if (config_["album-len"].isInt()) album = album.substr(0, config_["album-len"].asInt());
if (config_["title-len"].isInt()) title = title.substr(0,config_["title-len"].asInt());
// TODO: format can fail
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
try {
label_.set_markup(
fmt::format(format,
fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon)));
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
if (tooltipEnabled()) {
std::string tooltip_format;
tooltip_format = config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "MPD (connected)";
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist),
fmt::arg("albumArtist", album_artist),
fmt::arg("album", album),
fmt::arg("title", title),
fmt::arg("date", date),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
try {
auto tooltip_text = fmt::format(tooltip_format,
fmt::arg("artist", artist.raw()),
fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()),
fmt::arg("date", date),
fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}
}
}
std::string waybar::modules::MPD::getStateIcon() {
std::string waybar::modules::MPD::getStateIcon() const {
if (!config_["state-icons"].isObject()) {
return "";
}
@ -229,7 +224,7 @@ std::string waybar::modules::MPD::getStateIcon() {
}
}
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) {
std::string waybar::modules::MPD::getOptionIcon(std::string optionName, bool activated) const {
if (!config_[optionName + "-icons"].isObject()) {
return "";
}
@ -252,25 +247,30 @@ void waybar::modules::MPD::tryConnect() {
}
connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
detail::unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
alternate_connection_ =
unique_connection(mpd_connection_new(server_, port_, timeout_), &mpd_connection_free);
if (connection_ == nullptr || alternate_connection_ == nullptr) {
if (connection_ == nullptr) {
spdlog::error("{}: Failed to connect to MPD", module_name_);
connection_.reset();
alternate_connection_.reset();
return;
}
try {
checkErrors(connection_.get());
spdlog::debug("{}: Connected to MPD", module_name_);
if (!password_.empty()) {
bool res = mpd_run_password(connection_.get(), password_.c_str());
if (!res) {
spdlog::error("{}: Wrong MPD password", module_name_);
connection_.reset();
return;
}
checkErrors(connection_.get());
}
} catch (std::runtime_error& e) {
spdlog::error("{}: Failed to connect to MPD: {}", module_name_, e.what());
connection_.reset();
alternate_connection_.reset();
}
}
@ -283,51 +283,34 @@ void waybar::modules::MPD::checkErrors(mpd_connection* conn) {
case MPD_ERROR_CLOSED:
mpd_connection_clear_error(conn);
connection_.reset();
alternate_connection_.reset();
state_ = MPD_STATE_UNKNOWN;
throw std::runtime_error("Connection to MPD closed");
default:
if (conn) {
auto error_message = mpd_connection_get_error_message(conn);
std::string error(error_message);
mpd_connection_clear_error(conn);
throw std::runtime_error(std::string(error_message));
throw std::runtime_error(error);
}
throw std::runtime_error("Invalid connection");
}
}
void waybar::modules::MPD::fetchState() {
if (connection_ == nullptr) {
spdlog::error("{}: Not connected to MPD", module_name_);
return;
}
auto conn = connection_.get();
status_ = unique_status(mpd_run_status(conn), &mpd_status_free);
status_ = detail::unique_status(mpd_run_status(conn), &mpd_status_free);
checkErrors(conn);
state_ = mpd_status_get_state(status_.get());
checkErrors(conn);
song_ = unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
void waybar::modules::MPD::waitForEvent() {
auto conn = alternate_connection_.get();
// Wait for a player (play/pause), option (random, shuffle, etc.), or playlist
// change
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
checkErrors(conn);
return;
}
// alternate_idle_ = true;
// See issue #277:
// https://github.com/Alexays/Waybar/issues/277
mpd_recv_idle(conn, /* disable_timeout = */ false);
// See issue #281:
// https://github.com/Alexays/Waybar/issues/281
std::lock_guard guard(connection_lock_);
checkErrors(conn);
mpd_response_finish(conn);
song_ = detail::unique_song(mpd_run_current_song(conn), &mpd_song_free);
checkErrors(conn);
}
@ -337,24 +320,13 @@ bool waybar::modules::MPD::handlePlayPause(GdkEventButton* const& e) {
}
if (e->button == 1) {
std::lock_guard guard(connection_lock_);
if (stopped()) {
mpd_run_play(connection_.get());
} else {
mpd_run_toggle_pause(connection_.get());
}
if (state_ == MPD_STATE_PLAY)
context_.pause();
else
context_.play();
} else if (e->button == 3) {
std::lock_guard guard(connection_lock_);
mpd_run_stop(connection_.get());
context_.stop();
}
return true;
}
bool waybar::modules::MPD::stopped() {
return connection_ == nullptr || state_ == MPD_STATE_UNKNOWN || state_ == MPD_STATE_STOP || status_ == nullptr;
}
bool waybar::modules::MPD::playing() { return connection_ != nullptr && state_ == MPD_STATE_PLAY; }
bool waybar::modules::MPD::paused() { return connection_ != nullptr && state_ == MPD_STATE_PAUSE; }

383
src/modules/mpd/state.cpp Normal file
View File

@ -0,0 +1,383 @@
#include "modules/mpd/state.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include "modules/mpd/mpd.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
#include "modules/mpd/state.inl.hpp"
} // namespace waybar::modules
#endif
namespace waybar::modules::detail {
#define IDLE_RUN_NOIDLE_AND_CMD(...) \
if (idle_connection_.connected()) { \
idle_connection_.disconnect(); \
auto conn = ctx_->connection().get(); \
if (!mpd_run_noidle(conn)) { \
if (mpd_connection_get_error(conn) != MPD_ERROR_SUCCESS) { \
spdlog::error("mpd: Idle: failed to unregister for IDLE events"); \
ctx_->checkErrors(conn); \
} \
} \
__VA_ARGS__; \
}
void Idle::play() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_play(conn));
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Idle::pause() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_pause(conn, true));
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Idle::stop() {
IDLE_RUN_NOIDLE_AND_CMD(mpd_run_stop(conn));
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
#undef IDLE_RUN_NOIDLE_AND_CMD
void Idle::update() noexcept {
// This is intentionally blank.
}
void Idle::entry() noexcept {
auto conn = ctx_->connection().get();
assert(conn != nullptr);
if (!mpd_send_idle_mask(
conn, static_cast<mpd_idle>(MPD_IDLE_PLAYER | MPD_IDLE_OPTIONS | MPD_IDLE_QUEUE))) {
ctx_->checkErrors(conn);
spdlog::error("mpd: Idle: failed to register for IDLE events");
} else {
spdlog::debug("mpd: Idle: watching FD");
sigc::slot<bool, Glib::IOCondition const&> idle_slot = sigc::mem_fun(*this, &Idle::on_io);
idle_connection_ =
Glib::signal_io().connect(idle_slot,
mpd_connection_get_fd(conn),
Glib::IO_IN | Glib::IO_PRI | Glib::IO_ERR | Glib::IO_HUP);
}
}
void Idle::exit() noexcept {
if (idle_connection_.connected()) {
idle_connection_.disconnect();
spdlog::debug("mpd: Idle: unwatching FD");
}
}
bool Idle::on_io(Glib::IOCondition const&) {
auto conn = ctx_->connection().get();
// callback should do this:
enum mpd_idle events = mpd_recv_idle(conn, /* ignore_timeout?= */ false);
spdlog::debug("mpd: Idle: recv_idle events -> {}", events);
mpd_response_finish(conn);
try {
ctx_->checkErrors(conn);
} catch (std::exception const& e) {
spdlog::warn("mpd: Idle: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
mpd_state state = ctx_->state();
if (state == MPD_STATE_STOP) {
ctx_->emit();
ctx_->setState(std::make_unique<Stopped>(ctx_));
} else if (state == MPD_STATE_PLAY) {
ctx_->emit();
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (state == MPD_STATE_PAUSE) {
ctx_->emit();
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->emit();
// self transition
ctx_->setState(std::make_unique<Idle>(ctx_));
}
return false;
}
void Playing::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Playing::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 1'000);
spdlog::debug("mpd: Playing: enabled 1 second periodic timer.");
}
void Playing::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Playing: disabled 1 second periodic timer.");
}
}
bool Playing::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
if (!ctx_->is_playing()) {
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false;
}
ctx_->queryMPD();
ctx_->emit();
} catch (std::exception const& e) {
spdlog::warn("mpd: Playing: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
return true;
}
void Playing::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Playing::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Playing::update() noexcept { ctx_->do_update(); }
void Paused::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Paused::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Paused: enabled 200 ms periodic timer.");
}
void Paused::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Paused: disabled 200 ms periodic timer.");
}
}
bool Paused::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Stopped>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Paused: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Paused::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Paused::stop() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_stop(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
void Paused::update() noexcept { ctx_->do_update(); }
void Stopped::entry() noexcept {
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Stopped::on_timer);
timer_connection_ = Glib::signal_timeout().connect(timer_slot, /* milliseconds */ 200);
spdlog::debug("mpd: Stopped: enabled 200 ms periodic timer.");
}
void Stopped::exit() noexcept {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Stopped: disabled 200 ms periodic timer.");
}
}
bool Stopped::on_timer() {
bool rc = true;
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (!ctx_->is_connected()) {
ctx_->setState(std::make_unique<Disconnected>(ctx_));
return false;
}
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_stopped()) {
ctx_->setState(std::make_unique<Idle>(ctx_));
rc = false;
} else if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
rc = false;
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
rc = false;
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Stopped: error: {}", e.what());
ctx_->setState(std::make_unique<Disconnected>(ctx_));
rc = false;
}
return rc;
}
void Stopped::play() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_play(ctx_->connection().get());
}
ctx_->setState(std::make_unique<Playing>(ctx_));
}
void Stopped::pause() {
if (timer_connection_.connected()) {
timer_connection_.disconnect();
mpd_run_pause(ctx_->connection().get(), true);
}
ctx_->setState(std::make_unique<Paused>(ctx_));
}
void Stopped::update() noexcept { ctx_->do_update(); }
void Disconnected::arm_timer(int interval) noexcept {
// unregister timer, if present
disarm_timer();
// register timer
sigc::slot<bool> timer_slot = sigc::mem_fun(*this, &Disconnected::on_timer);
timer_connection_ =
Glib::signal_timeout().connect(timer_slot, interval);
spdlog::debug("mpd: Disconnected: enabled interval timer.");
}
void Disconnected::disarm_timer() noexcept {
// unregister timer, if present
if (timer_connection_.connected()) {
timer_connection_.disconnect();
spdlog::debug("mpd: Disconnected: disabled interval timer.");
}
}
void Disconnected::entry() noexcept {
ctx_->emit();
arm_timer(1'000);
}
void Disconnected::exit() noexcept {
disarm_timer();
}
bool Disconnected::on_timer() {
// Attempt to connect with MPD.
try {
ctx_->tryConnect();
// Success?
if (ctx_->is_connected()) {
ctx_->fetchState();
ctx_->emit();
if (ctx_->is_playing()) {
ctx_->setState(std::make_unique<Playing>(ctx_));
} else if (ctx_->is_paused()) {
ctx_->setState(std::make_unique<Paused>(ctx_));
} else {
ctx_->setState(std::make_unique<Stopped>(ctx_));
}
return false; // do not rearm timer
}
} catch (std::exception const& e) {
spdlog::warn("mpd: Disconnected: error: {}", e.what());
}
arm_timer(ctx_->interval() * 1'000);
return false;
}
void Disconnected::update() noexcept { ctx_->do_update(); }
} // namespace waybar::modules::detail

View File

@ -1,10 +1,14 @@
#include "modules/network.hpp"
#include <spdlog/spdlog.h>
#include <sys/eventfd.h>
#include <linux/if.h>
#include <fstream>
#include <cassert>
#include <optional>
#include "util/format.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
namespace {
@ -15,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) {
@ -78,14 +83,29 @@ 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),
cidr_(-1),
efd_(-1),
ev_fd_(-1),
want_route_dump_(false),
want_link_dump_(false),
want_addr_dump_(false),
dump_in_progress_(false),
cidr_(0),
signal_strength_dbm_(0),
signal_strength_(0),
frequency_(0),
rfkill_{RFKILL_TYPE_WLAN} {
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_WLAN},
#endif
frequency_(0) {
// Start with some "text" in the module's label_, update() will then
// update it. Since the text should be different, update() will be able
// to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown.
label_.set_markup("<s></s>");
auto down_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_DOWN_TOTAL_KEY);
auto up_octets = read_netstat(BANDWIDTH_CATEGORY, BANDWIDTH_UP_TOTAL_KEY);
if (down_octets) {
@ -100,21 +120,35 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
bandwidth_up_total_ = 0;
}
if (!config_["interface"].isString()) {
// "interface" isn't configure, then try to guess the external
// interface currently used for internet.
want_route_dump_ = true;
} else {
// Look for an interface that match "interface"
// and then find the address associated with it.
want_link_dump_ = true;
want_addr_dump_ = true;
}
createEventSocket();
createInfoSocket();
auto default_iface = getPreferredIface(-1, false);
if (default_iface != -1) {
ifid_ = default_iface;
char ifname[IF_NAMESIZE];
if_indextoname(default_iface, ifname);
ifname_ = ifname;
getInterfaceAddress();
}
dp.emit();
// Ask for a dump of interfaces and then addresses to populate our
// information. First the interface dump, and once done, the callback
// will be called again which will ask for addresses dump.
askForStateDump();
worker();
}
waybar::modules::Network::~Network() {
if (ev_fd_ > -1) {
close(ev_fd_);
}
if (efd_ > -1) {
close(efd_);
}
if (ev_sock_ != nullptr) {
nl_socket_drop_membership(ev_sock_, RTNLGRP_LINK);
if (family_ == AF_INET) {
@ -135,17 +169,53 @@ void waybar::modules::Network::createEventSocket() {
ev_sock_ = nl_socket_alloc();
nl_socket_disable_seq_check(ev_sock_);
nl_socket_modify_cb(ev_sock_, NL_CB_VALID, NL_CB_CUSTOM, handleEvents, this);
nl_socket_modify_cb(ev_sock_, NL_CB_FINISH, NL_CB_CUSTOM, handleEventsDone, this);
auto groups = RTMGRP_LINK | (family_ == AF_INET ? RTMGRP_IPV4_IFADDR : RTMGRP_IPV6_IFADDR);
nl_join_groups(ev_sock_, groups); // Deprecated
if (nl_connect(ev_sock_, NETLINK_ROUTE) != 0) {
throw std::runtime_error("Can't connect network socket");
}
if (nl_socket_set_nonblocking(ev_sock_)) {
throw std::runtime_error("Can't set non-blocking on network socket");
}
nl_socket_add_membership(ev_sock_, RTNLGRP_LINK);
if (family_ == AF_INET) {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_IFADDR);
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_IFADDR);
}
if (!config_["interface"].isString()) {
if (family_ == AF_INET) {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV4_ROUTE);
} else {
nl_socket_add_membership(ev_sock_, RTNLGRP_IPV6_ROUTE);
}
}
efd_ = epoll_create1(EPOLL_CLOEXEC);
if (efd_ < 0) {
throw std::runtime_error("Can't create epoll");
}
{
ev_fd_ = eventfd(0, EFD_NONBLOCK);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET;
event.data.fd = ev_fd_;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, ev_fd_, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
{
auto fd = nl_socket_get_fd(ev_sock_);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
event.data.fd = fd;
if (epoll_ctl(efd_, EPOLL_CTL_ADD, fd, &event) == -1) {
throw std::runtime_error("Can't add epoll event");
}
}
}
void waybar::modules::Network::createInfoSocket() {
@ -174,13 +244,43 @@ void waybar::modules::Network::worker() {
}
thread_timer_.sleep_for(interval_);
};
thread_rfkill_ = [this] {
rfkill_.waitForEvent();
{
std::lock_guard<std::mutex> lock(mutex_);
if (ifid_ > 0) {
getInfo();
dp.emit();
#ifdef WANT_RFKILL
rfkill_.on_update.connect([this](auto &) {
/* If we are here, it's likely that the network thread already holds the mutex and will be
* holding it for a next few seconds.
* Let's delegate the update to the timer thread instead of blocking the main thread.
*/
thread_timer_.wake_up();
});
#else
spdlog::warn("Waybar has been built without rfkill support.");
#endif
thread_ = [this] {
std::array<struct epoll_event, EPOLL_MAX> events{};
int ec = epoll_wait(efd_, events.data(), EPOLL_MAX, -1);
if (ec > 0) {
for (auto i = 0; i < ec; i++) {
if (events[i].data.fd == nl_socket_get_fd(ev_sock_)) {
int rc = 0;
// Read as many message as possible, until the socket blocks
while (true) {
errno = 0;
rc = nl_recvmsgs_default(ev_sock_);
if (rc == -NLE_AGAIN || errno == EAGAIN) {
rc = 0;
break;
}
}
if (rc < 0) {
spdlog::error("nl_recvmsgs_default error: {}", nl_geterror(-rc));
thread_.stop();
break;
}
} else {
thread_.stop();
break;
}
}
}
};
@ -188,10 +288,13 @@ void waybar::modules::Network::worker() {
const std::string waybar::modules::Network::getNetworkState() const {
if (ifid_ == -1) {
#ifdef WANT_RFKILL
if (rfkill_.getState())
return "disabled";
#endif
return "disconnected";
}
if (!carrier_) return "disconnected";
if (ipaddr_.empty()) return "linked";
if (essid_.empty()) return "ethernet";
return "wifi";
@ -221,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();
@ -241,6 +348,7 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_),
fmt::arg("icon", getIcon(signal_strength_, state_)),
@ -269,6 +377,7 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_),
fmt::arg("ipaddr", ipaddr_),
fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_),
fmt::arg("frequency", frequency_),
fmt::arg("icon", getIcon(signal_strength_, state_)),
@ -277,7 +386,7 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")));
if (label_.get_tooltip_text() != text) {
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_text(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
@ -289,349 +398,350 @@ auto waybar::modules::Network::update() -> void {
ALabel::update();
}
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
int waybar::modules::Network::getExternalInterface(int skip_idx) const {
static const uint32_t route_buffer_size = 8192;
struct nlmsghdr * hdr = nullptr;
struct rtmsg * rt = nullptr;
char resp[route_buffer_size] = {0};
int ifidx = -1;
/* Prepare request. */
constexpr uint32_t reqlen = NLMSG_SPACE(sizeof(*rt));
char req[reqlen] = {0};
/* Build the RTM_GETROUTE request. */
hdr = reinterpret_cast<struct nlmsghdr *>(req);
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETROUTE;
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
rt->rtm_family = family_;
rt->rtm_table = RT_TABLE_MAIN;
/* Issue the query. */
if (netlinkRequest(req, reqlen) < 0) {
goto out;
}
/* Read the response(s).
*
* WARNING: All the packets generated by the request must be consumed (as in,
* consume responses till NLMSG_DONE/NLMSG_ERROR is encountered).
*/
do {
auto len = netlinkResponse(resp, route_buffer_size);
if (len < 0) {
goto out;
}
/* Parse the response payload into netlink messages. */
for (hdr = reinterpret_cast<struct nlmsghdr *>(resp); NLMSG_OK(hdr, len);
hdr = NLMSG_NEXT(hdr, len)) {
if (hdr->nlmsg_type == NLMSG_DONE) {
goto out;
}
if (hdr->nlmsg_type == NLMSG_ERROR) {
/* Even if we found the interface index, something is broken with the
* netlink socket, so return an error.
*/
ifidx = -1;
goto out;
}
/* If we found the correct answer, skip parsing the attributes. */
if (ifidx != -1) {
continue;
}
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
*/
rt = static_cast<struct rtmsg *>(NLMSG_DATA(hdr));
if (rt->rtm_table != RT_TABLE_MAIN) {
continue;
}
/* Parse all the attributes for a single routing table entry. */
struct rtattr *attr = RTM_RTA(rt);
uint64_t attrlen = RTM_PAYLOAD(hdr);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is
* set, that it is all 0s.
*/
switch (attr->rta_type) {
case RTA_GATEWAY:
/* The gateway of the route.
*
* If someone every needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
has_gateway = true;
break;
case RTA_DST: {
/* The destination address.
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (family_ == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char *)RTA_DATA(attr) + i);
}
has_destination = (c == 0);
break;
}
case RTA_OIF:
/* The output interface index. */
temp_idx = *static_cast<int *>(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 && temp_idx != skip_idx) {
ifidx = temp_idx;
break;
}
}
} while (true);
out:
return ifidx;
}
void waybar::modules::Network::getInterfaceAddress() {
struct ifaddrs *ifaddr, *ifa;
cidr_ = 0;
int success = getifaddrs(&ifaddr);
if (success != 0) {
return;
}
ifa = ifaddr;
while (ifa != nullptr) {
if (ifa->ifa_addr != nullptr && ifa->ifa_addr->sa_family == family_ &&
ifa->ifa_name == ifname_) {
char ipaddr[INET6_ADDRSTRLEN];
char netmask[INET6_ADDRSTRLEN];
unsigned int cidr = 0;
if (family_ == AF_INET) {
ipaddr_ = inet_ntop(AF_INET,
&reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr)->sin_addr,
ipaddr,
INET_ADDRSTRLEN);
auto net_addr = reinterpret_cast<struct sockaddr_in *>(ifa->ifa_netmask);
netmask_ = inet_ntop(AF_INET, &net_addr->sin_addr, netmask, INET_ADDRSTRLEN);
unsigned int cidrRaw = net_addr->sin_addr.s_addr;
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
} else {
ipaddr_ = inet_ntop(AF_INET6,
&reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr)->sin6_addr,
ipaddr,
INET6_ADDRSTRLEN);
auto net_addr = reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_netmask);
netmask_ = inet_ntop(AF_INET6, &net_addr->sin6_addr, netmask, INET6_ADDRSTRLEN);
for (size_t i = 0; i < sizeof(net_addr->sin6_addr.s6_addr); ++i) {
unsigned char cidrRaw = net_addr->sin6_addr.s6_addr[i];
while (cidrRaw) {
cidr += cidrRaw & 1;
cidrRaw >>= 1;
}
}
}
cidr_ = cidr;
break;
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
}
int waybar::modules::Network::netlinkRequest(void *req, uint32_t reqlen, uint32_t groups) const {
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = {req, reqlen};
struct msghdr msg = {
.msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
return sendmsg(nl_socket_get_fd(ev_sock_), &msg, 0);
}
int waybar::modules::Network::netlinkResponse(void *resp, uint32_t resplen, uint32_t groups) const {
struct sockaddr_nl sa = {};
sa.nl_family = AF_NETLINK;
sa.nl_groups = groups;
struct iovec iov = {resp, resplen};
struct msghdr msg = {
.msg_name = &sa,
.msg_namelen = sizeof(sa),
.msg_iov = &iov,
.msg_iovlen = 1,
};
auto ret = recvmsg(nl_socket_get_fd(ev_sock_), &msg, 0);
if (msg.msg_flags & MSG_TRUNC) {
return -1;
}
return ret;
}
bool waybar::modules::Network::checkInterface(struct ifinfomsg *rtif, std::string name) {
bool waybar::modules::Network::checkInterface(std::string name) {
if (config_["interface"].isString()) {
return config_["interface"].asString() == name ||
wildcardMatch(config_["interface"].asString(), name);
}
// getExternalInterface may need some delay to detect external interface
for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) {
auto external_iface = getExternalInterface();
if (external_iface > 0) {
return external_iface == rtif->ifi_index;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
return false;
}
int waybar::modules::Network::getPreferredIface(int skip_idx, bool wait) const {
int ifid = -1;
if (config_["interface"].isString()) {
ifid = if_nametoindex(config_["interface"].asCString());
if (ifid > 0) {
return ifid;
} else {
// Try with wildcard
struct ifaddrs *ifaddr, *ifa;
int success = getifaddrs(&ifaddr);
if (success != 0) {
return -1;
}
ifa = ifaddr;
ifid = -1;
while (ifa != nullptr) {
if (wildcardMatch(config_["interface"].asString(), ifa->ifa_name)) {
ifid = if_nametoindex(ifa->ifa_name);
break;
}
ifa = ifa->ifa_next;
}
freeifaddrs(ifaddr);
return ifid;
}
}
// getExternalInterface may need some delay to detect external interface
for (uint8_t tries = 0; tries < MAX_RETRY; tries += 1) {
ifid = getExternalInterface(skip_idx);
if (ifid > 0) {
return ifid;
}
if (wait) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
return -1;
}
void waybar::modules::Network::clearIface() {
ifid_ = -1;
ifname_.clear();
essid_.clear();
ipaddr_.clear();
gwaddr_.clear();
netmask_.clear();
carrier_ = false;
cidr_ = 0;
signal_strength_dbm_ = 0;
signal_strength_ = 0;
frequency_ = 0;
}
void waybar::modules::Network::checkNewInterface(struct ifinfomsg *rtif) {
auto new_iface = getPreferredIface(rtif->ifi_index);
if (new_iface != -1) {
ifid_ = new_iface;
char ifname[IF_NAMESIZE];
if_indextoname(new_iface, ifname);
ifname_ = ifname;
getInterfaceAddress();
thread_timer_.wake_up();
} else {
ifid_ = -1;
dp.emit();
}
}
int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
std::lock_guard<std::mutex> lock(net->mutex_);
auto nh = nlmsg_hdr(msg);
auto ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
if (nh->nlmsg_type == RTM_DELADDR) {
// Check for valid interface
if (ifi->ifi_index == net->ifid_) {
net->ipaddr_.clear();
net->netmask_.clear();
net->cidr_ = 0;
if (!(ifi->ifi_flags & IFF_RUNNING)) {
net->clearIface();
// Check for a new interface and get info
net->checkNewInterface(ifi);
} else {
net->dp.emit();
}
bool is_del_event = false;
switch (nh->nlmsg_type) {
case RTM_DELLINK:
is_del_event = true;
case RTM_NEWLINK: {
struct ifinfomsg *ifi = static_cast<struct ifinfomsg *>(NLMSG_DATA(nh));
ssize_t attrlen = IFLA_PAYLOAD(nh);
struct rtattr *ifla = IFLA_RTA(ifi);
const char *ifname = NULL;
size_t ifname_len = 0;
std::optional<bool> carrier;
if (net->ifid_ != -1 && ifi->ifi_index != net->ifid_) {
return NL_OK;
}
} else if (nh->nlmsg_type == RTM_NEWLINK || nh->nlmsg_type == RTM_DELLINK) {
char ifname[IF_NAMESIZE];
if_indextoname(ifi->ifi_index, ifname);
// Check for valid interface
if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) {
net->ifname_ = ifname;
net->ifid_ = ifi->ifi_index;
// Get Iface and WIFI info
net->getInterfaceAddress();
net->thread_timer_.wake_up();
return NL_OK;
} else if (ifi->ifi_index == net->ifid_ &&
(!(ifi->ifi_flags & IFF_RUNNING) || !(ifi->ifi_flags & IFF_UP) ||
!net->checkInterface(ifi, ifname))) {
// 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();
// Check for a new interface and get info
net->checkNewInterface(ifi);
net->dp.emit();
net->want_route_dump_ = true;
net->askForStateDump();
return NL_OK;
}
} else {
char ifname[IF_NAMESIZE];
if_indextoname(ifi->ifi_index, ifname);
// Auto detected network can also be assigned here
if (ifi->ifi_index != net->ifid_ && net->checkInterface(ifi, ifname)) {
// If iface is different, clear data
if (ifi->ifi_index != net->ifid_) {
net->clearIface();
for (; RTA_OK(ifla, attrlen); ifla = RTA_NEXT(ifla, attrlen)) {
switch (ifla->rta_type) {
case IFLA_IFNAME:
ifname = static_cast<const char *>(RTA_DATA(ifla));
ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0
break;
case IFLA_CARRIER: {
carrier = *(char*)RTA_DATA(ifla) == 1;
break;
}
}
net->ifname_ = ifname;
net->ifid_ = ifi->ifi_index;
}
// Check for valid interface
if (ifi->ifi_index == net->ifid_) {
// Get Iface and WIFI info
net->getInterfaceAddress();
net->thread_timer_.wake_up();
if (!is_del_event && ifi->ifi_index == net->ifid_) {
// Update interface information
if (net->ifname_.empty() && ifname != NULL) {
std::string new_ifname (ifname, ifname_len);
net->ifname_ = new_ifname;
}
if (carrier.has_value()) {
if (net->carrier_ != *carrier) {
if (*carrier) {
// Ask for WiFi information
net->thread_timer_.wake_up();
} else {
// clear state related to WiFi connection
net->essid_.clear();
net->signal_strength_dbm_ = 0;
net->signal_strength_ = 0;
net->frequency_ = 0;
}
}
net->carrier_ = carrier.value();
}
} else if (!is_del_event && net->ifid_ == -1) {
// Checking if it's an interface we care about.
std::string new_ifname (ifname, ifname_len);
if (net->checkInterface(new_ifname)) {
spdlog::debug("network: selecting new interface {}/{}", new_ifname, ifi->ifi_index);
net->ifname_ = new_ifname;
net->ifid_ = ifi->ifi_index;
if (carrier.has_value()) {
net->carrier_ = carrier.value();
}
net->thread_timer_.wake_up();
/* An address for this new interface should be received via an
* RTM_NEWADDR event either because we ask for a dump of both links
* and addrs, or because this interface has just been created and
* the addr will be sent after the RTM_NEWLINK event.
* So we don't need to do anything. */
}
} else if (is_del_event && net->ifid_ >= 0) {
// Our interface has been deleted, start looking/waiting for one we care.
spdlog::debug("network: interface {}/{} deleted", net->ifname_, net->ifid_);
net->clearIface();
net->dp.emit();
}
break;
}
case RTM_DELADDR:
is_del_event = true;
case RTM_NEWADDR: {
struct ifaddrmsg *ifa = static_cast<struct ifaddrmsg *>(NLMSG_DATA(nh));
ssize_t attrlen = IFA_PAYLOAD(nh);
struct rtattr *ifa_rta = IFA_RTA(ifa);
if ((int)ifa->ifa_index != net->ifid_) {
return NL_OK;
}
if (ifa->ifa_family != net->family_) {
return NL_OK;
}
// We ignore address mark as scope for the link or host,
// which should leave scope global addresses.
if (ifa->ifa_scope >= RT_SCOPE_LINK) {
return NL_OK;
}
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) {
case IFA_ADDRESS: {
char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta),
ipaddr, sizeof (ipaddr));
net->cidr_ = ifa->ifa_prefixlen;
switch (ifa->ifa_family) {
case AF_INET: {
struct in_addr netmask;
netmask.s_addr = htonl(~0 << (32 - ifa->ifa_prefixlen));
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask,
ipaddr, sizeof (ipaddr));
}
case AF_INET6: {
struct in6_addr netmask;
for (int i = 0; i < 16; i++) {
int v = (i + 1) * 8 - ifa->ifa_prefixlen;
if (v < 0) v = 0;
if (v > 8) v = 8;
netmask.s6_addr[i] = ~0 << v;
}
net->netmask_ = inet_ntop(ifa->ifa_family, &netmask,
ipaddr, sizeof (ipaddr));
}
}
spdlog::debug("network: {}, new addr {}/{}", net->ifname_, net->ipaddr_, net->cidr_);
} else {
net->ipaddr_.clear();
net->cidr_ = 0;
net->netmask_.clear();
spdlog::debug("network: {} addr deleted {}/{}",
net->ifname_,
inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta),
ipaddr, sizeof (ipaddr)),
ifa->ifa_prefixlen);
}
net->dp.emit();
break;
}
}
}
break;
}
return NL_SKIP;
char temp_gw_addr[INET6_ADDRSTRLEN];
case RTM_DELROUTE:
is_del_event = true;
case RTM_NEWROUTE: {
// Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
// to find the interface used to reach the outside world
struct rtmsg *rtm = static_cast<struct rtmsg *>(NLMSG_DATA(nh));
ssize_t attrlen = RTM_PAYLOAD(nh);
struct rtattr *attr = RTM_RTA(rtm);
bool has_gateway = false;
bool has_destination = false;
int temp_idx = -1;
uint32_t priority = 0;
/* Find the message(s) concerting the main routing table, each message
* corresponds to a single routing table entry.
*/
if (rtm->rtm_table != RT_TABLE_MAIN) {
return NL_OK;
}
/* Parse all the attributes for a single routing table entry. */
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
/* Determine if this routing table entry corresponds to the default
* route by seeing if it has a gateway, and if a destination addr is
* set, that it is all 0s.
*/
switch(attr->rta_type) {
case RTA_GATEWAY:
/* The gateway of the route.
*
* If someone ever needs to figure out the gateway address as well,
* it's here as the attribute payload.
*/
inet_ntop(net->family_, RTA_DATA(attr), temp_gw_addr, sizeof(temp_gw_addr));
has_gateway = true;
break;
case RTA_DST: {
/* The destination address.
* Should be either missing, or maybe all 0s. Accept both.
*/
const uint32_t nr_zeroes = (net->family_ == AF_INET) ? 4 : 16;
unsigned char c = 0;
size_t dstlen = RTA_PAYLOAD(attr);
if (dstlen != nr_zeroes) {
break;
}
for (uint32_t i = 0; i < dstlen; i += 1) {
c |= *((unsigned char *)RTA_DATA(attr) + i);
}
has_destination = (c == 0);
break;
}
case RTA_OIF:
/* 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;
}
}
// 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;
net->gwaddr_ = temp_gw_addr;
spdlog::debug("network: new default route via {} on if{} metric {}", temp_gw_addr, 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_
&& 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;
}
}
return NL_OK;
}
void waybar::modules::Network::askForStateDump(void) {
/* We need to wait until the current dump is done before sending new
* messages. handleEventsDone() is called when a dump is done. */
if (dump_in_progress_)
return;
struct rtgenmsg rt_hdr = {
.rtgen_family = AF_UNSPEC,
};
if (want_route_dump_) {
rt_hdr.rtgen_family = family_;
nl_send_simple(ev_sock_, RTM_GETROUTE, NLM_F_DUMP,
&rt_hdr, sizeof (rt_hdr));
want_route_dump_ = false;
dump_in_progress_ = true;
} else if (want_link_dump_) {
nl_send_simple(ev_sock_, RTM_GETLINK, NLM_F_DUMP,
&rt_hdr, sizeof (rt_hdr));
want_link_dump_ = false;
dump_in_progress_ = true;
} else if (want_addr_dump_) {
rt_hdr.rtgen_family = family_;
nl_send_simple(ev_sock_, RTM_GETADDR, NLM_F_DUMP,
&rt_hdr, sizeof (rt_hdr));
want_addr_dump_ = false;
dump_in_progress_ = true;
}
}
int waybar::modules::Network::handleEventsDone(struct nl_msg *msg, void *data) {
auto net = static_cast<waybar::modules::Network *>(data);
net->dump_in_progress_ = false;
net->askForStateDump();
return NL_OK;
}
int waybar::modules::Network::handleScan(struct nl_msg *msg, void *data) {

Some files were not shown because too many files have changed in this diff Show More