Compare commits

..

115 Commits

Author SHA1 Message Date
f0bead34d4 chore: 0.9.17 2023-01-11 11:39:30 +01:00
d6bd440027 fix: lint 2023-01-09 15:48:31 +01:00
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
2045aac5b0 Fix crash upon reconnecting monitor 2023-01-08 18:49:24 +01:00
a92223c316 Merge pull request #1926 from robertgzr/mpris-module 2023-01-07 09:23:25 +01:00
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
b3b5d8f9ab Activate ci for mpris module
Signed-off-by: Robert Günzler <r@gnzler.io>
2023-01-07 01:44:25 +01:00
86850f5c7a Merge pull request #1520 from robertgzr/mpris-module 2023-01-06 22:35:24 +01:00
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
1d6cfe7ce6 Merge pull request #1921 from Jonher937/cpu-load-pushed-back-twice 2023-01-05 08:52:22 +01:00
2a3ebc12de fix: cpu_load pushed twice to the vector 2023-01-05 01:10:04 +01:00
1938bb5d28 fix: lint 2023-01-04 16:26:50 +01:00
a35861b3b9 Merge pull request #1918 from ldelossa/sway-workspaces-alpha-sort 2023-01-03 11:52:39 +01:00
8b512e7b22 sway,feat: allow alphabetical sort
for users who do not utilize any form of "workspace prev/next" commands,
it can be very handle to sort the workspaces alphabetically.

this commit adds a new "alphabetical_sort" to the `sway/workspaces`
module which allows the module to alway sort workspaces alphabetically.

this docs are updated to warn the user of the implications involved.

Signed-off-by: Louis DeLosSantos <louis.delos@gmail.com>
2023-01-02 17:03:28 -05:00
fb083f93dc Merge pull request #1911 from cdump/master 2022-12-31 13:35:46 +01:00
f795e7a308 modules/clock: improve ux when calendar_shift is used:
1. change only date, but not time
  2. use shifted values only in tooltip
  3. reset shift when mouse leaves (popup closes)
2022-12-28 10:13:10 +03:00
21abd4f9f9 Merge pull request #1910 from eneshecan/master 2022-12-27 15:44:42 +01:00
f724cc3f9d Fix wrong layout name in hyprland language module when a variant is used 2022-12-27 15:29:10 +01:00
bfbb2f9a40 Merge pull request #1906 from Arisa-Snowbell/gitignore 2022-12-26 09:33:55 +01:00
91357f210d Ignore .cache generated by clangd 2022-12-26 06:39:15 +01:00
3e48551f25 Merge pull request #1897 from eneshecan/master 2022-12-21 09:43:08 +01:00
c05f41d732 Make linter happy 2022-12-21 01:55:39 +01:00
4d59de42af Implement hyprland submap module 2022-12-21 01:45:57 +01:00
6e296838e4 Update hyprland language module docs 2022-12-21 00:20:16 +01:00
e00e36981e Merge pull request #1890 from eneshecan/master 2022-12-16 10:37:54 +01:00
4136ffaecb Minor refactorings and formatting fixes for hyprland language module 2022-12-16 10:01:58 +01:00
bd199e414b Merge pull request #1888 from eneshecan/master
Fixes undefined
2022-12-16 09:20:49 +01:00
531bdfb8bb Fix hyprland language initialization issues 2022-12-15 01:48:14 +01:00
c1ea7626b9 Merge pull request #1887 from LukashonakV/ISSUE#1874 2022-12-14 16:08:36 +01:00
995802e8ae ISSUE#1874 - happy linter 2022-12-14 16:49:03 +03:00
0079092699 ISSUE#1874
1. Calendar. Weeks. Fix right paddings when first days of the week is
Monday
2. Fix small perfomrance penalty(avoid of defining parameter in the
month loop)
3. Small name convention for format string variables
2022-12-14 16:43:23 +03:00
b5c686c0dd Merge pull request #1882 from LukashonakV/ISSUE#1877 2022-12-12 09:18:10 +01:00
4c4d09992e Regular expression improved 2022-12-10 18:36:58 +03:00
9218968d2f Wrong assigning 2022-12-10 17:55:21 +03:00
a08967e008 Happy linter 2022-12-10 16:54:26 +03:00
272c638f7e Happy linter 2022-12-10 16:48:22 +03:00
57ad7f9536 ISSUE#1877 Calendar week numbers
1. Let's do code simplier
2. Week format using regexp. Needs when user provide additional
characters in format string and need to align week days according
3. Week format has got default formats: ":%U",":%V"
4. Week number is based on the first day of the week now. The output is
the same as of date library now.
5. Avoiding of unnecessary operations
2022-12-10 14:02:15 +03:00
2a76d8e5b9 Merge pull request #1862 from alebastr/battery-ignore-scope-device 2022-12-07 15:01:26 +01:00
c5babb4c44 Merge pull request #1868 from prohornikitin/calendar-week-numbers
Fix https://github.com/Alexays/Waybar/issues/1802
2022-12-06 09:01:18 +01:00
328575a721 fix: calendar week numbers
fix their format to correct

fix last number hide if the last day of the month is the last day of the week

some refactoring(mostly renaming abbreviations to the full phrases)
2022-12-06 03:47:28 +03:00
ea9078d887 Merge pull request #1867 from cyrinux/feat/macsmc-battery-support 2022-12-05 22:30:47 +01:00
b1833b1f36 feat: add macsmc-battery time remaining support for asahi
use time_to_empty_now and time_to_full_now
2022-12-05 22:09:05 +01:00
53e89dace7 Merge pull request #1865 from Dordovel/master
Fix https://github.com/Alexays/Waybar/pull/1854#issue-1469577538
2022-12-05 09:01:45 +01:00
3cbcef61cf fix AIconLabel spacing between image and label 2022-12-05 10:02:38 +03:00
22084691ff fix(battery): ignore non-system power supplies
Linux power_supply sysfs interface allows checking if the battery powers
the whole system or a specific device/tree of devices with `scope`
attribute[1]. We can use it to skip the non-system power supplies in the
battery module and avoid adding HIDs or other peripheral devices to the
total.

The logic is based on UPower, where it is assumed that "Unknown" devices
or devices without a `scope` are system power supplies.

[1]: https://lore.kernel.org/lkml/alpine.LNX.2.00.1201031556460.24984@pobox.suse.cz/T/
2022-12-04 00:14:42 -08:00
f4afa59861 Merge pull request #1860 from prohornikitin/hide-module-if-empty 2022-12-03 15:32:12 +01:00
ce8c13788a fix formatting issues 2022-12-02 19:32:03 +03:00
b74f3c7aaa hide mdp/pulseaudio/sndio if text 'resolves' to be empty. 2022-12-02 18:15:51 +03:00
2111865efe refactor: remove warning 2022-12-01 08:45:12 +01:00
94d6ae9741 Merge pull request #1845 from adamant-pwn/patch-1 2022-11-29 10:47:20 +01:00
e6760bf9dd Merge pull request #1846 from Dordovel/master 2022-11-29 08:46:06 +01:00
7671ccfbc6 added file existence check 2022-11-29 09:00:12 +03:00
459541ed89 Don't search "Keyboard at" from hyprland/language
The current output form of `hyprctl devices` is like this:
```
        Keyboard at 6f80ad70:
                ITE Tech. Inc. ITE Device(8910) Keyboard
                        rules: r "", m "", l "us,ru", v "", o "grp:alt_shift_toggle"
                        active keymap: Russian
                        main: no
```

That is, `Keyboard at` goes _before_ the keyboard name, so looking for `Keyboard at` only makes it skip to the keyboard _after_ the one that the user specified.
2022-11-29 01:11:25 +01:00
da3d9533d1 Merge pull request #1841 from encbar5/inverted-date-scroll 2022-11-28 07:43:48 +01:00
8db1996ccc Allow calendar_shift_init_ to be negative 2022-11-27 21:24:56 -06:00
cfef78a5bc Merge pull request #1837 from smoak/smoak/fix-wireplumber-bluetooth 2022-11-26 20:45:29 +01:00
60fa5e9f67 fix: wireplumber module when used with a bluetooth device
This fixes #1811 by falling back to `node.description` if `node.nick` is
not available. This can happen for bluetooth devices that do not have a
`node.nick`.
2022-11-26 11:35:45 -08:00
cea59ddc6c Merge pull request #1836 from smoak/smoak/wireplumber-icon-support 2022-11-26 20:23:17 +01:00
3730793197 feat: add icon support to the wireplumber module
Adds basic icon support for the wireplumber module.

This can be achieved by using `{icon}` in the `format` config and
pairing it with the `format-icons` config as well.

Example:

```
"wireplumber": {
    "format": "{volume}% {icon}",
    "format-icons": ["", "", ""]
}
```
2022-11-26 10:02:16 -08:00
d2b4076ac8 Merge pull request #1799 from Keloran/upower-click 2022-11-25 09:04:31 +01:00
e63e3a0ca9 Update upower.cpp 2022-11-25 09:03:27 +01:00
99d370d9ed Update README.md 2022-11-24 20:51:54 +01:00
80b2b29a77 Merge pull request #1397 from JakeStanger/feat/image-module
Resolves https://github.com/Alexays/Waybar/issues/1191
2022-11-24 20:40:56 +01:00
27ad9ec267 Update README.md 2022-11-24 20:37:30 +01:00
9eb6c4e296 chore: v0.9.16 2022-11-24 20:34:12 +01:00
748c6125d0 Merge pull request #1810 from pinselimo/revert-1120 2022-11-24 15:20:29 +01:00
235861fd3d button: Remove AButton class 2022-11-24 13:08:16 +01:00
5e9bbe5c76 modules: Revert button to label 2022-11-24 13:08:16 +01:00
74fa131ebe Merge pull request #1809 from herlev/hyprland-named-workspace-crash 2022-11-24 07:46:17 +01:00
2c7cb0e9d4 Fix crashes when using named workspaces in Hyprland
The first crash occurs when trying to parse the
ID of a workspace as an uint, since named
workspaces has negative IDs. This is fixed by
using ints for workspace IDs instead of uints.

The second crash occurs when converting a
workspace name that isn't a number to an integer.
This is fixed by wrapping std::stoi in a try
block and only sorting by number, when both names
can successfully be converted to integers.
2022-11-24 02:16:44 +01:00
ce8ae5bf17 Merge pull request #1748 from lilydjwg/fix-zombies
fixes https://github.com/Alexays/Waybar/issues/1713
2022-11-23 21:31:47 +01:00
062e7bb9b4 Merge pull request #1797 from smoak/wireplumber-support 2022-11-22 12:44:39 +01:00
3acd31c3e9 syntax issue 2022-11-21 09:48:41 +00:00
456e06c4b5 exact opposite, lol 2022-11-21 09:46:57 +00:00
a2751cfcd6 alt text readded 2022-11-18 14:25:16 +00:00
d9cc995405 added onclick to upower 2022-11-18 13:10:04 +00:00
00a2ebf00d added onclick to upower 2022-11-18 13:09:38 +00:00
c2f98d07ef feat: wireplumber support
Adds basic support for showing volume via wireplumber. Allows specifying
the node-id or falling back to the default Audio/Sink node id if node-id
is not set. If tooltip on hover is enabled, will show `{node_name}` by
default otherwise `tooltip-format`.

Format replacements:

`{volume}` - Volume in percentage
`{node_name}` - The node's nickname (`node.nick` property)
2022-11-16 23:23:07 -08:00
833dcc1bb8 Merge pull request #1795 from schmidma/bluez-output-detection 2022-11-16 19:07:46 +01:00
8c24e26f0e Recognize outputs with 'bluez' in monitor name as bluetooth class 2022-11-16 19:01:12 +01:00
56b4a11a9c Merge pull request #1793 from Dordovel/master 2022-11-16 07:41:29 +01:00
1111763251 added path settings 2022-11-16 08:04:18 +03:00
769858fbb4 fix call parent event handle, added commang to open user folder 2022-11-15 16:15:26 +03:00
2695815bcc Merge pull request #1787 from Dordovel/master 2022-11-15 08:41:12 +01:00
49afb87e34 Merge branch 'Alexays:master' into master 2022-11-13 16:17:04 +03:00
5250123dcb Merge pull request #1788 from grfrederic/normalize-battery-capacity 2022-11-12 23:41:00 +01:00
c0b3e9ee35 normalize capacity by number of batteries 2022-11-12 22:39:53 +01:00
454ba610f4 clicking on the user label opens the default file manager 2022-11-11 15:15:12 +03:00
3718902b9d Merge pull request #1785 from ElJeffe/hyprland_monitor_remove 2022-11-10 09:21:52 +01:00
9f0a14c22b make linter happy 2022-11-10 09:19:49 +01:00
781da93f3d Merge pull request #1780 from ElJeffe/hyprland_monitor_remove 2022-11-09 23:09:30 +01:00
8f4f67f69f Do not crash when a monitor is removed 2022-11-09 09:34:19 +01:00
8be5bab8ad Merge pull request #1734 from baltitenger/backlight-hide-when-powered-off
closes https://github.com/Alexays/Waybar/issues/393
2022-11-07 14:09:54 +01:00
d02e23c759 feat(backlight): hide if the display is powered off 2022-11-07 13:57:21 +01:00
d2b22c6ec5 fix: lint 2022-11-07 09:23:47 +01:00
ed898cd211 Merge pull request #1773 from kj/backlight-format-states 2022-11-07 09:23:17 +01:00
1a1c617520 Merge pull request #1772 from kj/fix-states-documentation 2022-11-07 09:13:41 +01:00
253222d31c Make backlight module respect format-<state> config 2022-11-07 21:06:16 +13:00
51e6fc6250 Fix states documentation 2022-11-07 20:30:01 +13:00
af1668dfd0 Merge pull request #1770 from Scrumplex/fix-mpd-double-encode 2022-11-06 09:52:31 +01:00
cf5877073a fix: don't escape mpd label twice
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2022-11-05 20:23:00 +01:00
bd567800c9 Merge pull request #1769 from ericliou/master 2022-11-04 22:01:36 +01:00
6477e539d0 Battery: Plugged status has higher priority 2022-11-04 20:01:53 +00:00
242e19a07d Merge pull request #1767 from b1rger/fix-typos 2022-11-04 15:07:31 +01:00
0e53c37d6b Fix typos in manual pages 2022-11-04 15:03:10 +01:00
3030850b22 refactor: inherit disabled button 2022-11-04 08:39:59 +01:00
92cc01f401 fix: label default style 2022-11-03 15:53:45 +01:00
d48eebd4d3 fix: use GTK_STYLE_PROVIDER_PRIORITY_APPLICATION 2022-11-03 14:10:18 +01:00
eb705533b5 feat: jsoncpp wrap 2022-11-03 14:08:22 +01:00
3cf027fc56 fix: button default style 2022-11-03 14:04:29 +01:00
09120caf17 Merge pull request #1762 from jpalus/jsoncpp-version 2022-11-03 11:02:19 +01:00
73495df377 build: require jsoncpp >= 1.9.2
with #1719 Waybar started using Json::Value.as() available since jsoncpp
1.9.2.
2022-11-03 10:59:03 +01:00
31137c30fb fix custom module leaves zombie processes behind when bars are removed
fixes #1713.
2022-10-27 18:12:14 +08:00
41dea6e46c Merge branch 'master' into feat/image-module 2022-02-22 23:40:59 +00:00
a650c7d90c feat: image module
Module which renders an image onto the bar.
2022-01-16 23:55:13 +00:00
91 changed files with 1868 additions and 562 deletions

2
.gitignore vendored
View File

@ -2,6 +2,8 @@
*~
vgcore.*
/.vscode
/.idea
/.cache
*.swp
packagecache
/subprojects/**/

View File

@ -2,4 +2,4 @@
FROM alpine:latest
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev 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,4 @@
FROM archlinux:base-devel
RUN pacman -Syu --noconfirm && \
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon
pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl

View File

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

View File

@ -8,5 +8,6 @@ RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' \
'pkgconfig(playerctl)' && \
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 && \
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 \
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 -n refresh && \
zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel

View File

@ -16,12 +16,14 @@
- Network
- Bluetooth
- Pulseaudio
- Wireplumber
- Disk
- Memory
- Cpu load average
- Temperature
- MPD
- Custom scripts
- Custom image
- Multiple output configuration
- And many more customizations

View File

@ -1,35 +0,0 @@
#pragma once
#include <glibmm/markup.h>
#include <gtkmm/button.h>
#include <gtkmm/cssprovider.h>
#include <gtkmm/label.h>
#include <json/json.h>
#include "AModule.hpp"
namespace waybar {
class AButton : public AModule {
public:
AButton(const Json::Value &, const std::string &, const std::string &, const std::string &format,
uint16_t interval = 0, bool ellipsize = false, bool enable_click = false,
bool enable_scroll = false);
virtual ~AButton() = default;
virtual auto update() -> void;
virtual std::string getIcon(uint16_t, const std::string &alt = "", uint16_t max = 0);
virtual std::string getIcon(uint16_t, const std::vector<std::string> &alts, uint16_t max = 0);
protected:
Gtk::Button button_ = Gtk::Button(name_);
Gtk::Label *label_ = (Gtk::Label *)button_.get_child();
std::string format_;
const std::chrono::seconds interval_;
bool alt_ = false;
std::string default_format_;
virtual bool handleToggle(GdkEventButton *const &e);
virtual std::string getState(uint8_t value, bool lesser = false);
};
} // namespace waybar

View File

@ -25,6 +25,7 @@
#ifdef HAVE_HYPRLAND
#include "modules/hyprland/backend.hpp"
#include "modules/hyprland/language.hpp"
#include "modules/hyprland/submap.hpp"
#include "modules/hyprland/window.hpp"
#endif
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
@ -41,6 +42,9 @@
#ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp"
#endif
#ifdef HAVE_MPRIS
#include "modules/mpris/mpris.hpp"
#endif
#ifdef HAVE_LIBNL
#include "modules/network.hpp"
#endif
@ -72,8 +76,12 @@
#ifdef HAVE_LIBJACK
#include "modules/jack.hpp"
#endif
#ifdef HAVE_LIBWIREPLUMBER
#include "modules/wireplumber.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/image.hpp"
#include "modules/temperature.hpp"
#include "modules/user.hpp"

View File

@ -5,7 +5,7 @@
#include <string_view>
#include <vector>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
@ -14,16 +14,18 @@ struct udev_device;
namespace waybar::modules {
class Backlight : public AButton {
class Backlight : public ALabel {
class BacklightDev {
public:
BacklightDev() = default;
BacklightDev(std::string name, int actual, int max);
BacklightDev(std::string name, int actual, int max, bool powered);
std::string_view name() const;
int get_actual() const;
void set_actual(int actual);
int get_max() const;
void set_max(int max);
bool get_powered() const;
void set_powered(bool powered);
friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) {
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
}
@ -32,6 +34,7 @@ class Backlight : public AButton {
std::string name_;
int actual_ = 1;
int max_ = 1;
bool powered_ = true;
};
public:

View File

@ -15,7 +15,7 @@
#include <string>
#include <vector>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
@ -26,7 +26,7 @@ namespace fs = std::experimental::filesystem;
namespace fs = std::filesystem;
#endif
class Battery : public AButton {
class Battery : public ALabel {
public:
Battery(const std::string&, const Json::Value&);
~Battery();

View File

@ -1,6 +1,6 @@
#pragma once
#include "AButton.hpp"
#include "ALabel.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
#endif
@ -12,7 +12,7 @@
namespace waybar::modules {
class Bluetooth : public AButton {
class Bluetooth : public ALabel {
struct ControllerInfo {
std::string path;
std::string address;

View File

@ -2,7 +2,7 @@
#include <date/tz.h>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar {
@ -14,7 +14,7 @@ namespace modules {
const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
class Clock : public AButton {
class Clock : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;
@ -33,6 +33,9 @@ class Clock : public AButton {
bool handleScroll(GdkEventScroll* 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;

View File

@ -9,12 +9,12 @@
#include <utility>
#include <vector>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Cpu : public AButton {
class Cpu : public ALabel {
public:
Cpu(const std::string&, const Json::Value&);
~Cpu() = default;

View File

@ -5,14 +5,14 @@
#include <csignal>
#include <string>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Custom : public AButton {
class Custom : public ALabel {
public:
Custom(const std::string&, const std::string&, const Json::Value&);
~Custom();

View File

@ -5,13 +5,13 @@
#include <fstream>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/format.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Disk : public AButton {
class Disk : public ALabel {
public:
Disk(const std::string&, const Json::Value&);
~Disk() = default;

View File

@ -1,17 +1,25 @@
#pragma once
#include <deque>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
namespace waybar::modules::hyprland {
class EventHandler {
public:
virtual void onEvent(const std::string& ev) = 0;
virtual ~EventHandler() = default;
};
class IPC {
public:
IPC() { startIPC(); }
void registerForIPC(const std::string&, std::function<void(const std::string&)>);
void registerForIPC(const std::string&, EventHandler*);
void unregisterForIPC(EventHandler*);
std::string getSocket1Reply(const std::string& rq);
@ -20,7 +28,7 @@ class IPC {
void parseIPC(const std::string&);
std::mutex callbackMutex;
std::deque<std::pair<std::string, std::function<void(const std::string&)>>> callbacks;
std::list<std::pair<std::string, EventHandler*>> callbacks;
};
inline std::unique_ptr<IPC> gIPC;

View File

@ -1,16 +1,16 @@
#include <fmt/format.h>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland {
class Language : public waybar::AButton {
class Language : public waybar::ALabel, public EventHandler {
public:
Language(const std::string&, const waybar::Bar&, const Json::Value&);
~Language() = default;
~Language();
auto update() -> void;

View File

@ -0,0 +1,26 @@
#include <fmt/format.h>
#include "ALabel.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland {
class Submap : public waybar::ALabel, public EventHandler {
public:
Submap(const std::string&, const waybar::Bar&, const Json::Value&);
~Submap();
auto update() -> void;
private:
void onEvent(const std::string&);
std::mutex mutex_;
const Bar& bar_;
util::JsonParser parser_;
std::string submap_;
};
} // namespace waybar::modules::hyprland

View File

@ -9,16 +9,16 @@
namespace waybar::modules::hyprland {
class Window : public waybar::ALabel {
class Window : public waybar::ALabel, public EventHandler {
public:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default;
~Window();
auto update() -> void;
private:
uint getActiveWorkspaceID(std::string);
std::string getLastWindowTitle(uint);
int getActiveWorkspaceID(std::string);
std::string getLastWindowTitle(int);
void onEvent(const std::string&);
bool separate_outputs;
@ -28,4 +28,4 @@ class Window : public waybar::ALabel {
std::string lastView;
};
} // namespace waybar::modules::hyprland
} // namespace waybar::modules::hyprland

View File

@ -2,13 +2,13 @@
#include <fmt/format.h>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
namespace waybar::modules {
class IdleInhibitor : public AButton {
class IdleInhibitor : public ALabel {
sigc::connection timeout_;
public:

34
include/modules/image.hpp Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <fmt/format.h>
#include <gtkmm/image.h>
#include <csignal>
#include <string>
#include "ALabel.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Image : public AModule {
public:
Image(const std::string&, const std::string&, const Json::Value&);
auto update() -> void;
void refresh(int /*signal*/);
private:
void delayWorker();
void handleEvent();
Gtk::Image image_;
std::string path_;
int size_;
int interval_;
util::SleeperThread thread_;
};
} // namespace waybar::modules

View File

@ -4,12 +4,12 @@
#include <memory>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "bar.hpp"
namespace waybar::modules {
class Inhibitor : public AButton {
class Inhibitor : public ALabel {
public:
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~Inhibitor() override;

View File

@ -5,12 +5,12 @@
#include <fstream>
#include <unordered_map>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Memory : public AButton {
class Memory : public ALabel {
public:
Memory(const std::string&, const Json::Value&);
~Memory() = default;

View File

@ -7,12 +7,12 @@
#include <condition_variable>
#include <thread>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "modules/mpd/state.hpp"
namespace waybar::modules {
class MPD : public AButton {
class MPD : public ALabel {
friend class detail::Context;
// State machine

View File

@ -7,7 +7,7 @@
#include <condition_variable>
#include <thread>
#include "AButton.hpp"
#include "ALabel.hpp"
namespace waybar::modules {
class MPD;

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

@ -10,7 +10,7 @@
#include <optional>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
#ifdef WANT_RFKILL
#include "util/rfkill.hpp"
@ -18,7 +18,7 @@
namespace waybar::modules {
class Network : public AButton {
class Network : public ALabel {
public:
Network(const std::string&, const Json::Value&);
~Network();

View File

@ -7,11 +7,11 @@
#include <algorithm>
#include <array>
#include "AButton.hpp"
#include "ALabel.hpp"
namespace waybar::modules {
class Pulseaudio : public AButton {
class Pulseaudio : public ALabel {
public:
Pulseaudio(const std::string&, const Json::Value&);
~Pulseaudio();

View File

@ -2,12 +2,12 @@
#include <fmt/chrono.h>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Clock : public AButton {
class Clock : public ALabel {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;

View File

@ -4,12 +4,12 @@
#include <vector>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Sndio : public AButton {
class Sndio : public ALabel {
public:
Sndio(const std::string &, const Json::Value &);
~Sndio();

View File

@ -6,7 +6,7 @@
#include <map>
#include <string>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
@ -14,7 +14,7 @@
namespace waybar::modules::sway {
class Language : public AButton, public sigc::trackable {
class Language : public ALabel, public sigc::trackable {
public:
Language(const std::string& id, const Json::Value& config);
~Language() = default;

View File

@ -2,7 +2,7 @@
#include <fmt/format.h>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
@ -10,7 +10,7 @@
namespace waybar::modules::sway {
class Mode : public AButton, public sigc::trackable {
class Mode : public ALabel, public sigc::trackable {
public:
Mode(const std::string&, const Json::Value&);
~Mode() = default;

View File

@ -4,12 +4,12 @@
#include <fstream>
#include "AButton.hpp"
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Temperature : public AButton {
class Temperature : public ALabel {
public:
Temperature(const std::string&, const Json::Value&);
~Temperature() = default;

View File

@ -14,18 +14,18 @@ class User : public AIconLabel {
~User() = default;
auto update() -> void;
bool handleToggle(GdkEventButton* const& e) override;
private:
util::SleeperThread thread_;
Glib::RefPtr<Gdk::Pixbuf> pixbuf_;
static constexpr inline int defaultUserImageWidth_ = 20;
static constexpr inline int defaultUserImageHeight_ = 20;
long uptime_as_seconds();
std::string get_user_login();
std::string get_user_home_dir();
std::string get_default_user_avatar_path();
std::string get_user_login() const;
std::string get_user_home_dir() const;
std::string get_default_user_avatar_path() const;
void init_default_user_avatar(int width, int height);
void init_user_avatar(const std::string& path, int width, int height);
void init_avatar(const Json::Value& config);

View File

@ -0,0 +1,39 @@
#pragma once
#include <fmt/format.h>
#include <wp/wp.h>
#include <algorithm>
#include <array>
#include "ALabel.hpp"
namespace waybar::modules {
class Wireplumber : public ALabel {
public:
Wireplumber(const std::string&, const Json::Value&);
~Wireplumber();
auto update() -> void;
private:
void loadRequiredApiModules();
void prepare();
void activatePlugins();
static void updateVolume(waybar::modules::Wireplumber* self);
static void updateNodeName(waybar::modules::Wireplumber* self);
static uint32_t getDefaultNodeId(waybar::modules::Wireplumber* self);
static void onPluginActivated(WpObject* p, GAsyncResult* res, waybar::modules::Wireplumber* self);
static void onObjectManagerInstalled(waybar::modules::Wireplumber* self);
WpCore* wp_core_;
GPtrArray* apis_;
WpObjectManager* om_;
uint32_t pending_plugins_;
bool muted_;
double volume_;
uint32_t node_id_{0};
std::string node_name_;
};
} // namespace waybar::modules

View File

@ -37,8 +37,8 @@ The *backlight* module displays the current backlight level.
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of backlight states which get activated on certain brightness levels.
typeof: object ++
A number of backlight states which get activated on certain brightness levels. See *waybar-states(5)*.
*on-click*: ++
typeof: string ++

View File

@ -33,7 +33,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
The interval in which the information gets polled.
*states*: ++
typeof: array ++
typeof: object ++
A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.
*format*: ++

View File

@ -137,7 +137,7 @@ Addressed by *bluetooth*
*{device_alias}*: Alias of the displayed device.
*{device_enumerate}*: Show a list of all connected devices, each on a seperate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++
*{device_enumerate}*: Show a list of all connected devices, each on a separate line. Define the format of each device with the *tooltip-format-enumerate-connected* ++
and/or *tooltip-format-enumerate-connected-battery* config options. Can only be used in the tooltip related format options.
# EXPERIMENTAL BATTERY PERCENTAGE FEATURE

View File

@ -42,7 +42,7 @@ The *cpu* module displays the current cpu utilization.
Positive value to rotate the text label.
*states*: ++
typeof: array ++
typeof: object ++
A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*.
*on-click*: ++

View File

@ -32,7 +32,7 @@ Addressed by *disk*
Positive value to rotate the text label.
*states*: ++
typeof: array ++
typeof: object ++
A number of disk utilization states which get activated on certain percentage thresholds (percentage_used). See *waybar-states(5)*.
*max-length*: ++

View File

@ -23,7 +23,7 @@ Feral Gamemode optimizations.
*tooltip*: ++
typeof: bool ++
defualt: true ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++

View File

@ -23,7 +23,7 @@ Addressed by *hyprland/language*
*keyboard-name*: ++
typeof: string ++
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "AT Translated set..." is recommended.
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
@ -32,9 +32,9 @@ Addressed by *hyprland/language*
```
"hyprland/language": {
"format": "Lang: {}"
"format-us": "AMERICA, HELL YEAH!" // For American English
"format-tr": "As bayrakları" // For Turkish
"keyboard-name": "AT Translated Set 2 keyboard"
"format-en": "AMERICA, HELL YEAH!"
"format-tr": "As bayrakları"
"keyboard-name": "at-translated-set-2-keyboard"
}
```

View File

@ -0,0 +1,82 @@
waybar-hyprland-submap(5)
# NAME
waybar - hyprland submap module
# DESCRIPTION
The *submap* module displays the currently active submap similar to *sway/mode*.
# CONFIGURATION
Addressed by *hyprland/submap*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} the currently active submap is displayed.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# EXAMPLES
```
"hyprland/submap": {
"format": "✌️ {}",
"max-length": 8,
"tooltip": false
}
```
# STYLE
- *#submap*

72
man/waybar-image.5.scd Normal file
View File

@ -0,0 +1,72 @@
waybar-custom(5)
# NAME
waybar - image module
# DESCRIPTION
The *image* module displays an image from a path.
# CONFIGURATION
Addressed by *custom/<name>*
*path*: ++
typeof: string ++
The path to the image.
*size*: ++
typeof: integer ++
The width/height to render the image.
*interval*: ++
typeof: integer ++
The interval (in seconds) to re-render the image.
This is useful if the contents of *path* changes.
If no *interval* is defined, the image will only be rendered once.
*signal*: ++
typeof: integer ++
The signal number used to update the module.
This can be used instead of *interval* if the file changes irregularly.
The number is valid between 1 and N, where *SIGRTMIN+N* = *SIGRTMAX*.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
# EXAMPLES
## Spotify:
## mpd:
```
"image/album-art": {
"path": "/tmp/mpd_art",
"size": 32,
"interval": 5,
"on-click": "mpc toggle"
}
```

View File

@ -6,7 +6,7 @@ waybar - inhibitor module
# DESCRIPTION
The *inhibitor* module allows to take an inhibitor lock that logind provides.
The *inhibitor* module allows one to take an inhibitor lock that logind provides.
See *systemd-inhibit*(1) for more information.
# CONFIGURATION

View File

@ -32,7 +32,7 @@ Addressed by *memory*
Positive value to rotate the text label.
*states*: ++
typeof: array ++
typeof: object ++
A number of memory utilization states which get activated on certain percentage thresholds. See *waybar-states(5)*.
*max-length*: ++

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

@ -43,8 +43,8 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
Positive value to rotate the text label.
*states*: ++
typeof: array ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)*
typeof: object ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)*.
*max-length*: ++
typeof: integer ++

View File

@ -7,14 +7,13 @@ apply a class when the value matches the declared state value.
# STATES
- Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
Every entry (*state*) consists of a *<name>* (typeof: *string*) and a *<value>* (typeof: *integer*).
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state.
Each class gets activated when the current capacity is equal or below the configured *<value>*.
- The state can be addressed as a CSS class in the *style.css*. The name of the CSS class is the *<name>* of the state.
Each class gets activated when the current value is equal to or less than the configured *<value>* for the *battery* module, or equal to or greater than the configured *<value>* for all other modules.
- Also each state can have its own *format*.
Those can be configured via *format-<name>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
- Also, each state can have its own *format*.
Those can be configured via *format-<name>*, or if you want to differentiate a bit more, as *format-<status>-<state>*.
# EXAMPLE

View File

@ -73,6 +73,10 @@ Addressed by *sway/workspaces*
typeof: bool ++
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
*alphabetical_sort*: ++
typeof: bool ++
Whether to sort workspaces alphabetically. Please note this can make "swaymsg workspace prev/next" move to workspaces inconsistent with the ordering shown in Waybar.
# FORMAT REPLACEMENTS
*{value}*: Name of the workspace, as defined by sway.

View File

@ -33,7 +33,7 @@ compatible devices in the tooltip.
*tooltip*: ++
typeof: bool ++
defualt: true ++
default: true ++
Option to disable tooltip on hover.
*tooltip-spacing*: ++
@ -47,6 +47,10 @@ compatible devices in the tooltip.
default: 4 ++
Defines the spacing between the tooltip window edge and the tooltip content.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
# FORMAT REPLACEMENTS
*{percentage}*: The battery capacity in percentage

View File

@ -0,0 +1,87 @@
waybar-wireplumber(5)
# NAME
waybar - WirePlumber module
# DESCRIPTION
The *wireplumber* module displays the current volume reported by WirePlumber.
# CONFIGURATION
*format*: ++
typeof: string ++
default: *{volume}%* ++
The format, how information should be displayed. This format is used when other formats aren't specified.
*format-muted*: ++
typeof: string ++
This format is used when the sound is muted.
*tooltip*: ++
typeof: bool ++
default: *true* ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: *{node_name}* ++
The format of information displayed in the tooltip.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*states*: ++
typeof: object ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)*.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
# FORMAT REPLACEMENTS
*{volume}*: Volume in percentage.
*{node_name}*: The node's nickname as reported by WirePlumber (*node.nick* property)
# EXAMPLES
```
"wireplumber": {
"format": "{volume}%",
"format-muted": "",
"on-click": "helvum"
}
```
# STYLE
- *#wireplumber*
- *#wireplumber.muted*

View File

@ -266,6 +266,7 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-keyboard-state(5)*
- *waybar-memory(5)*
- *waybar-mpd(5)*
- *waybar-mpris(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-river-mode(5)*

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.15',
version: '0.9.17',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
@ -86,8 +86,11 @@ wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled()))
jsoncpp = dependency('jsoncpp')
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or
get_option('logind').enabled() or
get_option('upower_glib').enabled() or
get_option('mpris').enabled()))
jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])
sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false)
libepoll = dependency('epoll-shim', required: false)
@ -95,12 +98,14 @@ libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry')
libjack = dependency('jack', required: get_option('jack'))
libwireplumber = dependency('wireplumber-0.4', required: get_option('wireplumber'))
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
@ -145,12 +150,12 @@ endif
src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/AButton.cpp',
'src/ALabel.cpp',
'src/AIconLabel.cpp',
'src/modules/custom.cpp',
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/image.cpp',
'src/modules/temperature.cpp',
'src/modules/user.cpp',
'src/main.cpp',
@ -219,6 +224,7 @@ if true
src_files += 'src/modules/hyprland/backend.cpp'
src_files += 'src/modules/hyprland/window.cpp'
src_files += 'src/modules/hyprland/language.cpp'
src_files += 'src/modules/hyprland/submap.cpp'
endif
if libnl.found() and libnlgen.found()
@ -237,6 +243,11 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable
src_files += 'src/modules/upower/upower_tooltip.cpp'
endif
if (playerctl.found() and giounix.found() and not get_option('logind').disabled())
add_project_arguments('-DHAVE_MPRIS', language: 'cpp')
src_files += 'src/modules/mpris/mpris.cpp'
endif
if libpulse.found()
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
src_files += 'src/modules/pulseaudio.cpp'
@ -247,6 +258,11 @@ if libjack.found()
src_files += 'src/modules/jack.cpp'
endif
if libwireplumber.found()
add_project_arguments('-DHAVE_LIBWIREPLUMBER', language: 'cpp')
src_files += 'src/modules/wireplumber.cpp'
endif
if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files(
@ -328,8 +344,10 @@ executable(
libnl,
libnlgen,
upower_glib,
playerctl,
libpulse,
libjack,
libwireplumber,
libudev,
libinotify,
libepoll,
@ -380,6 +398,7 @@ if scdoc.found()
'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd',
'waybar-mpd.5.scd',
'waybar-mpris.5.scd',
'waybar-network.5.scd',
'waybar-pulseaudio.5.scd',
'waybar-river-mode.5.scd',

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('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower')
option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris')
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
@ -16,3 +17,4 @@ option('logind', type: 'feature', value: 'auto', description: 'Enable support fo
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')
option('wireplumber', type: 'feature', value: 'auto', description: 'Enable support for WirePlumber')

View File

@ -81,6 +81,7 @@ button:hover {
#backlight,
#network,
#pulseaudio,
#wireplumber,
#custom-media,
#tray,
#mode,
@ -176,6 +177,15 @@ label:focus {
color: #2a5c45;
}
#wireplumber {
background-color: #fff0f5;
color: #000000;
}
#wireplumber.muted {
background-color: #f53c3c;
}
#custom-media {
background-color: #66cc99;
color: #2a5c45;

View File

@ -1,160 +0,0 @@
#include "AButton.hpp"
#include <fmt/format.h>
#include <util/command.hpp>
namespace waybar {
AButton::AButton(const Json::Value& config, const std::string& name, const std::string& id,
const std::string& format, uint16_t interval, bool ellipsize, bool enable_click,
bool enable_scroll)
: AModule(config, name, id, config["format-alt"].isString() || enable_click, enable_scroll),
format_(config_["format"].isString() ? config_["format"].asString() : format),
interval_(config_["interval"] == "once"
? std::chrono::seconds(100000000)
: std::chrono::seconds(
config_["interval"].isUInt() ? config_["interval"].asUInt() : interval)),
default_format_(format_) {
button_.set_name(name);
button_.set_relief(Gtk::RELIEF_NONE);
/* https://github.com/Alexays/Waybar/issues/1731 */
auto css = Gtk::CssProvider::create();
css->load_from_data("button { min-width: 0; }");
button_.get_style_context()->add_provider(css, GTK_STYLE_PROVIDER_PRIORITY_USER);
if (!id.empty()) {
button_.get_style_context()->add_class(id);
}
event_box_.add(button_);
if (config_["max-length"].isUInt()) {
label_->set_max_width_chars(config_["max-length"].asInt());
label_->set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_->set_single_line_mode(true);
} else if (ellipsize && label_->get_max_width_chars() == -1) {
label_->set_ellipsize(Pango::EllipsizeMode::ELLIPSIZE_END);
label_->set_single_line_mode(true);
}
if (config_["min-length"].isUInt()) {
label_->set_width_chars(config_["min-length"].asUInt());
}
uint rotate = 0;
if (config_["rotate"].isUInt()) {
rotate = config["rotate"].asUInt();
label_->set_angle(rotate);
}
if (config_["align"].isDouble()) {
auto align = config_["align"].asFloat();
if (rotate == 90 || rotate == 270) {
label_->set_yalign(align);
} else {
label_->set_xalign(align);
}
}
if (!(config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||
config_["on-click-right"].isString() || config_["format-alt"].isString() || enable_click)) {
button_.set_sensitive(false);
} else {
button_.signal_pressed().connect([this] {
GdkEventButton* e = (GdkEventButton*)gdk_event_new(GDK_BUTTON_PRESS);
e->button = 1;
handleToggle(e);
});
}
}
auto AButton::update() -> void { AModule::update(); }
std::string AButton::getIcon(uint16_t percentage, const std::string& alt, uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
format_icons = format_icons[alt];
} else {
format_icons = format_icons["default"];
}
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
std::string AButton::getIcon(uint16_t percentage, const std::vector<std::string>& alts,
uint16_t max) {
auto format_icons = config_["format-icons"];
if (format_icons.isObject()) {
std::string _alt = "default";
for (const auto& alt : alts) {
if (!alt.empty() && (format_icons[alt].isString() || format_icons[alt].isArray())) {
_alt = alt;
break;
}
}
format_icons = format_icons[_alt];
}
if (format_icons.isArray()) {
auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
if (format_icons.isString()) {
return format_icons.asString();
}
return "";
}
bool waybar::AButton::handleToggle(GdkEventButton* const& e) {
if (config_["format-alt-click"].isUInt() && e->button == config_["format-alt-click"].asUInt()) {
alt_ = !alt_;
if (alt_ && config_["format-alt"].isString()) {
format_ = config_["format-alt"].asString();
} else {
format_ = default_format_;
}
}
return AModule::handleToggle(e);
}
std::string AButton::getState(uint8_t value, bool lesser) {
if (!config_["states"].isObject()) {
return "";
}
// Get current state
std::vector<std::pair<std::string, uint8_t>> states;
if (config_["states"].isObject()) {
for (auto it = config_["states"].begin(); it != config_["states"].end(); ++it) {
if (it->isUInt() && it.key().isString()) {
states.emplace_back(it.key().asString(), it->asUInt());
}
}
}
// Sort states
std::sort(states.begin(), states.end(), [&lesser](auto& a, auto& b) {
return lesser ? a.second < b.second : a.second > b.second;
});
std::string valid_state;
for (auto const& state : states) {
if ((lesser ? value <= state.second : value >= state.second) && valid_state.empty()) {
button_.get_style_context()->add_class(state.first);
valid_state = state.first;
} else {
button_.get_style_context()->remove_class(state.first);
}
}
return valid_state;
}
} // namespace waybar

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]);
}
#endif
#ifdef HAVE_MPRIS
if (ref == "mpris") {
return new waybar::modules::mpris::Mpris(id, config_[name]);
}
#endif
#ifdef HAVE_SWAY
if (ref == "sway/mode") {
return new waybar::modules::sway::Mode(id, config_[name]);
@ -67,6 +72,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "hyprland/language") {
return new waybar::modules::hyprland::Language(id, bar_, config_[name]);
}
if (ref == "hyprland/submap") {
return new waybar::modules::hyprland::Submap(id, bar_, config_[name]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
@ -137,12 +145,19 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "jack") {
return new waybar::modules::JACK(id, config_[name]);
}
#endif
#ifdef HAVE_LIBWIREPLUMBER
if (ref == "wireplumber") {
return new waybar::modules::Wireplumber(id, config_[name]);
}
#endif
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);
}
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]);
} else if (ref.compare(0, 6, "image/") == 0 && ref.size() > 6) {
return new waybar::modules::Image(ref.substr(6), id, config_[name]);
}
} catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what());

View File

@ -73,8 +73,9 @@ void check_nn(const void *ptr, const char *message = "ptr was null") {
}
} // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max)
: name_(std::move(name)), actual_(actual), max_(max) {}
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max,
bool powered)
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
@ -86,8 +87,12 @@ int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
bool waybar::modules::Backlight::BacklightDev::get_powered() const { return powered_; }
void waybar::modules::Backlight::BacklightDev::set_powered(bool powered) { powered_ = powered; }
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
: AButton(config, "backlight", id, "{percent}%", 2),
: ALabel(config, "backlight", id, "{percent}%", 2),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
// Get initial state
{
@ -172,21 +177,26 @@ auto waybar::modules::Backlight::update() -> void {
return;
}
const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_->set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
getState(percent);
if (best->get_powered()) {
event_box_.show();
const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
getState(percent);
} else {
event_box_.hide();
}
} else {
if (!previous_best_.has_value()) {
return;
}
label_->set_markup("");
label_.set_markup("");
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
// Call parent update
AButton::update();
ALabel::update();
}
template <class ForwardIt>
@ -215,6 +225,7 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
const char *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
auto found =
std::find_if(first, last, [name](const auto &device) { return device.name() == name; });
@ -225,10 +236,14 @@ void waybar::modules::Backlight::upsert_device(ForwardIt first, ForwardIt last,
if (max != nullptr) {
found->set_max(std::stoi(max));
}
if (power != nullptr) {
found->set_powered(std::stoi(power) == 0);
}
} else {
const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
const int max_int = max == nullptr ? 0 : std::stoi(max);
*inserter = BacklightDev{name, actual_int, max_int};
const bool power_bool = power == nullptr ? true : std::stoi(power) == 0;
*inserter = BacklightDev{name, actual_int, max_int, power_bool};
++inserter;
}
}

View File

@ -6,7 +6,7 @@
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: AButton(config, "battery", id, "{capacity}%", 60) {
: ALabel(config, "battery", id, "{capacity}%", 60) {
#if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
@ -107,6 +107,15 @@ void waybar::modules::Battery::refreshBatteries() {
std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")) {
// Ignore non-system power supplies unless explicitly requested
if (!bat_defined && fs::exists(node.path() / "scope")) {
std::string scope;
std::ifstream(node.path() / "scope") >> scope;
if (g_ascii_strcasecmp(scope.data(), "device") == 0) {
continue;
}
}
check_map[node.path()] = true;
auto search = batteries_.find(node.path());
if (search == batteries_.end()) {
@ -233,6 +242,10 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
bool total_energy_full_design_exists = false;
uint32_t total_capacity = 0;
bool total_capacity_exists = false;
uint32_t time_to_empty_now = 0;
bool time_to_empty_now_exists = false;
uint32_t time_to_full_now = 0;
bool time_to_full_now_exists = false;
std::string status = "Unknown";
for (auto const& item : batteries_) {
@ -260,6 +273,16 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
std::ifstream(bat / "current_avg") >> current_now;
}
if (fs::exists(bat / "time_to_empty_now")) {
time_to_empty_now_exists = true;
std::ifstream(bat / "time_to_empty_now") >> time_to_empty_now;
}
if (fs::exists(bat / "time_to_full_now")) {
time_to_full_now_exists = true;
std::ifstream(bat / "time_to_full_now") >> time_to_full_now;
}
uint32_t voltage_now = 0;
bool voltage_now_exists = false;
if (fs::exists(bat / "voltage_now")) {
@ -470,7 +493,9 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
}
}
if (!adapter_.empty() && status == "Discharging") {
// Give `Plugged` higher priority over `Not charging`.
// So in a setting where TLP is used, `Plugged` is shown when the threshold is reached
if (!adapter_.empty() && (status == "Discharging" || status == "Not charging")) {
bool online;
std::string current_status;
std::ifstream(adapter_ / "online") >> online;
@ -479,8 +504,16 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
}
float time_remaining{0.0f};
if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (status == "Discharging" && time_to_empty_now_exists) {
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 1000.0f;
} else if (status == "Discharging" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && time_to_full_now_exists) {
if (time_to_full_now_exists && (time_to_full_now != 0))
time_remaining = -(float)time_to_full_now / 1000.0f;
// If we've turned positive it means the battery is past 100% and so just report that as no
// time remaining
if (time_remaining > 0.0f) time_remaining = 0.0f;
} else if (status == "Charging" && total_energy_exists && total_energy_full_exists &&
total_power_exists) {
if (total_power != 0)
@ -493,7 +526,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
float calculated_capacity{0.0f};
if (total_capacity_exists) {
if (total_capacity > 0.0f)
calculated_capacity = (float)total_capacity;
calculated_capacity = (float)total_capacity / batteries_.size();
else if (total_energy_full_exists && total_energy_exists) {
if (total_energy_full > 0.0f)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
@ -611,14 +644,14 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
button_.get_style_context()->remove_class(old_status_);
label_.get_style_context()->remove_class(old_status_);
}
button_.get_style_context()->add_class(status);
label_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
@ -632,10 +665,10 @@ auto waybar::modules::Battery::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_->set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -80,7 +80,7 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned
} // namespace
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: AButton(config, "bluetooth", id, " {status}", 10),
: ALabel(config, "bluetooth", id, " {status}", 10),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_BLUETOOTH},
#endif
@ -190,10 +190,10 @@ auto waybar::modules::Bluetooth::update() -> void {
format_.empty() ? event_box_.hide() : event_box_.show();
auto update_style_context = [this](const std::string& style_class, bool in_next_state) {
if (in_next_state && !button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->add_class(style_class);
} else if (!in_next_state && button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->remove_class(style_class);
if (in_next_state && !label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->add_class(style_class);
} else if (!in_next_state && label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->remove_class(style_class);
}
};
update_style_context("discoverable", cur_controller_.discoverable);
@ -205,7 +205,7 @@ auto waybar::modules::Bluetooth::update() -> void {
update_style_context(state, true);
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::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
@ -246,7 +246,7 @@ auto waybar::modules::Bluetooth::update() -> void {
device_enumerate_.erase(0, 1);
}
}
button_.set_tooltip_text(fmt::format(
label_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
@ -260,7 +260,7 @@ auto waybar::modules::Bluetooth::update() -> void {
}
// Call parent update
AButton::update();
ALabel::update();
}
// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is

View File

@ -5,6 +5,7 @@
#include <ctime>
#include <iomanip>
#include <regex>
#include <sstream>
#include <type_traits>
@ -18,7 +19,7 @@
using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: AButton(config, "clock", id, "{:%H:%M}", 60, false, false, true),
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0),
is_calendar_in_tooltip_(false),
is_timezoned_list_in_tooltip_(false) {
@ -65,6 +66,11 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
if (config_["on-scroll"][kCalendarPlaceholder].isInt()) {
calendar_shift_init_ =
date::months{config_["on-scroll"].get(kCalendarPlaceholder, 0).asInt()};
event_box_.add_events(Gdk::LEAVE_NOTIFY_MASK);
event_box_.signal_leave_notify_event().connect([this](GdkEventCrossing*) {
calendar_shift_ = date::months{0};
return false;
});
}
}
@ -74,6 +80,22 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
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] {
dp.emit();
auto now = std::chrono::system_clock::now();
@ -96,8 +118,14 @@ bool waybar::modules::Clock::is_timezone_fixed() {
auto waybar::modules::Clock::update() -> void {
auto time_zone = current_timezone();
auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now) +
calendar_shift_)};
waybar_time wtime = {locale_,
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 now_shifted = date::sys_days{shifted_date} + (now - date::floor<date::days>(now));
waybar_time shifted_wtime = {
locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now_shifted))};
std::string text = "";
if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt
@ -107,24 +135,28 @@ auto waybar::modules::Clock::update() -> void {
} else {
text = fmt::format(format_, wtime);
}
label_->set_markup(text);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
std::string calendar_lines{""};
std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) calendar_lines = calendar_text(wtime);
if (is_timezoned_list_in_tooltip_) timezoned_time_lines = timezones_text(&now);
if (is_calendar_in_tooltip_) {
calendar_lines = calendar_text(shifted_wtime);
}
if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now);
}
auto tooltip_format = config_["tooltip-format"].asString();
text =
fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
button_.set_tooltip_markup(text);
text = fmt::format(tooltip_format, shifted_wtime,
fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text);
}
}
// Call parent update
AButton::update();
ALabel::update();
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
@ -136,7 +168,7 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
auto dir = AModule::getScrollDir(e);
// Shift calendar date
if (calendar_shift_init_.count() > 0) {
if (calendar_shift_init_.count() != 0) {
if (dir == SCROLL_DIR::UP)
calendar_shift_ += calendar_shift_init_;
else
@ -168,73 +200,70 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
const auto ymd{date::year_month_day{daypoint}};
if (calendar_cached_ymd_ == ymd) return calendar_cached_text_;
if (calendar_cached_ymd_ == ymd) {
return calendar_cached_text_;
}
const auto curr_day{(calendar_shift_init_.count() > 0 && calendar_shift_.count() != 0)
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 week_format{config_["format-calendar-weekdays"].isString()
? config_["format-calendar-weekdays"].asString()
: ""};
const auto wn_format{config_["format-calendar-weeks"].isString()
? config_["format-calendar-weeks"].asString()
: ""};
const auto first_dow = first_day_of_week();
std::stringstream os;
const auto first_dow = first_day_of_week();
int ws{0}; // weeks-pos: side(1 - left, 2 - right)
enum class WeeksSide {
LEFT,
RIGHT,
HIDDEN,
};
WeeksSide weeks_pos = WeeksSide::HIDDEN;
if (config_["calendar-weeks-pos"].isString()) {
if (config_["calendar-weeks-pos"].asString() == "left") {
ws = 1;
weeks_pos = WeeksSide::LEFT;
// Add paddings before the header
os << std::string(4, ' ');
os << std::string(3 + fmt_weeks_left_pad_, ' ');
} else if (config_["calendar-weeks-pos"].asString() == "right") {
ws = 2;
weeks_pos = WeeksSide::RIGHT;
}
}
weekdays_header(first_dow, os);
// First week prefixed with spaces if needed.
auto wd = date::weekday(ym / 1);
// 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();
date::sys_days lwd{static_cast<date::sys_days>(ym / 1) + date::days{7 - empty_days}};
if (first_dow == date::Monday) {
lwd -= date::days{1};
}
/* Print weeknumber on the left for the first row*/
if (ws == 1) {
os << fmt::format(wn_format, lwd);
os << ' ';
lwd += date::weeks{1};
if (weeks_pos == WeeksSide::LEFT) {
os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
}
if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' ');
}
auto last_day = (ym / date::literals::last).day();
for (auto d = date::day(1); d <= last_day; ++d, ++wd) {
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 (ws == 2) {
os << ' ';
os << fmt::format(wn_format, lwd);
lwd += date::weeks{1};
if (weeks_pos == WeeksSide::RIGHT) {
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
}
os << '\n';
if (ws == 1) {
os << fmt::format(wn_format, lwd);
os << ' ';
lwd += date::weeks{1};
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();
@ -242,17 +271,17 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
} else {
os << "<b><u>" << date::format("%e", d) << "</u></b>";
}
} else if (config_["format-calendar"].isString()) {
os << fmt::format(config_["format-calendar"].asString(), date::format("%e", d));
} else
os << date::format("%e", d);
} else {
os << fmt::format(fmt_str_calendar_, date::format("%e", d));
}
/*Print weeks on the right when the endings with spaces*/
if (ws == 2 && d == last_day) {
if (weeks_pos == WeeksSide::RIGHT && d == last_day) {
empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding());
if (empty_days > 0) {
os << std::string(empty_days * 3 + 1, ' ');
os << fmt::format(wn_format, lwd);
if (empty_days > 0 && empty_days < 7) {
os << std::string(empty_days * 3, ' ');
}
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
}
}
@ -262,12 +291,14 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
return result;
}
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std::ostream& os)
auto waybar::modules::Clock::weekdays_header(const date::weekday& first_week_day, std::ostream& os)
-> void {
std::stringstream res;
auto wd = first_dow;
auto wd = first_week_day;
do {
if (wd != first_dow) res << ' ';
if (wd != first_week_day) {
res << ' ';
}
Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length();
@ -278,8 +309,8 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
}
const std::string pad(2 - clen, ' ');
res << pad << wd_ustring;
} while (++wd != first_dow);
res << "\n";
} while (++wd != first_week_day);
res << '\n';
if (config_["format-calendar-weekdays"].isString()) {
os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str());
@ -303,7 +334,7 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
timezone = date::current_zone();
}
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))};
os << fmt::format(format_, wtime) << "\n";
os << fmt::format(format_, wtime) << '\n';
}
return os.str();
}

View File

@ -10,7 +10,7 @@
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: AButton(config, "cpu", id, "{usage}%", 10) {
: ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -23,7 +23,7 @@ auto waybar::modules::Cpu::update() -> void {
auto [cpu_usage, tooltip] = getCpuUsage();
auto [max_frequency, min_frequency, avg_frequency] = getCpuFrequency();
if (tooltipEnabled()) {
button_.set_tooltip_text(tooltip);
label_.set_tooltip_text(tooltip);
}
auto format = format_;
auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
@ -39,7 +39,6 @@ auto waybar::modules::Cpu::update() -> void {
auto icons = std::vector<std::string>{state};
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("load", cpu_load));
store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
store.push_back(fmt::arg("max_frequency", max_frequency));
@ -52,11 +51,11 @@ auto waybar::modules::Cpu::update() -> void {
auto icon_format = fmt::format("icon{}", core_i);
store.push_back(fmt::arg(icon_format.c_str(), getIcon(cpu_usage[i], icons)));
}
label_->set_markup(fmt::vformat(format, store));
label_.set_markup(fmt::vformat(format, store));
}
// Call parent update
AButton::update();
ALabel::update();
}
double waybar::modules::Cpu::getCpuLoad() {

View File

@ -4,7 +4,7 @@
waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
const Json::Value& config)
: AButton(config, "custom-" + name, id, "{}"),
: ALabel(config, "custom-" + name, id, "{}"),
name_(name),
id_(id),
percentage_(0),
@ -21,6 +21,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
waybar::modules::Custom::~Custom() {
if (pid_ != -1) {
killpg(pid_, SIGTERM);
waitpid(pid_, NULL, 0);
pid_ = -1;
}
}
@ -103,13 +104,13 @@ void waybar::modules::Custom::handleEvent() {
}
bool waybar::modules::Custom::handleScroll(GdkEventScroll* e) {
auto ret = AButton::handleScroll(e);
auto ret = ALabel::handleScroll(e);
handleEvent();
return ret;
}
bool waybar::modules::Custom::handleToggle(GdkEventButton* const& e) {
auto ret = AButton::handleToggle(e);
auto ret = ALabel::handleToggle(e);
handleEvent();
return ret;
}
@ -131,33 +132,33 @@ auto waybar::modules::Custom::update() -> void {
if (str.empty()) {
event_box_.hide();
} else {
label_->set_markup(str);
label_.set_markup(str);
if (tooltipEnabled()) {
if (text_ == tooltip_) {
if (button_.get_tooltip_markup() != str) {
button_.set_tooltip_markup(str);
if (label_.get_tooltip_markup() != str) {
label_.set_tooltip_markup(str);
}
} else {
if (button_.get_tooltip_markup() != tooltip_) {
button_.set_tooltip_markup(tooltip_);
if (label_.get_tooltip_markup() != tooltip_) {
label_.set_tooltip_markup(tooltip_);
}
}
}
auto classes = button_.get_style_context()->list_classes();
auto classes = label_.get_style_context()->list_classes();
for (auto const& c : classes) {
if (c == id_) continue;
button_.get_style_context()->remove_class(c);
label_.get_style_context()->remove_class(c);
}
for (auto const& c : class_) {
button_.get_style_context()->add_class(c);
label_.get_style_context()->add_class(c);
}
button_.get_style_context()->add_class("flat");
button_.get_style_context()->add_class("text-button");
label_.get_style_context()->add_class("flat");
label_.get_style_context()->add_class("text-button");
event_box_.show();
}
}
// Call parent update
AButton::update();
ALabel::update();
}
void waybar::modules::Custom::parseOutputRaw() {

View File

@ -3,7 +3,7 @@
using namespace waybar::util;
waybar::modules::Disk::Disk(const std::string& id, const Json::Value& config)
: AButton(config, "disk", id, "{}%", 30), path_("/") {
: ALabel(config, "disk", id, "{}%", 30), path_("/") {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -58,7 +58,7 @@ auto waybar::modules::Disk::update() -> void {
event_box_.hide();
} else {
event_box_.show();
label_->set_markup(
label_.set_markup(
fmt::format(format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
@ -70,12 +70,12 @@ auto waybar::modules::Disk::update() -> void {
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
button_.set_tooltip_text(
label_.set_tooltip_text(
fmt::format(tooltip_format, stats.f_bavail * 100 / stats.f_blocks, fmt::arg("free", free),
fmt::arg("percentage_free", stats.f_bavail * 100 / stats.f_blocks),
fmt::arg("used", used), fmt::arg("percentage_used", percentage_used),
fmt::arg("total", total), fmt::arg("path", path_)));
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -95,15 +95,37 @@ void IPC::parseIPC(const std::string& ev) {
for (auto& [eventname, handler] : callbacks) {
if (eventname == request) {
handler(ev);
handler->onEvent(ev);
}
}
}
void IPC::registerForIPC(const std::string& ev, std::function<void(const std::string&)> fn) {
void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {
if (!ev_handler) {
return;
}
callbackMutex.lock();
callbacks.emplace_back(std::make_pair(ev, fn));
callbacks.emplace_back(std::make_pair(ev, ev_handler));
callbackMutex.unlock();
}
void IPC::unregisterForIPC(EventHandler* ev_handler) {
if (!ev_handler) {
return;
}
callbackMutex.lock();
for (auto it = callbacks.begin(); it != callbacks.end();) {
auto it_current = it;
it++;
auto& [eventname, handler] = *it_current;
if (handler == ev_handler) {
callbacks.erase(it_current);
}
}
callbackMutex.unlock();
}
@ -168,4 +190,4 @@ std::string IPC::getSocket1Reply(const std::string& rq) {
return std::string(buffer);
}
} // namespace waybar::modules::hyprland
} // namespace waybar::modules::hyprland

View File

@ -11,7 +11,7 @@
namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: AButton(config, "language", id, "{}", 0, true), bar_(bar) {
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
@ -21,40 +21,45 @@ Language::Language(const std::string& id, const Bar& bar, const Json::Value& con
// get the active layout when open
initLanguage();
button_.hide();
AButton::update();
label_.hide();
ALabel::update();
// register for hyprland ipc
gIPC->registerForIPC("activelayout", [&](const std::string& ev) { this->onEvent(ev); });
gIPC->registerForIPC("activelayout", this);
}
Language::~Language() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto Language::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
if (!format_.empty()) {
button_.show();
label_->set_markup(layoutName_);
label_.show();
label_.set_markup(layoutName_);
} else {
button_.hide();
label_.hide();
}
AButton::update();
ALabel::update();
}
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
auto keebName = ev.substr(0, ev.find_last_of(','));
keebName = keebName.substr(keebName.find_first_of('>') + 2);
auto kbName = ev.substr(ev.find_last_of('>') + 1, ev.find_first_of(','));
auto layoutName = ev.substr(ev.find_first_of(',') + 1);
if (config_.isMember("keyboard-name") && keebName != config_["keyboard-name"].asString())
if (config_.isMember("keyboard-name") && kbName != config_["keyboard-name"].asString())
return; // ignore
const auto BRIEFNAME = getShortFrom(layoutName);
const auto briefName = getShortFrom(layoutName);
if (config_.isMember("format-" + BRIEFNAME)) {
const auto PROPNAME = "format-" + BRIEFNAME;
layoutName = fmt::format(format_, config_[PROPNAME].asString());
if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString());
} else {
layoutName = fmt::format(format_, layoutName);
}
@ -71,19 +76,30 @@ void Language::onEvent(const std::string& ev) {
}
void Language::initLanguage() {
const auto INPUTDEVICES = gIPC->getSocket1Reply("devices");
const auto inputDevices = gIPC->getSocket1Reply("devices");
if (!config_.isMember("keyboard-name")) return;
const auto KEEBNAME = config_["keyboard-name"].asString();
const auto kbName = config_["keyboard-name"].asString();
try {
auto searcher = INPUTDEVICES.substr(INPUTDEVICES.find(KEEBNAME) + KEEBNAME.length());
searcher = searcher.substr(searcher.find("Keyboard at"));
searcher = searcher.substr(searcher.find("keymap:") + 7);
auto searcher = kbName.empty()
? inputDevices
: inputDevices.substr(inputDevices.find(kbName) + kbName.length());
searcher = searcher.substr(searcher.find("keymap:") + 8);
searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
layoutName_ = searcher;
auto layoutName = std::string{};
const auto briefName = getShortFrom(searcher);
if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString());
} else {
layoutName = fmt::format(format_, searcher);
}
layoutName = waybar::util::sanitize_string(layoutName);
layoutName_ = layoutName;
spdlog::debug("hyprland language initLanguage found {}", layoutName_);

View File

@ -0,0 +1,62 @@
#include "modules/hyprland/submap.hpp"
#include <spdlog/spdlog.h>
#include <util/sanitize_str.hpp>
namespace waybar::modules::hyprland {
Submap::Submap(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "submap", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
label_.hide();
ALabel::update();
// register for hyprland ipc
gIPC->registerForIPC("submap", this);
}
Submap::~Submap() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto Submap::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
if (submap_.empty()) {
event_box_.hide();
} else {
label_.set_markup(fmt::format(format_, submap_));
if (tooltipEnabled()) {
label_.set_tooltip_text(submap_);
}
event_box_.show();
}
// Call parent update
ALabel::update();
}
void Submap::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
if (ev.find("submap") == std::string::npos) {
return;
}
auto submapName = ev.substr(ev.find_last_of('>') + 1);
submapName = waybar::util::sanitize_string(submapName);
submap_ = submapName;
spdlog::debug("hyprland submap onevent with {}", submap_);
dp.emit();
}
} // namespace waybar::modules::hyprland

View File

@ -25,7 +25,13 @@ Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
ALabel::update();
// register for hyprland ipc
gIPC->registerForIPC("activewindow", [&](const std::string& ev) { this->onEvent(ev); });
gIPC->registerForIPC("activewindow", this);
}
Window::~Window() {
gIPC->unregisterForIPC(this);
// wait for possible event handler to finish
std::lock_guard<std::mutex> lg(mutex_);
}
auto Window::update() -> void {
@ -43,24 +49,26 @@ auto Window::update() -> void {
ALabel::update();
}
uint Window::getActiveWorkspaceID(std::string monitorName) {
int Window::getActiveWorkspaceID(std::string monitorName) {
auto cmd = waybar::util::command::exec("hyprctl monitors -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto monitor = std::find_if(json.begin(), json.end(),
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
assert(monitor != std::end(json));
return (*monitor)["activeWorkspace"]["id"].as<uint>();
if (monitor == std::end(json)) {
return 0;
}
return (*monitor)["activeWorkspace"]["id"].as<int>();
}
std::string Window::getLastWindowTitle(uint workspaceID) {
std::string Window::getLastWindowTitle(int workspaceID) {
auto cmd = waybar::util::command::exec("hyprctl workspaces -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) {
return workspace["id"].as<uint>() == workspaceID;
return workspace["id"].as<int>() == workspaceID;
});
if (workspace == std::end(json)) {
@ -89,4 +97,4 @@ void Window::onEvent(const std::string& ev) {
dp.emit();
}
} // namespace waybar::modules::hyprland
} // namespace waybar::modules::hyprland

View File

@ -8,7 +8,7 @@ bool waybar::modules::IdleInhibitor::status = false;
waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: AButton(config, "idle_inhibitor", id, "{status}", 0, false, true),
: ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
bar_(bar),
idle_inhibitor_(nullptr),
pid_(-1) {
@ -49,13 +49,13 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
button_.get_style_context()->remove_class("deactivated");
label_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
button_.get_style_context()->remove_class("activated");
label_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
@ -63,11 +63,11 @@ auto waybar::modules::IdleInhibitor::update() -> void {
}
std::string status_text = status ? "activated" : "deactivated";
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
button_.get_style_context()->add_class(status_text);
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
button_.set_tooltip_markup(
label_.set_tooltip_markup(
status ? fmt::format(config_["tooltip-format-activated"].isString()
? config_["tooltip-format-activated"].asString()
: "{status}",
@ -80,7 +80,7 @@ auto waybar::modules::IdleInhibitor::update() -> void {
fmt::arg("icon", getIcon(0, status_text))));
}
// Call parent update
AButton::update();
ALabel::update();
}
void waybar::modules::IdleInhibitor::toggleStatus() {
@ -124,6 +124,6 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
}
}
AButton::handleToggle(e);
ALabel::handleToggle(e);
return true;
}

59
src/modules/image.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "modules/image.hpp"
#include <spdlog/spdlog.h>
waybar::modules::Image::Image(const std::string& name, const std::string& id,
const Json::Value& config)
: AModule(config, "image-" + name, id, "{}") {
event_box_.add(image_);
dp.emit();
path_ = config["path"].asString();
size_ = config["size"].asInt();
interval_ = config_["interval"].asInt();
if (size_ == 0) {
size_ = 16;
}
if (interval_ == 0) {
interval_ = INT_MAX;
}
delayWorker();
}
void waybar::modules::Image::delayWorker() {
thread_ = [this] {
dp.emit();
auto interval = std::chrono::seconds(interval_);
thread_.sleep_for(interval);
};
}
void waybar::modules::Image::refresh(int sig) {
if (sig == SIGRTMIN + config_["signal"].asInt()) {
thread_.wake_up();
}
}
auto waybar::modules::Image::update() -> void {
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
if (Glib::file_test(path_, Glib::FILE_TEST_EXISTS))
pixbuf = Gdk::Pixbuf::create_from_file(path_, size_, size_);
else
pixbuf = {};
if (pixbuf) {
image_.set(pixbuf);
image_.show();
} else {
image_.clear();
image_.hide();
}
AModule::update();
}

View File

@ -98,7 +98,7 @@ auto getInhibitors(const Json::Value& config) -> std::string {
namespace waybar::modules {
Inhibitor::Inhibitor(const std::string& id, const Bar& bar, const Json::Value& config)
: AButton(config, "inhibitor", id, "{status}", true),
: ALabel(config, "inhibitor", id, "{status}", true),
dbus_(::dbus()),
inhibitors_(::getInhibitors(config)) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
@ -117,16 +117,16 @@ auto Inhibitor::activated() -> bool { return handle_ != -1; }
auto Inhibitor::update() -> void {
std::string status_text = activated() ? "activated" : "deactivated";
button_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
button_.get_style_context()->add_class(status_text);
label_.get_style_context()->remove_class(activated() ? "deactivated" : "activated");
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
button_.set_tooltip_text(status_text);
label_.set_tooltip_text(status_text);
}
return AButton::update();
return ALabel::update();
}
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
@ -142,7 +142,7 @@ auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
}
}
return AButton::handleToggle(e);
return ALabel::handleToggle(e);
}
} // namespace waybar::modules

View File

@ -1,7 +1,7 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: AButton(config, "memory", id, "{}%", 30) {
: ALabel(config, "memory", id, "{}%", 30) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -55,7 +55,7 @@ auto waybar::modules::Memory::update() -> void {
} else {
event_box_.show();
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::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
@ -67,7 +67,7 @@ auto waybar::modules::Memory::update() -> void {
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
button_.set_tooltip_text(fmt::format(
label_.set_tooltip_text(fmt::format(
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
@ -75,12 +75,12 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} else {
button_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
} else {
event_box_.hide();
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -15,7 +15,7 @@ namespace waybar::modules {
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: AButton(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
@ -47,7 +47,7 @@ auto waybar::modules::MPD::update() -> void {
context_.update();
// Call parent update
AButton::update();
ALabel::update();
}
void waybar::modules::MPD::queryMPD() {
@ -88,15 +88,20 @@ std::string waybar::modules::MPD::getFilename() const {
void waybar::modules::MPD::setLabel() {
if (connection_ == nullptr) {
button_.get_style_context()->add_class("disconnected");
button_.get_style_context()->remove_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
label_.get_style_context()->add_class("disconnected");
label_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString()
: "disconnected";
label_->set_markup(format);
if (format.empty()) {
label_.set_markup(format);
label_.show();
} else {
label_.hide();
}
if (tooltipEnabled()) {
std::string tooltip_format;
@ -104,12 +109,11 @@ void waybar::modules::MPD::setLabel() {
? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)";
// Nothing to format
button_.set_tooltip_text(tooltip_format);
label_.set_tooltip_text(tooltip_format);
}
return;
} else {
button_.get_style_context()->remove_class("disconnected");
}
label_.get_style_context()->remove_class("disconnected");
auto format = format_;
Glib::ustring artist, album_artist, album, title;
@ -121,19 +125,19 @@ void waybar::modules::MPD::setLabel() {
if (stopped()) {
format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
button_.get_style_context()->add_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
label_.get_style_context()->add_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
} else {
button_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("stopped");
if (playing()) {
button_.get_style_context()->add_class("playing");
button_.get_style_context()->remove_class("paused");
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
} else if (paused()) {
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
button_.get_style_context()->add_class("paused");
button_.get_style_context()->remove_class("playing");
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
}
stateIcon = getStateIcon();
@ -169,17 +173,21 @@ void waybar::modules::MPD::setLabel() {
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try {
label_->set_markup(fmt::format(
format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)));
auto text = fmt::format(
format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon),
fmt::arg("filename", filename));
if (text.empty()) {
label_.hide();
} else {
label_.show();
label_.set_markup(text);
}
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
@ -198,7 +206,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon));
button_.set_tooltip_text(tooltip_text);
label_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}

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

@ -78,7 +78,7 @@ waybar::modules::Network::readBandwidthUsage() {
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: AButton(config, "network", id, DEFAULT_FORMAT, 60),
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
@ -95,11 +95,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#endif
frequency_(0.0) {
// Start with some "text" in the module's label_-> update() will then
// Start with some "text" in the module's label_. update() will then
// update it. Since the text should be different, update() will be able
// to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown.
label_->set_markup("<s></s>");
label_.set_markup("<s></s>");
auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) {
@ -309,8 +309,8 @@ auto waybar::modules::Network::update() -> void {
if (!alt_) {
auto state = getNetworkState();
if (!state_.empty() && button_.get_style_context()->has_class(state_)) {
button_.get_style_context()->remove_class(state_);
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
@ -322,8 +322,8 @@ auto waybar::modules::Network::update() -> void {
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!button_.get_style_context()->has_class(state)) {
button_.get_style_context()->add_class(state);
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
@ -349,8 +349,8 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (text.compare(label_->get_label()) != 0) {
label_->set_markup(text);
if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text);
if (text.empty()) {
event_box_.hide();
} else {
@ -382,16 +382,16 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (button_.get_tooltip_text() != tooltip_text) {
button_.set_tooltip_markup(tooltip_text);
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_markup(tooltip_text);
}
} else if (button_.get_tooltip_text() != text) {
button_.set_tooltip_markup(text);
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_markup(text);
}
}
// Call parent update
AButton::update();
ALabel::update();
}
bool waybar::modules::Network::checkInterface(std::string name) {

View File

@ -1,7 +1,7 @@
#include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: AButton(config, "pulseaudio", id, "{volume}%"),
: ALabel(config, "pulseaudio", id, "{volume}%"),
mainloop_(nullptr),
mainloop_api_(nullptr),
context_(nullptr),
@ -260,11 +260,12 @@ auto waybar::modules::Pulseaudio::update() -> void {
if (!alt_) {
std::string format_name = "format";
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
monitor_.find("a2dp-sink") != std::string::npos || // PipeWire
monitor_.find("bluez") != std::string::npos) {
format_name = format_name + "-bluetooth";
button_.get_style_context()->add_class("bluetooth");
label_.get_style_context()->add_class("bluetooth");
} else {
button_.get_style_context()->remove_class("bluetooth");
label_.get_style_context()->remove_class("bluetooth");
}
if (muted_) {
// Check muted bluetooth format exist, otherwise fallback to default muted format
@ -272,32 +273,38 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_name = "format";
}
format_name = format_name + "-muted";
button_.get_style_context()->add_class("muted");
button_.get_style_context()->add_class("sink-muted");
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
} else {
button_.get_style_context()->remove_class("muted");
button_.get_style_context()->remove_class("sink-muted");
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
}
format = config_[format_name].isString() ? config_[format_name].asString() : format;
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_) {
button_.get_style_context()->add_class("source-muted");
label_.get_style_context()->add_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
button_.get_style_context()->remove_class("source-muted");
label_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_->set_markup(fmt::format(
auto text = fmt::format(
format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
if (text.empty()) {
label_.hide();
} else {
label_.set_markup(text);
label_.show();
}
getState(volume_);
if (tooltipEnabled()) {
@ -305,16 +312,16 @@ auto waybar::modules::Pulseaudio::update() -> void {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
button_.set_tooltip_text(fmt::format(
label_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
} else {
button_.set_tooltip_text(desc_);
label_.set_tooltip_text(desc_);
}
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -3,7 +3,7 @@
#include <time.h>
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: AButton(config, "clock", id, "{:%H:%M}", 60) {
: ALabel(config, "clock", id, "{:%H:%M}", 60) {
thread_ = [this] {
dp.emit();
auto now = std::chrono::system_clock::now();
@ -19,17 +19,17 @@ auto waybar::modules::Clock::update() -> void {
auto now = std::chrono::system_clock::now();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
auto text = fmt::format(format_, localtime);
label_->set_markup(text);
label_.set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, localtime);
button_.set_tooltip_text(tooltip_text);
label_.set_tooltip_text(tooltip_text);
} else {
button_.set_tooltip_text(text);
label_.set_tooltip_text(text);
}
}
// Call parent update
AButton::update();
ALabel::update();
}

View File

@ -41,7 +41,7 @@ auto Sndio::connect_to_sndio() -> void {
}
Sndio::Sndio(const std::string &id, const Json::Value &config)
: AButton(config, "sndio", id, "{volume}%", 1, false, true),
: ALabel(config, "sndio", id, "{volume}%", 1, false, true),
hdl_(nullptr),
pfds_(0),
addr_(0),
@ -105,14 +105,20 @@ auto Sndio::update() -> void {
unsigned int vol = 100. * static_cast<double>(volume_) / static_cast<double>(maxval_);
if (volume_ == 0) {
button_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("muted");
} else {
button_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("muted");
}
label_->set_markup(fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_)));
auto text = fmt::format(format, fmt::arg("volume", vol), fmt::arg("raw_value", volume_));
if (text.empty()) {
label_.hide();
} else {
label_.set_markup(text);
label_.show();
}
AButton::update();
ALabel::update();
}
auto Sndio::set_desc(struct sioctl_desc *d, unsigned int val) -> void {

View File

@ -10,9 +10,6 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
watcher_(SNI::Watcher::getInstance()),
host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
spdlog::warn(
"For a functional tray you must have libappindicator-* installed and export "
"XDG_CURRENT_DESKTOP=Unity");
box_.set_name("tray");
event_box_.add(box_);
if (!id.empty()) {

View File

@ -18,7 +18,7 @@ const std::string Language::XKB_LAYOUT_NAMES_KEY = "xkb_layout_names";
const std::string Language::XKB_ACTIVE_LAYOUT_NAME_KEY = "xkb_active_layout_name";
Language::Language(const std::string& id, const Json::Value& config)
: AButton(config, "language", id, "{}", 0, true) {
: ALabel(config, "language", id, "{}", 0, true) {
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
@ -99,7 +99,7 @@ auto Language::update() -> void {
format_, fmt::arg("short", layout_.short_name),
fmt::arg("shortDescription", layout_.short_description), fmt::arg("long", layout_.full_name),
fmt::arg("variant", layout_.variant), fmt::arg("flag", layout_.country_flag())));
label_->set_markup(display_layout);
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(
@ -107,22 +107,22 @@ auto Language::update() -> void {
fmt::arg("shortDescription", layout_.short_description),
fmt::arg("long", layout_.full_name), fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag())));
button_.set_tooltip_markup(tooltip_display_layout);
label_.set_tooltip_markup(tooltip_display_layout);
} else {
button_.set_tooltip_markup(display_layout);
label_.set_tooltip_markup(display_layout);
}
}
event_box_.show();
// Call parent update
AButton::update();
ALabel::update();
}
auto Language::set_current_layout(std::string current_layout) -> void {
button_.get_style_context()->remove_class(layout_.short_name);
label_.get_style_context()->remove_class(layout_.short_name);
layout_ = layouts_map_[current_layout];
button_.get_style_context()->add_class(layout_.short_name);
label_.get_style_context()->add_class(layout_.short_name);
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {

View File

@ -5,7 +5,7 @@
namespace waybar::modules::sway {
Mode::Mode(const std::string& id, const Json::Value& config)
: AButton(config, "mode", id, "{}", 0, true) {
: ALabel(config, "mode", id, "{}", 0, true) {
ipc_.subscribe(R"(["mode"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Mode::onEvent));
// Launch worker
@ -42,14 +42,14 @@ auto Mode::update() -> void {
if (mode_.empty()) {
event_box_.hide();
} else {
label_->set_markup(fmt::format(format_, mode_));
label_.set_markup(fmt::format(format_, mode_));
if (tooltipEnabled()) {
button_.set_tooltip_text(mode_);
label_.set_tooltip_text(mode_);
}
event_box_.show();
}
// Call parent update
AButton::update();
ALabel::update();
}
} // namespace waybar::modules::sway

View File

@ -130,6 +130,10 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
// In a first pass, the maximum "num" value is computed to enqueue
// unnumbered workspaces behind numbered ones when computing the sort
// attribute.
//
// Note: if the 'alphabetical_sort' option is true, the user is in
// agreement that the "workspace prev/next" commands may not follow
// the order displayed in Waybar.
int max_num = -1;
for (auto &workspace : workspaces_) {
max_num = std::max(workspace["num"].asInt(), max_num);
@ -143,16 +147,19 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
}
}
std::sort(workspaces_.begin(), workspaces_.end(),
[](const Json::Value &lhs, const Json::Value &rhs) {
[this](const Json::Value &lhs, const Json::Value &rhs) {
auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString();
int l = lhs["sort"].asInt();
int r = rhs["sort"].asInt();
if (l == r) {
if (l == r || config_["alphabetical_sort"].asBool()) {
// In case both integers are the same, lexicographical
// sort. The code above already ensure that this will only
// happend in case of explicitly numbered workspaces.
//
// Additionally, if the config specifies to sort workspaces
// alphabetically do this here.
return lname < rname;
}

View File

@ -7,7 +7,7 @@
#endif
waybar::modules::Temperature::Temperature(const std::string& id, const Json::Value& config)
: AButton(config, "temperature", id, "{temperatureC}°C", 10) {
: ALabel(config, "temperature", id, "{temperatureC}°C", 10) {
#if defined(__FreeBSD__)
// try to read sysctl?
#else
@ -42,9 +42,9 @@ auto waybar::modules::Temperature::update() -> void {
auto format = format_;
if (critical) {
format = config_["format-critical"].isString() ? config_["format-critical"].asString() : format;
button_.get_style_context()->add_class("critical");
label_.get_style_context()->add_class("critical");
} else {
button_.get_style_context()->remove_class("critical");
label_.get_style_context()->remove_class("critical");
}
if (format.empty()) {
@ -55,21 +55,21 @@ auto waybar::modules::Temperature::update() -> void {
}
auto max_temp = config_["critical-threshold"].isInt() ? config_["critical-threshold"].asInt() : 0;
label_->set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
label_.set_markup(fmt::format(format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k),
fmt::arg("icon", getIcon(temperature_c, "", max_temp))));
if (tooltipEnabled()) {
std::string tooltip_format = "{temperatureC}°C";
if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("temperatureC", temperature_c),
fmt::arg("temperatureF", temperature_f),
fmt::arg("temperatureK", temperature_k)));
}
// Call parent update
AButton::update();
ALabel::update();
}
float waybar::modules::Temperature::getTemperature() {

View File

@ -252,8 +252,7 @@ const std::string UPower::getDeviceStatus(UpDeviceState& state) {
bool UPower::handleToggle(GdkEventButton* const& event) {
std::lock_guard<std::mutex> guard(m_Mutex);
showAltText = !showAltText;
dp.emit();
return true;
return AModule::handleToggle(event);
}
std::string UPower::timeToString(gint64 time) {

View File

@ -6,7 +6,13 @@
#include <algorithm>
#include <chrono>
#include <iostream>
#include "gdkmm/cursor.h"
#include "gdkmm/event.h"
#include "gdkmm/types.h"
#include "glibmm/fileutils.h"
#include "sigc++/functors/mem_fun.h"
#include "sigc++/functors/ptr_fun.h"
#if HAVE_CPU_LINUX
#include <sys/sysinfo.h>
@ -16,15 +22,34 @@
#include <time.h>
#endif
const static int LEFT_MOUSE_BUTTON_CODE = 1;
namespace waybar::modules {
User::User(const std::string& id, const Json::Value& config)
: AIconLabel(config, "user", id, "{user} {work_H}:{work_M}", 60, false, false, true) {
: AIconLabel(config, "user", id, "{user} {work_H}:{work_M}", 60, false, true, true) {
AIconLabel::box_.set_spacing(0);
if (AIconLabel::iconEnabled()) {
this->init_avatar(AIconLabel::config_);
}
this->init_update_worker();
}
bool User::handleToggle(GdkEventButton* const& e) {
if (AIconLabel::config_["open-on-click"].isBool() &&
AIconLabel::config_["open-on-click"].asBool() && e->button == LEFT_MOUSE_BUTTON_CODE) {
std::string openPath = this->get_user_home_dir();
if (AIconLabel::config_["open-path"].isString()) {
std::string customPath = AIconLabel::config_["open-path"].asString();
if (!customPath.empty()) {
openPath = std::move(customPath);
}
}
Gio::AppInfo::launch_default_for_uri("file:///" + openPath);
}
return true;
}
long User::uptime_as_seconds() {
long uptime = 0;
@ -45,9 +70,9 @@ long User::uptime_as_seconds() {
return uptime;
}
std::string User::get_user_login() { return Glib::get_user_name(); }
std::string User::get_user_login() const { return Glib::get_user_name(); }
std::string User::get_user_home_dir() { return Glib::get_home_dir(); }
std::string User::get_user_home_dir() const { return Glib::get_home_dir(); }
void User::init_update_worker() {
this->thread_ = [this] {
@ -74,7 +99,7 @@ void User::init_avatar(const Json::Value& config) {
this->init_default_user_avatar(width, width);
}
std::string User::get_default_user_avatar_path() {
std::string User::get_default_user_avatar_path() const {
return this->get_user_home_dir() + "/" + ".face";
}
@ -83,8 +108,12 @@ void User::init_default_user_avatar(int width, int height) {
}
void User::init_user_avatar(const std::string& path, int width, int height) {
this->pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);
AIconLabel::image_.set(this->pixbuf_);
if (Glib::file_test(path, Glib::FILE_TEST_EXISTS)) {
Glib::RefPtr<Gdk::Pixbuf> pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);
AIconLabel::image_.set(pixbuf_);
} else {
AIconLabel::box_.remove(AIconLabel::image_);
}
}
auto User::update() -> void {
@ -109,6 +138,6 @@ auto User::update() -> void {
fmt::arg("work_S", fmt::format("{:%S}", workSystemTimeSeconds)),
fmt::arg("user", systemUser));
ALabel::label_.set_markup(label);
ALabel::update();
AIconLabel::update();
}
}; // namespace waybar::modules

190
src/modules/wireplumber.cpp Normal file
View File

@ -0,0 +1,190 @@
#include "modules/wireplumber.hpp"
waybar::modules::Wireplumber::Wireplumber(const std::string& id, const Json::Value& config)
: ALabel(config, "wireplumber", id, "{volume}%"),
wp_core_(nullptr),
apis_(nullptr),
om_(nullptr),
pending_plugins_(0),
muted_(false),
volume_(0.0),
node_id_(0) {
wp_init(WP_INIT_ALL);
wp_core_ = wp_core_new(NULL, NULL);
apis_ = g_ptr_array_new_with_free_func(g_object_unref);
om_ = wp_object_manager_new();
prepare();
loadRequiredApiModules();
if (!wp_core_connect(wp_core_)) {
throw std::runtime_error("Could not connect to PipeWire\n");
}
g_signal_connect_swapped(om_, "installed", (GCallback)onObjectManagerInstalled, this);
activatePlugins();
dp.emit();
}
waybar::modules::Wireplumber::~Wireplumber() {
g_clear_pointer(&apis_, g_ptr_array_unref);
g_clear_object(&om_);
g_clear_object(&wp_core_);
}
uint32_t waybar::modules::Wireplumber::getDefaultNodeId(waybar::modules::Wireplumber* self) {
uint32_t id;
g_autoptr(WpPlugin) def_nodes_api = wp_plugin_find(self->wp_core_, "default-nodes-api");
if (!def_nodes_api) {
throw std::runtime_error("Default nodes API is not loaded\n");
}
g_signal_emit_by_name(def_nodes_api, "get-default-node", "Audio/Sink", &id);
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) {
throw std::runtime_error(fmt::format("Object '{}' not found\n", self->node_id_));
}
g_autoptr(WpProperties) properties =
WP_IS_PIPEWIRE_OBJECT(proxy) ? wp_pipewire_object_get_properties(WP_PIPEWIRE_OBJECT(proxy))
: wp_properties_new_empty();
g_autoptr(WpProperties) global_p = wp_global_proxy_get_global_properties(WP_GLOBAL_PROXY(proxy));
properties = wp_properties_ensure_unique_owner(properties);
wp_properties_add(properties, global_p);
wp_properties_set(properties, "object.id", NULL);
auto nick = wp_properties_get(properties, "node.nick");
auto description = wp_properties_get(properties, "node.description");
self->node_name_ = nick ? nick : description;
}
void waybar::modules::Wireplumber::updateVolume(waybar::modules::Wireplumber* self) {
double vol;
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 (!variant) {
auto err = fmt::format("Node {} does not support volume\n", self->node_id_);
throw std::runtime_error(err);
}
g_variant_lookup(variant, "volume", "d", &vol);
g_variant_lookup(variant, "mute", "b", &self->muted_);
g_clear_pointer(&variant, g_variant_unref);
self->volume_ = std::round(vol * 100.0F);
self->dp.emit();
}
void waybar::modules::Wireplumber::onObjectManagerInstalled(waybar::modules::Wireplumber* self) {
self->node_id_ =
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");
updateVolume(self);
updateNodeName(self);
g_signal_connect_swapped(mixer_api, "changed", (GCallback)updateVolume, self);
}
void waybar::modules::Wireplumber::onPluginActivated(WpObject* p, GAsyncResult* res,
waybar::modules::Wireplumber* self) {
g_autoptr(GError) error = NULL;
if (!wp_object_activate_finish(p, res, &error)) {
throw std::runtime_error(error->message);
}
if (--self->pending_plugins_ == 0) {
wp_core_install_object_manager(self->wp_core_, self->om_);
}
}
void waybar::modules::Wireplumber::activatePlugins() {
for (uint16_t i = 0; i < apis_->len; i++) {
WpPlugin* plugin = static_cast<WpPlugin*>(g_ptr_array_index(apis_, i));
pending_plugins_++;
wp_object_activate(WP_OBJECT(plugin), WP_PLUGIN_FEATURE_ENABLED, NULL,
(GAsyncReadyCallback)onPluginActivated, this);
}
}
void waybar::modules::Wireplumber::prepare() {
wp_object_manager_add_interest(om_, WP_TYPE_NODE, NULL);
wp_object_manager_add_interest(om_, WP_TYPE_GLOBAL_PROXY, NULL);
wp_object_manager_request_object_features(om_, WP_TYPE_GLOBAL_PROXY,
WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL);
}
void waybar::modules::Wireplumber::loadRequiredApiModules() {
g_autoptr(GError) error = NULL;
if (!wp_core_load_component(wp_core_, "libwireplumber-module-default-nodes-api", "module", NULL,
&error)) {
throw std::runtime_error(error->message);
}
if (!wp_core_load_component(wp_core_, "libwireplumber-module-mixer-api", "module", NULL,
&error)) {
throw std::runtime_error(error->message);
}
g_ptr_array_add(apis_, wp_plugin_find(wp_core_, "default-nodes-api"));
g_ptr_array_add(apis_, ({
WpPlugin* p = wp_plugin_find(wp_core_, "mixer-api");
g_object_set(G_OBJECT(p), "scale", 1 /* cubic */, NULL);
p;
}));
}
auto waybar::modules::Wireplumber::update() -> void {
auto format = format_;
std::string tooltip_format;
if (muted_) {
format = config_["format-muted"].isString() ? config_["format-muted"].asString() : format;
label_.get_style_context()->add_class("muted");
} else {
label_.get_style_context()->remove_class("muted");
}
std::string markup = fmt::format(format, fmt::arg("node_name", node_name_),
fmt::arg("volume", volume_), fmt::arg("icon", getIcon(volume_)));
label_.set_markup(markup);
getState(volume_);
if (tooltipEnabled()) {
if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_),
fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_))));
} else {
label_.set_tooltip_text(node_name_);
}
}
// Call parent update
ALabel::update();
}

View File

@ -6,8 +6,10 @@
#include <algorithm>
#include <iterator>
#include <stdexcept>
#include <vector>
#include "client.hpp"
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
@ -66,10 +68,13 @@ auto WorkspaceManager::workspace_comparator() const
auto is_name_less = lhs->get_name() < rhs->get_name();
auto is_name_eq = lhs->get_name() == rhs->get_name();
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name());
if (sort_by_number_) {
return is_number_less;
try {
auto is_number_less = std::stoi(lhs->get_name()) < std::stoi(rhs->get_name());
return is_number_less;
} catch (std::invalid_argument) {
}
}
if (sort_by_name_) {
@ -162,8 +167,20 @@ WorkspaceManager::~WorkspaceManager() {
return;
}
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
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_);
workspace_manager_ = nullptr;
}
}
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {

9
subprojects/jsoncpp.wrap Normal file
View File

@ -0,0 +1,9 @@
[wrap-file]
directory = jsoncpp-1.9.5
source_url = https://github.com/open-source-parsers/jsoncpp/archive/1.9.5.tar.gz
source_filename = jsoncpp-1.9.5.tar.gz
source_hash = f409856e5920c18d0c2fb85276e24ee607d2a09b5e7d5f0a371368903c275da2
[provide]
jsoncpp = jsoncpp_dep