Compare commits

..

71 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
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
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
39 changed files with 1184 additions and 113 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

@ -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
@ -77,6 +81,7 @@
#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/image.hpp"
#include "modules/temperature.hpp"
#include "modules/user.hpp"

View File

@ -33,6 +33,9 @@ class Clock : public ALabel {
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

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

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

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

@ -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"
}
```

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

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

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

@ -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.16',
version: '0.9.17',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
@ -86,7 +86,10 @@ wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled()))
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or
get_option('logind').enabled() or
get_option('upower_glib').enabled() or
get_option('mpris').enabled()))
jsoncpp = dependency('jsoncpp', version : ['>=1.9.2'], fallback : ['jsoncpp', 'jsoncpp_dep'])
sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false)
@ -95,6 +98,7 @@ libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))
libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev'))
@ -151,6 +155,7 @@ src_files = files(
'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'
@ -333,6 +344,7 @@ executable(
libnl,
libnlgen,
upower_glib,
playerctl,
libpulse,
libjack,
libwireplumber,
@ -386,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')

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]);
@ -148,6 +156,8 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
}
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

@ -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")) {
@ -481,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)

View File

@ -5,6 +5,7 @@
#include <ctime>
#include <iomanip>
#include <regex>
#include <sstream>
#include <type_traits>
@ -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
@ -113,12 +141,16 @@ auto waybar::modules::Clock::update() -> void {
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));
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);
}
}
@ -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

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

View File

@ -49,18 +49,17 @@ auto Language::update() -> void {
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);
}
@ -77,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

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

@ -96,7 +96,12 @@ void waybar::modules::MPD::setLabel() {
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;
@ -107,9 +112,8 @@ void waybar::modules::MPD::setLabel() {
label_.set_tooltip_text(tooltip_format);
}
return;
} else {
label_.get_style_context()->remove_class("disconnected");
}
label_.get_style_context()->remove_class("disconnected");
auto format = format_;
Glib::ustring artist, album_artist, album, title;
@ -169,7 +173,7 @@ void waybar::modules::MPD::setLabel() {
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try {
label_.set_markup(fmt::format(
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),
@ -177,7 +181,13 @@ 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),
fmt::arg("filename", filename)));
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());
}

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

@ -295,10 +295,16 @@ auto waybar::modules::Pulseaudio::update() -> void {
}
}
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()) {

View File

@ -110,7 +110,13 @@ auto Sndio::update() -> void {
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();
}
ALabel::update();
}

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

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

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

@ -10,6 +10,7 @@
#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"
@ -26,6 +27,7 @@ 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, true, true) {
AIconLabel::box_.set_spacing(0);
if (AIconLabel::iconEnabled()) {
this->init_avatar(AIconLabel::config_);
}
@ -106,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) {
Glib::RefPtr<Gdk::Pixbuf> pixbuf_ = Gdk::Pixbuf::create_from_file(path, width, height);
AIconLabel::image_.set(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 {
@ -132,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

View File

@ -54,17 +54,25 @@ uint32_t waybar::modules::Wireplumber::getDefaultNodeId(waybar::modules::Wireplu
}
void waybar::modules::Wireplumber::updateNodeName(waybar::modules::Wireplumber* self) {
auto proxy = static_cast<WpPipewireObject*>(wp_object_manager_lookup(
self->om_, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u",
self->node_id_, NULL));
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_pipewire_object_get_properties(proxy);
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);
self->node_name_ = wp_properties_get(properties, "node.nick");
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) {
@ -120,6 +128,7 @@ void waybar::modules::Wireplumber::activatePlugins() {
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);
}
@ -156,8 +165,8 @@ auto waybar::modules::Wireplumber::update() -> void {
label_.get_style_context()->remove_class("muted");
}
std::string markup =
fmt::format(format, fmt::arg("node_name", node_name_), fmt::arg("volume", volume_));
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_);
@ -169,7 +178,8 @@ auto waybar::modules::Wireplumber::update() -> void {
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("node_name", node_name_),
fmt::arg("volume", volume_)));
fmt::arg("volume", volume_),
fmt::arg("icon", getIcon(volume_))));
} else {
label_.set_tooltip_text(node_name_);
}

View File

@ -9,6 +9,7 @@
#include <stdexcept>
#include <vector>
#include "client.hpp"
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
@ -166,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 {