Compare commits

..

269 Commits

Author SHA1 Message Date
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
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
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
114 changed files with 3130 additions and 627 deletions

View File

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

View File

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

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

View File

@ -7,9 +7,12 @@
#### Current features
- 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)
- Local time
- Battery
- UPower
- Network
- Bluetooth
- Pulseaudio
@ -70,6 +73,7 @@ libmpdclient [MPD module]
libsndio [sndio module]
libevdev [KeyboardState module]
xkbregistry
upower [UPower battery module]
```
**Build dependencies**
@ -103,6 +107,7 @@ sudo apt install \
libspdlog-dev \
libwayland-dev \
scdoc \
upower \
libxkbregistry-dev
```

35
include/AButton.hpp Normal file
View File

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

View File

@ -48,10 +48,10 @@ class AModule : public IModule {
{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_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_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

View File

@ -14,6 +14,7 @@ namespace waybar {
class Config {
public:
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 */
static std::optional<std::string> findConfigPath(

View File

@ -9,6 +9,7 @@
#ifdef HAVE_SWAY
#include "modules/sway/language.hpp"
#include "modules/sway/mode.hpp"
#include "modules/sway/scratchpad.hpp"
#include "modules/sway/window.hpp"
#include "modules/sway/workspaces.hpp"
#endif
@ -17,10 +18,16 @@
#include "modules/wlr/workspace_manager.hpp"
#endif
#ifdef HAVE_RIVER
#include "modules/river/mode.hpp"
#include "modules/river/tags.hpp"
#include "modules/river/window.hpp"
#endif
#if defined(__linux__) && !defined(NO_FILESYSTEM)
#ifdef HAVE_HYPRLAND
#include "modules/hyprland/backend.hpp"
#include "modules/hyprland/language.hpp"
#include "modules/hyprland/window.hpp"
#endif
#if defined(__FreeBSD__) || (defined(__linux__) && !defined(NO_FILESYSTEM))
#include "modules/battery.hpp"
#endif
#if defined(HAVE_CPU_LINUX) || defined(HAVE_CPU_BSD)
@ -62,9 +69,13 @@
#include "modules/bluetooth.hpp"
#include "modules/inhibitor.hpp"
#endif
#ifdef HAVE_LIBJACK
#include "modules/jack.hpp"
#endif
#include "bar.hpp"
#include "modules/custom.hpp"
#include "modules/temperature.hpp"
#include "modules/user.hpp"
namespace waybar {

View File

@ -5,7 +5,7 @@
#include <string_view>
#include <vector>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
@ -14,7 +14,7 @@ struct udev_device;
namespace waybar::modules {
class Backlight : public ALabel {
class Backlight : public AButton {
class BacklightDev {
public:
BacklightDev() = default;

View File

@ -6,14 +6,16 @@
#include <filesystem>
#endif
#include <fmt/format.h>
#if defined(__linux__)
#include <sys/inotify.h>
#endif
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
@ -24,7 +26,7 @@ namespace fs = std::experimental::filesystem;
namespace fs = std::filesystem;
#endif
class Battery : public ALabel {
class Battery : public AButton {
public:
Battery(const std::string&, const Json::Value&);
~Battery();

View File

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

View File

@ -2,7 +2,7 @@
#include <date/tz.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar {
@ -14,7 +14,7 @@ namespace modules {
const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
class Clock : public ALabel {
class Clock : public AButton {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;
@ -25,8 +25,9 @@ class Clock : public ALabel {
std::locale locale_;
std::vector<const date::time_zone*> time_zones_;
int current_time_zone_idx_;
date::year_month_day cached_calendar_ymd_ = date::January / 1 / 0;
std::string cached_calendar_text_;
date::year_month_day calendar_cached_ymd_{date::January / 1 / 0};
date::months calendar_shift_{0}, calendar_shift_init_{0};
std::string calendar_cached_text_;
bool is_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_;
@ -36,10 +37,8 @@ class Clock : public ALabel {
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday;
const date::time_zone* current_timezone();
auto print_iso_weeknum(std::ostream& os, int weeknum) -> void;
bool is_timezone_fixed();
auto timezones_text(std::chrono::system_clock::time_point* now) -> std::string;
};
} // namespace modules
} // namespace waybar

View File

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

View File

@ -5,14 +5,14 @@
#include <csignal>
#include <string>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/command.hpp"
#include "util/json.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Custom : public ALabel {
class Custom : public AButton {
public:
Custom(const std::string&, const std::string&, const Json::Value&);
~Custom();
@ -30,6 +30,7 @@ class Custom : public ALabel {
const std::string name_;
std::string text_;
std::string id_;
std::string alt_;
std::string tooltip_;
std::vector<std::string> class_;

View File

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

View File

@ -0,0 +1,28 @@
#pragma once
#include <deque>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
namespace waybar::modules::hyprland {
class IPC {
public:
IPC() { startIPC(); }
void registerForIPC(const std::string&, std::function<void(const std::string&)>);
std::string getSocket1Reply(const std::string& rq);
private:
void startIPC();
void parseIPC(const std::string&);
std::mutex callbackMutex;
std::deque<std::pair<std::string, std::function<void(const std::string&)>>> 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 "AButton.hpp"
#include "bar.hpp"
#include "modules/hyprland/backend.hpp"
#include "util/json.hpp"
namespace waybar::modules::hyprland {
class Language : public waybar::AButton {
public:
Language(const std::string&, const waybar::Bar&, const Json::Value&);
~Language() = default;
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,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:
Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default;
auto update() -> void;
private:
uint getActiveWorkspaceID(std::string);
std::string getLastWindowTitle(uint);
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

@ -2,13 +2,13 @@
#include <fmt/format.h>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "bar.hpp"
#include "client.hpp"
namespace waybar::modules {
class IdleInhibitor : public ALabel {
class IdleInhibitor : public AButton {
sigc::connection timeout_;
public:
@ -20,6 +20,7 @@ class IdleInhibitor : public ALabel {
private:
bool handleToggle(GdkEventButton* const& e);
void toggleStatus();
const Bar& bar_;
struct zwp_idle_inhibitor_v1* idle_inhibitor_;

View File

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

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

View File

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

View File

@ -7,12 +7,12 @@
#include <condition_variable>
#include <thread>
#include "ALabel.hpp"
#include "AButton.hpp"
#include "modules/mpd/state.hpp"
namespace waybar::modules {
class MPD : public ALabel {
class MPD : public AButton {
friend class detail::Context;
// State machine
@ -41,6 +41,7 @@ class MPD : public ALabel {
private:
std::string getTag(mpd_tag_type type, unsigned idx = 0) const;
std::string getFilename() const;
void setLabel();
std::string getStateIcon() const;
std::string getOptionIcon(std::string optionName, bool activated) const;

View File

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

View File

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

View File

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

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,17 +1,13 @@
#pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include "ALabel.hpp"
#include "AButton.hpp"
#include "util/sleeper_thread.hpp"
namespace waybar::modules {
class Clock : public ALabel {
class Clock : public AButton {
public:
Clock(const std::string&, const Json::Value&);
~Clock() = default;

View File

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

View File

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

View File

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

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:
void onEvent(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);
void getTree();
std::string rewriteTitle(const std::string& title);
void updateAppIconName();
void updateAppIcon();
@ -35,6 +34,7 @@ class Window : public AIconLabel, public sigc::trackable {
std::string app_class_;
std::string old_app_id_;
std::size_t app_nb_;
std::string shell_;
unsigned app_icon_size_{24};
bool update_app_icon_{true};
std::string app_icon_name_;

View File

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

View File

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

View File

@ -2,6 +2,8 @@
#include <libupower-glib/upower.h>
#include <unordered_map>
#include "gtkmm/box.h"
#include "gtkmm/label.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;
private:
util::SleeperThread thread_;
Glib::RefPtr<Gdk::Pixbuf> pixbuf_;
static constexpr inline int defaultUserImageWidth_ = 20;
static constexpr inline int defaultUserImageHeight_ = 20;
long uptime_as_seconds();
std::string get_user_login();
std::string get_user_home_dir();
std::string get_default_user_avatar_path();
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

@ -74,6 +74,10 @@ class Task {
std::string app_id_;
uint32_t state_ = 0;
int32_t drag_start_x;
int32_t drag_start_y;
int32_t drag_start_button = -1;
private:
std::string repr() const;
std::string state_string(bool = false) const;
@ -105,6 +109,12 @@ class Task {
/* Callbacks for Gtk events */
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:
bool operator==(const Task &) const;

View File

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

View File

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

View File

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

@ -39,7 +39,7 @@ The *battery* module displays the current capacity and state (eg. charging) of y
*format*: ++
typeof: string ++
default: {capacity}% ++
The format, how the time should be displayed.
The format, how information should be displayed.
*format-time*: ++
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 two arguments are:
The three arguments are:
*{H}*: Hours
*{M}*: Minutes
*{m}*: Zero-padded minutes
# CUSTOM FORMATS

View File

@ -42,6 +42,12 @@ Addressed by *bluetooth*
typeof: string ++
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*: ++
typeof: integer ++
Positive value to rotate the text label.
@ -115,6 +121,8 @@ Addressed by *bluetooth*
*{status}*: Status of the bluetooth device.
*{icon}*: Icon, as defined in *format-icons*.
*{num_connections}*: Number of connections the displayed controller has.
*{controller_address}*: Address of the displayed controller.

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-us": "AMERICA, HELL YEAH!" // For American English
"format-tr": "As bayrakları" // For Turkish
"keyboard-name": "AT Translated Set 2 keyboard"
}
```
# STYLE
- *#language*

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 ++
Threshold to be used when scrolling.
*start-activated*: ++
typeof: bool ++
default: *false* ++
Whether the inhibit should be activated when starting waybar.
*timeout*: ++
typeof: double ++
The number of minutes the inhibit should last.
@ -72,6 +77,14 @@ screensaving, also known as "presentation mode".
default: true ++
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
*{status}*: status (*activated* or *deactivated*)

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
*interval*: ++
Deprecated, this module use event loop now, the interval has no effect.
typeof: integer ++
default: 1 ++
The interval, in seconds, to poll the keyboard state.

View File

@ -149,10 +149,20 @@ Addressed by *network*
*{bandwidthDownBits}*: Instant down speed in bits/seconds.
*{bandwidthTotalBits}*: Instant total speed in bits/seconds.
*{bandwidthUpOctets}*: Instant up 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*.
# EXAMPLES

View File

@ -96,6 +96,15 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
default: true ++
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
*{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

@ -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*: ++
typeof: string ++
default: {} ++
The format, how information should be displayed. On {} data gets inserted.
default: {title} ++
The format, how information should be displayed.
*rotate*: ++
typeof: integer ++
@ -80,6 +80,15 @@ Addressed by *sway/window*
default: 24 ++
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* is an object where keys are regular expressions and values are

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.
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*: ++
typeof: bool ++
default: false ++
@ -75,7 +80,8 @@ Additional to workspace name matching, the following *format-icons* can be set.
"5": "",
"focused": "",
"default": ""
}
},
"sort-by-number": true
}
```

View File

@ -268,9 +268,12 @@ A module group is defined by specifying a module named "group/some-group-name".
- *waybar-mpd(5)*
- *waybar-network(5)*
- *waybar-pulseaudio(5)*
- *waybar-river-mode(5)*
- *waybar-river-tags(5)*
- *waybar-river-window(5)*
- *waybar-states(5)*
- *waybar-sway-mode(5)*
- *waybar-sway-scratchpad(5)*
- *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)*
- *waybar-wlr-taskbar(5)*

View File

@ -1,6 +1,6 @@
project(
'waybar', 'cpp', 'c',
version: '0.9.13',
version: '0.9.15',
license: 'MIT',
meson_version: '>= 0.49.0',
default_options : [
@ -79,8 +79,8 @@ is_netbsd = host_machine.system() == 'netbsd'
is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
fmt = dependency('fmt', version : ['>=8.1.1'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.10.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=enabled'])
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols')
@ -89,7 +89,9 @@ dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gt
giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled() or get_option('upower_glib').enabled()))
jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0')
libinotify = dependency('libinotify', required: false)
libepoll = dependency('epoll-shim', required: false)
libinput = dependency('libinput', required: get_option('libinput'))
libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
upower_glib = dependency('upower-glib', required: get_option('upower_glib'))
@ -98,6 +100,7 @@ libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry')
libjack = dependency('jack', required: get_option('jack'))
libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found()
@ -142,18 +145,22 @@ endif
src_files = files(
'src/factory.cpp',
'src/AModule.cpp',
'src/AButton.cpp',
'src/ALabel.cpp',
'src/AIconLabel.cpp',
'src/modules/custom.cpp',
'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp',
'src/modules/temperature.cpp',
'src/modules/user.cpp',
'src/main.cpp',
'src/bar.cpp',
'src/client.cpp',
'src/config.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
@ -175,6 +182,11 @@ elif is_dragonfly or is_freebsd or is_netbsd or is_openbsd
'src/modules/memory/bsd.cpp',
'src/modules/memory/common.cpp',
)
if is_freebsd
src_files += files(
'src/modules/battery.cpp',
)
endif
endif
add_project_arguments('-DHAVE_SWAY', language: 'cpp')
@ -184,7 +196,8 @@ src_files += [
'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp',
'src/modules/sway/workspaces.cpp'
'src/modules/sway/workspaces.cpp',
'src/modules/sway/scratchpad.cpp'
]
if true
@ -196,10 +209,18 @@ endif
if true
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/window.cpp'
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'
endif
if libnl.found() and libnlgen.found()
add_project_arguments('-DHAVE_LIBNL', language: 'cpp')
src_files += 'src/modules/network.cpp'
@ -221,6 +242,11 @@ if libpulse.found()
src_files += 'src/modules/pulseaudio.cpp'
endif
if libjack.found()
add_project_arguments('-DHAVE_LIBJACK', language: 'cpp')
src_files += 'src/modules/jack.cpp'
endif
if dbusmenu_gtk.found()
add_project_arguments('-DHAVE_DBUSMENU', language: 'cpp')
src_files += files(
@ -236,8 +262,9 @@ if libudev.found() and (is_linux or libepoll.found())
src_files += 'src/modules/backlight.cpp'
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_LIBINPUT', language: 'cpp')
src_files += 'src/modules/keyboard_state.cpp'
endif
@ -297,11 +324,14 @@ executable(
gtkmm,
dbusmenu_gtk,
giounix,
libinput,
libnl,
libnlgen,
upower_glib,
libpulse,
libjack,
libudev,
libinotify,
libepoll,
libmpdclient,
libevdev,
@ -345,16 +375,19 @@ if scdoc.found()
'waybar-cpu.5.scd',
'waybar-custom.5.scd',
'waybar-disk.5.scd',
'waybar-gamemode.5.scd',
'waybar-idle-inhibitor.5.scd',
'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd',
'waybar-mpd.5.scd',
'waybar-network.5.scd',
'waybar-pulseaudio.5.scd',
'waybar-river-mode.5.scd',
'waybar-river-tags.5.scd',
'waybar-river-window.5.scd',
'waybar-sway-language.5.scd',
'waybar-sway-mode.5.scd',
'waybar-sway-scratchpad.5.scd',
'waybar-sway-window.5.scd',
'waybar-sway-workspaces.5.scd',
'waybar-temperature.5.scd',
@ -395,6 +428,7 @@ endif
catch2 = dependency(
'catch2',
version: '>=3.0.0',
fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'),
)

View File

@ -1,4 +1,5 @@
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('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')
@ -14,3 +15,4 @@ option('sndio', type: 'feature', value: 'auto', description: 'Enable support for
option('logind', type: 'feature', value: 'auto', description: 'Enable support for logind')
option('tests', type: 'feature', value: 'auto', description: 'Enable tests')
option('experimental', type : 'boolean', value : false, description: 'Enable experimental features')
option('jack', type: 'feature', value: 'auto', description: 'Enable support for JACK')

View File

@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright>
<interface name="zriver_status_manager_v1" version="2">
<interface name="zriver_status_manager_v1" version="3">
<description summary="manage river status objects">
A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar.
@ -85,7 +85,7 @@
</event>
</interface>
<interface name="zriver_seat_status_v1" version="1">
<interface name="zriver_seat_status_v1" version="3">
<description summary="track seat focus">
This interface allows clients to receive information about the current
focus of a seat. Note that (un)focused_output events will only be sent
@ -121,5 +121,13 @@
</description>
<arg name="title" type="string" summary="title of the focused view"/>
</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>
</protocol>

View File

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

View File

@ -34,21 +34,28 @@ window#waybar.chromium {
border: none;
}
#workspaces button {
padding: 0 5px;
background-color: transparent;
color: #ffffff;
button {
/* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent;
/* Avoid rounded borders under each workspace name */
/* Avoid rounded borders under each button name */
border: none;
border-radius: 0;
}
/* 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 {
background: rgba(0, 0, 0, 0.2);
box-shadow: inset 0 -3px #ffffff;
}
#workspaces button.focused {
@ -78,6 +85,7 @@ window#waybar.chromium {
#tray,
#mode,
#idle_inhibitor,
#scratchpad,
#mpd {
padding: 0 10px;
color: #ffffff;
@ -252,3 +260,11 @@ label:focus {
#keyboard-state > label.locked {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad {
background: rgba(0, 0, 0, 0.2);
}
#scratchpad.empty {
background-color: transparent;
}

160
src/AButton.cpp Normal file
View File

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

View File

@ -181,7 +181,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
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_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_RIGHT, false);
}

View File

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

View File

@ -1,15 +1,17 @@
#include "config.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <wordexp.h>
#include <filesystem>
#include <fstream>
#include <stdexcept>
#include "util/json.hpp"
namespace fs = std::filesystem;
namespace waybar {
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/",
};
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;
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
spdlog::debug("Found config file: {}", path.string());
return result;
}
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,
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 &name : names) {
if (auto res = tryExpandPath(dir + name); res) {
if (auto res = tryExpandPath(dir, name); res) {
return res;
}
}
@ -69,11 +91,11 @@ void Config::resolveConfigIncludes(Json::Value &config, int depth) {
if (includes.isArray()) {
for (const auto &include : includes) {
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()) {
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 ref = name.substr(0, hash_pos);
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") {
return new waybar::modules::Battery(id, config_[name]);
}
@ -35,6 +35,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sway/language") {
return new waybar::modules::sway::Language(id, config_[name]);
}
if (ref == "sway/scratchpad") {
return new waybar::modules::sway::Scratchpad(id, config_[name]);
}
#endif
#ifdef HAVE_WLR
if (ref == "wlr/taskbar") {
@ -47,12 +50,23 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
#endif
#endif
#ifdef HAVE_RIVER
if (ref == "river/mode") {
return new waybar::modules::river::Mode(id, bar_, config_[name]);
}
if (ref == "river/tags") {
return new waybar::modules::river::Tags(id, bar_, config_[name]);
}
if (ref == "river/window") {
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]);
}
#endif
if (ref == "idle_inhibitor") {
return new waybar::modules::IdleInhibitor(id, bar_, config_[name]);
@ -70,6 +84,9 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "clock") {
return new waybar::modules::Clock(id, config_[name]);
}
if (ref == "user") {
return new waybar::modules::User(id, config_[name]);
}
if (ref == "disk") {
return new waybar::modules::Disk(id, config_[name]);
}
@ -115,6 +132,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "inhibitor") {
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
if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]);

View File

@ -87,7 +87,7 @@ int waybar::modules::Backlight::BacklightDev::get_max() const { return max_; }
void waybar::modules::Backlight::BacklightDev::set_max(int max) { max_ = max; }
waybar::modules::Backlight::Backlight(const std::string &id, const Json::Value &config)
: ALabel(config, "backlight", id, "{percent}%", 2),
: AButton(config, "backlight", id, "{percent}%", 2),
preferred_device_(config["device"].isString() ? config["device"].asString() : "") {
// Get initial state
{
@ -174,19 +174,19 @@ auto waybar::modules::Backlight::update() -> void {
const uint8_t percent =
best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
label_->set_markup(fmt::format(format_, fmt::arg("percent", std::to_string(percent)),
fmt::arg("icon", getIcon(percent))));
getState(percent);
} else {
if (!previous_best_.has_value()) {
return;
}
label_.set_markup("");
label_->set_markup("");
}
previous_best_ = best == nullptr ? std::nullopt : std::optional{*best};
previous_format_ = format_;
// Call parent update
ALabel::update();
AButton::update();
}
template <class ForwardIt>

View File

@ -1,9 +1,13 @@
#include "modules/battery.hpp"
#if defined(__FreeBSD__)
#include <sys/sysctl.h>
#endif
#include <spdlog/spdlog.h>
#include <iostream>
waybar::modules::Battery::Battery(const std::string& id, const Json::Value& config)
: ALabel(config, "battery", id, "{capacity}%", 60) {
: AButton(config, "battery", id, "{capacity}%", 60) {
#if defined(__linux__)
battery_watch_fd_ = inotify_init1(IN_CLOEXEC);
if (battery_watch_fd_ == -1) {
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) {
throw std::runtime_error("Could not watch for battery plug/unplug");
}
#endif
worker();
}
waybar::modules::Battery::~Battery() {
#if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_);
if (global_watch >= 0) {
@ -39,9 +44,16 @@ waybar::modules::Battery::~Battery() {
batteries_.erase(it);
}
close(battery_watch_fd_);
#endif
}
void waybar::modules::Battery::worker() {
#if defined(__FreeBSD__)
thread_timer_ = [this] {
dp.emit();
thread_timer_.sleep_for(interval_);
};
#else
thread_timer_ = [this] {
// Make sure we eventually update the list of batteries even if we miss an
// inotify event for some reason
@ -68,9 +80,11 @@ void waybar::modules::Battery::worker() {
refreshBatteries();
dp.emit();
};
#endif
}
void waybar::modules::Battery::refreshBatteries() {
#if defined(__linux__)
std::lock_guard<std::mutex> guard(battery_list_mutex_);
// Mark existing list of batteries as not necessarily found
std::map<fs::path, bool> check_map;
@ -108,7 +122,7 @@ void waybar::modules::Battery::refreshBatteries() {
}
auto adap_defined = config_["adapter"].isString();
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();
}
}
@ -135,6 +149,7 @@ void waybar::modules::Battery::refreshBatteries() {
batteries_.erase(check.first);
}
}
#endif
}
// Unknown > Full > Not charging > Discharging > Charging
@ -156,105 +171,35 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
std::lock_guard<std::mutex> guard(battery_list_mutex_);
try {
uint32_t total_power = 0; // μW
uint32_t total_energy = 0; // μWh
uint32_t total_energy_full = 0;
uint32_t total_energy_full_design = 0;
uint32_t total_capacity{0};
std::string status = "Unknown";
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);
#if defined(__FreeBSD__)
/* Allocate state of battery units reported via ACPI. */
int battery_units = 0;
size_t battery_units_size = sizeof battery_units;
if (sysctlbyname("hw.acpi.battery.units", &battery_units, &battery_units_size, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.units failed");
}
// Some battery will report current and charge in μA/μAh.
// Scale these by the voltage to get μW/μWh.
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;
}
if (battery_units < 0) {
throw std::runtime_error("No battery units");
}
// Show the "smallest" status among all batteries
if (status_gt(status, _status)) {
status = _status;
}
total_power += power_now;
total_energy += energy_now;
total_energy_full += energy_full;
total_energy_full_design += energy_full_design;
total_capacity += capacity;
int capacity;
size_t size_capacity = sizeof capacity;
if (sysctlbyname("hw.acpi.battery.life", &capacity, &size_capacity, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.life failed");
}
if (!adapter_.empty() && status == "Discharging") {
bool online;
std::ifstream(adapter_ / "online") >> online;
if (online) {
status = "Plugged";
}
int time;
size_t size_time = sizeof time;
if (sysctlbyname("hw.acpi.battery.time", &time, &size_time, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.time failed");
}
float time_remaining = 0;
if (status == "Discharging" && total_power != 0) {
time_remaining = (float)total_energy / total_power;
} else if (status == "Charging" && total_power != 0) {
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);
int rate;
size_t size_rate = sizeof rate;
if (sysctlbyname("hw.acpi.battery.rate", &rate, &size_rate, NULL, 0) != 0) {
throw std::runtime_error("sysctl hw.acpi.battery.rate failed");
}
auto status = getAdapterStatus(capacity);
// Handle full-at
if (config_["full-at"].isUInt()) {
auto full_at = config_["full-at"].asUInt();
@ -268,13 +213,317 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
capacity = 100.f;
}
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
// stuck reporting they're still charging but not yet done
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;
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;
}
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;
}
}
if (!adapter_.empty() && status == "Discharging") {
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" && total_power_exists && total_energy_exists) {
if (total_power != 0) time_remaining = (float)total_energy / total_power;
} 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;
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};
#endif
} catch (const std::exception& e) {
spdlog::error("Battery: {}", e.what());
return {0, 0, "Unknown", 0};
@ -282,13 +531,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 {
#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()) {
bool online;
std::string status;
std::ifstream(adapter_ / "online") >> online;
std::getline(std::ifstream(adapter_ / "status"), status);
#endif
if (capacity == 100) {
return "Full";
}
if (online) {
if (online && status != "Discharging") {
return "Plugged";
}
return "Discharging";
@ -308,14 +570,18 @@ const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemai
if (config_["format-time"].isString()) {
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 {
#if defined(__linux__)
if (batteries_.empty()) {
event_box_.hide();
return;
}
#endif
auto [capacity, time_remaining, status, power] = getInfos();
if (status == "Unknown") {
status = getAdapterStatus(capacity);
@ -345,14 +611,14 @@ auto waybar::modules::Battery::update() -> void {
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
label_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
button_.set_tooltip_text(fmt::format(tooltip_format, fmt::arg("timeTo", tooltip_text_default),
fmt::arg("power", power), fmt::arg("capacity", capacity),
fmt::arg("time", time_remaining_formatted)));
}
if (!old_status_.empty()) {
label_.get_style_context()->remove_class(old_status_);
button_.get_style_context()->remove_class(old_status_);
}
label_.get_style_context()->add_class(status);
button_.get_style_context()->add_class(status);
old_status_ = status;
if (!state.empty() && config_["format-" + status + "-" + state].isString()) {
format = config_["format-" + status + "-" + state].asString();
@ -366,10 +632,10 @@ auto waybar::modules::Battery::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{status + "-" + state, status, state};
label_.set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
label_->set_markup(fmt::format(format, fmt::arg("capacity", capacity), fmt::arg("power", power),
fmt::arg("icon", getIcon(capacity, icons)),
fmt::arg("time", time_remaining_formatted)));
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -80,7 +80,7 @@ auto getUcharProperty(GDBusProxy* proxy, const char* property_name) -> unsigned
} // namespace
waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value& config)
: ALabel(config, "bluetooth", id, " {status}", 10),
: AButton(config, "bluetooth", id, " {status}", 10),
#ifdef WANT_RFKILL
rfkill_{RFKILL_TYPE_BLUETOOTH},
#endif
@ -101,6 +101,7 @@ waybar::modules::Bluetooth::Bluetooth(const std::string& id, const Json::Value&
} else {
spdlog::error("findCurController() failed: no bluetooth controller found");
}
event_box_.hide();
return;
}
findConnectedDevices(cur_controller_.path, connected_devices_);
@ -152,11 +153,23 @@ auto waybar::modules::Bluetooth::update() -> void {
#ifdef WANT_RFKILL
if (rfkill_.getState()) state = "disabled";
#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 (state == "connected" && cur_focussed_device_.battery_percentage.has_value() &&
config_["format-connected-battery"].isString()) {
if (battery_available && config_["format-connected-battery"].isString()) {
format_ = config_["format-connected-battery"].asString();
icon_label = getIcon(cur_focussed_device_.battery_percentage.value_or(0));
} else if (config_["format-" + state].isString()) {
format_ = config_["format-" + state].asString();
} else if (config_["format"].isString()) {
@ -165,7 +178,10 @@ auto waybar::modules::Bluetooth::update() -> void {
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();
} else if (config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
@ -174,10 +190,10 @@ auto waybar::modules::Bluetooth::update() -> void {
format_.empty() ? event_box_.hide() : event_box_.show();
auto update_style_context = [this](const std::string& style_class, bool in_next_state) {
if (in_next_state && !label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->add_class(style_class);
} else if (!in_next_state && label_.get_style_context()->has_class(style_class)) {
label_.get_style_context()->remove_class(style_class);
if (in_next_state && !button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->add_class(style_class);
} else if (!in_next_state && button_.get_style_context()->has_class(style_class)) {
button_.get_style_context()->remove_class(style_class);
}
};
update_style_context("discoverable", cur_controller_.discoverable);
@ -189,14 +205,14 @@ auto waybar::modules::Bluetooth::update() -> void {
update_style_context(state, true);
state_ = state;
label_.set_markup(fmt::format(
label_->set_markup(fmt::format(
format_, fmt::arg("status", state_), fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
fmt::arg("controller_address_type", cur_controller_.address_type),
fmt::arg("controller_alias", cur_controller_.alias),
fmt::arg("device_address", cur_focussed_device_.address),
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))));
if (tooltipEnabled()) {
@ -209,14 +225,18 @@ auto waybar::modules::Bluetooth::update() -> void {
if ((tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) ||
tooltip_enumerate_connections_) {
ss << "\n";
std::string enumerate_format =
(tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value())
? config_["tooltip-format-enumerate-connected-battery"].asString()
: config_["tooltip-format-enumerate-connected"].asString();
std::string enumerate_format;
std::string enumerate_icon;
if (tooltip_enumerate_connections_battery_ && dev.battery_percentage.has_value()) {
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(
enumerate_format, fmt::arg("device_address", dev.address),
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)));
}
}
@ -226,7 +246,7 @@ auto waybar::modules::Bluetooth::update() -> void {
device_enumerate_.erase(0, 1);
}
}
label_.set_tooltip_text(fmt::format(
button_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("status", state_),
fmt::arg("num_connections", connected_devices_.size()),
fmt::arg("controller_address", cur_controller_.address),
@ -234,13 +254,13 @@ auto waybar::modules::Bluetooth::update() -> void {
fmt::arg("controller_alias", cur_controller_.alias),
fmt::arg("device_address", cur_focussed_device_.address),
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_enumerate", device_enumerate_)));
}
// Call parent update
ALabel::update();
AButton::update();
}
// NOTE: only for when the org.bluez.Battery1 interface is added/removed after/before a device is

View File

@ -1,15 +1,10 @@
#include "modules/clock.hpp"
#include <fmt/chrono.h>
#include <spdlog/spdlog.h>
#include <iomanip>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include <ctime>
#include <iomanip>
#include <sstream>
#include <type_traits>
@ -23,7 +18,7 @@
using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
: AButton(config, "clock", id, "{:%H:%M}", 60, false, false, true),
current_time_zone_idx_(0),
is_calendar_in_tooltip_(false),
is_timezoned_list_in_tooltip_(false) {
@ -66,6 +61,13 @@ 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()};
}
}
if (config_["locale"].isString()) {
locale_ = std::locale(config_["locale"].asString());
} else {
@ -94,8 +96,8 @@ bool waybar::modules::Clock::is_timezone_fixed() {
auto waybar::modules::Clock::update() -> void {
auto time_zone = current_timezone();
auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_,
date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
waybar_time wtime = {locale_, date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now) +
calendar_shift_)};
std::string text = "";
if (!is_timezone_fixed()) {
// As date dep is not fully compatible, prefer fmt
@ -105,28 +107,24 @@ auto waybar::modules::Clock::update() -> void {
} else {
text = fmt::format(format_, wtime);
}
label_.set_markup(text);
label_->set_markup(text);
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
std::string calendar_lines = "";
std::string timezoned_time_lines = "";
if (is_calendar_in_tooltip_) {
calendar_lines = calendar_text(wtime);
}
if (is_timezoned_list_in_tooltip_) {
timezoned_time_lines = timezones_text(&now);
}
std::string calendar_lines{""};
std::string timezoned_time_lines{""};
if (is_calendar_in_tooltip_) calendar_lines = calendar_text(wtime);
if (is_timezoned_list_in_tooltip_) timezoned_time_lines = timezones_text(&now);
auto tooltip_format = config_["tooltip-format"].asString();
text =
fmt::format(tooltip_format, wtime, fmt::arg(kCalendarPlaceholder.c_str(), calendar_lines),
fmt::arg(KTimezonedTimeListPlaceholder.c_str(), timezoned_time_lines));
label_.set_tooltip_markup(text);
button_.set_tooltip_markup(text);
}
}
// Call parent update
ALabel::update();
AButton::update();
}
bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
@ -136,20 +134,30 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* 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();
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;
// Shift calendar date
if (calendar_shift_init_.count() > 0) {
if (dir == SCROLL_DIR::UP)
calendar_shift_ += calendar_shift_init_;
else
calendar_shift_ -= calendar_shift_init_;
} else {
current_time_zone_idx_ =
current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
// Change time zone
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true;
}
if (time_zones_.size() == 1) {
return true;
}
auto nr_zones = time_zones_.size();
if (dir == SCROLL_DIR::UP) {
size_t new_idx = current_time_zone_idx_ + 1;
current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else {
current_time_zone_idx_ =
current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
}
update();
@ -158,25 +166,27 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll* e) {
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 ymd = date::year_month_day(daypoint);
if (cached_calendar_ymd_ == ymd) {
return cached_calendar_text_;
}
const auto ymd{date::year_month_day{daypoint}};
const date::year_month ym(ymd.year(), ymd.month());
const auto curr_day = ymd.day();
if (calendar_cached_ymd_ == ymd) return calendar_cached_text_;
const auto curr_day{(calendar_shift_init_.count() > 0 && calendar_shift_.count() != 0)
? date::day{0}
: ymd.day()};
const date::year_month ym{ymd.year(), ymd.month()};
const auto week_format{config_["format-calendar-weekdays"].isString()
? config_["format-calendar-weekdays"].asString()
: ""};
const auto wn_format{config_["format-calendar-weeks"].isString()
? config_["format-calendar-weeks"].asString()
: ""};
std::stringstream os;
const auto first_dow = first_day_of_week();
int ws{0}; // weeks-pos: side(1 - left, 2 - right)
int wn{0}; // weeknumber
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") {
ws = 1;
// Add paddings before the header
@ -187,15 +197,22 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
}
weekdays_header(first_dow, os);
/* Print weeknumber on the left for the first row*/
if (ws == 1) {
print_iso_weeknum(os, wn);
os << ' ';
++wn;
}
// First week prefixed with spaces if needed.
auto wd = date::weekday(ym / 1);
auto empty_days = (wd - first_dow).count();
date::sys_days lwd{static_cast<date::sys_days>(ym / 1) + date::days{7 - empty_days}};
if (first_dow == date::Monday) {
lwd -= date::days{1};
}
/* Print weeknumber on the left for the first row*/
if (ws == 1) {
os << fmt::format(wn_format, lwd);
os << ' ';
lwd += date::weeks{1};
}
if (empty_days > 0) {
os << std::string(empty_days * 3 - 1, ' ');
}
@ -206,16 +223,16 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
} else if (unsigned(d) != 1) {
if (ws == 2) {
os << ' ';
print_iso_weeknum(os, wn);
++wn;
os << fmt::format(wn_format, lwd);
lwd += date::weeks{1};
}
os << '\n';
if (ws == 1) {
print_iso_weeknum(os, wn);
os << fmt::format(wn_format, lwd);
os << ' ';
++wn;
lwd += date::weeks{1};
}
}
if (d == curr_day) {
@ -234,14 +251,14 @@ auto waybar::modules::Clock::calendar_text(const waybar_time& wtime) -> std::str
empty_days = 6 - (wd.c_encoding() - first_dow.c_encoding());
if (empty_days > 0) {
os << std::string(empty_days * 3 + 1, ' ');
print_iso_weeknum(os, wn);
os << fmt::format(wn_format, lwd);
}
}
}
auto result = os.str();
cached_calendar_ymd_ = ymd;
cached_calendar_text_ = result;
calendar_cached_ymd_ = ymd;
calendar_cached_text_ = result;
return result;
}
@ -291,16 +308,6 @@ auto waybar::modules::Clock::timezones_text(std::chrono::system_clock::time_poin
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
template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>;

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,171 @@
#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(ev);
}
}
}
void IPC::registerForIPC(const std::string& ev, std::function<void(const std::string&)> fn) {
callbackMutex.lock();
callbacks.emplace_back(std::make_pair(ev, fn));
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,123 @@
#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)
: AButton(config, "language", id, "{}", 0, true), bar_(bar) {
modulesReady = true;
if (!gIPC.get()) {
gIPC = std::make_unique<IPC>();
}
// get the active layout when open
initLanguage();
button_.hide();
AButton::update();
// register for hyprland ipc
gIPC->registerForIPC("activelayout", [&](const std::string& ev) { this->onEvent(ev); });
}
auto Language::update() -> void {
std::lock_guard<std::mutex> lg(mutex_);
if (!format_.empty()) {
button_.show();
label_->set_markup(layoutName_);
} else {
button_.hide();
}
AButton::update();
}
void Language::onEvent(const std::string& ev) {
std::lock_guard<std::mutex> lg(mutex_);
auto layoutName = ev.substr(ev.find_last_of(',') + 1);
auto keebName = ev.substr(0, ev.find_last_of(','));
keebName = keebName.substr(keebName.find_first_of('>') + 2);
if (config_.isMember("keyboard-name") && keebName != 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");
if (!config_.isMember("keyboard-name")) return;
const auto KEEBNAME = config_["keyboard-name"].asString();
try {
auto searcher = INPUTDEVICES.substr(INPUTDEVICES.find(KEEBNAME) + KEEBNAME.length());
searcher = searcher.substr(searcher.find("Keyboard at"));
searcher = searcher.substr(searcher.find("keymap:") + 7);
searcher = searcher.substr(0, searcher.find_first_of("\n\t"));
layoutName_ = searcher;
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,92 @@
#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", [&](const std::string& ev) { this->onEvent(ev); });
}
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();
}
uint Window::getActiveWorkspaceID(std::string monitorName) {
auto cmd = waybar::util::command::exec("hyprctl monitors -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto monitor = std::find_if(json.begin(), json.end(),
[&](Json::Value monitor) { return monitor["name"] == monitorName; });
assert(monitor != std::end(json));
return (*monitor)["activeWorkspace"]["id"].as<uint>();
}
std::string Window::getLastWindowTitle(uint workspaceID) {
auto cmd = waybar::util::command::exec("hyprctl workspaces -j");
assert(cmd.exit_code == 0);
Json::Value json = parser_.parse(cmd.out);
assert(json.isArray());
auto workspace = std::find_if(json.begin(), json.end(), [&](Json::Value workspace) {
return workspace["id"].as<uint>() == workspaceID;
});
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,
const Json::Value& config)
: ALabel(config, "idle_inhibitor", id, "{status}"),
: AButton(config, "idle_inhibitor", id, "{status}", 0, false, true),
bar_(bar),
idle_inhibitor_(nullptr),
pid_(-1) {
@ -16,6 +16,11 @@ waybar::modules::IdleInhibitor::IdleInhibitor(const std::string& id, const Bar&
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_.signal_button_press_event().connect(
sigc::mem_fun(*this, &IdleInhibitor::handleToggle));
@ -44,13 +49,13 @@ waybar::modules::IdleInhibitor::~IdleInhibitor() {
auto waybar::modules::IdleInhibitor::update() -> void {
// Check status
if (status) {
label_.get_style_context()->remove_class("deactivated");
button_.get_style_context()->remove_class("deactivated");
if (idle_inhibitor_ == nullptr) {
idle_inhibitor_ = zwp_idle_inhibit_manager_v1_create_inhibitor(
waybar::Client::inst()->idle_inhibit_manager, bar_.surface);
}
} else {
label_.get_style_context()->remove_class("activated");
button_.get_style_context()->remove_class("activated");
if (idle_inhibitor_ != nullptr) {
zwp_idle_inhibitor_v1_destroy(idle_inhibitor_);
idle_inhibitor_ = nullptr;
@ -58,44 +63,58 @@ auto waybar::modules::IdleInhibitor::update() -> void {
}
std::string status_text = status ? "activated" : "deactivated";
label_.set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
label_.get_style_context()->add_class(status_text);
label_->set_markup(fmt::format(format_, fmt::arg("status", status_text),
fmt::arg("icon", getIcon(0, status_text))));
button_.get_style_context()->add_class(status_text);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_text);
button_.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
ALabel::update();
AButton::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) {
if (e->button == 1) {
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);
}
toggleStatus();
// Make all other idle inhibitor modules update
for (auto const& module : waybar::modules::IdleInhibitor::modules) {
@ -105,6 +124,6 @@ bool waybar::modules::IdleInhibitor::handleToggle(GdkEventButton* const& e) {
}
}
ALabel::handleToggle(e);
AButton::handleToggle(e);
return true;
}

View File

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

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" {
#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/types.h>
#include <unistd.h>
}
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()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr) {
devices_path_("/dev/input/"),
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");
if (config_["numlock"].asBool()) {
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()) {
std::string dev_path = config_["device-path"].asString();
fd_ = openFile(dev_path, O_NONBLOCK | O_CLOEXEC | O_RDONLY);
dev_ = openDevice(fd_);
} else {
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");
tryAddDevice(dev_path);
if (libinput_devices_.empty()) {
spdlog::error("keyboard-state: Cannot find device {}", dev_path);
}
}
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();
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() {
libevdev_free(dev_);
try {
closeFile(fd_);
} catch (const std::runtime_error& e) {
spdlog::warn(e.what());
for (const auto& [_, dev_ptr] : libinput_devices_) {
libinput_path_remove_device(dev_ptr);
}
}
auto waybar::modules::KeyboardState::update() -> void {
int err = LIBEVDEV_READ_STATUS_SUCCESS;
while (err == LIBEVDEV_READ_STATUS_SUCCESS) {
input_event ev;
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_NORMAL, &ev);
while (err == LIBEVDEV_READ_STATUS_SYNC) {
err = libevdev_next_event(dev_, LIBEVDEV_READ_FLAG_SYNC, &ev);
sleep(0); // Wait for keyboard status change
int numl = 0, capsl = 0, scrolll = 0;
try {
std::string dev_path;
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 {
bool state;
@ -211,3 +291,25 @@ auto waybar::modules::KeyboardState::update() -> void {
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

@ -1,7 +1,7 @@
#include "modules/memory.hpp"
waybar::modules::Memory::Memory(const std::string& id, const Json::Value& config)
: ALabel(config, "memory", id, "{}%", 30) {
: AButton(config, "memory", id, "{}%", 30) {
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
@ -31,17 +31,18 @@ auto waybar::modules::Memory::update() -> void {
}
if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
auto total_swap_gigabytes = swaptotal / std::pow(1024, 2);
float total_ram_gigabytes =
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_swap_percentage = 0;
if (swaptotal && swapfree) {
used_swap_percentage = 100 * (swaptotal - swapfree) / swaptotal;
}
auto used_ram_gigabytes = (memtotal - memfree) / std::pow(1024, 2);
auto used_swap_gigabytes = (swaptotal - swapfree) / std::pow(1024, 2);
auto available_ram_gigabytes = memfree / std::pow(1024, 2);
auto available_swap_gigabytes = swapfree / std::pow(1024, 2);
float used_ram_gigabytes = 0.01 * round((memtotal - memfree) / 10485.76);
float used_swap_gigabytes = 0.01 * round((swaptotal - swapfree) / 10485.76);
float available_ram_gigabytes = 0.01 * round(memfree / 10485.76);
float available_swap_gigabytes = 0.01 * round(swapfree / 10485.76);
auto format = format_;
auto state = getState(used_ram_percentage);
@ -54,7 +55,7 @@ auto waybar::modules::Memory::update() -> void {
} else {
event_box_.show();
auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format(
label_->set_markup(fmt::format(
format, used_ram_percentage, fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
@ -66,7 +67,7 @@ auto waybar::modules::Memory::update() -> void {
if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString();
label_.set_tooltip_text(fmt::format(
button_.set_tooltip_text(fmt::format(
tooltip_format, used_ram_percentage, fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage),
@ -74,12 +75,12 @@ auto waybar::modules::Memory::update() -> void {
fmt::arg("swapUsed", used_swap_gigabytes), fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
button_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
}
}
} else {
event_box_.hide();
}
// Call parent update
ALabel::update();
AButton::update();
}

View File

@ -4,6 +4,9 @@
#include <glibmm/ustring.h>
#include <spdlog/spdlog.h>
#include <util/sanitize_str.hpp>
using namespace waybar::util;
#include "modules/mpd/state.hpp"
#if defined(MPD_NOINLINE)
namespace waybar::modules {
@ -12,7 +15,7 @@ namespace waybar::modules {
#endif
waybar::modules::MPD::MPD(const std::string& id, const Json::Value& config)
: ALabel(config, "mpd", id, "{album} - {artist} - {title}", 5),
: AButton(config, "mpd", id, "{album} - {artist} - {title}", 5, false, true),
module_name_(id.empty() ? "mpd" : "mpd#" + id),
server_(nullptr),
port_(config_["port"].isUInt() ? config["port"].asUInt() : 0),
@ -44,7 +47,7 @@ auto waybar::modules::MPD::update() -> void {
context_.update();
// Call parent update
ALabel::update();
AButton::update();
}
void waybar::modules::MPD::queryMPD() {
@ -73,17 +76,27 @@ std::string waybar::modules::MPD::getTag(mpd_tag_type type, unsigned idx) const
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() {
if (connection_ == nullptr) {
label_.get_style_context()->add_class("disconnected");
label_.get_style_context()->remove_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
button_.get_style_context()->add_class("disconnected");
button_.get_style_context()->remove_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
auto format = config_["format-disconnected"].isString()
? config_["format-disconnected"].asString()
: "disconnected";
label_.set_markup(format);
label_->set_markup(format);
if (tooltipEnabled()) {
std::string tooltip_format;
@ -91,16 +104,16 @@ void waybar::modules::MPD::setLabel() {
? config_["tooltip-format-disconnected"].asString()
: "MPD (disconnected)";
// Nothing to format
label_.set_tooltip_text(tooltip_format);
button_.set_tooltip_text(tooltip_format);
}
return;
} else {
label_.get_style_context()->remove_class("disconnected");
button_.get_style_context()->remove_class("disconnected");
}
auto format = format_;
Glib::ustring artist, album_artist, album, title;
std::string date;
std::string date, filename;
int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime;
@ -108,28 +121,29 @@ void waybar::modules::MPD::setLabel() {
if (stopped()) {
format =
config_["format-stopped"].isString() ? config_["format-stopped"].asString() : "stopped";
label_.get_style_context()->add_class("stopped");
label_.get_style_context()->remove_class("playing");
label_.get_style_context()->remove_class("paused");
button_.get_style_context()->add_class("stopped");
button_.get_style_context()->remove_class("playing");
button_.get_style_context()->remove_class("paused");
} else {
label_.get_style_context()->remove_class("stopped");
button_.get_style_context()->remove_class("stopped");
if (playing()) {
label_.get_style_context()->add_class("playing");
label_.get_style_context()->remove_class("paused");
button_.get_style_context()->add_class("playing");
button_.get_style_context()->remove_class("paused");
} else if (paused()) {
format = config_["format-paused"].isString() ? config_["format-paused"].asString()
: config_["format"].asString();
label_.get_style_context()->add_class("paused");
label_.get_style_context()->remove_class("playing");
button_.get_style_context()->add_class("paused");
button_.get_style_context()->remove_class("playing");
}
stateIcon = getStateIcon();
artist = getTag(MPD_TAG_ARTIST);
album_artist = getTag(MPD_TAG_ALBUM_ARTIST);
album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE);
artist = sanitize_string(getTag(MPD_TAG_ARTIST));
album_artist = sanitize_string(getTag(MPD_TAG_ALBUM_ARTIST));
album = sanitize_string(getTag(MPD_TAG_ALBUM));
title = sanitize_string(getTag(MPD_TAG_TITLE));
date = sanitize_string(getTag(MPD_TAG_DATE));
filename = sanitize_string(getFilename());
song_pos = mpd_status_get_song_pos(status_.get()) + 1;
volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
@ -155,17 +169,17 @@ void waybar::modules::MPD::setLabel() {
if (config_["title-len"].isInt()) title = title.substr(0, config_["title-len"].asInt());
try {
label_.set_markup(
fmt::format(format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("volume", volume), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("songPosition", song_pos),
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon)));
label_->set_markup(fmt::format(
format, fmt::arg("artist", Glib::Markup::escape_text(artist).raw()),
fmt::arg("albumArtist", Glib::Markup::escape_text(album_artist).raw()),
fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("queueLength", queue_length),
fmt::arg("stateIcon", stateIcon), fmt::arg("consumeIcon", consumeIcon),
fmt::arg("randomIcon", randomIcon), fmt::arg("repeatIcon", repeatIcon),
fmt::arg("singleIcon", singleIcon), fmt::arg("filename", filename)));
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error: {}", e.what());
}
@ -184,7 +198,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("queueLength", queue_length), fmt::arg("stateIcon", stateIcon),
fmt::arg("consumeIcon", consumeIcon), fmt::arg("randomIcon", randomIcon),
fmt::arg("repeatIcon", repeatIcon), fmt::arg("singleIcon", singleIcon));
label_.set_tooltip_text(tooltip_text);
button_.set_tooltip_text(tooltip_text);
} catch (fmt::format_error const& e) {
spdlog::warn("mpd: format error (tooltip): {}", e.what());
}

View File

@ -10,6 +10,13 @@ namespace waybar::modules {
} // namespace waybar::modules
#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 {
#define IDLE_RUN_NOIDLE_AND_CMD(...) \

View File

@ -78,7 +78,7 @@ waybar::modules::Network::readBandwidthUsage() {
}
waybar::modules::Network::Network(const std::string &id, const Json::Value &config)
: ALabel(config, "network", id, DEFAULT_FORMAT, 60),
: AButton(config, "network", id, DEFAULT_FORMAT, 60),
ifid_(-1),
family_(config["family"] == "ipv6" ? AF_INET6 : AF_INET),
efd_(-1),
@ -95,11 +95,11 @@ waybar::modules::Network::Network(const std::string &id, const Json::Value &conf
#endif
frequency_(0.0) {
// Start with some "text" in the module's label_, update() will then
// Start with some "text" in the module's label_-> update() will then
// update it. Since the text should be different, update() will be able
// to show or hide the event_box_. This is to work around the case where
// the module start with no text, but the the event_box_ is shown.
label_.set_markup("<s></s>");
label_->set_markup("<s></s>");
auto bandwidth = readBandwidthUsage();
if (bandwidth.has_value()) {
@ -309,8 +309,8 @@ auto waybar::modules::Network::update() -> void {
if (!alt_) {
auto state = getNetworkState();
if (!state_.empty() && label_.get_style_context()->has_class(state_)) {
label_.get_style_context()->remove_class(state_);
if (!state_.empty() && button_.get_style_context()->has_class(state_)) {
button_.get_style_context()->remove_class(state_);
}
if (config_["format-" + state].isString()) {
default_format_ = config_["format-" + state].asString();
@ -322,8 +322,8 @@ auto waybar::modules::Network::update() -> void {
if (config_["tooltip-format-" + state].isString()) {
tooltip_format = config_["tooltip-format-" + state].asString();
}
if (!label_.get_style_context()->has_class(state)) {
label_.get_style_context()->add_class(state);
if (!button_.get_style_context()->has_class(state)) {
button_.get_style_context()->add_class(state);
}
format_ = default_format_;
state_ = state;
@ -339,12 +339,18 @@ auto waybar::modules::Network::update() -> void {
fmt::arg("icon", getIcon(signal_strength_, state_)),
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("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("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("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")));
if (text.compare(label_.get_label()) != 0) {
label_.set_markup(text);
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (text.compare(label_->get_label()) != 0) {
label_->set_markup(text);
if (text.empty()) {
event_box_.hide();
} else {
@ -366,20 +372,26 @@ auto waybar::modules::Network::update() -> void {
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("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("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("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")));
if (label_.get_tooltip_text() != tooltip_text) {
label_.set_tooltip_text(tooltip_text);
fmt::arg("bandwidthUpBytes", pow_format(bandwidth_up / interval_.count(), "B/s")),
fmt::arg("bandwidthTotalBytes",
pow_format((bandwidth_up + bandwidth_down) / interval_.count(), "B/s")));
if (button_.get_tooltip_text() != tooltip_text) {
button_.set_tooltip_markup(tooltip_text);
}
} else if (label_.get_tooltip_text() != text) {
label_.set_tooltip_text(text);
} else if (button_.get_tooltip_text() != text) {
button_.set_tooltip_markup(text);
}
}
// Call parent update
ALabel::update();
AButton::update();
}
bool waybar::modules::Network::checkInterface(std::string name) {
@ -634,7 +646,12 @@ int waybar::modules::Network::handleEvents(struct nl_msg *msg, void *data) {
if (has_gateway && !has_destination && temp_idx != -1) {
// Check if this is the first default route we see, or if this new
// 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
// route on a different interface.
net->clearIface();

View File

@ -1,7 +1,7 @@
#include "modules/pulseaudio.hpp"
waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value &config)
: ALabel(config, "pulseaudio", id, "{volume}%"),
: AButton(config, "pulseaudio", id, "{volume}%"),
mainloop_(nullptr),
mainloop_api_(nullptr),
context_(nullptr),
@ -36,6 +36,7 @@ waybar::modules::Pulseaudio::Pulseaudio(const std::string &id, const Json::Value
}
waybar::modules::Pulseaudio::~Pulseaudio() {
pa_context_disconnect(context_);
mainloop_api_->quit(mainloop_api_, 0);
pa_threaded_mainloop_stop(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;
pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_;
int max_volume = 100;
double step = 1;
// isDouble returns true for integers as well, just in case
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 (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);
}
} 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);
}
}
@ -166,6 +183,15 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
if (i == nullptr) return;
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 (i->state != PA_SINK_RUNNING) {
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";
if (auto ff = pa_proplist_gets(i->proplist, PA_PROP_DEVICE_FORM_FACTOR)) {
pa->form_factor_ = ff;
} else {
pa->form_factor_ = "";
}
pa->dp.emit();
}
@ -234,9 +262,9 @@ auto waybar::modules::Pulseaudio::update() -> void {
if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth");
button_.get_style_context()->add_class("bluetooth");
} else {
label_.get_style_context()->remove_class("bluetooth");
button_.get_style_context()->remove_class("bluetooth");
}
if (muted_) {
// Check muted bluetooth format exist, otherwise fallback to default muted format
@ -244,29 +272,29 @@ auto waybar::modules::Pulseaudio::update() -> void {
format_name = "format";
}
format_name = format_name + "-muted";
label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
button_.get_style_context()->add_class("muted");
button_.get_style_context()->add_class("sink-muted");
} else {
label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
button_.get_style_context()->remove_class("muted");
button_.get_style_context()->remove_class("sink-muted");
}
format = config_[format_name].isString() ? config_[format_name].asString() : format;
}
// TODO: find a better way to split source/sink
std::string format_source = "{volume}%";
if (source_muted_) {
label_.get_style_context()->add_class("source-muted");
button_.get_style_context()->add_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source-muted"].asString();
}
} else {
label_.get_style_context()->remove_class("source-muted");
button_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
}
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_.set_markup(fmt::format(
label_->set_markup(fmt::format(
format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_), fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
@ -277,16 +305,16 @@ auto waybar::modules::Pulseaudio::update() -> void {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
button_.set_tooltip_text(fmt::format(
tooltip_format, fmt::arg("desc", desc_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
} else {
label_.set_tooltip_text(desc_);
button_.set_tooltip_text(desc_);
}
}
// Call parent update
ALabel::update();
AButton::update();
}

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
#include "modules/sni/host.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
namespace waybar::modules::SNI {

View File

@ -300,10 +300,6 @@ void Item::updateImage() {
auto pixbuf = getIconPixbuf();
auto scaled_icon_size = getScaledIconSize();
if (!pixbuf) {
pixbuf = getIconByName("image-missing", getScaledIconSize());
}
// If the loaded icon is not square, assume that the icon height should match the
// requested icon size, but the width is allowed to be different. As such, if the
// height of the image does not match the requested icon size, resize the icon such that
@ -313,24 +309,47 @@ void Item::updateImage() {
pixbuf = pixbuf->scale_simple(width, scaled_icon_size, Gdk::InterpType::INTERP_BILINEAR);
}
auto surface = Gdk::Cairo::create_surface_from_pixbuf(pixbuf, 0, image.get_window());
auto surface =
Gdk::Cairo::create_surface_from_pixbuf(pixbuf, image.get_scale_factor(), image.get_window());
image.set(surface);
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
try {
if (!icon_name.empty()) {
if (!icon_name.empty()) {
try {
std::ifstream temp(icon_name);
if (temp.is_open()) {
return Gdk::Pixbuf::create_from_file(icon_name);
}
return getIconByName(icon_name, getScaledIconSize());
} else if (icon_pixmap) {
return icon_pixmap;
} catch (Glib::Error& e) {
// Ignore because we want to also try different methods of getting an icon.
//
// But a warning is logged, as the file apparently exists, but there was
// a failure in creating a pixbuf out of it.
spdlog::warn("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
try {
// Will throw if it can not find an icon.
return getIconByName(icon_name, getScaledIconSize());
} catch (Glib::Error& e) {
spdlog::trace("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
} catch (Glib::Error& e) {
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
}
// Return the pixmap only if an icon for the given name could not be found.
if (icon_pixmap) {
return icon_pixmap;
}
if (icon_name.empty()) {
spdlog::error("Item '{}': No icon name or pixmap given.", id);
} else {
spdlog::error("Item '{}': Could not find an icon named '{}' and no pixmap given.", id,
icon_name);
}
return getIconByName("image-missing", getScaledIconSize());
}

View File

@ -1,6 +1,5 @@
#include "modules/sway/bar.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <sstream>

View File

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

View File

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

View File

@ -0,0 +1,82 @@
#include "modules/sway/scratchpad.hpp"
#include <spdlog/spdlog.h>
#include <string>
namespace waybar::modules::sway {
Scratchpad::Scratchpad(const std::string& id, const Json::Value& config)
: ALabel(config, "scratchpad", id,
config["format"].isString() ? config["format"].asString() : "{icon} {count}"),
tooltip_format_(config_["tooltip-format"].isString() ? config_["tooltip-format"].asString()
: "{app}: {title}"),
show_empty_(config_["show-empty"].isBool() ? config_["show-empty"].asBool() : false),
tooltip_enabled_(config_["tooltip"].isBool() ? config_["tooltip"].asBool() : true),
tooltip_text_(""),
count_(0) {
ipc_.subscribe(R"(["window"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Scratchpad::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Scratchpad::onCmd));
getTree();
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("Scratchpad: {}", e.what());
}
});
}
auto Scratchpad::update() -> void {
if (count_ || show_empty_) {
event_box_.show();
label_.set_markup(
fmt::format(format_, fmt::arg("icon", getIcon(count_, "", config_["format-icons"].size())),
fmt::arg("count", count_)));
if (tooltip_enabled_) {
label_.set_tooltip_markup(tooltip_text_);
}
} else {
event_box_.hide();
}
if (count_) {
label_.get_style_context()->remove_class("empty");
} else {
label_.get_style_context()->add_class("empty");
}
ALabel::update();
}
auto Scratchpad::getTree() -> void {
try {
ipc_.sendCmd(IPC_GET_TREE);
} catch (const std::exception& e) {
spdlog::error("Scratchpad: {}", e.what());
}
}
auto Scratchpad::onCmd(const struct Ipc::ipc_response& res) -> void {
try {
std::lock_guard<std::mutex> lock(mutex_);
auto tree = parser_.parse(res.payload);
count_ = tree["nodes"][0]["nodes"][0]["floating_nodes"].size();
if (tooltip_enabled_) {
tooltip_text_.clear();
for (const auto& window : tree["nodes"][0]["nodes"][0]["floating_nodes"]) {
tooltip_text_.append(fmt::format(tooltip_format_ + '\n',
fmt::arg("app", window["app_id"].asString()),
fmt::arg("title", window["name"].asString())));
}
if (!tooltip_text_.empty()) {
tooltip_text_.pop_back();
}
}
dp.emit();
} catch (const std::exception& e) {
spdlog::error("Scratchpad: {}", e.what());
}
}
auto Scratchpad::onEvent(const struct Ipc::ipc_response& res) -> void { getTree(); }
} // namespace waybar::modules::sway

View File

@ -12,10 +12,12 @@
#include <regex>
#include <string>
#include "util/rewrite_title.hpp"
namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
: AIconLabel(config, "window", id, "{title}", 0, true), bar_(bar), windowId_(-1) {
// Icon size
if (config_["icon-size"].isUInt()) {
app_icon_size_ = config["icon-size"].asUInt();
@ -44,7 +46,7 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_, app_class_) =
std::tie(app_nb_, windowId_, window_, app_id_, app_class_, shell_) =
getFocusedNode(payload["nodes"], output);
updateAppIconName();
dp.emit();
@ -175,8 +177,9 @@ auto Window::update() -> void {
bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty");
}
label_.set_markup(
fmt::format(format_, fmt::arg("title", rewriteTitle(window_)), fmt::arg("app_id", app_id_)));
label_.set_markup(fmt::format(
format_, fmt::arg("title", waybar::util::rewriteTitle(window_, config_["rewrite"])),
fmt::arg("app_id", app_id_), fmt::arg("shell", shell_)));
if (tooltipEnabled()) {
label_.set_tooltip_text(window_);
}
@ -206,7 +209,7 @@ int leafNodesInWorkspace(const Json::Value& node) {
return sum;
}
std::tuple<std::size_t, int, std::string, std::string, std::string> gfnWithWorkspace(
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string> gfnWithWorkspace(
const Json::Value& nodes, std::string& output, const Json::Value& config_, const Bar& bar_,
Json::Value& parentWorkspace) {
for (auto const& node : nodes) {
@ -222,31 +225,34 @@ std::tuple<std::size_t, int, std::string, std::string, std::string> gfnWithWorks
const auto app_class = node["window_properties"]["class"].isString()
? node["window_properties"]["class"].asString()
: "";
const auto shell = node["shell"].isString() ? node["shell"].asString() : "";
int nb = node.size();
if (parentWorkspace != 0) nb = leafNodesInWorkspace(parentWorkspace);
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()), app_id,
app_class};
return {nb, node["id"].asInt(), Glib::Markup::escape_text(node["name"].asString()),
app_id, app_class, shell};
}
}
// iterate
if (node["type"] == "workspace") parentWorkspace = node;
auto [nb, id, name, app_id, app_class] =
auto [nb, id, name, app_id, app_class, shell] =
gfnWithWorkspace(node["nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id, app_class};
return {nb, id, name, app_id, app_class, shell};
}
// Search for floating node
std::tie(nb, id, name, app_id, app_class) =
std::tie(nb, id, name, app_id, app_class, shell) =
gfnWithWorkspace(node["floating_nodes"], output, config_, bar_, parentWorkspace);
if (id > -1 && !name.empty()) {
return {nb, id, name, app_id, app_class};
return {nb, id, name, app_id, app_class, shell};
}
}
return {0, -1, "", "", ""};
return {0, -1, "", "", "", ""};
}
std::tuple<std::size_t, int, std::string, std::string, std::string> Window::getFocusedNode(
const Json::Value& nodes, std::string& output) {
std::tuple<std::size_t, int, std::string, std::string, std::string, std::string>
Window::getFocusedNode(const Json::Value& nodes, std::string& output) {
Json::Value placeholder = 0;
return gfnWithWorkspace(nodes, output, config_, bar_, placeholder);
}
@ -259,30 +265,4 @@ void Window::getTree() {
}
}
std::string Window::rewriteTitle(const std::string& title) {
const auto& rules = config_["rewrite"];
if (!rules.isObject()) {
return title;
}
std::string res = title;
for (auto it = rules.begin(); it != rules.end(); ++it) {
if (it.key().isString() && it->isString()) {
try {
// malformated regexes will cause an exception.
// in this case, log error and try the next rule.
const std::regex rule{it.key().asString()};
if (std::regex_match(title, rule)) {
res = std::regex_replace(res, rule, it->asString());
}
} catch (const std::regex_error& e) {
spdlog::error("Invalid rule {}: {}", it.key().asString(), e.what());
}
}
}
return res;
}
} // namespace waybar::modules::sway

View File

@ -8,7 +8,7 @@
namespace waybar::modules::sway {
// Helper function to to assign a number to a workspace, just like sway. In fact
// Helper function to assign a number to a workspace, just like sway. In fact
// this is taken quite verbatim from `sway/ipc-json.c`.
int Workspaces::convertWorkspaceNameToNum(std::string name) {
if (isdigit(name[0])) {

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