Compare commits

..

384 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
fd417c0805 chore: 0.9.15 2022-11-03 09:43:05 +01:00
afa590f781 Merge pull request #1758 from Alexays/revert-1687-fix-custom-module-markup
Revert "Escape text in custom module"
2022-11-02 06:00:00 +01:00
df36ac3408 Revert "Escape text in custom module" 2022-11-02 05:59:50 +01:00
ebdf575d45 fix: lint 2022-10-28 14:44:48 +02:00
a4d27ea806 fix: checking router id in handleEvent function 2022-10-28 14:44:04 +02:00
a10266ceee fix: add power to tooltip format 2022-10-28 14:35:18 +02:00
31137c30fb fix custom module leaves zombie processes behind when bars are removed
fixes #1713.
2022-10-27 18:12:14 +08:00
c374c412d3 chore: remove unwanted file 2022-10-27 10:00:38 +02:00
2fec1d4907 fix: typo 2022-10-27 09:14:07 +02:00
be28ee3d7e fix(#1707): hide module when no controller found 2022-10-27 09:00:31 +02:00
2d7e21ed7d fix: lint 2022-10-26 17:26:15 +02:00
930a3e168b Merge pull request #1747 from bi4k8/taskbar-task-reorder 2022-10-26 17:25:55 +02:00
7948d03d25 Merge pull request #1744 from Quantenzitrone/master 2022-10-26 17:14:58 +02:00
ff61e7bf4e taskbar: implement drag-and-drop task reordering 2022-10-25 19:39:23 +00:00
64849f52c9 fixed memory module not rounding numbers 2022-10-22 02:31:14 +02:00
1374b0fce4 Merge pull request #1740 from ErikReider/gamemode-segfault-fix 2022-10-20 13:09:50 +02:00
1ceaff27c2 Fixed gamemode module segfaulting when disconnecting monitor 2022-10-20 12:38:52 +02:00
527017baca chore: update date wrap 2022-10-20 11:05:19 +02:00
f330e51472 fix: typo 2022-10-20 10:57:27 +02:00
411c6f4b4b chore: update catch 2022-10-20 10:56:47 +02:00
e8e8ccb6cf Merge pull request #1661 from asas1asas200/zeng-feat-improve_keyboard 2022-10-20 10:50:35 +02:00
a24f2d72a7 Merge pull request #1730 from herlev/hyprland-window-rewrite 2022-10-20 10:48:57 +02:00
ffa458223d Merge pull request #1724 from llyyr/fix-build-with-catch2 2022-10-20 10:47:52 +02:00
aa8bd51952 Merge pull request #1738 from pinselimo/fix-button-padding 2022-10-20 10:46:08 +02:00
05dbfe261a style: Revert set default minimal width of buttons to zero #1737 2022-10-20 10:34:20 +02:00
1f591e36f1 button: Hardcode min-width property set to zero
Buttons come with an intrinsic min-width but lack a method to alter this
property. Setting the requested size to zero has also no effect on it.
The only way found to work is to hard code the CSS into the button.
2022-10-20 10:31:11 +02:00
d0677c1801 Merge pull request #1737 from pinselimo/fix-button-padding 2022-10-19 14:59:49 +02:00
c18c6b080a Set default minimal width of buttons to zero
Even if all margins, padding and borders of buttons are removed the
label inside the buttons may still be padded if they are too short.
Setting the minimal width of buttons to zero fixes this issue.
2022-10-19 14:48:56 +02:00
7240611d87 Merge pull request #1736 from pinselimo/fix-battery-module 2022-10-19 13:39:20 +02:00
e1045381fe Fix linter 2022-10-19 13:30:28 +02:00
e660a3634d Fix linter 2022-10-19 13:29:05 +02:00
54e04b5a30 Refactor rewriteTitle 2022-10-19 13:25:08 +02:00
662a250705 Fix battery indicator crash on linux
A pre-processor flag was misspelled and is now corrected.
2022-10-19 13:15:21 +02:00
f72c1a54d3 Merge branch 'Alexays:master' into hyprland-window-rewrite 2022-10-19 12:18:22 +02:00
6b221133c2 Merge pull request #1703 from lbartoletti/freebsd_battery 2022-10-19 09:08:47 +02:00
d01fda6fae Merge pull request #1721 from herlev/sort-workspaces-by-number 2022-10-19 09:08:03 +02:00
692b90c995 fix build 2022-10-19 08:36:15 +02:00
dea2d721eb Merge pull request #1733 from ItsDrike/master 2022-10-18 19:45:42 +02:00
72a2ada82c remove clang-format lines 2022-10-18 19:30:43 +02:00
6156a62294 fix time_remaining. FreeBSD sysctl returns minutes and not hours 2022-10-18 19:30:43 +02:00
d4d35e2f89 apply clang-format 2022-10-18 19:30:43 +02:00
a58988ea9d Battery: replace #else by #elif defined(__linux__)
Cannot use #else here when inotify_init1() is hidden behind #if defined(__Linux__).

Co-authored-by: Jan Beich <jbeich@FreeBSD.org>
2022-10-18 19:30:43 +02:00
0ada5ac8b0 Battery::getAdapterStatus: better code format 2022-10-18 19:30:41 +02:00
1421163df3 remove useless include <sys/types.h> 2022-10-18 19:30:23 +02:00
9d5f0e45c0 Add test if there is battery 2022-10-18 19:30:23 +02:00
45e44e03bd Apply jbeich suggestion for if defined(__linux__) 2022-10-18 19:30:21 +02:00
830c5cd5d0 FreeBSD: Add support to battery
This commit aims to propose a FreeBSD to gain battery support using sysctl on hw.acpi.battery.*
2022-10-18 19:30:04 +02:00
90f206f92a Fix crash on quickly switching workspaces
The hyprland/window widget had an assertion ensuring that the output
from hyprctl matched the currently selected workspace id. However this
assertion fails if workspaces are switched too quickly, causing the
selected workspace to differ in id from the one in hyprctl, failing this
assertion which then crashes the entire program.

This fix simply changes this assertion into an if statement, and if a
mismatch is found, empty string is returned as the window name.
2022-10-18 18:36:00 +02:00
59e7f1974c Document hyprland/window rewrite option 2022-10-18 13:21:20 +02:00
97ae2ff343 Add rewrite option to hyprland/window 2022-10-18 13:18:43 +02:00
3d63080346 Document sort-by-number option in man page 2022-10-18 12:25:22 +02:00
cb842d9d50 Merge branch 'Alexays:master' into sort-workspaces-by-number 2022-10-18 12:19:00 +02:00
a7e6330078 Merge pull request #1729 from pinselimo/use_gtk_button_v2 2022-10-18 11:27:17 +02:00
93807b0b3e resources: Remove border effect on hover
Moves the ``border = none;`` attribute from workspace buttons to the
global scope. The hover effects on all buttons are now consistent in the
default stylesheet.
2022-10-18 11:25:20 +02:00
6e73c58e60 fix: lint 2022-10-18 09:01:45 +02:00
209225e381 Merge pull request #1701 from Dordovel/master 2022-10-18 09:01:00 +02:00
7746328daa Merge pull request #1667 from asas1asas200/zeng-feat-sway_scratchpad 2022-10-18 09:00:31 +02:00
c7d475ee86 Merge pull request #1728 from lilydjwg/fixpa 2022-10-18 08:45:45 +02:00
4ed13df092 Merge branch 'Alexays:master' into master 2022-10-17 19:00:21 +03:00
33c3ab35a8 Fix linter error (formatting) 2022-10-17 10:13:37 +02:00
4dfea72db0 Merge branch 'Alexays:master' into sort-workspaces-by-number 2022-10-17 10:01:12 +02:00
504132dc55 Merge pull request #1719 from herlev/master 2022-10-17 09:53:05 +02:00
debbfccf07 Merge pull request #1705 from lbartoletti/freebsd_temperature_use_thermal-zone_config 2022-10-17 09:52:54 +02:00
56ec72c31c Merge branch 'master' into master 2022-10-17 09:44:17 +02:00
27c6c96b37 Merge branch 'master' into freebsd_temperature_use_thermal-zone_config 2022-10-17 09:34:06 +02:00
8551c4bbe3 fix: lint 2022-10-17 09:19:00 +02:00
58362abfaf Merge pull request #1630 from duxovni/pow_format 2022-10-17 09:16:47 +02:00
2abeba2b52 Merge pull request #1679 from tomcharnock/master 2022-10-17 09:10:54 +02:00
bfa3adcfd6 Merge pull request #1120 from pinselimo/use_gtk_button_v2 2022-10-17 09:09:12 +02:00
2db6fc8b1b Merge pull request #1687 from sespiros/fix-custom-module-markup 2022-10-17 09:08:01 +02:00
c2dd296d31 Merge pull request #1704 from gunslingerfry/master 2022-10-17 09:07:26 +02:00
5b0c5ea9ce Merge pull request #1720 from IanManske/inhibitor-default-state 2022-10-17 09:06:26 +02:00
c7bb0ae0af Merge pull request #1636 from IsaacWoods/master 2022-10-17 09:05:03 +02:00
b2f90dffe1 Merge pull request #1710 from m-braunschweig/filename 2022-10-17 09:04:21 +02:00
f86dff60e6 utils: add sanitize_str to encode '&' etc.
gtk requires some chars (<>&"') to be encoded for them to render
properly. `sanitize_str` sanitizes raw strings that have such chars and
returns a properly encoded string
2022-10-17 00:31:19 +02:00
1db3c55b48 Fix build with catch2>=3.0.0 2022-10-16 19:21:43 +05:30
35254ee834 pulseaudio: disconnect on destruction 2022-10-16 15:24:17 +08:00
9a0013cb10 Add option to wlr/workspaces to sort workspaces by number 2022-10-15 01:44:58 +02:00
cca5227210 Add config value for inhibitor default state. 2022-10-13 21:47:57 -04:00
cf9d98a0be remove <optional> dependency 2022-10-13 23:49:41 +02:00
015409acaf Allow hyprland/window to show active window on a per monitor basis 2022-10-13 23:41:56 +02:00
2b735f44bc modules: Set tooltip on button
Mouse-over tooltips set on the label only appear once the mouse hovers
over exactly the label. Other apps (e.g. firefox) show the tooltip once
the pointer hovers the button. Not solely its label. With this commit we
get the same behaviour.
2022-10-12 10:25:30 +02:00
8fa5d9b838 modules: Set style-context on button
Fixes issue where the class parameters in style.css would have no
effect.

The CSS now references the GtkButton instead of the GtkLabel. Removing
all style-classes from the custom module GtkButton however removes
any properties set via style.css. Thus, the default classes 'flat' and
'text-button' are added on every update of these modules.
2022-10-12 10:25:30 +02:00
0012bcbd74 resources: Set button hover effects globally
Since now modules as well as workspaces are buttons, the fix for
the 'strange hover effects' has to be applied on a global level.
In return there is a nice hover effect also on the modules.
2022-10-12 10:25:30 +02:00
b8322c4b4b button: Add AButton class
The AButton class is designed as full a substitute to ALabel. The
GtkButton attribute 'button_' is initialized with a label. This
label can the be referenced by the subsequent inheritors of AButton
instead of the GtkLabel attribute 'label_' of ALabel.
For convenience a GtkLabel* 'label_' attribute is added to AButton.

If the button cannot be clicked it is disabled, effectively acting
like its label predecessor.

GtkButton seems to catch one-click mouse events regardless of the
flags set on it. Therefore, 'signal_pressed' is connected to a
function creating a fake GdkEventButton* and calling 'handleToggle'
(for details on this possible bug in GTK see:
https://stackoverflow.com/questions/45334911 )

In accordance with other GtkButtons (i.e. the sway/workspace ones)
set_relief(Gtk::RELIEF_NONE) is called on the 'button_' instance.
2022-10-12 10:25:29 +02:00
07050cf354 Merge branch 'Alexays:master' into master 2022-10-04 15:37:36 +03:00
ddf3e11240 remove clang-format lines 2022-10-04 11:28:32 +02:00
1ca660460a apply clang-format 2022-10-04 08:03:54 +02:00
0898236586 remove useless include <sys/types.h> 2022-10-04 07:37:05 +02:00
c3e91cd228 [FreeBSD] Use thermal-zone
The zone was hardcoded in #1702.
This commit allows to use the "thermal-zone"
variable.

Follow up #1702
2022-10-04 07:29:16 +02:00
c500c7d9a1 Fixed pulseaudio max-volume configuration. Fixed issue where volume stepping would cause the max volume to go above the max and never reach 0. 2022-09-30 15:25:12 -06:00
5da45ece9d Merge pull request #1702 from lbartoletti/freebsd_temperature 2022-09-30 21:24:05 +02:00
024777a5bc FreeBSD: Add support to temperature
This commit aims to propose a FreeBSD to gain temperature support using
sysctl on hw.acpi.thermal.tz0.temperature.
2022-09-30 21:12:28 +02:00
9758833027 added user module 2022-09-30 14:33:23 +03:00
9a958f6848 Merge branch 'master' of https://github.com/Alexays/Waybar 2022-09-22 09:52:45 +02:00
9e03bb61c7 Escape text in custom module 2022-09-16 01:19:44 +03:00
710f89599e Merge pull request #1686 from Alexays/revert-1685-master 2022-09-15 15:47:20 +02:00
d1700bf202 Revert "added checking router id in handleEvent function, because module does…" 2022-09-15 15:47:14 +02:00
e1b31db42b Merge pull request #1685 from Dordovel/master 2022-09-15 13:10:54 +02:00
52e9f624be added checking router id in handleEvent function, because module doesn't update state 2022-09-15 14:03:32 +03:00
e75eafcb34 Merge branch 'master' of https://github.com/tomcharnock/Waybar 2022-09-09 07:19:12 +00:00
6558a156b3 Add man entry for the ignored-sinks option 2022-09-09 00:54:32 +01:00
faf8954712 Add config option to ignore Pulseaudio Sinks
Fixes #1347
2022-09-09 00:51:25 +01:00
e58f1fd3e0 Merge pull request #1412 from eigenbrot/battery_zero_pad_minutes
Add battery format-time option for zero-padded minutes
2022-09-07 16:54:28 +02:00
6b83360e76 Add "{m}" battery format-time option for zero-pad minutes 2022-09-07 08:53:07 -06:00
03ca8de6d7 Update config.cpp 2022-09-07 10:33:57 +02:00
ac193ae669 Merge pull request #1646 from LukashonakV/ISSUE#1545
Issue#1545. Calendar scrolling opportunity
2022-09-07 09:16:59 +02:00
38d2815425 Merge pull request #1678 from asas1asas200/zeng-feat-idle_inhibitor_tooltip
feat(idle-inhibitor): add tooltip format
2022-09-05 17:52:27 +02:00
79f21c0d7b Merge pull request #1600 from leophys/master
Add support for reading the config path from env
2022-09-05 10:56:01 +02:00
0306c97173 Merge branch 'master' of https://github.com/Alexays/Waybar 2022-09-05 09:54:04 +02:00
8a82cdff16 Merge pull request #1659 from TheRealLorenz/master
Add 'max-volume' option to pulseaudio
2022-09-05 09:13:01 +02:00
29bdff5314 Merge pull request #1657 from vaxerski/hyprlandLanguage
Added a basic hyprland/language module
2022-09-05 09:12:17 +02:00
eb017347b8 Add support for reading the config path from env
This commit adds support to reading the config base path from the
environment variable `WAYBAR_CONFIG_DIR`. If it is set, but no
configuration is found there, it falls back to the previous mechanism
of using the default paths, without erroring.
2022-09-05 08:21:36 +02:00
912d7f8588 Making calculations uint64_t 2022-09-03 18:08:26 +02:00
5647146ac0 Added Discharging clause and corrected typo 2022-09-03 17:52:11 +02:00
af2a3f8bda Added alternative calculations for time remaining 2022-09-03 16:06:13 +02:00
55e83f90d1 feat(idle-inhibitor): add tooltip format 2022-09-03 19:21:32 +08:00
0d94853613 Added alternative variable calculations 2022-09-02 15:37:23 +02:00
120c68e014 Updated logic in battery module 2022-09-02 11:42:46 +02:00
4deb6d812d Merge pull request #1653 from kennypm/dsp
add JACK module
2022-09-02 08:12:57 +02:00
bc201fd0eb doc(sway/scratchpad): add man page 2022-08-31 16:27:25 +08:00
d2ff116c92 feat(sway/scratchpad): add some configs
Add some configs for displaying.
Remove draft codes.
2022-08-31 16:27:25 +08:00
e3342467fc feat(sway/scratchpad): add basic counter 2022-08-31 16:27:25 +08:00
ce10ce0d5e Merge pull request #1672 from asas1asas200/zeng-style-lint 2022-08-31 10:06:43 +02:00
4a929240df style(lint): fix some files lint 2022-08-31 15:51:50 +08:00
33d13af6d1 Merge pull request #1670 from akliuxingyuan/master 2022-08-30 20:43:51 +02:00
90878a5c98 Merge pull request #1669 from asas1asas200/zeng-fix-network 2022-08-30 20:43:03 +02:00
0d27949f0a scale icons for HiDPI monitor 2022-08-30 23:13:38 +08:00
f6322d2dd1 fix(network): dont escape essid in tooltip
Like #1256 , but escape by calling `set_tooltip_markup()`, because the
label text uses `set_markup()`.
2022-08-30 23:05:34 +08:00
330d166c82 Merge pull request #1668 from alex-courtis/1591-river-escape-window-and-mode 2022-08-29 08:26:40 +02:00
5f2dd99e6d #1591 river escape window and mode 2022-08-29 16:22:08 +10:00
8b03e38594 fix(keyboard): correct device-path config behavior 2022-08-24 14:08:34 +08:00
5944989a8a doc(keyboard): add deprecated warning 2022-08-24 02:41:12 +08:00
58a399b9af chore(ci, meson): add inotify dependency for BSD 2022-08-24 02:22:40 +08:00
dcd75b3b40 feat(keybaord): enable hotplug support
Use inotify listening devices path changes to implement hotplug support.
The new hotplug thread is also an event loop, so the interval value has
no effect.
The evdev is now open on demand.

Fix libinput_interface object life-time.
2022-08-23 23:30:16 +08:00
17f91391b6 Merge branch 'Alexays:master' into ISSUE#1545 2022-08-23 09:00:08 +00:00
061f4550f4 feat(keyboard): improve keyboard response time
Use libinput event for keyboard state updates.
The state will update when CAPS_LOCK, NUM_LOCK or SCROLL_LOCK has been
released,
`interval` will have no effect after this change.
2022-08-22 22:49:59 +08:00
fd24d7bcf6 Merge pull request #1660 from asas1asas200/master 2022-08-21 08:09:35 +02:00
51670f0506 Fix typo 2022-08-21 12:35:33 +08:00
4e930ba50a Add 'max-volume' option to pulseaudio. Fixes #1607 2022-08-20 22:21:57 +02:00
8839a86afe Merge branch 'Alexays:master' into ISSUE#1545 2022-08-19 13:52:52 +00:00
f4bfe777d9 oops 2022-08-18 20:56:26 -04:00
59e57ab9a0 man page and adjust default format 2022-08-18 17:05:04 -04:00
f00f30a5ae Merge pull request #5 from NotAShelf/hyprlandLanguage
Add man docs for Hyprland language module
2022-08-18 19:23:06 +02:00
40bc2e96db wording 2022-08-18 20:21:14 +03:00
9ac9dc368e Merge branch 'hyprlandLanguage' of https://github.com/vaxerski/Waybar into hyprlandLanguage 2022-08-18 20:16:42 +03:00
39c170bf10 remove one comment that I forgot to 2022-08-18 19:13:24 +02:00
5fea01300c Merge branch 'hyprlandLanguage' of github.com:NotAShelf/Waybar into hyprlandLanguage 2022-08-18 20:12:44 +03:00
b181cd04b6 update man docs for format-<lang> option 2022-08-18 20:11:44 +03:00
d786f9a0e6 Merge branch 'vaxerski:hyprlandLanguage' into hyprlandLanguage 2022-08-18 20:10:31 +03:00
c5910ae19a Merge branch 'hyprlandLanguage' of https://github.com/vaxerski/Waybar into hyprlandLanguage 2022-08-18 20:09:34 +03:00
ed6467e785 fix linter 2022-08-18 19:02:46 +02:00
43c3ca1d38 added the thing i was talking about 2022-08-18 18:59:34 +02:00
97f0d6fa42 remove redundant formatting 2022-08-18 19:35:40 +03:00
b8a68b8085 man documentation for hl language module 2022-08-18 19:32:26 +03:00
8881b9a6ef fix linter the most 2022-08-18 18:06:34 +02:00
e8942feefc fix linter more 2022-08-18 18:05:40 +02:00
a23d58e900 fix linter 2022-08-18 18:04:39 +02:00
16d5619f3b added a basic hyprland/language module 2022-08-18 18:00:27 +02:00
bcee4e15d3 fix: lint files 2022-08-18 15:22:25 +02:00
b7bd06ad8f Update window.cpp 2022-08-18 15:21:50 +02:00
e50c246601 Merge pull request #1651 from TheRealLorenz/master
Feature: sway/window can show 'shell' parameter
2022-08-18 15:21:09 +02:00
ee504b826d Update README.md 2022-08-18 15:16:28 +02:00
848ae1f818 Merge pull request #1656 from vaxerski/hyprland
Added a Hyprland backend and a Window module
2022-08-18 15:15:45 +02:00
406eb0ee9a Merge pull request #4 from NotAShelf/hyprland
Init man documentation
2022-08-18 15:10:42 +02:00
112d481ae7 Init man documentation 2022-08-18 15:59:00 +03:00
872cd6083d Merge pull request #3 from vaxerski/revert-2-master
Revert "init man documentation"
2022-08-18 14:54:30 +02:00
8dc78e4e40 Revert "init man documentation" 2022-08-18 14:54:20 +02:00
e662b8c624 Merge pull request #2 from NotAShelf/master
init man documentation
2022-08-18 14:53:15 +02:00
e0451816e2 init man documentation 2022-08-18 15:29:59 +03:00
e2e59a52df make the linter happy 2022-08-17 22:03:49 +02:00
123ed36739 remove workspaces module as its buggy and unnecessary 2022-08-17 21:58:33 +02:00
c64058c947 stabilize window module 2022-08-17 21:54:23 +02:00
56d46e62c1 add samplerate callback since pipewire supports dynamic samplerate changes 2022-08-12 11:30:12 -04:00
89a57f6722 simplify build option description 2022-08-11 18:35:33 -04:00
4336f10b29 Merge branch 'dsp' of https://github.com/kennypm/Waybar into dsp 2022-08-11 17:26:45 -04:00
a7979a3e56 add locks and refactor for clarity 2022-08-11 17:26:27 -04:00
bfed2114e4 jack_client_close working properly now 2022-08-11 15:49:24 -04:00
f65a372855 Merge branch 'Alexays:master' into dsp 2022-08-11 07:46:20 +00:00
6f3fe6d339 Update waybar-sway-window.5.scd 2022-08-11 08:41:10 +02:00
c287b0c82b Update manpage for sway/window 2022-08-10 22:24:48 +02:00
5b1cd65e20 Fix: better formatting 2022-08-10 10:41:18 +02:00
99ed2bb7fa Feature: sway/window can show 'shell' parameter 2022-08-10 10:34:51 +02:00
ddd5b4e157 refactor 2022-08-07 15:29:42 -04:00
e9e5780aae Calendar scrolling opportunity 2022-08-06 13:55:20 +03:00
061ad13082 Bug: tripple click uses wrong event type 2022-08-06 13:52:00 +03:00
77bea7c182 Merge pull request #1631 from m-braunschweig/filename
mpd: add filename formatter
2022-08-04 10:05:14 +02:00
c2ab2e6d19 Merge pull request #1627 from datMaffin/master
sni: Use the pixmap if for the given icon name an icon could not be found
2022-08-04 10:04:48 +02:00
11239a4900 mpd: add filename formatter 2022-08-03 20:52:18 +02:00
95b5348c24 sni: change missing icon in theme logging from info to trace 2022-08-03 17:34:34 +02:00
9616df58da Merge branch 'Alexays:master' into dsp 2022-08-01 03:30:45 +00:00
7b115913de Merge pull request #1638 from ErikReider/master 2022-07-31 21:27:05 +02:00
4029c5423f Added UPower to README 2022-07-31 10:56:42 +02:00
3996764880 Merge pull request #1637 from jbeich/ci 2022-07-30 09:09:26 +02:00
60821257ac chore(ci): adjust FreeBSD to follow upstream recommendations
- use macos-12 as macos-10.15 will be removed on 2022-08-30
- use major version to transparently pick up updates
2022-07-29 20:05:24 +00:00
e14005a6aa Fix binary pow formatting for values between 1000 and 1024 2022-07-21 16:37:43 -04:00
15dbe8965e fix Linter error 2022-07-19 22:36:59 -04:00
decc5bcd68 namespace cleanup 2022-07-19 22:34:35 -04:00
92870cab2a namespace cleanup 2022-07-19 22:30:42 -04:00
4cb2cc9f21 fix Linter errors 2022-07-19 21:54:36 -04:00
02df861829 fix Linter errors 2022-07-19 21:53:32 -04:00
23eaffc04b fix Linter errors 2022-07-19 21:49:56 -04:00
714451e4f9 cleanup 2022-07-19 19:40:23 -04:00
4cd6024f07 move issue from comment to Issues 2022-07-19 19:36:48 -04:00
8b5f42d934 remove unnecessary libprocps dependency 2022-07-19 19:27:39 -04:00
b65c976bc1 fix build type 2022-07-19 01:41:32 -04:00
5e7c9378df update fork 2022-07-19 01:40:05 -04:00
a9569e7d5c Merge branch 'dsp' of https://github.com/kennypm/Waybar into dsp 2022-07-19 01:39:19 -04:00
318a6e0969 fix segfault when stopping JACK2 server 2022-07-19 01:38:56 -04:00
a1d046b2e7 Update README.md 2022-07-19 01:38:56 -04:00
c7b09eea11 changed callbacks to use static_cast 2022-07-19 01:38:56 -04:00
bc8517fd08 fix callbacks 2022-07-19 01:38:56 -04:00
9439e4183c fix callbacks 2022-07-19 01:38:56 -04:00
8fc8bb40bf Initial commit for Waybar JACK monitoring module
-DSP load
  -xruns
  -connected/disconnected state
  -only tested with Pipewire so far but should work with JACK2 as well

 On branch dsp
 Changes to be committed:
	modified:   include/factory.hpp
	new file:   include/modules/jack.hpp
	modified:   meson.build
	modified:   meson_options.txt
	modified:   src/factory.cpp
	new file:   src/modules/jack.cpp
2022-07-19 01:38:35 -04:00
d906080f26 Merge pull request #1617 from alebastr/fmt-9
fix: adapt to fmt 9.0.0 breaking changes
2022-07-18 10:00:46 +02:00
04d66de866 sni: remove unnecesary parameter 2022-07-17 22:20:24 +02:00
699f732146 sni: Remove unnecessary getIconByName call 2022-07-17 22:15:14 +02:00
f437bf96e3 sni: Prefer system icons over pixmap 2022-07-17 22:15:12 +02:00
fc9a390977 sni: Use the given pixmap even if there is a name given 2022-07-17 22:14:57 +02:00
56a45e962b Merge pull request #1628 from carlosV2/master 2022-07-17 18:15:23 +02:00
48d2759df5 add layout as class to language module 2022-07-17 16:13:32 +01:00
1116ff0d67 Merge pull request #1624 from ersen0/fix-man
battery: fix wrong definition for "format"
2022-07-15 10:44:09 +02:00
0c04aea108 battery: fix wrong definition for "format" 2022-07-15 11:01:14 +03:00
a44622aa9f fix: fmt 9.x deprecation warning for implicit enum conversions 2022-07-13 22:36:37 -07:00
3117aefdf3 fix: drop conditionals for ancient fmt versions 2022-07-13 22:36:33 -07:00
24a8332b62 fix: adapt to fmt 9.0.0 breaking changes 2022-07-13 22:36:32 -07:00
84e7689521 Merge pull request #1621 from jbeich/ci 2022-07-14 07:33:06 +02:00
0708573fa4 chore(ci): upgrade FreeBSD to 13.1
FreeBSD doesn't support /latest and /quarterly package repos on EOL
versions. 13.0 reaches EOL on 2022-08-31, so avoid CI breakage.
2022-07-13 21:26:45 +00:00
08d472d1b1 Merge pull request #1612 from LukashonakV/Gentoo_CI
New Gentoo CI
2022-07-12 16:24:10 +02:00
c35f91ed7a Update linux.yml
New Gentoo CI container
2022-07-06 09:50:21 +00:00
9c3af1b6ad Gentoo docker file 2022-07-06 09:47:24 +00:00
17b60bc737 minor changes 2022-07-01 15:35:25 +02:00
c1f92d2a3c added workspaces 2022-07-01 15:16:54 +02:00
72f478c195 added backend and hyprland/window 2022-07-01 12:46:28 +02:00
5128a5d9f3 Merge pull request #1599 from LukashonakV/ISSUE#1565
Last weekday applies Unix fmt
2022-06-27 10:18:12 +02:00
36aa22189b Last weekday applies Unix fmt 2022-06-24 16:44:06 +03:00
d10d9b8202 Merge pull request #1590 from qubidt/pulseaudio-fix
pulseaudio: avoid retaining outdated form factor
2022-06-15 09:15:50 +02:00
e57899c0c5 pulseaudio: avoid retaining outdated form factor
when the module fails to get the pulseaudio device form factor, the
module persists the existing value, resulting in the incorrect
format-icon being used to format the label on device changes.

reset the form factor value so that the icon lookup properly falls back
to "default" when missing
2022-06-14 13:57:03 -05:00
249c0aad73 fix: lint 2022-06-14 09:17:40 +02:00
18a4f87a59 Merge pull request #1588 from qubidt/custom-module-class
Retain instance name css class for custom modules
2022-06-14 09:15:42 +02:00
458c03bf95 retain instance name css class for custom modules
When adding a custom module with a name, e.g.:

```jsonc
{
  ...,
  "custom/foo#bar": { },
  ...
}
```

The custom module does not retain the `bar` class as it should, because
all the classes are replaced with the runtime output:

1b4a7b02f4/src/modules/custom.cpp (L141-L147)

Avoid removing the module instance name class so css class behavior is
consistent between all modules.
2022-06-13 16:10:41 -05:00
1b4a7b02f4 Merge pull request #1575 from cosandr/bandwidth-update
Add total bandwidth formatting options
2022-06-11 11:45:18 +02:00
fb2ac8a765 Merge pull request #1580 from tiosgz/river-mode-readme
README.md: add river/mode to feature list
2022-06-02 16:48:46 +02:00
13100326b0 README.md: add river/mode to feature list 2022-06-02 14:36:18 +00:00
ca0d35286d Merge pull request #1579 from tiosgz/river-mode
river/mode: new module
2022-06-02 14:22:21 +02:00
f3a049c6df river/mode: new module
This module shows river's current mapping mode (e.g. normal, locked).
2022-06-01 15:35:08 +00:00
074b7c4b99 Merge pull request #1578 from NickHastings/readme-river
Advertise river modules
2022-06-01 10:18:03 +02:00
b24fd35add Advertise river modules 2022-06-01 09:10:26 +09:00
c27dab9379 Merge pull request #1576 from daangoossens22/fix_bluetooth 2022-05-28 15:28:20 +02:00
6857691679 style(bluetooth): apply project style 2022-05-28 12:58:37 +02:00
a475be7cf7 feat(bluetooth): add format-icons 2022-05-28 12:35:33 +02:00
00c11c64ca fix(bluetooth): tooltip-format-connected-battery 2022-05-28 12:33:47 +02:00
4e2305639b Add option for displaying total bandwidth 2022-05-28 10:54:10 +02:00
e0f29dbf71 Add bandwidth in bytes to 5 waybar-network 2022-05-28 10:39:43 +02:00
36d3d511d6 Merge pull request #1571 from daangoossens22/position_bar
fix: vertical bar not anchored when width is set
2022-05-25 16:42:20 +02:00
ae9fb57790 fix: vertical bar not anchored when width is set 2022-05-25 16:09:21 +02:00
b8ee448e71 Merge pull request #1567 from jbeich/freebsd 2022-05-23 20:47:45 +02:00
632058a4f6 chore(ci): test upower module on freebsd 2022-05-23 16:33:48 +00:00
d25278f710 fix(upower): add missing include for libc++
In file included from src/modules/upower/upower.cpp:1:
include/modules/upower/upower.hpp:25:16: error: no template named 'unordered_map' in namespace 'std'
  typedef std::unordered_map<std::string, UpDevice *> Devices;
          ~~~~~^
In file included from src/modules/upower/upower_tooltip.cpp:1:
include/modules/upower/upower_tooltip.hpp:13:16: error: no template named 'unordered_map' in namespace 'std'
  typedef std::unordered_map<std::string, UpDevice*> Devices;
          ~~~~~^
2022-05-23 16:27:32 +00:00
2dfd64e1c9 Merge pull request #1566 from ErikReider/master
Added gamemode man file to meson
2022-05-23 14:43:58 +02:00
3c182c9ca9 Added gamemode man file to meson 2022-05-23 14:13:30 +02:00
e094480684 Very basic hypr window title module 2022-04-02 21:08:43 +02:00
ccce2b700b fix segfault when stopping JACK2 server 2022-02-24 02:46:45 -05:00
41dea6e46c Merge branch 'master' into feat/image-module 2022-02-22 23:40:59 +00:00
65c3f0a132 Merge branch 'dsp' of https://github.com/kennypm/Waybar into dsp 2022-02-18 02:14:51 -05:00
e6262b870c changed callbacks to use static_cast 2022-02-18 02:13:43 -05:00
823ed887ab Update README.md 2022-02-12 05:53:32 -05:00
3bf815f6de fix callbacks 2022-02-12 01:52:51 -05:00
c1cda1553a fix callbacks 2022-02-12 01:51:11 -05:00
f6ee90e5ba Merge branch 'dsp' of https://github.com/kennypm/Waybar into dsp 2022-02-12 01:49:14 -05:00
d5c400c0cc Initial commit for Waybar JACK monitoring module
-DSP load
  -xruns
  -connected/disconnected state
  -only tested with Pipewire so far but should work with JACK2 as well

 On branch dsp
 Changes to be committed:
	modified:   include/factory.hpp
	new file:   include/modules/jack.hpp
	modified:   meson.build
	modified:   meson_options.txt
	modified:   src/factory.cpp
	new file:   src/modules/jack.cpp
2022-02-09 02:53:52 -05:00
a650c7d90c feat: image module
Module which renders an image onto the bar.
2022-01-16 23:55:13 +00:00
126 changed files with 4336 additions and 527 deletions

View File

@ -4,14 +4,14 @@ on: [ push, pull_request ]
jobs: jobs:
clang: clang:
# Run actions in a FreeBSD vm on the macos-10.15 runner # Run actions in a FreeBSD VM on the macos-12 runner
# https://github.com/actions/runner/issues/385 - for FreeBSD runner support # https://github.com/actions/runner/issues/385 - for FreeBSD runner support
# https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners # https://github.com/actions/virtual-environments/issues/4060 - for lack of VirtualBox on MacOS 11 runners
runs-on: macos-10.15 runs-on: macos-12
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0.1.6 # aka FreeBSD 13.0 uses: vmactions/freebsd-vm@v0
with: with:
mem: 2048 mem: 2048
usesh: true usesh: true
@ -21,7 +21,8 @@ jobs:
pkg install -y git # subprojects/date pkg install -y git # subprojects/date
pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \ pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \ libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
pkgconf pulseaudio scdoc sndio spdlog wayland-protocols pkgconf pulseaudio scdoc sndio spdlog wayland-protocols upower \
libinotify
run: | run: |
meson build -Dman-pages=enabled meson build -Dman-pages=enabled
ninja -C build ninja -C build

View File

@ -12,6 +12,7 @@ jobs:
- debian - debian
- fedora - fedora
- opensuse - opensuse
- gentoo
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: container:

2
.gitignore vendored
View File

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

View File

@ -2,4 +2,4 @@
FROM alpine:latest FROM alpine:latest
RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata RUN apk add --no-cache git meson alpine-sdk libinput-dev wayland-dev wayland-protocols mesa-dev libxkbcommon-dev eudev-dev pixman-dev gtkmm3-dev jsoncpp-dev pugixml-dev libnl3-dev pulseaudio-dev libmpdclient-dev sndio-dev scdoc libxkbcommon tzdata playerctl-dev

View File

@ -3,4 +3,4 @@
FROM archlinux:base-devel FROM archlinux:base-devel
RUN pacman -Syu --noconfirm && \ RUN pacman -Syu --noconfirm && \
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon pacman -S --noconfirm git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection libxkbcommon playerctl

View File

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

View File

@ -8,5 +8,6 @@ RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \ 'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \ 'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \ '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 dnf clean all -y

11
Dockerfiles/gentoo Normal file
View File

@ -0,0 +1,11 @@
# vim: ft=Dockerfile
FROM gentoo/stage3:latest
RUN export FEATURES="-ipc-sandbox -network-sandbox -pid-sandbox -sandbox -usersandbox" && \
emerge --sync && \
eselect news read --quiet new 1>/dev/null 2>&1 && \
emerge --verbose --update --deep --with-bdeps=y --backtrack=30 --newuse @world && \
USE="wayland gtk3 gtk -doc X" emerge dev-vcs/git dev-libs/wayland dev-libs/wayland-protocols =dev-cpp/gtkmm-3.24.6 x11-libs/libxkbcommon \
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/playerctl

View File

@ -6,4 +6,4 @@ RUN zypper -n up && \
zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \ zypper addrepo https://download.opensuse.org/repositories/X11:Wayland/openSUSE_Tumbleweed/X11:Wayland.repo | echo 'a' && \
zypper -n refresh && \ zypper -n refresh && \
zypper -n install -t pattern devel_C_C++ && \ zypper -n install -t pattern devel_C_C++ && \
zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc zypper -n install git meson clang libinput10 libinput-devel pugixml-devel libwayland-client0 libwayland-cursor0 wayland-protocols-devel wayland-devel Mesa-libEGL-devel Mesa-libGLESv2-devel libgbm-devel libxkbcommon-devel libudev-devel libpixman-1-0-devel gtkmm3-devel jsoncpp-devel libxkbregistry-devel scdoc playerctl-devel

View File

@ -7,18 +7,23 @@
#### Current features #### Current features
- Sway (Workspaces, Binding mode, Focused window name) - Sway (Workspaces, Binding mode, Focused window name)
- River (Mapping mode, Tags, Focused window name)
- Hyprland (Focused window name)
- Tray [#21](https://github.com/Alexays/Waybar/issues/21) - Tray [#21](https://github.com/Alexays/Waybar/issues/21)
- Local time - Local time
- Battery - Battery
- UPower
- Network - Network
- Bluetooth - Bluetooth
- Pulseaudio - Pulseaudio
- Wireplumber
- Disk - Disk
- Memory - Memory
- Cpu load average - Cpu load average
- Temperature - Temperature
- MPD - MPD
- Custom scripts - Custom scripts
- Custom image
- Multiple output configuration - Multiple output configuration
- And many more customizations - And many more customizations
@ -70,6 +75,7 @@ libmpdclient [MPD module]
libsndio [sndio module] libsndio [sndio module]
libevdev [KeyboardState module] libevdev [KeyboardState module]
xkbregistry xkbregistry
upower [UPower battery module]
``` ```
**Build dependencies** **Build dependencies**
@ -103,6 +109,7 @@ sudo apt install \
libspdlog-dev \ libspdlog-dev \
libwayland-dev \ libwayland-dev \
scdoc \ scdoc \
upower \
libxkbregistry-dev libxkbregistry-dev
``` ```

View File

@ -48,10 +48,10 @@ class AModule : public IModule {
{std::make_pair(3, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-right"}, {std::make_pair(3, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-right"},
{std::make_pair(8, GdkEventType::GDK_BUTTON_PRESS), "on-click-backward"}, {std::make_pair(8, GdkEventType::GDK_BUTTON_PRESS), "on-click-backward"},
{std::make_pair(8, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-backward"}, {std::make_pair(8, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-backward"},
{std::make_pair(8, GdkEventType::GDK_2BUTTON_PRESS), "on-triple-click-backward"}, {std::make_pair(8, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-backward"},
{std::make_pair(9, GdkEventType::GDK_BUTTON_PRESS), "on-click-forward"}, {std::make_pair(9, GdkEventType::GDK_BUTTON_PRESS), "on-click-forward"},
{std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-forward"}, {std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), "on-double-click-forward"},
{std::make_pair(9, GdkEventType::GDK_2BUTTON_PRESS), "on-triple-click-forward"}}; {std::make_pair(9, GdkEventType::GDK_3BUTTON_PRESS), "on-triple-click-forward"}};
}; };
} // namespace waybar } // namespace waybar

View File

@ -14,6 +14,7 @@ namespace waybar {
class Config { class Config {
public: public:
static const std::vector<std::string> CONFIG_DIRS; static const std::vector<std::string> CONFIG_DIRS;
static const char *CONFIG_PATH_ENV;
/* Try to find any of provided names in the supported set of config directories */ /* Try to find any of provided names in the supported set of config directories */
static std::optional<std::string> findConfigPath( static std::optional<std::string> findConfigPath(

View File

@ -9,6 +9,7 @@
#ifdef HAVE_SWAY #ifdef HAVE_SWAY
#include "modules/sway/language.hpp" #include "modules/sway/language.hpp"
#include "modules/sway/mode.hpp" #include "modules/sway/mode.hpp"
#include "modules/sway/scratchpad.hpp"
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp" #include "modules/sway/workspaces.hpp"
#endif #endif
@ -17,10 +18,17 @@
#include "modules/wlr/workspace_manager.hpp" #include "modules/wlr/workspace_manager.hpp"
#endif #endif
#ifdef HAVE_RIVER #ifdef HAVE_RIVER
#include "modules/river/mode.hpp"
#include "modules/river/tags.hpp" #include "modules/river/tags.hpp"
#include "modules/river/window.hpp" #include "modules/river/window.hpp"
#endif #endif
#if defined(__linux__) && !defined(NO_FILESYSTEM) #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))
#include "modules/battery.hpp" #include "modules/battery.hpp"
#endif #endif
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD) #if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
@ -34,6 +42,9 @@
#ifdef HAVE_DBUSMENU #ifdef HAVE_DBUSMENU
#include "modules/sni/tray.hpp" #include "modules/sni/tray.hpp"
#endif #endif
#ifdef HAVE_MPRIS
#include "modules/mpris/mpris.hpp"
#endif
#ifdef HAVE_LIBNL #ifdef HAVE_LIBNL
#include "modules/network.hpp" #include "modules/network.hpp"
#endif #endif
@ -62,9 +73,17 @@
#include "modules/bluetooth.hpp" #include "modules/bluetooth.hpp"
#include "modules/inhibitor.hpp" #include "modules/inhibitor.hpp"
#endif #endif
#ifdef HAVE_LIBJACK
#include "modules/jack.hpp"
#endif
#ifdef HAVE_LIBWIREPLUMBER
#include "modules/wireplumber.hpp"
#endif
#include "bar.hpp" #include "bar.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/image.hpp"
#include "modules/temperature.hpp" #include "modules/temperature.hpp"
#include "modules/user.hpp"
namespace waybar { namespace waybar {

View File

@ -18,12 +18,14 @@ class Backlight : public ALabel {
class BacklightDev { class BacklightDev {
public: public:
BacklightDev() = default; 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; std::string_view name() const;
int get_actual() const; int get_actual() const;
void set_actual(int actual); void set_actual(int actual);
int get_max() const; int get_max() const;
void set_max(int max); void set_max(int max);
bool get_powered() const;
void set_powered(bool powered);
friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) { friend inline bool operator==(const BacklightDev &lhs, const BacklightDev &rhs) {
return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_; return lhs.name_ == rhs.name_ && lhs.actual_ == rhs.actual_ && lhs.max_ == rhs.max_;
} }
@ -32,6 +34,7 @@ class Backlight : public ALabel {
std::string name_; std::string name_;
int actual_ = 1; int actual_ = 1;
int max_ = 1; int max_ = 1;
bool powered_ = true;
}; };
public: public:

View File

@ -6,7 +6,9 @@
#include <filesystem> #include <filesystem>
#endif #endif
#include <fmt/format.h> #include <fmt/format.h>
#if defined(__linux__)
#include <sys/inotify.h> #include <sys/inotify.h>
#endif
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>

View File

@ -25,21 +25,23 @@ class Clock : public ALabel {
std::locale locale_; std::locale locale_;
std::vector<const date::time_zone*> time_zones_; std::vector<const date::time_zone*> time_zones_;
int current_time_zone_idx_; int current_time_zone_idx_;
date::year_month_day cached_calendar_ymd_ = date::January / 1 / 0; date::year_month_day calendar_cached_ymd_{date::January / 1 / 0};
std::string cached_calendar_text_; date::months calendar_shift_{0}, calendar_shift_init_{0};
std::string calendar_cached_text_;
bool is_calendar_in_tooltip_; bool is_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_; bool is_timezoned_list_in_tooltip_;
bool handleScroll(GdkEventScroll* e); bool handleScroll(GdkEventScroll* e);
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 calendar_text(const waybar_time& wtime) -> std::string;
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday; auto first_day_of_week() -> date::weekday;
const date::time_zone* current_timezone(); const date::time_zone* current_timezone();
auto print_iso_weeknum(std::ostream& os, int weeknum) -> void;
bool is_timezone_fixed(); bool is_timezone_fixed();
auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string; auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string;
}; };
} // namespace modules } // namespace modules
} // namespace waybar } // namespace waybar

View File

@ -30,6 +30,7 @@ class Custom : public ALabel {
const std::string name_; const std::string name_;
std::string text_; std::string text_;
std::string id_;
std::string alt_; std::string alt_;
std::string tooltip_; std::string tooltip_;
std::vector<std::string> class_; std::vector<std::string> class_;

View File

@ -0,0 +1,36 @@
#pragma once
#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&, EventHandler*);
void unregisterForIPC(EventHandler*);
std::string getSocket1Reply(const std::string& rq);
private:
void startIPC();
void parseIPC(const std::string&);
std::mutex callbackMutex;
std::list<std::pair<std::string, EventHandler*>> callbacks;
};
inline std::unique_ptr<IPC> gIPC;
inline bool modulesReady = false;
}; // namespace waybar::modules::hyprland

View File

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

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

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

View File

@ -20,6 +20,7 @@ class IdleInhibitor : public ALabel {
private: private:
bool handleToggle(GdkEventButton* const& e); bool handleToggle(GdkEventButton* const& e);
void toggleStatus();
const Bar& bar_; const Bar& bar_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_; struct zwp_idle_inhibitor_v1* idle_inhibitor_;

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

44
include/modules/jack.hpp Normal file
View File

@ -0,0 +1,44 @@
#pragma once
#include <fmt/format.h>
#include <jack/jack.h>
#include <jack/thread.h>
#include <fstream>
#include "ALabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class JACK : public ALabel {
public:
JACK(const std::string &, const Json::Value &);
~JACK() = default;
auto update() -> void;
int bufSize(jack_nframes_t size);
int sampleRate(jack_nframes_t rate);
int xrun();
void shutdown();
private:
std::string JACKState();
jack_client_t *client_;
jack_nframes_t bufsize_;
jack_nframes_t samplerate_;
unsigned int xruns_;
float load_;
bool running_;
std::mutex mutex_;
std::string state_;
util::SleeperThread thread_;
};
} // namespace waybar::modules
int bufSizeCallback(jack_nframes_t size, void *obj);
int sampleRateCallback(jack_nframes_t rate, void *obj);
int xrunCallback(void *obj);
void shutdownCallback(void *obj);

View File

@ -1,19 +1,17 @@
#pragma once #pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h> #include <fmt/chrono.h>
#endif
#include <gtkmm/label.h> #include <gtkmm/label.h>
#include <unordered_map>
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
extern "C" { extern "C" {
#include <libevdev/libevdev.h> #include <libevdev/libevdev.h>
#include <libinput.h>
} }
namespace waybar::modules { namespace waybar::modules {
@ -25,6 +23,8 @@ class KeyboardState : public AModule {
auto update() -> void; auto update() -> void;
private: private:
auto tryAddDevice(const std::string&) -> void;
Gtk::Box box_; Gtk::Box box_;
Gtk::Label numlock_label_; Gtk::Label numlock_label_;
Gtk::Label capslock_label_; Gtk::Label capslock_label_;
@ -36,11 +36,12 @@ class KeyboardState : public AModule {
const std::chrono::seconds interval_; const std::chrono::seconds interval_;
std::string icon_locked_; std::string icon_locked_;
std::string icon_unlocked_; std::string icon_unlocked_;
std::string devices_path_;
int fd_; struct libinput* libinput_;
libevdev* dev_; std::unordered_map<std::string, struct libinput_device*> libinput_devices_;
util::SleeperThread thread_; util::SleeperThread libinput_thread_, hotplug_thread_;
}; };
} // namespace waybar::modules } // namespace waybar::modules

View File

@ -41,6 +41,7 @@ class MPD : public ALabel {
private: private:
std::string getTag(mpd_tag_type type, unsigned idx = 0) const; std::string getTag(mpd_tag_type type, unsigned idx = 0) const;
std::string getFilename() const;
void setLabel(); void setLabel();
std::string getStateIcon() const; std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const; std::string getOptionIcon(std::string optionName, bool activated) const;

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

@ -0,0 +1,28 @@
#pragma once
#include <wayland-client.h>
#include "ALabel.hpp"
#include "bar.hpp"
#include "river-status-unstable-v1-client-protocol.h"
namespace waybar::modules::river {
class Mode : public waybar::ALabel {
public:
Mode(const std::string &, const waybar::Bar &, const Json::Value &);
~Mode();
// Handlers for wayland events
void handle_mode(const char *mode);
struct zriver_status_manager_v1 *status_manager_;
struct wl_seat *seat_;
private:
const waybar::Bar &bar_;
std::string mode_;
struct zriver_seat_status_v1 *seat_status_;
};
} /* namespace waybar::modules::river */

View File

@ -1,11 +1,7 @@
#pragma once #pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h> #include <fmt/chrono.h>
#endif
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"

View File

@ -0,0 +1,35 @@
#pragma once
#include <gtkmm/label.h>
#include <mutex>
#include <string>
#include "ALabel.hpp"
#include "bar.hpp"
#include "client.hpp"
#include "modules/sway/ipc/client.hpp"
#include "util/json.hpp"
namespace waybar::modules::sway {
class Scratchpad : public ALabel {
public:
Scratchpad(const std::string&, const Json::Value&);
~Scratchpad() = default;
auto update() -> void;
private:
auto getTree() -> void;
auto onCmd(const struct Ipc::ipc_response&) -> void;
auto onEvent(const struct Ipc::ipc_response&) -> void;
std::string tooltip_format_;
bool show_empty_;
bool tooltip_enabled_;
std::string tooltip_text_;
int count_;
std::mutex mutex_;
Ipc ipc_;
util::JsonParser parser_;
};
} // namespace waybar::modules::sway

View File

@ -21,10 +21,9 @@ class Window : public AIconLabel, public sigc::trackable {
private: private:
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
std::tuple<std::size_t, int, std::string, std::string, std::string> getFocusedNode( std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> getFocusedNode(
const Json::Value& nodes, std::string& output); const Json::Value& nodes, std::string& output);
void getTree(); void getTree();
std::string rewriteTitle(const std::string& title);
void updateAppIconName(); void updateAppIconName();
void updateAppIcon(); void updateAppIcon();
@ -35,6 +34,7 @@ class Window : public AIconLabel, public sigc::trackable {
std::string app_class_; std::string app_class_;
std::string old_app_id_; std::string old_app_id_;
std::size_t app_nb_; std::size_t app_nb_;
std::string shell_;
unsigned app_icon_size_{24}; unsigned app_icon_size_{24};
bool update_app_icon_{true}; bool update_app_icon_{true};
std::string app_icon_name_; std::string app_icon_name_;

View File

@ -5,6 +5,7 @@
#include <iostream> #include <iostream>
#include <map> #include <map>
#include <string> #include <string>
#include <unordered_map>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "glibconfig.h" #include "glibconfig.h"

View File

@ -2,6 +2,8 @@
#include <libupower-glib/upower.h> #include <libupower-glib/upower.h>
#include <unordered_map>
#include "gtkmm/box.h" #include "gtkmm/box.h"
#include "gtkmm/label.h" #include "gtkmm/label.h"
#include "gtkmm/window.h" #include "gtkmm/window.h"

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

@ -0,0 +1,34 @@
#pragma once
#include <fmt/chrono.h>
#include <gdkmm/pixbuf.h>
#include <glibmm/refptr.h>
#include "AIconLabel.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class User : public AIconLabel {
public:
User(const std::string&, const Json::Value&);
~User() = default;
auto update() -> void;
bool handleToggle(GdkEventButton* const& e) override;
private:
util::SleeperThread thread_;
static constexpr inline int defaultUserImageWidth_ = 20;
static constexpr inline int defaultUserImageHeight_ = 20;
long uptime_as_seconds();
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);
void init_update_worker();
};
} // namespace waybar::modules

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

@ -74,6 +74,10 @@ class Task {
std::string app_id_; std::string app_id_;
uint32_t state_ = 0; uint32_t state_ = 0;
int32_t drag_start_x;
int32_t drag_start_y;
int32_t drag_start_button = -1;
private: private:
std::string repr() const; std::string repr() const;
std::string state_string(bool = false) const; std::string state_string(bool = false) const;
@ -105,6 +109,12 @@ class Task {
/* Callbacks for Gtk events */ /* Callbacks for Gtk events */
bool handle_clicked(GdkEventButton *); bool handle_clicked(GdkEventButton *);
bool handle_button_release(GdkEventButton *);
bool handle_motion_notify(GdkEventMotion *);
void handle_drag_data_get(const Glib::RefPtr<Gdk::DragContext> &context,
Gtk::SelectionData &selection_data, guint info, guint time);
void handle_drag_data_received(const Glib::RefPtr<Gdk::DragContext> &context, int x, int y,
Gtk::SelectionData selection_data, guint info, guint time);
public: public:
bool operator==(const Task &) const; bool operator==(const Task &) const;

View File

@ -152,6 +152,7 @@ class WorkspaceManager : public AModule {
bool sort_by_name_ = true; bool sort_by_name_ = true;
bool sort_by_coordinates_ = true; bool sort_by_coordinates_ = true;
bool sort_by_number_ = false;
bool all_outputs_ = false; bool all_outputs_ = false;
bool active_only_ = false; bool active_only_ = false;
bool creation_delayed_ = false; bool creation_delayed_ = false;

View File

@ -56,9 +56,10 @@ struct formatter<pow_format> {
fraction /= base; fraction /= base;
} }
auto max_width = 4 // coeff in {:.3g} format auto number_width = 5 // coeff in {:.1f} format
+ 1 // prefix from units array + s.binary_; // potential 4th digit before the decimal point
+ s.binary_ // for the 'i' in GiB. auto max_width = number_width + 1 // prefix from units array
+ s.binary_ // for the 'i' in GiB.
+ s.unit_.length(); + s.unit_.length();
const char* format; const char* format;
@ -69,15 +70,16 @@ struct formatter<pow_format> {
case '<': case '<':
return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width); return format_to(ctx.out(), "{:<{}}", fmt::format("{}", s), max_width);
case '=': case '=':
format = "{coefficient:<4.3g}{padding}{prefix}{unit}"; format = "{coefficient:<{number_width}.1f}{padding}{prefix}{unit}";
break; break;
case 0: case 0:
default: default:
format = "{coefficient:.3g}{prefix}{unit}"; format = "{coefficient:.1f}{prefix}{unit}";
break; break;
} }
return format_to( return format_to(
ctx.out(), format, fmt::arg("coefficient", fraction), ctx.out(), format, fmt::arg("coefficient", fraction),
fmt::arg("number_width", number_width),
fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")), fmt::arg("prefix", std::string() + units[pow] + ((s.binary_ && pow) ? "i" : "")),
fmt::arg("unit", s.unit_), fmt::arg("unit", s.unit_),
fmt::arg("padding", pow ? "" fmt::arg("padding", pow ? ""

View File

@ -1,7 +1,15 @@
#pragma once #pragma once
#include <fmt/ostream.h>
#include <json/json.h> #include <json/json.h>
#if (FMT_VERSION >= 90000)
template <>
struct fmt::formatter<Json::Value> : ostream_formatter {};
#endif
namespace waybar::util { namespace waybar::util {
struct JsonParser { struct JsonParser {

View File

@ -0,0 +1,8 @@
#pragma once
#include <json/json.h>
#include <string>
namespace waybar::util {
std::string rewriteTitle(const std::string&, const Json::Value&);
}

View File

@ -0,0 +1,6 @@
#pragma once
#include <string>
namespace waybar::util {
std::string sanitize_string(std::string str);
} // namespace waybar::util

View File

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

View File

@ -33,13 +33,13 @@ The *battery* module displays the current capacity and state (eg. charging) of y
The interval in which the information gets polled. The interval in which the information gets polled.
*states*: ++ *states*: ++
typeof: array ++ typeof: object ++
A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*. A number of battery states which get activated on certain capacity levels. See *waybar-states(5)*.
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: {capacity}% ++ default: {capacity}% ++
The format, how the time should be displayed. The format, how information should be displayed.
*format-time*: ++ *format-time*: ++
typeof: string ++ typeof: string ++
@ -114,9 +114,10 @@ The *battery* module displays the current capacity and state (eg. charging) of y
The *battery* module allows you to define how time should be formatted via *format-time*. The *battery* module allows you to define how time should be formatted via *format-time*.
The two arguments are: The three arguments are:
*{H}*: Hours *{H}*: Hours
*{M}*: Minutes *{M}*: Minutes
*{m}*: Zero-padded minutes
# CUSTOM FORMATS # CUSTOM FORMATS

View File

@ -42,6 +42,12 @@ Addressed by *bluetooth*
typeof: string ++ typeof: string ++
This format is used when the displayed controller is connected to at least 1 device. This format is used when the displayed controller is connected to at least 1 device.
*format-icons*: ++
typeof: array/object ++
Based on the current battery percentage (see section *EXPERIMENTAL BATTERY PERCENTAGE FEATURE*), the corresponding icon gets selected. ++
The order is *low* to *high*. Will only show the current battery percentage icon in the *\*-connected-battery* config options. ++
Or by the state if it is an object. It will fall back to the enabled state if its derivatives are not defined (on, off, connected).
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label.
@ -115,6 +121,8 @@ Addressed by *bluetooth*
*{status}*: Status of the bluetooth device. *{status}*: Status of the bluetooth device.
*{icon}*: Icon, as defined in *format-icons*.
*{num_connections}*: Number of connections the displayed controller has. *{num_connections}*: Number of connections the displayed controller has.
*{controller_address}*: Address of the displayed controller. *{controller_address}*: Address of the displayed controller.
@ -129,7 +137,7 @@ Addressed by *bluetooth*
*{device_alias}*: Alias of the displayed device. *{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. and/or *tooltip-format-enumerate-connected-battery* config options. Can only be used in the tooltip related format options.
# EXPERIMENTAL BATTERY PERCENTAGE FEATURE # 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. Positive value to rotate the text label.
*states*: ++ *states*: ++
typeof: array ++ typeof: object ++
A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*. A number of cpu usage states which get activated on certain usage levels. See *waybar-states(5)*.
*on-click*: ++ *on-click*: ++

View File

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

View File

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

View File

@ -0,0 +1,43 @@
waybar-hyprland-language(5)
# NAME
waybar - hyprland language module
# DESCRIPTION
The *language* module displays the currently selected language.
# CONFIGURATION
Addressed by *hyprland/language*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} the currently selected language is displayed.
*format-<lang>* ++
typeof: string++
Provide an alternative name to display per language where <lang> is the language of your choosing. Can be passed multiple times with multiple languages as shown by the example below.
*keyboard-name*: ++
typeof: string ++
Specifies which keyboard to use from hyprctl devices output. Using the option that begins with "at-translated-set..." is recommended.
# EXAMPLES
```
"hyprland/language": {
"format": "Lang: {}"
"format-en": "AMERICA, HELL YEAH!"
"format-tr": "As bayrakları"
"keyboard-name": "at-translated-set-2-keyboard"
}
```
# STYLE
- *#language*

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*

View File

@ -0,0 +1,50 @@
waybar-hyprland-window(5)
# NAME
waybar - hyprland window module
# DESCRIPTION
The *window* module displays the title of the currently focused window in Hyprland.
# CONFIGURATION
Addressed by *hyprland/window*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} the current window title is displayed.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
# REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are
rewrite rules if the expression matches. Rules may contain references to
captures of the expression.
Regular expression and replacement follow ECMA-script rules.
If no expression matches, the title is left unchanged.
Invalid expressions (e.g., mismatched parentheses) are skipped.
# EXAMPLES
```
"hyprland/window": {
"format": "{}",
"rewrite": {
"(.*) - Mozilla Firefox": "🌎 $1",
"(.*) - zsh": "> [$1]"
}
}
```
# STYLE
- *#window*

View File

@ -63,6 +63,11 @@ screensaving, also known as "presentation mode".
typeof: double ++ typeof: double ++
Threshold to be used when scrolling. Threshold to be used when scrolling.
*start-activated*: ++
typeof: bool ++
default: *false* ++
Whether the inhibit should be activated when starting waybar.
*timeout*: ++ *timeout*: ++
typeof: double ++ typeof: double ++
The number of minutes the inhibit should last. The number of minutes the inhibit should last.
@ -72,6 +77,14 @@ screensaving, also known as "presentation mode".
default: true ++ default: true ++
Option to disable tooltip on hover. Option to disable tooltip on hover.
*tooltip-format-activated*: ++
typeof: string ++
This format is used when the inhibit is activated.
*tooltip-format-deactivated*: ++
typeof: string ++
This format is used when the inhibit is deactivated.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{status}*: status (*activated* or *deactivated*) *{status}*: status (*activated* or *deactivated*)

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 # 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. See *systemd-inhibit*(1) for more information.
# CONFIGURATION # CONFIGURATION

112
man/waybar-jack.5.scd Normal file
View File

@ -0,0 +1,112 @@
waybar-jack(5)
# NAME
waybar - JACK module
# DESCRIPTION
The *jack* module displays the current state of the JACK server.
# CONFIGURATION
Addressed by *jack*
*format*: ++
typeof: string ++
default: *{load}%* ++
The format, how information should be displayed. This format is used when other formats aren't specified.
*format-connected*: ++
typeof: string ++
This format is used when the module is connected to the JACK server.
*format-disconnected*: ++
typeof: string ++
This format is used when the module is not connected to the JACK server.
*format-xrun*: ++
typeof: string ++
This format is used for one polling interval, when the JACK server reports an xrun.
*realtime*: ++
typeof: bool ++
default: *true* ++
Option to drop real-time privileges for the JACK client opened by Waybar.
*tooltip*: ++
typeof: bool ++
default: *true* ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: *{bufsize}/{samplerate} {latency}ms* ++
The format of information displayed in the tooltip.
*interval*: ++
typeof: integer ++
default: 1 ++
The interval in which the information gets polled.
*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.
# FORMAT REPLACEMENTS
*{load}*: The current CPU load estimated by JACK.
*{bufsize}*: The size of the JACK buffer.
*{samplerate}*: The samplerate at which the JACK server is running.
*{latency}*: The duration, in ms, of the current buffer size.
*{xruns}*: The number of xruns reported by the JACK server since starting Waybar.
# EXAMPLES
```
"jack": {
"format": "DSP {}%",
"format-xrun": "{xruns} xruns",
"format-disconnected": "DSP off",
"realtime": true
}
```
# STYLE
- *#jack*
- *#jack.connected*
- *#jack.disconnected*
- *#jack.xrun*

View File

@ -13,6 +13,7 @@ You must be a member of the input group to use this module.
# CONFIGURATION # CONFIGURATION
*interval*: ++ *interval*: ++
Deprecated, this module use event loop now, the interval has no effect.
typeof: integer ++ typeof: integer ++
default: 1 ++ default: 1 ++
The interval, in seconds, to poll the keyboard state. The interval, in seconds, to poll the keyboard state.

View File

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

@ -149,10 +149,20 @@ Addressed by *network*
*{bandwidthDownBits}*: Instant down speed in bits/seconds. *{bandwidthDownBits}*: Instant down speed in bits/seconds.
*{bandwidthTotalBits}*: Instant total speed in bits/seconds.
*{bandwidthUpOctets}*: Instant up speed in octets/seconds. *{bandwidthUpOctets}*: Instant up speed in octets/seconds.
*{bandwidthDownOctets}*: Instant down speed in octets/seconds. *{bandwidthDownOctets}*: Instant down speed in octets/seconds.
*{bandwidthTotalOctets}*: Instant total speed in octets/seconds.
*{bandwidthUpBytes}*: Instant up speed in bytes/seconds.
*{bandwidthDownBytes}*: Instant down speed in bytes/seconds.
*{bandwidthTotalBytes}*: Instant total speed in bytes/seconds.
*{icon}*: Icon, as defined in *format-icons*. *{icon}*: Icon, as defined in *format-icons*.
# EXAMPLES # EXAMPLES

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. Positive value to rotate the text label.
*states*: ++ *states*: ++
typeof: array ++ typeof: object ++
A number of volume states which get activated on certain volume levels. See *waybar-states(5)* A number of volume states which get activated on certain volume levels. See *waybar-states(5)*.
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++
@ -96,6 +96,15 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
default: true ++ default: true ++
Option to disable tooltip on hover. Option to disable tooltip on hover.
*max-volume*: ++
typeof: integer ++
default: 100 ++
The maximum volume that can be set, in percentage.
*ignored-sinks*: ++
typeof: array ++
Sinks in this list will not be shown as the active sink by Waybar. Entries should be the sink's description field.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name. *{desc}*: Pulseaudio port's description, for bluetooth it'll be the device name.

View File

@ -0,0 +1,75 @@
waybar-river-mode(5)
# NAME
waybar - river mode module
# DESCRIPTION
The *mode* module displays the current mapping mode of river.
# CONFIGURATION
Addressed by *river/mode*
*format*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
*rotate*: ++
typeof: integer ++
Positive value to rotate the text label.
*max-length*: ++
typeof: integer ++
The maximum length in character the module should display.
*min-length*: ++
typeof: integer ++
The minimum length in characters the module should take up.
*align*: ++
typeof: float ++
The alignment of the text, where 0 is left-aligned and 1 is right-aligned. If the module is rotated, it will follow the flow of the text.
*on-click*: ++
typeof: string ++
Command to execute when clicked on the module.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*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
```
"river/mode": {
"format": " {}"
}
```
# STYLE
- *#mode*
- *#mode.<mode>*

View File

@ -7,14 +7,13 @@ apply a class when the value matches the declared state value.
# STATES # 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. - 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>*. 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*. - Also, each state can have its own *format*.
Those can be configured via *format-<name>*. Those can be configured via *format-<name>*, or if you want to differentiate a bit more, as *format-<status>-<state>*.
Or if you want to differentiate a bit more even as *format-<status>-<state>*.
# EXAMPLE # EXAMPLE

View File

@ -0,0 +1,64 @@
waybar-sway-scratchpad(5)
# NAME
waybar - sway scratchpad module
# DESCRIPTION
The *scratchpad* module displays the scratchpad status in Sway
# CONFIGURATION
Addressed by *sway/scratchpad*
*format*: ++
typeof: string ++
default: {icon} {count} ++
The format, how information should be displayed.
*show-empty*: ++
typeof: bool ++
default: false ++
Option to show module when scratchpad is empty.
*format-icons*: ++
typeof: array/object ++
Based on the current scratchpad window counts, the corresponding icon gets selected.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
*tooltip-format*: ++
typeof: string ++
default: {app}: {title} ++
The format, how information in the tooltip should be displayed.
# FORMAT REPLACEMENTS
*{icon}*: Icon, as defined in *format-icons*.
*{count}*: Number of windows in the scratchpad.
*{app}*: Name of the application in the scratchpad.
*{title}*: Title of the application in the scratchpad.
# EXAMPLES
```
"sway/scratchpad": {
"format": "{icon} {count}",
"show-empty": false,
"format-icons": ["", ""],
"tooltip": true,
"tooltip-format": "{app}: {title}"
}
```
# STYLE
- *#scratchpad*
- *#scratchpad.empty*

View File

@ -14,8 +14,8 @@ Addressed by *sway/window*
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: {} ++ default: {title} ++
The format, how information should be displayed. On {} data gets inserted. The format, how information should be displayed.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
@ -80,6 +80,15 @@ Addressed by *sway/window*
default: 24 ++ default: 24 ++
Option to change the size of the application icon. Option to change the size of the application icon.
# FORMAT REPLACEMENTS
*{title}*: The title of the focused window.
*{app_id}*: The app_id of the focused window.
*{shell}*: The shell of the focused window. It's 'xwayland' when the window is
running through xwayland, otherwise it's 'xdg-shell'.
# REWRITE RULES # REWRITE RULES
*rewrite* is an object where keys are regular expressions and values are *rewrite* is an object where keys are regular expressions and values are

View File

@ -73,6 +73,10 @@ Addressed by *sway/workspaces*
typeof: bool ++ 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. 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 # FORMAT REPLACEMENTS
*{value}*: Name of the workspace, as defined by sway. *{value}*: Name of the workspace, as defined by sway.

View File

@ -33,7 +33,7 @@ compatible devices in the tooltip.
*tooltip*: ++ *tooltip*: ++
typeof: bool ++ typeof: bool ++
defualt: true ++ default: true ++
Option to disable tooltip on hover. Option to disable tooltip on hover.
*tooltip-spacing*: ++ *tooltip-spacing*: ++
@ -47,6 +47,10 @@ compatible devices in the tooltip.
default: 4 ++ default: 4 ++
Defines the spacing between the tooltip window edge and the tooltip content. 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 # FORMAT REPLACEMENTS
*{percentage}*: The battery capacity in percentage *{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

@ -33,6 +33,11 @@ Addressed by *wlr/workspaces*
Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first. Note that if both *sort-by-name* and *sort-by-coordinates* are true sort by name will be first.
If both are false - sort by id will be performed. If both are false - sort by id will be performed.
*sort-by-number*: ++
typeof: bool ++
default: false ++
If set to true, workspace names will be sorted numerically. Takes presedence over any other sort-by option.
*all-outputs*: ++ *all-outputs*: ++
typeof: bool ++ typeof: bool ++
default: false ++ default: false ++
@ -75,7 +80,8 @@ Additional to workspace name matching, the following *format-icons* can be set.
"5": "", "5": "",
"focused": "", "focused": "",
"default": "" "default": ""
} },
"sort-by-number": true
} }
``` ```

View File

@ -266,11 +266,15 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-keyboard-state(5)* - *waybar-keyboard-state(5)*
- *waybar-memory(5)* - *waybar-memory(5)*
- *waybar-mpd(5)* - *waybar-mpd(5)*
- *waybar-mpris(5)*
- *waybar-network(5)* - *waybar-network(5)*
- *waybar-pulseaudio(5)* - *waybar-pulseaudio(5)*
- *waybar-river-mode(5)*
- *waybar-river-tags(5)* - *waybar-river-tags(5)*
- *waybar-river-window(5)*
- *waybar-states(5)* - *waybar-states(5)*
- *waybar-sway-mode(5)* - *waybar-sway-mode(5)*
- *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)* - *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)* - *waybar-sway-workspaces(5)*
- *waybar-wlr-taskbar(5)* - *waybar-wlr-taskbar(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.9.13', version: '0.9.17',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.49.0', meson_version: '>= 0.49.0',
default_options : [ default_options : [
@ -79,25 +79,33 @@ is_netbsd = host_machine.system() == 'netbsd'
is_openbsd = host_machine.system() == 'openbsd' is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads') thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled'])
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled())) giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or
jsoncpp = dependency('jsoncpp') 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') sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false)
libepoll = dependency('epoll-shim', required: false) libepoll = dependency('epoll-shim', required: false)
libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl')) libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
upower_glib = dependency('upower-glib', required: get_option('upower_glib')) upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
playerctl = dependency('playerctl', version : ['>=2.0.0'], required: get_option('mpris'))
libpulse = dependency('libpulse', required: get_option('pulseaudio')) libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev')) libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev')) libevdev = dependency('libevdev', required: get_option('libevdev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd')) libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry') 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')) libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found() if libsndio.found()
@ -147,13 +155,17 @@ src_files = files(
'src/modules/custom.cpp', 'src/modules/custom.cpp',
'src/modules/disk.cpp', 'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp', 'src/modules/idle_inhibitor.cpp',
'src/modules/image.cpp',
'src/modules/temperature.cpp', 'src/modules/temperature.cpp',
'src/modules/user.cpp',
'src/main.cpp', 'src/main.cpp',
'src/bar.cpp', 'src/bar.cpp',
'src/client.cpp', 'src/client.cpp',
'src/config.cpp', 'src/config.cpp',
'src/group.cpp', 'src/group.cpp',
'src/util/ustring_clen.cpp' 'src/util/ustring_clen.cpp',
'src/util/sanitize_str.cpp',
'src/util/rewrite_title.cpp'
) )
if is_linux if is_linux
@ -175,6 +187,11 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
'src/modules/memory/bsd.cpp', 'src/modules/memory/bsd.cpp',
'src/modules/memory/common.cpp', 'src/modules/memory/common.cpp',
) )
if is_freebsd
src_files += files(
'src/modules/battery.cpp',
)
endif
endif endif
add_project_arguments('-DHAVE_SWAY', language: 'cpp') add_project_arguments('-DHAVE_SWAY', language: 'cpp')
@ -184,7 +201,8 @@ src_files += [
'src/modules/sway/mode.cpp', 'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp', 'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp', 'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp' 'src/modules/sway/workspaces.cpp',
'src/modules/sway/scratchpad.cpp'
] ]
if true if true
@ -196,10 +214,19 @@ endif
if true if true
add_project_arguments('-DHAVE_RIVER', language: 'cpp') add_project_arguments('-DHAVE_RIVER', language: 'cpp')
src_files += 'src/modules/river/mode.cpp'
src_files += 'src/modules/river/tags.cpp' src_files += 'src/modules/river/tags.cpp'
src_files += 'src/modules/river/window.cpp' src_files += 'src/modules/river/window.cpp'
endif endif
if true
add_project_arguments('-DHAVE_HYPRLAND', language: 'cpp')
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() if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp') add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += 'src/modules/network.cpp' src_files += 'src/modules/network.cpp'
@ -216,11 +243,26 @@ if (upower_glib.found() and giounix.found() and not get_option('logind').disable
src_files += 'src/modules/upower/upower_tooltip.cpp' src_files += 'src/modules/upower/upower_tooltip.cpp'
endif endif
if (playerctl.found() and giounix.found() and not get_option('logind').disabled())
add_project_arguments('-DHAVE_MPRIS', language: 'cpp')
src_files += 'src/modules/mpris/mpris.cpp'
endif
if libpulse.found() if libpulse.found()
add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp') add_project_arguments('-DHAVE_LIBPULSE', language: 'cpp')
src_files += 'src/modules/pulseaudio.cpp' src_files += 'src/modules/pulseaudio.cpp'
endif endif
if libjack.found()
add_project_arguments('-DHAVE_LIBJACK', language: 'cpp')
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() if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp') add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files( src_files += files(
@ -236,8 +278,9 @@ if libudev.found() and (is_linux or libepoll.found())
src_files += 'src/modules/backlight.cpp' src_files += 'src/modules/backlight.cpp'
endif endif
if libevdev.found() and (is_linux or libepoll.found()) if libevdev.found() and (is_linux or libepoll.found()) and libinput.found() and (is_linux or libinotify.found())
add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp') add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')
add_project_arguments('-DHAVE_LIBINPUT', language: 'cpp')
src_files += 'src/modules/keyboard_state.cpp' src_files += 'src/modules/keyboard_state.cpp'
endif endif
@ -297,11 +340,16 @@ executable(
gtkmm, gtkmm,
dbusmenu_gtk, dbusmenu_gtk,
giounix, giounix,
libinput,
libnl, libnl,
libnlgen, libnlgen,
upower_glib, upower_glib,
playerctl,
libpulse, libpulse,
libjack,
libwireplumber,
libudev, libudev,
libinotify,
libepoll, libepoll,
libmpdclient, libmpdclient,
libevdev, libevdev,
@ -345,16 +393,20 @@ if scdoc.found()
'waybar-cpu.5.scd', 'waybar-cpu.5.scd',
'waybar-custom.5.scd', 'waybar-custom.5.scd',
'waybar-disk.5.scd', 'waybar-disk.5.scd',
'waybar-gamemode.5.scd',
'waybar-idle-inhibitor.5.scd', 'waybar-idle-inhibitor.5.scd',
'waybar-keyboard-state.5.scd', 'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd', 'waybar-memory.5.scd',
'waybar-mpd.5.scd', 'waybar-mpd.5.scd',
'waybar-mpris.5.scd',
'waybar-network.5.scd', 'waybar-network.5.scd',
'waybar-pulseaudio.5.scd', 'waybar-pulseaudio.5.scd',
'waybar-river-mode.5.scd',
'waybar-river-tags.5.scd', 'waybar-river-tags.5.scd',
'waybar-river-window.5.scd', 'waybar-river-window.5.scd',
'waybar-sway-language.5.scd', 'waybar-sway-language.5.scd',
'waybar-sway-mode.5.scd', 'waybar-sway-mode.5.scd',
'waybar-sway-scratchpad.5.scd',
'waybar-sway-window.5.scd', 'waybar-sway-window.5.scd',
'waybar-sway-workspaces.5.scd', 'waybar-sway-workspaces.5.scd',
'waybar-temperature.5.scd', 'waybar-temperature.5.scd',
@ -395,6 +447,7 @@ endif
catch2 = dependency( catch2 = dependency(
'catch2', 'catch2',
version: '>=3.0.0',
fallback: ['catch2', 'catch2_dep'], fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'), required: get_option('tests'),
) )

View File

@ -1,9 +1,11 @@
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.') option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.')
option('libinput', type: 'feature', value: 'auto', description: 'Enable libinput support for libinput related features')
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features') option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features') option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower') option('upower_glib', type: 'feature', value: 'auto', description: 'Enable support for upower')
option('mpris', type: 'feature', value: 'auto', description: 'Enable support for mpris')
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')
@ -14,3 +16,5 @@ option('sndio', type: 'feature', value: 'auto', description: 'Enable support for
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind') option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
option('tests', type: 'feature', value: 'auto', description: 'Enable tests') option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features') 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

@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright> </copyright>
<interface name="zriver_status_manager_v1" version="2"> <interface name="zriver_status_manager_v1" version="3">
<description summary="manage river status objects"> <description summary="manage river status objects">
A global factory for objects that receive status information specific A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar. to river. It could be used to implement, for example, a status bar.
@ -85,7 +85,7 @@
</event> </event>
</interface> </interface>
<interface name="zriver_seat_status_v1" version="1"> <interface name="zriver_seat_status_v1" version="3">
<description summary="track seat focus"> <description summary="track seat focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent focus of a seat. Note that (un)focused_output events will only be sent
@ -121,5 +121,13 @@
</description> </description>
<arg name="title" type="string" summary="title of the focused view"/> <arg name="title" type="string" summary="title of the focused view"/>
</event> </event>
<event name="mode" since="3">
<description summary="the active mode changed">
Sent once on binding the interface and again whenever a new mode
is entered (e.g. with riverctl enter-mode foobar).
</description>
<arg name="name" type="string" summary="name of the mode"/>
</event>
</interface> </interface>
</protocol> </protocol>

View File

@ -5,7 +5,7 @@
// "width": 1280, // Waybar width // "width": 1280, // Waybar width
"spacing": 4, // Gaps between modules (4px) "spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules // Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"], "modules-left": ["sway/workspaces", "sway/mode", "sway/scratchpad", "custom/media"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
@ -36,6 +36,13 @@
"sway/mode": { "sway/mode": {
"format": "<span style=\"italic\">{}</span>" "format": "<span style=\"italic\">{}</span>"
}, },
"sway/scratchpad": {
"format": "{icon} {count}",
"show-empty": false,
"format-icons": ["", ""],
"tooltip": true,
"tooltip-format": "{app}: {title}"
},
"mpd": { "mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ", "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
"format-disconnected": "Disconnected ", "format-disconnected": "Disconnected ",

View File

@ -34,21 +34,28 @@ window#waybar.chromium {
border: none; border: none;
} }
#workspaces button { button {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
/* Use box-shadow instead of border so the text isn't offset */ /* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent; box-shadow: inset 0 -3px transparent;
/* Avoid rounded borders under each workspace name */ /* Avoid rounded borders under each button name */
border: none; border: none;
border-radius: 0; border-radius: 0;
} }
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
button:hover {
background: inherit;
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
}
#workspaces button:hover { #workspaces button:hover {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
box-shadow: inset 0 -3px #ffffff;
} }
#workspaces button.focused { #workspaces button.focused {
@ -74,10 +81,12 @@ window#waybar.chromium {
#backlight, #backlight,
#network, #network,
#pulseaudio, #pulseaudio,
#wireplumber,
#custom-media, #custom-media,
#tray, #tray,
#mode, #mode,
#idle_inhibitor, #idle_inhibitor,
#scratchpad,
#mpd { #mpd {
padding: 0 10px; padding: 0 10px;
color: #ffffff; color: #ffffff;
@ -168,6 +177,15 @@ label:focus {
color: #2a5c45; color: #2a5c45;
} }
#wireplumber {
background-color: #fff0f5;
color: #000000;
}
#wireplumber.muted {
background-color: #f53c3c;
}
#custom-media { #custom-media {
background-color: #66cc99; background-color: #66cc99;
color: #2a5c45; color: #2a5c45;
@ -252,3 +270,11 @@ label:focus {
#keyboard-state > label.locked { #keyboard-state > label.locked {
background: rgba(0, 0, 0, 0.2); background: rgba(0, 0, 0, 0.2);
} }
#scratchpad {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad.empty {
background-color: transparent;
}

View File

@ -181,7 +181,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
if (vertical_ && height_ > 1) { if (vertical_ && height_ > 1) {
gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false); gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_BOTTOM, false);
gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, false); gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_TOP, false);
} else if (width_ > 1) { } else if (!vertical_ && width_ > 1) {
gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, false); gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_LEFT, false);
gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, false); gtk_layer_set_anchor(window_.gobj(), GTK_LAYER_SHELL_EDGE_RIGHT, false);
} }

View File

@ -1,12 +1,12 @@
#include "client.hpp" #include "client.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <iostream> #include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp" #include "util/clara.hpp"
#include "util/format.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() { waybar::Client *waybar::Client::inst() {

View File

@ -1,15 +1,17 @@
#include "config.hpp" #include "config.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <unistd.h> #include <unistd.h>
#include <wordexp.h> #include <wordexp.h>
#include <filesystem>
#include <fstream> #include <fstream>
#include <stdexcept> #include <stdexcept>
#include "util/json.hpp" #include "util/json.hpp"
namespace fs = std::filesystem;
namespace waybar { namespace waybar {
const std::vector<std::string> Config::CONFIG_DIRS = { const std::vector<std::string> Config::CONFIG_DIRS = {
@ -17,12 +19,25 @@ const std::vector<std::string> Config::CONFIG_DIRS = {
"/etc/xdg/waybar/", SYSCONFDIR "/xdg/waybar/", "./resources/", "/etc/xdg/waybar/", SYSCONFDIR "/xdg/waybar/", "./resources/",
}; };
std::optional<std::string> tryExpandPath(const std::string &path) { const char *Config::CONFIG_PATH_ENV = "WAYBAR_CONFIG_DIR";
std::optional<std::string> tryExpandPath(const std::string base, const std::string filename) {
fs::path path;
if (filename != "") {
path = fs::path(base) / fs::path(filename);
} else {
path = fs::path(base);
}
spdlog::debug("Try expanding: {}", path.string());
wordexp_t p; wordexp_t p;
if (wordexp(path.c_str(), &p, 0) == 0) { if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) { if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv; std::string result = *p.we_wordv;
wordfree(&p); wordfree(&p);
spdlog::debug("Found config file: {}", path.string());
return result; return result;
} }
wordfree(&p); wordfree(&p);
@ -32,10 +47,17 @@ std::optional<std::string> tryExpandPath(const std::string &path) {
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names, std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
const std::vector<std::string> &dirs) { const std::vector<std::string> &dirs) {
std::vector<std::string> paths; if (const char *dir = std::getenv(Config::CONFIG_PATH_ENV)) {
for (const auto &name : names) {
if (auto res = tryExpandPath(dir, name); res) {
return res;
}
}
}
for (const auto &dir : dirs) { for (const auto &dir : dirs) {
for (const auto &name : names) { for (const auto &name : names) {
if (auto res = tryExpandPath(dir + name); res) { if (auto res = tryExpandPath(dir, name); res) {
return res; return res;
} }
} }
@ -69,11 +91,11 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) {
if (includes.isArray()) { if (includes.isArray()) {
for (const auto &include : includes) { for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString()); spdlog::info("Including resource file: {}", include.asString());
setupConfig(config, tryExpandPath(include.asString()).value_or(""), ++depth); setupConfig(config, tryExpandPath(include.asString(), "").value_or(""), ++depth);
} }
} else if (includes.isString()) { } else if (includes.isString()) {
spdlog::info("Including resource file: {}", includes.asString()); spdlog::info("Including resource file: {}", includes.asString());
setupConfig(config, tryExpandPath(includes.asString()).value_or(""), ++depth); setupConfig(config, tryExpandPath(includes.asString(), "").value_or(""), ++depth);
} }
} }

View File

@ -7,7 +7,7 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
auto hash_pos = name.find('#'); auto hash_pos = name.find('#');
auto ref = name.substr(0, hash_pos); auto ref = name.substr(0, hash_pos);
auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : ""; auto id = hash_pos != std::string::npos ? name.substr(hash_pos + 1) : "";
#if defined(__linux__) && !defined(NO_FILESYSTEM) #if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
if (ref == "battery") { if (ref == "battery") {
return new waybar::modules::Battery(id, config_[name]); return new waybar::modules::Battery(id, config_[name]);
} }
@ -22,6 +22,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::upower::UPower(id, config_[name]); return new waybar::modules::upower::UPower(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_MPRIS
if (ref == "mpris") {
return new waybar::modules::mpris::Mpris(id, config_[name]);
}
#endif
#ifdef HAVE_SWAY #ifdef HAVE_SWAY
if (ref == "sway/mode") { if (ref == "sway/mode") {
return new waybar::modules::sway::Mode(id, config_[name]); return new waybar::modules::sway::Mode(id, config_[name]);
@ -35,6 +40,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/language") { if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]); return new waybar::modules::sway::Language(id, config_[name]);
} }
if (ref == "sway/scratchpad") {
return new waybar::modules::sway::Scratchpad(id, config_[name]);
}
#endif #endif
#ifdef HAVE_WLR #ifdef HAVE_WLR
if (ref == "wlr/taskbar") { if (ref == "wlr/taskbar") {
@ -47,12 +55,26 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
#endif #endif
#endif #endif
#ifdef HAVE_RIVER #ifdef HAVE_RIVER
if (ref == "river/mode") {
return new waybar::modules::river::Mode(id, bar_, config_[name]);
}
if (ref == "river/tags") { if (ref == "river/tags") {
return new waybar::modules::river::Tags(id, bar_, config_[name]); return new waybar::modules::river::Tags(id, bar_, config_[name]);
} }
if (ref == "river/window") { if (ref == "river/window") {
return new waybar::modules::river::Window(id, bar_, config_[name]); return new waybar::modules::river::Window(id, bar_, config_[name]);
} }
#endif
#ifdef HAVE_HYPRLAND
if (ref == "hyprland/window") {
return new waybar::modules::hyprland::Window(id, bar_, config_[name]);
}
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 #endif
if (ref == "idle_inhibitor") { if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]); return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
@ -70,6 +92,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "clock") { if (ref == "clock") {
return new waybar::modules::Clock(id, config_[name]); return new waybar::modules::Clock(id, config_[name]);
} }
if (ref == "user") {
return new waybar::modules::User(id, config_[name]);
}
if (ref == "disk") { if (ref == "disk") {
return new waybar::modules::Disk(id, config_[name]); return new waybar::modules::Disk(id, config_[name]);
} }
@ -115,12 +140,24 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "inhibitor") { if (ref == "inhibitor") {
return new waybar::modules::Inhibitor(id, bar_, config_[name]); return new waybar::modules::Inhibitor(id, bar_, config_[name]);
} }
#endif
#ifdef HAVE_LIBJACK
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 #endif
if (ref == "temperature") { if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]); return new waybar::modules::Temperature(id, config_[name]);
} }
if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) { if (ref.compare(0, 7, "custom/") == 0 && ref.size() > 7) {
return new waybar::modules::Custom(ref.substr(7), id, config_[name]); 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) { } catch (const std::exception& e) {
auto err = fmt::format("Disabling module \"{}\", {}", name, e.what()); 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 } // namespace
waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max) waybar::modules::Backlight::BacklightDev::BacklightDev(std::string name, int actual, int max,
: name_(std::move(name)), actual_(actual), max_(max) {} bool powered)
: name_(std::move(name)), actual_(actual), max_(max), powered_(powered) {}
std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; } std::string_view waybar::modules::Backlight::BacklightDev::name() const { return name_; }
@ -86,6 +87,10 @@ int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = 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) waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
: ALabel(config, "backlight", id, "{percent}%", 2), : ALabel(config, "backlight", id, "{percent}%", 2),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") { preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
@ -172,11 +177,16 @@ auto waybar::modules::Backlight::update() -> void {
return; return;
} }
const uint8_t percent = if (best->get_powered()) {
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max()); event_box_.show();
label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)), const uint8_t percent =
fmt::arg("icon", getIcon(percent)))); best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
getState(percent); 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 { } else {
if (!previous_best_.has_value()) { if (!previous_best_.has_value()) {
return; return;
@ -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 *actual = udev_device_get_sysattr_value(dev, actual_brightness_attr);
const char *max = udev_device_get_sysattr_value(dev, "max_brightness"); const char *max = udev_device_get_sysattr_value(dev, "max_brightness");
const char *power = udev_device_get_sysattr_value(dev, "bl_power");
auto found = auto found =
std::find_if(first, last, [name](const auto &device) { return device.name() == name; }); 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) { if (max != nullptr) {
found->set_max(std::stoi(max)); found->set_max(std::stoi(max));
} }
if (power != nullptr) {
found->set_powered(std::stoi(power) == 0);
}
} else { } else {
const int actual_int = actual == nullptr ? 0 : std::stoi(actual); const int actual_int = actual == nullptr ? 0 : std::stoi(actual);
const int max_int = max == nullptr ? 0 : std::stoi(max); 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; ++inserter;
} }
} }

View File

@ -1,9 +1,13 @@
#include "modules/battery.hpp" #include "modules/battery.hpp"
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config) waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) { : ALabel(config, "battery", id, "{capacity}%", 60) {
#if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC); battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) { if (battery_watch_fd_ == -1) {
throw std::runtime_error("Unable to listen batteries."); throw std::runtime_error("Unable to listen batteries.");
@ -19,11 +23,12 @@ waybar::modules::Battery::Battery(const std::string& id, const Json::Value& conf
if (global_watch < 0) { if (global_watch < 0) {
throw std::runtime_error("Could not watch for battery plug/unplug"); throw std::runtime_error("Could not watch for battery plug/unplug");
} }
#endif
worker(); worker();
} }
waybar::modules::Battery::~Battery() { waybar::modules::Battery::~Battery() {
#if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_); std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) { if (global_watch >= 0) {
@ -39,9 +44,16 @@ waybar::modules::Battery::~Battery() {
batteries_.erase(it); batteries_.erase(it);
} }
close(battery_watch_fd_); close(battery_watch_fd_);
#endif
} }
void waybar::modules::Battery::worker() { void waybar::modules::Battery::worker() {
#if defined(__FreeBSD__)
thread_timer_ = [this] {
dp.emit();
thread_timer_.sleep_for(interval_);
};
#else
thread_timer_ = [this] { thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an // Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason // inotify event for some reason
@ -68,9 +80,11 @@ void waybar::modules::Battery::worker() {
refreshBatteries(); refreshBatteries();
dp.emit(); dp.emit();
}; };
#endif
} }
void waybar::modules::Battery::refreshBatteries() { void waybar::modules::Battery::refreshBatteries() {
#if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_); std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found // Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map; std::map<fs::path, bool> check_map;
@ -93,6 +107,15 @@ void waybar::modules::Battery::refreshBatteries() {
std::ifstream(node.path() / "type") >> type; std::ifstream(node.path() / "type") >> type;
if (!type.compare("Battery")) { 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; check_map[node.path()] = true;
auto search = batteries_.find(node.path()); auto search = batteries_.find(node.path());
if (search == batteries_.end()) { if (search == batteries_.end()) {
@ -108,7 +131,7 @@ void waybar::modules::Battery::refreshBatteries() {
} }
auto adap_defined = config_["adapter"].isString(); auto adap_defined = config_["adapter"].isString();
if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) && if (((adap_defined && dir_name == config_["adapter"].asString()) || !adap_defined) &&
fs::exists(node.path() / "online")) { (fs::exists(node.path() / "online") || fs::exists(node.path() / "status"))) {
adapter_ = node.path(); adapter_ = node.path();
} }
} }
@ -135,6 +158,7 @@ void waybar::modules::Battery::refreshBatteries() {
batteries_.erase(check.first); batteries_.erase(check.first);
} }
} }
#endif
} }
// Unknown > Full > Not charging > Discharging > Charging // Unknown > Full > Not charging > Discharging > Charging
@ -156,105 +180,35 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
std::lock_guard<std::mutex> guard(battery_list_mutex_); std::lock_guard<std::mutex> guard(battery_list_mutex_);
try { try {
uint32_t total_power = 0; // μW #if defined(__FreeBSD__)
uint32_t total_energy = 0; // μWh /* Allocate state of battery units reported via ACPI. */
uint32_t total_energy_full = 0; int battery_units = 0;
uint32_t total_energy_full_design = 0; size_t battery_units_size = sizeof battery_units;
uint32_t total_capacity{0}; if (sysctlbyname("hw.acpi.battery.units", &battery_units, &battery_units_size, NULL, 0) != 0) {
std::string status = "Unknown"; throw std::runtime_error("sysctl hw.acpi.battery.units failed");
for (auto const& item : batteries_) { }
auto bat = item.first;
uint32_t power_now;
uint32_t energy_full;
uint32_t energy_now;
uint32_t energy_full_design;
uint32_t capacity{0};
std::string _status;
std::getline(std::ifstream(bat / "status"), _status);
// Some battery will report current and charge in μA/μAh. if (battery_units < 0) {
// Scale these by the voltage to get μW/μWh. throw std::runtime_error("No battery units");
if (fs::exists(bat / "current_now") || fs::exists(bat / "current_avg")) { }
uint32_t voltage_now;
uint32_t current_now;
uint32_t charge_now;
uint32_t charge_full;
uint32_t charge_full_design;
// Some batteries have only *_avg, not *_now
if (fs::exists(bat / "voltage_now"))
std::ifstream(bat / "voltage_now") >> voltage_now;
else
std::ifstream(bat / "voltage_avg") >> voltage_now;
if (fs::exists(bat / "current_now"))
std::ifstream(bat / "current_now") >> current_now;
else
std::ifstream(bat / "current_avg") >> current_now;
std::ifstream(bat / "charge_full") >> charge_full;
std::ifstream(bat / "charge_full_design") >> charge_full_design;
if (fs::exists(bat / "charge_now"))
std::ifstream(bat / "charge_now") >> charge_now;
else {
// charge_now is missing on some systems, estimate using capacity.
uint32_t capacity;
std::ifstream(bat / "capacity") >> capacity;
charge_now = (capacity * charge_full) / 100;
}
power_now = ((uint64_t)current_now * (uint64_t)voltage_now) / 1000000;
energy_now = ((uint64_t)charge_now * (uint64_t)voltage_now) / 1000000;
energy_full = ((uint64_t)charge_full * (uint64_t)voltage_now) / 1000000;
energy_full_design = ((uint64_t)charge_full_design * (uint64_t)voltage_now) / 1000000;
} // Gamepads such as PS Dualshock provide the only capacity
else if (fs::exists(bat / "energy_now") && fs::exists(bat / "energy_full")) {
std::ifstream(bat / "power_now") >> power_now;
std::ifstream(bat / "energy_now") >> energy_now;
std::ifstream(bat / "energy_full") >> energy_full;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
} else {
std::ifstream(bat / "capacity") >> capacity;
power_now = 0;
energy_now = 0;
energy_full = 0;
energy_full_design = 0;
}
// Show the "smallest" status among all batteries int capacity;
if (status_gt(status, _status)) { size_t size_capacity = sizeof capacity;
status = _status; if (sysctlbyname("hw.acpi.battery.life", &capacity, &size_capacity, NULL, 0) != 0) {
} throw std::runtime_error("sysctl hw.acpi.battery.life failed");
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
total_energy_full_design += energy_full_design;
total_capacity += capacity;
} }
if (!adapter_.empty() && status == "Discharging") { int time;
bool online; size_t size_time = sizeof time;
std::ifstream(adapter_ / "online") >> online; if (sysctlbyname("hw.acpi.battery.time", &time, &size_time, NULL, 0) != 0) {
if (online) { throw std::runtime_error("sysctl hw.acpi.battery.time failed");
status = "Plugged";
}
} }
float time_remaining = 0; int rate;
if (status == "Discharging" && total_power != 0) { size_t size_rate = sizeof rate;
time_remaining = (float)total_energy / total_power; if (sysctlbyname("hw.acpi.battery.rate", &rate, &size_rate, NULL, 0) != 0) {
} else if (status == "Charging" && total_power != 0) { throw std::runtime_error("sysctl hw.acpi.battery.rate failed");
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
if (time_remaining > 0.0f) {
// If we've turned positive it means the battery is past 100% and so
// just report that as no time remaining
time_remaining = 0.0f;
}
}
float capacity{0.0f};
if (total_energy_full > 0.0f) {
capacity = ((float)total_energy * 100.0f / (float)total_energy_full);
} else {
capacity = (float)total_capacity;
}
// Handle design-capacity
if (config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) {
capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design);
} }
auto status = getAdapterStatus(capacity);
// Handle full-at // Handle full-at
if (config_["full-at"].isUInt()) { if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt(); auto full_at = config_["full-at"].asUInt();
@ -268,13 +222,341 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
capacity = 100.f; capacity = 100.f;
} }
uint8_t cap = round(capacity); uint8_t cap = round(capacity);
if (cap == 100 && status == "Charging") { if (cap == 100 && status == "Plugged") {
// If we've reached 100% just mark as full as some batteries can stay // If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done // stuck reporting they're still charging but not yet done
status = "Full"; status = "Full";
} }
// spdlog::info("{} {} {} {}", capacity,time,status,rate);
return {capacity, time / 60.0, status, rate};
#elif defined(__linux__)
uint32_t total_power = 0; // μW
bool total_power_exists = false;
uint32_t total_energy = 0; // μWh
bool total_energy_exists = false;
uint32_t total_energy_full = 0;
bool total_energy_full_exists = false;
uint32_t total_energy_full_design = 0;
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_) {
auto bat = item.first;
std::string _status;
std::getline(std::ifstream(bat / "status"), _status);
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
uint32_t capacity = 0;
bool capacity_exists = false;
if (fs::exists(bat / "capacity")) {
capacity_exists = true;
std::ifstream(bat / "capacity") >> capacity;
}
uint32_t current_now = 0;
bool current_now_exists = false;
if (fs::exists(bat / "current_now")) {
current_now_exists = true;
std::ifstream(bat / "current_now") >> current_now;
} else if (fs::exists(bat / "current_avg")) {
current_now_exists = true;
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")) {
voltage_now_exists = true;
std::ifstream(bat / "voltage_now") >> voltage_now;
} else if (fs::exists(bat / "voltage_avg")) {
voltage_now_exists = true;
std::ifstream(bat / "voltage_avg") >> voltage_now;
}
uint32_t charge_full = 0;
bool charge_full_exists = false;
if (fs::exists(bat / "charge_full")) {
charge_full_exists = true;
std::ifstream(bat / "charge_full") >> charge_full;
}
uint32_t charge_full_design = 0;
bool charge_full_design_exists = false;
if (fs::exists(bat / "charge_full_design")) {
charge_full_design_exists = true;
std::ifstream(bat / "charge_full_design") >> charge_full_design;
}
uint32_t charge_now = 0;
bool charge_now_exists = false;
if (fs::exists(bat / "charge_now")) {
charge_now_exists = true;
std::ifstream(bat / "charge_now") >> charge_now;
}
uint32_t power_now = 0;
bool power_now_exists = false;
if (fs::exists(bat / "power_now")) {
power_now_exists = true;
std::ifstream(bat / "power_now") >> power_now;
}
uint32_t energy_now = 0;
bool energy_now_exists = false;
if (fs::exists(bat / "energy_now")) {
energy_now_exists = true;
std::ifstream(bat / "energy_now") >> energy_now;
}
uint32_t energy_full = 0;
bool energy_full_exists = false;
if (fs::exists(bat / "energy_full")) {
energy_full_exists = true;
std::ifstream(bat / "energy_full") >> energy_full;
}
uint32_t energy_full_design = 0;
bool energy_full_design_exists = false;
if (fs::exists(bat / "energy_full_design")) {
energy_full_design_exists = true;
std::ifstream(bat / "energy_full_design") >> energy_full_design;
}
if (!voltage_now_exists) {
if (power_now_exists && current_now_exists && current_now != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)power_now / (uint64_t)current_now;
} else if (energy_full_design_exists && charge_full_design_exists &&
charge_full_design != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)energy_full_design / (uint64_t)charge_full_design;
} else if (energy_now_exists) {
if (charge_now_exists && charge_now != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)energy_now / (uint64_t)charge_now;
} else if (capacity_exists && charge_full_exists) {
charge_now_exists = true;
charge_now = (uint64_t)charge_full * (uint64_t)capacity / 100;
if (charge_full != 0 && capacity != 0) {
voltage_now_exists = true;
voltage_now =
1000000 * (uint64_t)energy_now * 100 / (uint64_t)charge_full / (uint64_t)capacity;
}
}
} else if (energy_full_exists) {
if (charge_full_exists && charge_full != 0) {
voltage_now_exists = true;
voltage_now = 1000000 * (uint64_t)energy_full / (uint64_t)charge_full;
} else if (charge_now_exists && capacity_exists) {
if (capacity != 0) {
charge_full_exists = true;
charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity;
}
if (charge_now != 0) {
voltage_now_exists = true;
voltage_now =
10000 * (uint64_t)energy_full * (uint64_t)capacity / (uint64_t)charge_now;
}
}
}
}
if (!capacity_exists) {
if (charge_now_exists && charge_full_exists && charge_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)charge_now / (uint64_t)charge_full;
} else if (energy_now_exists && energy_full_exists && energy_full != 0) {
capacity_exists = true;
capacity = 100 * (uint64_t)energy_now / (uint64_t)energy_full;
} else if (charge_now_exists && energy_full_exists && voltage_now_exists) {
if (!charge_full_exists && voltage_now != 0) {
charge_full_exists = true;
charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;
}
if (energy_full != 0) {
capacity_exists = true;
capacity = (uint64_t)charge_now * (uint64_t)voltage_now / 10000 / (uint64_t)energy_full;
}
} else if (charge_full_exists && energy_now_exists && voltage_now_exists) {
if (!charge_now_exists && voltage_now != 0) {
charge_now_exists = true;
charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now;
}
if (voltage_now != 0 && charge_full != 0) {
capacity_exists = true;
capacity = 100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now /
(uint64_t)charge_full;
}
}
}
if (!energy_now_exists && voltage_now_exists) {
if (charge_now_exists) {
energy_now_exists = true;
energy_now = (uint64_t)charge_now * (uint64_t)voltage_now / 1000000;
} else if (capacity_exists && charge_full_exists) {
charge_now_exists = true;
charge_now = (uint64_t)capacity * (uint64_t)charge_full / 100;
energy_now_exists = true;
energy_now =
(uint64_t)voltage_now * (uint64_t)capacity * (uint64_t)charge_full / 1000000 / 100;
} else if (capacity_exists && energy_full) {
if (voltage_now != 0) {
charge_full_exists = true;
charge_full = 1000000 * (uint64_t)energy_full / (uint64_t)voltage_now;
charge_now_exists = true;
charge_now = (uint64_t)capacity * 10000 * (uint64_t)energy_full / (uint64_t)voltage_now;
}
energy_now_exists = true;
energy_now = (uint64_t)capacity * (uint64_t)energy_full / 100;
}
}
if (!energy_full_exists && voltage_now_exists) {
if (charge_full_exists) {
energy_full_exists = true;
energy_full = (uint64_t)charge_full * (uint64_t)voltage_now / 1000000;
} else if (charge_now_exists && capacity_exists && capacity != 0) {
charge_full_exists = true;
charge_full = 100 * (uint64_t)charge_now / (uint64_t)capacity;
energy_full_exists = true;
energy_full = (uint64_t)charge_now * (uint64_t)voltage_now / (uint64_t)capacity / 10000;
} else if (capacity_exists && energy_now) {
if (voltage_now != 0) {
charge_now_exists = true;
charge_now = 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now;
}
if (capacity != 0) {
energy_full_exists = true;
energy_full = 100 * (uint64_t)energy_now / (uint64_t)capacity;
if (voltage_now != 0) {
charge_full_exists = true;
charge_full =
100 * 1000000 * (uint64_t)energy_now / (uint64_t)voltage_now / (uint64_t)capacity;
}
}
}
}
if (!power_now_exists && voltage_now_exists && current_now_exists) {
power_now_exists = true;
power_now = (uint64_t)voltage_now * (uint64_t)current_now / 1000000;
}
if (!energy_full_design_exists && voltage_now_exists && charge_full_design_exists) {
energy_full_design_exists = true;
energy_full_design = (uint64_t)voltage_now * (uint64_t)charge_full_design / 1000000;
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) status = _status;
if (power_now_exists) {
total_power_exists = true;
total_power += power_now;
}
if (energy_now_exists) {
total_energy_exists = true;
total_energy += energy_now;
}
if (energy_full_exists) {
total_energy_full_exists = true;
total_energy_full += energy_full;
}
if (energy_full_design_exists) {
total_energy_full_design_exists = true;
total_energy_full_design += energy_full_design;
}
if (capacity_exists) {
total_capacity_exists = true;
total_capacity += capacity;
}
}
// 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;
std::getline(std::ifstream(adapter_ / "status"), current_status);
if (online && current_status != "Discharging") status = "Plugged";
}
float time_remaining{0.0f};
if (status == "Discharging" && time_to_empty_now_exists) {
if (time_to_empty_now != 0) time_remaining = (float)time_to_empty_now / 1000.0f;
} 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)
time_remaining = -(float)(total_energy_full - total_energy) / total_power;
// 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;
}
float calculated_capacity{0.0f};
if (total_capacity_exists) {
if (total_capacity > 0.0f)
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);
}
}
// Handle design-capacity
if ((config_["design-capacity"].isBool() ? config_["design-capacity"].asBool() : false) &&
total_energy_exists && total_energy_full_design_exists) {
if (total_energy_full_design > 0.0f)
calculated_capacity = ((float)total_energy * 100.0f / (float)total_energy_full_design);
}
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
if (full_at < 100) calculated_capacity = 100.f * calculated_capacity / full_at;
}
// Handle it gracefully by clamping at 100%
// This can happen when the battery is calibrating and goes above 100%
if (calculated_capacity > 100.f) calculated_capacity = 100.f;
uint8_t cap = round(calculated_capacity);
// If we've reached 100% just mark as full as some batteries can stay stuck reporting they're
// still charging but not yet done
if (cap == 100 && status == "Charging") status = "Full";
return {cap, time_remaining, status, total_power / 1e6}; return {cap, time_remaining, status, total_power / 1e6};
#endif
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what()); spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown", 0}; return {0, 0, "Unknown", 0};
@ -282,13 +564,26 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
} }
const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const { const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) const {
#if defined(__FreeBSD__)
int state;
size_t size_state = sizeof state;
if (sysctlbyname("hw.acpi.battery.state", &state, &size_state, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.state failed");
}
bool online = state == 2;
std::string status{"Unknown"}; // TODO: add status in FreeBSD
{
#else
if (!adapter_.empty()) { if (!adapter_.empty()) {
bool online; bool online;
std::string status;
std::ifstream(adapter_ / "online") >> online; std::ifstream(adapter_ / "online") >> online;
std::getline(std::ifstream(adapter_ / "status"), status);
#endif
if (capacity == 100) { if (capacity == 100) {
return "Full"; return "Full";
} }
if (online) { if (online && status != "Discharging") {
return "Plugged"; return "Plugged";
} }
return "Discharging"; return "Discharging";
@ -308,14 +603,18 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
if (config_["format-time"].isString()) { if (config_["format-time"].isString()) {
format = config_["format-time"].asString(); format = config_["format-time"].asString();
} }
return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes)); std::string zero_pad_minutes = fmt::format("{:02d}", minutes);
return fmt::format(format, fmt::arg("H", full_hours), fmt::arg("M", minutes),
fmt::arg("m", zero_pad_minutes));
} }
auto waybar::modules::Battery::update() -> void { auto waybar::modules::Battery::update() -> void {
#if defined(__linux__)
if (batteries_.empty()) { if (batteries_.empty()) {
event_box_.hide(); event_box_.hide();
return; return;
} }
#endif
auto [capacity, time_remaining, status, power] = getInfos(); auto [capacity, time_remaining, status, power] = getInfos();
if (status == "Unknown") { if (status == "Unknown") {
status = getAdapterStatus(capacity); status = getAdapterStatus(capacity);
@ -346,7 +645,7 @@ auto waybar::modules::Battery::update() -> void {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
} }
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default), label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("capacity", capacity), fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted))); fmt::arg("time", time_remaining_formatted)));
} }
if (!old_status_.empty()) { if (!old_status_.empty()) {

View File

@ -101,6 +101,7 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
} else { } else {
spdlog::error("findCurController() failed: no bluetooth controller found"); spdlog::error("findCurController() failed: no bluetooth controller found");
} }
event_box_.hide();
return; return;
} }
findConnectedDevices(cur_controller_.path, connected_devices_); findConnectedDevices(cur_controller_.path, connected_devices_);
@ -152,11 +153,23 @@ auto waybar::modules::Bluetooth::update() -> void {
#ifdef WANT_RFKILL #ifdef WANT_RFKILL
if (rfkill_.getState()) state = "disabled"; if (rfkill_.getState()) state = "disabled";
#endif #endif
bool battery_available =
state == "connected" && cur_focussed_device_.battery_percentage.has_value();
#ifdef WANT_RFKILL
// also adds enabled icon if icon for state is not defined
std::vector<std::string> states = {state, rfkill_.getState() ? "disabled" : "enabled"};
std::string icon = getIcon(0, states);
#else
std::string icon = getIcon(0, state);
#endif
std::string icon_label = icon;
std::string icon_tooltip = icon;
if (!alt_) { if (!alt_) {
if (state == "connected" && cur_focussed_device_.battery_percentage.has_value() && if (battery_available && config_["format-connected-battery"].isString()) {
config_["format-connected-battery"].isString()) {
format_ = config_["format-connected-battery"].asString(); format_ = config_["format-connected-battery"].asString();
icon_label = getIcon(cur_focussed_device_.battery_percentage.value_or(0));
} else if (config_["format-" + state].isString()) { } else if (config_["format-" + state].isString()) {
format_ = config_["format-" + state].asString(); format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) { } else if (config_["format"].isString()) {
@ -165,7 +178,10 @@ auto waybar::modules::Bluetooth::update() -> void {
format_ = default_format_; format_ = default_format_;
} }
} }
if (config_["tooltip-format-" + state].isString()) { if (battery_available && config_["tooltip-format-connected-battery"].isString()) {
tooltip_format = config_["tooltip-format-connected-battery"].asString();
icon_tooltip = getIcon(cur_focussed_device_.battery_percentage.value_or(0));
} else if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString(); tooltip_format = config_["tooltip-format-" + state].asString();
} else if (config_["tooltip-format"].isString()) { } else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString(); tooltip_format = config_["tooltip-format"].asString();
@ -196,7 +212,7 @@ auto waybar::modules::Bluetooth::update() -> void {
fmt::arg("controller_alias", cur_controller_.alias), fmt::arg("controller_alias", cur_controller_.alias),
fmt::arg("device_address", cur_focussed_device_.address), fmt::arg("device_address", cur_focussed_device_.address),
fmt::arg("device_address_type", cur_focussed_device_.address_type), fmt::arg("device_address_type", cur_focussed_device_.address_type),
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_label),
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)))); fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0))));
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -209,14 +225,18 @@ auto waybar::modules::Bluetooth::update() -> void {
if ((tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) || if ((tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) ||
tooltip_enumerate_connections_) { tooltip_enumerate_connections_) {
ss << "\n"; ss << "\n";
std::string enumerate_format = std::string enumerate_format;
(tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) std::string enumerate_icon;
? config_["tooltip-format-enumerate-connected-battery"].asString() if (tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) {
: config_["tooltip-format-enumerate-connected"].asString(); enumerate_format = config_["tooltip-format-enumerate-connected-battery"].asString();
enumerate_icon = getIcon(dev.battery_percentage.value_or(0));
} else {
enumerate_format = config_["tooltip-format-enumerate-connected"].asString();
}
ss << fmt::format( ss << fmt::format(
enumerate_format, fmt::arg("device_address", dev.address), enumerate_format, fmt::arg("device_address", dev.address),
fmt::arg("device_address_type", dev.address_type), fmt::arg("device_address_type", dev.address_type),
fmt::arg("device_alias", dev.alias), fmt::arg("device_alias", dev.alias), fmt::arg("icon", enumerate_icon),
fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0))); fmt::arg("device_battery_percentage", dev.battery_percentage.value_or(0)));
} }
} }
@ -234,7 +254,7 @@ auto waybar::modules::Bluetooth::update() -> void {
fmt::arg("controller_alias", cur_controller_.alias), fmt::arg("controller_alias", cur_controller_.alias),
fmt::arg("device_address", cur_focussed_device_.address), fmt::arg("device_address", cur_focussed_device_.address),
fmt::arg("device_address_type", cur_focussed_device_.address_type), fmt::arg("device_address_type", cur_focussed_device_.address_type),
fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("device_alias", cur_focussed_device_.alias), fmt::arg("icon", icon_tooltip),
fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)), fmt::arg("device_battery_percentage", cur_focussed_device_.battery_percentage.value_or(0)),
fmt::arg("device_enumerate", device_enumerate_))); fmt::arg("device_enumerate", device_enumerate_)));
} }

View File

@ -1,15 +1,11 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <iomanip>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include <ctime> #include <ctime>
#include <iomanip>
#include <regex>
#include <sstream> #include <sstream>
#include <type_traits> #include <type_traits>
@ -66,12 +62,40 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
} }
} }
if (is_calendar_in_tooltip_) {
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;
});
}
}
if (config_["locale"].isString()) { if (config_["locale"].isString()) {
locale_ = std::locale(config_["locale"].asString()); locale_ = std::locale(config_["locale"].asString());
} else { } else {
locale_ = std::locale(""); locale_ = std::locale("");
} }
if (config_["format-calendar-weeks"].isString()) {
fmt_str_weeks_ =
std::regex_replace(config_["format-calendar-weeks"].asString(), std::regex("\\{\\}"),
(first_day_of_week() == date::Monday) ? "{:%V}" : "{:%U}");
fmt_weeks_left_pad_ =
std::regex_replace(fmt_str_weeks_, std::regex("</?[^>]+>|\\{.*\\}"), "").length();
} else {
fmt_str_weeks_ = "";
}
if (config_["format-calendar"].isString()) {
fmt_str_calendar_ = config_["format-calendar"].asString();
} else {
fmt_str_calendar_ = "{}";
}
thread_ = [this] { thread_ = [this] {
dp.emit(); dp.emit();
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
@ -96,6 +120,12 @@ auto waybar::modules::Clock::update() -> void {
auto now = std::chrono::system_clock::now(); auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_, waybar_time wtime = {locale_,
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))}; date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
auto shifted_date = date::year_month_day{date::floor<date::days>(now)} + calendar_shift_;
auto 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 = ""; std::string text = "";
if (!is_timezone_fixed()) { if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt // As date dep is not fully compatible, prefer fmt
@ -109,18 +139,18 @@ auto waybar::modules::Clock::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
std::string calendar_lines = ""; std::string calendar_lines{""};
std::string timezoned_time_lines = ""; std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) { if (is_calendar_in_tooltip_) {
calendar_lines = calendar_text(wtime); calendar_lines = calendar_text(shifted_wtime);
} }
if (is_timezoned_list_in_tooltip_) { if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now); timezoned_time_lines = timezones_text(&now);
} }
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
text = text = fmt::format(tooltip_format, shifted_wtime,
fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines), fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines)); fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text); label_.set_tooltip_markup(text);
} }
} }
@ -136,20 +166,30 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
} }
auto dir = AModule::getScrollDir(e); auto dir = AModule::getScrollDir(e);
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (time_zones_.size() == 1) {
return true;
}
auto nr_zones = time_zones_.size(); // Shift calendar date
if (dir == SCROLL_DIR::UP) { if (calendar_shift_init_.count() != 0) {
size_t new_idx = current_time_zone_idx_ + 1; if (dir == SCROLL_DIR::UP)
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; calendar_shift_ += calendar_shift_init_;
else
calendar_shift_ -= calendar_shift_init_;
} else { } else {
current_time_zone_idx_ = // Change time zone
current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1; if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (time_zones_.size() == 1) {
return true;
}
auto nr_zones = time_zones_.size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = current_time_zone_idx_ + 1;
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
current_time_zone_idx_ =
current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
} }
update(); update();
@ -158,66 +198,72 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string { auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::string {
const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time()); const auto daypoint = date::floor<date::days>(wtime.ztime.get_local_time());
const auto ymd = date::year_month_day(daypoint); const auto ymd{date::year_month_day{daypoint}};
if (cached_calendar_ymd_ == ymd) {
return cached_calendar_text_; if (calendar_cached_ymd_ == ymd) {
return calendar_cached_text_;
} }
const date::year_month ym(ymd.year(), ymd.month()); const auto curr_day{(calendar_shift_init_.count() != 0 && calendar_shift_.count() != 0)
const auto curr_day = ymd.day(); ? date::day{0}
: ymd.day()};
const date::year_month ym{ymd.year(), ymd.month()};
const auto first_dow = first_day_of_week();
std::stringstream os; std::stringstream os;
const auto first_dow = first_day_of_week(); enum class WeeksSide {
int ws{0}; // weeks-pos: side(1 - left, 2 - right) LEFT,
int wn{0}; // weeknumber RIGHT,
HIDDEN,
};
WeeksSide weeks_pos = WeeksSide::HIDDEN;
if (config_["calendar-weeks-pos"].isString()) { if (config_["calendar-weeks-pos"].isString()) {
wn = (date::sys_days{date::year_month_day{ym / 1}} -
date::sys_days{date::year_month_day{ymd.year() / 1 / 1}})
.count() /
7 +
1;
if (config_["calendar-weeks-pos"].asString() == "left") { if (config_["calendar-weeks-pos"].asString() == "left") {
ws = 1; weeks_pos = WeeksSide::LEFT;
// Add paddings before the header // 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") { } else if (config_["calendar-weeks-pos"].asString() == "right") {
ws = 2; weeks_pos = WeeksSide::RIGHT;
} }
} }
weekdays_header(first_dow, os); weekdays_header(first_dow, os);
/* Print weeknumber on the left for the first row*/
if (ws == 1) { // First week day prefixed with spaces if needed.
print_iso_weeknum(os, wn); date::sys_days print_wd{ym / 1};
os << ' '; auto wd{date::weekday{print_wd}};
++wn;
}
// First week prefixed with spaces if needed.
auto wd = date::weekday(ym / 1);
auto empty_days = (wd - first_dow).count(); auto empty_days = (wd - first_dow).count();
/* Print weeknumber on the left for the first row*/
if (weeks_pos == WeeksSide::LEFT) {
os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
}
if (empty_days > 0) { if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' '); 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) { if (wd != first_dow) {
os << ' '; os << ' ';
} else if (unsigned(d) != 1) { } else if (unsigned(d) != 1) {
if (ws == 2) { if (weeks_pos == WeeksSide::RIGHT) {
os << ' '; os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
print_iso_weeknum(os, wn);
++wn;
} }
os << '\n'; os << '\n';
if (ws == 1) { print_wd = (ym / d);
print_iso_weeknum(os, wn);
os << ' '; if (weeks_pos == WeeksSide::LEFT) {
++wn; os << fmt::format(fmt_str_weeks_, print_wd) << ' ';
} }
} }
if (d == curr_day) { if (d == curr_day) {
if (config_["today-format"].isString()) { if (config_["today-format"].isString()) {
auto today_format = config_["today-format"].asString(); auto today_format = config_["today-format"].asString();
@ -225,32 +271,34 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
} else { } else {
os << "<b><u>" << date::format("%e", d) << "</u></b>"; os << "<b><u>" << date::format("%e", d) << "</u></b>";
} }
} else if (config_["format-calendar"].isString()) { } else {
os << fmt::format(config_["format-calendar"].asString(), date::format("%e", d)); os << fmt::format(fmt_str_calendar_, date::format("%e", d));
} else }
os << date::format("%e", d);
/*Print weeks on the right when the endings with spaces*/ /*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()); empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding());
if (empty_days > 0) { if (empty_days > 0 && empty_days < 7) {
os << std::string(empty_days * 3 + 1, ' '); os << std::string(empty_days * 3, ' ');
print_iso_weeknum(os, wn);
} }
os << ' ' << fmt::format(fmt_str_weeks_, print_wd);
} }
} }
auto result = os.str(); auto result = os.str();
cached_calendar_ymd_ = ymd; calendar_cached_ymd_ = ymd;
cached_calendar_text_ = result; calendar_cached_text_ = result;
return result; 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 { -> void {
std::stringstream res; std::stringstream res;
auto wd = first_dow; auto wd = first_week_day;
do { do {
if (wd != first_dow) res << ' '; if (wd != first_week_day) {
res << ' ';
}
Glib::ustring wd_ustring(date::format(locale_, "%a", wd)); Glib::ustring wd_ustring(date::format(locale_, "%a", wd));
auto clen = ustring_clen(wd_ustring); auto clen = ustring_clen(wd_ustring);
auto wd_len = wd_ustring.length(); auto wd_len = wd_ustring.length();
@ -261,8 +309,8 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
} }
const std::string pad(2 - clen, ' '); const std::string pad(2 - clen, ' ');
res << pad << wd_ustring; res << pad << wd_ustring;
} while (++wd != first_dow); } while (++wd != first_week_day);
res << "\n"; res << '\n';
if (config_["format-calendar-weekdays"].isString()) { if (config_["format-calendar-weekdays"].isString()) {
os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str()); os << fmt::format(config_["format-calendar-weekdays"].asString(), res.str());
@ -286,21 +334,11 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
timezone = date::current_zone(); timezone = date::current_zone();
} }
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))}; 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(); return os.str();
} }
auto waybar::modules::Clock::print_iso_weeknum(std::ostream& os, int weeknum) -> void {
std::stringstream res;
res << std::setfill('0') << std::setw(2) << weeknum;
if (config_["format-calendar-weeks"].isString()) {
os << fmt::format(config_["format-calendar-weeks"].asString(), res.str());
} else
os << res.str();
}
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
template <auto fn> template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>; using deleter_from_fn = std::integral_constant<decltype(fn), fn>;

View File

@ -39,7 +39,6 @@ auto waybar::modules::Cpu::update() -> void {
auto icons = std::vector<std::string>{state}; auto icons = std::vector<std::string>{state};
fmt::dynamic_format_arg_store<fmt::format_context> store; 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("load", cpu_load));
store.push_back(fmt::arg("usage", total_usage)); store.push_back(fmt::arg("usage", total_usage));
store.push_back(fmt::arg("icon", getIcon(total_usage, icons))); store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
store.push_back(fmt::arg("max_frequency", max_frequency)); store.push_back(fmt::arg("max_frequency", max_frequency));

View File

@ -4,7 +4,12 @@
waybar::modules::Custom::Custom(const std::string& name, const std::string& id, waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
const Json::Value& config) const Json::Value& config)
: ALabel(config, "custom-" + name, id, "{}"), name_(name), fp_(nullptr), pid_(-1) { : ALabel(config, "custom-" + name, id, "{}"),
name_(name),
id_(id),
percentage_(0),
fp_(nullptr),
pid_(-1) {
dp.emit(); dp.emit();
if (interval_.count() > 0) { if (interval_.count() > 0) {
delayWorker(); delayWorker();
@ -16,6 +21,7 @@ waybar::modules::Custom::Custom(const std::string& name, const std::string& id,
waybar::modules::Custom::~Custom() { waybar::modules::Custom::~Custom() {
if (pid_ != -1) { if (pid_ != -1) {
killpg(pid_, SIGTERM); killpg(pid_, SIGTERM);
waitpid(pid_, NULL, 0);
pid_ = -1; pid_ = -1;
} }
} }
@ -140,11 +146,14 @@ auto waybar::modules::Custom::update() -> void {
} }
auto classes = label_.get_style_context()->list_classes(); auto classes = label_.get_style_context()->list_classes();
for (auto const& c : classes) { for (auto const& c : classes) {
if (c == id_) continue;
label_.get_style_context()->remove_class(c); label_.get_style_context()->remove_class(c);
} }
for (auto const& c : class_) { for (auto const& c : class_) {
label_.get_style_context()->add_class(c); label_.get_style_context()->add_class(c);
} }
label_.get_style_context()->add_class("flat");
label_.get_style_context()->add_class("text-button");
event_box_.show(); event_box_.show();
} }
} }

View File

@ -109,7 +109,7 @@ Gamemode::Gamemode(const std::string& id, const Json::Value& config)
} }
Gamemode::~Gamemode() { Gamemode::~Gamemode() {
if (gamemode_proxy) gamemode_proxy->unreference(); if (gamemode_proxy) gamemode_proxy.reset();
if (gamemodeWatcher_id > 0) { if (gamemodeWatcher_id > 0) {
Gio::DBus::unwatch_name(gamemodeWatcher_id); Gio::DBus::unwatch_name(gamemodeWatcher_id);
gamemodeWatcher_id = 0; gamemodeWatcher_id = 0;

View File

@ -0,0 +1,193 @@
#include "modules/hyprland/backend.hpp"
#include <ctype.h>
#include <netdb.h>
#include <netinet/in.h>
#include <spdlog/spdlog.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <string>
namespace waybar::modules::hyprland {
void IPC::startIPC() {
// will start IPC and relay events to parseIPC
std::thread([&]() {
// check for hyprland
const char* HIS = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!HIS) {
spdlog::warn("Hyprland is not running, Hyprland IPC will not be available.");
return;
}
if (!modulesReady) return;
spdlog::info("Hyprland IPC starting");
struct sockaddr_un addr;
int socketfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (socketfd == -1) {
spdlog::error("Hyprland IPC: socketfd failed");
return;
}
addr.sun_family = AF_UNIX;
// socket path
std::string socketPath = "/tmp/hypr/" + std::string(HIS) + "/.socket2.sock";
strncpy(addr.sun_path, socketPath.c_str(), sizeof(addr.sun_path) - 1);
addr.sun_path[sizeof(addr.sun_path) - 1] = 0;
int l = sizeof(struct sockaddr_un);
if (connect(socketfd, (struct sockaddr*)&addr, l) == -1) {
spdlog::error("Hyprland IPC: Unable to connect?");
return;
}
auto file = fdopen(socketfd, "r");
while (1) {
// read
char buffer[1024]; // Hyprland socket2 events are max 1024 bytes
auto recievedCharPtr = fgets(buffer, 1024, file);
if (!recievedCharPtr) {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
continue;
}
callbackMutex.lock();
std::string messageRecieved(buffer);
messageRecieved = messageRecieved.substr(0, messageRecieved.find_first_of('\n'));
spdlog::debug("hyprland IPC received {}", messageRecieved);
parseIPC(messageRecieved);
callbackMutex.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}).detach();
}
void IPC::parseIPC(const std::string& ev) {
// todo
std::string request = ev.substr(0, ev.find_first_of('>'));
for (auto& [eventname, handler] : callbacks) {
if (eventname == request) {
handler->onEvent(ev);
}
}
}
void IPC::registerForIPC(const std::string& ev, EventHandler* ev_handler) {
if (!ev_handler) {
return;
}
callbackMutex.lock();
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();
}
std::string IPC::getSocket1Reply(const std::string& rq) {
// basically hyprctl
const auto SERVERSOCKET = socket(AF_UNIX, SOCK_STREAM, 0);
if (SERVERSOCKET < 0) {
spdlog::error("Hyprland IPC: Couldn't open a socket (1)");
return "";
}
const auto SERVER = gethostbyname("localhost");
if (!SERVER) {
spdlog::error("Hyprland IPC: Couldn't get host (2)");
return "";
}
// get the instance signature
auto instanceSig = getenv("HYPRLAND_INSTANCE_SIGNATURE");
if (!instanceSig) {
spdlog::error("Hyprland IPC: HYPRLAND_INSTANCE_SIGNATURE was not set! (Is Hyprland running?)");
return "";
}
std::string instanceSigStr = std::string(instanceSig);
sockaddr_un serverAddress = {0};
serverAddress.sun_family = AF_UNIX;
std::string socketPath = "/tmp/hypr/" + instanceSigStr + "/.socket.sock";
strcpy(serverAddress.sun_path, socketPath.c_str());
if (connect(SERVERSOCKET, (sockaddr*)&serverAddress, SUN_LEN(&serverAddress)) < 0) {
spdlog::error("Hyprland IPC: Couldn't connect to " + socketPath + ". (3)");
return "";
}
auto sizeWritten = write(SERVERSOCKET, rq.c_str(), rq.length());
if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't write (4)");
return "";
}
char buffer[8192] = {0};
sizeWritten = read(SERVERSOCKET, buffer, 8192);
if (sizeWritten < 0) {
spdlog::error("Hyprland IPC: Couldn't read (5)");
return "";
}
close(SERVERSOCKET);
return std::string(buffer);
}
} // namespace waybar::modules::hyprland

View File

@ -0,0 +1,139 @@
#include "modules/hyprland/language.hpp"
#include <spdlog/spdlog.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbregistry.h>
#include <util/sanitize_str.hpp>
#include "modules/hyprland/backend.hpp"
namespace waybar::modules::hyprland {
Language::Language(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
// get the active layout when open
initLanguage();
label_.hide();
ALabel::update();
// register for hyprland ipc
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()) {
label_.show();
label_.set_markup(layoutName_);
} else {
label_.hide();
}
ALabel::update();
}
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
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") && kbName != config_["keyboard-name"].asString())
return; // ignore
const auto briefName = getShortFrom(layoutName);
if (config_.isMember("format-" + briefName)) {
const auto propName = "format-" + briefName;
layoutName = fmt::format(format_, config_[propName].asString());
} else {
layoutName = fmt::format(format_, layoutName);
}
layoutName = waybar::util::sanitize_string(layoutName);
if (layoutName == layoutName_) return;
layoutName_ = layoutName;
spdlog::debug("hyprland language onevent with {}", layoutName);
dp.emit();
}
void Language::initLanguage() {
const auto inputDevices = gIPC->getSocket1Reply("devices");
const auto kbName = config_["keyboard-name"].asString();
try {
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"));
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_);
dp.emit();
} catch (std::exception& e) {
spdlog::error("hyprland language initLanguage failed with {}", e.what());
}
}
std::string Language::getShortFrom(const std::string& fullName) {
const auto CONTEXT = rxkb_context_new(RXKB_CONTEXT_LOAD_EXOTIC_RULES);
rxkb_context_parse_default_ruleset(CONTEXT);
std::string foundName = "";
rxkb_layout* layout = rxkb_layout_first(CONTEXT);
while (layout) {
std::string nameOfLayout = rxkb_layout_get_description(layout);
if (nameOfLayout != fullName) {
layout = rxkb_layout_next(layout);
continue;
}
std::string briefName = rxkb_layout_get_brief(layout);
rxkb_context_unref(CONTEXT);
return briefName;
}
rxkb_context_unref(CONTEXT);
return "";
}
} // namespace waybar::modules::hyprland

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

@ -0,0 +1,100 @@
#include "modules/hyprland/window.hpp"
#include <spdlog/spdlog.h>
#include <regex>
#include <util/sanitize_str.hpp>
#include "modules/hyprland/backend.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/rewrite_title.hpp"
namespace waybar::modules::hyprland {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "window", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
separate_outputs = config["separate-outputs"].as<bool>();
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
label_.hide();
ALabel::update();
// register for hyprland ipc
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 {
// fix ampersands
std::lock_guard<std::mutex> lg(mutex_);
if (!format_.empty()) {
label_.show();
label_.set_markup(
fmt::format(format_, waybar::util::rewriteTitle(lastView, config_["rewrite"])));
} else {
label_.hide();
}
ALabel::update();
}
int Window::getActiveWorkspaceID(std::string monitorName) {
auto cmd = waybar::util::command::exec("hyprctl monitors -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto monitor = std::find_if(json.begin(), json.end(),
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
if (monitor == std::end(json)) {
return 0;
}
return (*monitor)["activeWorkspace"]["id"].as<int>();
}
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<int>() == workspaceID;
});
if (workspace == std::end(json)) {
return "";
}
return (*workspace)["lastwindowtitle"].as<std::string>();
}
void Window::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
std::string windowName;
if (separate_outputs) {
windowName = getLastWindowTitle(getActiveWorkspaceID(this->bar_.output->name));
} else {
windowName = ev.substr(ev.find_first_of(',') + 1).substr(0, 256);
}
windowName = waybar::util::sanitize_string(windowName);
if (windowName == lastView) return;
lastView = windowName;
spdlog::debug("hyprland window onevent with {}", windowName);
dp.emit();
}
} // 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, waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar& bar,
const Json::Value& config) const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"), : ALabel(config, "idle_inhibitor", id, "{status}", 0, false, true),
bar_(bar), bar_(bar),
idle_inhibitor_(nullptr), idle_inhibitor_(nullptr),
pid_(-1) { pid_(-1) {
@ -16,6 +16,11 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
throw std::runtime_error("idle-inhibit not available"); throw std::runtime_error("idle-inhibit not available");
} }
if (waybar::modules::IdleInhibitor::modules.empty() && config_["start-activated"].isBool() &&
config_["start-activated"].asBool() != status) {
toggleStatus();
}
event_box_.add_events(Gdk::BUTTON_PRESS_MASK); event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect( event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle)); sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
@ -62,40 +67,54 @@ auto waybar::modules::IdleInhibitor::update() -> void {
fmt::arg("icon", getIcon(0, status_text)))); fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text); label_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(status_text); label_.set_tooltip_markup(
status ? fmt::format(config_["tooltip-format-activated"].isString()
? config_["tooltip-format-activated"].asString()
: "{status}",
fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text)))
: fmt::format(config_["tooltip-format-deactivated"].isString()
? config_["tooltip-format-deactivated"].asString()
: "{status}",
fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }
void waybar::modules::IdleInhibitor::toggleStatus() {
status = !status;
if (timeout_.connected()) {
/* cancel any already active timeout handler */
timeout_.disconnect();
}
if (status && config_["timeout"].isNumeric()) {
auto timeoutMins = config_["timeout"].asDouble();
int timeoutSecs = timeoutMins * 60;
timeout_ = Glib::signal_timeout().connect_seconds(
[]() {
/* intentionally not tied to a module instance lifetime
* as the output with `this` can be disconnected
*/
spdlog::info("deactivating idle_inhibitor by timeout");
status = false;
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
module->update();
}
/* disconnect */
return false;
},
timeoutSecs);
}
}
bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) { bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
if (e->button == 1) { if (e->button == 1) {
status = !status; toggleStatus();
if (timeout_.connected()) {
/* cancel any already active timeout handler */
timeout_.disconnect();
}
if (status && config_["timeout"].isNumeric()) {
auto timeoutMins = config_["timeout"].asDouble();
int timeoutSecs = timeoutMins * 60;
timeout_ = Glib::signal_timeout().connect_seconds(
[]() {
/* intentionally not tied to a module instance lifetime
* as the output with `this` can be disconnected
*/
spdlog::info("deactivating idle_inhibitor by timeout");
status = false;
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
module->update();
}
/* disconnect */
return false;
},
timeoutSecs);
}
// Make all other idle inhibitor modules update // Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) { for (auto const& module : waybar::modules::IdleInhibitor::modules) {

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

126
src/modules/jack.cpp Normal file
View File

@ -0,0 +1,126 @@
#include "modules/jack.hpp"
namespace waybar::modules {
JACK::JACK(const std::string &id, const Json::Value &config)
: ALabel(config, "jack", id, "{load}%", 1) {
running_ = false;
client_ = NULL;
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
std::string JACK::JACKState() {
std::lock_guard<std::mutex> lock(mutex_);
if (running_) {
load_ = jack_cpu_load(client_);
return state_;
}
xruns_ = 0;
load_ = 0;
bufsize_ = 0;
samplerate_ = 0;
if (client_) {
jack_client_close(client_);
client_ = NULL;
}
client_ = jack_client_open("waybar", JackNoStartServer, NULL);
if (!client_) return "disconnected";
if (config_["realtime"].isBool() && !config_["realtime"].asBool()) {
pthread_t jack_thread = jack_client_thread_id(client_);
jack_drop_real_time_scheduling(jack_thread);
}
bufsize_ = jack_get_buffer_size(client_);
samplerate_ = jack_get_sample_rate(client_);
jack_set_sample_rate_callback(client_, sampleRateCallback, this);
jack_set_buffer_size_callback(client_, bufSizeCallback, this);
jack_set_xrun_callback(client_, xrunCallback, this);
jack_on_shutdown(client_, shutdownCallback, this);
if (jack_activate(client_)) return "disconnected";
running_ = true;
return "connected";
}
auto JACK::update() -> void {
std::string format;
std::string state = JACKState();
float latency = 1000 * (float)bufsize_ / (float)samplerate_;
if (label_.get_style_context()->has_class("xrun")) {
label_.get_style_context()->remove_class("xrun");
state = "connected";
}
if (label_.get_style_context()->has_class(state_))
label_.get_style_context()->remove_class(state_);
label_.get_style_context()->add_class(state);
state_ = state;
if (config_["format-" + state].isString()) {
format = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
format = config_["format"].asString();
} else
format = "{load}%";
label_.set_markup(fmt::format(format, fmt::arg("load", std::round(load_)),
fmt::arg("bufsize", bufsize_), fmt::arg("samplerate", samplerate_),
fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_)));
if (tooltipEnabled()) {
std::string tooltip_format = "{bufsize}/{samplerate} {latency}ms";
if (config_["tooltip-format"].isString()) tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("load", std::round(load_)), fmt::arg("bufsize", bufsize_),
fmt::arg("samplerate", samplerate_), fmt::arg("latency", fmt::format("{:.2f}", latency)),
fmt::arg("xruns", xruns_)));
}
// Call parent update
ALabel::update();
}
int JACK::bufSize(jack_nframes_t size) {
bufsize_ = size;
return 0;
}
int JACK::sampleRate(jack_nframes_t rate) {
samplerate_ = rate;
return 0;
}
int JACK::xrun() {
xruns_ += 1;
state_ = "xrun";
return 0;
}
void JACK::shutdown() {
std::lock_guard<std::mutex> lock(mutex_);
running_ = false;
}
} // namespace waybar::modules
int bufSizeCallback(jack_nframes_t size, void *obj) {
return static_cast<waybar::modules::JACK *>(obj)->bufSize(size);
}
int sampleRateCallback(jack_nframes_t rate, void *obj) {
return static_cast<waybar::modules::JACK *>(obj)->sampleRate(rate);
}
int xrunCallback(void *obj) { return static_cast<waybar::modules::JACK *>(obj)->xrun(); }
void shutdownCallback(void *obj) { return static_cast<waybar::modules::JACK *>(obj)->shutdown(); }

View File

@ -8,8 +8,13 @@
extern "C" { extern "C" {
#include <fcntl.h> #include <fcntl.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
#include <poll.h>
#include <sys/inotify.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <unistd.h>
} }
class errno_error : public std::runtime_error { class errno_error : public std::runtime_error {
@ -99,8 +104,18 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
icon_unlocked_(config_["format-icons"]["unlocked"].isString() icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString() ? config_["format-icons"]["unlocked"].asString()
: "unlocked"), : "unlocked"),
fd_(0), devices_path_("/dev/input/"),
dev_(nullptr) { libinput_(nullptr),
libinput_devices_({}) {
static struct libinput_interface interface = {
[](const char* path, int flags, void* user_data) { return open(path, flags); },
[](int fd, void* user_data) { close(fd); }};
if (config_["interval"].isUInt()) {
spdlog::warn("keyboard-state: interval is deprecated");
}
libinput_ = libinput_path_create_context(&interface, NULL);
box_.set_name("keyboard-state"); box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) { if (config_["numlock"].asBool()) {
numlock_label_.get_style_context()->add_class("numlock"); numlock_label_.get_style_context()->add_class("numlock");
@ -121,70 +136,135 @@ waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar&
if (config_["device-path"].isString()) { if (config_["device-path"].isString()) {
std::string dev_path = config_["device-path"].asString(); std::string dev_path = config_["device-path"].asString();
fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY); tryAddDevice(dev_path);
dev_ = openDevice(fd_); if (libinput_devices_.empty()) {
} else { spdlog::error("keyboard-state: Cannot find device {}", dev_path);
DIR* dev_dir = opendir("/dev/input");
if (dev_dir == nullptr) {
throw errno_error(errno, "Failed to open /dev/input");
}
dirent* ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type != DT_CHR) continue;
std::string dev_path = std::string("/dev/input/") + ep->d_name;
int fd = openFile(dev_path.c_str(), O_NONBLOCK | O_CLOEXEC | O_RDONLY);
try {
auto dev = openDevice(fd);
if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
fd_ = fd;
dev_ = dev;
break;
}
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
closeFile(fd);
}
if (dev_ == nullptr) {
throw errno_error(errno, "Failed to find keyboard device");
} }
} }
thread_ = [this] { DIR* dev_dir = opendir(devices_path_.c_str());
if (dev_dir == nullptr) {
throw errno_error(errno, "Failed to open " + devices_path_);
}
dirent* ep;
while ((ep = readdir(dev_dir))) {
if (ep->d_type == DT_DIR) continue;
std::string dev_path = devices_path_ + ep->d_name;
tryAddDevice(dev_path);
}
if (libinput_devices_.empty()) {
throw errno_error(errno, "Failed to find keyboard device");
}
libinput_thread_ = [this] {
dp.emit(); dp.emit();
thread_.sleep_for(interval_); while (1) {
struct pollfd fd = {libinput_get_fd(libinput_), POLLIN, 0};
poll(&fd, 1, -1);
libinput_dispatch(libinput_);
struct libinput_event* event;
while ((event = libinput_get_event(libinput_))) {
auto type = libinput_event_get_type(event);
if (type == LIBINPUT_EVENT_KEYBOARD_KEY) {
auto keyboard_event = libinput_event_get_keyboard_event(event);
auto state = libinput_event_keyboard_get_key_state(keyboard_event);
if (state == LIBINPUT_KEY_STATE_RELEASED) {
uint32_t key = libinput_event_keyboard_get_key(keyboard_event);
switch (key) {
case KEY_CAPSLOCK:
case KEY_NUMLOCK:
case KEY_SCROLLLOCK:
dp.emit();
break;
default:
break;
}
}
}
libinput_event_destroy(event);
}
}
};
hotplug_thread_ = [this] {
int fd;
fd = inotify_init();
if (fd < 0) {
spdlog::error("Failed to initialize inotify: {}", strerror(errno));
return;
}
inotify_add_watch(fd, devices_path_.c_str(), IN_CREATE | IN_DELETE);
while (1) {
int BUF_LEN = 1024 * (sizeof(struct inotify_event) + 16);
char buf[BUF_LEN];
int length = read(fd, buf, 1024);
if (length < 0) {
spdlog::error("Failed to read inotify: {}", strerror(errno));
return;
}
for (int i = 0; i < length;) {
struct inotify_event* event = (struct inotify_event*)&buf[i];
std::string dev_path = devices_path_ + event->name;
if (event->mask & IN_CREATE) {
// Wait for device setup
int timeout = 10;
while (timeout--) {
try {
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
closeFile(fd);
break;
} catch (const errno_error& e) {
if (e.code == EACCES) {
sleep(1);
}
}
}
tryAddDevice(dev_path);
} else if (event->mask & IN_DELETE) {
auto it = libinput_devices_.find(dev_path);
if (it != libinput_devices_.end()) {
spdlog::info("Keyboard {} has been removed.", dev_path);
libinput_devices_.erase(it);
}
}
i += sizeof(struct inotify_event) + event->len;
}
}
}; };
} }
waybar::modules::KeyboardState::~KeyboardState() { waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_); for (const auto& [_, dev_ptr] : libinput_devices_) {
try { libinput_path_remove_device(dev_ptr);
closeFile(fd_);
} catch (const std::runtime_error& e) {
spdlog::warn(e.what());
} }
} }
auto waybar::modules::KeyboardState::update() -> void { auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS; sleep(0); // Wait for keyboard status change
while (err == LIBEVDEV_READ_STATUS_SUCCESS) { int numl = 0, capsl = 0, scrolll = 0;
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev); try {
while (err == LIBEVDEV_READ_STATUS_SYNC) { std::string dev_path;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev); if (config_["device-path"].isString() &&
libinput_devices_.find(config_["device-path"].asString()) != libinput_devices_.end()) {
dev_path = config_["device-path"].asString();
} else {
dev_path = libinput_devices_.begin()->first;
}
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd);
numl = libevdev_get_event_value(dev, EV_LED, LED_NUML);
capsl = libevdev_get_event_value(dev, EV_LED, LED_CAPSL);
scrolll = libevdev_get_event_value(dev, EV_LED, LED_SCROLLL);
libevdev_free(dev);
closeFile(fd);
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
} }
} }
if (-err != EAGAIN) {
throw errno_error(-err, "Failed to sync evdev device");
}
int numl = libevdev_get_event_value(dev_, EV_LED, LED_NUML);
int capsl = libevdev_get_event_value(dev_, EV_LED, LED_CAPSL);
int scrolll = libevdev_get_event_value(dev_, EV_LED, LED_SCROLLL);
struct { struct {
bool state; bool state;
@ -211,3 +291,25 @@ auto waybar::modules::KeyboardState::update() -> void {
AModule::update(); AModule::update();
} }
auto waybar::modules ::KeyboardState::tryAddDevice(const std::string& dev_path) -> void {
try {
int fd = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
auto dev = openDevice(fd);
if (supportsLockStates(dev)) {
spdlog::info("Found device {} at '{}'", libevdev_get_name(dev), dev_path);
if (libinput_devices_.find(dev_path) == libinput_devices_.end()) {
auto device = libinput_path_add_device(libinput_, dev_path.c_str());
libinput_device_ref(device);
libinput_devices_[dev_path] = device;
}
}
libevdev_free(dev);
closeFile(fd);
} catch (const errno_error& e) {
// ENOTTY just means the device isn't an evdev device, skip it
if (e.code != ENOTTY) {
spdlog::warn(e.what());
}
}
}

View File

@ -31,17 +31,18 @@ auto waybar::modules::Memory::update() -> void {
} }
if (memtotal > 0 && memfree >= 0) { if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2); float total_ram_gigabytes =
auto total_swap_gigabytes = swaptotal / std::pow(1024, 2); 0.01 * round(memtotal / 10485.76); // 100*10485.76 = 2^20 = 1024^2 = GiB/KiB
float total_swap_gigabytes = 0.01 * round(swaptotal / 10485.76);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; int used_ram_percentage = 100 * (memtotal - memfree) / memtotal;
int used_swap_percentage = 0; int used_swap_percentage = 0;
if (swaptotal && swapfree) { if (swaptotal && swapfree) {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal; used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
} }
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2); float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);
auto used_swap_gigabytes = (swaptotal - swapfree) / std::pow(1024, 2); float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76);
auto available_ram_gigabytes = memfree / std::pow(1024, 2); float available_ram_gigabytes = 0.01 * round(memfree / 10485.76);
auto available_swap_gigabytes = swapfree / std::pow(1024, 2); float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76);
auto format = format_; auto format = format_;
auto state = getState(used_ram_percentage); auto state = getState(used_ram_percentage);

View File

@ -4,6 +4,9 @@
#include <glibmm/ustring.h> #include <glibmm/ustring.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <util/sanitize_str.hpp>
using namespace waybar::util;
#include "modules/mpd/state.hpp" #include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE) #if defined(MPD_NOINLINE)
namespace waybar::modules { namespace waybar::modules {
@ -12,7 +15,7 @@ namespace waybar::modules {
#endif #endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config) waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5), : ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
module_name_(id.empty() ? "mpd" : "mpd#" + id), module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr), server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0), port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
@ -73,6 +76,16 @@ std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const
return result; return result;
} }
std::string waybar::modules::MPD::getFilename() const {
std::string path = mpd_song_get_uri(song_.get());
size_t position = path.find_last_of("/");
if (position == std::string::npos) {
return path;
} else {
return path.substr(position + 1);
}
}
void waybar::modules::MPD::setLabel() { void waybar::modules::MPD::setLabel() {
if (connection_ == nullptr) { if (connection_ == nullptr) {
label_.get_style_context()->add_class("disconnected"); label_.get_style_context()->add_class("disconnected");
@ -83,7 +96,12 @@ void waybar::modules::MPD::setLabel() {
auto format = config_["format-disconnected"].isString() auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString() ? config_["format-disconnected"].asString()
: "disconnected"; : "disconnected";
label_.set_markup(format); if (format.empty()) {
label_.set_markup(format);
label_.show();
} else {
label_.hide();
}
if (tooltipEnabled()) { if (tooltipEnabled()) {
std::string tooltip_format; std::string tooltip_format;
@ -94,13 +112,12 @@ void waybar::modules::MPD::setLabel() {
label_.set_tooltip_text(tooltip_format); label_.set_tooltip_text(tooltip_format);
} }
return; return;
} else {
label_.get_style_context()->remove_class("disconnected");
} }
label_.get_style_context()->remove_class("disconnected");
auto format = format_; auto format = format_;
Glib::ustring artist, album_artist, album, title; Glib::ustring artist, album_artist, album, title;
std::string date; std::string date, filename;
int song_pos = 0, queue_length = 0, volume = 0; int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime; std::chrono::seconds elapsedTime, totalTime;
@ -125,11 +142,12 @@ void waybar::modules::MPD::setLabel() {
stateIcon = getStateIcon(); stateIcon = getStateIcon();
artist = getTag(MPD_TAG_ARTIST); artist = sanitize_string(getTag(MPD_TAG_ARTIST));
album_artist = getTag(MPD_TAG_ALBUM_ARTIST); album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST));
album = getTag(MPD_TAG_ALBUM); album = sanitize_string(getTag(MPD_TAG_ALBUM));
title = getTag(MPD_TAG_TITLE); title = sanitize_string(getTag(MPD_TAG_TITLE));
date = getTag(MPD_TAG_DATE); date = sanitize_string(getTag(MPD_TAG_DATE));
filename = sanitize_string(getFilename());
song_pos = mpd_status_get_song_pos(status_.get()) + 1; song_pos = mpd_status_get_song_pos(status_.get()) + 1;
volume = mpd_status_get_volume(status_.get()); volume = mpd_status_get_volume(status_.get());
if (volume < 0) { if (volume < 0) {
@ -155,17 +173,21 @@ void waybar::modules::MPD::setLabel() {
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt()); if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try { try {
label_.set_markup( auto text = fmt::format(
fmt::format(format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()), format, fmt::arg("artist", artist.raw()), fmt::arg("albumArtist", album_artist.raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()), fmt::arg("album", album.raw()), fmt::arg("title", title.raw()), fmt::arg("date", date),
fmt::arg("album", Glib::Markup::escape_text(album).raw()), fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("title", Glib::Markup::escape_text(title).raw()), fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime), fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos), fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon), fmt::arg("filename", filename));
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon), if (text.empty()) {
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon))); label_.hide();
} else {
label_.show();
label_.set_markup(text);
}
} catch (fmt::format_error const& e) { } catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what()); spdlog::warn("mpd: format error: {}", e.what());
} }

View File

@ -10,6 +10,13 @@ namespace waybar::modules {
} // namespace waybar::modules } // namespace waybar::modules
#endif #endif
#if FMT_VERSION >= 90000
/* Satisfy fmt 9.x deprecation of implicit conversion of enums to int */
auto format_as(enum mpd_idle val) {
return static_cast<std::underlying_type_t<enum mpd_idle>>(val);
}
#endif
namespace waybar::modules::detail { namespace waybar::modules::detail {
#define IDLE_RUN_NOIDLE_AND_CMD(...) \ #define IDLE_RUN_NOIDLE_AND_CMD(...) \

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

@ -95,7 +95,7 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#endif #endif
frequency_(0.0) { 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 // 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 // to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown. // the module start with no text, but the the event_box_ is shown.
@ -339,10 +339,16 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("icon", getIcon(signal_strength_, state_)), fmt::arg("icon", getIcon(signal_strength_, state_)),
fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthDownBits", pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")),
fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s"))); 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) { if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text); label_.set_markup(text);
if (text.empty()) { if (text.empty()) {
@ -366,15 +372,21 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("bandwidthDownBits", fmt::arg("bandwidthDownBits",
pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")), pow_format(bandwidth_down * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")), fmt::arg("bandwidthUpBits", pow_format(bandwidth_up * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthTotalBits",
pow_format((bandwidth_up + bandwidth_down) * 8ull / interval_.count(), "b/s")),
fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")), fmt::arg("bandwidthDownOctets", pow_format(bandwidth_down / interval_.count(), "o/s")),
fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")), fmt::arg("bandwidthUpOctets", pow_format(bandwidth_up / interval_.count(), "o/s")),
fmt::arg("bandwidthTotalOctets",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "o/s")),
fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")), fmt::arg("bandwidthDownBytes", pow_format(bandwidth_down / interval_.count(), "B/s")),
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s"))); 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 (label_.get_tooltip_text() != tooltip_text) { if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_markup(tooltip_text);
} }
} else if (label_.get_tooltip_text() != text) { } else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_text(text); label_.set_tooltip_markup(text);
} }
} }
@ -634,7 +646,12 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
if (has_gateway && !has_destination && temp_idx != -1) { if (has_gateway && !has_destination && temp_idx != -1) {
// Check if this is the first default route we see, or if this new // Check if this is the first default route we see, or if this new
// route have a higher priority. // route have a higher priority.
if (!is_del_event && ((net->ifid_ == -1) || (priority < net->route_priority))) { /** Module doesn`t update state, because RTA_GATEWAY call before enable new router and set
higher priority. Disable router -> RTA_GATEWAY -> up new router -> set higher priority added
checking route id
**/
if (!is_del_event &&
((net->ifid_ == -1) || (priority < net->route_priority) || (net->ifid_ != temp_idx))) {
// Clear if's state for the case were there is a higher priority // Clear if's state for the case were there is a higher priority
// route on a different interface. // route on a different interface.
net->clearIface(); net->clearIface();

View File

@ -36,6 +36,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value
} }
waybar::modules::Pulseaudio::~Pulseaudio() { waybar::modules::Pulseaudio::~Pulseaudio() {
pa_context_disconnect(context_);
mainloop_api_->quit(mainloop_api_, 0); mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(mainloop_); pa_threaded_mainloop_stop(mainloop_);
pa_threaded_mainloop_free(mainloop_); pa_threaded_mainloop_free(mainloop_);
@ -90,16 +91,32 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick; pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_; pa_cvolume pa_volume = pa_volume_;
int max_volume = 100;
double step = 1;
// isDouble returns true for integers as well, just in case // isDouble returns true for integers as well, just in case
if (config_["scroll-step"].isDouble()) { if (config_["scroll-step"].isDouble()) {
change = round(config_["scroll-step"].asDouble() * volume_tick); step = config_["scroll-step"].asDouble();
} }
if (config_["max-volume"].isInt()) {
max_volume = std::min(config_["max-volume"].asInt(), static_cast<int>(PA_VOLUME_UI_MAX));
}
if (dir == SCROLL_DIR::UP) { if (dir == SCROLL_DIR::UP) {
if (volume_ + 1 <= 100) { if (volume_ < max_volume) {
if (volume_ + step > max_volume) {
change = round((max_volume - volume_) * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_inc(&pa_volume, change); pa_cvolume_inc(&pa_volume, change);
} }
} else if (dir == SCROLL_DIR::DOWN) { } else if (dir == SCROLL_DIR::DOWN) {
if (volume_ - 1 >= 0) { if (volume_ > 0) {
if (volume_ - step < 0) {
change = round(volume_ * volume_tick);
} else {
change = round(step * volume_tick);
}
pa_cvolume_dec(&pa_volume, change); pa_cvolume_dec(&pa_volume, change);
} }
} }
@ -166,6 +183,15 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
if (i == nullptr) return; if (i == nullptr) return;
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (pa->config_["ignored-sinks"].isArray()) {
for (const auto &ignored_sink : pa->config_["ignored-sinks"]) {
if (ignored_sink.asString() == i->description) {
return;
}
}
}
if (pa->current_sink_name_ == i->name) { if (pa->current_sink_name_ == i->name) {
if (i->state != PA_SINK_RUNNING) { if (i->state != PA_SINK_RUNNING) {
pa->current_sink_running_ = false; pa->current_sink_running_ = false;
@ -190,6 +216,8 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown"; pa->port_name_ = i->active_port != nullptr ? i->active_port->name : "Unknown";
if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) { if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
pa->form_factor_ = ff; pa->form_factor_ = ff;
} else {
pa->form_factor_ = "";
} }
pa->dp.emit(); pa->dp.emit();
} }
@ -232,7 +260,8 @@ auto waybar::modules::Pulseaudio::update() -> void {
if (!alt_) { if (!alt_) {
std::string format_name = "format"; std::string format_name = "format";
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio 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"; format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth"); label_.get_style_context()->add_class("bluetooth");
} else { } else {
@ -266,10 +295,16 @@ auto waybar::modules::Pulseaudio::update() -> void {
} }
} }
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); 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_), format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_), fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())))); fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon())));
if (text.empty()) {
label_.hide();
} else {
label_.set_markup(text);
label_.show();
}
getState(volume_); getState(volume_);
if (tooltipEnabled()) { if (tooltipEnabled()) {

114
src/modules/river/mode.cpp Normal file
View File

@ -0,0 +1,114 @@
#include "modules/river/mode.hpp"
#include <spdlog/spdlog.h>
#include <wayland-client.h>
#include "client.hpp"
namespace waybar::modules::river {
static void listen_focused_output(void *data, struct zriver_seat_status_v1 *seat_status,
struct wl_output *output) {
// Intentionally empty
}
static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *seat_status,
struct wl_output *output) {
// Intentionally empty
}
static void listen_focused_view(void *data, struct zriver_seat_status_v1 *seat_status,
const char *title) {
// Intentionally empty
}
static void listen_mode(void *data, struct zriver_seat_status_v1 *seat_status, const char *mode) {
static_cast<Mode *>(data)->handle_mode(mode);
}
static const zriver_seat_status_v1_listener seat_status_listener_impl = {
.focused_output = listen_focused_output,
.unfocused_output = listen_unfocused_output,
.focused_view = listen_focused_view,
.mode = listen_mode,
};
static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 3);
if (version < ZRIVER_SEAT_STATUS_V1_MODE_SINCE_VERSION) {
spdlog::error(
"river server does not support the \"mode\" event; the module will be disabled");
return;
}
static_cast<Mode *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
} else if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Mode *>(data)->seat_ = static_cast<struct wl_seat *>(
wl_registry_bind(registry, name, &wl_seat_interface, version));
}
}
static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
// Nobody cares
}
static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove};
Mode::Mode(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::ALabel(config, "mode", id, "{}"),
status_manager_{nullptr},
seat_{nullptr},
bar_(bar),
mode_{""},
seat_status_{nullptr} {
struct wl_display *display = Client::inst()->wl_display;
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener_impl, this);
wl_display_roundtrip(display);
if (!status_manager_) {
spdlog::error("river_status_manager_v1 not advertised");
return;
}
if (!seat_) {
spdlog::error("wl_seat not advertised");
}
label_.hide();
ALabel::update();
seat_status_ = zriver_status_manager_v1_get_river_seat_status(status_manager_, seat_);
zriver_seat_status_v1_add_listener(seat_status_, &seat_status_listener_impl, this);
zriver_status_manager_v1_destroy(status_manager_);
}
Mode::~Mode() {
if (seat_status_) {
zriver_seat_status_v1_destroy(seat_status_);
}
}
void Mode::handle_mode(const char *mode) {
if (format_.empty()) {
label_.hide();
} else {
if (!mode_.empty()) {
label_.get_style_context()->remove_class(mode_);
}
label_.get_style_context()->add_class(mode);
label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(mode).raw()));
label_.show();
}
mode_ = mode;
ALabel::update();
}
} /* namespace waybar::modules::river */

View File

@ -24,10 +24,16 @@ static void listen_unfocused_output(void *data, struct zriver_seat_status_v1 *zr
static_cast<Window *>(data)->handle_unfocused_output(output); static_cast<Window *>(data)->handle_unfocused_output(output);
} }
static void listen_mode(void *data, struct zriver_seat_status_v1 *zriver_seat_status_v1,
const char *mode) {
// This module doesn't care
}
static const zriver_seat_status_v1_listener seat_status_listener_impl{ static const zriver_seat_status_v1_listener seat_status_listener_impl{
.focused_output = listen_focused_output, .focused_output = listen_focused_output,
.unfocused_output = listen_unfocused_output, .unfocused_output = listen_unfocused_output,
.focused_view = listen_focused_view, .focused_view = listen_focused_view,
.mode = listen_mode,
}; };
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
@ -100,7 +106,7 @@ void Window::handle_focused_view(const char *title) {
label_.hide(); // hide empty labels or labels with empty format label_.hide(); // hide empty labels or labels with empty format
} else { } else {
label_.show(); label_.show();
label_.set_markup(fmt::format(format_, title)); label_.set_markup(fmt::format(format_, Glib::Markup::escape_text(title).raw()));
} }
ALabel::update(); ALabel::update();

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