Compare commits

...

46 Commits

Author SHA1 Message Date
Alex
dd9e3d0172
Merge pull request #1871 from Narice/feat/nix-flake-support 2023-01-24 09:34:03 +01:00
Alex
a9613892bb
Rename .envrc to .envrc.sample 2023-01-24 09:33:38 +01:00
Alex
e7af29ac1e
Merge pull request #1971 from LukashonakV/YearCalendar 2023-01-23 17:07:21 +01:00
Viktar Lukashonak
3b28af8b51
Merge remote-tracking branch 'origin/master' into YearCalendar
Signed-off-by: Viktar Lukashonak <myxabeer@gmail.com>
2023-01-23 18:49:45 +03:00
Viktar Lukashonak
ed31b20c26
Merge branch 'master' into YearCalendar
Signed-off-by: Viktar Lukashonak <myxabeer@gmail.com>
2023-01-23 18:42:32 +03:00
Alex
fea7ec9133
Merge pull request #1941 from eneshecan/master
Fixes https://github.com/Alexays/Waybar/issues/1940
2023-01-23 10:24:44 +01:00
Enes Hecan
3c8ca009ff Sanitize hyprland language string only instead of the whole format. Fixes #1940 2023-01-23 09:50:40 +01:00
Alex
f4cfafd238 fix: lint 2023-01-23 09:25:02 +01:00
Alex
5182fadede
Merge pull request #1944 from PolpOnline/master 2023-01-23 09:22:27 +01:00
Alex
18e0e0c7c1
Merge pull request #1964 from tactikauan/master 2023-01-23 09:20:46 +01:00
Alex
69b95e6d71
Merge pull request #1959 from alebastr/gcc-13 2023-01-23 09:18:13 +01:00
Alex
5e2895ccb3
Merge pull request #1966 from asas1asas200/zeng-doc-image 2023-01-22 13:39:39 +01:00
asas1asas200
0ca1c3957a docs(image): add image doc in meson and fix title 2023-01-22 20:16:46 +08:00
Kauan Decarli
de77787b60 Allow any module to implement signal handling 2023-01-21 21:57:28 -03:00
Aleksei Bavshin
51b6c22cab
ci: add glibc locales for date formatting tests.
Add some missing dependencies for Fedora.
2023-01-21 02:18:46 -08:00
Aleksei Bavshin
01cee153a4
ci: try to build with cpp_std=c++20
Add an extra job to build with `-std=c++20` on Fedora.
Update actions/checkout to v3.
2023-01-21 02:18:45 -08:00
Aleksei Bavshin
93e340a081
feat(clock): support chrono Time Zone extensions.
Use chrono Calendars and Time Zones (P0355R7, P1466R3) when available
instead of the `date` library.
Verified with a patched build of a recent GCC 13 snapshot.
2023-01-21 02:18:44 -08:00
Aleksei Bavshin
6225db0a48
test: refactor date formatter tests.
- Add tests for global locale.
- Warn about missing locales.
- Downgrade REQUIRE to CHECK.
- Skip tests if localized formatting does not work as expected.
2023-01-21 02:18:43 -08:00
Aleksei Bavshin
ea17a66dfc
fix: compilation errors with cpp_std=c++20
There were two main issues with fmtlib and C++20 mode:

 - `fmt::format` defaults to compile-time argument checking and requires
   using `fmt::runtime(format_string)` to bypass that.
 - `std::format` implementation introduces conflicting declarations and
   we have to specify the namespace for all `format`/`format_to` calls.
2023-01-20 22:50:02 -08:00
Aleksei Bavshin
67efe1af89
refactor(clock): remove struct waybar_time
The structure was used to pass the locale instance to the date
formatter. All the supported versions of `fmt` are passing the locale
parameter via `FormatContext.locale()` so we can remove the struct and
simplify the code.

While we at it, drop `date::make_zoned` in favor of CTAD on a
`date::zoned_time` constructor.
2023-01-20 22:50:01 -08:00
Aleksei Bavshin
ba498869c5
fix(clock): delete outdated warning 2023-01-20 22:50:00 -08:00
Aleksei Bavshin
43d52c59d9
test: fix build with Catch2 v2.x
Use smaller includes for Catch2 v3.
2023-01-20 22:49:59 -08:00
Aleksei Bavshin
ca9d237b00
fix(sway): add missing includes for GCC 13
See also: https://gcc.gnu.org/gcc-13/porting_to.html
2023-01-20 22:49:58 -08:00
Alex
bc07a82579
Merge pull request #1962 from maximbaz/hyprland-submap-init 2023-01-20 23:46:08 +01:00
Maxim Baz
6e9f21fc6b
hyprland/submap: run initial render on startup 2023-01-20 23:40:08 +01:00
Alex
eb9d2d9c5d
Merge pull request #1935 from multun/master 2023-01-17 09:40:45 +01:00
Alex
be3df41ae0
Merge pull request #1942 from smoak/fix-wireplumber-crashes
Fixes https://github.com/Alexays/Waybar/issues/1907
2023-01-17 09:36:05 +01:00
Sasha Moak
4e8ccf36b5 fix(wireplumber): waybar crashes when default node changes
In order to fix the issue, the default node name is cached rather than
the default node id. This is due to ids being unstable. So now when the
object manager is installed (ie ready), the default node name is
retrieved and stored for later.

Now when the mixer changed signal is emitted, the id of the changed node
is used to get the node from the object manager. The nodes name is
grabbed off that node and compared against the default node name, if
they match the volume is updated. Some safeguarding has been added such
that if the node cannot be found off the object manager, it's ignored.

Additionally, the "changed" signal on the default nodes api is now
utilized to update the default node name if it has changed. This way if
the default node changes, the module will be updated with the correct
volume and node.nick.

This adds additional debug logging for helping diagnose wireplumber
issues.

This also adds the wireplumber man page entry to the main waybar
supported section.
2023-01-16 10:29:35 -08:00
PolpOnline
a4b1b0a211
modules/custom: Added percentage rounding 2023-01-13 22:39:59 +01:00
Alex
6c8e186586
Merge pull request #1939 from RobertMueller2/master 2023-01-13 15:31:50 +01:00
Rene D. Obermueller
544c6deb88 sway/window: fix manpage 2023-01-13 15:08:59 +01:00
Alex
e59b4e4c93
Merge pull request #1419 from RobertMueller2/sway-window-newstyles 2023-01-13 13:58:43 +01:00
Alex
d71a4569fe
Merge pull request #1937 from cyrinux/fix/battery-time-remaining
fix https://github.com/Alexays/Waybar/pull/1867#pullrequestreview-1246756355
2023-01-13 09:51:40 +01:00
Cyril LEVIS
120cba0f5e
fix: battery time remaining
time is reported in second and should be divided by 3600 and not 1000.
2023-01-13 09:44:39 +01:00
Rene D. Obermueller
328573332f sway-window, Issue 1399: new style classes
Provides CSS classes empty, floating, tabbed, tiled, solo, stacked and
app_id.

Adds offscreen-css bool option (default false), only effective when
"all-outputs" is true. This adds styles on outputs without focused
node, according to its focused workspaces window situation.

Adds an "offscreen-css-text" string option (default empty), only
effective when "all-outputs" and "offscreen-style" are set. This
is shown as a text on outputs without a focused node.

Adds a "show-focused-workspace" bool option (default false) to indicate
the workspace name if the whole workspace is focused when nodes are
also present. If not set, empty text is shown, but css classes
according to nodes in the workspace are still applied.

Limitation:
When the top level layout changes, there is no sway event so the
module cannot react. Perhaps in the future recurring polling can
be added to go around this limitation.
2023-01-12 05:53:59 +01:00
Victor "multun" Collod
df0fdce92b get_desktop_app_info: fix crash on failed DesktopAppInfo::create
Even though it makes little sense for this call to fail,
it sometimes randomly does, and takes down waybar with it.
2023-01-11 23:02:12 +01:00
Alex
f0bead34d4 chore: 0.9.17 2023-01-11 11:39:30 +01:00
Alex
d6bd440027 fix: lint 2023-01-09 15:48:31 +01:00
Alex
c692d7bf64
Merge pull request #1929 from julianschuler/monitor-reconnect-fix
Fixes https://github.com/Alexays/Waybar/issues/1783
2023-01-08 19:56:27 +01:00
Julian Schuler
2045aac5b0 Fix crash upon reconnecting monitor 2023-01-08 18:49:24 +01:00
Alex
a92223c316
Merge pull request #1926 from robertgzr/mpris-module 2023-01-07 09:23:25 +01:00
Robert Günzler
24d03d13ce
mpris: fix build errors
to address https://github.com/Alexays/Waybar/pull/1520#issuecomment-1374229080

Signed-off-by: Robert Günzler <r@gnzler.io>
2023-01-07 01:44:25 +01:00
Robert Günzler
b3b5d8f9ab
Activate ci for mpris module
Signed-off-by: Robert Günzler <r@gnzler.io>
2023-01-07 01:44:25 +01:00
Alex
86850f5c7a
Merge pull request #1520 from robertgzr/mpris-module 2023-01-06 22:35:24 +01:00
Robert Günzler
0bc5314e08
Add mpris module
Uses libplayerctl to use the MPRIS dbus protocol to query, listen and
control media players.

Signed-off-by: Robert Günzler <r@gnzler.io>
2023-01-06 20:55:31 +01:00
Narice
d5a86526bc dev: Added Nix Flake support
- Enables Nix users to get the git version of waybar
- Enables Nix users to develop waybar easily
- Adds a fully reproducible development environment
  - The user only has to install Nix, no other depencencies
  - Automatic dev env on directory entry through .envrc
2022-12-06 18:44:26 +01:00
73 changed files with 2064 additions and 594 deletions

1
.envrc.sample Normal file
View File

@ -0,0 +1 @@
use flake

View File

@ -9,7 +9,7 @@ jobs:
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
runs-on: macos-12 runs-on: macos-12
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0 uses: vmactions/freebsd-vm@v0
with: with:

View File

@ -6,7 +6,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: DoozyX/clang-format-lint-action@v0.13 - uses: DoozyX/clang-format-lint-action@v0.13
with: with:
source: '.' source: '.'

View File

@ -13,16 +13,20 @@ jobs:
- fedora - fedora
- opensuse - opensuse
- gentoo - gentoo
cpp_std: [c++17]
include:
- distro: fedora
cpp_std: c++20
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:
image: alexays/waybar:${{ matrix.distro }} image: alexays/waybar:${{ matrix.distro }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- name: configure - name: configure
run: meson -Dman-pages=enabled build run: meson -Dman-pages=enabled -Dcpp_std=${{matrix.cpp_std}} build
- name: build - name: build
run: ninja -C build run: ninja -C build
- name: test - name: test
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar run: meson test -C build --no-rebuild --verbose --suite waybar

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ packagecache
*.exe *.exe
*.out *.out
*.app *.app
/.direnv/

View File

@ -2,4 +2,4 @@
FROM alpine:latest 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

View File

@ -3,4 +3,5 @@
FROM archlinux:base-devel FROM archlinux:base-devel
RUN pacman -Syu --noconfirm && \ 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

View File

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

View File

@ -2,11 +2,33 @@
FROM fedora:latest FROM fedora:latest
RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \ RUN dnf install -y @c-development \
'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \ git-core glibc-langpack-en meson scdoc \
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \ 'pkgconfig(catch2)' \
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ 'pkgconfig(date)' \
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ 'pkgconfig(dbusmenu-gtk3-0.4)' \
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ 'pkgconfig(fmt)' \
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \ '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 dnf clean all -y

View File

@ -8,4 +8,4 @@ RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersa
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \ 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" 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 \ 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

View File

@ -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 addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \
zypper -n refresh && \ zypper -n refresh && \
zypper -n install -t pattern devel_C_C++ && \ zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel 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

94
flake.lock Normal file
View File

@ -0,0 +1,94 @@
{
"nodes": {
"devshell": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1667210711,
"narHash": "sha256-IoErjXZAkzYWHEpQqwu/DeRNJGFdR7X2OGbkhMqMrpw=",
"owner": "numtide",
"repo": "devshell",
"rev": "96a9dd12b8a447840cc246e17a47b81a4268bba7",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"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": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"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": 1670152712,
"narHash": "sha256-LJttwIvJqsZIj8u1LxVRv82vwUtkzVqQVi7Wb8gxPS4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "14ddeaebcbe9a25748221d1d7ecdf98e20e2325e",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devshell": "devshell",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
}
}
},
"root": "root",
"version": 7
}

65
flake.nix Normal file
View File

@ -0,0 +1,65 @@
{
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";
};
outputs = { self, flake-utils, devshell, nixpkgs }:
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: rec {
waybar = prev.callPackage ./nix/default.nix {
version = "0.9.16" + "+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
];
language.c.libraries = with pkgs; [
];
};
});
}

View File

@ -15,6 +15,7 @@ class AModule : public IModule {
bool enable_scroll = false); bool enable_scroll = false);
virtual ~AModule(); virtual ~AModule();
virtual auto update() -> void; virtual auto update() -> void;
virtual auto refresh(int) -> void{};
virtual operator Gtk::Widget &(); virtual operator Gtk::Widget &();
Glib::Dispatcher dp; Glib::Dispatcher dp;

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <json/json.h> #include <json/json.h>
#ifdef HAVE_LIBDATE #if defined(HAVE_CHRONO_TIMEZONES) || defined(HAVE_LIBDATE)
#include "modules/clock.hpp" #include "modules/clock.hpp"
#else #else
#include "modules/simpleclock.hpp" #include "modules/simpleclock.hpp"
@ -42,6 +42,9 @@
#ifdef HAVE_DBUSMENU #ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp" #include "modules/sni/tray.hpp"
#endif #endif
#ifdef HAVE_MPRIS
#include "modules/mpris/mpris.hpp"
#endif
#ifdef HAVE_LIBNL #ifdef HAVE_LIBNL
#include "modules/network.hpp" #include "modules/network.hpp"
#endif #endif

View File

@ -1,19 +1,25 @@
#pragma once #pragma once
#include <date/tz.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/date.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace waybar { namespace waybar::modules {
struct waybar_time;
namespace modules {
const std::string kCalendarPlaceholder = "calendar"; const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list"; const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
enum class WeeksSide {
LEFT,
RIGHT,
HIDDEN,
};
enum class CldMode {
MONTH,
YEAR
};
class Clock : public ALabel { class Clock : public ALabel {
public: public:
Clock(const std::string&, const Json::Value&); Clock(const std::string&, const Json::Value&);
@ -22,26 +28,37 @@ class Clock : public ALabel {
private: private:
util::SleeperThread thread_; util::SleeperThread thread_;
std::map<std::pair<uint, GdkEventType>, void (waybar::modules::Clock::*)()> eventMap_;
std::locale locale_; std::locale locale_;
std::vector<const date::time_zone*> time_zones_; std::vector<const date::time_zone*> time_zones_;
int current_time_zone_idx_; 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_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_; bool is_timezoned_list_in_tooltip_;
bool handleScroll(GdkEventScroll* e); bool handleScroll(GdkEventScroll* e);
bool handleToggle(GdkEventButton* const& e);
std::string fmt_str_weeks_;
std::string fmt_str_calendar_;
int fmt_weeks_left_pad_{0};
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; auto first_day_of_week() -> date::weekday;
const date::time_zone* current_timezone(); const date::time_zone* current_timezone();
bool is_timezone_fixed(); bool is_timezone_fixed();
auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string; auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string;
/*Calendar properties*/
WeeksSide cldWPos_{WeeksSide::HIDDEN};
std::map<int, std::string const> 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_{2}; // 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_{};
/*Calendar functions*/
auto get_calendar(const date::zoned_seconds& now,
const date::zoned_seconds& wtime) -> std::string;
void cldModeSwitch();
}; };
} // namespace modules } // namespace waybar::modules
} // namespace waybar

View File

@ -0,0 +1,68 @@
#pragma once
#include <iostream>
#include <optional>
#include <string>
#include "gtkmm/box.h"
#include "gtkmm/label.h"
extern "C" {
#include <playerctl/playerctl.h>
}
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules::mpris {
class Mpris : public AModule {
public:
Mpris(const std::string&, const Json::Value&);
~Mpris();
auto update() -> void;
bool handleToggle(GdkEventButton* const&);
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<std::string> artist;
std::optional<std::string> album;
std::optional<std::string> title;
std::optional<std::string> length; // as HH:MM:SS
};
auto getPlayerInfo() -> std::optional<PlayerInfo>;
auto getIcon(const Json::Value&, const std::string&) -> std::string;
Gtk::Box box_;
Gtk::Label label_;
// config
std::string format_;
std::string format_playing_;
std::string format_paused_;
std::string format_stopped_;
std::chrono::seconds interval_;
std::string player_;
std::vector<std::string> ignored_players_;
PlayerctlPlayerManager* manager;
PlayerctlPlayer* player;
std::string lastStatus;
std::string lastPlayer;
util::SleeperThread thread_;
};
} // namespace waybar::modules::mpris

View File

@ -8,6 +8,7 @@
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string>
#include "ipc.hpp" #include "ipc.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"

View File

@ -19,10 +19,11 @@ class Window : public AIconLabel, public sigc::trackable {
auto update() -> void; auto update() -> void;
private: private:
void setClass(std::string classname, bool enable);
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> getFocusedNode( std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
const Json::Value& nodes, std::string& output); getFocusedNode(const Json::Value& nodes, std::string& output);
void getTree(); void getTree();
void updateAppIconName(); void updateAppIconName();
void updateAppIcon(); void updateAppIcon();
@ -32,12 +33,14 @@ class Window : public AIconLabel, public sigc::trackable {
int windowId_; int windowId_;
std::string app_id_; std::string app_id_;
std::string app_class_; std::string app_class_;
std::string layout_;
std::string old_app_id_; std::string old_app_id_;
std::size_t app_nb_; std::size_t app_nb_;
std::string shell_; std::string shell_;
unsigned app_icon_size_{24}; unsigned app_icon_size_{24};
bool update_app_icon_{true}; bool update_app_icon_{true};
std::string app_icon_name_; std::string app_icon_name_;
int floating_count_;
util::JsonParser parser_; util::JsonParser parser_;
std::mutex mutex_; std::mutex mutex_;
Ipc ipc_; Ipc ipc_;

View File

@ -4,6 +4,7 @@
#include <gtkmm/button.h> #include <gtkmm/button.h>
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <string_view>
#include <unordered_map> #include <unordered_map>
#include "AModule.hpp" #include "AModule.hpp"
@ -21,7 +22,9 @@ class Workspaces : public AModule, public sigc::trackable {
auto update() -> void; auto update() -> void;
private: 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); static int convertWorkspaceNameToNum(std::string name);

View File

@ -20,15 +20,19 @@ class Wireplumber : public ALabel {
void loadRequiredApiModules(); void loadRequiredApiModules();
void prepare(); void prepare();
void activatePlugins(); void activatePlugins();
static void updateVolume(waybar::modules::Wireplumber* self); static void updateVolume(waybar::modules::Wireplumber* self, uint32_t id);
static void updateNodeName(waybar::modules::Wireplumber* self); static void updateNodeName(waybar::modules::Wireplumber* self, uint32_t id);
static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self);
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self); static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
static void onObjectManagerInstalled(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);
WpCore* wp_core_; WpCore* wp_core_;
GPtrArray* apis_; GPtrArray* apis_;
WpObjectManager* om_; WpObjectManager* om_;
WpPlugin* mixer_api_;
WpPlugin* def_nodes_api_;
gchar* default_node_name_;
uint32_t pending_plugins_; uint32_t pending_plugins_;
bool muted_; bool muted_;
double volume_; double volume_;

60
include/util/date.hpp Normal file
View File

@ -0,0 +1,60 @@
#pragma once
#include <fmt/format.h>
#if HAVE_CHRONO_TIMEZONES
#include <chrono>
#include <format>
/* Compatibility layer for <date/tz.h> on top of C++20 <chrono> */
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 <date/tz.h>
#endif
template <typename Duration, typename TimeZonePtr>
struct fmt::formatter<date::zoned_time<Duration, TimeZonePtr>> {
std::string_view specs;
template <typename ParseContext>
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 <typename FormatContext>
auto format(const date::zoned_time<Duration, TimeZonePtr>& ztime, FormatContext& ctx) {
if (ctx.locale()) {
const auto loc = ctx.locale().template get<std::locale>();
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));
}
};

View File

@ -66,9 +66,9 @@ struct formatter<pow_format> {
std::string string; std::string string;
switch (spec) { switch (spec) {
case '>': case '>':
return format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width); return fmt::format_to(ctx.out(), "{:>{}}", fmt::format("{}", s), max_width);
case '<': case '<':
return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); return fmt::format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
case '=': case '=':
format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}"; format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}";
break; break;
@ -77,8 +77,8 @@ struct formatter<pow_format> {
format = "{coefficient:.1f}{prefix}{unit}"; format = "{coefficient:.1f}{prefix}{unit}";
break; break;
} }
return format_to( return fmt::format_to(
ctx.out(), format, fmt::arg("coefficient", fraction), ctx.out(), fmt::runtime(format), fmt::arg("coefficient", fraction),
fmt::arg("number_width", number_width), fmt::arg("number_width", number_width),
fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")),
fmt::arg("unit", s.unit_), fmt::arg("unit", s.unit_),

View File

@ -1,39 +0,0 @@
#pragma once
#include <date/tz.h>
#include <fmt/format.h>
namespace waybar {
struct waybar_time {
std::locale locale;
date::zoned_seconds ztime;
};
} // namespace waybar
template <>
struct fmt::formatter<waybar::waybar_time> {
std::string_view specs;
template <typename ParseContext>
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 <typename FormatContext>
auto format(const waybar::waybar_time& t, FormatContext& ctx) {
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime));
}
};

View File

@ -1,4 +1,4 @@
waybar-custom(5) waybar-image(5)
# NAME # NAME

103
man/waybar-mpris.5.scd Normal file
View File

@ -0,0 +1,103 @@
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.
*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": "DEFAULT: {player_icon} {dynamic}",
"format-paused": "DEFAULT: {status_icon} <i>{dynamic}</i>",
"player-icons": {
"default": "▶",
"mpv": "🎵"
},
"status-icons": {
"paused": "⏸"
},
// "ignored-players": ["firefox"]
}
```
# STYLE
- *#mpris*
- *#mpris.${status}*
- *#mpris.${player}*

View File

@ -66,6 +66,25 @@ Addressed by *sway/window*
default: true ++ default: true ++
Option to disable tooltip on hover. 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*: ++ *rewrite*: ++
typeof: object ++ typeof: object ++
Rules to rewrite window title. See *rewrite rules*. Rules to rewrite window title. See *rewrite rules*.
@ -117,6 +136,10 @@ Invalid expressions (e.g., mismatched parentheses) are skipped.
# STYLE # STYLE
- *#window* - *#window*
- *window#waybar.empty* When no windows 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 window is in the workspace - *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.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace - *window#waybar.<app_id>* Where *app_id* is the app_id or *instance* name like (*chromium*) of the only window in the workspace

View File

@ -263,9 +263,11 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-custom(5)* - *waybar-custom(5)*
- *waybar-disk(5)* - *waybar-disk(5)*
- *waybar-idle-inhibitor(5)* - *waybar-idle-inhibitor(5)*
- *waybar-image(5)*
- *waybar-keyboard-state(5)* - *waybar-keyboard-state(5)*
- *waybar-memory(5)* - *waybar-memory(5)*
- *waybar-mpd(5)* - *waybar-mpd(5)*
- *waybar-mpris(5)*
- *waybar-network(5)* - *waybar-network(5)*
- *waybar-pulseaudio(5)* - *waybar-pulseaudio(5)*
- *waybar-river-mode(5)* - *waybar-river-mode(5)*
@ -276,6 +278,7 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-sway-scratchpad(5)* - *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)* - *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)* - *waybar-sway-workspaces(5)*
- *waybar-wireplumber(5)*
- *waybar-wlr-taskbar(5)* - *waybar-wlr-taskbar(5)*
- *waybar-wlr-workspaces(5)* - *waybar-wlr-workspaces(5)*
- *waybar-temperature(5)* - *waybar-temperature(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.9.16', version: '0.9.17',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.49.0', meson_version: '>= 0.49.0',
default_options : [ default_options : [
@ -86,7 +86,10 @@ wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) 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']) jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])
sigcpp = dependency('sigc++-2.0') sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false) 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')) libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-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')) 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')) libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev')) libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev')) libevdev = dependency('libevdev', required: get_option('libevdev'))
@ -119,11 +123,18 @@ gtk_layer_shell = dependency('gtk-layer-shell-0',
required: get_option('gtk-layer-shell'), required: get_option('gtk-layer-shell'),
fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep']) fallback : ['gtk-layer-shell', 'gtk_layer_shell_dep'])
systemd = dependency('systemd', required: get_option('systemd')) systemd = dependency('systemd', required: get_option('systemd'))
tz_dep = dependency('date',
cpp_lib_chrono = compiler.compute_int('__cpp_lib_chrono', prefix : '#include <chrono>')
have_chrono_timezones = cpp_lib_chrono >= 201907
if have_chrono_timezones
tz_dep = declare_dependency()
else
tz_dep = dependency('date',
required: false, required: false,
default_options : [ 'use_system_tzdb=true' ], default_options : [ 'use_system_tzdb=true' ],
modules : [ 'date::date', 'date::date-tz' ], modules : [ 'date::date', 'date::date-tz' ],
fallback: [ 'date', 'tz_dep' ]) fallback: [ 'date', 'tz_dep' ])
endif
prefix = get_option('prefix') prefix = get_option('prefix')
sysconfdir = get_option('sysconfdir') sysconfdir = get_option('sysconfdir')
@ -239,6 +250,11 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable
src_files += 'src/modules/upower/upower_tooltip.cpp' src_files += 'src/modules/upower/upower_tooltip.cpp'
endif 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() if libpulse.found()
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
src_files += 'src/modules/pulseaudio.cpp' src_files += 'src/modules/pulseaudio.cpp'
@ -303,7 +319,10 @@ if get_option('rfkill').enabled() and is_linux
) )
endif 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') add_project_arguments('-DHAVE_LIBDATE', language: 'cpp')
src_files += 'src/modules/clock.cpp' src_files += 'src/modules/clock.cpp'
else else
@ -335,6 +354,7 @@ executable(
libnl, libnl,
libnlgen, libnlgen,
upower_glib, upower_glib,
playerctl,
libpulse, libpulse,
libjack, libjack,
libwireplumber, libwireplumber,
@ -385,9 +405,11 @@ if scdoc.found()
'waybar-disk.5.scd', 'waybar-disk.5.scd',
'waybar-gamemode.5.scd', 'waybar-gamemode.5.scd',
'waybar-idle-inhibitor.5.scd', 'waybar-idle-inhibitor.5.scd',
'waybar-image.5.scd',
'waybar-keyboard-state.5.scd', 'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd', 'waybar-memory.5.scd',
'waybar-mpd.5.scd', 'waybar-mpd.5.scd',
'waybar-mpris.5.scd',
'waybar-network.5.scd', 'waybar-network.5.scd',
'waybar-pulseaudio.5.scd', 'waybar-pulseaudio.5.scd',
'waybar-river-mode.5.scd', 'waybar-river-mode.5.scd',
@ -436,7 +458,7 @@ endif
catch2 = dependency( catch2 = dependency(
'catch2', 'catch2',
version: '>=3.0.0', version: '>=2.0.0',
fallback: ['catch2', 'catch2_dep'], fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'), required: get_option('tests'),
) )

View File

@ -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('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('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower') 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('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')

139
nix/default.nix Normal file
View File

@ -0,0 +1,139 @@
{ lib
, stdenv
, fetchFromGitHub
, meson
, pkg-config
, ninja
, wrapGAppsHook
, wayland
, wlroots
, gtkmm3
, libsigcxx
, jsoncpp
, scdoc
, spdlog
, gtk-layer-shell
, howard-hinnant-date
, libinotify-kqueue
, libxkbcommon
, evdevSupport ? true
, libevdev
, inputSupport ? true
, libinput
, jackSupport ? true
, libjack2
, mpdSupport ? true
, libmpdclient
, nlSupport ? true
, libnl
, pulseSupport ? true
, libpulseaudio
, rfkillSupport ? true
, runTests ? true
, catch2_3
, sndioSupport ? true
, sndio
, swaySupport ? true
, sway
, traySupport ? true
, libdbusmenu-gtk3
, udevSupport ? true
, udev
, upowerSupport ? true
, upower
, wireplumberSupport ? true
, wireplumber
, withMediaPlayer ? false
, glib
, gobject-introspection
, python3
, playerctl
, version
}:
stdenv.mkDerivation rec {
pname = "waybar";
inherit version;
# version = "0.9.16";
src = lib.cleanSourceWith {
filter = name: type:
let
baseName = baseNameOf (toString name);
in
! (
lib.hasSuffix ".nix" baseName
);
src = lib.cleanSource ../.;
};
nativeBuildInputs = [
meson
ninja
pkg-config
scdoc
wrapGAppsHook
] ++ lib.optional withMediaPlayer gobject-introspection;
propagatedBuildInputs = lib.optionals withMediaPlayer [
glib
playerctl
python3.pkgs.pygobject3
];
strictDeps = false;
buildInputs = with lib;
[ wayland wlroots gtkmm3 libsigcxx jsoncpp spdlog gtk-layer-shell howard-hinnant-date libxkbcommon ]
++ optional (!stdenv.isLinux) libinotify-kqueue
++ optional evdevSupport libevdev
++ optional inputSupport libinput
++ optional jackSupport libjack2
++ optional mpdSupport libmpdclient
++ optional nlSupport libnl
++ optional pulseSupport libpulseaudio
++ optional sndioSupport sndio
++ optional swaySupport sway
++ optional traySupport libdbusmenu-gtk3
++ optional udevSupport udev
++ optional upowerSupport upower
++ optional wireplumberSupport wireplumber;
checkInputs = [ catch2_3 ];
doCheck = runTests;
mesonFlags = (lib.mapAttrsToList
(option: enable: "-D${option}=${if enable then "enabled" else "disabled"}")
{
dbusmenu-gtk = traySupport;
jack = jackSupport;
libinput = inputSupport;
libnl = nlSupport;
libudev = udevSupport;
mpd = mpdSupport;
pulseaudio = pulseSupport;
rfkill = rfkillSupport;
sndio = sndioSupport;
tests = runTests;
upower_glib = upowerSupport;
wireplumber = wireplumberSupport;
}
) ++ [
"-Dsystemd=disabled"
"-Dgtk-layer-shell=enabled"
"-Dman-pages=enabled"
];
preFixup = lib.optionalString withMediaPlayer ''
cp $src/resources/custom_modules/mediaplayer.py $out/bin/waybar-mediaplayer.py
wrapProgram $out/bin/waybar-mediaplayer.py \
--prefix PYTHONPATH : "$PYTHONPATH:$out/${python3.sitePackages}"
'';
meta = with lib; {
description = "Highly customizable Wayland bar for Sway and Wlroots based compositors";
license = licenses.mit;
maintainers = with maintainers; [ FlorianFranzen minijackson synthetica lovesegfault ];
platforms = platforms.unix;
homepage = "https://github.com/alexays/waybar";
};
}

View File

@ -725,10 +725,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
void waybar::Bar::handleSignal(int signal) { void waybar::Bar::handleSignal(int signal) {
for (auto& module : modules_all_) { for (auto& module : modules_all_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); module->refresh(signal);
if (custom != nullptr) {
custom->refresh(signal);
}
} }
} }

View File

@ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::upower::UPower(id, config_[name]); return new waybar::modules::upower::UPower(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_MPRIS
if (ref == "mpris") {
return new waybar::modules::mpris::Mpris(id, config_[name]);
}
#endif
#ifdef HAVE_SWAY #ifdef HAVE_SWAY
if (ref == "sway/mode") { if (ref == "sway/mode") {
return new waybar::modules::sway::Mode(id, config_[name]); return new waybar::modules::sway::Mode(id, config_[name]);

View File

@ -48,13 +48,13 @@ struct UdevMonitorDeleter {
void check_eq(int rc, int expected, const char *message = "eq, rc was: ") { void check_eq(int rc, int expected, const char *message = "eq, rc was: ") {
if (rc != expected) { 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: ") { void check_neq(int rc, int bad_rc, const char *message = "neq, rc was: ") {
if (rc == bad_rc) { 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: ") { void check_gte(int rc, int gte, const char *message = "rc was: ") {
if (rc < gte) { if (rc < gte) {
throw std::runtime_error(fmt::format(message, rc)); throw std::runtime_error(fmt::format(fmt::runtime(message), rc));
} }
} }
@ -181,7 +181,8 @@ auto waybar::modules::Backlight::update() -> void {
event_box_.show(); event_box_.show();
const uint8_t percent = const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); 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)), label_.set_markup(fmt::format(fmt::runtime(format_),
fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent)))); fmt::arg("icon", getIcon(percent))));
getState(percent); getState(percent);
} else { } else {

View File

@ -505,12 +505,12 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
float time_remaining{0.0f}; float time_remaining{0.0f};
if (status == "Discharging" && time_to_empty_now_exists) { 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) { } else if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power; if (total_power != 0) time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && time_to_full_now_exists) { } else if (status == "Charging" && time_to_full_now_exists) {
if (time_to_full_now_exists && (time_to_full_now != 0)) if (time_to_full_now_exists && (time_to_full_now != 0))
time_remaining = -(float)time_to_full_now / 1000.0f; 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 // If we've turned positive it means the battery is past 100% and so just report that as no
// time remaining // time remaining
if (time_remaining > 0.0f) time_remaining = 0.0f; if (time_remaining > 0.0f) time_remaining = 0.0f;
@ -604,7 +604,7 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
format = config_["format-time"].asString(); format = config_["format-time"].asString();
} }
std::string zero_pad_minutes = fmt::format("{:02d}", minutes); 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)); fmt::arg("m", zero_pad_minutes));
} }
@ -644,7 +644,8 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) { } else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); 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("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted))); fmt::arg("time", time_remaining_formatted)));
} }
@ -665,9 +666,9 @@ auto waybar::modules::Battery::update() -> void {
} else { } else {
event_box_.show(); event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state}; auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power), label_.set_markup(fmt::format(
fmt::arg("icon", getIcon(capacity, icons)), fmt::runtime(format), fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("time", time_remaining_formatted))); fmt::arg("icon", getIcon(capacity, icons)), fmt::arg("time", time_remaining_formatted)));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -206,7 +206,8 @@ auto waybar::modules::Bluetooth::update() -> void {
state_ = state; state_ = state;
label_.set_markup(fmt::format( 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", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_address_type", cur_controller_.address_type),
fmt::arg("controller_alias", cur_controller_.alias), 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(); enumerate_format = config_["tooltip-format-enumerate-connected"].asString();
} }
ss << fmt::format( 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_address_type", dev.address_type),
fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); 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( 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("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address), fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type), fmt::arg("controller_address_type", cur_controller_.address_type),

View File

@ -10,14 +10,11 @@
#include <type_traits> #include <type_traits>
#include "util/ustring_clen.hpp" #include "util/ustring_clen.hpp"
#include "util/waybar_time.hpp"
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h> #include <langinfo.h>
#include <locale.h> #include <locale.h>
#endif #endif
using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0), current_time_zone_idx_(0),
@ -41,12 +38,6 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
time_zones_.push_back(nullptr); 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.");
}
// Check if a particular placeholder is present in the tooltip format, to know what to calculate // Check if a particular placeholder is present in the tooltip format, to know what to calculate
// on update. // on update.
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
@ -62,39 +53,98 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
} }
} }
// Calendar configuration
if (is_calendar_in_tooltip_) { if (is_calendar_in_tooltip_) {
if (config_["on-scroll"][kCalendarPlaceholder].isInt()) { if (config_[kCalendarPlaceholder]["weeks-pos"].isString()) {
calendar_shift_init_ = if (config_[kCalendarPlaceholder]["weeks-pos"].asString() == "left") {
date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()}; 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()) {
fmtMap_.insert(
{4, std::regex_replace(config_[kCalendarPlaceholder]["format"]["weeks"].asString(),
std::regex("\\{\\}"),
(first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}")});
if (cldWPos_ == WeeksSide::HIDDEN)
cldWnLen_ = 0;
else {
// tmp contains full length of the weeks including user characters
Glib::ustring tmp{std::regex_replace(fmtMap_[4], std::regex("</?[^>]+>|\\{.*\\}"), "")};
cldWnLen_ += (tmp.size() + 1);
cldMonColLen_ += cldWnLen_;
}
} else {
if (cldWPos_ != WeeksSide::HIDDEN)
fmtMap_.insert({4, (first_day_of_week() == date::Monday) ? "{:%W}" : "{:%U}"});
}
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()});
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<std::string, const CldMode&> 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_.add_events(Gdk::LEAVE_NOTIFY_MASK);
event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) { event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
calendar_shift_ = date::months{0}; cldCurrShift_ = date::months{0};
return false; return false;
}); });
} }
if (config_[kCalendarPlaceholder]["on-click-left"].isString()) {
if (config_[kCalendarPlaceholder]["on-click-left"].asString() == "mode")
eventMap_.insert({std::make_pair(1, GdkEventType::GDK_BUTTON_PRESS),
&waybar::modules::Clock::cldModeSwitch});
}
if (config_[kCalendarPlaceholder]["on-click-right"].isString()) {
if (config_[kCalendarPlaceholder]["on-click-right"].asString() == "mode")
eventMap_.insert({std::make_pair(3, GdkEventType::GDK_BUTTON_PRESS),
&waybar::modules::Clock::cldModeSwitch});
}
} }
if (config_["locale"].isString()) { if (config_["locale"].isString())
locale_ = std::locale(config_["locale"].asString()); locale_ = std::locale(config_["locale"].asString());
} else { else
locale_ = std::locale(""); locale_ = std::locale("");
}
if (config_["format-calendar-weeks"].isString()) {
fmt_str_weeks_ =
std::regex_replace(config_["format-calendar-weeks"].asString(), std::regex("\\{\\}"),
(first_day_of_week() == date::Monday) ? "{:%V}" : "{:%U}");
fmt_weeks_left_pad_ =
std::regex_replace(fmt_str_weeks_, std::regex("</?[^>]+>|\\{.*\\}"), "").length();
} else {
fmt_str_weeks_ = "";
}
if (config_["format-calendar"].isString()) {
fmt_str_calendar_ = config_["format-calendar"].asString();
} else {
fmt_str_calendar_ = "{}";
}
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
@ -116,24 +166,22 @@ bool waybar::modules::Clock::is_timezone_fixed() {
} }
auto waybar::modules::Clock::update() -> void { auto waybar::modules::Clock::update() -> void {
auto time_zone = current_timezone(); const auto* time_zone = current_timezone();
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_, auto ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now)};
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + calendar_shift_; auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + cldCurrShift_;
auto now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now)); auto now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now));
waybar_time shifted_wtime = { auto shifted_ztime = date::zoned_time{time_zone, date::floor<std::chrono::seconds>(now_shifted)};
locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now_shifted))};
std::string text = ""; std::string text{""};
if (!is_timezone_fixed()) { if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt // As date dep is not fully compatible, prefer fmt
tzset(); tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); 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 { } else {
text = fmt::format(format_, wtime); text = fmt::format(locale_, fmt::runtime(format_), ztime);
} }
label_.set_markup(text); label_.set_markup(text);
@ -142,13 +190,13 @@ auto waybar::modules::Clock::update() -> void {
std::string calendar_lines{""}; std::string calendar_lines{""};
std::string timezoned_time_lines{""}; std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) { if (is_calendar_in_tooltip_) {
calendar_lines = calendar_text(shifted_wtime); calendar_lines = get_calendar(ztime, shifted_ztime);
} }
if (is_timezoned_list_in_tooltip_) { if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now); timezoned_time_lines = timezones_text(&now);
} }
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
text = fmt::format(tooltip_format, shifted_wtime, text = fmt::format(locale_, fmt::runtime(tooltip_format), shifted_ztime,
fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text); label_.set_tooltip_markup(text);
@ -159,6 +207,21 @@ auto waybar::modules::Clock::update() -> void {
ALabel::update(); ALabel::update();
} }
bool waybar::modules::Clock::handleToggle(GdkEventButton* const& e) {
const std::map<std::pair<uint, GdkEventType>, void (waybar::modules::Clock::*)()>::const_iterator&
rec{eventMap_.find(std::pair(e->button, e->type))};
const auto callMethod{(rec != eventMap_.cend()) ? rec->second : nullptr};
if (callMethod) {
(this->*callMethod)();
} else
return AModule::handleToggle(e);
update();
return true;
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) { bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
// defer to user commands if set // defer to user commands if set
if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) { if (config_["on-scroll-up"].isString() || config_["on-scroll-down"].isString()) {
@ -168,11 +231,11 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
auto dir = AModule::getScrollDir(e); auto dir = AModule::getScrollDir(e);
// Shift calendar date // Shift calendar date
if (calendar_shift_init_.count() != 0) { if (cldShift_.count() != 0) {
if (dir == SCROLL_DIR::UP) if (dir == SCROLL_DIR::UP)
calendar_shift_ += calendar_shift_init_; cldCurrShift_ += ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
else else
calendar_shift_ -= calendar_shift_init_; cldCurrShift_ -= ((cldMode_ == CldMode::YEAR) ? 12 : 1) * cldShift_;
} else { } else {
// Change time zone // Change time zone
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
@ -196,126 +259,212 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
return true; return true;
} }
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { // The number of weeks in calendar month layout plus 1 more for calendar titles
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time()); unsigned cldRowsInMonth(date::year_month const ym, date::weekday const firstdow) {
const auto ymd{date::year_month_day{daypoint}}; using namespace date;
return static_cast<unsigned>(
if (calendar_cached_ymd_ == ymd) { ceil<weeks>((weekday{ym / 1} - firstdow) + ((ym / last).day() - day{0})).count()) +
return calendar_cached_text_; 2;
}
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 first_dow = first_day_of_week();
std::stringstream os;
enum class WeeksSide {
LEFT,
RIGHT,
HIDDEN,
};
WeeksSide weeks_pos = WeeksSide::HIDDEN;
if (config_["calendar-weeks-pos"].isString()) {
if (config_["calendar-weeks-pos"].asString() == "left") {
weeks_pos = WeeksSide::LEFT;
// Add paddings before the header
os << std::string(3 + fmt_weeks_left_pad_, ' ');
} else if (config_["calendar-weeks-pos"].asString() == "right") {
weeks_pos = WeeksSide::RIGHT;
}
}
weekdays_header(first_dow, os);
// First week day prefixed with spaces if needed.
date::sys_days print_wd{ym / 1};
auto wd{date::weekday{print_wd}};
auto empty_days = (wd - first_dow).count();
/* Print weeknumber on the left for the first row*/
if (weeks_pos == WeeksSide::LEFT) {
os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
}
if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' ');
}
const auto last_day = (ym / date::literals::last).day();
for (auto d{date::day{1}}; d <= last_day; ++d, ++wd) {
if (wd != first_dow) {
os << ' ';
} else if (unsigned(d) != 1) {
if (weeks_pos == WeeksSide::RIGHT) {
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
}
os << '\n';
print_wd = (ym / d);
if (weeks_pos == WeeksSide::LEFT) {
os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
}
}
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 << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else {
os << fmt::format(fmt_str_calendar_, date::format("%e", d));
}
/*Print weeks on the right when the endings with spaces*/
if (weeks_pos == WeeksSide::RIGHT && d == last_day) {
empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding());
if (empty_days > 0 && empty_days < 7) {
os << std::string(empty_days * 3, ' ');
}
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
}
}
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) auto cldGetWeekForLine(date::year_month const ym, date::weekday const firstdow, unsigned const line)
-> void { -> const date::year_month_weekday {
std::stringstream res; unsigned index = line - 2;
auto wd = first_week_day; auto sd = date::sys_days{ym / 1};
do { if (date::weekday{sd} == firstdow) ++index;
if (wd != first_week_day) { auto ymdw = ym / firstdow[index];
res << ' '; return ymdw;
}
auto getCalendarLine(date::year_month_day const currDate, date::year_month const ym,
unsigned const line, date::weekday const firstdow, int rowLen,
const std::locale* const locale_) -> std::string {
using namespace date::literals;
std::ostringstream res;
switch (line) {
case 0: {
// Output month and year title
Glib::ustring wd_ustring{Glib::ustring::format(
std::left, std::setw(rowLen), date::format(*locale_, "%B %Y", ym), std::right)};
res << wd_ustring;
break;
} }
Glib::ustring wd_ustring(date::format(locale_, "%a", wd)); case 1: {
auto clen = ustring_clen(wd_ustring); // Output weekday names title
auto wd_len = wd_ustring.length(); 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) { while (clen > 2) {
wd_ustring = wd_ustring.substr(0, wd_len - 1); wd_ustring = wd_ustring.substr(0, wd_len - 1);
wd_len--; --wd_len;
clen = ustring_clen(wd_ustring); clen = ustring_clen(wd_ustring);
} }
const std::string pad(2 - clen, ' '); const std::string pad(2 - clen, ' ');
res << pad << wd_ustring;
} while (++wd != first_week_day);
res << '\n';
if (config_["format-calendar-weekdays"].isString()) { if (wd != firstdow) res << ' ';
os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str());
} else res << pad << wd_ustring;
os << res.str(); } 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<unsigned>((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<unsigned>((firstdow - wd).count()) * 3, ' ');
} else // Otherwise not a valid week, output a blank row
res << std::string(rowLen, ' ');
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<date::days>(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 firstdow = first_day_of_week();
const auto maxRows{12 / cldMonCols_};
std::ostringstream os;
std::ostringstream tmp;
// get currdate
daypoint = date::floor<date::days>(now.get_local_time());
const auto currDate{date::year_month_day{daypoint}};
if (cldMode_ == CldMode::YEAR) {
if (y / date::month{1} / 1 == cldYearShift_)
return cldYearCached_;
else
cldYearShift_ = y / date::month{1} / 1;
}
if (cldMode_ == CldMode::MONTH) {
if (ym == cldMonShift_)
return cldMonCached_;
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<unsigned>(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 && cldWnLen_ > 0) os << std::string(cldWnLen_, ' ');
if (line > 1 && line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
os << fmt::format(fmt::runtime(fmtMap_[4]),
(line == 2)
? date::sys_days{ymTmp / 1}
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)})
<< ' ';
}
os << getCalendarLine(currDate, ymTmp, line, firstdow, cldMonColLen_, &locale_);
// Week numbers on the right
if (cldWPos_ == WeeksSide ::RIGHT && line > 0) {
if (line == 1 && cldWnLen_ > 0) os << std::string(cldWnLen_, ' ');
if (line > 1 && line < ml[static_cast<unsigned>(ymTmp.month()) - 1u])
os << ' '
<< fmt::format(fmt::runtime(fmtMap_[4]),
(line == 2)
? date::sys_days{ymTmp / 1}
: date::sys_days{cldGetWeekForLine(ymTmp, firstdow, line)});
}
}
}
// 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();
}
void waybar::modules::Clock::cldModeSwitch() {
cldMode_ = (cldMode_ == CldMode::YEAR) ? CldMode::MONTH : CldMode::YEAR;
} }
auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now) auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_point* now)
@ -324,7 +473,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
return ""; return "";
} }
std::stringstream os; std::stringstream os;
waybar_time wtime;
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) { for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) { if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
continue; continue;
@ -333,8 +481,8 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
if (!timezone) { if (!timezone) {
timezone = date::current_zone(); timezone = date::current_zone();
} }
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))}; auto ztime = date::zoned_time{timezone, date::floor<std::chrono::seconds>(*now)};
os << fmt::format(format_, wtime) << '\n'; os << fmt::format(locale_, fmt::runtime(format_), ztime) << '\n';
} }
return os.str(); return os.str();
} }

View File

@ -126,7 +126,7 @@ auto waybar::modules::Custom::update() -> void {
} else { } else {
parseOutputRaw(); 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("icon", getIcon(percentage_, alt_)),
fmt::arg("percentage", percentage_)); fmt::arg("percentage", percentage_));
if (str.empty()) { if (str.empty()) {
@ -209,8 +209,8 @@ void waybar::modules::Custom::parseOutputJson() {
class_.push_back(c.asString()); class_.push_back(c.asString());
} }
} }
if (!parsed["percentage"].asString().empty() && parsed["percentage"].isUInt()) { if (!parsed["percentage"].asString().empty() && parsed["percentage"].isNumeric()) {
percentage_ = parsed["percentage"].asUInt(); percentage_ = (int)lround(parsed["percentage"].asFloat());
} else { } else {
percentage_ = 0; percentage_ = 0;
} }

View File

@ -58,11 +58,11 @@ auto waybar::modules::Disk::update() -> void {
event_box_.hide(); event_box_.hide();
} else { } else {
event_box_.show(); event_box_.show();
label_.set_markup( label_.set_markup(fmt::format(
fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), 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("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
fmt::arg("total", total), fmt::arg("path", path_))); fmt::arg("path", path_)));
} }
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -70,11 +70,11 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text( label_.set_tooltip_text(fmt::format(
fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free), 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("percentage_free", stats.f_bavail * 100 / stats.f_blocks), fmt::arg("used", used),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used), fmt::arg("percentage_used", percentage_used), fmt::arg("total", total),
fmt::arg("total", total), fmt::arg("path", path_))); fmt::arg("path", path_)));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -213,13 +213,13 @@ auto Gamemode::update() -> void {
// Tooltip // Tooltip
if (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); box_.set_tooltip_text(text);
} }
// Label format // Label format
std::string str = std::string str = fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::format(showAltText ? format_alt : format, fmt::arg("glyph", useIcon ? "" : glyph), fmt::arg("glyph", useIcon ? "" : glyph),
fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : "")); fmt::arg("count", gameCount > 0 ? std::to_string(gameCount) : ""));
label_.set_markup(str); label_.set_markup(str);

View File

@ -55,17 +55,17 @@ void Language::onEvent(const std::string& ev) {
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString()) if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore return; // ignore
layoutName = waybar::util::sanitize_string(layoutName);
const auto briefName = getShortFrom(layoutName); const auto briefName = getShortFrom(layoutName);
if (config_.isMember("format-" + briefName)) { if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName; const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString()); layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
} else { } else {
layoutName = fmt::format(format_, layoutName); layoutName = fmt::format(fmt::runtime(format_), layoutName);
} }
layoutName = waybar::util::sanitize_string(layoutName);
if (layoutName == layoutName_) return; if (layoutName == layoutName_) return;
layoutName_ = layoutName; layoutName_ = layoutName;
@ -87,18 +87,18 @@ void Language::initLanguage() {
searcher = searcher.substr(searcher.find("keymap:") + 8); searcher = searcher.substr(searcher.find("keymap:") + 8);
searcher = searcher.substr(0, searcher.find_first_of("\n\t")); searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
searcher = waybar::util::sanitize_string(searcher);
auto layoutName = std::string{}; auto layoutName = std::string{};
const auto briefName = getShortFrom(searcher); const auto briefName = getShortFrom(searcher);
if (config_.isMember("format-" + briefName)) { if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName; const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString()); layoutName = fmt::format(fmt::runtime(format_), config_[propName].asString());
} else { } else {
layoutName = fmt::format(format_, searcher); layoutName = fmt::format(fmt::runtime(format_), searcher);
} }
layoutName = waybar::util::sanitize_string(layoutName);
layoutName_ = layoutName; layoutName_ = layoutName;
spdlog::debug("hyprland language initLanguage found {}", layoutName_); spdlog::debug("hyprland language initLanguage found {}", layoutName_);

View File

@ -19,6 +19,7 @@ Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
// register for hyprland ipc // register for hyprland ipc
gIPC->registerForIPC("submap", this); gIPC->registerForIPC("submap", this);
dp.emit();
} }
Submap::~Submap() { Submap::~Submap() {
@ -33,7 +34,7 @@ auto Submap::update() -> void {
if (submap_.empty()) { if (submap_.empty()) {
event_box_.hide(); event_box_.hide();
} else { } else {
label_.set_markup(fmt::format(format_, submap_)); label_.set_markup(fmt::format(fmt::runtime(format_), submap_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(submap_); label_.set_tooltip_text(submap_);
} }

View File

@ -40,8 +40,8 @@ auto Window::update() -> void {
if (!format_.empty()) { if (!format_.empty()) {
label_.show(); label_.show();
label_.set_markup( label_.set_markup(fmt::format(fmt::runtime(format_),
fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"]))); waybar::util::rewriteTitle(lastView, config_["rewrite"])));
} else { } else {
label_.hide(); label_.hide();
} }

View File

@ -63,19 +63,13 @@ auto waybar::modules::IdleInhibitor::update() -> void {
} }
std::string status_text = status ? "activated" : "deactivated"; 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)))); fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_markup( auto config = config_[status ? "tooltip-format-activated" : "tooltip-format-deactivated"];
status ? fmt::format(config_["tooltip-format-activated"].isString() auto tooltip_format = config.isString() ? config.asString() : "{status}";
? config_["tooltip-format-activated"].asString() label_.set_tooltip_markup(fmt::format(fmt::runtime(tooltip_format),
: "{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("status", status_text),
fmt::arg("icon", getIcon(0, status_text)))); fmt::arg("icon", getIcon(0, status_text))));
} }

View File

@ -118,7 +118,7 @@ auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated"; std::string status_text = activated() ? "activated" : "deactivated";
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated"); 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)))); fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);

View File

@ -72,7 +72,7 @@ auto JACK::update() -> void {
} else } else
format = "{load}%"; 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("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_))); fmt::arg("xruns", xruns_)));
@ -81,9 +81,9 @@ auto JACK::update() -> void {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms"; std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString(); if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_), fmt::runtime(tooltip_format), fmt::arg("load", std::round(load_)),
fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("xruns", xruns_))); fmt::arg("latency", fmt::format("{:.2f}", latency)), fmt::arg("xruns", xruns_)));
} }
// Call parent update // Call parent update

View File

@ -278,7 +278,7 @@ auto waybar::modules::KeyboardState::update() -> void {
}; };
for (auto& label_state : label_states) { for (auto& label_state : label_states) {
std::string text; 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("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name)); fmt::arg("name", label_state.name));
label_state.label.set_markup(text); label_state.label.set_markup(text);

View File

@ -56,7 +56,8 @@ auto waybar::modules::Memory::update() -> void {
event_box_.show(); event_box_.show();
auto icons = std::vector<std::string>{state}; auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format( 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("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), 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()) { if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format( label_.set_tooltip_text(fmt::format(
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes), fmt::runtime(tooltip_format), used_ram_percentage,
fmt::arg("swapTotal", total_swap_gigabytes), fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes), fmt::arg("swapPercentage", used_swap_percentage), fmt::arg("used", used_ram_gigabytes),
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes), fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),

View File

@ -174,14 +174,14 @@ void waybar::modules::MPD::setLabel() {
try { try {
auto text = fmt::format( auto text = fmt::format(
format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()), fmt::runtime(format), fmt::arg("artist", artist.raw()),
fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("volume", volume),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon), fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("filename", filename)); fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename));
if (text.empty()) { if (text.empty()) {
label_.hide(); label_.hide();
} else { } else {
@ -198,7 +198,7 @@ void waybar::modules::MPD::setLabel() {
: "MPD (connected)"; : "MPD (connected)";
try { try {
auto tooltip_text = 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("albumArtist", album_artist.raw()), fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()), fmt::arg("date", date), fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),

394
src/modules/mpris/mpris.cpp Normal file
View File

@ -0,0 +1,394 @@
#include "modules/mpris/mpris.hpp"
#include <fmt/core.h>
#include <optional>
#include <sstream>
#include <string>
extern "C" {
#include <playerctl/playerctl.h>
}
#include <spdlog/spdlog.h>
namespace waybar::modules::mpris {
const std::string DEFAULT_FORMAT = "{player} ({status}): {dynamic}";
Mpris::Mpris(const std::string& id, const Json::Value& config)
: AModule(config, "mpris", id),
box_(Gtk::ORIENTATION_HORIZONTAL, 0),
label_(),
format_(DEFAULT_FORMAT),
interval_(0),
player_("playerctld"),
manager(),
player() {
box_.pack_start(label_);
box_.set_name(name_);
event_box_.add(box_);
event_box_.signal_button_press_event().connect(sigc::mem_fun(*this, &Mpris::handleToggle));
if (config_["format"].isString()) {
format_ = config_["format"].asString();
}
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_["interval"].isUInt()) {
interval_ = std::chrono::seconds(config_["interval"].asUInt());
}
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) {
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<PlayerctlPlayerName*>(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::getIcon(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 "";
}
auto Mpris::onPlayerNameAppeared(PlayerctlPlayerManager* manager, PlayerctlPlayerName* player_name,
gpointer data) -> void {
Mpris* mpris = static_cast<Mpris*>(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<Mpris*>(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<Mpris*>(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<Mpris*>(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<Mpris*>(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<Mpris*>(data);
if (!mpris) return;
spdlog::debug("mpris: player-metadata callback");
// update widget
mpris->dp.emit();
}
auto Mpris::getPlayerInfo() -> std::optional<PlayerInfo> {
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<PlayerctlPlayerName*>(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,
};
if (auto artist_ = playerctl_player_get_artist(player, &error)) {
spdlog::debug("mpris[{}]: artist = {}", info.name, artist_);
info.artist = Glib::Markup::escape_text(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 = Glib::Markup::escape_text(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 = Glib::Markup::escape_text(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<std::chrono::hours>(len);
auto len_m = std::chrono::duration_cast<std::chrono::minutes>(len - len_h);
auto len_s = std::chrono::duration_cast<std::chrono::seconds>(len - 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;
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 AModule::handleToggle(e);
}
playerctl_player_play_pause(player, &error);
break;
case 2: // middle-click
if (config_["on-middle-click"].isString()) {
return AModule::handleToggle(e);
}
playerctl_player_previous(player, &error);
break;
case 3: // right-click
if (config_["on-right-click"].isString()) {
return AModule::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 {
auto opt = getPlayerInfo();
if (!opt) {
event_box_.set_visible(false);
AModule::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);
// dynamic is the auto-formatted string containing a nice out-of-the-box
// format text
std::stringstream dynamic;
if (info.artist) dynamic << *info.artist << " - ";
if (info.album) dynamic << *info.album << " - ";
if (info.title) dynamic << *info.title;
if (info.length)
dynamic << " "
<< "<small>"
<< "[" << *info.length << "]"
<< "</small>";
// set css class for player status
if (!lastStatus.empty() && box_.get_style_context()->has_class(lastStatus)) {
box_.get_style_context()->remove_class(lastStatus);
}
if (!box_.get_style_context()->has_class(info.status_string)) {
box_.get_style_context()->add_class(info.status_string);
}
lastStatus = info.status_string;
// set css class for player name
if (!lastPlayer.empty() && box_.get_style_context()->has_class(lastPlayer)) {
box_.get_style_context()->remove_class(lastPlayer);
}
if (!box_.get_style_context()->has_class(info.name)) {
box_.get_style_context()->add_class(info.name);
}
lastPlayer = info.name;
auto formatstr = format_;
switch (info.status) {
case PLAYERCTL_PLAYBACK_STATUS_PLAYING:
if (!format_playing_.empty()) formatstr = format_playing_;
break;
case PLAYERCTL_PLAYBACK_STATUS_PAUSED:
if (!format_paused_.empty()) formatstr = format_paused_;
break;
case PLAYERCTL_PLAYBACK_STATUS_STOPPED:
if (!format_stopped_.empty()) formatstr = format_stopped_;
break;
}
auto label_format =
fmt::format(fmt::runtime(formatstr), fmt::arg("player", info.name),
fmt::arg("status", info.status_string), fmt::arg("artist", *info.artist),
fmt::arg("title", *info.title), fmt::arg("album", *info.album),
fmt::arg("length", *info.length), fmt::arg("dynamic", dynamic.str()),
fmt::arg("player_icon", getIcon(config_["player-icons"], info.name)),
fmt::arg("status_icon", getIcon(config_["status-icons"], info.status_string)));
label_.set_markup(label_format);
event_box_.set_visible(true);
// call parent update
AModule::update();
}
} // namespace waybar::modules::mpris

View File

@ -331,7 +331,7 @@ auto waybar::modules::Network::update() -> void {
getState(signal_strength_); getState(signal_strength_);
auto text = fmt::format( 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("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
@ -363,8 +363,8 @@ auto waybar::modules::Network::update() -> void {
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
auto tooltip_text = fmt::format( auto tooltip_text = fmt::format(
tooltip_format, fmt::arg("essid", essid_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::runtime(tooltip_format), fmt::arg("essid", essid_),
fmt::arg("signalStrength", signal_strength_), fmt::arg("signaldBm", signal_strength_dbm_), fmt::arg("signalStrength", signal_strength_),
fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_), fmt::arg("signalStrengthApp", signal_strength_app_), fmt::arg("ifname", ifname_),
fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_), fmt::arg("netmask", netmask_), fmt::arg("ipaddr", ipaddr_), fmt::arg("gwaddr", gwaddr_),
fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)), fmt::arg("cidr", cidr_), fmt::arg("frequency", fmt::format("{:.1f}", frequency_)),

View File

@ -294,9 +294,9 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_source = config_["format-source"].asString(); 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( 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("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))); fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
if (text.empty()) { if (text.empty()) {
@ -313,7 +313,7 @@ auto waybar::modules::Pulseaudio::update() -> void {
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format( 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("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon())))); fmt::arg("icon", getIcon(volume_, getPulseIcon()))));

View File

@ -103,7 +103,7 @@ void Mode::handle_mode(const char *mode) {
} }
label_.get_style_context()->add_class(mode); label_.get_style_context()->add_class(mode);
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw())); label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(mode).raw()));
label_.show(); label_.show();
} }

View File

@ -106,7 +106,7 @@ void Window::handle_focused_view(const char *title) {
label_.hide(); // hide empty labels or labels with empty format label_.hide(); // hide empty labels or labels with empty format
} else { } else {
label_.show(); label_.show();
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw())); label_.set_markup(fmt::format(fmt::runtime(format_), Glib::Markup::escape_text(title).raw()));
} }
ALabel::update(); ALabel::update();

View File

@ -110,7 +110,8 @@ auto Sndio::update() -> void {
label_.get_style_context()->remove_class("muted"); label_.get_style_context()->remove_class("muted");
} }
auto text = fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)); auto text =
fmt::format(fmt::runtime(format), fmt::arg("volume", vol), fmt::arg("raw_value", volume_));
if (text.empty()) { if (text.empty()) {
label_.hide(); label_.hide();
} else { } else {

View File

@ -2,6 +2,8 @@
#include <fcntl.h> #include <fcntl.h>
#include <stdexcept>
namespace waybar::modules::sway { namespace waybar::modules::sway {
Ipc::Ipc() { Ipc::Ipc() {

View File

@ -96,14 +96,14 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
auto Language::update() -> void { auto Language::update() -> void {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto display_layout = trim(fmt::format( auto display_layout = trim(fmt::format(
format_, fmt::arg("short", layout_.short_name), fmt::runtime(format_), fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name), fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag()))); fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
label_.set_markup(display_layout); label_.set_markup(display_layout);
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (tooltip_format_ != "") { if (tooltip_format_ != "") {
auto tooltip_display_layout = trim( auto tooltip_display_layout = trim(
fmt::format(tooltip_format_, fmt::arg("short", layout_.short_name), fmt::format(fmt::runtime(tooltip_format_), fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description), fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant), fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag()))); fmt::arg("flag", layout_.country_flag())));

View File

@ -42,7 +42,7 @@ auto Mode::update() -> void {
if (mode_.empty()) { if (mode_.empty()) {
event_box_.hide(); event_box_.hide();
} else { } else {
label_.set_markup(fmt::format(format_, mode_)); label_.set_markup(fmt::format(fmt::runtime(format_), mode_));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(mode_); label_.set_tooltip_text(mode_);
} }

View File

@ -32,7 +32,8 @@ auto Scratchpad::update() -> void {
if (count_ || show_empty_) { if (count_ || show_empty_) {
event_box_.show(); event_box_.show();
label_.set_markup( label_.set_markup(
fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())), fmt::format(fmt::runtime(format_),
fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
fmt::arg("count", count_))); fmt::arg("count", count_)));
if (tooltip_enabled_) { if (tooltip_enabled_) {
label_.set_tooltip_markup(tooltip_text_); label_.set_tooltip_markup(tooltip_text_);
@ -64,7 +65,7 @@ auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {
if (tooltip_enabled_) { if (tooltip_enabled_) {
tooltip_text_.clear(); tooltip_text_.clear();
for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) { for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) {
tooltip_text_.append(fmt::format(tooltip_format_ + '\n', tooltip_text_.append(fmt::format(fmt::runtime(tooltip_format_ + '\n'),
fmt::arg("app", window["app_id"].asString()), fmt::arg("app", window["app_id"].asString()),
fmt::arg("title", window["name"].asString()))); fmt::arg("title", window["name"].asString())));
} }

View File

@ -17,7 +17,7 @@
namespace waybar::modules::sway { namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) { : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
// Icon size // Icon size
if (config_["icon-size"].isUInt()) { if (config_["icon-size"].isUInt()) {
app_icon_size_ = config["icon-size"].asUInt(); app_icon_size_ = config["icon-size"].asUInt();
@ -35,6 +35,7 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
ipc_.handleEvent(); ipc_.handleEvent();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
spdlog::trace("Window::Window exception");
} }
}); });
} }
@ -46,12 +47,13 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : ""; auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) = std::tie(app_nb_, floating_count_, windowId_, window_, app_id_, app_class_, shell_, layout_) =
getFocusedNode(payload["nodes"], output); getFocusedNode(payload["nodes"], output);
updateAppIconName(); updateAppIconName();
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
spdlog::trace("Window::onCmd exception");
} }
} }
@ -156,29 +158,55 @@ void Window::updateAppIcon() {
} }
auto Window::update() -> void { auto Window::update() -> void {
if (!old_app_id_.empty()) { spdlog::trace("workspace layout {}, tiled count {}, floating count {}", layout_, app_nb_,
bar_.window.get_style_context()->remove_class(old_app_id_); floating_count_);
}
int mode = 0;
if (app_nb_ == 0) { if (app_nb_ == 0) {
bar_.window.get_style_context()->remove_class("solo"); if (floating_count_ == 0) {
if (!bar_.window.get_style_context()->has_class("empty")) { mode += 1;
bar_.window.get_style_context()->add_class("empty"); } else {
mode += 4;
} }
} else if (app_nb_ == 1) { } else if (app_nb_ == 1) {
bar_.window.get_style_context()->remove_class("empty"); mode += 2;
if (!bar_.window.get_style_context()->has_class("solo")) { } else {
bar_.window.get_style_context()->add_class("solo"); if (layout_ == "tabbed") {
mode += 8;
} else if (layout_ == "stacked") {
mode += 16;
} else {
mode += 32;
} }
if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) { if (!app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
bar_.window.get_style_context()->add_class(app_id_); bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_; old_app_id_ = app_id_;
} }
} else {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
} }
label_.set_markup(fmt::format(
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])), if (!old_app_id_.empty() && ((mode & 2) == 0 || old_app_id_ != app_id_) &&
bar_.window.get_style_context()->has_class(old_app_id_)) {
spdlog::trace("Removing app_id class: {}", old_app_id_);
bar_.window.get_style_context()->remove_class(old_app_id_);
old_app_id_ = "";
}
setClass("empty", ((mode & 1) > 0));
setClass("solo", ((mode & 2) > 0));
setClass("floating", ((mode & 4) > 0));
setClass("tabbed", ((mode & 8) > 0));
setClass("stacked", ((mode & 16) > 0));
setClass("tiled", ((mode & 32) > 0));
if ((mode & 2) > 0 && !app_id_.empty() && !bar_.window.get_style_context()->has_class(app_id_)) {
spdlog::trace("Adding app_id class: {}", app_id_);
bar_.window.get_style_context()->add_class(app_id_);
old_app_id_ = app_id_;
}
label_.set_markup(
fmt::format(fmt::runtime(format_),
fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_))); fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(window_); label_.set_tooltip_text(window_);
@ -190,71 +218,143 @@ auto Window::update() -> void {
AIconLabel::update(); AIconLabel::update();
} }
int leafNodesInWorkspace(const Json::Value& node) { void Window::setClass(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);
}
}
std::pair<int, int> leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"]; auto const& nodes = node["nodes"];
auto const& floating_nodes = node["floating_nodes"]; auto const& floating_nodes = node["floating_nodes"];
if (nodes.empty() && floating_nodes.empty()) { if (nodes.empty() && floating_nodes.empty()) {
if (node["type"] == "workspace") if (node["type"].asString() == "workspace")
return 0; return {0, 0};
else else if (node["type"].asString() == "floating_con") {
return 1; return {0, 1};
} else {
return {1, 0};
}
} }
int sum = 0; int sum = 0;
if (!nodes.empty()) { int floating_sum = 0;
for (auto const& node : nodes) sum += leafNodesInWorkspace(node); for (auto const& node : nodes) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
} }
if (!floating_nodes.empty()) { for (auto const& node : floating_nodes) {
for (auto const& node : floating_nodes) sum += leafNodesInWorkspace(node); std::pair all_leaf_nodes = leafNodesInWorkspace(node);
sum += all_leaf_nodes.first;
floating_sum += all_leaf_nodes.second;
} }
return sum; return {sum, floating_sum};
} }
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> gfnWithWorkspace( std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_, gfnWithWorkspace(const Json::Value& nodes, std::string& output, const Json::Value& config_,
Json::Value& parentWorkspace) { const Bar& bar_, Json::Value& parentWorkspace,
const Json::Value& immediateParent) {
for (auto const& node : nodes) { for (auto const& node : nodes) {
if (node["output"].isString()) { if (node["type"].asString() == "output") {
output = node["output"].asString(); if ((!config_["all-outputs"].asBool() || config_["offscreen-css"].asBool()) &&
(node["name"].asString() != bar_.output->name)) {
continue;
} }
output = node["name"].asString();
} else if (node["type"].asString() == "workspace") {
// needs to be a string comparison, because filterWorkspace is the current_workspace
if (node["name"].asString() != immediateParent["current_workspace"].asString()) {
continue;
}
if (node["focused"].asBool()) {
std::pair all_leaf_nodes = leafNodesInWorkspace(node);
return {all_leaf_nodes.first,
all_leaf_nodes.second,
node["id"].asInt(),
(((all_leaf_nodes.first > 0) || (all_leaf_nodes.second > 0)) &&
(config_["show-focused-workspace-name"].asBool()))
? node["name"].asString()
: "",
"",
"",
"",
node["layout"].asString()};
}
parentWorkspace = node;
} else if ((node["type"].asString() == "con" || node["type"].asString() == "floating_con") &&
(node["focused"].asBool())) {
// found node // found node
if (node["focused"].asBool() && (node["type"] == "con" || node["type"] == "floating_con")) { spdlog::trace("actual output {}, output found {}, node (focused) found {}", bar_.output->name,
if ((!config_["all-outputs"].asBool() && output == bar_.output->name) || output, node["name"].asString());
config_["all-outputs"].asBool()) {
auto app_id = node["app_id"].isString() ? node["app_id"].asString() auto app_id = node["app_id"].isString() ? node["app_id"].asString()
: node["window_properties"]["instance"].asString(); : node["window_properties"]["instance"].asString();
const auto app_class = node["window_properties"]["class"].isString() const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString() ? node["window_properties"]["class"].asString()
: ""; : "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : ""; const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
int nb = node.size(); int nb = node.size();
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace); int floating_count = 0;
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()), std::string workspace_layout = "";
app_id, app_class, shell}; if (!parentWorkspace.isNull()) {
std::pair all_leaf_nodes = leafNodesInWorkspace(parentWorkspace);
nb = all_leaf_nodes.first;
floating_count = all_leaf_nodes.second;
workspace_layout = parentWorkspace["layout"].asString();
} }
return {nb,
floating_count,
node["id"].asInt(),
Glib::Markup::escape_text(node["name"].asString()),
app_id,
app_class,
shell,
workspace_layout};
} }
// iterate // iterate
if (node["type"] == "workspace") parentWorkspace = node; auto [nb, f, id, name, app_id, app_class, shell, workspace_layout] =
auto [nb, id, name, app_id, app_class, shell] = gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace, node);
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace); auto [nb2, f2, id2, name2, app_id2, app_class2, shell2, workspace_layout2] =
if (id > -1 && !name.empty()) { gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace, node);
return {nb, id, name, app_id, app_class, shell};
} // if ((id > 0 || ((id2 < 0 || name2.empty()) && id > -1)) && !name.empty()) {
// Search for floating node if ((id > 0) || (id2 < 0 && id > -1)) {
std::tie(nb, id, name, app_id, app_class, shell) = return {nb, f, id, name, app_id, app_class, shell, workspace_layout};
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace); } else if (id2 > 0 && !name2.empty()) {
if (id > -1 && !name.empty()) { return {nb2, f2, id2, name2, app_id2, app_class, shell2, workspace_layout2};
return {nb, id, name, app_id, app_class, shell};
} }
} }
return {0, -1, "", "", "", ""};
// this only comes into effect when no focused children are present
if (config_["all-outputs"].asBool() && config_["offscreen-css"].asBool() &&
immediateParent["type"].asString() == "workspace") {
std::pair all_leaf_nodes = leafNodesInWorkspace(immediateParent);
// using an empty string as default ensures that no window depending styles are set due to the
// checks above for !name.empty()
return {all_leaf_nodes.first,
all_leaf_nodes.second,
0,
(all_leaf_nodes.first > 0 || all_leaf_nodes.second > 0)
? config_["offscreen-css-text"].asString()
: "",
"",
"",
"",
immediateParent["layout"].asString()};
}
return {0, 0, -1, "", "", "", "", ""};
} }
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> std::tuple<std::size_t, int, int, std::string, std::string, std::string, std::string, std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) { Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0; Json::Value placeholder = Json::Value::null;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder); return gfnWithWorkspace(nodes, output, config_, bar_, placeholder, placeholder);
} }
void Window::getTree() { void Window::getTree() {
@ -262,6 +362,7 @@ void Window::getTree() {
ipc_.sendCmd(IPC_GET_TREE); ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
spdlog::trace("Window::getTree exception");
} }
} }

View File

@ -233,7 +233,7 @@ auto Workspaces::update() -> void {
std::string output = (*it)["name"].asString(); std::string output = (*it)["name"].asString();
if (config_["format"].isString()) { if (config_["format"].isString()) {
auto format = config_["format"].asString(); auto format = config_["format"].asString();
output = fmt::format(format, fmt::arg("icon", getIcon(output, *it)), output = fmt::format(fmt::runtime(format), fmt::arg("icon", getIcon(output, *it)),
fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)), fmt::arg("value", output), fmt::arg("name", trimWorkspaceName(output)),
fmt::arg("index", (*it)["num"].asString())); fmt::arg("index", (*it)["num"].asString()));
} }
@ -259,11 +259,9 @@ Gtk::Button &Workspaces::addButton(const Json::Value &node) {
try { try {
if (node["target_output"].isString()) { if (node["target_output"].isString()) {
ipc_.sendCmd(IPC_COMMAND, ipc_.sendCmd(IPC_COMMAND,
fmt::format(workspace_switch_cmd_ + "; move workspace to output \"{}\"; " + fmt::format(persistent_workspace_switch_cmd_, "--no-auto-back-and-forth",
workspace_switch_cmd_, node["name"].asString(), node["target_output"].asString(),
"--no-auto-back-and-forth", node["name"].asString(), "--no-auto-back-and-forth", node["name"].asString()));
node["target_output"].asString(), "--no-auto-back-and-forth",
node["name"].asString()));
} else { } else {
ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"", ipc_.sendCmd(IPC_COMMAND, fmt::format("workspace {} \"{}\"",
config_["disable-auto-back-and-forth"].asBool() config_["disable-auto-back-and-forth"].asBool()

View File

@ -55,7 +55,7 @@ auto waybar::modules::Temperature::update() -> void {
} }
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0; auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c), label_.set_markup(fmt::format(fmt::runtime(format), fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k), fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp)))); fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
@ -64,9 +64,9 @@ auto waybar::modules::Temperature::update() -> void {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c), label_.set_tooltip_text(fmt::format(
fmt::arg("temperatureF", temperature_f), fmt::runtime(tooltip_format), fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureK", temperature_k))); fmt::arg("temperatureF", temperature_f), fmt::arg("temperatureK", temperature_k)));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();

View File

@ -336,8 +336,8 @@ auto UPower::update() -> void {
break; break;
} }
std::string label_format = std::string label_format =
fmt::format(showAltText ? format_alt : format, fmt::arg("percentage", percentString), fmt::format(fmt::runtime(showAltText ? format_alt : format),
fmt::arg("time", time_format)); fmt::arg("percentage", percentString), fmt::arg("time", time_format));
// Only set the label text if it doesn't only contain spaces // Only set the label text if it doesn't only contain spaces
bool onlySpaces = true; bool onlySpaces = true;
for (auto& character : label_format) { for (auto& character : label_format) {

View File

@ -127,12 +127,12 @@ auto User::update() -> void {
auto startSystemTime = currentSystemTime - workSystemTimeSeconds; auto startSystemTime = currentSystemTime - workSystemTimeSeconds;
long workSystemDays = uptimeSeconds / 86400; long workSystemDays = uptimeSeconds / 86400;
auto label = fmt::format(ALabel::format_, fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)), auto label = fmt::format(
fmt::runtime(ALabel::format_), fmt::arg("up_H", fmt::format("{:%H}", startSystemTime)),
fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)), fmt::arg("up_M", fmt::format("{:%M}", startSystemTime)),
fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)), fmt::arg("up_d", fmt::format("{:%d}", startSystemTime)),
fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)), fmt::arg("up_m", fmt::format("{:%m}", startSystemTime)),
fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("up_Y", fmt::format("{:%Y}", startSystemTime)), fmt::arg("work_d", workSystemDays),
fmt::arg("work_d", workSystemDays),
fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)), fmt::arg("work_H", fmt::format("{:%H}", workSystemTimeSeconds)),
fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)), fmt::arg("work_M", fmt::format("{:%M}", workSystemTimeSeconds)),
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)), fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),

View File

@ -1,15 +1,22 @@
#include "modules/wireplumber.hpp" #include "modules/wireplumber.hpp"
#include <spdlog/spdlog.h>
bool isValidNodeId(uint32_t id) { return id > 0 && id < G_MAXUINT32; }
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config) waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
: ALabel(config, "wireplumber", id, "{volume}%"), : ALabel(config, "wireplumber", id, "{volume}%"),
wp_core_(nullptr), wp_core_(nullptr),
apis_(nullptr), apis_(nullptr),
om_(nullptr), om_(nullptr),
mixer_api_(nullptr),
def_nodes_api_(nullptr),
default_node_name_(nullptr),
pending_plugins_(0), pending_plugins_(0),
muted_(false), muted_(false),
volume_(0.0), volume_(0.0),
node_id_(0) { node_id_(0) {
wp_init(WP_INIT_ALL); wp_init(WP_INIT_PIPEWIRE);
wp_core_ = wp_core_new(NULL, NULL); wp_core_ = wp_core_new(NULL, NULL);
apis_ = g_ptr_array_new_with_free_func(g_object_unref); apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new(); om_ = wp_object_manager_new();
@ -18,10 +25,15 @@ waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Val
loadRequiredApiModules(); loadRequiredApiModules();
spdlog::debug("[{}]: connecting to pipewire...", this->name_);
if (!wp_core_connect(wp_core_)) { if (!wp_core_connect(wp_core_)) {
spdlog::error("[{}]: Could not connect to PipeWire", this->name_);
throw std::runtime_error("Could not connect to PipeWire\n"); throw std::runtime_error("Could not connect to PipeWire\n");
} }
spdlog::debug("[{}]: connected!", this->name_);
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this); g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
activatePlugins(); activatePlugins();
@ -33,33 +45,26 @@ waybar::modules::Wireplumber::~Wireplumber() {
g_clear_pointer(&apis_, g_ptr_array_unref); g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_); g_clear_object(&om_);
g_clear_object(&wp_core_); g_clear_object(&wp_core_);
g_clear_object(&mixer_api_);
g_clear_object(&def_nodes_api_);
g_free(&default_node_name_);
} }
uint32_t waybar::modules::Wireplumber::getDefaultNodeId(waybar::modules::Wireplumber* self) { void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self, uint32_t id) {
uint32_t id; spdlog::debug("[{}]: updating node name with node.id {}", self->name_, id);
g_autoptr(WpPlugin) def_nodes_api = wp_plugin_find(self->wp_core_, "default-nodes-api");
if (!def_nodes_api) { if (!isValidNodeId(id)) {
throw std::runtime_error("Default nodes API is not loaded\n"); spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node name update.", self->name_, id);
return;
} }
g_signal_emit_by_name(def_nodes_api, "get-default-node", "Audio/Sink", &id); auto proxy = static_cast<WpProxy*>(wp_object_manager_lookup(
self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
if (id <= 0 || id >= G_MAXUINT32) {
auto err = fmt::format("'{}' is not a valid ID (returned by default-nodes-api)\n", id);
throw std::runtime_error(err);
}
return id;
}
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self) {
auto proxy = static_cast<WpProxy*>(
wp_object_manager_lookup(self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY,
"bound-id", "=u", self->node_id_, NULL));
if (!proxy) { if (!proxy) {
throw std::runtime_error(fmt::format("Object '{}' not found\n", self->node_id_)); auto err = fmt::format("Object '{}' not found\n", id);
spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err);
} }
g_autoptr(WpProperties) properties = g_autoptr(WpProperties) properties =
@ -73,15 +78,24 @@ void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber*
auto description = wp_properties_get(properties, "node.description"); auto description = wp_properties_get(properties, "node.description");
self->node_name_ = nick ? nick : description; self->node_name_ = nick ? nick : description;
spdlog::debug("[{}]: Updating node name to: {}", self->name_, self->node_name_);
} }
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self) { void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: updating volume", self->name_);
double vol; double vol;
GVariant* variant = NULL; GVariant* variant = NULL;
g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api");
g_signal_emit_by_name(mixer_api, "get-volume", self->node_id_, &variant); if (!isValidNodeId(id)) {
spdlog::error("[{}]: '{}' is not a valid node ID. Ignoring volume update.", self->name_, id);
return;
}
g_signal_emit_by_name(self->mixer_api_, "get-volume", id, &variant);
if (!variant) { if (!variant) {
auto err = fmt::format("Node {} does not support volume\n", self->node_id_); auto err = fmt::format("Node {} does not support volume\n", id);
spdlog::error("[{}]: {}", self->name_, err);
throw std::runtime_error(err); throw std::runtime_error(err);
} }
@ -93,22 +107,121 @@ void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* se
self->dp.emit(); self->dp.emit();
} }
void waybar::modules::Wireplumber::onMixerChanged(waybar::modules::Wireplumber* self, uint32_t id) {
spdlog::debug("[{}]: (onMixerChanged) - id: {}", self->name_, id);
g_autoptr(WpNode) node = static_cast<WpNode*>(wp_object_manager_lookup(
self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL));
if (!node) {
spdlog::warn("[{}]: (onMixerChanged) - Object with id {} not found", self->name_, id);
return;
}
const gchar* name = wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
if (g_strcmp0(self->default_node_name_, name) != 0) {
spdlog::debug(
"[{}]: (onMixerChanged) - ignoring mixer update for node: id: {}, name: {} as it is not "
"the default node: {}",
self->name_, id, name, self->default_node_name_);
return;
}
spdlog::debug("[{}]: (onMixerChanged) - Need to update volume for node with id {} and name {}",
self->name_, id, name);
updateVolume(self, id);
}
void waybar::modules::Wireplumber::onDefaultNodesApiChanged(waybar::modules::Wireplumber* self) {
spdlog::debug("[{}]: (onDefaultNodesApiChanged)", self->name_);
uint32_t default_node_id;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id);
if (!isValidNodeId(default_node_id)) {
spdlog::warn("[{}]: '{}' is not a valid node ID. Ignoring node change.", self->name_,
default_node_id);
return;
}
g_autoptr(WpNode) node = static_cast<WpNode*>(
wp_object_manager_lookup(self->om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id",
"=u", default_node_id, NULL));
if (!node) {
spdlog::warn("[{}]: (onDefaultNodesApiChanged) - Object with id {} not found", self->name_,
default_node_id);
return;
}
const gchar* default_node_name =
wp_pipewire_object_get_property(WP_PIPEWIRE_OBJECT(node), "node.name");
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - got the following default node: Node(name: {}, id: {})",
self->name_, default_node_name, default_node_id);
if (g_strcmp0(self->default_node_name_, default_node_name) == 0) {
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node has not changed. Node(name: {}, id: {}). "
"Ignoring.",
self->name_, self->default_node_name_, default_node_id);
return;
}
spdlog::debug(
"[{}]: (onDefaultNodesApiChanged) - Default node changed to -> Node(name: {}, id: {})",
self->name_, default_node_name, default_node_id);
self->default_node_name_ = g_strdup(default_node_name);
updateVolume(self, default_node_id);
updateNodeName(self, default_node_id);
}
void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) { void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
self->node_id_ = spdlog::debug("[{}]: onObjectManagerInstalled", self->name_);
self->config_["node-id"].isInt() ? self->config_["node-id"].asInt() : getDefaultNodeId(self);
g_autoptr(WpPlugin) mixer_api = wp_plugin_find(self->wp_core_, "mixer-api"); self->def_nodes_api_ = wp_plugin_find(self->wp_core_, "default-nodes-api");
updateVolume(self); if (!self->def_nodes_api_) {
updateNodeName(self); spdlog::error("[{}]: default nodes api is not loaded.", self->name_);
g_signal_connect_swapped(mixer_api, "changed", (GCallback)updateVolume, self); throw std::runtime_error("Default nodes API is not loaded\n");
}
self->mixer_api_ = wp_plugin_find(self->wp_core_, "mixer-api");
if (!self->mixer_api_) {
spdlog::error("[{}]: mixer api is not loaded.", self->name_);
throw std::runtime_error("Mixer api is not loaded\n");
}
uint32_t default_node_id;
g_signal_emit_by_name(self->def_nodes_api_, "get-default-configured-node-name", "Audio/Sink",
&self->default_node_name_);
g_signal_emit_by_name(self->def_nodes_api_, "get-default-node", "Audio/Sink", &default_node_id);
if (self->default_node_name_) {
spdlog::debug("[{}]: (onObjectManagerInstalled) - default configured node name: {} and id: {}",
self->name_, self->default_node_name_, default_node_id);
}
updateVolume(self, default_node_id);
updateNodeName(self, default_node_id);
g_signal_connect_swapped(self->mixer_api_, "changed", (GCallback)onMixerChanged, self);
g_signal_connect_swapped(self->def_nodes_api_, "changed", (GCallback)onDefaultNodesApiChanged,
self);
} }
void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res, void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) { waybar::modules::Wireplumber* self) {
auto plugin_name = wp_plugin_get_name(WP_PLUGIN(p));
spdlog::debug("[{}]: onPluginActivated: {}", self->name_, plugin_name);
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
if (!wp_object_activate_finish(p, res, &error)) { if (!wp_object_activate_finish(p, res, &error)) {
spdlog::error("[{}]: error activating plugin: {}", self->name_, error->message);
throw std::runtime_error(error->message); throw std::runtime_error(error->message);
} }
@ -118,6 +231,7 @@ void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult*
} }
void waybar::modules::Wireplumber::activatePlugins() { void waybar::modules::Wireplumber::activatePlugins() {
spdlog::debug("[{}]: activating plugins", name_);
for (uint16_t i = 0; i < apis_->len; i++) { for (uint16_t i = 0; i < apis_->len; i++) {
WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i)); WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));
pending_plugins_++; pending_plugins_++;
@ -127,13 +241,13 @@ void waybar::modules::Wireplumber::activatePlugins() {
} }
void waybar::modules::Wireplumber::prepare() { void waybar::modules::Wireplumber::prepare() {
wp_object_manager_add_interest(om_, WP_TYPE_NODE, NULL); spdlog::debug("[{}]: preparing object manager", name_);
wp_object_manager_add_interest(om_, WP_TYPE_GLOBAL_PROXY, NULL); wp_object_manager_add_interest(om_, WP_TYPE_NODE, WP_CONSTRAINT_TYPE_PW_PROPERTY, "media.class",
wp_object_manager_request_object_features(om_, WP_TYPE_GLOBAL_PROXY, "=s", "Audio/Sink", NULL);
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
} }
void waybar::modules::Wireplumber::loadRequiredApiModules() { void waybar::modules::Wireplumber::loadRequiredApiModules() {
spdlog::debug("[{}]: loading required modules", name_);
g_autoptr(GError) error = NULL; g_autoptr(GError) error = NULL;
if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL, if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL,
@ -165,7 +279,7 @@ auto waybar::modules::Wireplumber::update() -> void {
label_.get_style_context()->remove_class("muted"); label_.get_style_context()->remove_class("muted");
} }
std::string markup = fmt::format(format, fmt::arg("node_name", node_name_), std::string markup = fmt::format(fmt::runtime(format), fmt::arg("node_name", node_name_),
fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))); fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)));
label_.set_markup(markup); label_.set_markup(markup);
@ -177,9 +291,9 @@ auto waybar::modules::Wireplumber::update() -> void {
} }
if (!tooltip_format.empty()) { if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_), label_.set_tooltip_text(
fmt::arg("volume", volume_), fmt::format(fmt::runtime(tooltip_format), fmt::arg("node_name", node_name_),
fmt::arg("icon", getIcon(volume_)))); fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_))));
} else { } else {
label_.set_tooltip_text(node_name_); label_.set_tooltip_text(node_name_);
} }

View File

@ -102,8 +102,11 @@ Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id
desktop_file = desktop_list[0][i]; desktop_file = desktop_list[0][i];
} else { } else {
auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]); auto tmp_info = Gio::DesktopAppInfo::create(desktop_list[0][i]);
auto startup_class = tmp_info->get_startup_wm_class(); if (!tmp_info)
// see https://github.com/Alexays/Waybar/issues/1446
continue;
auto startup_class = tmp_info->get_startup_wm_class();
if (startup_class == app_id) { if (startup_class == app_id) {
desktop_file = desktop_list[0][i]; desktop_file = desktop_list[0][i];
break; break;
@ -615,7 +618,8 @@ void Task::update() {
app_id = Glib::Markup::escape_text(app_id); app_id = Glib::Markup::escape_text(app_id);
} }
if (!format_before_.empty()) { if (!format_before_.empty()) {
auto txt = fmt::format(format_before_, fmt::arg("title", title), fmt::arg("name", name), auto txt =
fmt::format(fmt::runtime(format_before_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))); fmt::arg("short_state", state_string(true)));
if (markup) if (markup)
@ -625,7 +629,8 @@ void Task::update() {
text_before_.show(); text_before_.show();
} }
if (!format_after_.empty()) { if (!format_after_.empty()) {
auto txt = fmt::format(format_after_, fmt::arg("title", title), fmt::arg("name", name), auto txt =
fmt::format(fmt::runtime(format_after_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))); fmt::arg("short_state", state_string(true)));
if (markup) if (markup)
@ -636,7 +641,8 @@ void Task::update() {
} }
if (!format_tooltip_.empty()) { if (!format_tooltip_.empty()) {
auto txt = fmt::format(format_tooltip_, fmt::arg("title", title), fmt::arg("name", name), auto txt =
fmt::format(fmt::runtime(format_tooltip_), fmt::arg("title", title), fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("state", state_string()), fmt::arg("app_id", app_id), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true))); fmt::arg("short_state", state_string(true)));
if (markup) if (markup)

View File

@ -9,6 +9,7 @@
#include <stdexcept> #include <stdexcept>
#include <vector> #include <vector>
#include "client.hpp"
#include "gtkmm/widget.h" #include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp" #include "modules/wlr/workspace_manager_binding.hpp"
@ -166,8 +167,20 @@ WorkspaceManager::~WorkspaceManager() {
return; return;
} }
wl_display *display = Client::inst()->wl_display;
// Send `stop` request and wait for one roundtrip. This is not quite correct as
// the protocol encourages us to wait for the .finished event, but it should work
// with wlroots workspace manager implementation.
zext_workspace_manager_v1_stop(workspace_manager_);
wl_display_roundtrip(display);
// If the .finished handler is still not executed, destroy the workspace manager here.
if (workspace_manager_) {
spdlog::warn("Foreign toplevel manager destroyed before .finished event");
zext_workspace_manager_v1_destroy(workspace_manager_); zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr; workspace_manager_ = nullptr;
}
} }
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void { auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
@ -366,7 +379,7 @@ Workspace::~Workspace() {
} }
auto Workspace::update() -> void { auto Workspace::update() -> void {
label_.set_markup(fmt::format(format_, fmt::arg("name", name_), label_.set_markup(fmt::format(fmt::runtime(format_), fmt::arg("name", name_),
fmt::arg("icon", with_icon_ ? get_icon() : ""))); fmt::arg("icon", with_icon_ ? get_icon() : "")));
} }

View File

@ -2,7 +2,11 @@
#include <glibmm.h> #include <glibmm.h>
#include <catch2/catch_all.hpp> #if __has_include(<catch2/catch_test_macros.hpp>)
#include <catch2/catch_test_macros.hpp>
#else
#include <catch2/catch.hpp>
#endif
#include <thread> #include <thread>
#include <type_traits> #include <type_traits>

View File

@ -1,6 +1,10 @@
#include "config.hpp" #include "config.hpp"
#include <catch2/catch_all.hpp> #if __has_include(<catch2/catch_test_macros.hpp>)
#include <catch2/catch_test_macros.hpp>
#else
#include <catch2/catch.hpp>
#endif
TEST_CASE("Load simple config", "[config]") { TEST_CASE("Load simple config", "[config]") {
waybar::Config conf; waybar::Config conf;

162
test/date.cpp Normal file
View File

@ -0,0 +1,162 @@
#include "util/date.hpp"
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#if __has_include(<catch2/catch_test_macros.hpp>)
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_all.hpp>
#else
#include <catch2/catch.hpp>
#endif
#ifndef SKIP
#define SKIP(...) \
WARN(__VA_ARGS__); \
return
#endif
using namespace std::literals::chrono_literals;
/*
* Check that the date/time formatter with locale and timezone support is working as expected.
*/
const date::zoned_time<std::chrono::seconds> TEST_TIME = date::zoned_time{
"UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s};
/*
* Check if the date formatted with LC_TIME=en_US is within expectations.
*
* The check expects Glibc output style and will fail with FreeBSD (different implementation)
* or musl (no implementation).
*/
static const bool LC_TIME_is_sane = []() {
try {
std::stringstream ss;
ss.imbue(std::locale("en_US.UTF-8"));
time_t t = 1641211200;
std::tm tm = *std::gmtime(&t);
ss << std::put_time(&tm, "%x %X");
return ss.str() == "01/03/2022 12:00:00 PM";
} catch (std::exception &) {
return false;
}
}();
TEST_CASE("Format UTC time", "[clock][util]") {
const auto loc = std::locale("C");
const auto tm = TEST_TIME;
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
if (!LC_TIME_is_sane) {
SKIP("Locale support check failed, skip tests");
}
/* Test a few locales that are most likely to be present */
SECTION("US locale") {
try {
const auto loc = std::locale("en_US.UTF-8");
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
} catch (const std::runtime_error &) {
WARN("Locale en_US not found, skip tests");
}
}
SECTION("GB locale") {
try {
const auto loc = std::locale("en_GB.UTF-8");
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 13:04:05");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
} catch (const std::runtime_error &) {
WARN("Locale en_GB not found, skip tests");
}
}
SECTION("Global locale") {
try {
const auto loc = std::locale::global(std::locale("en_US.UTF-8"));
CHECK(fmt::format("{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
std::locale::global(loc);
} catch (const std::runtime_error &) {
WARN("Locale en_US not found, skip tests");
}
}
}
TEST_CASE("Format zoned time", "[clock][util]") {
const auto loc = std::locale("C");
const auto tm = date::zoned_time{"America/New_York", TEST_TIME};
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK(fmt::format(loc, "{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
if (!LC_TIME_is_sane) {
SKIP("Locale support check failed, skip tests");
}
/* Test a few locales that are most likely to be present */
SECTION("US locale") {
try {
const auto loc = std::locale("en_US.UTF-8");
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
} catch (const std::runtime_error &) {
WARN("Locale en_US not found, skip tests");
}
}
SECTION("GB locale") {
try {
const auto loc = std::locale("en_GB.UTF-8");
CHECK(fmt::format(loc, "{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format(loc, "{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05"));
CHECK(fmt::format(loc, "{:%x %X}", tm) == "03/01/22 08:04:05");
CHECK(fmt::format(loc, "{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
} catch (const std::runtime_error &) {
WARN("Locale en_GB not found, skip tests");
}
}
SECTION("Global locale") {
try {
const auto loc = std::locale::global(std::locale("en_US.UTF-8"));
CHECK(fmt::format("{}", tm).empty()); // no format specified
CHECK_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
CHECK(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
CHECK(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
std::locale::global(loc);
} catch (const std::runtime_error &) {
WARN("Locale en_US not found, skip tests");
}
}
}

View File

@ -3,8 +3,13 @@
#include <spdlog/sinks/stdout_sinks.h> #include <spdlog/sinks/stdout_sinks.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#if __has_include(<catch2/catch_all.hpp>)
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include <catch2/reporters/catch_reporter_tap.hpp> #include <catch2/reporters/catch_reporter_tap.hpp>
#else
#include <catch2/catch.hpp>
#include <catch2/catch_reporter_tap.hpp>
#endif
#include <memory> #include <memory>
int main(int argc, char* argv[]) { int main(int argc, char* argv[]) {
@ -13,10 +18,16 @@ int main(int argc, char* argv[]) {
session.applyCommandLine(argc, argv); session.applyCommandLine(argc, argv);
const auto logger = spdlog::default_logger(); const auto logger = spdlog::default_logger();
#if CATCH_VERSION_MAJOR >= 3
for (const auto& spec : session.config().getReporterSpecs()) { for (const auto& spec : session.config().getReporterSpecs()) {
if (spec.name() == "tap") { const auto& reporter_name = spec.name();
#else
{
const auto& reporter_name = session.config().getReporterName();
#endif
if (reporter_name == "tap") {
spdlog::set_pattern("# [%l] %v"); spdlog::set_pattern("# [%l] %v");
} else if (spec.name() == "compact") { } else if (reporter_name == "compact") {
logger->sinks().clear(); logger->sinks().clear();
} else { } else {
logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()}); logger->sinks().assign({std::make_shared<spdlog::sinks::stderr_sink_st>()});

View File

@ -15,7 +15,7 @@ test_src = files(
if tz_dep.found() if tz_dep.found()
test_dep += tz_dep test_dep += tz_dep
test_src += files('waybar_time.cpp') test_src += files('date.cpp')
endif endif
waybar_test = executable( waybar_test = executable(

View File

@ -1,90 +0,0 @@
#include "util/waybar_time.hpp"
#include <date/date.h>
#include <date/tz.h>
#include <catch2/catch_all.hpp>
#include <chrono>
#include <stdexcept>
using namespace std::literals::chrono_literals;
/*
* Check that the date/time formatter with locale and timezone support is working as expected.
*/
const date::zoned_time<std::chrono::seconds> TEST_TIME = date::make_zoned(
"UTC", date::local_days{date::Monday[1] / date::January / 2022} + 13h + 4min + 5s);
TEST_CASE("Format UTC time", "[clock][util]") {
waybar::waybar_time tm{std::locale("C"), TEST_TIME};
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 13:04:05 2022 UTC");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
/* Test a few locales that are most likely to be present */
SECTION("US locale") {
try {
tm.locale = std::locale("en_US");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 01:04:05 PM"));
REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 01:04:05 PM");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
SECTION("GB locale") {
try {
tm.locale = std::locale("en_GB");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 13:04:05"));
REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 13:04:05");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103130405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
}
TEST_CASE("Format zoned time", "[clock][util]") {
waybar::waybar_time tm{std::locale("C"), date::make_zoned("America/New_York", TEST_TIME)};
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE(fmt::format("{:%c %Z}", tm) == "Mon Jan 3 08:04:05 2022 EST");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
/* Test a few locales that are most likely to be present */
SECTION("US locale") {
try {
tm.locale = std::locale("en_US");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05 AM"));
REQUIRE(fmt::format("{:%x %X}", tm) == "01/03/2022 08:04:05 AM");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
SECTION("GB locale") {
try {
tm.locale = std::locale("en_GB");
REQUIRE(fmt::format("{}", tm).empty()); // no format specified
REQUIRE_THAT(fmt::format("{:%c}", tm), // HowardHinnant/date#704
Catch::Matchers::StartsWith("Mon 03 Jan 2022 08:04:05"));
REQUIRE(fmt::format("{:%x %X}", tm) == "03/01/22 08:04:05");
REQUIRE(fmt::format("{arg:%Y%m%d%H%M%S}", fmt::arg("arg", tm)) == "20220103080405");
} catch (const std::runtime_error&) {
// locale not found; ignore
}
}
}