diff --git a/.envrc.sample b/.envrc.sample
new file mode 100644
index 00000000..3550a30f
--- /dev/null
+++ b/.envrc.sample
@@ -0,0 +1 @@
+use flake
diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml
index a6da7ef7..550f9453 100644
--- a/.github/workflows/freebsd.yml
+++ b/.github/workflows/freebsd.yml
@@ -9,7 +9,7 @@ jobs:
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
runs-on: macos-12
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0
with:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e9f1656f..d11d2ccc 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -6,7 +6,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.13
with:
source: '.'
diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml
index 4c77c3af..1c00a5ed 100644
--- a/.github/workflows/linux.yml
+++ b/.github/workflows/linux.yml
@@ -13,16 +13,20 @@ jobs:
- fedora
- opensuse
- gentoo
+ cpp_std: [c++17]
+ include:
+ - distro: fedora
+ cpp_std: c++20
runs-on: ubuntu-latest
container:
image: alexays/waybar:${{ matrix.distro }}
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: configure
- run: meson -Dman-pages=enabled build
+ run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build
- name: build
run: ninja -C build
- name: test
- run: meson test -C build --no-rebuild --print-errorlogs --suite waybar
+ run: make test
diff --git a/.gitignore b/.gitignore
index 56a2f73d..4d7babf3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,8 @@
*~
vgcore.*
/.vscode
+/.idea
+/.cache
*.swp
packagecache
/subprojects/**/
@@ -41,3 +43,4 @@ packagecache
*.exe
*.out
*.app
+/.direnv/
diff --git a/Dockerfiles/alpine b/Dockerfiles/alpine
index 03836aaa..d9a3dd58 100644
--- a/Dockerfiles/alpine
+++ b/Dockerfiles/alpine
@@ -2,4 +2,4 @@
FROM alpine:latest
-RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata
+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 tzdata playerctl-dev
diff --git a/Dockerfiles/archlinux b/Dockerfiles/archlinux
index 40a1b2e3..cab4146b 100644
--- a/Dockerfiles/archlinux
+++ b/Dockerfiles/archlinux
@@ -3,4 +3,5 @@
FROM archlinux:base-devel
RUN pacman -Syu --noconfirm && \
- pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon
+ pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl && \
+ sed -Ei 's/#(en_(US|GB)\.UTF)/\1/' /etc/locale.gen && locale-gen
diff --git a/Dockerfiles/debian b/Dockerfiles/debian
index 026d8fdb..578588c7 100644
--- a/Dockerfiles/debian
+++ b/Dockerfiles/debian
@@ -3,5 +3,5 @@
FROM debian:sid
RUN apt-get update && \
- apt-get install -y build-essential meson ninja-build git pkg-config libinput10 libpugixml-dev libinput-dev wayland-protocols libwayland-client0 libwayland-cursor0 libwayland-dev libegl1-mesa-dev libgles2-mesa-dev libgbm-dev libxkbcommon-dev libudev-dev libpixman-1-dev libgtkmm-3.0-dev libjsoncpp-dev scdoc libdbusmenu-gtk3-dev libnl-3-dev libnl-genl-3-dev libpulse-dev libmpdclient-dev gobject-introspection libgirepository1.0-dev libxkbcommon-dev libxkbregistry-dev libxkbregistry0 && \
+ 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 libplayerctl-dev && \
apt-get clean
diff --git a/Dockerfiles/fedora b/Dockerfiles/fedora
index a61dcd3e..5892159c 100644
--- a/Dockerfiles/fedora
+++ b/Dockerfiles/fedora
@@ -2,11 +2,33 @@
FROM fedora:latest
-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)' && \
+RUN dnf install -y @c-development \
+ git-core glibc-langpack-en meson scdoc \
+ 'pkgconfig(catch2)' \
+ '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(jack)' \
+ 'pkgconfig(jsoncpp)' \
+ 'pkgconfig(libevdev)' \
+ 'pkgconfig(libinput)' \
+ 'pkgconfig(libmpdclient)' \
+ 'pkgconfig(libnl-3.0)' \
+ 'pkgconfig(libnl-genl-3.0)' \
+ 'pkgconfig(libpulse)' \
+ 'pkgconfig(libudev)' \
+ 'pkgconfig(playerctl)' \
+ 'pkgconfig(pugixml)' \
+ 'pkgconfig(sigc++-2.0)' \
+ 'pkgconfig(spdlog)' \
+ 'pkgconfig(upower-glib)' \
+ 'pkgconfig(wayland-client)' \
+ 'pkgconfig(wayland-cursor)' \
+ 'pkgconfig(wayland-protocols)' \
+ 'pkgconfig(wireplumber-0.4)' \
+ 'pkgconfig(xkbregistry)' && \
dnf clean all -y
diff --git a/Dockerfiles/gentoo b/Dockerfiles/gentoo
index 536ef637..f2ec0dc9 100644
--- a/Dockerfiles/gentoo
+++ b/Dockerfiles/gentoo
@@ -6,6 +6,6 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa
emerge --sync && \
eselect news read --quiet new 1>/dev/null 2>&1 && \
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \
- USE="wayland gtk3 gtk -doc X" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \
+ USE="wayland gtk3 gtk -doc X pulseaudio minimal" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \
x11-libs/gtk+:3 dev-libs/libdbusmenu dev-libs/libnl sys-power/upower media-libs/libpulse dev-libs/libevdev media-libs/libmpdclient \
- media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc
+ media-sound/sndio gui-libs/gtk-layer-shell app-text/scdoc media-sound/playerctl dev-libs/iniparser sci-libs/fftw
diff --git a/Dockerfiles/opensuse b/Dockerfiles/opensuse
index 49dea272..bdb42fbf 100644
--- a/Dockerfiles/opensuse
+++ b/Dockerfiles/opensuse
@@ -6,4 +6,4 @@ 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 libxkbregistry-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 playerctl-devel
diff --git a/Makefile b/Makefile
index 94f8ee6e..b1dbfc6e 100644
--- a/Makefile
+++ b/Makefile
@@ -19,5 +19,9 @@ run: build
debug-run: build-debug
./build/waybar --log-level debug
+test:
+ meson test -C build --no-rebuild --verbose --suite waybar
+.PHONY: test
+
clean:
rm -rf build
diff --git a/README.md b/README.md
index 2b57b139..718ceb44 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# 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)
![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
> Highly customizable Wayland bar for Sway and Wlroots based compositors.
-> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
+> Available in Arch [community](https://www.archlinux.org/packages/extra/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar).
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
@@ -9,6 +9,7 @@
- Sway (Workspaces, Binding mode, Focused window name)
- River (Mapping mode, Tags, Focused window name)
- Hyprland (Focused window name)
+- DWL (Tags) [requires dwl ipc patch](https://github.com/djpohly/dwl/wiki/ipc)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time
- Battery
diff --git a/default.nix b/default.nix
new file mode 100644
index 00000000..2cccff28
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,10 @@
+(import
+ (
+ let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in
+ fetchTarball {
+ url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
+ sha256 = lock.nodes.flake-compat.locked.narHash;
+ }
+ )
+ { src = ./.; }
+).defaultNix
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 00000000..b10c9bf7
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,111 @@
+{
+ "nodes": {
+ "devshell": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ },
+ "locked": {
+ "lastModified": 1676293499,
+ "narHash": "sha256-uIOTlTxvrXxpKeTvwBI1JGDGtCxMXE3BI0LFwoQMhiQ=",
+ "owner": "numtide",
+ "repo": "devshell",
+ "rev": "71e3022e3ab20bbf1342640547ef5bc14fb43bf4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "devshell",
+ "type": "github"
+ }
+ },
+ "flake-compat": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1673956053,
+ "narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
+ "type": "github"
+ },
+ "original": {
+ "owner": "edolstra",
+ "repo": "flake-compat",
+ "type": "github"
+ }
+ },
+ "flake-utils": {
+ "locked": {
+ "lastModified": 1642700792,
+ "narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "flake-utils_2": {
+ "locked": {
+ "lastModified": 1676283394,
+ "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1643381941,
+ "narHash": "sha256-pHTwvnN4tTsEKkWlXQ8JMY423epos8wUOhthpwJjtpc=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "5efc8ca954272c4376ac929f4c5ffefcc20551d5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "nixpkgs_2": {
+ "locked": {
+ "lastModified": 1676300157,
+ "narHash": "sha256-1HjRzfp6LOLfcj/HJHdVKWAkX9QRAouoh6AjzJiIerU=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "545c7a31e5dedea4a6d372712a18e00ce097d462",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "ref": "nixos-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "devshell": "devshell",
+ "flake-compat": "flake-compat",
+ "flake-utils": "flake-utils_2",
+ "nixpkgs": "nixpkgs_2"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 00000000..97f4ed57
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,93 @@
+{
+ description = "Highly customizable Wayland bar for Sway and Wlroots based compositors.";
+
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ devshell.url = "github:numtide/devshell";
+ flake-utils.url = "github:numtide/flake-utils";
+ flake-compat = {
+ url = "github:edolstra/flake-compat";
+ flake = false;
+ };
+ };
+
+ outputs = { self, flake-utils, devshell, nixpkgs, flake-compat }:
+ let
+ inherit (nixpkgs) lib;
+ genSystems = lib.genAttrs [
+ "x86_64-linux"
+ ];
+
+ pkgsFor = genSystems (system:
+ import nixpkgs {
+ inherit system;
+ });
+
+ mkDate = longDate: (lib.concatStringsSep "-" [
+ (builtins.substring 0 4 longDate)
+ (builtins.substring 4 2 longDate)
+ (builtins.substring 6 2 longDate)
+ ]);
+ in
+ {
+ overlays.default = _: prev: {
+ waybar = prev.callPackage ./nix/default.nix {
+ version = prev.waybar.version + "+date=" + (mkDate (self.lastModifiedDate or "19700101")) + "_" + (self.shortRev or "dirty");
+ };
+ };
+ packages = genSystems
+ (system:
+ (self.overlays.default null pkgsFor.${system})
+ // {
+ default = self.packages.${system}.waybar;
+ });
+ } //
+ flake-utils.lib.eachDefaultSystem (system: {
+ devShell =
+ let pkgs = import nixpkgs {
+ inherit system;
+
+ overlays = [ devshell.overlay ];
+ };
+ in
+ pkgs.devshell.mkShell {
+ imports = [ "${pkgs.devshell.extraModulesDir}/language/c.nix" ];
+ commands = [
+ {
+ package = pkgs.devshell.cli;
+ help = "Per project developer environments";
+ }
+ ];
+ devshell.packages = with pkgs; [
+ clang-tools
+ gdb
+ # from nativeBuildInputs
+ gnumake
+ meson
+ ninja
+ pkg-config
+ scdoc
+ ] ++ (map lib.getDev [
+ # from buildInputs
+ wayland wlroots gtkmm3 libsigcxx jsoncpp spdlog gtk-layer-shell howard-hinnant-date libxkbcommon
+ # optional dependencies
+ gobject-introspection glib playerctl python3.pkgs.pygobject3
+ libevdev libinput libjack2 libmpdclient playerctl libnl
+ libpulseaudio sndio sway libdbusmenu-gtk3 udev upower wireplumber
+
+ # from propagated build inputs?
+ at-spi2-atk atkmm cairo cairomm catch2 fmt_8 fontconfig
+ gdk-pixbuf glibmm gtk3 harfbuzz pango pangomm wayland-protocols
+ ]);
+ env = with pkgs; [
+ { name = "CPLUS_INCLUDE_PATH"; prefix = "$DEVSHELL_DIR/include"; }
+ { name = "PKG_CONFIG_PATH"; prefix = "$DEVSHELL_DIR/lib/pkgconfig"; }
+ { name = "PKG_CONFIG_PATH"; prefix = "$DEVSHELL_DIR/share/pkgconfig"; }
+ { name = "PATH"; prefix = "${wayland.bin}/bin"; }
+ { name = "LIBRARY_PATH"; prefix = "${lib.getLib sndio}/lib"; }
+ { name = "LIBRARY_PATH"; prefix = "${lib.getLib zlib}/lib"; }
+ { name = "LIBRARY_PATH"; prefix = "${lib.getLib howard-hinnant-date}/lib"; }
+ ];
+ };
+ });
+}
diff --git a/include/AIconLabel.hpp b/include/AIconLabel.hpp
index aeeba6cb..054d031a 100644
--- a/include/AIconLabel.hpp
+++ b/include/AIconLabel.hpp
@@ -13,7 +13,7 @@ class AIconLabel : public ALabel {
const std::string &format, uint16_t interval = 0, bool ellipsize = false,
bool enable_click = false, bool enable_scroll = false);
virtual ~AIconLabel() = default;
- virtual auto update() -> void;
+ auto update() -> void override;
protected:
Gtk::Image image_;
diff --git a/include/ALabel.hpp b/include/ALabel.hpp
index 14f82242..888c65a8 100644
--- a/include/ALabel.hpp
+++ b/include/ALabel.hpp
@@ -14,7 +14,7 @@ class ALabel : public AModule {
uint16_t interval = 0, bool ellipsize = false, bool enable_click = false,
bool enable_scroll = false);
virtual ~ALabel() = default;
- virtual auto update() -> void;
+ auto update() -> void override;
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
virtual std::string getIcon(uint16_t, const std::vector &alts, uint16_t max = 0);
@@ -25,7 +25,7 @@ class ALabel : public AModule {
bool alt_ = false;
std::string default_format_;
- virtual bool handleToggle(GdkEventButton *const &e);
+ bool handleToggle(GdkEventButton *const &e) override;
virtual std::string getState(uint8_t value, bool lesser = false);
};
diff --git a/include/AModule.hpp b/include/AModule.hpp
index 357f70ec..9b16076b 100644
--- a/include/AModule.hpp
+++ b/include/AModule.hpp
@@ -11,15 +11,20 @@ namespace waybar {
class AModule : public IModule {
public:
- AModule(const Json::Value &, const std::string &, const std::string &, bool enable_click = false,
- bool enable_scroll = false);
virtual ~AModule();
- virtual auto update() -> void;
- virtual operator Gtk::Widget &();
+ auto update() -> void override;
+ virtual auto refresh(int) -> void{};
+ operator Gtk::Widget &() override;
+ auto doAction(const std::string &name) -> void override;
Glib::Dispatcher dp;
protected:
+ // Don't need to make an object directly
+ // Derived classes are able to use it
+ AModule(const Json::Value &, const std::string &, const std::string &, bool enable_click = false,
+ bool enable_scroll = false);
+
enum SCROLL_DIR { NONE, UP, DOWN, LEFT, RIGHT };
SCROLL_DIR getScrollDir(GdkEventScroll *e);
@@ -36,6 +41,7 @@ class AModule : public IModule {
std::vector pid_;
gdouble distance_scrolled_y_;
gdouble distance_scrolled_x_;
+ std::map eventActionMap_;
static const inline std::map, std::string> eventMap_{
{std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS), "on-click"},
{std::make_pair(1, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click"},
diff --git a/include/IModule.hpp b/include/IModule.hpp
index 961a4612..b76c33e3 100644
--- a/include/IModule.hpp
+++ b/include/IModule.hpp
@@ -9,6 +9,7 @@ class IModule {
virtual ~IModule() = default;
virtual auto update() -> void = 0;
virtual operator Gtk::Widget&() = 0;
+ virtual auto doAction(const std::string& name) -> void = 0;
};
} // namespace waybar
diff --git a/include/factory.hpp b/include/factory.hpp
index d69930f9..90d0ac1d 100644
--- a/include/factory.hpp
+++ b/include/factory.hpp
@@ -1,7 +1,7 @@
#pragma once
#include
-#ifdef HAVE_LIBDATE
+#if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE)
#include "modules/clock.hpp"
#else
#include "modules/simpleclock.hpp"
@@ -18,14 +18,20 @@
#include "modules/wlr/workspace_manager.hpp"
#endif
#ifdef HAVE_RIVER
+#include "modules/river/layout.hpp"
#include "modules/river/mode.hpp"
#include "modules/river/tags.hpp"
#include "modules/river/window.hpp"
#endif
+#ifdef HAVE_DWL
+#include "modules/dwl/tags.hpp"
+#endif
#ifdef HAVE_HYPRLAND
#include "modules/hyprland/backend.hpp"
#include "modules/hyprland/language.hpp"
+#include "modules/hyprland/submap.hpp"
#include "modules/hyprland/window.hpp"
+#include "modules/hyprland/workspaces.hpp"
#endif
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
#include "modules/battery.hpp"
@@ -41,6 +47,9 @@
#ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp"
#endif
+#ifdef HAVE_MPRIS
+#include "modules/mpris/mpris.hpp"
+#endif
#ifdef HAVE_LIBNL
#include "modules/network.hpp"
#endif
@@ -75,6 +84,9 @@
#ifdef HAVE_LIBWIREPLUMBER
#include "modules/wireplumber.hpp"
#endif
+#ifdef HAVE_LIBCAVA
+#include "modules/cava.hpp"
+#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/image.hpp"
diff --git a/include/group.hpp b/include/group.hpp
index 5e828677..60e31c96 100644
--- a/include/group.hpp
+++ b/include/group.hpp
@@ -12,10 +12,10 @@ namespace waybar {
class Group : public AModule {
public:
- Group(const std::string&, const Bar&, const Json::Value&);
+ Group(const std::string&, const std::string&, const Json::Value&, bool);
~Group() = default;
- auto update() -> void;
- operator Gtk::Widget&();
+ auto update() -> void override;
+ operator Gtk::Widget&() override;
Gtk::Box box;
};
diff --git a/include/modules/backlight.hpp b/include/modules/backlight.hpp
index 81e2516a..ade4bc78 100644
--- a/include/modules/backlight.hpp
+++ b/include/modules/backlight.hpp
@@ -6,6 +6,7 @@
#include
#include "ALabel.hpp"
+#include "giomm/dbusproxy.h"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
@@ -39,8 +40,8 @@ class Backlight : public ALabel {
public:
Backlight(const std::string &, const Json::Value &);
- ~Backlight();
- auto update() -> void;
+ virtual ~Backlight();
+ auto update() -> void override;
private:
template
@@ -50,6 +51,8 @@ class Backlight : public ALabel {
template
static void enumerate_devices(ForwardIt first, ForwardIt last, Inserter inserter, udev *udev);
+ bool handleScroll(GdkEventScroll *e) override;
+
const std::string preferred_device_;
static constexpr int EPOLL_MAX_EVENTS = 16;
@@ -60,5 +63,7 @@ class Backlight : public ALabel {
std::vector devices_;
// thread must destruct before shared data
util::SleeperThread udev_thread_;
+
+ Glib::RefPtr login_proxy_;
};
} // namespace waybar::modules
diff --git a/include/modules/battery.hpp b/include/modules/battery.hpp
index 54a7dd3c..017b0e48 100644
--- a/include/modules/battery.hpp
+++ b/include/modules/battery.hpp
@@ -29,8 +29,8 @@ namespace fs = std::filesystem;
class Battery : public ALabel {
public:
Battery(const std::string&, const Json::Value&);
- ~Battery();
- auto update() -> void;
+ virtual ~Battery();
+ auto update() -> void override;
private:
static inline const fs::path data_dir_ = "/sys/class/power_supply/";
diff --git a/include/modules/bluetooth.hpp b/include/modules/bluetooth.hpp
index bd9737b1..18481e31 100644
--- a/include/modules/bluetooth.hpp
+++ b/include/modules/bluetooth.hpp
@@ -45,8 +45,8 @@ class Bluetooth : public ALabel {
public:
Bluetooth(const std::string&, const Json::Value&);
- ~Bluetooth() = default;
- auto update() -> void;
+ virtual ~Bluetooth() = default;
+ auto update() -> void override;
private:
static auto onInterfaceAddedOrRemoved(GDBusObjectManager*, GDBusObject*, GDBusInterface*,
diff --git a/include/modules/cava.hpp b/include/modules/cava.hpp
new file mode 100644
index 00000000..d4da2b77
--- /dev/null
+++ b/include/modules/cava.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "ALabel.hpp"
+#include "util/sleeper_thread.hpp"
+
+extern "C" {
+#include
+}
+
+namespace waybar::modules {
+using namespace std::literals::chrono_literals;
+
+class Cava final : public ALabel {
+ public:
+ Cava(const std::string&, const Json::Value&);
+ virtual ~Cava();
+ auto update() -> void override;
+ auto doAction(const std::string& name) -> void override;
+
+ private:
+ util::SleeperThread thread_;
+ util::SleeperThread thread_fetch_input_;
+
+ struct error_s error_ {}; // cava errors
+ struct config_params prm_ {}; // cava parameters
+ struct audio_raw audio_raw_ {}; // cava handled raw audio data(is based on audio_data)
+ struct audio_data audio_data_ {}; // cava audio data
+ struct cava_plan* plan_; //{new cava_plan{}};
+ // Cava API to read audio source
+ ptr input_source_;
+ // Delay to handle audio source
+ std::chrono::milliseconds frame_time_milsec_{1s};
+ // Text to display
+ std::string text_{""};
+ int rePaint_{1};
+ std::chrono::seconds fetch_input_delay_{4};
+ std::chrono::seconds suspend_silence_delay_{0};
+ bool silence_{false};
+ int sleep_counter_{0};
+ // Cava method
+ void pause_resume();
+ // ModuleActionMap
+ static inline std::map actionMap_{
+ {"mode", &waybar::modules::Cava::pause_resume}};
+};
+} // namespace waybar::modules
diff --git a/include/modules/clock.hpp b/include/modules/clock.hpp
index 08ab05e0..fab38111 100644
--- a/include/modules/clock.hpp
+++ b/include/modules/clock.hpp
@@ -1,44 +1,72 @@
#pragma once
-#include
-
#include "ALabel.hpp"
+#include "util/date.hpp"
#include "util/sleeper_thread.hpp"
-namespace waybar {
-
-struct waybar_time;
-
-namespace modules {
+namespace waybar::modules {
const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
-class Clock : public ALabel {
+enum class WeeksSide {
+ LEFT,
+ RIGHT,
+ HIDDEN,
+};
+
+enum class CldMode { MONTH, YEAR };
+
+class Clock final : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
- ~Clock() = default;
- auto update() -> void;
+ virtual ~Clock() = default;
+ auto update() -> void override;
+ auto doAction(const std::string& name) -> void override;
private:
util::SleeperThread thread_;
std::locale locale_;
std::vector time_zones_;
int current_time_zone_idx_;
- date::year_month_day calendar_cached_ymd_{date::January / 1 / 0};
- date::months calendar_shift_{0}, calendar_shift_init_{0};
- std::string calendar_cached_text_;
bool is_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_;
- 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;
const date::time_zone* current_timezone();
bool is_timezone_fixed();
auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string;
+
+ /*Calendar properties*/
+ WeeksSide cldWPos_{WeeksSide::HIDDEN};
+ std::map fmtMap_;
+ CldMode cldMode_{CldMode::MONTH};
+ uint cldMonCols_{3}; // Count of the month in the row
+ int cldMonColLen_{20}; // Length of the month column
+ int cldWnLen_{3}; // Length of the week number
+ date::year_month_day cldYearShift_;
+ date::year_month cldMonShift_;
+ date::months cldCurrShift_{0};
+ date::months cldShift_{0};
+ std::string cldYearCached_{};
+ std::string cldMonCached_{};
+ date::day cldBaseDay_{0};
+ /*Calendar functions*/
+ auto get_calendar(const date::zoned_seconds& now, const date::zoned_seconds& wtime)
+ -> std::string;
+ /*Clock actions*/
+ void cldModeSwitch();
+ void cldShift_up();
+ void cldShift_down();
+ void tz_up();
+ void tz_down();
+
+ // ModuleActionMap
+ static inline std::map actionMap_{
+ {"mode", &waybar::modules::Clock::cldModeSwitch},
+ {"shift_up", &waybar::modules::Clock::cldShift_up},
+ {"shift_down", &waybar::modules::Clock::cldShift_down},
+ {"tz_up", &waybar::modules::Clock::tz_up},
+ {"tz_down", &waybar::modules::Clock::tz_down}};
};
-} // namespace modules
-} // namespace waybar
+} // namespace waybar::modules
diff --git a/include/modules/cpu.hpp b/include/modules/cpu.hpp
index 539f926c..a5235486 100644
--- a/include/modules/cpu.hpp
+++ b/include/modules/cpu.hpp
@@ -17,8 +17,8 @@ namespace waybar::modules {
class Cpu : public ALabel {
public:
Cpu(const std::string&, const Json::Value&);
- ~Cpu() = default;
- auto update() -> void;
+ virtual ~Cpu() = default;
+ auto update() -> void override;
private:
double getCpuLoad();
diff --git a/include/modules/custom.hpp b/include/modules/custom.hpp
index 711d07e2..a6024a84 100644
--- a/include/modules/custom.hpp
+++ b/include/modules/custom.hpp
@@ -15,9 +15,9 @@ namespace waybar::modules {
class Custom : public ALabel {
public:
Custom(const std::string&, const std::string&, const Json::Value&);
- ~Custom();
- auto update() -> void;
- void refresh(int /*signal*/);
+ virtual ~Custom();
+ auto update() -> void override;
+ void refresh(int /*signal*/) override;
private:
void delayWorker();
@@ -25,8 +25,8 @@ class Custom : public ALabel {
void parseOutputRaw();
void parseOutputJson();
void handleEvent();
- bool handleScroll(GdkEventScroll* e);
- bool handleToggle(GdkEventButton* const& e);
+ bool handleScroll(GdkEventScroll* e) override;
+ bool handleToggle(GdkEventButton* const& e) override;
const std::string name_;
std::string text_;
diff --git a/include/modules/disk.hpp b/include/modules/disk.hpp
index ec386b21..2a307c9e 100644
--- a/include/modules/disk.hpp
+++ b/include/modules/disk.hpp
@@ -14,8 +14,8 @@ namespace waybar::modules {
class Disk : public ALabel {
public:
Disk(const std::string&, const Json::Value&);
- ~Disk() = default;
- auto update() -> void;
+ virtual ~Disk() = default;
+ auto update() -> void override;
private:
util::SleeperThread thread_;
diff --git a/include/modules/dwl/tags.hpp b/include/modules/dwl/tags.hpp
new file mode 100644
index 00000000..53dff989
--- /dev/null
+++ b/include/modules/dwl/tags.hpp
@@ -0,0 +1,34 @@
+#pragma once
+
+#include
+#include
+
+#include "AModule.hpp"
+#include "bar.hpp"
+#include "dwl-ipc-unstable-v2-client-protocol.h"
+#include "xdg-output-unstable-v1-client-protocol.h"
+
+namespace waybar::modules::dwl {
+
+class Tags : public waybar::AModule {
+ public:
+ Tags(const std::string &, const waybar::Bar &, const Json::Value &);
+ virtual ~Tags();
+
+ // Handlers for wayland events
+ void handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused);
+
+ void handle_primary_clicked(uint32_t tag);
+ bool handle_button_press(GdkEventButton *event_button, uint32_t tag);
+
+ struct zdwl_ipc_manager_v2 *status_manager_;
+ struct wl_seat *seat_;
+
+ private:
+ const waybar::Bar &bar_;
+ Gtk::Box box_;
+ std::vector buttons_;
+ struct zdwl_ipc_output_v2 *output_status_;
+};
+
+} /* namespace waybar::modules::dwl */
diff --git a/include/modules/gamemode.hpp b/include/modules/gamemode.hpp
index b027393f..69c0c3ae 100644
--- a/include/modules/gamemode.hpp
+++ b/include/modules/gamemode.hpp
@@ -18,8 +18,8 @@ namespace waybar::modules {
class Gamemode : public AModule {
public:
Gamemode(const std::string &, const Json::Value &);
- ~Gamemode();
- auto update() -> void;
+ virtual ~Gamemode();
+ auto update() -> void override;
private:
const std::string DEFAULT_ICON_NAME = "input-gaming-symbolic";
@@ -39,7 +39,7 @@ class Gamemode : public AModule {
const Glib::VariantContainerBase &arguments);
void getData();
- bool handleToggle(GdkEventButton *const &);
+ bool handleToggle(GdkEventButton *const &) override;
// Config
std::string format = DEFAULT_FORMAT;
diff --git a/include/modules/hyprland/backend.hpp b/include/modules/hyprland/backend.hpp
index d8767818..e23b1582 100644
--- a/include/modules/hyprland/backend.hpp
+++ b/include/modules/hyprland/backend.hpp
@@ -6,6 +6,8 @@
#include
#include
+#include "util/json.hpp"
+
namespace waybar::modules::hyprland {
class EventHandler {
@@ -22,12 +24,14 @@ class IPC {
void unregisterForIPC(EventHandler*);
std::string getSocket1Reply(const std::string& rq);
+ Json::Value getSocket1JsonReply(const std::string& rq);
private:
void startIPC();
void parseIPC(const std::string&);
std::mutex callbackMutex;
+ util::JsonParser parser_;
std::list> callbacks;
};
diff --git a/include/modules/hyprland/language.hpp b/include/modules/hyprland/language.hpp
index a07cfc00..30789d06 100644
--- a/include/modules/hyprland/language.hpp
+++ b/include/modules/hyprland/language.hpp
@@ -10,20 +10,29 @@ namespace waybar::modules::hyprland {
class Language : public waybar::ALabel, public EventHandler {
public:
Language(const std::string&, const waybar::Bar&, const Json::Value&);
- ~Language();
+ virtual ~Language();
- auto update() -> void;
+ auto update() -> void override;
private:
- void onEvent(const std::string&);
+ void onEvent(const std::string&) override;
void initLanguage();
- std::string getShortFrom(const std::string&);
+
+ struct Layout {
+ std::string full_name;
+ std::string short_name;
+ std::string variant;
+ std::string short_description;
+ };
+
+ auto getLayout(const std::string&) -> Layout;
std::mutex mutex_;
const Bar& bar_;
util::JsonParser parser_;
- std::string layoutName_;
+
+ Layout layout_;
};
} // namespace waybar::modules::hyprland
diff --git a/include/modules/hyprland/submap.hpp b/include/modules/hyprland/submap.hpp
new file mode 100644
index 00000000..e2a84981
--- /dev/null
+++ b/include/modules/hyprland/submap.hpp
@@ -0,0 +1,26 @@
+#include
+
+#include "ALabel.hpp"
+#include "bar.hpp"
+#include "modules/hyprland/backend.hpp"
+#include "util/json.hpp"
+
+namespace waybar::modules::hyprland {
+
+class Submap : public waybar::ALabel, public EventHandler {
+ public:
+ Submap(const std::string&, const waybar::Bar&, const Json::Value&);
+ virtual ~Submap();
+
+ auto update() -> void override;
+
+ private:
+ void onEvent(const std::string&) override;
+
+ std::mutex mutex_;
+ const Bar& bar_;
+ util::JsonParser parser_;
+ std::string submap_;
+};
+
+} // namespace waybar::modules::hyprland
diff --git a/include/modules/hyprland/window.hpp b/include/modules/hyprland/window.hpp
index 35438cdd..950be05f 100644
--- a/include/modules/hyprland/window.hpp
+++ b/include/modules/hyprland/window.hpp
@@ -1,7 +1,5 @@
#include
-#include
-
#include "ALabel.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
@@ -12,20 +10,37 @@ namespace waybar::modules::hyprland {
class Window : public waybar::ALabel, public EventHandler {
public:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
- ~Window();
+ virtual ~Window();
- auto update() -> void;
+ auto update() -> void override;
private:
- int getActiveWorkspaceID(std::string);
- std::string getLastWindowTitle(int);
- void onEvent(const std::string&);
+ struct Workspace {
+ int id;
+ int windows;
+ std::string last_window;
+ std::string last_window_title;
+
+ static auto parse(const Json::Value&) -> Workspace;
+ };
+
+ auto getActiveWorkspace(const std::string&) -> Workspace;
+ auto getActiveWorkspace() -> Workspace;
+ void onEvent(const std::string&) override;
+ void queryActiveWorkspace();
+ void setClass(const std::string&, bool enable);
bool separate_outputs;
std::mutex mutex_;
const Bar& bar_;
util::JsonParser parser_;
- std::string lastView;
+ std::string last_title_;
+ Workspace workspace_;
+ std::string solo_class_;
+ std::string last_solo_class_;
+ bool solo_;
+ bool all_floating_;
+ bool fullscreen_;
};
} // namespace waybar::modules::hyprland
diff --git a/include/modules/hyprland/workspaces.hpp b/include/modules/hyprland/workspaces.hpp
new file mode 100644
index 00000000..500bbe36
--- /dev/null
+++ b/include/modules/hyprland/workspaces.hpp
@@ -0,0 +1,64 @@
+#include
+#include
+
+#include
+
+#include "AModule.hpp"
+#include "bar.hpp"
+#include "modules/hyprland/backend.hpp"
+
+namespace waybar::modules::hyprland {
+
+struct WorkspaceDto {
+ int id;
+
+ static WorkspaceDto parse(const Json::Value& value);
+};
+
+class Workspace {
+ public:
+ Workspace(int id);
+ Workspace(WorkspaceDto dto);
+ int id() const { return id_; };
+ int active() const { return active_; };
+ std::string& select_icon(std::map& icons_map);
+ void set_active(bool value = true) { active_ = value; };
+ Gtk::Button& button() { return button_; };
+
+ void update(const std::string& format, const std::string& icon);
+
+ private:
+ int id_;
+ bool active_;
+
+ Gtk::Button button_;
+ Gtk::Box content_;
+ Gtk::Label label_;
+};
+
+class Workspaces : public AModule, public EventHandler {
+ public:
+ Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
+ virtual ~Workspaces();
+ void update() override;
+ void init();
+
+ private:
+ void onEvent(const std::string&) override;
+ void sort_workspaces();
+ void create_workspace(int id);
+ void remove_workspace(int id);
+
+ std::string format_;
+ std::map icons_map_;
+ bool with_icon_;
+ int active_workspace_id;
+ std::vector> workspaces_;
+ std::vector workspaces_to_create_;
+ std::vector workspaces_to_remove_;
+ std::mutex mutex_;
+ const Bar& bar_;
+ Gtk::Box box_;
+};
+
+} // namespace waybar::modules::hyprland
diff --git a/include/modules/idle_inhibitor.hpp b/include/modules/idle_inhibitor.hpp
index 8378e58d..22bd808f 100644
--- a/include/modules/idle_inhibitor.hpp
+++ b/include/modules/idle_inhibitor.hpp
@@ -13,13 +13,13 @@ class IdleInhibitor : public ALabel {
public:
IdleInhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
- ~IdleInhibitor();
- auto update() -> void;
+ virtual ~IdleInhibitor();
+ auto update() -> void override;
static std::list modules;
static bool status;
private:
- bool handleToggle(GdkEventButton* const& e);
+ bool handleToggle(GdkEventButton* const& e) override;
void toggleStatus();
const Bar& bar_;
diff --git a/include/modules/image.hpp b/include/modules/image.hpp
index 00b8393c..7c0d014f 100644
--- a/include/modules/image.hpp
+++ b/include/modules/image.hpp
@@ -7,6 +7,7 @@
#include
#include "ALabel.hpp"
+#include "gtkmm/box.h"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
@@ -15,18 +16,23 @@ namespace waybar::modules {
class Image : public AModule {
public:
- Image(const std::string&, const std::string&, const Json::Value&);
- auto update() -> void;
- void refresh(int /*signal*/);
+ Image(const std::string&, const Json::Value&);
+ virtual ~Image() = default;
+ auto update() -> void override;
+ void refresh(int /*signal*/) override;
private:
void delayWorker();
void handleEvent();
+ void parseOutputRaw();
- Gtk::Image image_;
+ Gtk::Box box_;
+ Gtk::Image image_;
std::string path_;
- int size_;
- int interval_;
+ std::string tooltip_;
+ int size_;
+ int interval_;
+ util::command::res output_;
util::SleeperThread thread_;
};
diff --git a/include/modules/inhibitor.hpp b/include/modules/inhibitor.hpp
index a5f300d6..43cb6cab 100644
--- a/include/modules/inhibitor.hpp
+++ b/include/modules/inhibitor.hpp
@@ -12,12 +12,12 @@ namespace waybar::modules {
class Inhibitor : public ALabel {
public:
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
- ~Inhibitor() override;
- auto update() -> void;
+ virtual ~Inhibitor();
+ auto update() -> void override;
auto activated() -> bool;
private:
- auto handleToggle(::GdkEventButton* const& e) -> bool;
+ auto handleToggle(::GdkEventButton* const& e) -> bool override;
const std::unique_ptr<::GDBusConnection, void (*)(::GDBusConnection*)> dbus_;
const std::string inhibitors_;
diff --git a/include/modules/jack.hpp b/include/modules/jack.hpp
index fbab0623..452c43a3 100644
--- a/include/modules/jack.hpp
+++ b/include/modules/jack.hpp
@@ -14,8 +14,8 @@ namespace waybar::modules {
class JACK : public ALabel {
public:
JACK(const std::string &, const Json::Value &);
- ~JACK() = default;
- auto update() -> void;
+ virtual ~JACK() = default;
+ auto update() -> void override;
int bufSize(jack_nframes_t size);
int sampleRate(jack_nframes_t rate);
diff --git a/include/modules/keyboard_state.hpp b/include/modules/keyboard_state.hpp
index ce9faba0..deb577e2 100644
--- a/include/modules/keyboard_state.hpp
+++ b/include/modules/keyboard_state.hpp
@@ -19,8 +19,8 @@ namespace waybar::modules {
class KeyboardState : public AModule {
public:
KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&);
- ~KeyboardState();
- auto update() -> void;
+ virtual ~KeyboardState();
+ auto update() -> void override;
private:
auto tryAddDevice(const std::string&) -> void;
diff --git a/include/modules/memory.hpp b/include/modules/memory.hpp
index e23ed841..3b6342b3 100644
--- a/include/modules/memory.hpp
+++ b/include/modules/memory.hpp
@@ -13,8 +13,8 @@ namespace waybar::modules {
class Memory : public ALabel {
public:
Memory(const std::string&, const Json::Value&);
- ~Memory() = default;
- auto update() -> void;
+ virtual ~Memory() = default;
+ auto update() -> void override;
private:
void parseMeminfo();
diff --git a/include/modules/mpd/mpd.hpp b/include/modules/mpd/mpd.hpp
index ae3f9152..32d526e9 100644
--- a/include/modules/mpd/mpd.hpp
+++ b/include/modules/mpd/mpd.hpp
@@ -37,7 +37,7 @@ class MPD : public ALabel {
public:
MPD(const std::string&, const Json::Value&);
virtual ~MPD() noexcept = default;
- auto update() -> void;
+ auto update() -> void override;
private:
std::string getTag(mpd_tag_type type, unsigned idx = 0) const;
diff --git a/include/modules/mpris/mpris.hpp b/include/modules/mpris/mpris.hpp
new file mode 100644
index 00000000..a0aee3b2
--- /dev/null
+++ b/include/modules/mpris/mpris.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include "gtkmm/box.h"
+#include "gtkmm/label.h"
+
+extern "C" {
+#include
+}
+
+#include "ALabel.hpp"
+#include "util/sleeper_thread.hpp"
+
+namespace waybar::modules::mpris {
+
+class Mpris : public ALabel {
+ public:
+ Mpris(const std::string&, const Json::Value&);
+ virtual ~Mpris();
+ auto update() -> void override;
+ bool handleToggle(GdkEventButton* const&) override;
+
+ private:
+ static auto onPlayerNameAppeared(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void;
+ static auto onPlayerNameVanished(PlayerctlPlayerManager*, PlayerctlPlayerName*, gpointer) -> void;
+ static auto onPlayerPlay(PlayerctlPlayer*, gpointer) -> void;
+ static auto onPlayerPause(PlayerctlPlayer*, gpointer) -> void;
+ static auto onPlayerStop(PlayerctlPlayer*, gpointer) -> void;
+ static auto onPlayerMetadata(PlayerctlPlayer*, GVariant*, gpointer) -> void;
+
+ struct PlayerInfo {
+ std::string name;
+ PlayerctlPlaybackStatus status;
+ std::string status_string;
+
+ std::optional artist;
+ std::optional album;
+ std::optional title;
+ std::optional length; // as HH:MM:SS
+ std::optional position; // same format
+ };
+
+ auto getPlayerInfo() -> std::optional;
+ auto getIconFromJson(const Json::Value&, const std::string&) -> std::string;
+ auto getArtistStr(const PlayerInfo&, bool) -> std::string;
+ auto getAlbumStr(const PlayerInfo&, bool) -> std::string;
+ auto getTitleStr(const PlayerInfo&, bool) -> std::string;
+ auto getLengthStr(const PlayerInfo&, bool) -> std::string;
+ auto getPositionStr(const PlayerInfo&, bool) -> std::string;
+ auto getDynamicStr(const PlayerInfo&, bool, bool) -> std::string;
+
+ // config
+ std::string format_playing_;
+ std::string format_paused_;
+ std::string format_stopped_;
+
+ std::string tooltip_;
+ std::string tooltip_playing_;
+ std::string tooltip_paused_;
+ std::string tooltip_stopped_;
+
+ int artist_len_;
+ int album_len_;
+ int title_len_;
+ int dynamic_len_;
+ std::string dynamic_separator_;
+ std::vector dynamic_order_;
+ std::vector dynamic_prio_;
+ bool truncate_hours_;
+ bool tooltip_len_limits_;
+ std::string ellipsis_;
+
+ std::string player_;
+ std::vector ignored_players_;
+
+ PlayerctlPlayerManager* manager;
+ PlayerctlPlayer* player;
+ std::string lastStatus;
+ std::string lastPlayer;
+
+ util::SleeperThread thread_;
+ std::chrono::time_point last_update_;
+};
+
+} // namespace waybar::modules::mpris
diff --git a/include/modules/network.hpp b/include/modules/network.hpp
index 8ec6b6df..47701b4e 100644
--- a/include/modules/network.hpp
+++ b/include/modules/network.hpp
@@ -21,8 +21,8 @@ namespace waybar::modules {
class Network : public ALabel {
public:
Network(const std::string&, const Json::Value&);
- ~Network();
- auto update() -> void;
+ virtual ~Network();
+ auto update() -> void override;
private:
static const uint8_t MAX_RETRY = 5;
@@ -62,6 +62,7 @@ class Network : public ALabel {
bool want_link_dump_;
bool want_addr_dump_;
bool dump_in_progress_;
+ bool is_p2p_;
unsigned long long bandwidth_down_total_;
unsigned long long bandwidth_up_total_;
@@ -77,7 +78,6 @@ class Network : public ALabel {
int32_t signal_strength_dbm_;
uint8_t signal_strength_;
std::string signal_strength_app_;
- float frequency_;
uint32_t route_priority;
util::SleeperThread thread_;
@@ -85,6 +85,7 @@ class Network : public ALabel {
#ifdef WANT_RFKILL
util::Rfkill rfkill_;
#endif
+ float frequency_;
};
} // namespace waybar::modules
diff --git a/include/modules/pulseaudio.hpp b/include/modules/pulseaudio.hpp
index a8e4890a..d0b17e47 100644
--- a/include/modules/pulseaudio.hpp
+++ b/include/modules/pulseaudio.hpp
@@ -14,8 +14,8 @@ namespace waybar::modules {
class Pulseaudio : public ALabel {
public:
Pulseaudio(const std::string&, const Json::Value&);
- ~Pulseaudio();
- auto update() -> void;
+ virtual ~Pulseaudio();
+ auto update() -> void override;
private:
static void subscribeCb(pa_context*, pa_subscription_event_type_t, uint32_t, void*);
@@ -25,7 +25,7 @@ class Pulseaudio : public ALabel {
static void serverInfoCb(pa_context*, const pa_server_info*, void*);
static void volumeModifyCb(pa_context*, int, void*);
- bool handleScroll(GdkEventScroll* e);
+ bool handleScroll(GdkEventScroll* e) override;
const std::vector getPulseIcon() const;
pa_threaded_mainloop* mainloop_;
diff --git a/include/modules/river/layout.hpp b/include/modules/river/layout.hpp
new file mode 100644
index 00000000..dc78ee20
--- /dev/null
+++ b/include/modules/river/layout.hpp
@@ -0,0 +1,33 @@
+#pragma once
+
+#include
+
+#include "ALabel.hpp"
+#include "bar.hpp"
+#include "river-status-unstable-v1-client-protocol.h"
+
+namespace waybar::modules::river {
+
+class Layout : public waybar::ALabel {
+ public:
+ Layout(const std::string &, const waybar::Bar &, const Json::Value &);
+ virtual ~Layout();
+
+ // Handlers for wayland events
+ void handle_name(const char *name);
+ void handle_clear();
+ void handle_focused_output(struct wl_output *output);
+ void handle_unfocused_output(struct wl_output *output);
+
+ struct zriver_status_manager_v1 *status_manager_;
+ struct wl_seat *seat_;
+
+ private:
+ const waybar::Bar &bar_;
+ struct wl_output *output_; // stores the output this module belongs to
+ struct wl_output *focused_output_; // stores the currently focused output
+ struct zriver_output_status_v1 *output_status_;
+ struct zriver_seat_status_v1 *seat_status_;
+};
+
+} /* namespace waybar::modules::river */
diff --git a/include/modules/river/mode.hpp b/include/modules/river/mode.hpp
index 2aff4950..246cecae 100644
--- a/include/modules/river/mode.hpp
+++ b/include/modules/river/mode.hpp
@@ -11,7 +11,7 @@ namespace waybar::modules::river {
class Mode : public waybar::ALabel {
public:
Mode(const std::string &, const waybar::Bar &, const Json::Value &);
- ~Mode();
+ virtual ~Mode();
// Handlers for wayland events
void handle_mode(const char *mode);
diff --git a/include/modules/river/tags.hpp b/include/modules/river/tags.hpp
index c2b1a119..fb3eefaa 100644
--- a/include/modules/river/tags.hpp
+++ b/include/modules/river/tags.hpp
@@ -14,7 +14,7 @@ namespace waybar::modules::river {
class Tags : public waybar::AModule {
public:
Tags(const std::string &, const waybar::Bar &, const Json::Value &);
- ~Tags();
+ virtual ~Tags();
// Handlers for wayland events
void handle_focused_tags(uint32_t tags);
diff --git a/include/modules/river/window.hpp b/include/modules/river/window.hpp
index 1f6c5252..bf29ebbf 100644
--- a/include/modules/river/window.hpp
+++ b/include/modules/river/window.hpp
@@ -13,7 +13,7 @@ namespace waybar::modules::river {
class Window : public waybar::ALabel {
public:
Window(const std::string &, const waybar::Bar &, const Json::Value &);
- ~Window();
+ virtual ~Window();
// Handlers for wayland events
void handle_focused_view(const char *title);
diff --git a/include/modules/simpleclock.hpp b/include/modules/simpleclock.hpp
index 5cbee4c6..43472ef0 100644
--- a/include/modules/simpleclock.hpp
+++ b/include/modules/simpleclock.hpp
@@ -10,8 +10,8 @@ namespace waybar::modules {
class Clock : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
- ~Clock() = default;
- auto update() -> void;
+ virtual ~Clock() = default;
+ auto update() -> void override;
private:
util::SleeperThread thread_;
diff --git a/include/modules/sndio.hpp b/include/modules/sndio.hpp
index eb9b218e..3fe36fad 100644
--- a/include/modules/sndio.hpp
+++ b/include/modules/sndio.hpp
@@ -12,12 +12,12 @@ namespace waybar::modules {
class Sndio : public ALabel {
public:
Sndio(const std::string &, const Json::Value &);
- ~Sndio();
- auto update() -> void;
+ virtual ~Sndio();
+ auto update() -> void override;
auto set_desc(struct sioctl_desc *, unsigned int) -> void;
auto put_val(unsigned int, unsigned int) -> void;
- bool handleScroll(GdkEventScroll *);
- bool handleToggle(GdkEventButton *const &);
+ bool handleScroll(GdkEventScroll *) override;
+ bool handleToggle(GdkEventButton *const &) override;
private:
auto connect_to_sndio() -> void;
diff --git a/include/modules/sni/tray.hpp b/include/modules/sni/tray.hpp
index c1499b9e..6cda35d7 100644
--- a/include/modules/sni/tray.hpp
+++ b/include/modules/sni/tray.hpp
@@ -13,8 +13,8 @@ namespace waybar::modules::SNI {
class Tray : public AModule {
public:
Tray(const std::string&, const Bar&, const Json::Value&);
- ~Tray() = default;
- auto update() -> void;
+ virtual ~Tray() = default;
+ auto update() -> void override;
private:
void onAdd(std::unique_ptr- & item);
diff --git a/include/modules/sway/ipc/client.hpp b/include/modules/sway/ipc/client.hpp
index 77dab083..a9a3e4e9 100644
--- a/include/modules/sway/ipc/client.hpp
+++ b/include/modules/sway/ipc/client.hpp
@@ -8,6 +8,8 @@
#include
#include
#include
+#include
+#include
#include "ipc.hpp"
#include "util/sleeper_thread.hpp"
diff --git a/include/modules/sway/language.hpp b/include/modules/sway/language.hpp
index f7602765..3e9519f5 100644
--- a/include/modules/sway/language.hpp
+++ b/include/modules/sway/language.hpp
@@ -17,8 +17,8 @@ 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;
+ virtual ~Language() = default;
+ auto update() -> void override;
private:
enum class DispayedShortFlag { None = 0, ShortName = 1, ShortDescription = 1 << 1 };
diff --git a/include/modules/sway/mode.hpp b/include/modules/sway/mode.hpp
index 5543c4eb..44585355 100644
--- a/include/modules/sway/mode.hpp
+++ b/include/modules/sway/mode.hpp
@@ -13,8 +13,8 @@ namespace waybar::modules::sway {
class Mode : public ALabel, public sigc::trackable {
public:
Mode(const std::string&, const Json::Value&);
- ~Mode() = default;
- auto update() -> void;
+ virtual ~Mode() = default;
+ auto update() -> void override;
private:
void onEvent(const struct Ipc::ipc_response&);
diff --git a/include/modules/sway/scratchpad.hpp b/include/modules/sway/scratchpad.hpp
index e68e7726..551cc8c8 100644
--- a/include/modules/sway/scratchpad.hpp
+++ b/include/modules/sway/scratchpad.hpp
@@ -15,8 +15,8 @@ namespace waybar::modules::sway {
class Scratchpad : public ALabel {
public:
Scratchpad(const std::string&, const Json::Value&);
- ~Scratchpad() = default;
- auto update() -> void;
+ virtual ~Scratchpad() = default;
+ auto update() -> void override;
private:
auto getTree() -> void;
@@ -32,4 +32,4 @@ class Scratchpad : public ALabel {
Ipc ipc_;
util::JsonParser parser_;
};
-} // namespace waybar::modules::sway
\ No newline at end of file
+} // namespace waybar::modules::sway
diff --git a/include/modules/sway/window.hpp b/include/modules/sway/window.hpp
index c13d5cee..22f5a59c 100644
--- a/include/modules/sway/window.hpp
+++ b/include/modules/sway/window.hpp
@@ -15,14 +15,15 @@ namespace waybar::modules::sway {
class Window : public AIconLabel, public sigc::trackable {
public:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
- ~Window() = default;
- auto update() -> void;
+ virtual ~Window() = default;
+ auto update() -> void override;
private:
+ void setClass(std::string classname, bool enable);
void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&);
- std::tuple getFocusedNode(
- const Json::Value& nodes, std::string& output);
+ std::tuple
+ getFocusedNode(const Json::Value& nodes, std::string& output);
void getTree();
void updateAppIconName();
void updateAppIcon();
@@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable {
int windowId_;
std::string app_id_;
std::string app_class_;
+ std::string layout_;
std::string old_app_id_;
std::size_t app_nb_;
std::string shell_;
unsigned app_icon_size_{24};
bool update_app_icon_{true};
std::string app_icon_name_;
+ int floating_count_;
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
diff --git a/include/modules/sway/workspaces.hpp b/include/modules/sway/workspaces.hpp
index e6df067f..d07edb49 100644
--- a/include/modules/sway/workspaces.hpp
+++ b/include/modules/sway/workspaces.hpp
@@ -4,6 +4,7 @@
#include
#include
+#include
#include
#include "AModule.hpp"
@@ -17,11 +18,13 @@ namespace waybar::modules::sway {
class Workspaces : public AModule, public sigc::trackable {
public:
Workspaces(const std::string&, const waybar::Bar&, const Json::Value&);
- ~Workspaces() = default;
- auto update() -> void;
+ virtual ~Workspaces() = default;
+ auto update() -> void override;
private:
- static inline const std::string workspace_switch_cmd_ = "workspace {} \"{}\"";
+ static constexpr std::string_view workspace_switch_cmd_ = "workspace {} \"{}\"";
+ static constexpr std::string_view persistent_workspace_switch_cmd_ =
+ R"(workspace {} "{}"; move workspace to output "{}"; workspace {} "{}")";
static int convertWorkspaceNameToNum(std::string name);
@@ -34,7 +37,7 @@ class Workspaces : public AModule, public sigc::trackable {
const std::string getCycleWorkspace(std::vector::iterator, bool prev) const;
uint16_t getWorkspaceIndex(const std::string& name) const;
std::string trimWorkspaceName(std::string);
- bool handleScroll(GdkEventScroll*);
+ bool handleScroll(GdkEventScroll*) override;
const Bar& bar_;
std::vector workspaces_;
diff --git a/include/modules/temperature.hpp b/include/modules/temperature.hpp
index 04caafc5..5440df77 100644
--- a/include/modules/temperature.hpp
+++ b/include/modules/temperature.hpp
@@ -12,8 +12,8 @@ namespace waybar::modules {
class Temperature : public ALabel {
public:
Temperature(const std::string&, const Json::Value&);
- ~Temperature() = default;
- auto update() -> void;
+ virtual ~Temperature() = default;
+ auto update() -> void override;
private:
float getTemperature();
diff --git a/include/modules/upower/upower.hpp b/include/modules/upower/upower.hpp
index 2724443f..446d1f53 100644
--- a/include/modules/upower/upower.hpp
+++ b/include/modules/upower/upower.hpp
@@ -19,8 +19,8 @@ namespace waybar::modules::upower {
class UPower : public AModule {
public:
UPower(const std::string &, const Json::Value &);
- ~UPower();
- auto update() -> void;
+ virtual ~UPower();
+ auto update() -> void override;
private:
typedef std::unordered_map Devices;
@@ -45,7 +45,7 @@ class UPower : public AModule {
void resetDevices();
void removeDevices();
bool show_tooltip_callback(int, int, bool, const Glib::RefPtr &tooltip);
- bool handleToggle(GdkEventButton *const &);
+ bool handleToggle(GdkEventButton *const &) override;
std::string timeToString(gint64 time);
const std::string getDeviceStatus(UpDeviceState &state);
@@ -74,6 +74,7 @@ class UPower : public AModule {
bool showAltText;
bool upowerRunning;
guint upowerWatcher_id;
+ std::string nativePath_;
};
} // namespace waybar::modules::upower
diff --git a/include/modules/upower/upower_tooltip.hpp b/include/modules/upower/upower_tooltip.hpp
index 730ee93e..05e9dcb3 100644
--- a/include/modules/upower/upower_tooltip.hpp
+++ b/include/modules/upower/upower_tooltip.hpp
@@ -24,7 +24,7 @@ class UPowerTooltip : public Gtk::Window {
public:
UPowerTooltip(uint iconSize, uint tooltipSpacing, uint tooltipPadding);
- ~UPowerTooltip();
+ virtual ~UPowerTooltip();
uint updateTooltip(Devices& devices);
};
diff --git a/include/modules/user.hpp b/include/modules/user.hpp
index ac14d25f..bcb03da4 100644
--- a/include/modules/user.hpp
+++ b/include/modules/user.hpp
@@ -11,8 +11,8 @@ namespace waybar::modules {
class User : public AIconLabel {
public:
User(const std::string&, const Json::Value&);
- ~User() = default;
- auto update() -> void;
+ virtual ~User() = default;
+ auto update() -> void override;
bool handleToggle(GdkEventButton* const& e) override;
diff --git a/include/modules/wireplumber.hpp b/include/modules/wireplumber.hpp
index c0ee7f0b..9bbf4d46 100644
--- a/include/modules/wireplumber.hpp
+++ b/include/modules/wireplumber.hpp
@@ -13,25 +13,32 @@ namespace waybar::modules {
class Wireplumber : public ALabel {
public:
Wireplumber(const std::string&, const Json::Value&);
- ~Wireplumber();
- auto update() -> void;
+ virtual ~Wireplumber();
+ auto update() -> void override;
private:
void loadRequiredApiModules();
void prepare();
void activatePlugins();
- static void updateVolume(waybar::modules::Wireplumber* self);
- static void updateNodeName(waybar::modules::Wireplumber* self);
- static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self);
+ static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
+ static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
static void onObjectManagerInstalled(waybar::modules::Wireplumber* self);
+ static void onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id);
+ static void onDefaultNodesApiChanged(waybar::modules::Wireplumber* self);
+
+ bool handleScroll(GdkEventScroll* e) override;
WpCore* wp_core_;
GPtrArray* apis_;
WpObjectManager* om_;
+ WpPlugin* mixer_api_;
+ WpPlugin* def_nodes_api_;
+ gchar* default_node_name_;
uint32_t pending_plugins_;
bool muted_;
double volume_;
+ double min_step_;
uint32_t node_id_{0};
std::string node_name_;
};
diff --git a/include/modules/wlr/taskbar.hpp b/include/modules/wlr/taskbar.hpp
index 7a1f17a6..4465dd06 100644
--- a/include/modules/wlr/taskbar.hpp
+++ b/include/modules/wlr/taskbar.hpp
@@ -40,6 +40,8 @@ class Task {
FULLSCREEN = (1 << 3),
INVALID = (1 << 4)
};
+ // made public so TaskBar can reorder based on configuration.
+ Gtk::Button button;
private:
static uint32_t global_id;
@@ -53,7 +55,6 @@ class Task {
uint32_t id_;
- Gtk::Button button_;
Gtk::Box content_;
Gtk::Image icon_;
Gtk::Label text_before_;
diff --git a/include/modules/wlr/workspace_manager.hpp b/include/modules/wlr/workspace_manager.hpp
index 963d610f..f7cc759e 100644
--- a/include/modules/wlr/workspace_manager.hpp
+++ b/include/modules/wlr/workspace_manager.hpp
@@ -22,7 +22,7 @@ class WorkspaceGroup;
class Workspace {
public:
Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
- zext_workspace_handle_v1 *workspace, uint32_t id);
+ zext_workspace_handle_v1 *workspace, uint32_t id, std::string name);
~Workspace();
auto update() -> void;
@@ -30,11 +30,15 @@ class Workspace {
auto is_active() const -> bool { return state_ & static_cast(State::ACTIVE); }
auto is_urgent() const -> bool { return state_ & static_cast(State::URGENT); }
auto is_hidden() const -> bool { return state_ & static_cast(State::HIDDEN); }
+ auto is_empty() const -> bool { return state_ & static_cast(State::EMPTY); }
+ auto is_persistent() const -> bool { return persistent_; }
// wlr stuff
auto handle_name(const std::string &name) -> void;
auto handle_coordinates(const std::vector &coordinates) -> void;
auto handle_state(const std::vector &state) -> void;
auto handle_remove() -> void;
+ auto make_persistent() -> void;
+ auto handle_duplicate() -> void;
auto handle_done() -> void;
auto handle_clicked(GdkEventButton *bt) -> bool;
@@ -48,6 +52,7 @@ class Workspace {
ACTIVE = (1 << 0),
URGENT = (1 << 1),
HIDDEN = (1 << 2),
+ EMPTY = (1 << 3),
};
private:
@@ -67,6 +72,7 @@ class Workspace {
static std::map icons_map_;
std::string format_;
bool with_icon_ = false;
+ bool persistent_ = false;
Gtk::Button button_;
Gtk::Box content_;
@@ -87,11 +93,14 @@ class WorkspaceGroup {
auto active_only() const -> bool;
auto creation_delayed() const -> bool;
auto workspaces() -> std::vector> & { return workspaces_; }
+ auto persistent_workspaces() -> std::vector & { return persistent_workspaces_; }
auto sort_workspaces() -> void;
auto set_need_to_sort() -> void { need_to_sort = true; }
auto add_button(Gtk::Button &button) -> void;
auto remove_button(Gtk::Button &button) -> void;
+ auto fill_persistent_workspaces() -> void;
+ auto create_persistent_workspaces() -> void;
// wlr stuff
auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void;
@@ -115,6 +124,8 @@ class WorkspaceGroup {
uint32_t id_;
std::vector> workspaces_;
bool need_to_sort = false;
+ std::vector persistent_workspaces_;
+ bool persistent_created_ = false;
};
class WorkspaceManager : public AModule {
diff --git a/include/util/clara.hpp b/include/util/clara.hpp
index 4cc6f486..73fa5415 100644
--- a/include/util/clara.hpp
+++ b/include/util/clara.hpp
@@ -989,7 +989,7 @@ struct Help : Opt {
showHelpFlag = flag;
return ParserResult::ok(ParseResultType::ShortCircuitAll);
}) {
- static_cast (*this)("display usage information")["-?"]["-h"]["--help"].optional();
+ static_cast(*this)("display usage information")["-?"]["-h"]["--help"].optional();
}
};
diff --git a/include/util/command.hpp b/include/util/command.hpp
index c9f238c1..0d729b77 100644
--- a/include/util/command.hpp
+++ b/include/util/command.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include
#include
#include
#include
@@ -68,7 +69,11 @@ inline int close(FILE* fp, pid_t pid) {
inline FILE* open(const std::string& cmd, int& pid) {
if (cmd == "") return nullptr;
int fd[2];
- if (pipe(fd) != 0) {
+ // Open the pipe with the close-on-exec flag set, so it will not be inherited
+ // by any other subprocesses launched by other threads (which could result in
+ // the pipe staying open after this child dies, causing us to hang when trying
+ // to read from it)
+ if (pipe2(fd, O_CLOEXEC) != 0) {
spdlog::error("Unable to pipe fd");
return nullptr;
}
@@ -77,6 +82,8 @@ inline FILE* open(const std::string& cmd, int& pid) {
if (child_pid < 0) {
spdlog::error("Unable to exec cmd {}, error {}", cmd.c_str(), strerror(errno));
+ ::close(fd[0]);
+ ::close(fd[1]);
return nullptr;
}
diff --git a/include/util/date.hpp b/include/util/date.hpp
new file mode 100644
index 00000000..380bb6e7
--- /dev/null
+++ b/include/util/date.hpp
@@ -0,0 +1,60 @@
+#pragma once
+
+#include
+
+#if HAVE_CHRONO_TIMEZONES
+#include
+#include
+
+/* Compatibility layer for on top of C++20 */
+namespace date {
+
+using namespace std::chrono;
+
+namespace literals {
+using std::chrono::last;
+}
+
+inline auto format(const std::string& spec, const auto& ztime) {
+ return spec.empty() ? "" : std::vformat("{:L" + spec + "}", std::make_format_args(ztime));
+}
+
+inline auto format(const std::locale& loc, const std::string& spec, const auto& ztime) {
+ return spec.empty() ? "" : std::vformat(loc, "{:L" + spec + "}", std::make_format_args(ztime));
+}
+
+} // namespace date
+
+#else
+#include
+#endif
+
+template
+struct fmt::formatter> {
+ std::string_view specs;
+
+ template
+ constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ auto it = ctx.begin();
+ if (it != ctx.end() && *it == ':') {
+ ++it;
+ }
+ auto end = it;
+ while (end != ctx.end() && *end != '}') {
+ ++end;
+ }
+ if (end != it) {
+ specs = {it, std::string_view::size_type(end - it)};
+ }
+ return end;
+ }
+
+ template
+ auto format(const date::zoned_time& ztime, FormatContext& ctx) {
+ if (ctx.locale()) {
+ const auto loc = ctx.locale().template get();
+ return fmt::format_to(ctx.out(), "{}", date::format(loc, fmt::to_string(specs), ztime));
+ }
+ return fmt::format_to(ctx.out(), "{}", date::format(fmt::to_string(specs), ztime));
+ }
+};
diff --git a/include/util/format.hpp b/include/util/format.hpp
index fac0377d..00b6a31c 100644
--- a/include/util/format.hpp
+++ b/include/util/format.hpp
@@ -66,9 +66,9 @@ struct formatter {
std::string string;
switch (spec) {
case '>':
- return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width);
+ return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width);
case '<':
- return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
+ return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
case '=':
format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}";
break;
@@ -77,8 +77,8 @@ struct formatter {
format = "{coefficient:.1f}{prefix}{unit}";
break;
}
- return format_to(
- ctx.out(), format, fmt::arg("coefficient", fraction),
+ return fmt::format_to(
+ ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction),
fmt::arg("number_width", number_width),
fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")),
fmt::arg("unit", s.unit_),
diff --git a/include/util/gtk_icon.hpp b/include/util/gtk_icon.hpp
new file mode 100644
index 00000000..44555f65
--- /dev/null
+++ b/include/util/gtk_icon.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include
+
+#include
+#include
+
+class DefaultGtkIconThemeWrapper {
+ private:
+ static std::mutex default_theme_mutex;
+
+ public:
+ static bool has_icon(const std::string&);
+ static Glib::RefPtr load_icon(const char*, int, Gtk::IconLookupFlags);
+};
diff --git a/include/util/prepare_for_sleep.h b/include/util/prepare_for_sleep.h
new file mode 100644
index 00000000..68db8d8e
--- /dev/null
+++ b/include/util/prepare_for_sleep.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "SafeSignal.hpp"
+
+namespace waybar::util {
+
+// Get a signal emited with value true when entering sleep, and false when exiting
+SafeSignal& prepare_for_sleep();
+} // namespace waybar::util
diff --git a/include/util/rewrite_title.hpp b/include/util/rewrite_string.hpp
similarity index 55%
rename from include/util/rewrite_title.hpp
rename to include/util/rewrite_string.hpp
index c477339e..2ab39ad8 100644
--- a/include/util/rewrite_title.hpp
+++ b/include/util/rewrite_string.hpp
@@ -4,5 +4,5 @@
#include
namespace waybar::util {
-std::string rewriteTitle(const std::string&, const Json::Value&);
+std::string rewriteString(const std::string&, const Json::Value&);
}
diff --git a/include/util/sleeper_thread.hpp b/include/util/sleeper_thread.hpp
index e12287a0..a724b1e8 100644
--- a/include/util/sleeper_thread.hpp
+++ b/include/util/sleeper_thread.hpp
@@ -6,6 +6,8 @@
#include
#include
+#include "prepare_for_sleep.h"
+
namespace waybar::util {
/**
@@ -33,7 +35,11 @@ class SleeperThread {
signal_ = false;
func();
}
- }} {}
+ }} {
+ connection_ = prepare_for_sleep().connect([this](bool sleep) {
+ if (not sleep) wake_up();
+ });
+ }
SleeperThread& operator=(std::function func) {
thread_ = std::thread([this, func] {
@@ -42,6 +48,11 @@ class SleeperThread {
func();
}
});
+ if (connection_.empty()) {
+ connection_ = prepare_for_sleep().connect([this](bool sleep) {
+ if (not sleep) wake_up();
+ });
+ }
return *this;
}
@@ -61,7 +72,7 @@ class SleeperThread {
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
}
- auto wake_up() {
+ void wake_up() {
{
std::lock_guard lck(mutex_);
signal_ = true;
@@ -96,6 +107,7 @@ class SleeperThread {
std::mutex mutex_;
bool do_run_ = true;
bool signal_ = false;
+ sigc::connection connection_;
};
} // namespace waybar::util
diff --git a/include/util/waybar_time.hpp b/include/util/waybar_time.hpp
deleted file mode 100644
index b9f9ea98..00000000
--- a/include/util/waybar_time.hpp
+++ /dev/null
@@ -1,39 +0,0 @@
-#pragma once
-
-#include
-#include
-
-namespace waybar {
-
-struct waybar_time {
- std::locale locale;
- date::zoned_seconds ztime;
-};
-
-} // namespace waybar
-
-template <>
-struct fmt::formatter {
- std::string_view specs;
-
- template
- constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- auto it = ctx.begin();
- if (it != ctx.end() && *it == ':') {
- ++it;
- }
- auto end = it;
- while (end != ctx.end() && *end != '}') {
- ++end;
- }
- if (end != it) {
- specs = {it, std::string_view::size_type(end - it)};
- }
- return end;
- }
-
- template
- auto format(const waybar::waybar_time& t, FormatContext& ctx) {
- return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime));
- }
-};
diff --git a/man/waybar-backlight.5.scd b/man/waybar-backlight.5.scd
index 9c8ba791..ca3d922b 100644
--- a/man/waybar-backlight.5.scd
+++ b/man/waybar-backlight.5.scd
@@ -58,16 +58,25 @@ The *backlight* module displays the current backlight level.
*on-scroll-up*: ++
typeof: string ++
- Command to execute when performing a scroll up on the module.
+ Command to execute when performing a scroll up on the module. This replaces the default behaviour of brightness control.
*on-scroll-down*: ++
typeof: string
- Command to execute when performing a scroll down on the module.
+ Command to execute when performing a scroll down on the module. This replaces the default behaviour of brightness control.
*smooth-scrolling-threshold*: ++
typeof: double
Threshold to be used when scrolling.
+*reverse-scrolling*: ++
+ typeof: bool ++
+ Option to reverse the scroll direction.
+
+*scroll-step*: ++
+ typeof: float ++
+ default: 1.0 ++
+ The speed in which to change the brightness when scrolling.
+
# EXAMPLE:
```
diff --git a/man/waybar-clock.5.scd b/man/waybar-clock.5.scd
index fb470e03..3c670566 100644
--- a/man/waybar-clock.5.scd
+++ b/man/waybar-clock.5.scd
@@ -1,105 +1,167 @@
-waybar-clock(5)
+waybar-clock(5) "waybar-clock" "User Manual"
# NAME
-waybar - clock module
+clock
# DESCRIPTION
-The *clock* module displays the current date and time.
+*clock* module displays current date and time
+
+# FILES
+
+$XDG_CONFIG_HOME/waybar/config ++
+ Per user configuration file
# CONFIGURATION
-*interval*: ++
- typeof: integer ++
- default: 60 ++
- The interval in which the information gets polled.
+1. Addressed by *clock*
+[- *Option*
+:- *Typeof*
+:- *Default*
+:- *Description*
+|[ *interval*
+:[ integer
+:[ 60
+:[ The interval in which the information gets polled
+|[ *format*
+:[ string
+:[ *{:%H:%M}*
+:[ The format, how the date and time should be displayed. See format options below
+|[ *timezone*
+:[ string
+:[
+:[ The timezone to display the time in, e.g. America/New_York. "" represents
+ the system's local timezone. See Wikipedia's unofficial list of timezones
+|[ *timezones*
+:[ list of strings
+:[
+:[ A list of timezones (as in *timezone*) to use for time display, changed using
+ the scroll wheel. Do not specify *timezone* option when *timezones* is specified.
+ "" represents the system's local timezone
+|[ *locale*
+:[ string
+:[
+:[ A locale to be used to display the time. Intended to render times in custom
+ timezones with the proper language and format
+|[ *max-length*
+:[ integer
+:[
+:[ The maximum length in character the module should display
+|[ *rotate*
+:[ integer
+:[
+:[ Positive value to rotate the text label
+|[ *on-click*
+:[ string
+:[
+:[ Command to execute when clicked on the module
+|[ *on-click-middle*
+:[ string
+:[
+:[ Command to execute when you middle clicked on the module using mousewheel
+|[ *on-click-right*
+:[ string
+:[
+:[ Command to execute when you right clicked on the module
+|[ *on-scroll-up*
+:[ string
+:[
+:[ Command to execute when scrolling up on the module
+|[ *on-scroll-down*
+:[ string
+:[
+:[ Command to execute when scrolling down on the module
+|[ *smooth-scrolling-threshold*
+:[ double
+:[
+:[ Threshold to be used when scrolling
+|[ *tooltip*
+:[ bool
+:[ true
+:[ Option to enable tooltip on hover
+|[ *tooltip-format*
+:[ string
+:[ same as format
+:[ Tooltip on hover
-*format*: ++
- typeof: string ++
- default: {:%H:%M} ++
- The format, how the date and time should be displayed. ++
- It uses the format of the date library. See https://howardhinnant.github.io/date/date.html#to_stream_formatting for details.
+View all valid format options in *strftime(3)* or have a look
-*timezone*: ++
- typeof: string ++
- default: inferred local timezone ++
- 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.
+2. Addressed by *clock: calendar*
+[- *Option*
+:- *Typeof*
+:- *Default*
+:- *Description*
+|[ *mode*
+:[ string
+:[ month
+:[ Calendar view mode. Possible values: year|month
+|[ *mode-mon-col*
+:[ integer
+:[ 3
+:[ Relevant for *mode=year*. Count of months per row
+|[ *weeks-pos*
+:[ integer
+:[
+:[ The position where week numbers should be displayed. Disabled when is empty.
+ Possible values: left|right
+|[ *on-scroll*
+:[ integer
+:[ 1
+:[ Value to scroll months/years forward/backward. Can be negative. Is
+ configured under *on-scroll* option
-*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.
+3. Adressed by *clock: calendar: format*
+[- *Option*
+:- *Typeof*
+:- *Default*
+:- *Description*
+|[ *months*
+:[ string
+:[
+:[ Format is applied to months header(January, February,...etc.)
+|[ *days*
+:[ string
+:[
+:[ Format is applied to days
+|[ *weeks*
+:[ string
+:[ *{:%U}*
+:[ Format is applied to week numbers. When weekday format is not provided then
+ is used default format: '{:%W}' when week starts with Monday, '{:%U}' otherwise
+|[ *weekdays*
+:[ string
+:[
+:[ Format is applied to weeks header(Su,Mo,...etc.)
+|[ *today*
+:[ string
+:[ *{}*
+:[ Format is applied to Today
-*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.
+## Actions
-*today-format*: ++
- typeof: string ++
- default: {} ++
- 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.
-
-*on-click*: ++
- typeof: string ++
- Command to execute when clicked on the module.
-
-*on-click-middle*: ++
- typeof: string ++
- Command to execute when middle-clicked on the module using mousewheel.
-
-*on-click-right*: ++
- typeof: string ++
- Command to execute when you right clicked on the module.
-
-*on-update*: ++
- typeof: string ++
- Command to execute when the module is updated.
-
-*on-scroll-up*: ++
- typeof: string ++
- Command to execute when scrolling up on the module.
-
-*on-scroll-down*: ++
- typeof: string ++
- Command to execute when scrolling down on the module.
-
-*smooth-scrolling-threshold*: ++
- typeof: double ++
- Threshold to be used when scrolling.
-
-*tooltip*: ++
- typeof: bool ++
- default: true ++
- Option to disable tooltip on hover.
-
-View all valid format options in *strftime(3)*.
+[- *String*
+:- *Action*
+|[ *mode*
+:[ Switch calendar mode between year/month
+|[ *tz_up*
+:[ Switch to the next provided time zone
+|[ *tz_down*
+:[ Switch to the previous provided time zone
+|[ *shift_up*
+:[ Switch to the next calendar month/year
+|[ *shift_down*
+:[ Switch to the previous calendar month/year
# FORMAT REPLACEMENTS
-*{calendar}*: Current month calendar
-*{timezoned_time_list}*: List of time in the rest timezones, if more than one timezone is set in the config
+- *{calendar}*: Current month calendar
+- *{timezoned_time_list}*: List of time in the rest timezones, if more than one timezone is set in the config
# EXAMPLES
+1. General
+
```
"clock": {
"interval": 60,
@@ -108,6 +170,101 @@ View all valid format options in *strftime(3)*.
}
```
+2. Calendar
+
+```
+"clock": {
+ "format": "{:%H:%M} ",
+ "format-alt": "{:%A, %B %d, %Y (%R)} ",
+ "tooltip-format": "{calendar}",
+ "calendar": {
+ "mode" : "year",
+ "mode-mon-col" : 3,
+ "weeks-pos" : "right",
+ "on-scroll" : 1,
+ "on-click-right": "mode",
+ "format": {
+ "months": "{}",
+ "days": "{}",
+ "weeks": "W{}",
+ "weekdays": "{}",
+ "today": "{}"
+ }
+ },
+ "actions": {
+ "on-click-right": "mode",
+ "on-click-forward": "tz_up",
+ "on-click-backward": "tz_down",
+ "on-scroll-up": "shift_up",
+ "on-scroll-down": "shift_down"
+ }
+},
+```
+
+3. Full date on hover
+
+```
+"clock": {
+ "interval": 60,
+ "tooltip": true,
+ "format": "{:%H.%M}",
+ "tooltip-format": "{:%Y-%m-%d}",
+}
+```
+
# STYLE
- *#clock*
+
+# Troubleshooting
+
+If clock module is disabled at startup with locale::facet::\_S\_create\_c\_locale ++
+name not valid error message try one of the followings:
+
+- check if LC_TIME is set properly (glibc)
+- set locale to C in the config file (musl)
+
+The locale option must be set for {calendar} to use the correct start-of-week, regardless of system locale.
+
+## Calendar in Chinese. Alignment
+
+In order to have aligned Chinese calendar there are some useful recommendations:
+
+. Use "WenQuanYi Zen Hei Mono" which is provided in most Linux distributions
+. Try different font sizes and find best for you. size = 9pt should be fine
+. In case when "WenQuanYi Zen Hei Mono" font is used disable monospace font pango tag
+
+Example of working config
+
+```
+"clock": {
+ "format": "{:%H:%M} ",
+ "format-alt": "{:%A, %B %d, %Y (%R)} ",
+ "tooltip-format": "\n{calendar}",
+ "calendar": {
+ "mode" : "year",
+ "mode-mon-col" : 3,
+ "weeks-pos" : "right",
+ "on-scroll" : 1,
+ "on-click-right": "mode",
+ "format": {
+ "months": "{}",
+ "days": "{}",
+ "weeks": "W{}",
+ "weekdays": "{}",
+ "today": "{}"
+ }
+ },
+ "actions": {
+ "on-click-right": "mode",
+ "on-click-forward": "tz_up",
+ "on-click-backward": "tz_down",
+ "on-scroll-up": "shift_up",
+ "on-scroll-down": "shift_down"
+ }
+ },
+```
+
+# AUTHOR
+
+Alexis Rouillard
diff --git a/man/waybar-dwl-tags.5.scd b/man/waybar-dwl-tags.5.scd
new file mode 100644
index 00000000..06fb577f
--- /dev/null
+++ b/man/waybar-dwl-tags.5.scd
@@ -0,0 +1,49 @@
+waybar-dwl-tags(5)
+
+# NAME
+
+waybar - dwl tags module
+
+# DESCRIPTION
+
+The *tags* module displays the current state of tags in dwl.
+
+# CONFIGURATION
+
+Addressed by *dwl/tags*
+
+*num-tags*: ++
+ typeof: uint ++
+ default: 9 ++
+ The number of tags that should be displayed. Max 32.
+
+*tag-labels*: ++
+ typeof: array ++
+ The label to display for each tag.
+
+*disable-click*: ++
+ typeof: bool ++
+ default: false ++
+ If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled.
+
+# EXAMPLE
+
+```
+"dwl/tags": {
+ "num-tags": 5
+}
+```
+
+# STYLE
+
+- *#tags button*
+- *#tags button.occupied*
+- *#tags button.focused*
+- *#tags button.urgent*
+
+Note that occupied/focused/urgent status may overlap. That is, a tag may be
+both occupied and focused at the same time.
+
+# SEE ALSO
+
+waybar(5), dwl(1)
diff --git a/man/waybar-hyprland-language.5.scd b/man/waybar-hyprland-language.5.scd
index cb169951..3e92def8 100644
--- a/man/waybar-hyprland-language.5.scd
+++ b/man/waybar-hyprland-language.5.scd
@@ -15,7 +15,7 @@ Addressed by *hyprland/language*
*format*: ++
typeof: string ++
default: {} ++
- The format, how information should be displayed. On {} the currently selected language is displayed.
+ The format, how information should be displayed.
*format-* ++
typeof: string++
@@ -23,18 +23,28 @@ Addressed by *hyprland/language*
*keyboard-name*: ++
typeof: string ++
- Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "AT Translated set..." is recommended.
+ Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
+# 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
```
"hyprland/language": {
- "format": "Lang: {}"
- "format-us": "AMERICA, HELL YEAH!" // For American English
- "format-tr": "As bayrakları" // For Turkish
- "keyboard-name": "AT Translated Set 2 keyboard"
+ "format": "Lang: {long}"
+ "format-en": "AMERICA, HELL YEAH!"
+ "format-tr": "As bayrakları"
+ "keyboard-name": "at-translated-set-2-keyboard"
}
```
diff --git a/man/waybar-hyprland-submap.5.scd b/man/waybar-hyprland-submap.5.scd
new file mode 100644
index 00000000..a00a2762
--- /dev/null
+++ b/man/waybar-hyprland-submap.5.scd
@@ -0,0 +1,82 @@
+waybar-hyprland-submap(5)
+
+# NAME
+
+waybar - hyprland submap module
+
+# DESCRIPTION
+
+The *submap* module displays the currently active submap similar to *sway/mode*.
+
+# CONFIGURATION
+
+Addressed by *hyprland/submap*
+
+*format*: ++
+ typeof: string ++
+ default: {} ++
+ The format, how information should be displayed. On {} the currently active submap is displayed.
+
+*rotate*: ++
+ typeof: integer ++
+ Positive value to rotate the text label.
+
+*max-length*: ++
+ typeof: integer ++
+ The maximum length in character the module should display.
+
+*min-length*: ++
+ typeof: integer ++
+ The minimum length in characters the module should take up.
+
+*align*: ++
+ typeof: float ++
+ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
+
+*on-click*: ++
+ typeof: string ++
+ Command to execute when clicked on the module.
+
+*on-click-middle*: ++
+ typeof: string ++
+ Command to execute when middle-clicked on the module using mousewheel.
+
+*on-click-right*: ++
+ typeof: string ++
+ Command to execute when you right clicked on the module.
+
+*on-update*: ++
+ typeof: string ++
+ Command to execute when the module is updated.
+
+*on-scroll-up*: ++
+ typeof: string ++
+ Command to execute when scrolling up on the module.
+
+*on-scroll-down*: ++
+ typeof: string ++
+ Command to execute when scrolling down on the module.
+
+*smooth-scrolling-threshold*: ++
+ typeof: double ++
+ Threshold to be used when scrolling.
+
+*tooltip*: ++
+ typeof: bool ++
+ default: true ++
+ Option to disable tooltip on hover.
+
+
+# EXAMPLES
+
+```
+"hyprland/submap": {
+ "format": "✌️ {}",
+ "max-length": 8,
+ "tooltip": false
+}
+```
+
+# STYLE
+
+- *#submap*
diff --git a/man/waybar-hyprland-workspaces.5.scd b/man/waybar-hyprland-workspaces.5.scd
new file mode 100644
index 00000000..0678fb22
--- /dev/null
+++ b/man/waybar-hyprland-workspaces.5.scd
@@ -0,0 +1,59 @@
+waybar-wlr-workspaces(5)
+
+# NAME
+
+waybar - hyprland workspaces module
+
+# DESCRIPTION
+
+The *workspaces* module displays the currently used workspaces in hyprland compositor.
+
+# CONFIGURATION
+
+Addressed by *hyprland/workspaces*
+
+*format*: ++
+ typeof: string ++
+ default: {id} ++
+ The format, how information should be displayed.
+
+*format-icons*: ++
+ typeof: array ++
+ Based on the workspace id and state, the corresponding icon gets selected. See *icons*.
+
+# FORMAT REPLACEMENTS
+
+*{id}*: id of workspace assigned by compositor
+
+*{icon}*: Icon, as defined in *format-icons*.
+
+# ICONS
+
+Additional to workspace name matching, the following *format-icons* can be set.
+
+- *default*: Will be shown, when no string match is found.
+- *active*: Will be shown, when workspace is active
+
+# EXAMPLES
+
+```
+"wlr/workspaces": {
+ "format": "{name}: {icon}",
+ "format-icons": {
+ "1": "",
+ "2": "",
+ "3": "",
+ "4": "",
+ "5": "",
+ "active": "",
+ "default": ""
+ },
+ "sort-by-number": true
+}
+```
+
+# Style
+
+- *#workspaces*
+- *#workspaces button*
+- *#workspaces button.active*
diff --git a/man/waybar-image.5.scd b/man/waybar-image.5.scd
index feff9f6e..d47dba39 100644
--- a/man/waybar-image.5.scd
+++ b/man/waybar-image.5.scd
@@ -1,4 +1,4 @@
-waybar-custom(5)
+waybar-image(5)
# NAME
@@ -10,12 +10,13 @@ The *image* module displays an image from a path.
# CONFIGURATION
-Addressed by *custom/*
-
*path*: ++
typeof: string ++
The path to the image.
-
+*exec*: ++
+ typeof: string ++
+ The path to the script, which should return image path file
+ it will only execute if the path is not set
*size*: ++
typeof: integer ++
The width/height to render the image.
@@ -56,17 +57,31 @@ Addressed by *custom/*
typeof: double ++
Threshold to be used when scrolling.
-# EXAMPLES
+*tooltip*: ++
+ typeof: bool ++
+ default: true ++
+ Option to enable tooltip on hover.
-## Spotify:
+# SCRIPT OUTPUT
-## mpd:
+Similar to the *custom* module, output values of the script is *newline* separated.
+The following is the output format:
```
-"image/album-art": {
+$path\\n$tooltip
+```
+
+# EXAMPLES
+
+```
+"image#album-art": {
"path": "/tmp/mpd_art",
"size": 32,
"interval": 5,
"on-click": "mpc toggle"
}
-```
\ No newline at end of file
+```
+
+# STYLE
+
+- *#image*
diff --git a/man/waybar-mpd.5.scd b/man/waybar-mpd.5.scd
index 044af98e..1dde8f79 100644
--- a/man/waybar-mpd.5.scd
+++ b/man/waybar-mpd.5.scd
@@ -74,20 +74,20 @@ Addressed by *mpd*
Tooltip information displayed when the MPD server can't be reached.
*artist-len*: ++
- typeof: integer ++
- Maximum length of the Artist tag.
+ typeof: integer ++
+ Maximum length of the Artist tag.
*album-len*: ++
- typeof: integer ++
- Maximum length of the Album tag.
+ typeof: integer ++
+ Maximum length of the Album tag.
*album-artist-len*: ++
- typeof: integer ++
- Maximum length of the Album Artist tag.
+ typeof: integer ++
+ Maximum length of the Album Artist tag.
*title-len*: ++
- typeof: integer ++
- Maximum length of the Title tag.
+ typeof: integer ++
+ Maximum length of the Title tag.
*rotate*: ++
typeof: integer ++
@@ -98,12 +98,12 @@ Addressed by *mpd*
The maximum length in character the module should display.
*min-length*: ++
- typeof: integer ++
- The minimum length in characters the module should take up.
+ 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.
+ 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 ++
diff --git a/man/waybar-mpris.5.scd b/man/waybar-mpris.5.scd
new file mode 100644
index 00000000..ad5c1df5
--- /dev/null
+++ b/man/waybar-mpris.5.scd
@@ -0,0 +1,197 @@
+waybar-mpris(5)
+
+# NAME
+
+waybar - MPRIS module
+
+# DESCRIPTION
+
+The *mpris* module displays currently playing media via libplayerctl.
+
+# CONFIGURATION
+
+*player*: ++
+ typeof: string ++
+ default: playerctld ++
+ Name of the MPRIS player to attach to. Using the default value always
+ follows the currenly active player.
+
+*ignored-players*: ++
+ typeof: []string ++
+ Ignore updates of the listed players, when using playerctld.
+
+*interval*: ++
+ typeof: integer ++
+ Refresh MPRIS information on a timer.
+
+*format*: ++
+ typeof: string ++
+ default: {player} ({status}) {dynamic} ++
+ The text format.
+
+*format-[status]*: ++
+ typeof: string ++
+ The status-specific text format.
+
+*tooltip*: ++
+ typeof: bool ++
+ default: true ++
+ Option to disable tooltip on hover.
+
+*tooltip-format*: ++
+ typeof: string ++
+ default: {player} ({status}) {dynamic} ++
+ The tooltip text format.
+
+*tooltip-format-[status]*: ++
+ typeof: string ++
+ The status-specific tooltip format.
+
+*artist-len*: ++
+ typeof: integer ++
+ Maximum length of the Artist tag (Wide/Fullwidth Unicode characters
+ count as two). Set to zero to hide the artist in `{dynamic}` tag.
+
+*album-len*: ++
+ typeof: integer ++
+ Maximum length of the Album tag (Wide/Fullwidth Unicode characters count
+ as two). Set to zero to hide the album in `{dynamic}` tag.
+
+*title-len*: ++
+ typeof: integer ++
+ Maximum length of the Title tag (Wide/Fullwidth Unicode characters count
+ as two). Set to zero to hide the title in `{dynamic}` tag.
+
+*dynamic-len*: ++
+ typeof: integer ++
+ Maximum length of the Dynamic tag (Wide/Fullwidth Unicode characters ++
+ count as two). The dynamic tag will not truncate any tags beyond their ++
+ set length limits, instead, it will attempt to fit as much of the ++
+ available tags as possible. It is recommended you set title-len to ++
+ something less than or equal to this value, so the title will always be ++
+ displayed.
+
+*dynamic-order*: ++
+ typeof: []string ++
+ default: ["title", "artist", "album", "position", "length"] ++
+ Order of the tags shown by Dynamic tag. The position and length tags ++
+ will always be combined in the format [{position}/{length}]. The order ++
+ of these tags in relation to other tags will be determined based on the ++
+ declaration of the first among the two tags. Absence in this list means ++
+ force exclusion.
+
+*dynamic-importance-order*: ++
+ typeof: []string ++
+ default: ["title", "artist", "album", "position", "length"] ++
+ Priority of the tags when truncating the Dynamic tag. The final ones ++
+ will be the first to be truncated. Absence in this list means force ++
+ inclusion.
+
+*dynamic-separator*: ++
+ typeof: string ++
+ default: " - " ++
+ These characters will be used to separate two different tags, except ++
+ when one of these tags is position and length.
+
+*truncate-hours*: ++
+ typeof: bool ++
+ default: true ++
+ Whether to hide hours when media duration is less than an hour long.
+
+*enable-tooltip-len-limits*: ++
+ typeof: bool ++
+ default: false ++
+ Option to enable the length limits for the tooltip as well. By default
+ the tooltip ignores all length limits.
+
+*ellipsis*: ++
+ typeof: string ++
+ default: "…" ++
+ This character will be used when any of the tags exceed their maximum
+ length. If you don't want to use an ellipsis, set this to empty string.
+
+*rotate*: ++
+ typeof: integer ++
+ Positive value to rotate the text label.
+
+*max-length*: ++
+ typeof: integer ++
+ The maximum length in character the module should display.
+
+*min-length*: ++
+ typeof: integer ++
+ The minimum length in characters the module should take up.
+
+*align*: ++
+ typeof: float ++
+ The alignment of the text, where 0 is left-aligned and 1 is right-aligned.
+ If the module is rotated, it will follow the flow of the text.
+
+*on-click*: ++
+ typeof: string ++
+ default: play-pause ++
+ Overwrite default action toggles.
+
+*on-middle-click*: ++
+ typeof: string ++
+ default: previous track ++
+ Overwrite default action toggles.
+
+*on-right-click*: ++
+ typeof: string ++
+ default: next track ++
+ Overwrite default action toggles.
+
+*player-icons*: ++
+ typeof: map[string]string ++
+ Allows setting _{player-icon}_ based on player-name property.
+
+*status-icons*: ++
+ typeof: map[string]string ++
+ Allows setting _{status-icon}_ based on player status (playing, paused,
+ stopped).
+
+
+# FORMAT REPLACEMENTS
+
+*{player}*: The name of the current media player
+
+*{status}*: The current status (playing, paused, stopped)
+
+*{artist}*: The artist of the current track
+
+*{album}*: The album title of the current track
+
+*{title}*: The title of the current track
+
+*{length}*: Length of the track, formatted as HH:MM:SS
+
+*{dynamic}*: Use _{artist}_, _{album}_, _{title}_ and _{length}_, automatically omit++
+ empty values
+
+*{player_icon}*: Chooses an icon from _player-icons_ based on _{player}_
+
+*{status_icon}*: Chooses an icon from _status-icons_ based on _{status}_
+
+# EXAMPLES
+
+```
+"mpris": {
+ "format": "{player_icon} {dynamic}",
+ "format-paused": "{status_icon} {dynamic}",
+ "player-icons": {
+ "default": "▶",
+ "mpv": "🎵"
+ },
+ "status-icons": {
+ "paused": "⏸"
+ },
+ // "ignored-players": ["firefox"]
+}
+```
+
+# STYLE
+
+- *#mpris*
+- *#mpris.${status}*
+- *#mpris.${player}*
diff --git a/man/waybar-pulseaudio.5.scd b/man/waybar-pulseaudio.5.scd
index b941c22c..bdb9c993 100644
--- a/man/waybar-pulseaudio.5.scd
+++ b/man/waybar-pulseaudio.5.scd
@@ -51,12 +51,12 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
The maximum length in character the module should display.
*min-length*: ++
- typeof: integer ++
- The minimum length in characters the module should take up.
+ 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.
+ 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 ++
@@ -91,6 +91,10 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
typeof: double ++
Threshold to be used when scrolling.
+*reverse-scrolling*: ++
+ typeof: bool ++
+ Option to reverse the scroll direction.
+
*tooltip*: ++
typeof: bool ++
default: true ++
diff --git a/man/waybar-river-layout.5.scd b/man/waybar-river-layout.5.scd
new file mode 100644
index 00000000..5b18eee8
--- /dev/null
+++ b/man/waybar-river-layout.5.scd
@@ -0,0 +1,67 @@
+waybar-river-layout(5)
+
+# NAME
+
+waybar - river layout module
+
+# DESCRIPTION
+
+The *layout* module displays the current layout in river.
+
+It may not be set until a layout is first applied.
+
+# CONFIGURATION
+
+Addressed by *river/layout*
+
+*format*: ++
+ typeof: string ++
+ default: {} ++
+ The format, how information should be displayed. On {} data gets inserted.
+
+*rotate*: ++
+ typeof: integer ++
+ Positive value to rotate the text label.
+
+*max-length*: ++
+ typeof: integer ++
+ The maximum length in character the module should display.
+
+*min-length*: ++
+ typeof: integer ++
+ The minimum length in characters the module should take up.
+
+*align*: ++
+ typeof: float ++
+ The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
+
+*on-click*: ++
+ typeof: string ++
+ Command to execute when clicked on the module.
+
+*on-click-middle*: ++
+ typeof: string ++
+ Command to execute when middle-clicked on the module using mousewheel.
+
+*on-click-right*: ++
+ typeof: string ++
+ Command to execute when you right clicked on the module.
+
+# EXAMPLE
+
+```
+"river/layout": {
+ "format": "{}",
+ "min-length": 4,
+ "align": "right"
+}
+```
+
+# STYLE
+
+- *#layout*
+- *#layout.focused* Applied when the output this module's bar belongs to is focused.
+
+# SEE ALSO
+
+waybar(5), river(1)
diff --git a/man/waybar-sway-window.5.scd b/man/waybar-sway-window.5.scd
index 6e5ebdb6..0dd16295 100644
--- a/man/waybar-sway-window.5.scd
+++ b/man/waybar-sway-window.5.scd
@@ -66,9 +66,28 @@ Addressed by *sway/window*
default: true ++
Option to disable tooltip on hover.
+*all-outputs*: ++
+ typeof: bool ++
+ default: false ++
+ Option to show the focused window along with its workspace styles on all outputs.
+
+*offscreen-css*: ++
+ typeof: bool ++
+ default: false ++
+ Only effective when all-outputs is true. Adds style according to present windows on unfocused outputs instead of showing the focused window and style.
+
+*offscreen-css-text*: ++
+ typeof: string ++
+ Only effective when both all-outputs and offscreen-style are true. On screens currently not focused, show the given text along with that workspaces styles.
+
+*show-focused-workspace-name*: ++
+ typeof: bool ++
+ default: false ++
+ If the workspace itself is focused and the workspace contains nodes or floating_nodes, show the workspace name. If not set, text remains empty but styles according to nodes in the workspace are still applied.
+
*rewrite*: ++
typeof: object ++
- Rules to rewrite window title. See *rewrite rules*.
+ Rules to rewrite the module format output. See *rewrite rules*.
*icon*: ++
typeof: bool ++
@@ -97,7 +116,7 @@ captures of the expression.
Regular expression and replacement follow ECMA-script rules.
-If no expression matches, the title is left unchanged.
+If no expression matches, the format output is left unchanged.
Invalid expressions (e.g., mismatched parentheses) are skipped.
@@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
# STYLE
- *#window*
-- *window#waybar.empty* When no windows is in the workspace
-- *window#waybar.solo* When one window is in the workspace
+- *window#waybar.empty* When no windows are in the workspace, or screen is not focused and offscreen-css option is not set
+- *window#waybar.solo* When one tiled window is in the workspace
+- *window#waybar.floating* When there are only floating windows in the workspace
+- *window#waybar.stacked* When there is more than one window in the workspace and the workspace layout is stacked
+- *window#waybar.tabbed* When there is more than one window in the workspace and the workspace layout is tabbed
+- *window#waybar.tiled* When there is more than one window in the workspace and the workspace layout is splith or splitv
- *window#waybar.* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace
diff --git a/man/waybar-sway-workspaces.5.scd b/man/waybar-sway-workspaces.5.scd
index b575e098..1e5f45db 100644
--- a/man/waybar-sway-workspaces.5.scd
+++ b/man/waybar-sway-workspaces.5.scd
@@ -73,6 +73,15 @@ Addressed by *sway/workspaces*
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.
+*alphabetical_sort*: ++
+ typeof: bool ++
+ Whether to sort workspaces alphabetically. Please note this can make "swaymsg workspace prev/next" move to workspaces inconsistent with the ordering shown in Waybar.
+
+warp-on-scroll: ++
+ typeof: bool ++
+ default: true ++
+ If set to false, you can scroll to cycle through workspaces without mouse warping being enabled. If set to true this behaviour is disabled.
+
# FORMAT REPLACEMENTS
*{value}*: Name of the workspace, as defined by sway.
@@ -83,6 +92,8 @@ Addressed by *sway/workspaces*
*{index}*: Index of the workspace.
+*{output}*: Output where the workspace is located.
+
# ICONS
Additional to workspace name matching, the following *format-icons* can be set.
diff --git a/man/waybar-temperature.5.scd b/man/waybar-temperature.5.scd
index 8d11e517..cc689dc0 100644
--- a/man/waybar-temperature.5.scd
+++ b/man/waybar-temperature.5.scd
@@ -19,6 +19,8 @@ Addressed by *temperature*
*hwmon-path*: ++
typeof: string ++
The temperature path to use, e.g. */sys/class/hwmon/hwmon2/temp1_input* instead of one in */sys/class/thermal/*.
+ This can also be an array of strings. In this case, waybar will check each item in the array and use the first valid one.
+ This is suitable if you want to share the same configuration file among different machines with different hardware configurations.
*hwmon-path-abs*: ++
typeof: string ++
@@ -117,7 +119,7 @@ Addressed by *temperature*
```
"temperature": {
// "thermal-zone": 2,
- // "hwmon-path": "/sys/class/hwmon/hwmon2/temp1_input",
+ // "hwmon-path": ["/sys/class/hwmon/hwmon2/temp1_input", "/sys/class/thermal/thermal_zone0/temp"],
// "critical-threshold": 80,
// "format-critical": "{temperatureC}°C ",
"format": "{temperatureC}°C "
diff --git a/man/waybar-upower.5.scd b/man/waybar-upower.5.scd
index a6ba4df0..fc37b665 100644
--- a/man/waybar-upower.5.scd
+++ b/man/waybar-upower.5.scd
@@ -11,6 +11,12 @@ compatible devices in the tooltip.
# CONFIGURATION
+*native-path*: ++
+ typeof: string ++
+ default: ++
+ The battery to monitor. Refer to the https://upower.freedesktop.org/docs/UpDevice.html#UpDevice--native-path ++
+ Can be obtained using `upower --dump`
+
*icon-size*: ++
typeof: integer ++
default: 20 ++
@@ -68,6 +74,25 @@ depending on the charging state.
"tooltip-spacing": 20
}
+```
+```
+"upower": {
+ "native-path": "/org/bluez/hci0/dev_D4_AE_41_38_D0_EF",
+ "icon-size": 20,
+ "hide-if-empty": true,
+ "tooltip": true,
+ "tooltip-spacing": 20
+}
+
+```
+```
+"upower": {
+ "native-path": "battery_sony_controller_battery_d0o27o88o32ofcoee",
+ "icon-size": 20,
+ "hide-if-empty": true,
+ "tooltip": true,
+ "tooltip-spacing": 20
+}
```
# STYLE
diff --git a/man/waybar-wireplumber.5.scd b/man/waybar-wireplumber.5.scd
index 3cf5694d..473df926 100644
--- a/man/waybar-wireplumber.5.scd
+++ b/man/waybar-wireplumber.5.scd
@@ -11,59 +11,77 @@ The *wireplumber* module displays the current volume reported by WirePlumber.
# CONFIGURATION
*format*: ++
- typeof: string ++
- default: *{volume}%* ++
- The format, how information should be displayed. This format is used when other formats aren't specified.
+ typeof: string ++
+ default: *{volume}%* ++
+ The format, how information should be displayed. This format is used when other formats aren't specified.
- *format-muted*: ++
- typeof: string ++
- This format is used when the sound is muted.
+*format-muted*: ++
+ typeof: string ++
+ This format is used when the sound is muted.
*tooltip*: ++
- typeof: bool ++
- default: *true* ++
- Option to disable tooltip on hover.
+ typeof: bool ++
+ default: *true* ++
+ Option to disable tooltip on hover.
*tooltip-format*: ++
- typeof: string ++
- default: *{node_name}* ++
- The format of information displayed in the tooltip.
+ typeof: string ++
+ default: *{node_name}* ++
+ The format of information displayed in the tooltip.
*rotate*: ++
- typeof: integer ++
- Positive value to rotate the text label.
+ typeof: integer ++
+ Positive value to rotate the text label.
*states*: ++
- typeof: object ++
- A number of volume states which get activated on certain volume levels. See *waybar-states(5)*.
+ typeof: object ++
+ A number of volume states which get activated on certain volume levels. See *waybar-states(5)*.
*max-length*: ++
- typeof: integer ++
- The maximum length in character the module should display.
+ 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.
+ 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.
+ 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 ++
+ The speed in which to change the volume when scrolling.
*on-click*: ++
- typeof: string ++
- Command to execute when clicked on the module.
+ typeof: string ++
+ Command to execute when clicked on the module.
*on-click-middle*: ++
- typeof: string ++
- Command to execute when middle-clicked on the module using mousewheel.
+ 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.
+ typeof: string ++
+ Command to execute when you right clicked on the module.
*on-update*: ++
- typeof: string ++
- Command to execute when the module is updated.
+ 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.
+
+*max-volume*: ++
+ typeof: float ++
+ default: 100 ++
+ The maximum volume that can be set, in percentage.
# FORMAT REPLACEMENTS
diff --git a/man/waybar-wlr-taskbar.5.scd b/man/waybar-wlr-taskbar.5.scd
index b2946ac5..5626eaec 100644
--- a/man/waybar-wlr-taskbar.5.scd
+++ b/man/waybar-wlr-taskbar.5.scd
@@ -52,6 +52,11 @@ Addressed by *wlr/taskbar*
default: false ++
If set to true, always reorder the tasks in the taskbar so that the currently active one is first. Otherwise don't reorder.
+*sort-by-app-id*: ++
+ typeof: bool ++
+ default: false ++
+ If set to true, group tasks by their app_id. Cannot be used with 'active-first'.
+
*on-click*: ++
typeof: string ++
The action which should be triggered when clicking on the application button with the left mouse button.
@@ -76,6 +81,10 @@ Addressed by *wlr/taskbar*
typeof: object ++
Dictionary of app_id to be replaced with
+*rewrite*: ++
+ typeof: object ++
+ Rules to rewrite the module format output. See *rewrite rules*.
+
# FORMAT REPLACEMENTS
*{icon}*: The icon of the application.
@@ -104,6 +113,18 @@ Addressed by *wlr/taskbar*
*close*: Close the application.
+# 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 format output is left unchanged.
+
+Invalid expressions (e.g., mismatched parentheses) are skipped.
+
# EXAMPLES
```
@@ -119,6 +140,10 @@ Addressed by *wlr/taskbar*
],
"app_ids-mapping": {
"firefoxdeveloperedition": "firefox-developer-edition"
+ },
+ "rewrite": {
+ "Firefox Web Browser": "Firefox",
+ "Foot Server": "Terminal"
}
}
```
diff --git a/man/waybar-wlr-workspaces.5.scd b/man/waybar-wlr-workspaces.5.scd
index 169112fc..4a256f02 100644
--- a/man/waybar-wlr-workspaces.5.scd
+++ b/man/waybar-wlr-workspaces.5.scd
@@ -65,7 +65,7 @@ Addressed by *wlr/workspaces*
Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string match is found.
-- *focused*: Will be shown, when workspace is focused
+- *active*: Will be shown, when workspace is active
# EXAMPLES
@@ -78,7 +78,7 @@ Additional to workspace name matching, the following *format-icons* can be set.
"3": "",
"4": "",
"5": "",
- "focused": "",
+ "active": "",
"default": ""
},
"sort-by-number": true
diff --git a/man/waybar.5.scd.in b/man/waybar.5.scd.in
index db94da64..0b3dc748 100644
--- a/man/waybar.5.scd.in
+++ b/man/waybar.5.scd.in
@@ -31,6 +31,7 @@ Also a minimal example configuration can be found on the at the bottom of this m
typeof: string|array ++
Specifies on which screen this bar will be displayed. Exclamation mark(*!*) can be used to exclude specific output.
Output specification follows sway's and can either be the output port such as "HDMI-A-1" or a string consisting of the make, model and serial such as "Some Company ABC123 0x00000000". See *sway-output(5)* for details.
+ In an array, star '*\**' can be used at the end to accept all outputs, in case all previous entries are exclusions.
*position* ++
typeof: string ++
@@ -78,7 +79,12 @@ Also a minimal example configuration can be found on the at the bottom of this m
Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++
Note: *hide* and *invisible* modes may be not as useful without Sway IPC.
-modifier-reset ++
+*start_hidden* ++
+ typeof: bool ++
+ default: *false* ++
+ Option to start the bar hidden.
+
+*modifier-reset* ++
typeof: string ++
default: *press*
Defines the timing of modifier key to reset the bar visibility.
@@ -243,6 +249,7 @@ A module group is defined by specifying a module named "group/some-group-name".
"modules-right": ["group/hardware", "clock"],
"group/hardware": {
+ "orientation": "vertical",
"modules": [
"cpu",
"memory",
@@ -254,6 +261,8 @@ A module group is defined by specifying a module named "group/some-group-name".
}
```
+Valid options for the (optional) "orientation" property are: "horizontal", "vertical", "inherit", and "orthogonal" (default).
+
# SUPPORTED MODULES
- *waybar-backlight(5)*
@@ -264,19 +273,23 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-custom(5)*
- *waybar-disk(5)*
- *waybar-idle-inhibitor(5)*
+- *waybar-image(5)*
- *waybar-keyboard-state(5)*
- *waybar-memory(5)*
- *waybar-mpd(5)*
+- *waybar-mpris(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-river-mode(5)*
- *waybar-river-tags(5)*
- *waybar-river-window(5)*
+- *waybar-river-layout(5)*
- *waybar-states(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
+- *waybar-wireplumber(5)*
- *waybar-wlr-taskbar(5)*
- *waybar-wlr-workspaces(5)*
- *waybar-temperature(5)*
diff --git a/meson.build b/meson.build
index 557a02dc..aa250b7f 100644
--- a/meson.build
+++ b/meson.build
@@ -1,8 +1,8 @@
project(
'waybar', 'cpp', 'c',
- version: '0.9.16',
+ version: '0.9.18',
license: 'MIT',
- meson_version: '>= 0.49.0',
+ meson_version: '>= 0.50.0',
default_options : [
'cpp_std=c++17',
'buildtype=release',
@@ -86,7 +86,10 @@ wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
-giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled()))
+giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or
+ get_option('logind').enabled() or
+ get_option('upower_glib').enabled() or
+ get_option('mpris').enabled()))
jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])
sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false)
@@ -95,6 +98,7 @@ libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
+playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev'))
@@ -119,11 +123,18 @@ 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',
- required: false,
- default_options : [ 'use_system_tzdb=true' ],
- modules : [ 'date::date', 'date::date-tz' ],
- fallback: [ 'date', 'tz_dep' ])
+
+cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include ')
+have_chrono_timezones = cpp_lib_chrono >= 201907
+if have_chrono_timezones
+ tz_dep = declare_dependency()
+else
+ tz_dep = dependency('date',
+ required: false,
+ default_options : [ 'use_system_tzdb=true' ],
+ modules : [ 'date::date', 'date::date-tz' ],
+ fallback: [ 'date', 'tz_dep' ])
+endif
prefix = get_option('prefix')
sysconfdir = get_option('sysconfdir')
@@ -159,11 +170,15 @@ src_files = files(
'src/client.cpp',
'src/config.cpp',
'src/group.cpp',
+ 'src/util/prepare_for_sleep.cpp',
'src/util/ustring_clen.cpp',
'src/util/sanitize_str.cpp',
- 'src/util/rewrite_title.cpp'
+ 'src/util/rewrite_string.cpp',
+ 'src/util/gtk_icon.cpp'
)
+inc_dirs = ['include']
+
if is_linux
add_project_arguments('-DHAVE_CPU_LINUX', language: 'cpp')
add_project_arguments('-DHAVE_MEMORY_LINUX', language: 'cpp')
@@ -213,6 +228,12 @@ if true
src_files += 'src/modules/river/mode.cpp'
src_files += 'src/modules/river/tags.cpp'
src_files += 'src/modules/river/window.cpp'
+ src_files += 'src/modules/river/layout.cpp'
+endif
+
+if true
+ add_project_arguments('-DHAVE_DWL', language: 'cpp')
+ src_files += 'src/modules/dwl/tags.cpp'
endif
if true
@@ -220,6 +241,8 @@ if true
src_files += 'src/modules/hyprland/backend.cpp'
src_files += 'src/modules/hyprland/window.cpp'
src_files += 'src/modules/hyprland/language.cpp'
+ src_files += 'src/modules/hyprland/submap.cpp'
+ src_files += 'src/modules/hyprland/workspaces.cpp'
endif
if libnl.found() and libnlgen.found()
@@ -238,6 +261,11 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable
src_files += 'src/modules/upower/upower_tooltip.cpp'
endif
+if (playerctl.found() and giounix.found() and not get_option('logind').disabled())
+ add_project_arguments('-DHAVE_MPRIS', language: 'cpp')
+ src_files += 'src/modules/mpris/mpris.cpp'
+endif
+
if libpulse.found()
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
src_files += 'src/modules/pulseaudio.cpp'
@@ -302,7 +330,10 @@ if get_option('rfkill').enabled() and is_linux
)
endif
-if tz_dep.found()
+if have_chrono_timezones
+ add_project_arguments('-DHAVE_CHRONO_TIMEZONES', language: 'cpp')
+ src_files += 'src/modules/clock.cpp'
+elif tz_dep.found()
add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
src_files += 'src/modules/clock.cpp'
else
@@ -313,6 +344,17 @@ if get_option('experimental')
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
endif
+cava = dependency('cava',
+ version : '>=0.8.4',
+ required: get_option('cava'),
+ fallback : ['cava', 'cava_dep'],
+ not_found_message: 'cava is not found. Building waybar without cava')
+
+if cava.found()
+ add_project_arguments('-DHAVE_LIBCAVA', language: 'cpp')
+ src_files += 'src/modules/cava.cpp'
+endif
+
subdir('protocol')
executable(
@@ -334,6 +376,7 @@ executable(
libnl,
libnlgen,
upower_glib,
+ playerctl,
libpulse,
libjack,
libwireplumber,
@@ -345,9 +388,10 @@ executable(
gtk_layer_shell,
libsndio,
tz_dep,
- xkbregistry
+ xkbregistry,
+ cava
],
- include_directories: [include_directories('include')],
+ include_directories: inc_dirs,
install: true,
)
@@ -384,14 +428,17 @@ if scdoc.found()
'waybar-disk.5.scd',
'waybar-gamemode.5.scd',
'waybar-idle-inhibitor.5.scd',
+ 'waybar-image.5.scd',
'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd',
'waybar-mpd.5.scd',
+ 'waybar-mpris.5.scd',
'waybar-network.5.scd',
'waybar-pulseaudio.5.scd',
'waybar-river-mode.5.scd',
'waybar-river-tags.5.scd',
'waybar-river-window.5.scd',
+ 'waybar-river-layout.5.scd',
'waybar-sway-language.5.scd',
'waybar-sway-mode.5.scd',
'waybar-sway-scratchpad.5.scd',
@@ -405,6 +452,8 @@ if scdoc.found()
'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd',
'waybar-upower.5.scd',
+ 'waybar-wireplumber.5.scd',
+ 'waybar-dwl-tags.5.scd',
]
if (giounix.found() and not get_option('logind').disabled())
@@ -435,7 +484,7 @@ endif
catch2 = dependency(
'catch2',
- version: '>=3.0.0',
+ version: '>=2.0.0',
fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'),
)
diff --git a/meson_options.txt b/meson_options.txt
index 402912f4..7dacf087 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -5,6 +5,7 @@ option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev s
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('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower')
+option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris')
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')
@@ -16,4 +17,5 @@ option('logind', type: 'feature', value: 'auto', description: 'Enable support fo
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')
-option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
\ No newline at end of file
+option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')
+option('cava', type: 'feature', value: 'auto', description: 'Enable support for Cava')
diff --git a/nix/default.nix b/nix/default.nix
new file mode 100644
index 00000000..fc77225d
--- /dev/null
+++ b/nix/default.nix
@@ -0,0 +1,20 @@
+{ lib
+, waybar
+, version
+}:
+
+waybar.overrideAttrs (prev: {
+ inherit version;
+ # version = "0.9.17";
+
+ src = lib.cleanSourceWith {
+ filter = name: type:
+ let
+ baseName = baseNameOf (toString name);
+ in
+ ! (
+ lib.hasSuffix ".nix" baseName
+ );
+ src = lib.cleanSource ../.;
+ };
+})
diff --git a/protocol/dwl-ipc-unstable-v2.xml b/protocol/dwl-ipc-unstable-v2.xml
new file mode 100644
index 00000000..74a212f0
--- /dev/null
+++ b/protocol/dwl-ipc-unstable-v2.xml
@@ -0,0 +1,166 @@
+
+
+
+
+ This protocol allows clients to update and get updates from dwl.
+
+ Warning! The protocol described in this file is experimental and
+ backward incompatible changes may be made. Backward compatible
+ changes may be added together with the corresponding interface
+ version bump.
+ Backward incompatible changes are done by bumping the version
+ number in the protocol and interface names and resetting the
+ interface version. Once the protocol is to be declared stable,
+ the 'z' prefix and the version number in the protocol and
+ interface names are removed and the interface version number is
+ reset.
+
+
+
+
+ This interface is exposed as a global in wl_registry.
+
+ Clients can use this interface to get a dwl_ipc_output.
+ After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
+ The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.
+
+
+
+
+ Indicates that the client will not the dwl_ipc_manager object anymore.
+ Objects created through this instance are not affected.
+
+
+
+
+
+ Get a dwl_ipc_outout for the specified wl_output.
+
+
+
+
+
+
+
+ This event is sent after binding.
+ A roundtrip after binding guarantees the client recieved all tags.
+
+
+
+
+
+
+ This event is sent after binding.
+ A roundtrip after binding guarantees the client recieved all layouts.
+
+
+
+
+
+
+
+ Observe and control a dwl output.
+
+ Events are double-buffered:
+ Clients should cache events and redraw when a dwl_ipc_output.frame event is sent.
+
+ Request are not double-buffered:
+ The compositor will update immediately upon request.
+
+
+
+
+
+
+
+
+
+
+ Indicates to that the client no longer needs this dwl_ipc_output.
+
+
+
+
+
+ Indicates the client should hide or show themselves.
+ If the client is visible then hide, if hidden then show.
+
+
+
+
+
+ Indicates if the output is active. Zero is invalid, nonzero is valid.
+
+
+
+
+
+
+ Indicates that a tag has been updated.
+
+
+
+
+
+
+
+
+
+ Indicates a new layout is selected.
+
+
+
+
+
+
+ Indicates the title has changed.
+
+
+
+
+
+
+ Indicates the appid has changed.
+
+
+
+
+
+
+ Indicates the layout has changed. Since layout symbols are dynamic.
+ As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying.
+ You can ignore the zdwl_ipc_output.layout event.
+
+
+
+
+
+
+ Indicates that a sequence of status updates have finished and the client should redraw.
+
+
+
+
+
+
+
+
+
+
+
+ The tags are updated as follows:
+ new_tags = (current_tags AND and_tags) XOR xor_tags
+
+
+
+
+
+
+
+
+
+
+
diff --git a/protocol/meson.build b/protocol/meson.build
index 6e82d63d..e1e745a9 100644
--- a/protocol/meson.build
+++ b/protocol/meson.build
@@ -30,6 +30,7 @@ client_protocols = [
['ext-workspace-unstable-v1.xml'],
['river-status-unstable-v1.xml'],
['river-control-unstable-v1.xml'],
+ ['dwl-ipc-unstable-v2.xml'],
]
client_protos_src = []
diff --git a/protocol/river-status-unstable-v1.xml b/protocol/river-status-unstable-v1.xml
index 6a742563..e9629dde 100644
--- a/protocol/river-status-unstable-v1.xml
+++ b/protocol/river-status-unstable-v1.xml
@@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
+
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 @@
-
+
This interface allows clients to receive information about the current
windowing state of an output.
@@ -83,6 +83,21 @@
+
+
+
+ Sent once on binding the interface should a layout name exist and again
+ whenever the name changes.
+
+
+
+
+
+
+ Sent when the current layout name has been removed without a new one
+ being set, for example when the active layout generator disconnects.
+
+
diff --git a/resources/config b/resources/config
index ad76e937..daad8ab1 100644
--- a/resources/config
+++ b/resources/config
@@ -12,6 +12,7 @@
// "sway/workspaces": {
// "disable-scroll": true,
// "all-outputs": true,
+ // "warp-on-scroll": false,
// "format": "{name}: {icon}",
// "format-icons": {
// "1": "",
diff --git a/src/AModule.cpp b/src/AModule.cpp
index b19594a1..2626cd89 100644
--- a/src/AModule.cpp
+++ b/src/AModule.cpp
@@ -12,6 +12,20 @@ AModule::AModule(const Json::Value& config, const std::string& name, const std::
config_(std::move(config)),
distance_scrolled_y_(0.0),
distance_scrolled_x_(0.0) {
+ // Configure module action Map
+ const Json::Value actions{config_["actions"]};
+ for (Json::Value::const_iterator it = actions.begin(); it != actions.end(); ++it) {
+ if (it.key().isString() && it->isString())
+ if (eventActionMap_.count(it.key().asString()) == 0) {
+ eventActionMap_.insert({it.key().asString(), it->asString()});
+ enable_click = true;
+ enable_scroll = true;
+ } else
+ spdlog::warn("Dublicate action is ignored: {0}", it.key().asString());
+ else
+ spdlog::warn("Wrong actions section configuration. See config by index: {}", it.index());
+ }
+
// configure events' user commands
if (enable_click) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
@@ -48,19 +62,33 @@ auto AModule::update() -> void {
pid_.push_back(util::command::forkExec(config_["on-update"].asString()));
}
}
+// Get mapping between event name and module action name
+// Then call overrided doAction in order to call appropriate module action
+auto AModule::doAction(const std::string& name) -> void {
+ if (!name.empty()) {
+ const std::map::const_iterator& recA{eventActionMap_.find(name)};
+ // Call overrided action if derrived class has implemented it
+ if (recA != eventActionMap_.cend() && name != recA->second) this->doAction(recA->second);
+ }
+}
bool AModule::handleToggle(GdkEventButton* const& e) {
+ std::string format{};
const std::map, std::string>::const_iterator& rec{
eventMap_.find(std::pair(e->button, e->type))};
- std::string format{(rec != eventMap_.cend()) ? rec->second : std::string{""}};
+ if (rec != eventMap_.cend()) {
+ // First call module actions
+ this->AModule::doAction(rec->second);
+ format = rec->second;
+ }
+ // Second call user scripts
if (!format.empty()) {
if (config_[format].isString())
format = config_[format].asString();
else
format.clear();
}
-
if (!format.empty()) {
pid_.push_back(util::command::forkExec(format));
}
@@ -69,11 +97,21 @@ bool AModule::handleToggle(GdkEventButton* const& e) {
}
AModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) {
+ // only affects up/down
+ bool reverse = config_["reverse-scrolling"].asBool();
+ bool reverse_mouse = config_["reverse-mouse-scrolling"].asBool();
+
+ // ignore reverse-scrolling if event comes from a mouse wheel
+ GdkDevice* device = gdk_event_get_source_device((GdkEvent*)e);
+ if (device != NULL && gdk_device_get_source(device) == GDK_SOURCE_MOUSE) {
+ reverse = reverse_mouse;
+ }
+
switch (e->direction) {
case GDK_SCROLL_UP:
- return SCROLL_DIR::UP;
+ return reverse ? SCROLL_DIR::DOWN : SCROLL_DIR::UP;
case GDK_SCROLL_DOWN:
- return SCROLL_DIR::DOWN;
+ return reverse ? SCROLL_DIR::UP : SCROLL_DIR::DOWN;
case GDK_SCROLL_LEFT:
return SCROLL_DIR::LEFT;
case GDK_SCROLL_RIGHT:
@@ -90,9 +128,9 @@ AModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) {
}
if (distance_scrolled_y_ < -threshold) {
- dir = SCROLL_DIR::UP;
+ dir = reverse ? SCROLL_DIR::DOWN : SCROLL_DIR::UP;
} else if (distance_scrolled_y_ > threshold) {
- dir = SCROLL_DIR::DOWN;
+ dir = reverse ? SCROLL_DIR::UP : SCROLL_DIR::DOWN;
} else if (distance_scrolled_x_ > threshold) {
dir = SCROLL_DIR::RIGHT;
} else if (distance_scrolled_x_ < -threshold) {
@@ -122,11 +160,19 @@ AModule::SCROLL_DIR AModule::getScrollDir(GdkEventScroll* e) {
bool AModule::handleScroll(GdkEventScroll* e) {
auto dir = getScrollDir(e);
- if (dir == SCROLL_DIR::UP && config_["on-scroll-up"].isString()) {
- pid_.push_back(util::command::forkExec(config_["on-scroll-up"].asString()));
- } else if (dir == SCROLL_DIR::DOWN && config_["on-scroll-down"].isString()) {
- pid_.push_back(util::command::forkExec(config_["on-scroll-down"].asString()));
- }
+ std::string eventName{};
+
+ if (dir == SCROLL_DIR::UP)
+ eventName = "on-scroll-up";
+ else if (dir == SCROLL_DIR::DOWN)
+ eventName = "on-scroll-down";
+
+ // First call module actions
+ this->AModule::doAction(eventName);
+ // Second call user scripts
+ if (config_[eventName].isString())
+ pid_.push_back(util::command::forkExec(config_[eventName].asString()));
+
dp.emit();
return true;
}
diff --git a/src/bar.cpp b/src/bar.cpp
index f46b7d0f..60104f0d 100644
--- a/src/bar.cpp
+++ b/src/bar.cpp
@@ -593,6 +593,10 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
setMode(MODE_DEFAULT);
}
+ if (config["start_hidden"].asBool()) {
+ setVisible(false);
+ }
+
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
#if HAVE_SWAY
@@ -725,10 +729,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
void waybar::Bar::handleSignal(int signal) {
for (auto& module : modules_all_) {
- auto* custom = dynamic_cast(module.get());
- if (custom != nullptr) {
- custom->refresh(signal);
- }
+ module->refresh(signal);
}
}
@@ -742,7 +743,13 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
AModule* module;
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
- auto group_module = new waybar::Group(ref, *this, config[ref]);
+ auto hash_pos = ref.find('#');
+ auto id_name = ref.substr(6, hash_pos - 6);
+ auto class_name = hash_pos != std::string::npos ? ref.substr(hash_pos + 1) : "";
+
+ auto parent = group ? group : &this->box_;
+ auto vertical = parent->get_orientation() == Gtk::ORIENTATION_VERTICAL;
+ auto group_module = new waybar::Group(id_name, class_name, config[ref], vertical);
getModules(factory, ref, &group_module->box);
module = group_module;
} else {
@@ -764,11 +771,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos,
modules_right_.emplace_back(module_sp);
}
}
- module->dp.connect([module, name] {
+ module->dp.connect([module, ref] {
try {
module->update();
} catch (const std::exception& e) {
- spdlog::error("{}: {}", name.asString(), e.what());
+ spdlog::error("{}: {}", ref, e.what());
}
});
} catch (const std::exception& e) {
diff --git a/src/config.cpp b/src/config.cpp
index a4d90909..45f5ee38 100644
--- a/src/config.cpp
+++ b/src/config.cpp
@@ -124,9 +124,21 @@ 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;
+ if (output_conf.isString()) {
+ auto config_output = output_conf.asString();
+ if (config_output.substr(0, 1) == "!") {
+ if (config_output.substr(1) == name || config_output.substr(1) == identifier) {
+ return false;
+ } else {
+ continue;
+ }
+ }
+ if (config_output == name || config_output == identifier) {
+ return true;
+ }
+ if (config_output.substr(0, 1) == "*") {
+ return true;
+ }
}
}
return false;
diff --git a/src/factory.cpp b/src/factory.cpp
index d16cb523..1d7a00b5 100644
--- a/src/factory.cpp
+++ b/src/factory.cpp
@@ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::upower::UPower(id, config_[name]);
}
#endif
+#ifdef HAVE_MPRIS
+ if (ref == "mpris") {
+ return new waybar::modules::mpris::Mpris(id, config_[name]);
+ }
+#endif
#ifdef HAVE_SWAY
if (ref == "sway/mode") {
return new waybar::modules::sway::Mode(id, config_[name]);
@@ -59,6 +64,14 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "river/window") {
return new waybar::modules::river::Window(id, bar_, config_[name]);
}
+ if (ref == "river/layout") {
+ return new waybar::modules::river::Layout(id, bar_, config_[name]);
+ }
+#endif
+#ifdef HAVE_DWL
+ if (ref == "dwl/tags") {
+ return new waybar::modules::dwl::Tags(id, bar_, config_[name]);
+ }
#endif
#ifdef HAVE_HYPRLAND
if (ref == "hyprland/window") {
@@ -67,6 +80,12 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "hyprland/language") {
return new waybar::modules::hyprland::Language(id, bar_, config_[name]);
}
+ if (ref == "hyprland/submap") {
+ return new waybar::modules::hyprland::Submap(id, bar_, config_[name]);
+ }
+ if (ref == "hyprland/workspaces") {
+ return new waybar::modules::hyprland::Workspaces(id, bar_, config_[name]);
+ }
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
@@ -90,6 +109,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "disk") {
return new waybar::modules::Disk(id, config_[name]);
}
+ if (ref == "image") {
+ return new waybar::modules::Image(id, config_[name]);
+ }
#ifdef HAVE_DBUSMENU
if (ref == "tray") {
return new waybar::modules::SNI::Tray(id, bar_, config_[name]);
@@ -142,14 +164,17 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "wireplumber") {
return new waybar::modules::Wireplumber(id, config_[name]);
}
+#endif
+#ifdef HAVE_LIBCAVA
+ if (ref == "cava") {
+ return new waybar::modules::Cava(id, config_[name]);
+ }
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]);
- } else if (ref.compare(0, 6, "image/") == 0 && ref.size() > 6) {
- return new waybar::modules::Image(ref.substr(6), id, config_[name]);
}
} catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());
diff --git a/src/group.cpp b/src/group.cpp
index 83243654..548fb0da 100644
--- a/src/group.cpp
+++ b/src/group.cpp
@@ -6,9 +6,30 @@
namespace waybar {
-Group::Group(const std::string& name, const Bar& bar, const Json::Value& config)
- : AModule(config, name, "", false, false),
- box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0} {}
+Group::Group(const std::string& name, const std::string& id, const Json::Value& config,
+ bool vertical)
+ : AModule(config, name, id, false, false),
+ box{vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0} {
+ box.set_name(name_);
+ if (!id.empty()) {
+ box.get_style_context()->add_class(id);
+ }
+
+ // default orientation: orthogonal to parent
+ auto orientation =
+ config_["orientation"].empty() ? "orthogonal" : config_["orientation"].asString();
+ if (orientation == "inherit") {
+ // keep orientation passed
+ } else if (orientation == "orthogonal") {
+ box.set_orientation(vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL);
+ } else if (orientation == "vertical") {
+ box.set_orientation(Gtk::ORIENTATION_VERTICAL);
+ } else if (orientation == "horizontal") {
+ box.set_orientation(Gtk::ORIENTATION_HORIZONTAL);
+ } else {
+ throw std::runtime_error("Invalid orientation value: " + orientation);
+ }
+}
auto Group::update() -> void {
// noop
diff --git a/src/main.cpp b/src/main.cpp
index e06774b4..ff446ffc 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -85,6 +85,12 @@ int main(int argc, char* argv[]) {
waybar::Client::inst()->reset();
});
+ std::signal(SIGINT, [](int /*signal*/) {
+ spdlog::info("Quitting.");
+ reload = false;
+ waybar::Client::inst()->reset();
+ });
+
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
std::signal(sig, [](int sig) {
for (auto& bar : waybar::Client::inst()->bars) {
diff --git a/src/modules/backlight.cpp b/src/modules/backlight.cpp
index aa734a4f..58d14dde 100644
--- a/src/modules/backlight.cpp
+++ b/src/modules/backlight.cpp
@@ -48,13 +48,13 @@ struct UdevMonitorDeleter {
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
if (rc != expected) {
- throw std::runtime_error(fmt::format(message, rc));
+ throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
}
}
void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
if (rc == bad_rc) {
- throw std::runtime_error(fmt::format(message, rc));
+ throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
}
}
@@ -62,7 +62,7 @@ void check0(int rc, const char *message = "rc wasn't 0") { check_eq(rc, 0, messa
void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) {
- throw std::runtime_error(fmt::format(message, rc));
+ throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
}
}
@@ -106,6 +106,15 @@ waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &
dp.emit();
}
+ // Set up scroll handler
+ event_box_.add_events(Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
+ event_box_.signal_scroll_event().connect(sigc::mem_fun(*this, &Backlight::handleScroll));
+
+ // Connect to the login interface
+ login_proxy_ = Gio::DBus::Proxy::create_for_bus_sync(
+ Gio::DBus::BusType::BUS_TYPE_SYSTEM, "org.freedesktop.login1",
+ "/org/freedesktop/login1/session/self", "org.freedesktop.login1.Session");
+
udev_thread_ = [this] {
std::unique_ptr udev{udev_new()};
check_nn(udev.get(), "Udev new failed");
@@ -181,9 +190,24 @@ auto waybar::modules::Backlight::update() -> void {
event_box_.show();
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))));
+ std::string desc =
+ fmt::format(fmt::runtime(format_), fmt::arg("percent", std::to_string(percent)),
+ fmt::arg("icon", getIcon(percent)));
+ label_.set_markup(desc);
getState(percent);
+ if (tooltipEnabled()) {
+ std::string tooltip_format;
+ if (config_["tooltip-format"].isString()) {
+ tooltip_format = config_["tooltip-format"].asString();
+ }
+ if (!tooltip_format.empty()) {
+ label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
+ fmt::arg("percent", std::to_string(percent)),
+ fmt::arg("icon", getIcon(percent))));
+ } else {
+ label_.set_tooltip_text(desc);
+ }
+ }
} else {
event_box_.hide();
}
@@ -263,3 +287,63 @@ void waybar::modules::Backlight::enumerate_devices(ForwardIt first, ForwardIt la
upsert_device(first, last, inserter, dev.get());
}
}
+
+bool waybar::modules::Backlight::handleScroll(GdkEventScroll *e) {
+ // Check if the user has set a custom command for scrolling
+ if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
+ return AModule::handleScroll(e);
+ }
+
+ // Fail fast if the proxy could not be initialized
+ if (!login_proxy_) {
+ return true;
+ }
+
+ // Check scroll direction
+ auto dir = AModule::getScrollDir(e);
+ if (dir == SCROLL_DIR::NONE) {
+ return true;
+ }
+
+ // Get scroll step
+ double step = 1;
+
+ if (config_["scroll-step"].isDouble()) {
+ step = config_["scroll-step"].asDouble();
+ }
+
+ // Get the best device
+ decltype(devices_) devices;
+ {
+ std::scoped_lock lock(udev_thread_mutex_);
+ devices = devices_;
+ }
+ const auto best = best_device(devices.cbegin(), devices.cend(), preferred_device_);
+
+ if (best == nullptr) {
+ return true;
+ }
+
+ // Compute the absolute step
+ const auto abs_step = static_cast(round(step * best->get_max() / 100.0f));
+
+ // Compute the new value
+ int new_value = best->get_actual();
+
+ if (dir == SCROLL_DIR::UP) {
+ new_value += abs_step;
+ } else if (dir == SCROLL_DIR::DOWN) {
+ new_value -= abs_step;
+ }
+
+ // Clamp the value
+ new_value = std::clamp(new_value, 0, best->get_max());
+
+ // Set the new value
+ auto call_args = Glib::VariantContainerBase(
+ g_variant_new("(ssu)", "backlight", std::string(best->name()).c_str(), new_value));
+
+ login_proxy_->call_sync("SetBrightness", call_args);
+
+ return true;
+}
diff --git a/src/modules/battery.cpp b/src/modules/battery.cpp
index b7a9cd05..c0f433ae 100644
--- a/src/modules/battery.cpp
+++ b/src/modules/battery.cpp
@@ -36,7 +36,8 @@ waybar::modules::Battery::~Battery() {
}
close(global_watch_fd_);
- for (auto it = batteries_.cbegin(); it != batteries_.cend(); it++) {
+ for (auto it = batteries_.cbegin(), next_it = it; it != batteries_.cend(); it = next_it) {
+ ++next_it;
auto watch_id = (*it).second;
if (watch_id >= 0) {
inotify_rm_watch(battery_watch_fd_, watch_id);
@@ -107,6 +108,15 @@ void waybar::modules::Battery::refreshBatteries() {
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")) {
+ // Ignore non-system power supplies unless explicitly requested
+ if (!bat_defined && fs::exists(node.path() / "scope")) {
+ std::string scope;
+ std::ifstream(node.path() / "scope") >> scope;
+ if (g_ascii_strcasecmp(scope.data(), "device") == 0) {
+ continue;
+ }
+ }
+
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
@@ -496,11 +506,12 @@ const std::tuple waybar::modules::Battery::g
float time_remaining{0.0f};
if (status == "Discharging" && time_to_empty_now_exists) {
- if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 1000.0f;
+ if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 3600.0f;
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && time_to_full_now_exists) {
- if (time_to_full_now_exists && (time_to_full_now != 0)) time_remaining = -(float)time_to_full_now / 1000.0f;
+ if (time_to_full_now_exists && (time_to_full_now != 0))
+ time_remaining = -(float)time_to_full_now / 3600.0f;
// If we've turned positive it means the battery is past 100% and so just report that as no
// time remaining
if (time_remaining > 0.0f) time_remaining = 0.0f;
@@ -594,7 +605,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
format = config_["format-time"].asString();
}
std::string zero_pad_minutes = fmt::format("{:02d}", minutes);
- return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes),
+ return fmt::format(fmt::runtime(format), fmt::arg("H", full_hours), fmt::arg("M", minutes),
fmt::arg("m", zero_pad_minutes));
}
@@ -634,7 +645,8 @@ auto waybar::modules::Battery::update() -> void {
} 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),
+ label_.set_tooltip_text(fmt::format(fmt::runtime(tooltip_format),
+ fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
@@ -655,9 +667,9 @@ auto waybar::modules::Battery::update() -> void {
} else {
event_box_.show();
auto icons = std::vector{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", time_remaining_formatted)));
+ label_.set_markup(fmt::format(
+ fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power),
+ fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();
diff --git a/src/modules/bluetooth.cpp b/src/modules/bluetooth.cpp
index e6a1fe39..c3a25473 100644
--- a/src/modules/bluetooth.cpp
+++ b/src/modules/bluetooth.cpp
@@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void {
state_ = state;
label_.set_markup(fmt::format(
- format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()),
+ fmt::runtime(format_), fmt::arg("status", state_),
+ fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
fmt::arg("controller_alias", cur_controller_.alias),
@@ -234,7 +235,7 @@ auto waybar::modules::Bluetooth::update() -> void {
enumerate_format = config_["tooltip-format-enumerate-connected"].asString();
}
ss << fmt::format(
- enumerate_format, fmt::arg("device_address", dev.address),
+ fmt::runtime(enumerate_format), fmt::arg("device_address", dev.address),
fmt::arg("device_address_type", dev.address_type),
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)));
@@ -247,7 +248,7 @@ auto waybar::modules::Bluetooth::update() -> void {
}
}
label_.set_tooltip_text(fmt::format(
- tooltip_format, fmt::arg("status", state_),
+ fmt::runtime(tooltip_format), fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
diff --git a/src/modules/cava.cpp b/src/modules/cava.cpp
new file mode 100644
index 00000000..be9bef4e
--- /dev/null
+++ b/src/modules/cava.cpp
@@ -0,0 +1,202 @@
+#include "modules/cava.hpp"
+
+#include
+
+waybar::modules::Cava::Cava(const std::string& id, const Json::Value& config)
+ : ALabel(config, "cava", id, "{}", 60, false, false, false) {
+ // Load waybar module config
+ char cfgPath[PATH_MAX];
+ cfgPath[0] = '\0';
+
+ if (config_["cava_config"].isString()) {
+ std::string strPath{config_["cava_config"].asString()};
+ const std::string fnd{"XDG_CONFIG_HOME"};
+ const std::string::size_type npos{strPath.find("$" + fnd)};
+ if (npos != std::string::npos) strPath.replace(npos, fnd.length() + 1, getenv(fnd.c_str()));
+ strcpy(cfgPath, strPath.data());
+ }
+ // Load cava config
+ error_.length = 0;
+
+ if (!load_config(cfgPath, &prm_, false, &error_)) {
+ spdlog::error("Error loading config. {0}", error_.message);
+ exit(EXIT_FAILURE);
+ }
+
+ // Override cava parameters by the user config
+ prm_.inAtty = 0;
+ prm_.output = output_method::OUTPUT_RAW;
+ strcpy(prm_.data_format, "ascii");
+ strcpy(prm_.raw_target, "/dev/stdout");
+ prm_.ascii_range = config_["format-icons"].size() - 1;
+
+ prm_.bar_width = 2;
+ prm_.bar_spacing = 0;
+ prm_.bar_height = 32;
+ prm_.bar_width = 1;
+ prm_.orientation = ORIENT_TOP;
+ prm_.xaxis = xaxis_scale::NONE;
+ prm_.mono_opt = AVERAGE;
+ prm_.autobars = 0;
+ prm_.gravity = 0;
+ prm_.integral = 1;
+
+ if (config_["framerate"].isInt()) prm_.framerate = config_["framerate"].asInt();
+ if (config_["autosens"].isInt()) prm_.autosens = config_["autosens"].asInt();
+ if (config_["sensitivity"].isInt()) prm_.sens = config_["sensitivity"].asInt();
+ if (config_["bars"].isInt()) prm_.fixedbars = config_["bars"].asInt();
+ if (config_["lower_cutoff_freq"].isNumeric())
+ prm_.lower_cut_off = config_["lower_cutoff_freq"].asLargestInt();
+ if (config_["higher_cutoff_freq"].isNumeric())
+ prm_.upper_cut_off = config_["higher_cutoff_freq"].asLargestInt();
+ if (config_["sleep_timer"].isInt()) prm_.sleep_timer = config_["sleep_timer"].asInt();
+ if (config_["method"].isString())
+ prm_.input = input_method_by_name(config_["method"].asString().c_str());
+ if (config_["source"].isString()) prm_.audio_source = config_["source"].asString().data();
+ if (config_["sample_rate"].isNumeric()) prm_.fifoSample = config_["sample_rate"].asLargestInt();
+ if (config_["sample_bits"].isInt()) prm_.fifoSampleBits = config_["sample_bits"].asInt();
+ if (config_["stereo"].isBool()) prm_.stereo = config_["stereo"].asBool();
+ if (config_["reverse"].isBool()) prm_.reverse = config_["reverse"].asBool();
+ if (config_["bar_delimiter"].isInt()) prm_.bar_delim = config_["bar_delimiter"].asInt();
+ if (config_["monstercat"].isBool()) prm_.monstercat = config_["monstercat"].asBool();
+ if (config_["waves"].isBool()) prm_.waves = config_["waves"].asBool();
+ if (config_["noise_reduction"].isDouble())
+ prm_.noise_reduction = config_["noise_reduction"].asDouble();
+ if (config_["input_delay"].isInt())
+ fetch_input_delay_ = std::chrono::seconds(config_["input_delay"].asInt());
+ // Make cava parameters configuration
+ plan_ = new cava_plan{};
+
+ audio_raw_.height = prm_.ascii_range;
+ audio_data_.format = -1;
+ audio_data_.source = new char[1 + strlen(prm_.audio_source)];
+ audio_data_.source[0] = '\0';
+ strcpy(audio_data_.source, prm_.audio_source);
+
+ audio_data_.rate = 0;
+ audio_data_.samples_counter = 0;
+ audio_data_.channels = 2;
+ audio_data_.IEEE_FLOAT = 0;
+
+ audio_data_.input_buffer_size = BUFFER_SIZE * audio_data_.channels;
+ audio_data_.cava_buffer_size = audio_data_.input_buffer_size * 8;
+
+ audio_data_.cava_in = new double[audio_data_.cava_buffer_size]{0.0};
+
+ audio_data_.terminate = 0;
+ audio_data_.suspendFlag = false;
+ input_source_ = get_input(&audio_data_, &prm_);
+
+ if (!input_source_) {
+ spdlog::error("cava API didn't provide input audio source method");
+ exit(EXIT_FAILURE);
+ }
+ // Calculate delay for Update() thread
+ frame_time_milsec_ = std::chrono::milliseconds((int)(1e3 / prm_.framerate));
+
+ // Init cava plan, audio_raw structure
+ audio_raw_init(&audio_data_, &audio_raw_, &prm_, plan_);
+ if (!plan_) spdlog::error("cava plan is not provided");
+ audio_raw_.previous_frame[0] = -1; // For first Update() call need to rePaint text message
+ // Read audio source trough cava API. Cava orginizes this process via infinity loop
+ thread_fetch_input_ = [this] {
+ thread_fetch_input_.sleep_for(fetch_input_delay_);
+ input_source_(&audio_data_);
+ };
+
+ thread_ = [this] {
+ dp.emit();
+ thread_.sleep_for(frame_time_milsec_);
+ };
+}
+
+waybar::modules::Cava::~Cava() {
+ thread_fetch_input_.stop();
+ thread_.stop();
+ delete plan_;
+ plan_ = nullptr;
+}
+
+void upThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
+ if (delta == std::chrono::seconds{0}) {
+ delta += std::chrono::seconds{1};
+ delay += delta;
+ }
+}
+
+void downThreadDelay(std::chrono::milliseconds& delay, std::chrono::seconds& delta) {
+ if (delta > std::chrono::seconds{0}) {
+ delay -= delta;
+ delta -= std::chrono::seconds{1};
+ }
+}
+
+auto waybar::modules::Cava::update() -> void {
+ if (audio_data_.suspendFlag) return;
+ silence_ = true;
+
+ for (int i{0}; i < audio_data_.input_buffer_size; ++i) {
+ if (audio_data_.cava_in[i]) {
+ silence_ = false;
+ sleep_counter_ = 0;
+ break;
+ }
+ }
+
+ if (silence_ && prm_.sleep_timer) {
+ if (sleep_counter_ <=
+ (int)(std::chrono::milliseconds(prm_.sleep_timer * 1s) / frame_time_milsec_)) {
+ ++sleep_counter_;
+ silence_ = false;
+ }
+ }
+
+ if (!silence_) {
+ downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
+ // Process: execute cava
+ pthread_mutex_lock(&audio_data_.lock);
+ cava_execute(audio_data_.cava_in, audio_data_.samples_counter, audio_raw_.cava_out, plan_);
+ if (audio_data_.samples_counter > 0) audio_data_.samples_counter = 0;
+ pthread_mutex_unlock(&audio_data_.lock);
+
+ // Do transformation under raw data
+ audio_raw_fetch(&audio_raw_, &prm_, &rePaint_);
+
+ if (rePaint_ == 1) {
+ text_.clear();
+
+ for (int i{0}; i < audio_raw_.number_of_bars; ++i) {
+ audio_raw_.previous_frame[i] = audio_raw_.bars[i];
+ text_.append(
+ getIcon((audio_raw_.bars[i] > prm_.ascii_range) ? prm_.ascii_range : audio_raw_.bars[i],
+ "", prm_.ascii_range + 1));
+ if (prm_.bar_delim != 0) text_.push_back(prm_.bar_delim);
+ }
+
+ label_.set_markup(text_);
+ ALabel::update();
+ }
+ } else
+ upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
+}
+
+auto waybar::modules::Cava::doAction(const std::string& name) -> void {
+ if ((actionMap_[name])) {
+ (this->*actionMap_[name])();
+ } else
+ spdlog::error("Cava. Unsupported action \"{0}\"", name);
+}
+
+// Cava actions
+void waybar::modules::Cava::pause_resume() {
+ pthread_mutex_lock(&audio_data_.lock);
+ if (audio_data_.suspendFlag) {
+ audio_data_.suspendFlag = false;
+ pthread_cond_broadcast(&audio_data_.resumeCond);
+ downThreadDelay(frame_time_milsec_, suspend_silence_delay_);
+ } else {
+ audio_data_.suspendFlag = true;
+ upThreadDelay(frame_time_milsec_, suspend_silence_delay_);
+ }
+ pthread_mutex_unlock(&audio_data_.lock);
+}
diff --git a/src/modules/clock.cpp b/src/modules/clock.cpp
index a40d4128..17cfd8da 100644
--- a/src/modules/clock.cpp
+++ b/src/modules/clock.cpp
@@ -5,18 +5,16 @@
#include
#include
+#include
#include
#include
#include "util/ustring_clen.hpp"
-#include "util/waybar_time.hpp"
#ifdef HAVE_LANGINFO_1STDAY
#include
#include
#endif
-using waybar::waybar_time;
-
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0),
@@ -24,26 +22,31 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
is_timezoned_list_in_tooltip_(false) {
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
for (const auto& zone_name : config_["timezones"]) {
- if (!zone_name.isString() || zone_name.asString().empty()) {
- time_zones_.push_back(nullptr);
- continue;
- }
- time_zones_.push_back(date::locate_zone(zone_name.asString()));
+ if (!zone_name.isString()) continue;
+ if (zone_name.asString().empty())
+ time_zones_.push_back(date::current_zone());
+ else
+ try {
+ time_zones_.push_back(date::locate_zone(zone_name.asString()));
+ } catch (const std::exception& e) {
+ spdlog::warn("Timezone: {0}. {1}", zone_name.asString(), e.what());
+ }
}
- } else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
- time_zones_.push_back(date::locate_zone(config_["timezone"].asString()));
+ } else if (config_["timezone"].isString()) {
+ if (config_["timezone"].asString().empty())
+ time_zones_.push_back(date::current_zone());
+ else
+ try {
+ time_zones_.push_back(date::locate_zone(config_["timezone"].asString()));
+ } catch (const std::exception& e) {
+ spdlog::warn("Timezone: {0}. {1}", config_["timezone"].asString(), e.what());
+ }
}
- // If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark
- // that local time should be shown.
+ // If all timezones are parsed and no one is good, add current time zone. nullptr in timezones
+ // vector means that local time should be shown
if (!time_zones_.size()) {
- time_zones_.push_back(nullptr);
- }
-
- if (!is_timezone_fixed()) {
- spdlog::warn(
- "As using a timezone, some format args may be missing as the date library haven't got a "
- "release since 2018.");
+ time_zones_.push_back(date::current_zone());
}
// Check if a particular placeholder is present in the tooltip format, to know what to calculate
@@ -61,18 +64,86 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
}
}
+ // Calendar configuration
if (is_calendar_in_tooltip_) {
- if (config_["on-scroll"][kCalendarPlaceholder].isInt()) {
- calendar_shift_init_ =
- date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()};
+ if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) {
+ if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") {
+ cldWPos_ = WeeksSide::LEFT;
+ } else if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "right") {
+ cldWPos_ = WeeksSide::RIGHT;
+ }
+ }
+ if (config_[kCalendarPlaceholder]["format"]["months"].isString())
+ fmtMap_.insert({0, config_[kCalendarPlaceholder]["format"]["months"].asString()});
+ else
+ fmtMap_.insert({0, "{}"});
+ if (config_[kCalendarPlaceholder]["format"]["days"].isString())
+ fmtMap_.insert({2, config_[kCalendarPlaceholder]["format"]["days"].asString()});
+ else
+ fmtMap_.insert({2, "{}"});
+ if (config_[kCalendarPlaceholder]["format"]["weeks"].isString() &&
+ cldWPos_ != WeeksSide::HIDDEN) {
+ fmtMap_.insert(
+ {4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(),
+ std::regex("\\{\\}"),
+ (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")});
+ Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("?[^>]+>|\\{.*\\}"), "")};
+ cldWnLen_ += tmp.size();
+ } else {
+ if (cldWPos_ != WeeksSide::HIDDEN)
+ fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"});
+ else
+ cldWnLen_ = 0;
+ }
+ if (config_[kCalendarPlaceholder]["format"]["weekdays"].isString())
+ fmtMap_.insert({1, config_[kCalendarPlaceholder]["format"]["weekdays"].asString()});
+ else
+ fmtMap_.insert({1, "{}"});
+ if (config_[kCalendarPlaceholder]["format"]["today"].isString()) {
+ fmtMap_.insert({3, config_[kCalendarPlaceholder]["format"]["today"].asString()});
+ cldBaseDay_ =
+ date::year_month_day{date::floor(std::chrono::system_clock::now())}.day();
+ } else
+ fmtMap_.insert({3, "{}"});
+ if (config_[kCalendarPlaceholder]["mode"].isString()) {
+ const std::string cfgMode{(config_[kCalendarPlaceholder]["mode"].isString())
+ ? config_[kCalendarPlaceholder]["mode"].asString()
+ : "month"};
+ const std::map monthModes{{"month", CldMode::MONTH},
+ {"year", CldMode::YEAR}};
+ if (monthModes.find(cfgMode) != monthModes.end())
+ cldMode_ = monthModes.at(cfgMode);
+ else
+ spdlog::warn(
+ "Clock calendar configuration \"mode\"\"\" \"{0}\" is not recognized. Mode = \"month\" "
+ "is using instead",
+ cfgMode);
+ }
+ if (config_[kCalendarPlaceholder]["mode-mon-col"].isInt()) {
+ cldMonCols_ = config_[kCalendarPlaceholder]["mode-mon-col"].asInt();
+ if (cldMonCols_ == 0u || 12 % cldMonCols_ != 0u) {
+ cldMonCols_ = 3u;
+ spdlog::warn(
+ "Clock calendar configuration \"mode-mon-col\" = {0} must be one of [1, 2, 3, 4, 6, "
+ "12]. Value 3 is using instead",
+ cldMonCols_);
+ }
+ } else
+ cldMonCols_ = 1;
+ if (config_[kCalendarPlaceholder]["on-scroll"].isInt()) {
+ cldShift_ = date::months{config_[kCalendarPlaceholder]["on-scroll"].asInt()};
+ event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK);
+ event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
+ cldCurrShift_ = date::months{0};
+ return false;
+ });
}
}
- if (config_["locale"].isString()) {
+ if (config_["locale"].isString())
locale_ = std::locale(config_["locale"].asString());
- } else {
+ else
locale_ = std::locale("");
- }
thread_ = [this] {
dp.emit();
@@ -94,18 +165,25 @@ bool waybar::modules::Clock::is_timezone_fixed() {
}
auto waybar::modules::Clock::update() -> void {
- auto time_zone = current_timezone();
+ const auto* time_zone = current_timezone();
auto now = std::chrono::system_clock::now();
- waybar_time wtime = {locale_, date::make_zoned(time_zone, date::floor(now) +
- calendar_shift_)};
- std::string text = "";
+ auto ztime = date::zoned_time{time_zone, date::floor(now)};
+
+ auto shifted_date = date::year_month_day{date::floor(now)} + cldCurrShift_;
+ if (cldCurrShift_.count()) {
+ shifted_date = date::year_month_day(shifted_date.year(), shifted_date.month(), date::day(1));
+ }
+ auto now_shifted = date::sys_days{shifted_date} + (now - date::floor(now));
+ auto shifted_ztime = date::zoned_time{time_zone, date::floor(now_shifted)};
+
+ std::string text{""};
if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt
tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
- text = fmt::format(locale_, format_, localtime);
+ text = fmt::format(locale_, fmt::runtime(format_), localtime);
} else {
- text = fmt::format(format_, wtime);
+ text = fmt::format(locale_, fmt::runtime(format_), ztime);
}
label_.set_markup(text);
@@ -114,15 +192,15 @@ auto waybar::modules::Clock::update() -> void {
std::string calendar_lines{""};
std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) {
- calendar_lines = calendar_text(wtime);
+ calendar_lines = get_calendar(ztime, shifted_ztime);
}
if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now);
}
auto tooltip_format = config_["tooltip-format"].asString();
- text =
- fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
- fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
+ text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime,
+ fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
+ fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text);
}
}
@@ -131,172 +209,251 @@ auto waybar::modules::Clock::update() -> void {
ALabel::update();
}
-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);
-
- // Shift calendar date
- if (calendar_shift_init_.count() != 0) {
- if (dir == SCROLL_DIR::UP)
- calendar_shift_ += calendar_shift_init_;
- else
- calendar_shift_ -= calendar_shift_init_;
- } else {
- // Change time zone
- if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
- return true;
- }
- if (time_zones_.size() == 1) {
- return true;
- }
-
- auto nr_zones = time_zones_.size();
- if (dir == SCROLL_DIR::UP) {
- size_t new_idx = current_time_zone_idx_ + 1;
- current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
- } else {
- current_time_zone_idx_ =
- current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
- }
- }
-
- update();
- return true;
-}
-
-auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
- const auto daypoint = date::floor(wtime.ztime.get_local_time());
- const auto ymd{date::year_month_day{daypoint}};
-
- if (calendar_cached_ymd_ == ymd) {
- return calendar_cached_text_;
- }
-
- const auto curr_day{(calendar_shift_init_.count() != 0 && calendar_shift_.count() != 0)
- ? date::day{0}
- : ymd.day()};
- const date::year_month ym{ymd.year(), ymd.month()};
- const auto weeks_format{config_["format-calendar-weeks"].isString()
- ? config_["format-calendar-weeks"].asString()
- : ""};
-
- std::stringstream os;
-
- const date::weekday first_week_day = first_day_of_week();
-
- enum class WeeksPlacement {
- LEFT,
- RIGHT,
- HIDDEN,
- };
- WeeksPlacement weeks_pos = WeeksPlacement::HIDDEN;
-
- if (config_["calendar-weeks-pos"].isString()) {
- if (config_["calendar-weeks-pos"].asString() == "left") {
- weeks_pos = WeeksPlacement::LEFT;
- // Add paddings before the header
- os << std::string(4, ' ');
- } else if (config_["calendar-weeks-pos"].asString() == "right") {
- weeks_pos = WeeksPlacement::RIGHT;
- }
- }
-
- weekdays_header(first_week_day, os);
-
- // First week prefixed with spaces if needed.
- auto first_month_day = date::weekday(ym / 1);
- int empty_days = (first_week_day - first_month_day).count() + 1;
- date::sys_days last_week_day{static_cast(ym / 1) + date::days{7 - empty_days}};
-
- if (first_week_day == date::Monday) {
- last_week_day -= date::days{1};
- }
- /* Print weeknumber on the left for the first row*/
- if (weeks_pos == WeeksPlacement::LEFT) {
- os << fmt::format(weeks_format, date::format("%U", last_week_day)) << ' ';
- last_week_day += date::weeks{1};
- }
-
- if (empty_days > 0) {
- os << std::string(empty_days * 3 - 1, ' ');
- }
- const auto last_day = (ym / date::literals::last).day();
- auto weekday = first_month_day;
- for (auto d = date::day(1); d <= last_day; ++d, ++weekday) {
- if (weekday != first_week_day) {
- os << ' ';
- } else if (unsigned(d) != 1) {
- last_week_day -= date::days{1};
- if (weeks_pos == WeeksPlacement::RIGHT) {
- os << ' ';
- os << fmt::format(weeks_format, date::format("%U", last_week_day));
- }
-
- os << "\n";
-
- if (weeks_pos == WeeksPlacement::LEFT) {
- os << fmt::format(weeks_format, date::format("%U", last_week_day));
- os << ' ';
- }
- last_week_day += date::weeks{1} + date::days{1};
- }
- if (d == curr_day) {
- if (config_["today-format"].isString()) {
- auto today_format = config_["today-format"].asString();
- os << fmt::format(today_format, date::format("%e", d));
- } else {
- os << "" << date::format("%e", d) << "";
- }
- } else if (config_["format-calendar"].isString()) {
- os << fmt::format(config_["format-calendar"].asString(), date::format("%e", d));
- } else {
- os << date::format("%e", d);
- }
- /*Print weeks on the right when the endings with spaces*/
- if (weeks_pos == WeeksPlacement::RIGHT && d == last_day) {
- last_week_day -= date::days{1};
- empty_days = 6 - (weekday - first_week_day).count();
- os << std::string(empty_days * 3 + 1, ' ');
- os << fmt::format(weeks_format, date::format("%U", last_week_day));
- last_week_day += date::days{1};
- }
- }
-
- auto result = os.str();
- calendar_cached_ymd_ = ymd;
- calendar_cached_text_ = result;
- return result;
-}
-
-auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day, std::ostream& os)
- -> void {
- std::stringstream res;
- auto wd = first_week_day;
- do {
- if (wd != first_week_day) {
- res << ' ';
- }
- Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
- 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 - clen, ' ');
- res << pad << wd_ustring;
- } while (++wd != first_week_day);
- res << "\n";
-
- if (config_["format-calendar-weekdays"].isString()) {
- os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str());
+auto waybar::modules::Clock::doAction(const std::string& name) -> void {
+ if ((actionMap_[name])) {
+ (this->*actionMap_[name])();
+ update();
} else
- os << res.str();
+ spdlog::error("Clock. Unsupported action \"{0}\"", name);
+}
+
+// The number of weeks in calendar month layout plus 1 more for calendar titles
+unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) {
+ using namespace date;
+ return static_cast(
+ ceil((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) +
+ 2;
+}
+
+auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line)
+ -> const date::year_month_weekday {
+ unsigned index = line - 2;
+ auto sd = date::sys_days{ym / 1};
+ if (date::weekday{sd} == firstdow) ++index;
+ auto ymdw = ym / firstdow[index];
+ return ymdw;
+}
+
+auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym,
+ unsigned const line, date::weekday const firstdow,
+ const std::locale* const locale_) -> std::string {
+ using namespace date::literals;
+ std::ostringstream res;
+
+ switch (line) {
+ case 0: {
+ // Output month and year title
+ res << date::format(*locale_, "%B %Y", ym);
+ break;
+ }
+ case 1: {
+ // Output weekday names title
+ auto wd{firstdow};
+ do {
+ Glib::ustring wd_ustring{date::format(*locale_, "%a", wd)};
+ 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 - clen, ' ');
+
+ if (wd != firstdow) res << ' ';
+
+ res << pad << wd_ustring;
+ } while (++wd != firstdow);
+ break;
+ }
+ case 2: {
+ // Output first week prefixed with spaces if necessary
+ auto wd = date::weekday{ym / 1};
+ res << std::string(static_cast((wd - firstdow).count()) * 3, ' ');
+
+ if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / 1_d)
+ res << date::format("%e", 1_d);
+ else
+ res << "{today}";
+
+ auto d = 2_d;
+
+ while (++wd != firstdow) {
+ if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
+ res << date::format(" %e", d);
+ else
+ res << " {today}";
+
+ ++d;
+ }
+ break;
+ }
+ default: {
+ // Output a non-first week:
+ auto ymdw{cldGetWeekForLine(ym, firstdow, line)};
+ if (ymdw.ok()) {
+ auto d = date::year_month_day{ymdw}.day();
+ auto const e = (ym / last).day();
+ auto wd = firstdow;
+
+ if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
+ res << date::format("%e", d);
+ else
+ res << "{today}";
+
+ while (++wd != firstdow && ++d <= e) {
+ if (currDate.year() != ym.year() || currDate.month() != ym.month() || currDate != ym / d)
+ res << date::format(" %e", d);
+ else
+ res << " {today}";
+ }
+ // Append row with spaces if the week did not complete
+ res << std::string(static_cast((firstdow - wd).count()) * 3, ' ');
+ }
+ break;
+ }
+ }
+
+ return res.str();
+}
+
+auto waybar::modules::Clock::get_calendar(const date::zoned_seconds& now,
+ const date::zoned_seconds& wtime) -> std::string {
+ auto daypoint = date::floor(wtime.get_local_time());
+ const auto ymd{date::year_month_day{daypoint}};
+ const auto ym{ymd.year() / ymd.month()};
+ const auto y{ymd.year()};
+ const auto d{ymd.day()};
+ const auto firstdow = first_day_of_week();
+ const auto maxRows{12 / cldMonCols_};
+ std::ostringstream os;
+ std::ostringstream tmp;
+ // get currdate
+ daypoint = date::floor(now.get_local_time());
+ const auto currDate{date::year_month_day{daypoint}};
+
+ if (cldMode_ == CldMode::YEAR) {
+ if (y / date::month{1} / 1 == cldYearShift_)
+ if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)
+ return cldYearCached_;
+ else
+ cldBaseDay_ = d;
+ else
+ cldYearShift_ = y / date::month{1} / 1;
+ }
+ if (cldMode_ == CldMode::MONTH) {
+ if (ym == cldMonShift_)
+ if (d == cldBaseDay_ || (uint)cldBaseDay_ == 0u)
+ return cldMonCached_;
+ else
+ cldBaseDay_ = d;
+ else
+ cldMonShift_ = ym;
+ }
+
+ // Compute number of lines needed for each calendar month
+ unsigned ml[12]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ for (auto& m : ml) {
+ if (cldMode_ == CldMode::YEAR || m == static_cast(ymd.month()))
+ m = cldRowsInMonth(y / date::month{m}, firstdow);
+ else
+ m = 0u;
+ }
+ for (auto row{0u}; row < maxRows; ++row) {
+ const auto lines = *std::max_element(std::begin(ml) + (row * cldMonCols_),
+ std::begin(ml) + ((row + 1) * cldMonCols_));
+ for (auto line{0u}; line < lines; ++line) {
+ for (auto col{0u}; col < cldMonCols_; ++col) {
+ const auto mon{date::month{row * cldMonCols_ + col + 1}};
+ if (cldMode_ == CldMode::YEAR || y / mon == ym) {
+ date::year_month ymTmp{y / mon};
+ if (col != 0 && cldMode_ == CldMode::YEAR) os << " ";
+
+ // Week numbers on the left
+ if (cldWPos_ == WeeksSide::LEFT && line > 0) {
+ if (line > 1) {
+ if (line < ml[static_cast(ymTmp.month()) - 1u])
+ os << fmt::format(fmt::runtime(fmtMap_[4]),
+ (line == 2)
+ ? date::sys_days{ymTmp / 1}
+ : date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)})
+ << ' ';
+ else
+ os << std::string(cldWnLen_, ' ');
+ }
+ }
+
+ os << fmt::format(
+ fmt::runtime((cldWPos_ != WeeksSide::LEFT || line == 0) ? "{:<{}}" : "{:>{}}"),
+ getCalendarLine(currDate, ymTmp, line, firstdow, &locale_),
+ (cldMonColLen_ + ((line < 2) ? cldWnLen_ : 0)));
+
+ // Week numbers on the right
+ if (cldWPos_ == WeeksSide ::RIGHT && line > 0) {
+ if (line > 1) {
+ if (line < ml[static_cast(ymTmp.month()) - 1u])
+ os << ' '
+ << fmt::format(fmt::runtime(fmtMap_[4]),
+ (line == 2)
+ ? date::sys_days{ymTmp / 1}
+ : date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)});
+ else
+ os << std::string(cldWnLen_, ' ');
+ }
+ }
+ }
+ }
+
+ // Apply user formats to calendar
+ if (line < 2)
+ tmp << fmt::format(fmt::runtime(fmtMap_[line]), os.str());
+ else
+ tmp << os.str();
+ // Clear ostringstream
+ std::ostringstream().swap(os);
+ if (line + 1u != lines || (row + 1u != maxRows && cldMode_ == CldMode::YEAR)) tmp << '\n';
+ }
+ if (row + 1u != maxRows && cldMode_ == CldMode::YEAR) tmp << '\n';
+ }
+
+ os << fmt::format( // Apply days format
+ fmt::runtime(fmt::format(fmt::runtime(fmtMap_[2]), tmp.str())),
+ // Apply today format
+ fmt::arg("today", fmt::format(fmt::runtime(fmtMap_[3]), date::format("%e", ymd.day()))));
+
+ if (cldMode_ == CldMode::YEAR)
+ cldYearCached_ = os.str();
+ else
+ cldMonCached_ = os.str();
+
+ return os.str();
+}
+
+/*Clock actions*/
+void waybar::modules::Clock::cldModeSwitch() {
+ cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;
+}
+void waybar::modules::Clock::cldShift_up() {
+ cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
+}
+void waybar::modules::Clock::cldShift_down() {
+ cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
+}
+void waybar::modules::Clock::tz_up() {
+ auto nr_zones = time_zones_.size();
+
+ if (nr_zones == 1) return;
+
+ size_t new_idx = current_time_zone_idx_ + 1;
+ current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
+}
+void waybar::modules::Clock::tz_down() {
+ auto nr_zones = time_zones_.size();
+
+ if (nr_zones == 1) return;
+
+ current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now)
@@ -305,7 +462,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
return "";
}
std::stringstream os;
- waybar_time wtime;
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
if (static_cast(time_zone_idx) == current_time_zone_idx_) {
continue;
@@ -314,8 +470,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
if (!timezone) {
timezone = date::current_zone();
}
- wtime = {locale_, date::make_zoned(timezone, date::floor(*now))};
- os << fmt::format(format_, wtime) << "\n";
+ auto ztime = date::zoned_time{timezone, date::floor(*now)};
+ os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n';
}
return os.str();
}
diff --git a/src/modules/cpu/common.cpp b/src/modules/cpu/common.cpp
index cdbbc3d4..8fedf842 100644
--- a/src/modules/cpu/common.cpp
+++ b/src/modules/cpu/common.cpp
@@ -39,7 +39,6 @@ auto waybar::modules::Cpu::update() -> void {
auto icons = std::vector{state};
fmt::dynamic_format_arg_store 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));
diff --git a/src/modules/custom.cpp b/src/modules/custom.cpp
index 23dba38d..5a246aff 100644
--- a/src/modules/custom.cpp
+++ b/src/modules/custom.cpp
@@ -88,6 +88,7 @@ void waybar::modules::Custom::continuousWorker() {
output_ = {0, output};
dp.emit();
}
+ free(buff);
};
}
@@ -126,7 +127,7 @@ auto waybar::modules::Custom::update() -> void {
} else {
parseOutputRaw();
}
- auto str = fmt::format(format_, text_, fmt::arg("alt", alt_),
+ auto str = fmt::format(fmt::runtime(format_), text_, fmt::arg("alt", alt_),
fmt::arg("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_));
if (str.empty()) {
@@ -209,8 +210,8 @@ void waybar::modules::Custom::parseOutputJson() {
class_.push_back(c.asString());
}
}
- if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) {
- percentage_ = parsed["percentage"].asUInt();
+ if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
+ percentage_ = (int)lround(parsed["percentage"].asFloat());
} else {
percentage_ = 0;
}
diff --git a/src/modules/disk.cpp b/src/modules/disk.cpp
index 5578dc2f..eb4d902f 100644
--- a/src/modules/disk.cpp
+++ b/src/modules/disk.cpp
@@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void {
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(
+ fmt::runtime(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_)));
}
if (tooltipEnabled()) {
@@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
- label_.set_tooltip_text(
- fmt::format(tooltip_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_tooltip_text(fmt::format(
+ fmt::runtime(tooltip_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_)));
}
// Call parent update
ALabel::update();
diff --git a/src/modules/dwl/tags.cpp b/src/modules/dwl/tags.cpp
new file mode 100644
index 00000000..7faa5c52
--- /dev/null
+++ b/src/modules/dwl/tags.cpp
@@ -0,0 +1,198 @@
+#include "modules/dwl/tags.hpp"
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include "client.hpp"
+#include "dwl-ipc-unstable-v2-client-protocol.h"
+
+#define TAG_INACTIVE 0
+#define TAG_ACTIVE 1
+#define TAG_URGENT 2
+
+namespace waybar::modules::dwl {
+
+/* dwl stuff */
+wl_array tags, layouts;
+
+static uint num_tags = 0;
+
+void toggle_visibility(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
+ // Intentionally empty
+}
+
+void active(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t active) {
+ // Intentionally empty
+}
+
+static void set_tag(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t tag, uint32_t state,
+ uint32_t clients, uint32_t focused) {
+ static_cast(data)->handle_view_tags(tag, state, clients, focused);
+
+ num_tags = (state & ZDWL_IPC_OUTPUT_V2_TAG_STATE_ACTIVE) ? num_tags | (1 << tag)
+ : num_tags & ~(1 << tag);
+}
+
+void set_layout_symbol(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *layout) {
+ // Intentionally empty
+}
+
+void title(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *title) {
+ // Intentionally empty
+}
+
+void dwl_frame(void *data, zdwl_ipc_output_v2 *zdwl_output_v2) {
+ // Intentionally empty
+}
+
+static void set_layout(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, uint32_t layout) {
+ // Intentionally empty
+}
+
+static void appid(void *data, zdwl_ipc_output_v2 *zdwl_output_v2, const char *appid){
+ // Intentionally empty
+};
+
+static const zdwl_ipc_output_v2_listener output_status_listener_impl{
+ .toggle_visibility = toggle_visibility,
+ .active = active,
+ .tag = set_tag,
+ .layout = set_layout,
+ .title = title,
+ .appid = appid,
+ .layout_symbol = set_layout_symbol,
+ .frame = dwl_frame,
+};
+
+static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
+ const char *interface, uint32_t version) {
+ if (std::strcmp(interface, zdwl_ipc_manager_v2_interface.name) == 0) {
+ static_cast(data)->status_manager_ = static_cast(
+ (zdwl_ipc_manager_v2 *)wl_registry_bind(registry, name, &zdwl_ipc_manager_v2_interface, 1));
+ }
+ if (std::strcmp(interface, wl_seat_interface.name) == 0) {
+ version = std::min(version, 1);
+ static_cast(data)->seat_ = static_cast(
+ wl_registry_bind(registry, name, &wl_seat_interface, version));
+ }
+}
+
+static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
+ /* Ignore event */
+}
+
+static const wl_registry_listener registry_listener_impl = {.global = handle_global,
+ .global_remove = handle_global_remove};
+
+Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
+ : waybar::AModule(config, "tags", id, false, false),
+ status_manager_{nullptr},
+ seat_{nullptr},
+ bar_(bar),
+ box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
+ output_status_{nullptr} {
+ struct wl_display *display = Client::inst()->wl_display;
+ struct wl_registry *registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, ®istry_listener_impl, this);
+ wl_display_roundtrip(display);
+
+ if (!status_manager_) {
+ spdlog::error("dwl_status_manager_v2 not advertised");
+ return;
+ }
+
+ if (!seat_) {
+ spdlog::error("wl_seat not advertised");
+ }
+
+ box_.set_name("tags");
+ if (!id.empty()) {
+ box_.get_style_context()->add_class(id);
+ }
+ event_box_.add(box_);
+
+ // Default to 9 tags, cap at 32
+ const uint32_t num_tags =
+ config["num-tags"].isUInt() ? std::min(32, config_["num-tags"].asUInt()) : 9;
+
+ std::vector tag_labels(num_tags);
+ for (uint32_t tag = 0; tag < num_tags; ++tag) {
+ tag_labels[tag] = std::to_string(tag + 1);
+ }
+ const Json::Value custom_labels = config["tag-labels"];
+ if (custom_labels.isArray() && !custom_labels.empty()) {
+ for (uint32_t tag = 0; tag < std::min(num_tags, custom_labels.size()); ++tag) {
+ tag_labels[tag] = custom_labels[tag].asString();
+ }
+ }
+
+ uint32_t i = 1;
+ for (const auto &tag_label : tag_labels) {
+ Gtk::Button &button = buttons_.emplace_back(tag_label);
+ button.set_relief(Gtk::RELIEF_NONE);
+ box_.pack_start(button, false, false, 0);
+ if (!config_["disable-click"].asBool()) {
+ button.signal_clicked().connect(
+ sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
+ button.signal_button_press_event().connect(
+ sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
+ }
+ button.show();
+ i <<= 1;
+ }
+
+ struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
+ output_status_ = zdwl_ipc_manager_v2_get_output(status_manager_, output);
+ zdwl_ipc_output_v2_add_listener(output_status_, &output_status_listener_impl, this);
+
+ zdwl_ipc_manager_v2_destroy(status_manager_);
+ status_manager_ = nullptr;
+}
+
+Tags::~Tags() {
+ if (status_manager_) {
+ zdwl_ipc_manager_v2_destroy(status_manager_);
+ }
+}
+
+void Tags::handle_primary_clicked(uint32_t tag) {
+ if (!output_status_) return;
+
+ zdwl_ipc_output_v2_set_tags(output_status_, tag, 1);
+}
+
+bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
+ if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
+ if (!output_status_) return true;
+ zdwl_ipc_output_v2_set_tags(output_status_, num_tags ^ tag, 0);
+ }
+ return true;
+}
+
+void Tags::handle_view_tags(uint32_t tag, uint32_t state, uint32_t clients, uint32_t focused) {
+ // First clear all occupied state
+ auto &button = buttons_[tag];
+ if (clients) {
+ button.get_style_context()->add_class("occupied");
+ } else {
+ button.get_style_context()->remove_class("occupied");
+ }
+
+ if (state & TAG_ACTIVE) {
+ button.get_style_context()->add_class("focused");
+ } else {
+ button.get_style_context()->remove_class("focused");
+ }
+
+ if (state & TAG_URGENT) {
+ button.get_style_context()->add_class("urgent");
+ } else {
+ button.get_style_context()->remove_class("urgent");
+ }
+}
+
+} /* namespace waybar::modules::dwl */
diff --git a/src/modules/gamemode.cpp b/src/modules/gamemode.cpp
index 7129297d..811f13ca 100644
--- a/src/modules/gamemode.cpp
+++ b/src/modules/gamemode.cpp
@@ -16,9 +16,9 @@
#include "glibmm/ustring.h"
#include "glibmm/variant.h"
#include "glibmm/varianttype.h"
-#include "gtkmm/icontheme.h"
#include "gtkmm/label.h"
#include "gtkmm/tooltip.h"
+#include "util/gtk_icon.hpp"
namespace waybar::modules {
Gamemode::Gamemode(const std::string& id, const Json::Value& config)
@@ -55,7 +55,7 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
}
box_.set_spacing(iconSpacing);
- // Wether to use icon or not
+ // Whether to use icon or not
if (config_["use-icon"].isBool()) {
useIcon = config_["use-icon"].asBool();
}
@@ -213,18 +213,18 @@ auto Gamemode::update() -> void {
// Tooltip
if (tooltip) {
- std::string text = fmt::format(tooltip_format, fmt::arg("count", gameCount));
+ std::string text = fmt::format(fmt::runtime(tooltip_format), fmt::arg("count", gameCount));
box_.set_tooltip_text(text);
}
// Label format
- std::string str =
- fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph),
- fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
+ std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),
+ fmt::arg("glyph", useIcon ? "" : glyph),
+ fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
label_.set_markup(str);
if (useIcon) {
- if (!Gtk::IconTheme::get_default()->has_icon(iconName)) {
+ if (!DefaultGtkIconThemeWrapper::has_icon(iconName)) {
iconName = DEFAULT_ICON_NAME;
}
icon_.set_from_icon_name(iconName, Gtk::ICON_SIZE_INVALID);
diff --git a/src/modules/hyprland/backend.cpp b/src/modules/hyprland/backend.cpp
index 76c071c0..79bc6375 100644
--- a/src/modules/hyprland/backend.cpp
+++ b/src/modules/hyprland/backend.cpp
@@ -133,6 +133,8 @@ void IPC::unregisterForIPC(EventHandler* ev_handler) {
std::string IPC::getSocket1Reply(const std::string& rq) {
// basically hyprctl
+ struct addrinfo ai_hints;
+ struct addrinfo* ai_res = NULL;
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
@@ -140,9 +142,11 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
return "";
}
- const auto SERVER = gethostbyname("localhost");
+ memset(&ai_hints, 0, sizeof(struct addrinfo));
+ ai_hints.ai_family = AF_UNSPEC;
+ ai_hints.ai_socktype = SOCK_STREAM;
- if (!SERVER) {
+ if (getaddrinfo("localhost", NULL, &ai_hints, &ai_res) != 0) {
spdlog::error("Hyprland IPC: Couldn't get host (2)");
return "";
}
@@ -177,17 +181,25 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
}
char buffer[8192] = {0};
+ std::string response;
- sizeWritten = read(SERVERSOCKET, buffer, 8192);
+ do {
+ sizeWritten = read(SERVERSOCKET, buffer, 8192);
- if (sizeWritten < 0) {
- spdlog::error("Hyprland IPC: Couldn't read (5)");
- return "";
- }
+ if (sizeWritten < 0) {
+ spdlog::error("Hyprland IPC: Couldn't read (5)");
+ close(SERVERSOCKET);
+ return "";
+ }
+ response.append(buffer, sizeWritten);
+ } while (sizeWritten == 8192);
close(SERVERSOCKET);
+ return response;
+}
- return std::string(buffer);
+Json::Value IPC::getSocket1JsonReply(const std::string& rq) {
+ return parser_.parse(getSocket1Reply("j/" + rq));
}
} // namespace waybar::modules::hyprland
diff --git a/src/modules/hyprland/language.cpp b/src/modules/hyprland/language.cpp
index 13e30ec6..aa22a482 100644
--- a/src/modules/hyprland/language.cpp
+++ b/src/modules/hyprland/language.cpp
@@ -6,7 +6,7 @@
#include
-#include "modules/hyprland/backend.hpp"
+#include "util/string.hpp"
namespace waybar::modules::hyprland {
@@ -22,7 +22,7 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
initLanguage();
label_.hide();
- ALabel::update();
+ update();
// register for hyprland ipc
gIPC->registerForIPC("activelayout", this);
@@ -37,9 +37,20 @@ Language::~Language() {
auto Language::update() -> void {
std::lock_guard lg(mutex_);
+ std::string layoutName = std::string{};
+ if (config_.isMember("format-" + layout_.short_description)) {
+ const auto propName = "format-" + layout_.short_description;
+ layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
+ } else {
+ layoutName = trim(fmt::format(fmt::runtime(format_), fmt::arg("long", layout_.full_name),
+ fmt::arg("short", layout_.short_name),
+ fmt::arg("shortDescription", layout_.short_description),
+ fmt::arg("variant", layout_.variant)));
+ }
+
if (!format_.empty()) {
label_.show();
- label_.set_markup(layoutName_);
+ label_.set_markup(layoutName);
} else {
label_.hide();
}
@@ -49,27 +60,15 @@ auto Language::update() -> void {
void Language::onEvent(const std::string& ev) {
std::lock_guard lg(mutex_);
- auto layoutName = ev.substr(ev.find_last_of(',') + 1);
- auto keebName = ev.substr(0, ev.find_last_of(','));
- keebName = keebName.substr(keebName.find_first_of('>') + 2);
+ std::string kbName(begin(ev) + ev.find_last_of('>') + 1, begin(ev) + ev.find_first_of(','));
+ auto layoutName = ev.substr(ev.find_first_of(',') + 1);
- if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString())
+ if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore
- const auto BRIEFNAME = getShortFrom(layoutName);
-
- if (config_.isMember("format-" + BRIEFNAME)) {
- const auto PROPNAME = "format-" + BRIEFNAME;
- layoutName = fmt::format(format_, config_[PROPNAME].asString());
- } else {
- layoutName = fmt::format(format_, layoutName);
- }
-
layoutName = waybar::util::sanitize_string(layoutName);
- if (layoutName == layoutName_) return;
-
- layoutName_ = layoutName;
+ layout_ = getLayout(layoutName);
spdlog::debug("hyprland language onevent with {}", layoutName);
@@ -77,20 +76,22 @@ void Language::onEvent(const std::string& ev) {
}
void Language::initLanguage() {
- const auto INPUTDEVICES = gIPC->getSocket1Reply("devices");
+ const auto inputDevices = gIPC->getSocket1Reply("devices");
- if (!config_.isMember("keyboard-name")) return;
-
- const auto KEEBNAME = config_["keyboard-name"].asString();
+ const auto kbName = config_["keyboard-name"].asString();
try {
- auto searcher = INPUTDEVICES.substr(INPUTDEVICES.find(KEEBNAME) + KEEBNAME.length());
- searcher = searcher.substr(searcher.find("keymap:") + 7);
+ auto searcher = kbName.empty()
+ ? inputDevices
+ : inputDevices.substr(inputDevices.find(kbName) + kbName.length());
+ searcher = searcher.substr(searcher.find("keymap:") + 8);
searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
- layoutName_ = searcher;
+ searcher = waybar::util::sanitize_string(searcher);
- spdlog::debug("hyprland language initLanguage found {}", layoutName_);
+ layout_ = getLayout(searcher);
+
+ spdlog::debug("hyprland language initLanguage found {}", layout_.full_name);
dp.emit();
@@ -99,11 +100,10 @@ void Language::initLanguage() {
}
}
-std::string Language::getShortFrom(const std::string& fullName) {
+auto Language::getLayout(const std::string& fullName) -> Layout {
const auto CONTEXT = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
rxkb_context_parse_default_ruleset(CONTEXT);
- std::string foundName = "";
rxkb_layout* layout = rxkb_layout_first(CONTEXT);
while (layout) {
std::string nameOfLayout = rxkb_layout_get_description(layout);
@@ -113,16 +113,26 @@ std::string Language::getShortFrom(const std::string& fullName) {
continue;
}
- std::string briefName = rxkb_layout_get_brief(layout);
+ auto name = std::string(rxkb_layout_get_name(layout));
+ auto variant_ = rxkb_layout_get_variant(layout);
+ std::string variant = variant_ == nullptr ? "" : std::string(variant_);
+
+ auto short_description_ = rxkb_layout_get_brief(layout);
+ std::string short_description =
+ short_description_ == nullptr ? "" : std::string(short_description_);
+
+ Layout info = Layout{nameOfLayout, name, variant, short_description};
rxkb_context_unref(CONTEXT);
- return briefName;
+ return info;
}
rxkb_context_unref(CONTEXT);
- return "";
+ spdlog::debug("hyprland language didn't find matching layout");
+
+ return Layout{"", "", "", ""};
}
} // namespace waybar::modules::hyprland
diff --git a/src/modules/hyprland/submap.cpp b/src/modules/hyprland/submap.cpp
new file mode 100644
index 00000000..22acbf31
--- /dev/null
+++ b/src/modules/hyprland/submap.cpp
@@ -0,0 +1,63 @@
+#include "modules/hyprland/submap.hpp"
+
+#include
+
+#include
+
+namespace waybar::modules::hyprland {
+
+Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
+ : ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
+ modulesReady = true;
+
+ if (!gIPC.get()) {
+ gIPC = std::make_unique();
+ }
+
+ label_.hide();
+ ALabel::update();
+
+ // register for hyprland ipc
+ gIPC->registerForIPC("submap", this);
+ dp.emit();
+}
+
+Submap::~Submap() {
+ gIPC->unregisterForIPC(this);
+ // wait for possible event handler to finish
+ std::lock_guard lg(mutex_);
+}
+
+auto Submap::update() -> void {
+ std::lock_guard lg(mutex_);
+
+ if (submap_.empty()) {
+ event_box_.hide();
+ } else {
+ label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
+ if (tooltipEnabled()) {
+ label_.set_tooltip_text(submap_);
+ }
+ event_box_.show();
+ }
+ // Call parent update
+ ALabel::update();
+}
+
+void Submap::onEvent(const std::string& ev) {
+ std::lock_guard lg(mutex_);
+
+ if (ev.find("submap") == std::string::npos) {
+ return;
+ }
+
+ auto submapName = ev.substr(ev.find_last_of('>') + 1);
+ submapName = waybar::util::sanitize_string(submapName);
+
+ submap_ = submapName;
+
+ spdlog::debug("hyprland submap onevent with {}", submap_);
+
+ dp.emit();
+}
+} // namespace waybar::modules::hyprland
diff --git a/src/modules/hyprland/window.cpp b/src/modules/hyprland/window.cpp
index d3d06cc5..cb820bcd 100644
--- a/src/modules/hyprland/window.cpp
+++ b/src/modules/hyprland/window.cpp
@@ -2,30 +2,35 @@
#include
+#include
#include
#include
+#include
#include "modules/hyprland/backend.hpp"
-#include "util/command.hpp"
#include "util/json.hpp"
-#include "util/rewrite_title.hpp"
+#include "util/rewrite_string.hpp"
namespace waybar::modules::hyprland {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "window", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
- separate_outputs = config["separate-outputs"].as();
+ separate_outputs = config["separate-outputs"].asBool();
if (!gIPC.get()) {
gIPC = std::make_unique();
}
- label_.hide();
- ALabel::update();
+ queryActiveWorkspace();
+ update();
// register for hyprland ipc
gIPC->registerForIPC("activewindow", this);
+ gIPC->registerForIPC("closewindow", this);
+ gIPC->registerForIPC("movewindow", this);
+ gIPC->registerForIPC("changefloatingmode", this);
+ gIPC->registerForIPC("fullscreen", this);
}
Window::~Window() {
@@ -38,63 +43,136 @@ auto Window::update() -> void {
// fix ampersands
std::lock_guard lg(mutex_);
+ std::string window_name = waybar::util::sanitize_string(workspace_.last_window_title);
+
+ if (window_name != last_title_) {
+ if (window_name.empty()) {
+ label_.get_style_context()->add_class("empty");
+ } else {
+ label_.get_style_context()->remove_class("empty");
+ }
+ last_title_ = window_name;
+ }
+
if (!format_.empty()) {
label_.show();
- label_.set_markup(
- fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"])));
+ label_.set_markup(fmt::format(fmt::runtime(format_),
+ waybar::util::rewriteString(window_name, config_["rewrite"])));
} else {
label_.hide();
}
+ setClass("empty", workspace_.windows == 0);
+ setClass("solo", solo_);
+ setClass("fullscreen", fullscreen_);
+ setClass("floating", all_floating_);
+
+ if (!last_solo_class_.empty() && solo_class_ != last_solo_class_) {
+ if (bar_.window.get_style_context()->has_class(last_solo_class_)) {
+ bar_.window.get_style_context()->remove_class(last_solo_class_);
+ spdlog::trace("Removing solo class: {}", last_solo_class_);
+ }
+ }
+
+ if (!solo_class_.empty() && solo_class_ != last_solo_class_) {
+ bar_.window.get_style_context()->add_class(solo_class_);
+ spdlog::trace("Adding solo class: {}", solo_class_);
+ }
+ last_solo_class_ = solo_class_;
+
ALabel::update();
}
-int Window::getActiveWorkspaceID(std::string monitorName) {
- auto cmd = waybar::util::command::exec("hyprctl monitors -j");
- assert(cmd.exit_code == 0);
- Json::Value json = parser_.parse(cmd.out);
- assert(json.isArray());
- auto monitor = std::find_if(json.begin(), json.end(),
- [&](Json::Value monitor) { return monitor["name"] == monitorName; });
- if (monitor == std::end(json)) {
- return 0;
- }
- return (*monitor)["activeWorkspace"]["id"].as();
+auto Window::getActiveWorkspace() -> Workspace {
+ const auto workspace = gIPC->getSocket1JsonReply("activeworkspace");
+ assert(workspace.isObject());
+ return Workspace::parse(workspace);
}
-std::string Window::getLastWindowTitle(int workspaceID) {
- auto cmd = waybar::util::command::exec("hyprctl workspaces -j");
- assert(cmd.exit_code == 0);
- Json::Value json = parser_.parse(cmd.out);
- assert(json.isArray());
- auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) {
- return workspace["id"].as() == workspaceID;
- });
-
- if (workspace == std::end(json)) {
- return "";
+auto Window::getActiveWorkspace(const std::string& monitorName) -> Workspace {
+ const auto monitors = gIPC->getSocket1JsonReply("monitors");
+ assert(monitors.isArray());
+ auto monitor = std::find_if(monitors.begin(), monitors.end(),
+ [&](Json::Value monitor) { return monitor["name"] == monitorName; });
+ if (monitor == std::end(monitors)) {
+ spdlog::warn("Monitor not found: {}", monitorName);
+ return Workspace{-1, 0, "", ""};
+ }
+ const int id = (*monitor)["activeWorkspace"]["id"].asInt();
+
+ const auto workspaces = gIPC->getSocket1JsonReply("workspaces");
+ assert(workspaces.isArray());
+ auto workspace = std::find_if(monitors.begin(), monitors.end(),
+ [&](Json::Value workspace) { return workspace["id"] == id; });
+ if (workspace == std::end(monitors)) {
+ spdlog::warn("No workspace with id {}", id);
+ return Workspace{-1, 0, "", ""};
+ }
+ return Workspace::parse(*workspace);
+}
+
+auto Window::Workspace::parse(const Json::Value& value) -> Window::Workspace {
+ return Workspace{value["id"].asInt(), value["windows"].asInt(), value["lastwindow"].asString(),
+ value["lastwindowtitle"].asString()};
+}
+
+void Window::queryActiveWorkspace() {
+ std::lock_guard lg(mutex_);
+
+ if (separate_outputs) {
+ workspace_ = getActiveWorkspace(this->bar_.output->name);
+ } else {
+ workspace_ = getActiveWorkspace();
+ }
+
+ if (workspace_.windows > 0) {
+ const auto clients = gIPC->getSocket1Reply("j/clients");
+ Json::Value json = parser_.parse(clients);
+ assert(json.isArray());
+ auto active_window = std::find_if(json.begin(), json.end(), [&](Json::Value window) {
+ return window["address"] == workspace_.last_window;
+ });
+ if (active_window == std::end(json)) {
+ return;
+ }
+
+ if (workspace_.windows == 1 && !(*active_window)["floating"].asBool()) {
+ solo_class_ = (*active_window)["class"].asString();
+ } else {
+ solo_class_ = "";
+ }
+ std::vector workspace_windows;
+ std::copy_if(json.begin(), json.end(), std::back_inserter(workspace_windows),
+ [&](Json::Value window) {
+ return window["workspace"]["id"] == workspace_.id && window["mapped"].asBool();
+ });
+ solo_ = 1 == std::count_if(workspace_windows.begin(), workspace_windows.end(),
+ [&](Json::Value window) { return !window["floating"].asBool(); });
+ all_floating_ = std::all_of(workspace_windows.begin(), workspace_windows.end(),
+ [&](Json::Value window) { return window["floating"].asBool(); });
+ fullscreen_ = (*active_window)["fullscreen"].asBool();
+ } else {
+ solo_class_ = "";
+ solo_ = false;
+ all_floating_ = false;
+ fullscreen_ = false;
}
- return (*workspace)["lastwindowtitle"].as();
}
void Window::onEvent(const std::string& ev) {
- std::lock_guard lg(mutex_);
-
- std::string windowName;
- if (separate_outputs) {
- windowName = getLastWindowTitle(getActiveWorkspaceID(this->bar_.output->name));
- } else {
- windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);
- }
-
- windowName = waybar::util::sanitize_string(windowName);
-
- if (windowName == lastView) return;
-
- lastView = windowName;
-
- spdlog::debug("hyprland window onevent with {}", windowName);
+ queryActiveWorkspace();
dp.emit();
}
+
+void Window::setClass(const std::string& classname, bool enable) {
+ if (enable) {
+ if (!bar_.window.get_style_context()->has_class(classname)) {
+ bar_.window.get_style_context()->add_class(classname);
+ }
+ } else {
+ bar_.window.get_style_context()->remove_class(classname);
+ }
+}
+
} // namespace waybar::modules::hyprland
diff --git a/src/modules/hyprland/workspaces.cpp b/src/modules/hyprland/workspaces.cpp
new file mode 100644
index 00000000..e169f916
--- /dev/null
+++ b/src/modules/hyprland/workspaces.cpp
@@ -0,0 +1,199 @@
+#include "modules/hyprland/workspaces.hpp"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+namespace waybar::modules::hyprland {
+
+Workspaces::Workspaces(const std::string &id, const Bar &bar, const Json::Value &config)
+ : AModule(config, "workspaces", id, false, false),
+ bar_(bar),
+ box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
+ Json::Value config_format = config["format"];
+
+ format_ = config_format.isString() ? config_format.asString() : "{id}";
+ with_icon_ = format_.find("{icon}") != std::string::npos;
+
+ if (with_icon_ && icons_map_.empty()) {
+ Json::Value format_icons = config["format-icons"];
+ for (std::string &name : format_icons.getMemberNames()) {
+ icons_map_.emplace(name, format_icons[name].asString());
+ }
+
+ icons_map_.emplace("", "");
+ }
+
+ box_.set_name("workspaces");
+ if (!id.empty()) {
+ box_.get_style_context()->add_class(id);
+ }
+ event_box_.add(box_);
+ modulesReady = true;
+ if (!gIPC.get()) {
+ gIPC = std::make_unique();
+ }
+
+ init();
+
+ gIPC->registerForIPC("workspace", this);
+ gIPC->registerForIPC("createworkspace", this);
+ gIPC->registerForIPC("destroyworkspace", this);
+}
+
+auto Workspaces::update() -> void {
+ for (int &workspace_to_remove : workspaces_to_remove_) {
+ remove_workspace(workspace_to_remove);
+ }
+
+ workspaces_to_remove_.clear();
+
+ for (int &workspace_to_create : workspaces_to_create_) {
+ create_workspace(workspace_to_create);
+ }
+
+ workspaces_to_create_.clear();
+
+ for (std::unique_ptr &workspace : workspaces_) {
+ workspace->set_active(workspace->id() == active_workspace_id);
+
+ std::string &workspace_icon = icons_map_[""];
+ if (with_icon_) {
+ workspace_icon = workspace->select_icon(icons_map_);
+ }
+
+ workspace->update(format_, workspace_icon);
+ }
+
+ AModule::update();
+}
+
+void Workspaces::onEvent(const std::string &ev) {
+ std::lock_guard lock(mutex_);
+ std::string eventName(begin(ev), begin(ev) + ev.find_first_of('>'));
+ std::string payload = ev.substr(eventName.size() + 2);
+ if (eventName == "workspace") {
+ std::from_chars(payload.data(), payload.data() + payload.size(), active_workspace_id);
+ } else if (eventName == "destroyworkspace") {
+ int deleted_workspace_id;
+ std::from_chars(payload.data(), payload.data() + payload.size(), deleted_workspace_id);
+ workspaces_to_remove_.push_back(deleted_workspace_id);
+ } else if (eventName == "createworkspace") {
+ int new_workspace_id;
+ std::from_chars(payload.data(), payload.data() + payload.size(), new_workspace_id);
+ workspaces_to_create_.push_back(new_workspace_id);
+ }
+
+ dp.emit();
+}
+
+void Workspaces::create_workspace(int id) {
+ workspaces_.push_back(std::make_unique(id));
+ Gtk::Button &new_workspace_button = workspaces_.back()->button();
+ box_.pack_start(new_workspace_button, false, false);
+ sort_workspaces();
+ new_workspace_button.show_all();
+}
+
+void Workspaces::remove_workspace(int id) {
+ auto workspace = std::find_if(workspaces_.begin(), workspaces_.end(),
+ [&](std::unique_ptr &x) { return x->id() == id; });
+
+ if (workspace == workspaces_.end()) {
+ spdlog::warn("Can't find workspace with id {}", id);
+ return;
+ }
+
+ box_.remove(workspace->get()->button());
+ workspaces_.erase(workspace);
+}
+
+void Workspaces::init() {
+ const auto activeWorkspace = WorkspaceDto::parse(gIPC->getSocket1JsonReply("activeworkspace"));
+ active_workspace_id = activeWorkspace.id;
+ const Json::Value workspaces_json = gIPC->getSocket1JsonReply("workspaces");
+ for (const Json::Value &workspace_json : workspaces_json) {
+ workspaces_.push_back(
+ std::make_unique(Workspace(WorkspaceDto::parse(workspace_json))));
+ }
+
+ for (auto &workspace : workspaces_) {
+ box_.pack_start(workspace->button(), false, false);
+ }
+
+ sort_workspaces();
+
+ dp.emit();
+}
+
+Workspaces::~Workspaces() {
+ gIPC->unregisterForIPC(this);
+ // wait for possible event handler to finish
+ std::lock_guard lg(mutex_);
+}
+
+WorkspaceDto WorkspaceDto::parse(const Json::Value &value) {
+ return WorkspaceDto{value["id"].asInt()};
+}
+
+Workspace::Workspace(WorkspaceDto dto) : Workspace(dto.id){};
+
+Workspace::Workspace(int id) : id_(id) {
+ button_.set_relief(Gtk::RELIEF_NONE);
+ content_.set_center_widget(label_);
+ button_.add(content_);
+};
+
+void add_or_remove_class(Glib::RefPtr context, bool condition,
+ const std::string &class_name) {
+ if (condition) {
+ context->add_class(class_name);
+ } else {
+ context->remove_class(class_name);
+ }
+}
+
+void Workspace::update(const std::string &format, const std::string &icon) {
+ Glib::RefPtr style_context = button_.get_style_context();
+ add_or_remove_class(style_context, active(), "active");
+
+ label_.set_markup(
+ fmt::format(fmt::runtime(format), fmt::arg("id", id()), fmt::arg("icon", icon)));
+}
+
+void Workspaces::sort_workspaces() {
+ std::sort(workspaces_.begin(), workspaces_.end(),
+ [](std::unique_ptr &lhs, std::unique_ptr &rhs) {
+ return lhs->id() < rhs->id();
+ });
+
+ for (size_t i = 0; i < workspaces_.size(); ++i) {
+ box_.reorder_child(workspaces_[i]->button(), i);
+ }
+}
+
+std::string &Workspace::select_icon(std::map &icons_map) {
+ if (active()) {
+ auto active_icon_it = icons_map.find("active");
+ if (active_icon_it != icons_map.end()) {
+ return active_icon_it->second;
+ }
+ }
+
+ auto named_icon_it = icons_map.find(std::to_string(id()));
+ if (named_icon_it != icons_map.end()) {
+ return named_icon_it->second;
+ }
+
+ auto default_icon_it = icons_map.find("default");
+ if (default_icon_it != icons_map.end()) {
+ return default_icon_it->second;
+ }
+
+ return icons_map[""];
+}
+} // namespace waybar::modules::hyprland
diff --git a/src/modules/idle_inhibitor.cpp b/src/modules/idle_inhibitor.cpp
index c4109b0e..a5fc9ac7 100644
--- a/src/modules/idle_inhibitor.cpp
+++ b/src/modules/idle_inhibitor.cpp
@@ -63,21 +63,15 @@ auto waybar::modules::IdleInhibitor::update() -> void {
}
std::string status_text = status ? "activated" : "deactivated";
- label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
+ label_.set_markup(fmt::format(fmt::runtime(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_markup(
- status ? fmt::format(config_["tooltip-format-activated"].isString()
- ? config_["tooltip-format-activated"].asString()
- : "{status}",
- fmt::arg("status", status_text),
- fmt::arg("icon", getIcon(0, status_text)))
- : fmt::format(config_["tooltip-format-deactivated"].isString()
- ? config_["tooltip-format-deactivated"].asString()
- : "{status}",
- fmt::arg("status", status_text),
- fmt::arg("icon", getIcon(0, status_text))));
+ auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"];
+ auto tooltip_format = config.isString() ? config.asString() : "{status}";
+ label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
+ fmt::arg("status", status_text),
+ fmt::arg("icon", getIcon(0, status_text))));
}
// Call parent update
ALabel::update();
diff --git a/src/modules/image.cpp b/src/modules/image.cpp
index eed19aea..843cd954 100644
--- a/src/modules/image.cpp
+++ b/src/modules/image.cpp
@@ -1,15 +1,16 @@
#include "modules/image.hpp"
-#include
-
-waybar::modules::Image::Image(const std::string& name, const std::string& id,
- const Json::Value& config)
- : AModule(config, "image-" + name, id, "{}") {
- event_box_.add(image_);
+waybar::modules::Image::Image(const std::string& id, const Json::Value& config)
+ : AModule(config, "image", id), box_(Gtk::ORIENTATION_HORIZONTAL, 0) {
+ box_.pack_start(image_);
+ box_.set_name("image");
+ if (!id.empty()) {
+ box_.get_style_context()->add_class(id);
+ }
+ event_box_.add(box_);
dp.emit();
- path_ = config["path"].asString();
size_ = config["size"].asInt();
interval_ = config_["interval"].asInt();
@@ -41,13 +42,25 @@ void waybar::modules::Image::refresh(int sig) {
auto waybar::modules::Image::update() -> void {
Glib::RefPtr pixbuf;
-
+ if (config_["path"].isString()) {
+ path_ = config_["path"].asString();
+ } else if (config_["exec"].isString()) {
+ output_ = util::command::exec(config_["exec"].asString());
+ parseOutputRaw();
+ } else {
+ path_ = "";
+ }
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS))
pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_);
else
pixbuf = {};
if (pixbuf) {
+ if (tooltipEnabled() && !tooltip_.empty()) {
+ if (box_.get_tooltip_markup() != tooltip_) {
+ box_.set_tooltip_markup(tooltip_);
+ }
+ }
image_.set(pixbuf);
image_.show();
} else {
@@ -57,3 +70,19 @@ auto waybar::modules::Image::update() -> void {
AModule::update();
}
+
+void waybar::modules::Image::parseOutputRaw() {
+ std::istringstream output(output_.out);
+ std::string line;
+ int i = 0;
+ while (getline(output, line)) {
+ if (i == 0) {
+ path_ = line;
+ } else if (i == 1) {
+ tooltip_ = line;
+ } else {
+ break;
+ }
+ i++;
+ }
+}
diff --git a/src/modules/inhibitor.cpp b/src/modules/inhibitor.cpp
index e4340b14..fe2a4be4 100644
--- a/src/modules/inhibitor.cpp
+++ b/src/modules/inhibitor.cpp
@@ -118,7 +118,7 @@ auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated";
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
- label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
+ label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
diff --git a/src/modules/jack.cpp b/src/modules/jack.cpp
index 3a92110c..9bd6fcd6 100644
--- a/src/modules/jack.cpp
+++ b/src/modules/jack.cpp
@@ -72,7 +72,7 @@ auto JACK::update() -> void {
} else
format = "{load}%";
- label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)),
+ label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("load", std::round(load_)),
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_)));
@@ -81,9 +81,9 @@ auto JACK::update() -> void {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(
- tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_),
- fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)),
- fmt::arg("xruns", xruns_)));
+ fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
+ fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
+ fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));
}
// Call parent update
diff --git a/src/modules/keyboard_state.cpp b/src/modules/keyboard_state.cpp
index b2750b68..4c081d6a 100644
--- a/src/modules/keyboard_state.cpp
+++ b/src/modules/keyboard_state.cpp
@@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void {
};
for (auto& label_state : label_states) {
std::string text;
- text = fmt::format(label_state.format,
+ text = fmt::format(fmt::runtime(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);
diff --git a/src/modules/memory/common.cpp b/src/modules/memory/common.cpp
index 4a0e6347..544d7814 100644
--- a/src/modules/memory/common.cpp
+++ b/src/modules/memory/common.cpp
@@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void {
event_box_.show();
auto icons = std::vector{state};
label_.set_markup(fmt::format(
- format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)),
+ fmt::runtime(format), used_ram_percentage,
+ fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
@@ -68,8 +69,8 @@ auto waybar::modules::Memory::update() -> void {
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("swapTotal", total_swap_gigabytes),
+ fmt::runtime(tooltip_format), used_ram_percentage,
+ fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
diff --git a/src/modules/mpd/mpd.cpp b/src/modules/mpd/mpd.cpp
index 2c855d39..73062c76 100644
--- a/src/modules/mpd/mpd.cpp
+++ b/src/modules/mpd/mpd.cpp
@@ -102,7 +102,6 @@ void waybar::modules::MPD::setLabel() {
} else {
label_.hide();
}
-
if (tooltipEnabled()) {
std::string tooltip_format;
@@ -123,7 +122,9 @@ void waybar::modules::MPD::setLabel() {
std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = "";
- if (stopped()) {
+ bool no_song = song_.get() == nullptr;
+ if (stopped() || no_song) {
+ if (no_song) spdlog::warn("Bug in mpd: no current song but state is not stopped.");
format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
label_.get_style_context()->add_class("stopped");
@@ -175,14 +176,14 @@ void waybar::modules::MPD::setLabel() {
try {
auto text = fmt::format(
- 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),
- fmt::arg("filename", filename));
+ fmt::runtime(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), fmt::arg("filename", filename));
if (text.empty()) {
label_.hide();
} else {
@@ -199,7 +200,7 @@ void waybar::modules::MPD::setLabel() {
: "MPD (connected)";
try {
auto tooltip_text =
- fmt::format(tooltip_format, fmt::arg("artist", artist.raw()),
+ fmt::format(fmt::runtime(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),
diff --git a/src/modules/mpris/mpris.cpp b/src/modules/mpris/mpris.cpp
new file mode 100644
index 00000000..a5621758
--- /dev/null
+++ b/src/modules/mpris/mpris.cpp
@@ -0,0 +1,723 @@
+#include "modules/mpris/mpris.hpp"
+
+#include
+
+#include
+#include
+#include
+
+extern "C" {
+#include
+}
+
+#include
+#include
+
+namespace waybar::modules::mpris {
+
+const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
+
+Mpris::Mpris(const std::string& id, const Json::Value& config)
+ : ALabel(config, "mpris", id, DEFAULT_FORMAT, 5, false, true),
+ tooltip_(DEFAULT_FORMAT),
+ artist_len_(-1),
+ album_len_(-1),
+ title_len_(-1),
+ dynamic_len_(-1),
+ dynamic_prio_({"title", "artist", "album", "position", "length"}),
+ dynamic_order_({"title", "artist", "album", "position", "length"}),
+ dynamic_separator_(" - "),
+ truncate_hours_(true),
+ tooltip_len_limits_(false),
+ // this character is used in Gnome so it's fine to use it here
+ ellipsis_("\u2026"),
+ player_("playerctld"),
+ manager(),
+ player(),
+ last_update_(std::chrono::system_clock::now() - interval_) {
+ if (config_["format-playing"].isString()) {
+ format_playing_ = config_["format-playing"].asString();
+ }
+ if (config_["format-paused"].isString()) {
+ format_paused_ = config_["format-paused"].asString();
+ }
+ if (config_["format-stopped"].isString()) {
+ format_stopped_ = config_["format-stopped"].asString();
+ }
+ if (config_["ellipsis"].isString()) {
+ ellipsis_ = config_["ellipsis"].asString();
+ }
+ if (config_["dynamic-separator"].isString()) {
+ dynamic_separator_ = config_["dynamic-separator"].asString();
+ }
+ if (tooltipEnabled()) {
+ if (config_["tooltip-format"].isString()) {
+ tooltip_ = config_["tooltip-format"].asString();
+ }
+ if (config_["tooltip-format-playing"].isString()) {
+ tooltip_playing_ = config_["tooltip-format-playing"].asString();
+ }
+ if (config_["tooltip-format-paused"].isString()) {
+ tooltip_paused_ = config_["tooltip-format-paused"].asString();
+ }
+ if (config_["tooltip-format-stopped"].isString()) {
+ tooltip_stopped_ = config_["tooltip-format-stopped"].asString();
+ }
+ if (config_["enable-tooltip-len-limits"].isBool()) {
+ tooltip_len_limits_ = config["enable-tooltip-len-limits"].asBool();
+ }
+ }
+
+ if (config["artist-len"].isUInt()) {
+ artist_len_ = config["artist-len"].asUInt();
+ }
+ if (config["album-len"].isUInt()) {
+ album_len_ = config["album-len"].asUInt();
+ }
+ if (config["title-len"].isUInt()) {
+ title_len_ = config["title-len"].asUInt();
+ }
+ if (config["dynamic-len"].isUInt()) {
+ dynamic_len_ = config["dynamic-len"].asUInt();
+ }
+ // "dynamic-priority" has been kept for backward compatibility
+ if (config_["dynamic-importance-order"].isArray() || config_["dynamic-priority"].isArray()) {
+ dynamic_prio_.clear();
+ const auto& dynamic_priority = config_["dynamic-importance-order"].isArray() ? config_["dynamic-importance-order"] : config_["dynamic-priority"];
+ for (const auto& value : dynamic_priority) {
+ if (value.isString()) {
+ dynamic_prio_.push_back(value.asString());
+ }
+ }
+ }
+ if (config_["dynamic-order"].isArray()) {
+ dynamic_order_.clear();
+ for (auto it = config_["dynamic-order"].begin(); it != config_["dynamic-order"].end(); ++it) {
+ if (it->isString()) {
+ dynamic_order_.push_back(it->asString());
+ }
+ }
+ }
+
+ if (config_["truncate-hours"].isBool()) {
+ truncate_hours_ = config["truncate-hours"].asBool();
+ }
+ if (config_["player"].isString()) {
+ player_ = config_["player"].asString();
+ }
+ if (config_["ignored-players"].isArray()) {
+ for (auto it = config_["ignored-players"].begin(); it != config_["ignored-players"].end();
+ ++it) {
+ if (it->isString()) {
+ ignored_players_.push_back(it->asString());
+ }
+ }
+ }
+
+ GError* error = nullptr;
+ manager = playerctl_player_manager_new(&error);
+ if (error) {
+ throw std::runtime_error(fmt::format("unable to create MPRIS client: {}", error->message));
+ }
+
+ g_object_connect(manager, "signal::name-appeared", G_CALLBACK(onPlayerNameAppeared), this, NULL);
+ g_object_connect(manager, "signal::name-vanished", G_CALLBACK(onPlayerNameVanished), this, NULL);
+
+ if (player_ == "playerctld") {
+ // use playerctld proxy
+ PlayerctlPlayerName name = {
+ .instance = (gchar*)player_.c_str(),
+ .source = PLAYERCTL_SOURCE_DBUS_SESSION,
+ };
+ player = playerctl_player_new_from_name(&name, &error);
+
+ } else {
+ GList* players = playerctl_list_players(&error);
+ if (error) {
+ auto e = fmt::format("unable to list players: {}", error->message);
+ g_error_free(error);
+ throw std::runtime_error(e);
+ }
+
+ for (auto p = players; p != NULL; p = p->next) {
+ auto pn = static_cast(p->data);
+ if (strcmp(pn->name, player_.c_str()) == 0) {
+ player = playerctl_player_new_from_name(pn, &error);
+ break;
+ }
+ }
+ }
+
+ if (error) {
+ throw std::runtime_error(
+ fmt::format("unable to connect to player {}: {}", player_, error->message));
+ }
+
+ if (player) {
+ g_object_connect(player, "signal::play", G_CALLBACK(onPlayerPlay), this, "signal::pause",
+ G_CALLBACK(onPlayerPause), this, "signal::stop", G_CALLBACK(onPlayerStop),
+ this, "signal::stop", G_CALLBACK(onPlayerStop), this, "signal::metadata",
+ G_CALLBACK(onPlayerMetadata), this, NULL);
+ }
+
+ // allow setting an interval count that triggers periodic refreshes
+ if (interval_.count() > 0) {
+ thread_ = [this] {
+ dp.emit();
+ thread_.sleep_for(interval_);
+ };
+ }
+
+ // trigger initial update
+ dp.emit();
+}
+
+Mpris::~Mpris() {
+ if (manager != NULL) g_object_unref(manager);
+ if (player != NULL) g_object_unref(player);
+}
+
+auto Mpris::getIconFromJson(const Json::Value& icons, const std::string& key) -> std::string {
+ if (icons.isObject()) {
+ if (icons[key].isString()) {
+ return icons[key].asString();
+ } else if (icons["default"].isString()) {
+ return icons["default"].asString();
+ }
+ }
+ return "";
+}
+
+// Wide characters count as two, zero-width characters count as zero
+// Modifies str in-place (unless width = std::string::npos)
+// Returns the total width of the string pre-truncating
+size_t utf8_truncate(std::string& str, size_t width = std::string::npos) {
+ if (str.length() == 0) return 0;
+
+ const gchar* trunc_end = nullptr;
+
+ size_t total_width = 0;
+
+ for (gchar *data = str.data(), *end = data + str.size(); data;) {
+ gunichar c = g_utf8_get_char_validated(data, end - data);
+ if (c == -1U || c == -2U) {
+ // invalid unicode, treat string as ascii
+ if (width != std::string::npos && str.length() > width) str.resize(width);
+ return str.length();
+ } else if (g_unichar_iswide(c)) {
+ total_width += 2;
+ } else if (!g_unichar_iszerowidth(c) && c != 0xAD) { // neither zero-width nor soft hyphen
+ total_width += 1;
+ }
+
+ data = g_utf8_find_next_char(data, end);
+ if (width != std::string::npos && total_width <= width && !g_unichar_isspace(c))
+ trunc_end = data;
+ }
+
+ if (trunc_end) str.resize(trunc_end - str.data());
+
+ return total_width;
+}
+
+size_t utf8_width(const std::string& str) { return utf8_truncate(const_cast(str)); }
+
+void truncate(std::string& s, const std::string& ellipsis, size_t max_len) {
+ if (max_len == 0) {
+ s.resize(0);
+ return;
+ }
+ size_t len = utf8_truncate(s, max_len);
+ if (len > max_len) {
+ size_t ellipsis_len = utf8_width(ellipsis);
+ if (max_len >= ellipsis_len) {
+ if (ellipsis_len) utf8_truncate(s, max_len - ellipsis_len);
+ s += ellipsis;
+ } else {
+ s.resize(0);
+ }
+ }
+}
+
+auto Mpris::getArtistStr(const PlayerInfo& info, bool truncated) -> std::string {
+ auto artist = info.artist.value_or(std::string());
+ if (truncated && artist_len_ >= 0) truncate(artist, ellipsis_, artist_len_);
+ return artist;
+}
+
+auto Mpris::getAlbumStr(const PlayerInfo& info, bool truncated) -> std::string {
+ auto album = info.album.value_or(std::string());
+ if (truncated && album_len_ >= 0) truncate(album, ellipsis_, album_len_);
+ return album;
+}
+
+auto Mpris::getTitleStr(const PlayerInfo& info, bool truncated) -> std::string {
+ auto title = info.title.value_or(std::string());
+ if (truncated && title_len_ >= 0) truncate(title, ellipsis_, title_len_);
+ return title;
+}
+
+auto Mpris::getLengthStr(const PlayerInfo& info, bool truncated) -> std::string {
+ if (info.length.has_value()) {
+ auto length = info.length.value();
+ return (truncated && length.substr(0, 3) == "00:") ? length.substr(3) : length;
+ }
+ return std::string();
+}
+
+auto Mpris::getPositionStr(const PlayerInfo& info, bool truncated) -> std::string {
+ if (info.position.has_value()) {
+ auto position = info.position.value();
+ return (truncated && position.substr(0, 3) == "00:") ? position.substr(3) : position;
+ }
+ return std::string();
+}
+
+auto Mpris::getDynamicStr(const PlayerInfo& info, bool truncated, bool html) -> std::string {
+ auto artist = getArtistStr(info, truncated);
+ auto album = getAlbumStr(info, truncated);
+ auto title = getTitleStr(info, truncated);
+ auto length = getLengthStr(info, truncated && truncate_hours_);
+ // keep position format same as length format
+ auto position = getPositionStr(info, truncated && truncate_hours_ && length.length() < 6);
+
+ size_t artistLen = utf8_width(artist);
+ size_t albumLen = utf8_width(album);
+ size_t titleLen = utf8_width(title);
+ size_t lengthLen = length.length();
+ size_t posLen = position.length();
+
+ bool showArtist = (artistLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
+ "artist") != dynamic_order_.end());
+ bool showAlbum = (albumLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
+ "album") != dynamic_order_.end());
+ bool showTitle = (titleLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
+ "title") != dynamic_order_.end());
+ bool showLength = (lengthLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
+ "length") != dynamic_order_.end());
+ bool showPos = (posLen != 0) && (std::find(dynamic_order_.begin(), dynamic_order_.end(),
+ "position") != dynamic_order_.end());
+
+ if (truncated && dynamic_len_ >= 0) {
+ //Since the first element doesn't present a separator and we don't know a priori which one
+ //it will be, we add a "virtual separatorLen" to the dynamicLen, since we are adding the
+ //separatorLen to all the other lengths.
+ size_t separatorLen = utf8_width(dynamic_separator_);
+ size_t dynamicLen = dynamic_len_ + separatorLen;
+ if (showArtist) artistLen += separatorLen;
+ if (showAlbum) albumLen += separatorLen;
+ if (showTitle) albumLen += separatorLen;
+ if (showLength) lengthLen += separatorLen;
+ if (showPos) posLen += separatorLen;
+
+ size_t totalLen = 0;
+
+ for (auto it = dynamic_prio_.begin(); it != dynamic_prio_.end(); ++it) {
+ if (*it == "artist") {
+ if (totalLen + artistLen > dynamicLen) {
+ showArtist = false;
+ } else if (showArtist) {
+ totalLen += artistLen;
+ }
+ } else if (*it == "album") {
+ if (totalLen + albumLen > dynamicLen) {
+ showAlbum = false;
+ } else if (showAlbum) {
+ totalLen += albumLen;
+ }
+ } else if (*it == "title") {
+ if (totalLen + titleLen > dynamicLen) {
+ showTitle = false;
+ } else if (showTitle) {
+ totalLen += titleLen;
+ }
+ } else if (*it == "length") {
+ if (totalLen + lengthLen > dynamicLen) {
+ showLength = false;
+ } else if (showLength) {
+ totalLen += lengthLen;
+ posLen = std::max((size_t)2, posLen) - 2;
+ }
+ } else if (*it == "position") {
+ if (totalLen + posLen > dynamicLen) {
+ showPos = false;
+ } else if (showPos) {
+ totalLen += posLen;
+ lengthLen = std::max((size_t)2, lengthLen) - 2;
+ }
+ }
+ }
+ }
+
+ std::stringstream dynamic;
+ if (html) {
+ artist = Glib::Markup::escape_text(artist);
+ album = Glib::Markup::escape_text(album);
+ title = Glib::Markup::escape_text(title);
+ }
+
+ bool lengthOrPositionShown = false;
+ bool previousShown = false;
+ std::string previousOrder = "";
+
+ for (const std::string& order : dynamic_order_) {
+ if ((order == "artist" && showArtist) ||
+ (order == "album" && showAlbum) ||
+ (order == "title" && showTitle)) {
+ if (previousShown &&
+ previousOrder != "length" &&
+ previousOrder != "position") {
+ dynamic << dynamic_separator_;
+ }
+
+ if (order == "artist") {
+ dynamic << artist;
+ } else if (order == "album") {
+ dynamic << album;
+ } else if (order == "title") {
+ dynamic << title;
+ }
+
+ previousShown = true;
+ } else if (order == "length" || order == "position") {
+ if (!lengthOrPositionShown && (showLength || showPos)) {
+ if (html) dynamic << "";
+ if (previousShown) dynamic << ' ';
+ dynamic << '[';
+ if (showPos) {
+ dynamic << position;
+ if (showLength) dynamic << '/';
+ }
+ if (showLength) dynamic << length;
+ dynamic << ']';
+ if (!dynamic.str().empty()) dynamic << ' ';
+ if (html) dynamic << "";
+ lengthOrPositionShown = true;
+ }
+ }
+ previousOrder = order;
+ }
+ return dynamic.str();
+}
+
+auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
+ gpointer data) -> void {
+ Mpris* mpris = static_cast(data);
+ if (!mpris) return;
+
+ spdlog::debug("mpris: name-appeared callback: {}", player_name->name);
+
+ if (std::string(player_name->name) != mpris->player_) {
+ return;
+ }
+
+ GError* error = nullptr;
+ mpris->player = playerctl_player_new_from_name(player_name, &error);
+ g_object_connect(mpris->player, "signal::play", G_CALLBACK(onPlayerPlay), mpris, "signal::pause",
+ G_CALLBACK(onPlayerPause), mpris, "signal::stop", G_CALLBACK(onPlayerStop),
+ mpris, "signal::stop", G_CALLBACK(onPlayerStop), mpris, "signal::metadata",
+ G_CALLBACK(onPlayerMetadata), mpris, NULL);
+
+ mpris->dp.emit();
+}
+
+auto Mpris::onPlayerNameVanished(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
+ gpointer data) -> void {
+ Mpris* mpris = static_cast(data);
+ if (!mpris) return;
+
+ spdlog::debug("mpris: player-vanished callback: {}", player_name->name);
+
+ if (std::string(player_name->name) == mpris->player_) {
+ mpris->player = nullptr;
+ mpris->dp.emit();
+ }
+}
+
+auto Mpris::onPlayerPlay(PlayerctlPlayer* player, gpointer data) -> void {
+ Mpris* mpris = static_cast(data);
+ if (!mpris) return;
+
+ spdlog::debug("mpris: player-play callback");
+ // update widget
+ mpris->dp.emit();
+}
+
+auto Mpris::onPlayerPause(PlayerctlPlayer* player, gpointer data) -> void {
+ Mpris* mpris = static_cast(data);
+ if (!mpris) return;
+
+ spdlog::debug("mpris: player-pause callback");
+ // update widget
+ mpris->dp.emit();
+}
+
+auto Mpris::onPlayerStop(PlayerctlPlayer* player, gpointer data) -> void {
+ Mpris* mpris = static_cast(data);
+ if (!mpris) return;
+
+ spdlog::debug("mpris: player-stop callback");
+
+ // hide widget
+ mpris->event_box_.set_visible(false);
+ // update widget
+ mpris->dp.emit();
+}
+
+auto Mpris::onPlayerMetadata(PlayerctlPlayer* player, GVariant* metadata, gpointer data) -> void {
+ Mpris* mpris = static_cast(data);
+ if (!mpris) return;
+
+ spdlog::debug("mpris: player-metadata callback");
+ // update widget
+ mpris->dp.emit();
+}
+
+auto Mpris::getPlayerInfo() -> std::optional {
+ if (!player) {
+ return std::nullopt;
+ }
+
+ GError* error = nullptr;
+
+ char* player_status = nullptr;
+ auto player_playback_status = PLAYERCTL_PLAYBACK_STATUS_STOPPED;
+ g_object_get(player, "status", &player_status, "playback-status", &player_playback_status, NULL);
+
+ std::string player_name = player_;
+ if (player_name == "playerctld") {
+ GList* players = playerctl_list_players(&error);
+ if (error) {
+ auto e = fmt::format("unable to list players: {}", error->message);
+ g_error_free(error);
+ throw std::runtime_error(e);
+ }
+ // > get the list of players [..] in order of activity
+ // https://github.com/altdesktop/playerctl/blob/b19a71cb9dba635df68d271bd2b3f6a99336a223/playerctl/playerctl-common.c#L248-L249
+ players = g_list_first(players);
+ if (players) player_name = static_cast(players->data)->name;
+ }
+
+ if (std::any_of(ignored_players_.begin(), ignored_players_.end(),
+ [&](const std::string& pn) { return player_name == pn; })) {
+ spdlog::warn("mpris[{}]: ignoring player update", player_name);
+ return std::nullopt;
+ }
+
+ // make status lowercase
+ player_status[0] = std::tolower(player_status[0]);
+
+ PlayerInfo info = {
+ .name = player_name,
+ .status = player_playback_status,
+ .status_string = player_status,
+ .artist = std::nullopt,
+ .album = std::nullopt,
+ .title = std::nullopt,
+ .length = std::nullopt,
+ };
+
+ if (auto artist_ = playerctl_player_get_artist(player, &error)) {
+ spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
+ info.artist = artist_;
+ g_free(artist_);
+ }
+ if (error) goto errorexit;
+
+ if (auto album_ = playerctl_player_get_album(player, &error)) {
+ spdlog::debug("mpris[{}]: album = {}", info.name, album_);
+ info.album = album_;
+ g_free(album_);
+ }
+ if (error) goto errorexit;
+
+ if (auto title_ = playerctl_player_get_title(player, &error)) {
+ spdlog::debug("mpris[{}]: title = {}", info.name, title_);
+ info.title = title_;
+ g_free(title_);
+ }
+ if (error) goto errorexit;
+
+ if (auto length_ = playerctl_player_print_metadata_prop(player, "mpris:length", &error)) {
+ spdlog::debug("mpris[{}]: mpris:length = {}", info.name, length_);
+ std::chrono::microseconds len = std::chrono::microseconds(std::strtol(length_, nullptr, 10));
+ auto len_h = std::chrono::duration_cast(len);
+ auto len_m = std::chrono::duration_cast(len - len_h);
+ auto len_s = std::chrono::duration_cast(len - len_h - len_m);
+ info.length = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
+ g_free(length_);
+ }
+ if (error) goto errorexit;
+
+ {
+ auto position_ = playerctl_player_get_position(player, &error);
+ if (error) {
+ // it's fine to have an error here because not all players report a position
+ g_error_free(error);
+ error = nullptr;
+ } else {
+ spdlog::debug("mpris[{}]: position = {}", info.name, position_);
+ std::chrono::microseconds len = std::chrono::microseconds(position_);
+ auto len_h = std::chrono::duration_cast(len);
+ auto len_m = std::chrono::duration_cast(len - len_h);
+ auto len_s = std::chrono::duration_cast(len - len_h - len_m);
+ info.position = fmt::format("{:02}:{:02}:{:02}", len_h.count(), len_m.count(), len_s.count());
+ }
+ }
+
+ return info;
+
+errorexit:
+ spdlog::error("mpris[{}]: {}", info.name, error->message);
+ g_error_free(error);
+ return std::nullopt;
+}
+
+bool Mpris::handleToggle(GdkEventButton* const& e) {
+ GError* error = nullptr;
+
+ auto info = getPlayerInfo();
+ if (!info) return false;
+
+ if (e->type == GdkEventType::GDK_BUTTON_PRESS) {
+ switch (e->button) {
+ case 1: // left-click
+ if (config_["on-click"].isString()) {
+ return ALabel::handleToggle(e);
+ }
+ playerctl_player_play_pause(player, &error);
+ break;
+ case 2: // middle-click
+ if (config_["on-middle-click"].isString()) {
+ return ALabel::handleToggle(e);
+ }
+ playerctl_player_previous(player, &error);
+ break;
+ case 3: // right-click
+ if (config_["on-right-click"].isString()) {
+ return ALabel::handleToggle(e);
+ }
+ playerctl_player_next(player, &error);
+ break;
+ }
+ }
+ if (error) {
+ spdlog::error("mpris[{}]: error running builtin on-click action: {}", (*info).name,
+ error->message);
+ g_error_free(error);
+ return false;
+ }
+ return true;
+}
+
+auto Mpris::update() -> void {
+ const auto now = std::chrono::system_clock::now();
+ if (now - last_update_ < interval_) return;
+ last_update_ = now;
+
+ auto opt = getPlayerInfo();
+ if (!opt) {
+ event_box_.set_visible(false);
+ ALabel::update();
+ return;
+ }
+ auto info = *opt;
+
+ if (info.status == PLAYERCTL_PLAYBACK_STATUS_STOPPED) {
+ spdlog::debug("mpris[{}]: player stopped, skipping update", info.name);
+ return;
+ }
+
+ spdlog::debug("mpris[{}]: running update", info.name);
+
+ // set css class for player status
+ if (!lastStatus.empty() && label_.get_style_context()->has_class(lastStatus)) {
+ label_.get_style_context()->remove_class(lastStatus);
+ }
+ if (!label_.get_style_context()->has_class(info.status_string)) {
+ label_.get_style_context()->add_class(info.status_string);
+ }
+ lastStatus = info.status_string;
+
+ // set css class for player name
+ if (!lastPlayer.empty() && label_.get_style_context()->has_class(lastPlayer)) {
+ label_.get_style_context()->remove_class(lastPlayer);
+ }
+ if (!label_.get_style_context()->has_class(info.name)) {
+ label_.get_style_context()->add_class(info.name);
+ }
+ lastPlayer = info.name;
+
+ auto formatstr = format_;
+ auto tooltipstr = tooltip_;
+ switch (info.status) {
+ case PLAYERCTL_PLAYBACK_STATUS_PLAYING:
+ if (!format_playing_.empty()) formatstr = format_playing_;
+ if (!tooltip_playing_.empty()) tooltipstr = tooltip_playing_;
+ break;
+ case PLAYERCTL_PLAYBACK_STATUS_PAUSED:
+ if (!format_paused_.empty()) formatstr = format_paused_;
+ if (!tooltip_paused_.empty()) tooltipstr = tooltip_paused_;
+ break;
+ case PLAYERCTL_PLAYBACK_STATUS_STOPPED:
+ if (!format_stopped_.empty()) formatstr = format_stopped_;
+ if (!tooltip_stopped_.empty()) tooltipstr = tooltip_stopped_;
+ break;
+ }
+
+ std::string length = getLengthStr(info, truncate_hours_);
+ std::string tooltipLength =
+ (tooltip_len_limits_ || length.length() > 5) ? length : getLengthStr(info, false);
+ // keep position format same as length format
+ std::string position = getPositionStr(info, truncate_hours_ && length.length() < 6);
+ std::string tooltipPosition =
+ (tooltip_len_limits_ || position.length() > 5) ? position : getPositionStr(info, false);
+
+ try {
+ auto label_format = fmt::format(
+ fmt::runtime(formatstr),
+ fmt::arg("player", std::string(Glib::Markup::escape_text(info.name))),
+ fmt::arg("status", info.status_string),
+ fmt::arg("artist", std::string(Glib::Markup::escape_text(getArtistStr(info, true)))),
+ fmt::arg("title", std::string(Glib::Markup::escape_text(getTitleStr(info, true)))),
+ fmt::arg("album", std::string(Glib::Markup::escape_text(getAlbumStr(info, true)))),
+ fmt::arg("length", length), fmt::arg("position", position),
+ fmt::arg("dynamic", getDynamicStr(info, true, true)),
+ fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
+ fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string)));
+
+ if (label_format.empty()) {
+ label_.hide();
+ } else {
+ label_.set_markup(label_format);
+ label_.show();
+ }
+ } catch (fmt::format_error const& e) {
+ spdlog::warn("mpris: format error: {}", e.what());
+ }
+
+ if (tooltipEnabled()) {
+ try {
+ auto tooltip_text = fmt::format(
+ fmt::runtime(tooltipstr), fmt::arg("player", info.name),
+ fmt::arg("status", info.status_string),
+ fmt::arg("artist", getArtistStr(info, tooltip_len_limits_)),
+ fmt::arg("title", getTitleStr(info, tooltip_len_limits_)),
+ fmt::arg("album", getAlbumStr(info, tooltip_len_limits_)),
+ fmt::arg("length", tooltipLength), fmt::arg("position", tooltipPosition),
+ fmt::arg("dynamic", getDynamicStr(info, tooltip_len_limits_, false)),
+ fmt::arg("player_icon", getIconFromJson(config_["player-icons"], info.name)),
+ fmt::arg("status_icon", getIconFromJson(config_["status-icons"], info.status_string)));
+
+ label_.set_tooltip_text(tooltip_text);
+ } catch (fmt::format_error const& e) {
+ spdlog::warn("mpris: format error (tooltip): {}", e.what());
+ }
+ }
+
+ event_box_.set_visible(true);
+ // call parent update
+ ALabel::update();
+}
+
+} // namespace waybar::modules::mpris
diff --git a/src/modules/network.cpp b/src/modules/network.cpp
index a4797eec..5eef1661 100644
--- a/src/modules/network.cpp
+++ b/src/modules/network.cpp
@@ -87,6 +87,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
want_link_dump_(false),
want_addr_dump_(false),
dump_in_progress_(false),
+ is_p2p_(false),
cidr_(0),
signal_strength_dbm_(0),
signal_strength_(0),
@@ -98,7 +99,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
// 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.
+ // the module start with no text, but the event_box_ is shown.
label_.set_markup("
");
auto bandwidth = readBandwidthUsage();
@@ -187,7 +188,7 @@ void waybar::modules::Network::createEventSocket() {
throw std::runtime_error("Can't create epoll");
}
{
- ev_fd_ = eventfd(0, EFD_NONBLOCK);
+ ev_fd_ = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET;
@@ -331,7 +332,7 @@ auto waybar::modules::Network::update() -> void {
getState(signal_strength_);
auto text = fmt::format(
- format_, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
+ fmt::runtime(format_), fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
@@ -363,8 +364,8 @@ auto waybar::modules::Network::update() -> void {
}
if (!tooltip_format.empty()) {
auto tooltip_text = fmt::format(
- tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_),
- fmt::arg("signalStrength", signal_strength_),
+ fmt::runtime(tooltip_format), fmt::arg("essid", essid_),
+ fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),
@@ -456,6 +457,8 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
case IFLA_IFNAME:
ifname = static_cast(RTA_DATA(ifla));
ifname_len = RTA_PAYLOAD(ifla) - 1; // minus \0
+ if (ifi->ifi_flags & IFF_POINTOPOINT && net->checkInterface(ifname))
+ net->is_p2p_ = true;
break;
case IFLA_CARRIER: {
carrier = *(char *)RTA_DATA(ifla) == 1;
@@ -494,6 +497,7 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
net->ifname_ = new_ifname;
net->ifid_ = ifi->ifi_index;
+ if (ifi->ifi_flags & IFF_POINTOPOINT) net->is_p2p_ = true;
if (carrier.has_value()) {
net->carrier_ = carrier.value();
}
@@ -537,7 +541,9 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
for (; RTA_OK(ifa_rta, attrlen); ifa_rta = RTA_NEXT(ifa_rta, attrlen)) {
switch (ifa_rta->rta_type) {
- case IFA_ADDRESS: {
+ case IFA_ADDRESS:
+ if (net->is_p2p_) continue;
+ case IFA_LOCAL:
char ipaddr[INET6_ADDRSTRLEN];
if (!is_del_event) {
net->ipaddr_ = inet_ntop(ifa->ifa_family, RTA_DATA(ifa_rta), ipaddr, sizeof(ipaddr));
@@ -570,7 +576,6 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
}
net->dp.emit();
break;
- }
}
}
break;
diff --git a/src/modules/pulseaudio.cpp b/src/modules/pulseaudio.cpp
index c7979976..d35e2983 100644
--- a/src/modules/pulseaudio.cpp
+++ b/src/modules/pulseaudio.cpp
@@ -81,13 +81,6 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
if (dir == SCROLL_DIR::NONE) {
return true;
}
- if (config_["reverse-scrolling"].asInt() == 1) {
- if (dir == SCROLL_DIR::UP) {
- dir = SCROLL_DIR::DOWN;
- } else if (dir == SCROLL_DIR::DOWN) {
- dir = SCROLL_DIR::UP;
- }
- }
double volume_tick = static_cast(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
@@ -279,7 +272,12 @@ auto waybar::modules::Pulseaudio::update() -> void {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
}
- format = config_[format_name].isString() ? config_[format_name].asString() : format;
+ auto state = getState(volume_, true);
+ if (!state.empty() && config_[format_name + "-" + state].isString()) {
+ format = config_[format_name + "-" + state].asString();
+ } else if (config_[format_name].isString()) {
+ format = config_[format_name].asString();
+ }
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
@@ -294,9 +292,9 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_source = config_["format-source"].asString();
}
}
- format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
+ format_source = fmt::format(fmt::runtime(format_source), fmt::arg("volume", source_volume_));
auto text = fmt::format(
- format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
+ fmt::runtime(format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
if (text.empty()) {
@@ -305,7 +303,6 @@ auto waybar::modules::Pulseaudio::update() -> void {
label_.set_markup(text);
label_.show();
}
- getState(volume_);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
@@ -313,7 +310,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
- tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
+ fmt::runtime(tooltip_format), fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
diff --git a/src/modules/river/layout.cpp b/src/modules/river/layout.cpp
new file mode 100644
index 00000000..e938400f
--- /dev/null
+++ b/src/modules/river/layout.cpp
@@ -0,0 +1,174 @@
+#include "modules/river/layout.hpp"
+
+#include
+#include
+
+#include "client.hpp"
+
+namespace waybar::modules::river {
+
+static void listen_focused_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
+ uint32_t tags) {
+ // Intentionally empty
+}
+
+static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
+ struct wl_array *tags) {
+ // Intentionally empty
+}
+
+static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
+ uint32_t tags) {
+ // Intentionally empty
+}
+
+static void listen_layout_name(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
+ const char *layout) {
+ static_cast(data)->handle_name(layout);
+}
+
+static void listen_layout_name_clear(void *data,
+ struct zriver_output_status_v1 *zriver_output_status_v1) {
+ static_cast(data)->handle_clear();
+}
+
+static void listen_focused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
+ struct wl_output *output) {
+ static_cast(data)->handle_focused_output(output);
+}
+
+static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
+ struct wl_output *output) {
+ static_cast(data)->handle_unfocused_output(output);
+}
+
+static void listen_focused_view(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
+ const char *title) {
+ // Intentionally empty
+}
+
+static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
+ const char *mode) {
+ // Intentionally empty
+}
+
+static const zriver_output_status_v1_listener output_status_listener_impl{
+ .focused_tags = listen_focused_tags,
+ .view_tags = listen_view_tags,
+ .urgent_tags = listen_urgent_tags,
+ .layout_name = listen_layout_name,
+ .layout_name_clear = listen_layout_name_clear,
+};
+
+static const zriver_seat_status_v1_listener seat_status_listener_impl{
+ .focused_output = listen_focused_output,
+ .unfocused_output = listen_unfocused_output,
+ .focused_view = listen_focused_view,
+ .mode = listen_mode,
+};
+
+static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
+ const char *interface, uint32_t version) {
+ if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
+ version = std::min(version, 4);
+
+ // implies ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_CLEAR_SINCE_VERSION
+ if (version < ZRIVER_OUTPUT_STATUS_V1_LAYOUT_NAME_SINCE_VERSION) {
+ spdlog::error(
+ "river server does not support the \"layout_name\" and \"layout_clear\" events; the "
+ "module will be disabled" +
+ std::to_string(version));
+ return;
+ }
+ static_cast(data)->status_manager_ = static_cast(
+ wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
+ }
+
+ if (std::strcmp(interface, wl_seat_interface.name) == 0) {
+ version = std::min