Compare commits

..

392 Commits

Author SHA1 Message Date
115c6e36e6 chore: v0.9.11 2022-03-10 09:50:04 +01:00
210f4454f0 Update waybar-sway-window.5.scd 2022-03-10 09:49:14 +01:00
77d8376fef refactor: disable icon by default 2022-03-10 09:48:50 +01:00
1aa7587cac Merge pull request #1333 from dartkron/list_of_times
Feature Clock: show list of time in other timezones in a tooltip
2022-03-08 10:00:04 +01:00
b6655e475b Merge branch 'master' into list_of_times 2022-03-08 09:59:22 +01:00
903fc2b6a2 Merge pull request #1459 from grmat/master
man: document icon in sway/window module
2022-03-07 15:17:35 +01:00
50fc63b749 man: document icon in sway/window module
Default changed in bcadf64031 and it
wasn't documented.
2022-03-07 14:56:37 +01:00
383d999fec chore: v0.9.10 2022-03-06 14:54:48 +01:00
56ebb09e5f Merge pull request #1440 from TheDaemoness/patch-1
Add 1 to songPosition in the MPD module
2022-03-06 14:51:20 +01:00
35d6da3965 Merge pull request #1292 from FlexW/feature/sway-app-icon
Show application icon when using sway window module
2022-03-06 14:50:41 +01:00
be1d2a02ca Merge pull request #1422 from technic/fixes
minor string utils methods refactoring
2022-03-06 14:50:02 +01:00
5e7ba0c9e3 Merge pull request #1445 from GrantMoyer/keyboard_state_errors
Improve keyboard_state error messages
2022-03-06 14:49:38 +01:00
18717d4b12 Merge remote-tracking branch 'upstream/master' into keyboard_state_errors 2022-03-04 12:14:23 -05:00
4dc1989744 Merge pull request #1451 from euclio/use-after-free 2022-03-04 06:10:25 +01:00
54085dbde0 avoid use-after-free in lambda 2022-03-03 19:40:43 -05:00
4635e8c5f8 Merge pull request #1450 from Xiretza/style-css-use-fontawesome 2022-03-03 18:38:05 +01:00
adc67b6f75 style.css: add fontawesome to beginning of font list
If there is some other font installed that 1) matches the four existing
font families and 2) provides its own glyph in the private use area used
by Awesome, then that font's glyph will be used instead of the intended
icon.

For example, the following character (U+F001, "music"): 
...looks like a pair of musical notes in fontawesome, but DejaVu Sans
also provides a glyph, which looks like a couple of squares. DejaVu Sans
matches first when "sans-serif" is requested, so its (unrelated) glyph
is used.
2022-03-03 18:18:21 +01:00
ebe4424795 Fix remaining posix compatability issues in keyboard_state 2022-02-25 13:28:47 -05:00
e3f56b8110 Don't use gnu extensions which are too new for debian and ubuntu 2022-02-25 13:13:43 -05:00
8f3fbebede Make error messages portable 2022-02-25 12:56:22 -05:00
a595b61e0f Improve keyboard_state error messages 2022-02-25 12:27:08 -05:00
91339f6ad4 Merge pull request #1444 from bd-g/feat/cpu-load-fix
CPU Load Formatting Fix
2022-02-23 09:12:53 +01:00
3c18c43b9a limit cpu load figure to 2 points 2022-02-22 16:33:33 -07:00
ac20428fdf Add 1 to songPosition in the MPD module 2022-02-18 17:41:33 -08:00
ad5ea7ad2b Merge pull request #1436 from jbeich/ci 2022-02-13 18:13:42 +01:00
851508df5e ci: explicitly install wayland-protocols on FreeBSD
meson.build:86:0: ERROR: Dependency "wayland-protocols" not found, tried pkgconfig
2022-02-13 11:14:35 +00:00
fc818dd794 ci: update freebsd-vm to 0.1.6 2022-02-13 11:12:52 +00:00
e066e3080e Merge pull request #1432 from DorianRudolph/fix-unplug
fix #1431: audio output does not update on unplug
2022-02-09 15:29:36 +01:00
1a93a6cfa5 fix #1431: audio output does not update on unplug 2022-02-09 14:20:09 +01:00
074b559da5 Merge pull request #1423 from technic/fmt-refactor
move Glib ustring format helper to utils
2022-02-08 18:25:55 +01:00
7a61a00fb3 Merge pull request #1421 from gemmelos/patch-2
Update man page clock module - tooltip
2022-02-08 18:25:13 +01:00
d4da04a750 move Glib ustring format helper to utils
this formatter is useful for other modules
which want to print Glib exceptions messages
2022-02-05 21:26:48 +01:00
f3819ee954 remove duplicated string manipulation methods 2022-02-05 21:02:58 +01:00
2697d432a4 ltrim and rtrim take argument by const-ref 2022-02-05 21:02:58 +01:00
061cb76fc4 make functions in header file inline
added header guard
now string utils can be used in any part of the project
2022-02-05 21:02:58 +01:00
6c188455a4 Update man page clock module - tooltip
Add tooltip documentation to the clock man page now that this is works.
2022-02-05 19:05:50 +01:00
12caae8fd2 Merge pull request #1410 from ErikReider/reverse-tray
Reverse the tray icon order
2022-02-02 12:56:25 +01:00
26ea6fae32 Merge pull request #1413 from snehrbass/patch-1
Fix typo in example for sway/mode man page
2022-02-02 12:55:34 +01:00
26419e45b7 Merge pull request #1418 from gemmelos/patch-1
Fix disabled tooltip on clock module
2022-02-02 12:55:10 +01:00
6be741afc9 Fix disabled tooltip on clock module
Seems like the tooltip text should only be set if both `if (tooltipEnabled())` and `if (config_["tooltip-format"].isString())` passes.
2022-02-02 12:41:06 +01:00
c80cc873af Update spotify example to work on copy paste 2022-02-02 00:01:18 -05:00
2b42872b6c Merge pull request #1417 from evyatark2/master 2022-02-01 22:00:29 +01:00
895bc878f8 Properly initialize distance_scrolled members.
When they were uninitialized it caused getScrollDir() to incorrectly return SCROLL_DIR::NONE in some circumstances
2022-02-01 22:42:03 +02:00
a0ee9e7fc1 Fix Typo in Example for sway/mode 2022-01-29 14:19:53 -05:00
941cf47693 Add config option "reverse-direction" 2022-01-28 19:14:46 +01:00
32d42749f9 use pack_end instead of pack_start 2022-01-28 18:42:52 +01:00
96caa9f094 Merge pull request #1406 from Spiffyk/language-segfault
sway/language: fix segmentation fault for layouts not found in XKBContext
2022-01-25 23:08:32 +01:00
f4f1267a71 sway/language: fix segmentation fault for layouts not found in XKBContext 2022-01-25 20:03:19 +01:00
bcadf64031 Show application icon when using sway window module
Signed-off-by: Felix Weilbach <felix.weilbach@t-online.de>
2022-01-24 17:37:51 +01:00
8974bbf7b4 Merge pull request #1312 from mazunki/master
Changed a setting to include {gwaddr} by default
2022-01-24 09:23:43 +01:00
1c08d26af0 Merge branch 'master' into master 2022-01-24 09:23:23 +01:00
c4cc7ae396 Update client.cpp 2022-01-24 09:22:47 +01:00
65dd245362 Update clock.cpp 2022-01-24 09:21:56 +01:00
667d0a45f4 Update command.hpp 2022-01-24 09:20:43 +01:00
53fc750fc3 Merge pull request #1404 from kevinoid/fix-issue-1139 2022-01-24 09:02:53 +01:00
27df7a9aa7 battery: read status with spaces
According to the [sysfs class power ABI],
/sys/class/power_supply/<supply_name>/status may contain "Not charging".
This is already handled by status_gt() and update() (where ' ' is
converted to '-' for use in config keys) but was not being read due to
skipws.  Read with std::getline() to handle this case.

[sysfs class power ABI]: https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power

Fixes: #1139
Signed-off-by: Kevin Locke <kevin@kevinlocke.name>
2022-01-23 18:36:59 -07:00
d575646c2d Merge pull request #1394 from nullobsi/patch-1
fix: Use locale when formatting clock
2022-01-18 11:35:55 +01:00
3a6e5be59d Use locale when formatting clock 2022-01-14 10:36:24 -08:00
81f0bcb3a3 Merge pull request #1388 from ericonr/man2
fix(man): add missing newlines to wlr docs.
2022-01-12 11:09:09 +01:00
9ae99c2621 Merge pull request #1386 from Anakael/pr/anakael/add-swap-flags
[Memory] feat: Add swap flags
2022-01-12 11:08:50 +01:00
5abdecc402 fix(man): add missing newlines to wlr docs. 2022-01-11 17:29:17 -03:00
1540797cd9 chore: v0.9.9 2022-01-10 09:58:29 +01:00
5d8bc35476 Merge pull request #1385 from mvdan/css-checkboxes
resources: don't break tray icon checkboxes via CSS
2022-01-09 13:25:31 +01:00
5c574975b7 resources: don't break tray icon checkboxes via CSS
These three lines break checkboxes and other forms of UI in status
indicator dropdowns. For instance, they break checkboxes on
NetworkManager's "nm-applet --indicator" via libappindicator-gtk3.

First, disabling borders completely hides those UI elements,
as they seem to render entirely via borders.
Second, min-height makes checkboxes just flat lines.

When removed entirely, the border settings seem to have had an effect on
the workspaces widget, which now renders with round underline borders.
To undo that, re-add those two lines inside its section.

The min-height setting doesn't seem to affect anything that I can see.
Remove it entirely, for now.

Fixes #1148.
2022-01-09 10:32:16 +00:00
18cbd2aa1a Merge pull request #1380 from alebastr/fmt-build-fix
Fix build with fmt 8.1.0
2022-01-08 13:48:41 +01:00
ce404199de chore: add tzdata to the alpine builder
Fixes date formatting test execution on alpine.
2022-01-07 22:50:23 -08:00
7f6bef2049 fix(util): make waybar_time formatter compatible with fmt 8.1.0
Stop using private implementation details of the `formatter<std::tm>`.
We never needed anything from the class besides the format specifier,
which is easily obtainable with public API.
2022-01-07 22:34:00 -08:00
1489a539f8 chore: bump supported fmt ver to 7.0.0
Certain features we use were added only in 7.0 and the code no longer
compiles with any earlier versions.
2022-01-07 22:33:52 -08:00
766c8a1035 test: add tests for waybar_time formatter 2022-01-07 22:33:50 -08:00
8c41aaae04 refactor(test): use single executable 2022-01-07 19:21:51 -08:00
c0d84853ea refactor(clock): extract waybar_time to util/waybar_time.hpp 2022-01-07 18:09:44 -08:00
2301788a81 created a hotfix for libfmt-8.1.0 and above 2022-01-08 01:44:46 +01:00
d22d6a4522 Merge branch 'master' of github.com:Alexays/Waybar 2022-01-08 01:02:57 +01:00
d9b5c2595a Merge pull request #1376 from languitar/bugfix/sway-workspace-sorting
fix: match sway workspace order with sway one
2022-01-07 10:30:19 +01:00
4a85ec0f59 fix: match sway workspace order with sway one
Ensure that sway workspaces are always displayed in the same order as
used internally by sway. The previous sorting code always sorted
unnumbered workspaces lexicographically. This isn't the order used by
sway internally. Therefore, commands such as "workspace next" might have
jumped arbitrarily in waybar.

This commit reworks the sorting code such that the internal order is
always obeyed. Additionally, numbered persistent workspaces are inserted
at their natural position at the front of the workspace list while
unnumbered ones are appended. This should match the expectations of
workspace ordering known from sway's behavior.

The changes make the configuration property "numeric-first" unnecessary
as this will always be the case now. There's also no reasonable way
around this behavior now. Otherwise, persistent workspaces would jump
around in the visual representation as soon as they become known to
sway.

Fixes #802
2022-01-07 10:26:27 +01:00
621d686310 Merge pull request #1375 from Anakael/pr/anakael/fix-invisible-application
[wlr/taskbar] fix: Missing tasks
2022-01-05 13:49:31 +01:00
812832d38a fix: Missing tasks 2022-01-05 15:38:52 +03:00
19069482e2 Merge branch 'master' of https://github.com/Alexays/Waybar into pr/anakael/fix-invisible-application 2022-01-05 14:57:32 +03:00
1e560cf0c9 merge 2022-01-05 14:47:44 +03:00
0c9cdf7e0b Merge pull request #1370 from volucris1/patch-1 2022-01-04 09:00:28 +01:00
5a4f7a70ef fix indent 2022-01-04 14:53:59 +07:00
ce56a80792 Merge pull request #1353 from bd-g/master
Network Module Enhancements
2021-12-30 10:03:04 +01:00
db95644d26 Merge pull request #1364 from ilkecan/master
Add `fixed-center` option
2021-12-30 10:01:04 +01:00
548bd2ab1a Add fixed-center option
Resolves #957
2021-12-28 16:00:25 +03:00
7b9b10afc6 Merge branch 'master' into master 2021-12-26 20:14:26 -07:00
8ec321ddaf Merge pull request #1221 from Anakael/pr/anakael/add-name-to-taskbar
[Taskbar] feat: Add name format replacementf feat: Add app ids mapping (ready for review)
2021-12-23 23:01:21 +01:00
13d25d403e add to network module - signalStrengthApp shows what applications can run at current signal strength 2021-12-14 11:37:39 -07:00
3218612d3b change frequency to GHz 2021-12-14 11:36:46 -07:00
9bc86347be change signal strength to penalize overly strong signals 2021-12-14 11:34:15 -07:00
d1f8b42d22 add Debian build dependency to README 2021-12-14 11:28:13 -07:00
0e3be30e01 Merge pull request #1352 from fhost/fhost/inhibitor-man
man: Add waybar-inhibitor man page
2021-12-14 08:47:08 +01:00
795246263f man: Add waybar-inhibitor man page 2021-12-14 08:30:28 +01:00
89a8bd976e Merge pull request #1337 from 187Qrly/patch-1
Add Gentoo to README.md
2021-12-14 07:44:07 +01:00
9b399ea2bb Merge pull request #1334 from bd-g/master
Adjust max wifi strength that is possible
2021-12-14 07:43:41 +01:00
9608e0dabf Merge pull request #1341 from fhost/fhost/logind
Add logind feature, with its 'inhibitor' module
2021-12-14 07:43:15 +01:00
1d1cfda90d Merge pull request #1289 from aashu/master
Add reverse scrolling config option for pulseaudio module
2021-12-14 07:42:13 +01:00
ad20c0af2d Merge branch 'master' into master 2021-12-14 10:22:25 +05:30
eae65099d0 Add logind feature, with its 'inhibitor' module
The logind feature adds a new inhibitor module which allows to acquire
the inhibitor locks that logind presents.

Signed-off-by: Alexis Cellier <kernelserror@gmail.com>
2021-12-12 17:52:26 +01:00
39f42cdd7e Merge pull request #1338 from jfred9/master
bar: Fix crash when unplugging HDMI
2021-12-08 10:42:22 +01:00
f573e32d0b bar: Fix crash when unplugging HDMI
There is a double delete situation which causes a SIGSEGV to happen
during destruction of bar.

This was introduced by the group feature patch.

The same object pointer is stored in two different vectors of
unique_ptr<AModule> element. Replace with shared_ptr to handle
reference counting correctly and avoid double delete.
2021-12-05 10:55:07 +01:00
5cbbd65ac4 Add Gentoo 2021-12-03 20:11:25 +01:00
2240c79b1a Adjust max wifi strength that is possible 2021-12-01 18:24:35 -07:00
ece86c96d7 Feature Clock: show list of time in other timezones in a tooltip
Introducing new tooltip placeholder: {timezoned_time_list}. It will be replaced with the list of times in different time zones.
I've found it useful to hover the mouse pointer on time and see time in all my timezones at once.
Current timezone excluding from the list, so if you will scroll over the time module and change the active timezone, this timezone will be excluded from the list and the previous active zone will be added.
2021-12-01 17:08:05 +00:00
9e8a71c4ef Merge pull request #1277 from dartkron/master
Refactor Clock: generalize multi timezones and single timezone cases
2021-12-01 11:51:35 +01:00
7069429c03 Merge pull request #1229 from kraftwerk28/sway-language-country-flag
`sway/language` country flag
2021-12-01 11:49:39 +01:00
d41a60d2d9 Merge pull request #1244 from alebastr/swaybar-ipc
Yet another swaybar ipc implementation
2021-12-01 11:48:49 +01:00
05f7727dae Merge branch 'master' into swaybar-ipc 2021-12-01 11:48:03 +01:00
9bc6fae15b Merge pull request #1331 from xytovl/per-device-pulse-icon
Allow sink in addition to source for pulse icon
2021-11-30 16:36:22 +01:00
9dac851f6d Allow sink in addition to source for pulse icon 2021-11-30 16:31:41 +01:00
b6e24bd527 Merge pull request #1305 from njoyard/feat/groups
Add module group feature
2021-11-29 15:19:48 +01:00
b6d0a4b63f feat(bar): allow customization of bar modes
Allow changing existing modes and adding new ones via `modes`
configuration key.
`modes` accepts a JSON object roughly described by the following type
```typescript
type BarMode = {
    layer: 'bottom' | 'top' | 'overlay';
    exclusive: bool;
    passthrough: bool;
    visible: bool;
};
type BarModeList = {
    [name: string]: BarMode;
};
```
and will be merged with the default modes defined in `bar.cpp`.

Note that with absence of other ways to set mode, only those defined in
the `sway-bar(5)`[1] documentation could be used right now.

[1]: https://github.com/swaywm/sway/blob/master/sway/sway-bar.5.scd
2021-11-28 12:19:45 -08:00
cf5ddb2a5e fix(swaybar-ipc): avoid unnecessary copy of struct swaybar_config 2021-11-28 11:34:21 -08:00
4b5dc1bb3a test: count copies and moves done by SafeSignal 2021-11-28 09:52:18 -08:00
22ff26252b Merge pull request #1328 from Anakael/pr/anakael/experimental-flag
build: Add experimental build option
2021-11-28 11:27:12 +01:00
a832814b41 Merge pull request #1329 from m-bdf/patch-1
mediaplayer.py: Exit properly on SIGPIPE
2021-11-28 11:26:41 +01:00
2fb671f5fa Revert protocol build 2021-11-28 01:19:21 +03:00
3c2fa1625d Finish 2021-11-28 01:12:35 +03:00
d5112678c3 mediaplayer.py: Exit properly on SIGPIPE 2021-11-27 23:07:26 +01:00
0c18e57937 add group feature 2021-11-25 12:01:17 +01:00
b4e19678b7 ci: increase FreeBSD VM memory to 2048MB
Intermittent CI failures without any useful diagnostics could be caused
by the OOM killer. 1024MB is not really enough to run 3 parallel jobs
with a modern C++ compiler.
2021-11-23 19:48:31 -08:00
8fe42ebd2e doc: update exclusive and passthrough defaults 2021-11-23 19:18:24 -08:00
bb7b376fa6 removed commented useless runtime errors 2021-11-24 01:37:21 +01:00
89afa8e149 Checking if emoji byte doesn't get out of bounds 2021-11-24 02:15:10 +02:00
59040c53e4 Move definition to .cpp 2021-11-24 02:15:10 +02:00
02560a6537 Update manpage 2021-11-24 02:15:10 +02:00
0472d279e4 Add {flag} format replacement 2021-11-24 02:15:10 +02:00
166504256a Merge branch 'master' of https://github.com/Alexays/Waybar into pr/anakael/add-name-to-taskbar 2021-11-23 23:10:49 +03:00
2290fe10aa fix(bar): handle ipc connection errors.
Try to use the default bar id (`bar-0`) if none is set.
2021-11-23 08:46:58 -08:00
9fa7bfc0cb Merge pull request #720 from jbeich/disk
disk: make {used} match df(1)
2021-11-23 13:54:46 +01:00
6712cd05a3 Merge branch 'master' into disk 2021-11-23 13:54:17 +01:00
977d21b5f6 Merge pull request #805 from Anakael/workspace-manager-implementation
WLR Workspace manager implementation
2021-11-23 13:52:17 +01:00
0a4841371b Merge branch 'master' into workspace-manager-implementation 2021-11-23 13:52:08 +01:00
deab028e52 Merge pull request #1325 from Alexays/revert-1256-patch-1
Revert "dont escape essid in tooltip"
2021-11-23 10:10:24 +01:00
c51a973d60 Revert "dont escape essid in tooltip" 2021-11-23 10:10:14 +01:00
5a5f8c3b9f Merge pull request #1256 from WuerfelDev/patch-1
dont escape essid in tooltip
2021-11-23 10:04:59 +01:00
b1dc3005b7 Merge pull request #1294 from Anakael/pr/anakael/ignore-by-title
feat: Ignore by title
2021-11-23 10:03:41 +01:00
98f7a10a51 Fix sort 2021-11-23 03:18:00 +03:00
23991b6543 Finish 2021-11-23 03:10:44 +03:00
331dfa87da Merge pull request #1230 from ciarand/if-changes-isolate
Switch network module to read /proc/net/dev
2021-11-22 08:00:54 +01:00
29a8f52464 Merge pull request #1322 from alebastr/freebsd-ci-update
ci: update FreeBSD configuration
2021-11-22 07:59:31 +01:00
6bfb674d1b fix(swaybar-ipc): better logs 2021-11-21 17:28:47 -08:00
ef4c6a9ba3 Update to proto. Fix displaying. Rename classes. 2021-11-22 01:12:55 +03:00
ffeecf626c Update names 2021-11-21 15:01:25 +03:00
4154492603 ci: update FreeBSD configuration
Use latest action v0.1.5.
Pin runner to macos-10.15. macos-latest will start using macos-11 images
without VirtualBox in less than a month[1].

[1] https://github.com/actions/virtual-environments/issues/4060
2021-11-19 21:17:55 -08:00
5baffbf8f8 doc: document swaybar ipc options, ipc and id 2021-11-19 20:28:57 -08:00
60c1706273 Update taskbar.cpp 2021-11-19 18:54:37 +03:00
cb6af026f6 Update taskbar.hpp 2021-11-19 18:51:28 +03:00
18ea53fcbc Update taskbar.hpp 2021-11-19 18:39:49 +03:00
23955fdcc2 Update taskbar.hpp 2021-11-19 18:38:59 +03:00
a06ed00727 Merge pull request #1317 from jfred9/master
river/tags: Add possibility for mouse clicks
2021-11-19 16:33:29 +01:00
15761ef802 Merge branch 'master' of https://github.com/Alexays/Waybar into workspace-manager-implementation 2021-11-16 21:44:50 +03:00
ad3f46214d river/tags: Add possibility for mouse clicks
Left mouse click - set-focused-tags
Right mouse click - toggle-focused-tags
2021-11-15 22:31:38 +01:00
13fda1607f Merge pull request #1311 from b1rger/patch-2
Fix typo
2021-11-12 09:04:41 +01:00
9fda6695ea Merge pull request #1314 from Scrumplex/fix-negative-mpd-vol
Ensure MPD volume is not negative
2021-11-12 09:04:21 +01:00
b24f9ea569 Ensure MPD volume is not negative
If the primary output does not support changing volume MPD will report
-1. Ensure that negative volume levels will be represented as 0 instead.

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2021-11-11 21:42:05 +01:00
e10c9dd011 changing want_route_dump to default to true, since we say we have gwaddr support 2021-11-09 19:04:05 +01:00
b0eab5d793 maybe we shouldn't actually runtime error, but still doing a check 2021-11-09 19:03:15 +01:00
17bb5643ae explicitly checking for errors to silence unused variable warnings when writing to fd 2021-11-09 18:38:07 +01:00
769b12f16a Fix typo 2021-11-06 09:00:15 +00:00
b511103fd9 Merge pull request #1301 from jonbakke/patch-1
Clarify less than/greater than in warning.
2021-10-29 13:23:28 +02:00
122fe33636 Merge pull request #1302 from marwing/master
Mark memory used by zfs arc as free in memory
2021-10-29 13:22:58 +02:00
8498ddefca Merge pull request #1304 from mamins1376/fix-1303
Fix divide by zero (#1303)
2021-10-29 13:22:01 +02:00
48117a2e97 Fix divide by zero (#1303) 2021-10-29 14:12:48 +03:30
decb13eef0 mark zfs arc size as free in memory 2021-10-28 19:10:46 +02:00
a015b2e3db Clarify less than/greater than in warning.
I was seeing "[warning] Requested height: 20 exceeds the minimum height: 30 required..."
Lines 114-134 are relevant; 133 overrides the requested height with the minimum height when GTK wants more pixels to work with. So, the code is behaving as expected, and "less than" matches the code's logic.
2021-10-28 09:37:11 -07:00
562e4157c0 Merge pull request #1297 from Molytho/master
Add spacing config option
2021-10-22 11:02:53 +02:00
01bfbc4656 Use spacing in config 2021-10-20 11:30:53 +02:00
7669029bfe Add man documentation for spacing config option 2021-10-20 11:30:49 +02:00
0b66454d5c Add spacing config option
This option allows to add spaces between the modules. It uses Gtk:Box's spacing property.
2021-10-20 11:30:40 +02:00
75a6dddea5 Finish 2021-10-20 01:23:00 +03:00
08b4a83331 Add reverse scrolling config option for pulseaudio
When natural scrolling is enabled, the behaviour of scrolling on pulseaudio
module is reversed, this commit reverses the direction of scroll variable
if "reverse-scrolling" is set to 1 in config file.
2021-10-15 19:07:25 +05:30
c5e4d26320 Fix working without timezone 2021-10-05 10:20:06 +00:00
d8bc6c92bb Fix style and spelling 2021-10-05 09:55:30 +00:00
110c66dd32 Refactor Clock: generalize multi timezones and single timezone cases
After this refactoring:
1. Timezones parses only once on start and the we refer to saved values. All time_zone.isString() checks gone to the constructor.
2. Single timezone case handling as case of multi timezoned logic.
3. Scroll event seems more clear now.
4. Tooltip template parses on start to check if there calendar placeholder or not. To do not calculate calendar_text() if not necessary.
2021-10-03 16:57:55 +00:00
6eb9606f23 Merge pull request #1274 from RobertMueller2/master
sway/window: include floating_nodes when considering window count for class
2021-10-03 10:27:26 +02:00
03ebbf6d98 Merge pull request #1275 from dartkron/master
Fix Clock crash on empty string in timezones field
2021-10-03 10:26:05 +02:00
174db444d6 Fix Clock crash on empty string in timezones field
Also fixed timezones behavior: now waybar starting with the first timezone in timezones list and falling back to timezone field only if timezones omit or has no elements.
2021-10-03 03:27:54 +00:00
9972384597 sway/window: include floating_nodes when considering window count for class 2021-10-02 18:35:38 +02:00
8b4dafd701 Merge pull request #1273 from ElyesH/master
Fix spelling errors
2021-10-02 18:29:41 +02:00
f18eb71ad7 Fix spelling errors
Signed-off-by: Elyes HAOUAS <ehaouas@noos.fr>
2021-10-02 18:13:17 +02:00
7af6e8413c Merge pull request #1271 from alebastr/sni-passive-visibility
fix(tray): fix visibility of Passive items
2021-10-02 09:49:57 +02:00
fbedc3d133 fix(tray): fix visibility of Passive items
`show_all` call from `Tray::update` attempts to walk the widget tree and
make every widget visible. Since we control individual tray item
visibility based on `Status` SNI property, we don't want that to happen.

Modify `Tray::update` to control the visibility of a whole tray module
only and ensure that the children of `Item` are still visible when
necessary.
2021-10-02 00:08:45 -07:00
fe5ac0fe08 Merge pull request #1257 from tokyovigilante/fix-catch2
Update catch2 dependency 2.13.3 -> 2.13.7
2021-09-23 22:40:31 +02:00
f638fe473a Update catch2 dependency 2.13.3 -> 2.13.7 2021-09-22 11:43:25 +12:00
8d04da1551 dont escape essid in tooltip
just like https://github.com/Alexays/Waybar/pull/834 but for the network name.
2021-09-21 13:15:36 +02:00
8534175c59 Merge pull request #1252 from gavinbeatty/fix-sway-language-tabs
sway/language: remove tabs, indent with 2 spaces
2021-09-19 19:08:28 +02:00
6778a79033 Merge pull request #1253 from Darkclainer/per-core-cpu-usage
Add cpu usage for every core
2021-09-19 19:00:46 +02:00
6e5a0bc80a Add cpu usage for every core 2021-09-19 14:02:25 +03:00
fe547901fa sway/language: remove tabs, indent with 2 spaces 2021-09-18 15:28:45 -05:00
cee08eccd1 Merge pull request #1251 from Alexays/revert-1250-master
Revert "Add CPU usage for every core"
2021-09-18 13:50:25 +02:00
67c7302938 Revert "Add CPU usage for every core" 2021-09-18 13:50:16 +02:00
8489646b66 Merge pull request #1250 from Darkclainer/master
Add CPU usage for every core
2021-09-18 13:48:39 +02:00
6938921e92 Merge pull request #1239 from mazunki/default-gateway
Added support for {gwaddr} as a variable
2021-09-18 13:47:32 +02:00
7b73cc472f Merge branch 'default-gateway' of github.com:mazunki/waybar into default-gateway 2021-09-18 02:09:57 +02:00
1c91c71dcd updated original debug message with gateway ip, similar, yet not identical to ip route 2021-09-18 02:09:25 +02:00
6142dfba6a updated original debug message with gateway ip, similar, yet not identical to default via 10.13.37.100 dev enp7s0 metric 2
10.13.37.0/24 dev enp7s0 proto kernel scope link src 10.13.37.97 's output
2021-09-18 01:51:16 +02:00
13239417d8 fixed wrong dependency for make target 2021-09-18 01:20:16 +02:00
5f083193e4 fixed tab indentation to spaces, removed debug 2021-09-18 01:12:58 +02:00
a51ac59252 Merge remote-tracking branch 'origin/master' 2021-09-17 22:30:31 +03:00
8da940f929 Update man pages for cpu usage 2021-09-17 22:22:14 +03:00
ce9e8aead3 Merge pull request #1222 from mswiger/use_g_memdup2
Use g_memdup2 instead of g_memdup
2021-09-17 20:56:47 +02:00
73ce2a99ff Merge pull request #1238 from Matan1x/master
round brightness
2021-09-17 20:56:28 +02:00
b028a47d57 Merge pull request #1243 from alebastr/config-unittest
Unit-tests for configuration includes
2021-09-17 20:56:09 +02:00
4bf577e89b Add CPU usage for every core 2021-09-17 21:18:21 +03:00
5991bbb741 ci: run unit-tests 2021-09-15 22:20:46 +07:00
d7d606b721 doc: update documentation for 'include' 2021-09-15 22:20:45 +07:00
0c1d3e30b6 fix(config): preserve explicit null when merging objects 2021-09-15 22:20:44 +07:00
ccc60b4245 refactor(config): more sensible multi-bar include behavior 2021-09-15 22:15:27 +07:00
8912bd3ed0 test: multi-bar config with includes 2021-09-15 21:34:56 +07:00
9f3b34e4d9 test: validate configuration load 2021-09-15 21:34:55 +07:00
6eba62f060 test: add build configs for catch2 2021-09-15 21:34:55 +07:00
1f16d7955d refactor(config): drop getValidPath 2021-09-15 21:34:54 +07:00
1f7d399b8e refactor(config): remove style handling from Config 2021-09-15 21:34:53 +07:00
4fff2eaaa0 refactor(client): change config visibility to public 2021-09-15 21:34:10 +07:00
b377520a38 refactor(client): extract config handling into a new class 2021-09-14 13:15:54 +07:00
95ecff0551 added example tooltip usage 2021-09-09 20:12:20 +02:00
2c380a53ca added support for the {gwaddr} variable 2021-09-09 20:05:18 +02:00
aacd0fcc65 round brightness 2021-09-08 17:12:30 +03:00
4f76c9bd43 Only use g_memdup2 for glib >= 2.68 2021-08-29 13:11:04 -07:00
8d9e322507 Add man 2021-08-29 16:37:02 +03:00
94e53c3777 Add swap flags 2021-08-29 16:34:29 +03:00
12c42fc6e4 Merge branch 'master' of https://github.com/Alexays/Waybar into pr/anakael/add-name-to-taskbar 2021-08-27 01:43:31 +03:00
5186dd27e6 Use while (getline) instead of a for loop
Also make the comments surrounding the /proc/net/dev parsing clearer and
remove the apparently redundant "is the netdev file still good?" check.
2021-08-26 11:33:52 -07:00
9d9f959769 Switch network module to read /proc/net/dev
This fixes issue #610 by reading bandwidth usage per-interface from
/proc/net/dev instead of globally via /proc/net/netstat. It supports the
same matching logic as elsewhere, so setting interface to '*' should
display the same sum-total bandwidth usage as the previous
implementation.
2021-08-25 15:00:05 -07:00
4e256cf3f0 Merge pull request #1223 from xi/cpu-mem-format-icon
add format-icons for cpu und memory
2021-08-23 09:22:02 +02:00
eae94ee14a Merge pull request #1224 from xi/rm-travis-shield
rm travis-ci shield
2021-08-23 09:10:07 +02:00
e0260ac4fc rm travis-ci shield 2021-08-23 08:02:08 +02:00
7b4b5e55a2 support format-icon for cpu und memory 2021-08-23 07:30:19 +02:00
cb49650ea4 Use g_memdup2 instead of g_memdup
This fixes a compile warning.

See: https://discourse.gnome.org/t/port-your-module-from-g-memdup-to-g-memdup2-now/5538
2021-08-22 14:46:40 -07:00
d60bb90b77 Fix typo 2021-08-22 20:43:37 +03:00
ddfe036f00 Format 2021-08-22 20:42:16 +03:00
38afa345dd Finish 2021-08-22 20:18:03 +03:00
ab38f13052 Merge pull request #1217 from gavinbeatty/patch-1
Fix memory leak and data race
2021-08-21 20:44:51 +02:00
9ee701974f Fix memory leak and data race
- Delete previous Layout before creating next one, and in destructor
- Use stack XKBContext instead of local new+delete
- Lock mutex in update() as it is called from a different thread than onEvent(res)
2021-08-20 10:06:35 -05:00
8940c3bbe8 Merge pull request #1211 from mswiger/fix_multi_display_tray_icon_scaling
Fix tray icon scaling on multi-display setups
2021-08-20 17:00:41 +02:00
51f2c6bc46 Merge pull request #1216 from Anakael/pr/anakael/add-short-desc-to-lang
fix: Add number to shortDescripton
2021-08-20 09:04:35 +02:00
c058a2d196 Add number to shortDescripton 2021-08-20 01:09:16 +03:00
a4fff66bec Merge pull request #1215 from ifreund/river-urgent
river/tags: support urgent tags
2021-08-19 15:07:35 +02:00
024fd42e27 river/tags: support urgent tags
Upstream river has a concept of urgent views/tags as of commit e59c2a73.
Introduce a new urgent style to expose this in the waybar module.
2021-08-19 15:01:49 +02:00
c413c1ec23 Merge pull request #1209 from Anakael/pr/anakael/add-short-desc-to-lang
feat: Add short desc to lang
2021-08-17 09:11:55 +02:00
2d80d31527 Fix tray icon scaling on multi-display setups 2021-08-16 23:33:29 -07:00
a87a967a97 Fix leak 2021-08-17 05:29:35 +03:00
a57e431437 Add shortDescription 2021-08-17 05:28:41 +03:00
61783aafaa save 2021-08-17 04:31:17 +03:00
e5787a2617 chore: 0.9.8 2021-08-16 15:47:34 +02:00
9aec6bbed4 Merge pull request #1190 from mswiger/fix_incorrect_tray_icon_scale
Fix incorrect tray icon scaling
2021-08-01 13:39:29 +02:00
4f6a9b1bc2 Fix incorrect tray icon scaling 2021-07-31 18:01:31 -07:00
28e7a96e37 Merge pull request #1188 from WhyNotHugo/patch-1
Don't start if graphical-session is not running
2021-07-31 20:36:52 +02:00
710f933fa6 Don't start if graphical-session is not running
Currently waybar _can_ try to start even if there's no graphical session (and
no sway) running. Adding `Requisite=` prevents this. From `systemd.unit(5)`:

    Requisite=
       Similar to Requires=. However, if the units listed here are not
       started already, they will not be started and the starting of
       this unit will fail immediately.  Requisite= does not imply an
       ordering dependency, even if both units are started in the same
       transaction. Hence this setting should usually be combined with
       After=, to ensure this unit is not started before the other
       unit.

       When Requisite=b.service is used on a.service, this dependency
       will show as RequisiteOf=a.service in property listing of
       b.service.  RequisiteOf= dependency cannot be specified directly.
2021-07-31 16:56:55 +02:00
bad72de960 Merge pull request #1105 from Amanieu/fix_power_calc
Fix power calculation when battery units are in μA instead of μW
2021-07-25 15:07:27 +02:00
65166109c9 Merge branch 'master' into fix_power_calc 2021-07-25 15:07:01 +02:00
91156dfc75 Merge pull request #1178 from Anakael/pr/anakael/add-languge-tooltip-format
[sway/language] Add tooltip-format
2021-07-25 15:05:58 +02:00
af2113931a fix typo 2021-07-24 17:26:49 +03:00
68e4457f3a Add tooltip-formay 2021-07-24 17:24:37 +03:00
1b4ddbca3a Merge pull request #1024 from GrantMoyer/keyboard_state
Keyboard state module
2021-07-23 16:15:02 +02:00
445ad22580 Merge branch 'master' into keyboard_state 2021-07-23 15:59:08 +02:00
88a5f713ed Prefer keyboard-state over keyboard_state 2021-07-23 09:45:07 -04:00
2009ceb350 Update opensuse 2021-07-23 15:01:29 +02:00
77a2eff2ce Update opensuse 2021-07-23 14:49:03 +02:00
cf832798fb Update debian 2021-07-23 14:46:03 +02:00
3f3f2d9c2c Merge pull request #1159 from Anakael/pr/anakael/sway-language-impr
[sway/language] Improve sway/language
2021-07-23 14:42:22 +02:00
b47705ac21 Merge branch 'master' into pr/anakael/sway-language-impr 2021-07-23 09:07:43 +02:00
b33be38877 Merge pull request #1145 from alebastr/sni-enhancements
tray module enhancements
2021-07-23 09:05:54 +02:00
a5fe6f40b8 feat(tray): handle Status property
On the `Passive` value of `Status` tray items would be hidden unless
`show-passive-items` is set to true.
On the `NeedsAttention` value of `Status` tray items will have a
`.needs-attention` CSS class.
2021-07-22 08:04:06 -07:00
245f7f4b11 feat(tray): handle scroll events 2021-07-22 08:04:05 -07:00
1418f96e46 feat(tray): fallback to Title for items without ToolTip 2021-07-22 08:04:04 -07:00
84a8f79bbe feat(tray): implement tooltips (text only) for tray items 2021-07-22 08:04:03 -07:00
4b6253e810 refactor(tray): infer changed properties from signal name
Comparing two GVariants is too expensive; let's collect the set of
properties updated by each signal and apply them unconditionally.
2021-07-22 08:04:00 -07:00
929fc16994 fix(tray): ignore unused WindowId property 2021-07-22 08:01:25 -07:00
811f0896c9 Merge pull request #1174 from mswiger/fix_blurry_tray_icons
Fix blurry tray icons for HiDPI displays
2021-07-21 10:36:16 +02:00
7729ca3427 Update debian 2021-07-21 10:28:56 +02:00
100d4d3499 Merge branch 'master' into pr/anakael/sway-language-impr 2021-07-21 09:40:19 +02:00
7f5fd1ac86 Update opensuse 2021-07-21 09:30:47 +02:00
1f5c07a07f Update debian 2021-07-21 09:27:54 +02:00
67d482d28b Update opensuse 2021-07-21 09:23:52 +02:00
1440ed29d4 Fix blurry tray icons for HiDPI displays 2021-07-20 22:29:34 -07:00
311c5779ea Remove unused variable 2021-07-20 23:03:41 -04:00
9880c6929f Install libevdev in FreeBSD workflow 2021-07-20 23:03:41 -04:00
99138ffdcd Add man page for keyboard_state module 2021-07-20 21:09:00 -04:00
08e886ebc6 Search for device automatically if none given 2021-07-20 21:09:00 -04:00
6fdbc27998 Add default style 2021-07-20 21:09:00 -04:00
40e6360722 Update css class when locked/unlocked 2021-07-20 21:09:00 -04:00
642e28166b Add more configuaration 2021-07-20 21:09:00 -04:00
6dfa31fb17 Basic keyboard state module 2021-07-20 21:09:00 -04:00
c91cc2218b Merge pull request #1170 from larsch/default_name_fix
Fix pulseaudio icon name compilation error
2021-07-20 15:26:18 +02:00
6f2bfd43bf Fix pulseaudio icon name compilation error 2021-07-20 15:25:05 +02:00
f43f8773c4 Merge pull request #1169 from roosemberth/pa-control-active-sink
pulseaudio: Control currently running sink
2021-07-20 14:01:23 +02:00
bb072675ba Merge pull request #1161 from xytovl/per-device-pulse-icon
Support per-device icon in pulseaudio
2021-07-20 14:00:14 +02:00
fa43072be7 Merge pull request #1163 from Anakael/pr/anakael/update-dockerfiles
Update dockerfiles
2021-07-20 13:59:09 +02:00
86a43b9042 pulseaudio: Control currently running sink
In a system with multiple sinks, the default sink may not always be
the once currently being used. It is more useful to control the
currently active sink rather than an unused one.

This patch does not make any difference if the system only uses the
default sink.

Signed-off-by: Roosembert Palacios <roosemberth@posteo.ch>
2021-07-20 10:16:53 +02:00
2506c0104a Update Dockerfiles/fedora
Co-authored-by: Aleksei Bavshin <alebastr89@gmail.com>
2021-07-16 11:37:58 +03:00
948eba92a5 Update dockerfiles 2021-07-16 03:03:11 +03:00
ad09072a6d Merge branch 'master' into pr/anakael/sway-language-impr 2021-07-15 22:17:38 +02:00
9c2b5efe7b Support per-device icon in pulseaudio 2021-07-15 09:20:43 +02:00
91cdf80c65 Merge pull request #1157 from tiosgz/multi-bar-fix
Do not fail to parse a multi-bar config
2021-07-14 09:04:24 +02:00
9cce5ea6b5 Update dockerfiles 2021-07-13 04:49:19 +03:00
8310700bbb Improve sway/language 2021-07-13 04:33:12 +03:00
78aaa5c1b4 Do not fail to parse a multi-bar config 2021-07-10 20:22:37 +00:00
7c1303f57c Merge pull request #1147 from alebastr/github-ci-linux
Add GitHub CI jobs on Linux
2021-07-03 01:08:39 +02:00
569517c531 chore: update freebsd-vm to 0.1.4 2021-07-03 00:55:59 +02:00
1c2e0083ba Merge pull request #1144 from ajakk/master
libfmt >=8.0.0 compatibility
2021-07-03 00:30:33 +02:00
a8edc0886d Delete .travis.yml 2021-07-03 00:28:43 +02:00
8e1f85e1c3 Update archlinux 2021-07-03 00:27:57 +02:00
5420a91046 chore: update FreeBSD action to address ntp sync issue 2021-07-01 00:13:30 -07:00
2a52efa99a chore: update fedora dockerfile 2021-06-30 23:51:28 -07:00
d3c59c42ef feat(ci): add GitHub CI jobs on Linux 2021-06-30 23:51:09 -07:00
368e4813de libfmt >=8.0.0 compatibility 2021-06-30 13:12:38 -05:00
36857ae72b Merge pull request #995 from OskarCarl/master
Add recursive config includes
2021-06-23 23:35:56 +02:00
982d571b2e Add include man section 2021-06-23 23:08:47 +02:00
e62b634f72 Workaround for circular imports 2021-06-21 19:29:09 +02:00
e8278431d2 Proper formatting 2021-06-21 19:05:01 +02:00
14f626d422 Add recursive config includes 2021-06-21 19:05:01 +02:00
d08fbb2ef2 Merge pull request #1132 from alebastr/fix-noexcept-condvar-crash
fix(util): protect std::condition_variable methods from pthread_cancel
2021-06-15 13:20:05 +02:00
5da268077c fix(util): protect std::condition_variable methods from pthread_cancel
The changes in GCC 11.x made `std::condition_variable` implementation
internals `noexcept`. `noexcept` is known to interact particularly bad
with `pthread_cancel`, i.e. `__cxxabiv1::__force_unwind` passing through
the `noexcept` call stack frame causes a `std::terminate` call and
immediate termination of the program

Digging through the GCC ML archives[1] lead me to the idea of patching
this with a few pthread_setcancelstate's. As bad as the solution is, it
seems to be the best we can do within C++17 limits and without major
rework.

[1]: https://gcc.gnu.org/legacy-ml/gcc/2017-08/msg00156.html
2021-06-12 12:56:44 -07:00
20160749e7 Merge pull request #1130 from tperard/fix-network-format-config
network: Fix mix use of default and state specific format
2021-06-08 22:53:10 +02:00
194f4c2f18 network: Fix mix use of default and state specific format
Whenever the network module is configured with both "format" and
"format-$state" and when the module use "format-$state" once, it
override the value that was saved from "format".

For example, if both "format" and "format-disconnect" are configured,
and only those, as soon as the module show information about a
disconnected interface, it will keep showing the format for
disconnected, even if the interface is connected again later.

Fix that by always setting a value to default_format_ in update() and
thus use the intended default format when needed.

Fixes #1129
2021-06-08 18:50:32 +01:00
9e34be7b16 Merge pull request #1126 from tperard/fix-network-auto-detection
Fix network interface auto detection
2021-06-05 18:06:30 +02:00
6e041d5275 Merge pull request #1125 from maximbaz/sway-language-ignore-empty 2021-06-05 18:01:26 +02:00
33617b67f0 network: Fix one case where default route is deleted without notification
When an interface's state is change to "down", all the route
associated with it are deleted without an RTM_DELROUTE event.

So when this happen, reset the module to start looking for a new
external interface / default route.

Fixes #1117
2021-06-05 16:52:04 +01:00
efaac20d82 network: Handle ip route priority
When there's a new default route with higher priority, switch to it.
2021-06-05 16:51:54 +01:00
ce97df34e6 network: Also clear ifname in clearIface()
Since we reset `ifid_`, clear `ifname_` as well.
2021-06-05 16:51:40 +01:00
23b9923eeb network: Parse whole RTM_NEWROUTE msg before interpreting it
The check to figure out if we have the default route should be after
the for loop that parses the route attributes, to avoid acting on
incomplete information. We are going to use more fields from the
message.
2021-06-05 16:51:35 +01:00
999c1b6b81 sway-language: ignore events with empty layout 2021-06-05 15:03:52 +02:00
1a98ecf6b0 Merge branch 'master' into fix_power_calc 2021-05-30 11:17:54 +01:00
5444a66e71 Merge pull request #1116 from tperard/fix-network-rework
Fix network module rework
2021-05-27 21:24:07 +02:00
f49a7a1acb network: Update WiFi information when available
The module doesn't update the `essid_` as soon as a WiFi interface is
connected, but that happens at some point later, depending on
"interval" configuration.

Fix that by rerunning the get WiFi information thread when the
`carrier` state changes. Also, we will clear the state related to WiFi
when the connection is drop to avoid stale information.
2021-05-27 19:36:14 +01:00
28dfb0ba41 network: Fix use of carrier information
Some RTM_NEWLINK messages may not have the IFLA_CARRIER information.
This is the case when a WiFi interface report scan result are
available. `carrier` is used regardless of if it is present in the
message or not. This would result in the interface appearing
"disconnected" in waybar when it isn't.

This patch now check that `carrier` is available before using it.

The same thing could potentially happen to `ifname` so check if it's
set before recording it.

Fixes: c1427ff (network: Handle carrier information)
Fixes #388
2021-05-26 19:23:20 +01:00
94a882bf95 Merge pull request #1113 from alebastr/exclusive-and-passthrough
Add config options for exclusive zone and input event passthrough
2021-05-22 10:34:39 +02:00
da2d603b53 doc: add man for exclusive and passthrough flags 2021-05-21 22:44:19 -07:00
7aaa3df701 feat(bar): add config flag to disable exclusive zone 2021-05-21 22:44:18 -07:00
729553d3bc feat(bar): add config flag for pointer event passthrough 2021-05-21 22:44:17 -07:00
f78a802d11 Merge pull request #1106 from tperard/network-module-reword
Network module rework
2021-05-21 17:02:28 +02:00
826a549d1f Merge pull request #1112 from yonatan8070/master
Add options to use a .json extension for the config filename
2021-05-21 17:00:02 +02:00
99918205ed Correct .json to .jsonc 2021-05-21 17:53:43 +03:00
c65ec9e14f Add options to use a .json extension for the config filename 2021-05-21 15:54:48 +03:00
c1427ff807 network: Handle carrier information
IFLA_CARRIER allows to know when a cable is plugged to the Ethernet
card or when the WiFi is connected. If there's no carrier, the
interface will be considered disconnected.
2021-05-15 16:38:00 +01:00
0bb436f949 network: Rework interface auto detection, handle route change events
Last part of the rework of handleEvents(), this time we take the
getExternalInterface() function and add it to the handleEvents()
function. That way, waybar can react immediately when a new "external
interface" is available and doesn't need to probe. Also that avoid to
have two different functions consuming from the same socket and we
don't need to recode some of the functions that are already available
via libnl (to send and receive messages).
2021-05-15 16:38:00 +01:00
0fc7ef6685 network: Rework address lookup to use only events
In order to get the IP address of an interface, we can get the
information out of NEWADDR events without needed to call getifaddrs().
And when now events are expected, we can requests a dump of all
addresses and handle addresses changes the same way via handleEvents()
only.
2021-05-15 16:38:00 +01:00
c9bbaa7241 network: Rework initial interface search by using a dump
Instead of using an alternative way to list all links in order to
choose one when an "interface" is in the configuration, we can ask for
a dump of all interface an reuse the handleEvents() function.

This patch also start to rework the handleEvents() function to grab
more information out of each event, like the interface name.
2021-05-15 16:38:00 +01:00
63fdf66ad6 network: Read all available messages on ev_sock_
When more than one message is available to read on the ev_sock_
socket, only the first one is read.

Make some changes to be able to read all the messages available by
setting the socket to non-blocking. This way we can detect when
there's nothing left to read and loop back to wait with epoll.
2021-05-15 16:38:00 +01:00
9357a6cb88 network: Start the module with some text in the label_
Fix modules starting with no text, but not hidding.

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.
2021-05-15 16:38:00 +01:00
dbc06abf18 network: Initialise cidr_ like clearIface() does 2021-05-15 16:38:00 +01:00
4d067619a8 Fix power calculation when battery units are in μA instead of μW 2021-05-15 15:55:38 +01:00
cf3d6545c3 Merge pull request #1101 from Max1Truc/master
fix: incorrect battery percentage on Pinebook Pro
2021-05-11 10:41:38 +02:00
f3a6e2d494 fix: incorrect battery percentage on Pinebook Pro 2021-05-10 21:00:14 +02:00
cdce3e03ea Update meson.build 2021-04-30 14:25:48 +02:00
b25b7d29fc Update spdlog.wrap 2021-04-30 14:25:26 +02:00
71d7596b6f fix: bluetooth status tooltip 2021-04-30 14:23:49 +02:00
06e699c862 Merge pull request #1087 from Synthetica9/multiple-rewrites
rewriteTitle: allow multiple sequential rewrites
2021-04-27 00:00:53 +02:00
a03283d65f rewriteTitle: allow multiple sequential rewrites 2021-04-26 20:26:43 +02:00
ef38061edd Merge pull request #1084 from gabegorelick/battery-discharging-full
[modules/battery] allow format-discharging-full
2021-04-26 09:28:00 +02:00
7e13e26c29 [modules/battery] allow format-discharging-full
`format-discharging-full` has been impossible since #923 made it
impossible to be full and discharging at the same time. This should
fix that by only making `format-charging-full` impossible. Whether
or not that should be allowed is a good question, but beyond the
scope of this change.

Fixes #1031
2021-04-25 22:00:24 -04:00
5f7329f5b9 Merge pull request #1081 from David96/master
[modules/pulseaudio] fix bluetooth class for PipeWire
2021-04-25 14:03:00 +02:00
2213380dc0 [modules/pulseaudio] fix bluetooth class for PipeWire
apparently, pipewire-pulse slightly changed the naming of the sink.
2021-04-25 11:19:35 +02:00
66d8035ed1 Merge pull request #1055 from vrld/feature-rewrite-window-title
Add option to rewrite sway/window title
2021-04-21 14:23:45 +02:00
7cdf178f8d Document changes in manpage
Add section on rewrite rules and extend example
2021-04-21 12:24:47 +02:00
af3c868a5b Catch exception on erroneous rules
std::regex and std::regex_replace may throw an std::regex_error if the
expression or replacement contain errors.

Log this error and carry on with the next rule, so that the title is
shown even if the config contains errors.
2021-04-21 12:24:47 +02:00
b16c8972c7 Add option to rewrite sway/window title
Rewrites window title according to config option "rewrite".
"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. Regex and replacement follow ECMA-script
rules. If no regex matches, the title is left unchanged.

example:
"sway/window": {
  "rewrite": {
    "(.*) - Mozilla Firefox": " $1",
    "(.*) - zsh": " $1",
  }
}
2021-04-21 12:24:47 +02:00
1c9b62de07 Merge pull request #1076 from Scrumplex/add-mpd-volume-status
Implement MPD volume status format template
2021-04-20 09:38:19 +02:00
fc89b01ba6 feat: implement mpd volume format template
Allow the user to show the current volume from MPD status via the
`format` and/or `tooltip-format` configuration options.

The values are provided by libmpdclient and are integers, generally
between 0-100 (without %). Values above 100 are also possible, as mpd
output plugins like `pulse` support volumes above 100%.

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2021-04-20 08:35:47 +02:00
70e67c5daa Merge pull request #1074 from Anakael/feature/taskbar-ignore-list
[wlr/taskbar] Add ignore-list param
2021-04-18 20:02:14 +02:00
5ad3b6018a Remove exceed protected 2021-04-18 21:37:58 +03:00
ba278985e8 Add ignore-list param to wlr/taskbar 2021-04-18 21:34:29 +03:00
5300461c79 chore: v0.9.7 2021-04-15 21:17:54 +02:00
d0f60c47bf Merge pull request #1070 from jgmdev/cpumodulefix
[Module CPU] fix crash due to empty frequencies.
2021-04-15 21:17:00 +02:00
07f2470e36 Merge pull request #974 from kamushadenes/patch-1
Improve Pulseaudio sink/source separation
2021-04-15 21:09:35 +02:00
f8f1e791a3 [Module CPU] fix crash due to empty frequencies.
On some systems (eg: ARM) the supported frequencies of the CPU are not
properly reported by /proc/cpuinfo so if that fails try to retrieve them
from /sys/devices/system/cpu/cpufreq/policy[0-9]/cpuinfo_[max|min]_freq.
2021-04-15 14:30:29 -04:00
dc38640341 Merge branch 'master' into patch-1 2021-01-18 10:55:40 -03:00
a7941a00c5 fix missing parentheses 2021-01-12 19:10:34 -03:00
f4ffb21c8c improve sink/source separation
Add additional fields, namely `source_volume` and `source_desc`
Add `tooltip-format`, reverting to default behavior if not specified
Add additional CSS classes, namely `sink-muted` and `source-muted`
2021-01-12 18:51:44 -03:00
ebdeb86703 feat(swaybar-ipc): handle visibility_by_modifier update 2020-10-19 19:35:55 -07:00
bc13453155 feat(swaybar-ipc): handle mode update 2021-09-15 22:39:51 +07:00
23e5181cac feat(swaybar-ipc): add swaybar IPC client 2020-10-19 19:34:48 -07:00
452dcaa5d3 feat(client): store bar_id argument 2021-11-19 20:28:41 -08:00
5905078e56 doc: document mode option of the bar config 2021-11-19 21:02:29 -08:00
52361ed360 refactor(bar): make setVisible switch between "default" and "invisible" modes 2021-11-21 11:00:57 -08:00
87b43c2171 feat(bar): attach CSS class mode-{mode} to window when setting mode 2021-11-19 20:02:57 -08:00
ae88d7d8dc feat(bar): use "default" mode to store global options
Read `layer`, `exclusive`, `passthrough` into a special mode "default".
Drop `overlay` layer hacks, as it's easier to use `"mode": "overlay"`
for the same result.
2021-11-19 19:31:41 -08:00
6d2ba7a75b feat(bar): store modes as a map of presets
This allows to apply the mode atomically and adds possibility of
defining custom modes (to be implemented).
2021-11-19 19:29:51 -08:00
03a641ed83 feat(bar): support swaybar mode for configuring window
Use `mode` (`waybar::Bar::setMode`) as a shorthand to configure bar
visibility, layer, exclusive zones and input event handling in the same
way as `swaybar` does.
See `sway-bar(5)` for a description of available modes.
2021-09-15 22:35:50 +07:00
3e2197a82a test(util): add tests for SafeSignal
Add a fixture for writing tests that require interaction with Glib event
loop and a very basic test for SafeSignal.
2020-12-28 17:28:03 -08:00
79883dbce4 feat(util): optimize SafeSignal for events from the main thread 2020-12-28 17:31:23 -08:00
8a0e76c8d8 fix(util): avoid creating temporary functor for each event 2020-10-19 18:42:25 -07:00
285a264aae feat(util): SafeSignal class for cross-thread signals with arguments
Implement a wrapper over Glib::Dispatcher that passes the arguments to
the signal consumer via synchronized `std::queue`.
Arguments are always passed by value and the return type of the signal
is expected to be `void`.
2020-12-28 17:26:55 -08:00
22409d27c5 Fix docs typos
Add removing buttons
Adjust handling multiple outputs.
2020-08-11 00:28:56 +03:00
42b6c089f3 Add docs and adjust sorting 2020-08-08 00:09:35 +03:00
0ad29a5960 Finish base implementation 2020-08-07 23:46:47 +03:00
4c251578e9 Add formatting and states handling 2020-08-07 02:45:08 +03:00
7638f5c595 Add base name representation 2020-08-06 01:42:57 +03:00
edd4d8ee11 Finish implement wlr 2020-08-05 23:10:08 +03:00
2b11b7ef8c Base wlr impl (Manager, Group) 2020-08-05 02:17:38 +03:00
94a4d41a65 disk: make used calculation match df(1) output
Substract space only available to superuser as well.
2020-05-26 07:47:08 +00:00
120 changed files with 5458 additions and 1093 deletions

View File

@ -4,19 +4,25 @@ on: [ push, pull_request ]
jobs: jobs:
clang: clang:
runs-on: macos-latest # until https://github.com/actions/runner/issues/385 # Run actions in a FreeBSD vm on the macos-10.15 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
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Test in FreeBSD VM - name: Test in FreeBSD VM
uses: vmactions/freebsd-vm@v0.0.9 # aka FreeBSD 12.2 uses: vmactions/freebsd-vm@v0.1.6 # aka FreeBSD 13.0
with: with:
mem: 2048
usesh: true usesh: true
prepare: | prepare: |
export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio export CPPFLAGS=-isystem/usr/local/include LDFLAGS=-L/usr/local/lib # sndio
sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
pkg install -y git # subprojects/date pkg install -y git # subprojects/date
pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio \ pkg install -y catch evdev-proto gtk-layer-shell gtkmm30 jsoncpp \
libfmt libmpdclient libudev-devd meson pkgconf pulseaudio scdoc spdlog libdbusmenu libevdev libfmt libmpdclient libudev-devd meson \
pkgconf pulseaudio scdoc sndio spdlog wayland-protocols
run: | run: |
meson build -Dman-pages=enabled meson build -Dman-pages=enabled
ninja -C build ninja -C build
meson test -C build --no-rebuild --print-errorlogs --suite waybar

27
.github/workflows/linux.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: linux
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
distro:
- alpine
- archlinux
- debian
- fedora
- opensuse
runs-on: ubuntu-latest
container:
image: alexays/waybar:${{ matrix.distro }}
steps:
- uses: actions/checkout@v2
- name: configure
run: meson -Dman-pages=enabled build
- name: build
run: ninja -C build
- name: test
run: meson test -C build --no-rebuild --print-errorlogs --suite waybar

View File

@ -1,38 +0,0 @@
language: cpp
services:
- docker
git:
submodules: false
env:
- distro: debian
- distro: archlinux
- distro: fedora
- distro: alpine
- distro: opensuse
before_install:
- docker pull alexays/waybar:${distro}
- find . -type f \( -name '*.cpp' -o -name '*.h' \) -print0 | xargs -r0 clang-format -i
script:
- echo FROM alexays/waybar:${distro} > Dockerfile
- echo ADD . /root >> Dockerfile
- docker build -t waybar .
- docker run waybar /bin/sh -c "cd /root && meson build -Dman-pages=enabled && ninja -C build"
jobs:
include:
- os: freebsd
compiler: clang
env:
before_install:
- export CPPFLAGS+=-isystem/usr/local/include LDFLAGS+=-L/usr/local/lib # sndio
- sudo sed -i '' 's/quarterly/latest/' /etc/pkg/FreeBSD.conf
- sudo pkg install -y gtk-layer-shell gtkmm30 jsoncpp libdbusmenu sndio
libfmt libmpdclient libudev-devd meson pulseaudio scdoc spdlog
script:
- meson build -Dman-pages=enabled
- ninja -C build

View File

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

View File

@ -1,6 +1,6 @@
# vim: ft=Dockerfile # vim: ft=Dockerfile
FROM archlinux/base:latest FROM archlinux:base-devel
RUN pacman -Syu --noconfirm && \ RUN pacman -Syu --noconfirm && \
pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm pacman -S git meson base-devel libinput wayland wayland-protocols pixman libxkbcommon mesa gtkmm3 jsoncpp pugixml scdoc libpulse libdbusmenu-gtk3 libmpdclient gobject-introspection --noconfirm libxkbcommon

View File

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

View File

@ -1,7 +1,12 @@
# vim: ft=Dockerfile # vim: ft=Dockerfile
FROM fedora:32 FROM fedora:latest
RUN dnf install sway meson git libinput-devel wayland-devel wayland-protocols-devel pugixml-devel egl-wayland-devel mesa-libEGL-devel mesa-libGLES-devel mesa-libgbm-devel libxkbcommon-devel libudev-devel pixman-devel gtkmm30-devel jsoncpp-devel scdoc -y && \ RUN dnf install -y @c-development git-core meson scdoc 'pkgconfig(date)' \
dnf group install "C Development Tools and Libraries" -y && \ 'pkgconfig(dbusmenu-gtk3-0.4)' 'pkgconfig(fmt)' 'pkgconfig(gdk-pixbuf-2.0)' \
'pkgconfig(gio-unix-2.0)' 'pkgconfig(gtk-layer-shell-0)' 'pkgconfig(gtkmm-3.0)' \
'pkgconfig(jsoncpp)' 'pkgconfig(libinput)' 'pkgconfig(libmpdclient)' \
'pkgconfig(libnl-3.0)' 'pkgconfig(libnl-genl-3.0)' 'pkgconfig(libpulse)' \
'pkgconfig(libudev)' 'pkgconfig(pugixml)' 'pkgconfig(sigc++-2.0)' 'pkgconfig(spdlog)' \
'pkgconfig(wayland-client)' 'pkgconfig(wayland-cursor)' 'pkgconfig(wayland-protocols)' 'pkgconfig(xkbregistry)' && \
dnf clean all -y dnf clean all -y

View File

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

View File

@ -16,5 +16,8 @@ install: build
run: build run: build
./build/waybar ./build/waybar
debug-run: build-debug
./build/waybar --log-level debug
clean: clean:
rm -rf build rm -rf build

View File

@ -1,8 +1,8 @@
# Waybar [![Travis](https://travis-ci.org/Alexays/Waybar.svg?branch=master)](https://travis-ci.org/Alexays/Waybar) [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png) # Waybar [![Licence](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Paypal Donate](https://img.shields.io/badge/Donate-Paypal-2244dd.svg)](https://paypal.me/ARouillard)<br>![Waybar](https://raw.githubusercontent.com/alexays/waybar/master/preview-2.png)
> Highly customizable Wayland bar for Sway and Wlroots based compositors.<br> > Highly customizable Wayland bar for Sway and Wlroots based compositors.<br>
> Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or > Available in Arch [community](https://www.archlinux.org/packages/community/x86_64/waybar/) or
[AUR](https://aur.archlinux.org/packages/waybar-git/), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br> [AUR](https://aur.archlinux.org/packages/waybar-git/), [Gentoo](https://packages.gentoo.org/packages/gui-apps/waybar), [openSUSE](https://build.opensuse.org/package/show/X11:Wayland/waybar), and [Alpine Linux](https://pkgs.alpinelinux.org/packages?name=waybar)<br>
> *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)* > *Waybar [examples](https://github.com/Alexays/Waybar/wiki/Examples)*
#### Current features #### Current features
@ -68,6 +68,8 @@ libappindicator-gtk3 [Tray module]
libdbusmenu-gtk3 [Tray module] libdbusmenu-gtk3 [Tray module]
libmpdclient [MPD module] libmpdclient [MPD module]
libsndio [sndio module] libsndio [sndio module]
libevdev [KeyboardState module]
xkbregistry
``` ```
**Build dependencies** **Build dependencies**
@ -86,6 +88,7 @@ sudo apt install \
clang-tidy \ clang-tidy \
gobject-introspection \ gobject-introspection \
libdbusmenu-gtk3-dev \ libdbusmenu-gtk3-dev \
libevdev-dev \
libfmt-dev \ libfmt-dev \
libgirepository1.0-dev \ libgirepository1.0-dev \
libgtk-3-dev \ libgtk-3-dev \
@ -99,7 +102,8 @@ sudo apt install \
libsigc++-2.0-dev \ libsigc++-2.0-dev \
libspdlog-dev \ libspdlog-dev \
libwayland-dev \ libwayland-dev \
scdoc scdoc \
libxkbregistry-dev
``` ```

25
include/AIconLabel.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
#include <gtkmm/box.h>
#include <gtkmm/image.h>
#include "ALabel.hpp"
namespace waybar {
class AIconLabel : public ALabel {
public:
AIconLabel(const Json::Value &config, const std::string &name, const std::string &id,
const std::string &format, uint16_t interval = 0, bool ellipsize = false,
bool enable_click = false, bool enable_scroll = false);
virtual ~AIconLabel() = default;
virtual auto update() -> void;
protected:
Gtk::Image image_;
Gtk::Box box_;
bool iconEnabled() const;
};
} // namespace waybar

View File

@ -14,7 +14,7 @@ class ALabel : public AModule {
virtual ~ALabel() = default; virtual ~ALabel() = default;
virtual auto update() -> void; 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::string &alt = "", uint16_t max = 0);
virtual std::string getIcon(uint16_t, std::vector<std::string> &alts, uint16_t max = 0); virtual std::string getIcon(uint16_t, const std::vector<std::string> &alts, uint16_t max = 0);
protected: protected:
Gtk::Label label_; Gtk::Label label_;

View File

@ -8,6 +8,9 @@
#include <gtkmm/window.h> #include <gtkmm/window.h>
#include <json/json.h> #include <json/json.h>
#include <memory>
#include <vector>
#include "AModule.hpp" #include "AModule.hpp"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
@ -36,6 +39,19 @@ struct bar_margins {
int left = 0; int left = 0;
}; };
struct bar_mode {
bar_layer layer;
bool exclusive;
bool passthrough;
bool visible;
};
#ifdef HAVE_SWAY
namespace modules::sway {
class BarIpcClient;
}
#endif // HAVE_SWAY
class BarSurface { class BarSurface {
protected: protected:
BarSurface() = default; BarSurface() = default;
@ -44,6 +60,7 @@ class BarSurface {
virtual void setExclusiveZone(bool enable) = 0; virtual void setExclusiveZone(bool enable) = 0;
virtual void setLayer(bar_layer layer) = 0; virtual void setLayer(bar_layer layer) = 0;
virtual void setMargins(const struct bar_margins &margins) = 0; virtual void setMargins(const struct bar_margins &margins) = 0;
virtual void setPassThrough(bool enable) = 0;
virtual void setPosition(const std::string_view &position) = 0; virtual void setPosition(const std::string_view &position) = 0;
virtual void setSize(uint32_t width, uint32_t height) = 0; virtual void setSize(uint32_t width, uint32_t height) = 0;
virtual void commit(){}; virtual void commit(){};
@ -53,37 +70,56 @@ class BarSurface {
class Bar { class Bar {
public: public:
using bar_mode_map = std::map<std::string_view, struct bar_mode>;
static const bar_mode_map PRESET_MODES;
static const std::string_view MODE_DEFAULT;
static const std::string_view MODE_INVISIBLE;
Bar(struct waybar_output *w_output, const Json::Value &); Bar(struct waybar_output *w_output, const Json::Value &);
Bar(const Bar &) = delete; Bar(const Bar &) = delete;
~Bar() = default; ~Bar();
void setMode(const std::string_view &);
void setVisible(bool visible); void setVisible(bool visible);
void toggle(); void toggle();
void handleSignal(int); void handleSignal(int);
struct waybar_output *output; struct waybar_output *output;
Json::Value config; Json::Value config;
struct wl_surface * surface; struct wl_surface *surface;
bool visible = true; bool visible = true;
bool vertical = false; bool vertical = false;
Gtk::Window window; Gtk::Window window;
#ifdef HAVE_SWAY
std::string bar_id;
#endif
private: private:
void onMap(GdkEventAny *); void onMap(GdkEventAny *);
auto setupWidgets() -> void; auto setupWidgets() -> void;
void getModules(const Factory &, const std::string &); void getModules(const Factory &, const std::string &, Gtk::Box*);
void setupAltFormatKeyForModule(const std::string &module_name); void setupAltFormatKeyForModule(const std::string &module_name);
void setupAltFormatKeyForModuleList(const char *module_list_name); void setupAltFormatKeyForModuleList(const char *module_list_name);
void setMode(const bar_mode &);
/* Copy initial set of modes to allow customization */
bar_mode_map configured_modes = PRESET_MODES;
std::string last_mode_{MODE_DEFAULT};
std::unique_ptr<BarSurface> surface_impl_; std::unique_ptr<BarSurface> surface_impl_;
bar_layer layer_;
Gtk::Box left_; Gtk::Box left_;
Gtk::Box center_; Gtk::Box center_;
Gtk::Box right_; Gtk::Box right_;
Gtk::Box box_; Gtk::Box box_;
std::vector<std::unique_ptr<waybar::AModule>> modules_left_; std::vector<std::shared_ptr<waybar::AModule>> modules_left_;
std::vector<std::unique_ptr<waybar::AModule>> modules_center_; std::vector<std::shared_ptr<waybar::AModule>> modules_center_;
std::vector<std::unique_ptr<waybar::AModule>> modules_right_; std::vector<std::shared_ptr<waybar::AModule>> modules_right_;
#ifdef HAVE_SWAY
using BarIpcClient = modules::sway::BarIpcClient;
std::unique_ptr<BarIpcClient> _ipc_client;
#endif
std::vector<std::shared_ptr<waybar::AModule>> modules_all_;
}; };
} // namespace waybar } // namespace waybar

View File

@ -3,11 +3,10 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <gdk/gdk.h> #include <gdk/gdk.h>
#include <gdk/gdkwayland.h> #include <gdk/gdkwayland.h>
#include <unistd.h>
#include <wayland-client.h> #include <wayland-client.h>
#include <wordexp.h>
#include "bar.hpp" #include "bar.hpp"
#include "config.hpp"
struct zwlr_layer_shell_v1; struct zwlr_layer_shell_v1;
struct zwp_idle_inhibitor_v1; struct zwp_idle_inhibitor_v1;
@ -29,16 +28,14 @@ class Client {
struct zxdg_output_manager_v1 * xdg_output_manager = nullptr; struct zxdg_output_manager_v1 * xdg_output_manager = nullptr;
struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr; struct zwp_idle_inhibit_manager_v1 *idle_inhibit_manager = nullptr;
std::vector<std::unique_ptr<Bar>> bars; std::vector<std::unique_ptr<Bar>> bars;
Config config;
std::string bar_id;
private: private:
Client() = default; Client() = default;
std::tuple<const std::string, const std::string> getConfigs(const std::string &config, const std::string getStyle(const std::string &style);
const std::string &style) const; void bindInterfaces();
void bindInterfaces();
const std::string getValidPath(const std::vector<std::string> &paths) const;
void handleOutput(struct waybar_output &output); void handleOutput(struct waybar_output &output);
bool isValidOutput(const Json::Value &config, struct waybar_output &output);
auto setupConfig(const std::string &config_file) -> void;
auto setupCss(const std::string &css_file) -> void; auto setupCss(const std::string &css_file) -> void;
struct waybar_output & getOutput(void *); struct waybar_output & getOutput(void *);
std::vector<Json::Value> getOutputConfigs(struct waybar_output &output); std::vector<Json::Value> getOutputConfigs(struct waybar_output &output);
@ -53,7 +50,6 @@ class Client {
void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor); void handleMonitorRemoved(Glib::RefPtr<Gdk::Monitor> monitor);
void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor); void handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> monitor);
Json::Value config_;
Glib::RefPtr<Gtk::StyleContext> style_context_; Glib::RefPtr<Gtk::StyleContext> style_context_;
Glib::RefPtr<Gtk::CssProvider> css_provider_; Glib::RefPtr<Gtk::CssProvider> css_provider_;
std::list<struct waybar_output> outputs_; std::list<struct waybar_output> outputs_;

39
include/config.hpp Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <json/json.h>
#include <optional>
#include <string>
#ifndef SYSCONFDIR
#define SYSCONFDIR "/etc"
#endif
namespace waybar {
class Config {
public:
static const std::vector<std::string> CONFIG_DIRS;
/* Try to find any of provided names in the supported set of config directories */
static std::optional<std::string> findConfigPath(
const std::vector<std::string> &names, const std::vector<std::string> &dirs = CONFIG_DIRS);
Config() = default;
void load(const std::string &config);
Json::Value &getConfig() { return config_; }
std::vector<Json::Value> getOutputConfigs(const std::string &name, const std::string &identifier);
private:
void setupConfig(Json::Value &dst, const std::string &config_file, int depth);
void resolveConfigIncludes(Json::Value &config, int depth);
void mergeConfig(Json::Value &a_config_, Json::Value &b_config_);
std::string config_file_;
Json::Value config_;
};
} // namespace waybar

View File

@ -14,6 +14,7 @@
#endif #endif
#ifdef HAVE_WLR #ifdef HAVE_WLR
#include "modules/wlr/taskbar.hpp" #include "modules/wlr/taskbar.hpp"
#include "modules/wlr/workspace_manager.hpp"
#endif #endif
#ifdef HAVE_RIVER #ifdef HAVE_RIVER
#include "modules/river/tags.hpp" #include "modules/river/tags.hpp"
@ -38,6 +39,9 @@
#ifdef HAVE_LIBUDEV #ifdef HAVE_LIBUDEV
#include "modules/backlight.hpp" #include "modules/backlight.hpp"
#endif #endif
#ifdef HAVE_LIBEVDEV
#include "modules/keyboard_state.hpp"
#endif
#ifdef HAVE_LIBPULSE #ifdef HAVE_LIBPULSE
#include "modules/pulseaudio.hpp" #include "modules/pulseaudio.hpp"
#endif #endif
@ -47,6 +51,9 @@
#ifdef HAVE_LIBSNDIO #ifdef HAVE_LIBSNDIO
#include "modules/sndio.hpp" #include "modules/sndio.hpp"
#endif #endif
#ifdef HAVE_GIO_UNIX
#include "modules/inhibitor.hpp"
#endif
#include "bar.hpp" #include "bar.hpp"
#include "modules/custom.hpp" #include "modules/custom.hpp"
#include "modules/temperature.hpp" #include "modules/temperature.hpp"

21
include/group.hpp Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <gtkmm/widget.h>
#include <gtkmm/box.h>
#include <json/json.h>
#include "AModule.hpp"
#include "bar.hpp"
#include "factory.hpp"
namespace waybar {
class Group : public AModule {
public:
Group(const std::string&, const Bar&, const Json::Value&);
~Group() = default;
auto update() -> void;
operator Gtk::Widget &();
Gtk::Box box;
};
} // namespace waybar

View File

@ -1,21 +1,17 @@
#pragma once #pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include <date/tz.h> #include <date/tz.h>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "util/sleeper_thread.hpp" #include "util/sleeper_thread.hpp"
namespace waybar::modules { namespace waybar {
struct waybar_time { struct waybar_time;
std::locale locale;
date::zoned_seconds ztime; namespace modules {
};
const std::string kCalendarPlaceholder = "calendar";
const std::string KTimezonedTimeListPlaceholder = "timezoned_time_list";
class Clock : public ALabel { class Clock : public ALabel {
public: public:
@ -26,17 +22,22 @@ class Clock : public ALabel {
private: private:
util::SleeperThread thread_; util::SleeperThread thread_;
std::locale locale_; std::locale locale_;
const date::time_zone* time_zone_; std::vector<const date::time_zone*> time_zones_;
bool fixed_time_zone_; int current_time_zone_idx_;
int time_zone_idx_;
date::year_month_day cached_calendar_ymd_ = date::January/1/0; date::year_month_day cached_calendar_ymd_ = date::January/1/0;
std::string cached_calendar_text_; std::string cached_calendar_text_;
bool is_calendar_in_tooltip_;
bool is_timezoned_list_in_tooltip_;
bool handleScroll(GdkEventScroll* e); bool handleScroll(GdkEventScroll* e);
auto calendar_text(const waybar_time& wtime) -> std::string; auto calendar_text(const waybar_time& wtime) -> std::string;
auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void; auto weekdays_header(const date::weekday& first_dow, std::ostream& os) -> void;
auto first_day_of_week() -> date::weekday; auto first_day_of_week() -> date::weekday;
const date::time_zone* current_timezone();
bool is_timezone_fixed();
auto timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string;
}; };
} // namespace waybar::modules } // namespace modules
} // namespace waybar

View File

@ -19,11 +19,11 @@ class Cpu : public ALabel {
auto update() -> void; auto update() -> void;
private: private:
double getCpuLoad(); double getCpuLoad();
std::tuple<uint16_t, std::string> getCpuUsage(); std::tuple<std::vector<uint16_t>, std::string> getCpuUsage();
std::tuple<float, float, float> getCpuFrequency(); std::tuple<float, float, float> getCpuFrequency();
std::vector<std::tuple<size_t, size_t>> parseCpuinfo(); std::vector<std::tuple<size_t, size_t>> parseCpuinfo();
std::vector<float> parseCpuFrequencies(); std::vector<float> parseCpuFrequencies();
std::vector<std::tuple<size_t, size_t>> prev_times_; std::vector<std::tuple<size_t, size_t>> prev_times_;

View File

@ -0,0 +1,27 @@
#pragma once
#include <memory>
#include <gio/gio.h>
#include "ALabel.hpp"
#include "bar.hpp"
namespace waybar::modules {
class Inhibitor : public ALabel {
public:
Inhibitor(const std::string&, const waybar::Bar&, const Json::Value&);
~Inhibitor() override;
auto update() -> void;
auto activated() -> bool;
private:
auto handleToggle(::GdkEventButton* const& e) -> bool;
const std::unique_ptr<::GDBusConnection, void(*)(::GDBusConnection*)> dbus_;
const std::string inhibitors_;
int handle_ = -1;
};
} // namespace waybar::modules

View File

@ -0,0 +1,45 @@
#pragma once
#include <fmt/format.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include "AModule.hpp"
#include "bar.hpp"
#include "util/sleeper_thread.hpp"
#include <gtkmm/label.h>
extern "C" {
#include <libevdev/libevdev.h>
}
namespace waybar::modules {
class KeyboardState : public AModule {
public:
KeyboardState(const std::string&, const waybar::Bar&, const Json::Value&);
~KeyboardState();
auto update() -> void;
private:
Gtk::Box box_;
Gtk::Label numlock_label_;
Gtk::Label capslock_label_;
Gtk::Label scrolllock_label_;
std::string numlock_format_;
std::string capslock_format_;
std::string scrolllock_format_;
const std::chrono::seconds interval_;
std::string icon_locked_;
std::string icon_unlocked_;
int fd_;
libevdev* dev_;
util::SleeperThread thread_;
};
} // namespace waybar::modules

View File

@ -2,9 +2,7 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <fmt/format.h> #include <fmt/format.h>
#include <ifaddrs.h>
#include <linux/nl80211.h> #include <linux/nl80211.h>
#include <net/if.h>
#include <netlink/genl/ctrl.h> #include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h> #include <netlink/genl/genl.h>
#include <netlink/netlink.h> #include <netlink/netlink.h>
@ -28,26 +26,24 @@ class Network : public ALabel {
static const uint8_t EPOLL_MAX = 200; static const uint8_t EPOLL_MAX = 200;
static int handleEvents(struct nl_msg*, void*); static int handleEvents(struct nl_msg*, void*);
static int handleEventsDone(struct nl_msg*, void*);
static int handleScan(struct nl_msg*, void*); static int handleScan(struct nl_msg*, void*);
void askForStateDump(void);
void worker(); void worker();
void createInfoSocket(); void createInfoSocket();
void createEventSocket(); void createEventSocket();
int getExternalInterface(int skip_idx = -1) const;
void getInterfaceAddress();
int netlinkRequest(void*, uint32_t, uint32_t groups = 0) const;
int netlinkResponse(void*, uint32_t, uint32_t groups = 0) const;
void parseEssid(struct nlattr**); void parseEssid(struct nlattr**);
void parseSignal(struct nlattr**); void parseSignal(struct nlattr**);
void parseFreq(struct nlattr**); void parseFreq(struct nlattr**);
bool associatedOrJoined(struct nlattr**); bool associatedOrJoined(struct nlattr**);
bool checkInterface(struct ifinfomsg* rtif, std::string name); bool checkInterface(std::string name);
int getPreferredIface(int skip_idx = -1, bool wait = true) const;
auto getInfo() -> void; auto getInfo() -> void;
void checkNewInterface(struct ifinfomsg* rtif);
const std::string getNetworkState() const; const std::string getNetworkState() const;
void clearIface(); void clearIface();
bool wildcardMatch(const std::string& pattern, const std::string& text) const; bool wildcardMatch(const std::string& pattern, const std::string& text) const;
std::optional<std::pair<unsigned long long, unsigned long long>> readBandwidthUsage();
int ifid_; int ifid_;
sa_family_t family_; sa_family_t family_;
@ -59,18 +55,27 @@ class Network : public ALabel {
int nl80211_id_; int nl80211_id_;
std::mutex mutex_; std::mutex mutex_;
bool want_route_dump_;
bool want_link_dump_;
bool want_addr_dump_;
bool dump_in_progress_;
unsigned long long bandwidth_down_total_; unsigned long long bandwidth_down_total_;
unsigned long long bandwidth_up_total_; unsigned long long bandwidth_up_total_;
std::string state_; std::string state_;
std::string essid_; std::string essid_;
bool carrier_;
std::string ifname_; std::string ifname_;
std::string ipaddr_; std::string ipaddr_;
std::string gwaddr_;
std::string netmask_; std::string netmask_;
int cidr_; int cidr_;
int32_t signal_strength_dbm_; int32_t signal_strength_dbm_;
uint8_t signal_strength_; uint8_t signal_strength_;
uint32_t frequency_; std::string signal_strength_app_;
float frequency_;
uint32_t route_priority;
util::SleeperThread thread_; util::SleeperThread thread_;
util::SleeperThread thread_timer_; util::SleeperThread thread_timer_;

View File

@ -24,7 +24,7 @@ class Pulseaudio : public ALabel {
static void volumeModifyCb(pa_context*, int, void*); static void volumeModifyCb(pa_context*, int, void*);
bool handleScroll(GdkEventScroll* e); bool handleScroll(GdkEventScroll* e);
const std::string getPortIcon() const; const std::vector<std::string> getPulseIcon() const;
pa_threaded_mainloop* mainloop_; pa_threaded_mainloop* mainloop_;
pa_mainloop_api* mainloop_api_; pa_mainloop_api* mainloop_api_;
@ -38,7 +38,8 @@ class Pulseaudio : public ALabel {
std::string form_factor_; std::string form_factor_;
std::string desc_; std::string desc_;
std::string monitor_; std::string monitor_;
std::string default_sink_name_; std::string current_sink_name_;
bool current_sink_running_;
// SOURCE // SOURCE
uint32_t source_idx_{0}; uint32_t source_idx_{0};
uint16_t source_volume_; uint16_t source_volume_;

View File

@ -6,6 +6,7 @@
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "river-status-unstable-v1-client-protocol.h" #include "river-status-unstable-v1-client-protocol.h"
#include "river-control-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river { namespace waybar::modules::river {
@ -18,8 +19,14 @@ class Tags : public waybar::AModule {
// Handlers for wayland events // Handlers for wayland events
void handle_focused_tags(uint32_t tags); void handle_focused_tags(uint32_t tags);
void handle_view_tags(struct wl_array *tags); void handle_view_tags(struct wl_array *tags);
void handle_urgent_tags(uint32_t tags);
void handle_primary_clicked(uint32_t tag);
bool handle_button_press(GdkEventButton *event_button, uint32_t tag);
struct zriver_status_manager_v1 *status_manager_; struct zriver_status_manager_v1 *status_manager_;
struct zriver_control_v1 *control_;
struct wl_seat *seat_;
private: private:
const waybar::Bar & bar_; const waybar::Bar & bar_;

View File

@ -5,13 +5,15 @@
#include <glibmm/refptr.h> #include <glibmm/refptr.h>
#include <json/json.h> #include <json/json.h>
#include <tuple> #include <tuple>
#include "bar.hpp"
#include "modules/sni/item.hpp" #include "modules/sni/item.hpp"
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
class Host { class Host {
public: public:
Host(const std::size_t id, const Json::Value&, const std::function<void(std::unique_ptr<Item>&)>&, Host(const std::size_t id, const Json::Value&, const Bar&,
const std::function<void(std::unique_ptr<Item>&)>&,
const std::function<void(std::unique_ptr<Item>&)>&); const std::function<void(std::unique_ptr<Item>&)>&);
~Host(); ~Host();
@ -36,6 +38,7 @@ class Host {
GCancellable* cancellable_ = nullptr; GCancellable* cancellable_ = nullptr;
SnWatcher* watcher_ = nullptr; SnWatcher* watcher_ = nullptr;
const Json::Value& config_; const Json::Value& config_;
const Bar& bar_;
const std::function<void(std::unique_ptr<Item>&)> on_add_; const std::function<void(std::unique_ptr<Item>&)> on_add_;
const std::function<void(std::unique_ptr<Item>&)> on_remove_; const std::function<void(std::unique_ptr<Item>&)> on_remove_;
}; };

View File

@ -11,11 +11,21 @@
#include <libdbusmenu-gtk/dbusmenu-gtk.h> #include <libdbusmenu-gtk/dbusmenu-gtk.h>
#include <sigc++/trackable.h> #include <sigc++/trackable.h>
#include <set>
#include <string_view>
#include "bar.hpp"
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
struct ToolTip {
Glib::ustring icon_name;
Glib::ustring text;
};
class Item : public sigc::trackable { class Item : public sigc::trackable {
public: public:
Item(const std::string&, const std::string&, const Json::Value&); Item(const std::string&, const std::string&, const Json::Value&, const Bar&);
~Item() = default; ~Item() = default;
std::string bus_name; std::string bus_name;
@ -27,10 +37,8 @@ class Item : public sigc::trackable {
Gtk::EventBox event_box; Gtk::EventBox event_box;
std::string category; std::string category;
std::string id; std::string id;
std::string status;
std::string title; std::string title;
int32_t window_id;
std::string icon_name; std::string icon_name;
Glib::RefPtr<Gdk::Pixbuf> icon_pixmap; Glib::RefPtr<Gdk::Pixbuf> icon_pixmap;
Glib::RefPtr<Gtk::IconTheme> icon_theme; Glib::RefPtr<Gtk::IconTheme> icon_theme;
@ -39,6 +47,7 @@ class Item : public sigc::trackable {
std::string attention_movie_name; std::string attention_movie_name;
std::string icon_theme_path; std::string icon_theme_path;
std::string menu; std::string menu;
ToolTip tooltip;
DbusmenuGtkMenu* dbus_menu = nullptr; DbusmenuGtkMenu* dbus_menu = nullptr;
Gtk::Menu* gtk_menu = nullptr; Gtk::Menu* gtk_menu = nullptr;
/** /**
@ -49,8 +58,10 @@ class Item : public sigc::trackable {
bool item_is_menu = true; bool item_is_menu = true;
private: private:
void onConfigure(GdkEventConfigure* ev);
void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result); void proxyReady(Glib::RefPtr<Gio::AsyncResult>& result);
void setProperty(const Glib::ustring& name, Glib::VariantBase& value); void setProperty(const Glib::ustring& name, Glib::VariantBase& value);
void setStatus(const Glib::ustring& value);
void getUpdatedProperties(); void getUpdatedProperties();
void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result); void processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& result);
void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
@ -58,14 +69,24 @@ class Item : public sigc::trackable {
void updateImage(); void updateImage();
Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant); Glib::RefPtr<Gdk::Pixbuf> extractPixBuf(GVariant* variant);
Glib::RefPtr<Gdk::Pixbuf> getIconPixbuf();
Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size); Glib::RefPtr<Gdk::Pixbuf> getIconByName(const std::string& name, int size);
double getScaledIconSize();
static void onMenuDestroyed(Item* self, GObject* old_menu_pointer); static void onMenuDestroyed(Item* self, GObject* old_menu_pointer);
void makeMenu(); void makeMenu();
bool handleClick(GdkEventButton* const& /*ev*/); bool handleClick(GdkEventButton* const& /*ev*/);
bool handleScroll(GdkEventScroll* const&);
// smooth scrolling threshold
gdouble scroll_threshold_ = 0;
gdouble distance_scrolled_x_ = 0;
gdouble distance_scrolled_y_ = 0;
// visibility of items with Status == Passive
bool show_passive_ = false;
Glib::RefPtr<Gio::DBus::Proxy> proxy_; Glib::RefPtr<Gio::DBus::Proxy> proxy_;
Glib::RefPtr<Gio::Cancellable> cancellable_; Glib::RefPtr<Gio::Cancellable> cancellable_;
bool update_pending_; std::set<std::string_view> update_pending_;
}; };
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@ -0,0 +1,49 @@
#pragma once
#include <string>
#include "modules/sway/ipc/client.hpp"
#include "util/SafeSignal.hpp"
#include "util/json.hpp"
namespace waybar {
class Bar;
namespace modules::sway {
/*
* Supported subset of i3/sway IPC barconfig object
*/
struct swaybar_config {
std::string id;
std::string mode;
std::string hidden_state;
};
/**
* swaybar IPC client
*/
class BarIpcClient {
public:
BarIpcClient(waybar::Bar& bar);
private:
void onInitialConfig(const struct Ipc::ipc_response& res);
void onIpcEvent(const struct Ipc::ipc_response&);
void onConfigUpdate(const swaybar_config& config);
void onVisibilityUpdate(bool visible_by_modifier);
void update();
Bar& bar_;
util::JsonParser parser_;
Ipc ipc_;
swaybar_config bar_config_;
bool visible_by_modifier_ = false;
SafeSignal<bool> signal_visible_;
SafeSignal<swaybar_config> signal_config_;
};
} // namespace modules::sway
} // namespace waybar

View File

@ -29,4 +29,8 @@ enum ipc_command_type {
IPC_EVENT_BINDING = ((1 << 31) | 5), IPC_EVENT_BINDING = ((1 << 31) | 5),
IPC_EVENT_SHUTDOWN = ((1 << 31) | 6), IPC_EVENT_SHUTDOWN = ((1 << 31) | 6),
IPC_EVENT_TICK = ((1 << 31) | 7), IPC_EVENT_TICK = ((1 << 31) | 7),
// sway-specific event types
IPC_EVENT_BAR_STATE_UPDATE = ((1<<31) | 20),
IPC_EVENT_INPUT = ((1<<31) | 21),
}; };

View File

@ -1,6 +1,11 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <xkbcommon/xkbregistry.h>
#include <map>
#include <string>
#include "ALabel.hpp" #include "ALabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
@ -16,13 +21,50 @@ class Language : public ALabel, public sigc::trackable {
auto update() -> void; auto update() -> void;
private: private:
enum class DispayedShortFlag {
None = 0,
ShortName = 1,
ShortDescription = 1 << 1
};
struct Layout {
std::string full_name;
std::string short_name;
std::string variant;
std::string short_description;
std::string country_flag() const;
};
class XKBContext {
public:
XKBContext();
~XKBContext();
auto next_layout() -> Layout*;
private:
rxkb_context* context_ = nullptr;
rxkb_layout* xkb_layout_ = nullptr;
Layout* layout_ = nullptr;
std::map<std::string, rxkb_layout*> base_layouts_by_name_;
};
void onEvent(const struct Ipc::ipc_response&); void onEvent(const struct Ipc::ipc_response&);
void onCmd(const struct Ipc::ipc_response&); void onCmd(const struct Ipc::ipc_response&);
std::string lang_; auto set_current_layout(std::string current_layout) -> void;
util::JsonParser parser_; auto init_layouts_map(const std::vector<std::string>& used_layouts) -> void;
std::mutex mutex_;
Ipc ipc_; const static std::string XKB_LAYOUT_NAMES_KEY;
const static std::string XKB_ACTIVE_LAYOUT_NAME_KEY;
Layout layout_;
std::string tooltip_format_ = "";
std::map<std::string, Layout> layouts_map_;
bool is_variant_displayed;
std::byte displayed_short_flag = static_cast<std::byte>(DispayedShortFlag::None);
util::JsonParser parser_;
std::mutex mutex_;
Ipc ipc_;
}; };
} // namespace waybar::modules::sway } // namespace waybar::modules::sway

View File

@ -2,7 +2,8 @@
#include <fmt/format.h> #include <fmt/format.h>
#include <tuple> #include <tuple>
#include "ALabel.hpp"
#include "AIconLabel.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "modules/sway/ipc/client.hpp" #include "modules/sway/ipc/client.hpp"
@ -10,7 +11,7 @@
namespace waybar::modules::sway { namespace waybar::modules::sway {
class Window : public ALabel, public sigc::trackable { class Window : public AIconLabel, public sigc::trackable {
public: public:
Window(const std::string&, const waybar::Bar&, const Json::Value&); Window(const std::string&, const waybar::Bar&, const Json::Value&);
~Window() = default; ~Window() = default;
@ -22,6 +23,8 @@ class Window : public ALabel, public sigc::trackable {
std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes, std::tuple<std::size_t, int, std::string, std::string> getFocusedNode(const Json::Value& nodes,
std::string& output); std::string& output);
void getTree(); void getTree();
std::string rewriteTitle(const std::string& title);
void updateAppIcon();
const Bar& bar_; const Bar& bar_;
std::string window_; std::string window_;

View File

@ -3,11 +3,14 @@
#include "AModule.hpp" #include "AModule.hpp"
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "giomm/desktopappinfo.h"
#include "util/json.hpp" #include "util/json.hpp"
#include <memory> #include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map>
#include <unordered_set>
#include <gdk/gdk.h> #include <gdk/gdk.h>
@ -60,14 +63,18 @@ class Task
Gtk::Image icon_; Gtk::Image icon_;
Gtk::Label text_before_; Gtk::Label text_before_;
Gtk::Label text_after_; Gtk::Label text_after_;
bool button_visible_; Glib::RefPtr<Gio::DesktopAppInfo> app_info_;
bool button_visible_ = false;
bool ignored_ = false;
bool with_icon_; bool with_icon_ = false;
bool with_name_ = false;
std::string format_before_; std::string format_before_;
std::string format_after_; std::string format_after_;
std::string format_tooltip_; std::string format_tooltip_;
std::string name_;
std::string title_; std::string title_;
std::string app_id_; std::string app_id_;
uint32_t state_ = 0; uint32_t state_ = 0;
@ -75,6 +82,9 @@ class Task
private: private:
std::string repr() const; std::string repr() const;
std::string state_string(bool = false) const; std::string state_string(bool = false) const;
void set_app_info_from_app_id_list(const std::string& app_id_list);
bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size);
void hide_if_ignored();
public: public:
/* Getter functions */ /* Getter functions */
@ -132,6 +142,8 @@ class Taskbar : public waybar::AModule
std::vector<TaskPtr> tasks_; std::vector<TaskPtr> tasks_;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_; std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes_;
std::unordered_set<std::string> ignore_list_;
std::map<std::string, std::string> app_ids_replace_map_;
struct zwlr_foreign_toplevel_manager_v1 *manager_; struct zwlr_foreign_toplevel_manager_v1 *manager_;
struct wl_seat *seat_; struct wl_seat *seat_;
@ -154,7 +166,9 @@ class Taskbar : public waybar::AModule
bool show_output(struct wl_output *) const; bool show_output(struct wl_output *) const;
bool all_outputs() const; bool all_outputs() const;
std::vector<Glib::RefPtr<Gtk::IconTheme>> icon_themes() const; const std::vector<Glib::RefPtr<Gtk::IconTheme>>& icon_themes() const;
const std::unordered_set<std::string>& ignore_list() const;
const std::map<std::string, std::string>& app_ids_replace_map() const;
}; };
} /* namespace waybar::modules::wlr */ } /* namespace waybar::modules::wlr */

View File

@ -0,0 +1,160 @@
#pragma once
#include <fmt/format.h>
#include <gtkmm/button.h>
#include <gtkmm/image.h>
#include <gtkmm/label.h>
#include <functional>
#include <map>
#include <memory>
#include <vector>
#include "AModule.hpp"
#include "bar.hpp"
#include "ext-workspace-unstable-v1-client-protocol.h"
namespace waybar::modules::wlr {
class WorkspaceManager;
class WorkspaceGroup;
class Workspace {
public:
Workspace(const waybar::Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
zext_workspace_handle_v1 *workspace, uint32_t id);
~Workspace();
auto update() -> void;
auto id() const -> uint32_t { return id_; }
auto is_active() const -> bool { return state_ & static_cast<uint32_t>(State::ACTIVE); }
auto is_urgent() const -> bool { return state_ & static_cast<uint32_t>(State::URGENT); }
auto is_hidden() const -> bool { return state_ & static_cast<uint32_t>(State::HIDDEN); }
// wlr stuff
auto handle_name(const std::string &name) -> void;
auto handle_coordinates(const std::vector<uint32_t> &coordinates) -> void;
auto handle_state(const std::vector<uint32_t> &state) -> void;
auto handle_remove() -> void;
auto handle_done() -> void;
auto handle_clicked(GdkEventButton *bt) -> bool;
auto show() -> void;
auto hide() -> void;
auto get_button_ref() -> Gtk::Button & { return button_; }
auto get_name() -> std::string & { return name_; }
auto get_coords() -> std::vector<uint32_t> & { return coordinates_; }
enum class State {
ACTIVE = (1 << 0),
URGENT = (1 << 1),
HIDDEN = (1 << 2),
};
private:
auto get_icon() -> std::string;
const Bar &bar_;
const Json::Value &config_;
WorkspaceGroup &workspace_group_;
// wlr stuff
zext_workspace_handle_v1 *workspace_handle_;
uint32_t state_ = 0;
uint32_t id_;
std::string name_;
std::vector<uint32_t> coordinates_;
static std::map<std::string, std::string> icons_map_;
std::string format_;
bool with_icon_ = false;
Gtk::Button button_;
Gtk::Box content_;
Gtk::Label label_;
};
class WorkspaceGroup {
public:
WorkspaceGroup(const waybar::Bar &bar, Gtk::Box &box, const Json::Value &config,
WorkspaceManager &manager, zext_workspace_group_handle_v1 *workspace_group_handle,
uint32_t id);
~WorkspaceGroup();
auto update() -> void;
auto id() const -> uint32_t { return id_; }
auto is_visible() const -> bool;
auto remove_workspace(uint32_t id_) -> void;
auto active_only() const -> bool;
auto creation_delayed() const -> bool;
auto workspaces() -> std::vector<std::unique_ptr<Workspace>> & { return workspaces_; }
auto sort_workspaces() -> void;
auto set_need_to_sort() -> void { need_to_sort = true; }
auto add_button(Gtk::Button &button) -> void;
auto remove_button(Gtk::Button &button) -> void;
// wlr stuff
auto handle_workspace_create(zext_workspace_handle_v1 *workspace_handle) -> void;
auto handle_remove() -> void;
auto handle_output_enter(wl_output *output) -> void;
auto handle_output_leave() -> void;
auto handle_done() -> void;
auto commit() -> void;
private:
static uint32_t workspace_global_id;
const waybar::Bar &bar_;
Gtk::Box &box_;
const Json::Value &config_;
WorkspaceManager &workspace_manager_;
// wlr stuff
zext_workspace_group_handle_v1 *workspace_group_handle_;
wl_output *output_ = nullptr;
uint32_t id_;
std::vector<std::unique_ptr<Workspace>> workspaces_;
bool need_to_sort = false;
};
class WorkspaceManager : public AModule {
public:
WorkspaceManager(const std::string &id, const waybar::Bar &bar, const Json::Value &config);
~WorkspaceManager() override;
auto update() -> void override;
auto all_outputs() const -> bool { return all_outputs_; }
auto active_only() const -> bool { return active_only_; }
auto workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)>;
auto creation_delayed() const -> bool { return creation_delayed_; }
auto sort_workspaces() -> void;
auto remove_workspace_group(uint32_t id_) -> void;
// wlr stuff
auto register_manager(wl_registry *registry, uint32_t name, uint32_t version) -> void;
auto handle_workspace_group_create(zext_workspace_group_handle_v1 *workspace_group_handle)
-> void;
auto handle_done() -> void;
auto handle_finished() -> void;
auto commit() -> void;
private:
const waybar::Bar &bar_;
Gtk::Box box_;
std::vector<std::unique_ptr<WorkspaceGroup>> groups_;
// wlr stuff
zext_workspace_manager_v1 *workspace_manager_ = nullptr;
static uint32_t group_global_id;
bool sort_by_name_ = true;
bool sort_by_coordinates_ = true;
bool all_outputs_ = false;
bool active_only_ = false;
bool creation_delayed_ = false;
};
} // namespace waybar::modules::wlr

View File

@ -0,0 +1,8 @@
#include "ext-workspace-unstable-v1-client-protocol.h"
namespace waybar::modules::wlr {
void add_registry_listener(void *data);
void add_workspace_listener(zext_workspace_handle_v1 *workspace_handle, void *data);
void add_workspace_group_listener(zext_workspace_group_handle_v1 *workspace_group_handle, void *data);
zext_workspace_manager_v1* workspace_manager_bind(wl_registry *registry, uint32_t name, uint32_t version, void *data);
}

View File

@ -0,0 +1,75 @@
#pragma once
#include <glibmm/dispatcher.h>
#include <sigc++/signal.h>
#include <functional>
#include <mutex>
#include <queue>
#include <thread>
#include <tuple>
#include <type_traits>
#include <utility>
namespace waybar {
/**
* Thread-safe signal wrapper.
* Uses Glib::Dispatcher to pass events to another thread and locked queue to pass the arguments.
*/
template <typename... Args>
struct SafeSignal : sigc::signal<void(std::decay_t<Args>...)> {
public:
SafeSignal() { dp_.connect(sigc::mem_fun(*this, &SafeSignal::handle_event)); }
template <typename... EmitArgs>
void emit(EmitArgs&&... args) {
if (main_tid_ == std::this_thread::get_id()) {
/*
* Bypass the queue if the method is called the main thread.
* Ensures that events emitted from the main thread are processed synchronously and saves a
* few CPU cycles on locking/queuing.
* As a downside, this makes main thread events prioritized over the other threads and
* disrupts chronological order.
*/
signal_t::emit(std::forward<EmitArgs>(args)...);
} else {
{
std::unique_lock lock(mutex_);
queue_.emplace(std::forward<EmitArgs>(args)...);
}
dp_.emit();
}
}
template <typename... EmitArgs>
inline void operator()(EmitArgs&&... args) {
emit(std::forward<EmitArgs>(args)...);
}
protected:
using signal_t = sigc::signal<void(std::decay_t<Args>...)>;
using slot_t = decltype(std::declval<signal_t>().make_slot());
using arg_tuple_t = std::tuple<std::decay_t<Args>...>;
// ensure that unwrapped methods are not accessible
using signal_t::emit_reverse;
using signal_t::make_slot;
void handle_event() {
for (std::unique_lock lock(mutex_); !queue_.empty(); lock.lock()) {
auto args = queue_.front();
queue_.pop();
lock.unlock();
std::apply(cached_fn_, args);
}
}
Glib::Dispatcher dp_;
std::mutex mutex_;
std::queue<arg_tuple_t> queue_;
const std::thread::id main_tid_ = std::this_thread::get_id();
// cache functor for signal emission to avoid recreating it on each event
const slot_t cached_fn_ = make_slot();
};
} // namespace waybar

View File

@ -68,7 +68,10 @@ inline int close(FILE* fp, pid_t pid) {
inline FILE* open(const std::string& cmd, int& pid) { inline FILE* open(const std::string& cmd, int& pid) {
if (cmd == "") return nullptr; if (cmd == "") return nullptr;
int fd[2]; int fd[2];
pipe(fd); if (pipe(fd) != 0){
spdlog::error("Unable to pipe fd");
return nullptr;
}
pid_t child_pid = fork(); pid_t child_pid = fork();

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <fmt/format.h> #include <fmt/format.h>
#include <glibmm/ustring.h>
class pow_format { class pow_format {
public: public:
@ -35,7 +36,11 @@ namespace fmt {
// The rationale for ignoring it is that the only reason to specify // The rationale for ignoring it is that the only reason to specify
// an alignment and a with is to get a fixed width bar, and ">" is // an alignment and a with is to get a fixed width bar, and ">" is
// sufficient in this implementation. // sufficient in this implementation.
#if FMT_VERSION < 80000
width = parse_nonnegative_int(it, end, ctx); width = parse_nonnegative_int(it, end, ctx);
#else
width = detail::parse_nonnegative_int(it, end, -1);
#endif
} }
return it; return it;
} }
@ -80,5 +85,15 @@ namespace fmt {
); );
} }
}; };
// Glib ustirng support
template <>
struct formatter<Glib::ustring> : formatter<std::string> {
template <typename FormatContext>
auto format(const Glib::ustring& value, FormatContext& ctx) {
return formatter<std::string>::format(value, ctx);
}
};
} }

View File

@ -8,6 +8,20 @@
namespace waybar::util { namespace waybar::util {
/**
* Defer pthread_cancel until the end of a current scope.
*
* Required to protect a scope where it's unsafe to raise `__forced_unwind` exception.
* An example of these is a call of a method marked as `noexcept`; an attempt to cancel within such
* a method may result in a `std::terminate` call.
*/
class CancellationGuard {
int oldstate;
public:
CancellationGuard() { pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); }
~CancellationGuard() { pthread_setcancelstate(oldstate, &oldstate); }
};
class SleeperThread { class SleeperThread {
public: public:
SleeperThread() = default; SleeperThread() = default;
@ -33,14 +47,16 @@ class SleeperThread {
bool isRunning() const { return do_run_; } bool isRunning() const { return do_run_; }
auto sleep_for(std::chrono::system_clock::duration dur) { auto sleep_for(std::chrono::system_clock::duration dur) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock;
return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; }); return condvar_.wait_for(lk, dur, [this] { return signal_ || !do_run_; });
} }
auto sleep_until( auto sleep_until(
std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration> std::chrono::time_point<std::chrono::system_clock, std::chrono::system_clock::duration>
time_point) { time_point) {
std::unique_lock lk(mutex_); std::unique_lock lk(mutex_);
CancellationGuard cancel_lock;
return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; }); return condvar_.wait_until(lk, time_point, [this] { return signal_ || !do_run_; });
} }

17
include/util/string.hpp Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <string>
const std::string WHITESPACE = " \n\r\t\f\v";
inline std::string ltrim(const std::string& s) {
size_t begin = s.find_first_not_of(WHITESPACE);
return (begin == std::string::npos) ? "" : s.substr(begin);
}
inline std::string rtrim(const std::string& s) {
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
inline std::string trim(const std::string& s) { return rtrim(ltrim(s)); }

View File

@ -0,0 +1,39 @@
#pragma once
#include <date/tz.h>
#include <fmt/format.h>
namespace waybar {
struct waybar_time {
std::locale locale;
date::zoned_seconds ztime;
};
} // namespace waybar
template <>
struct fmt::formatter<waybar::waybar_time> {
std::string_view specs;
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
auto it = ctx.begin();
if (it != ctx.end() && *it == ':') {
++it;
}
auto end = it;
while (end != ctx.end() && *end != '}') {
++end;
}
if (end != it) {
specs = {it, std::string_view::size_type(end - it)};
}
return end;
}
template <typename FormatContext>
auto format(const waybar::waybar_time& t, FormatContext& ctx) {
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(specs), t.ztime));
}
};

View File

@ -120,7 +120,7 @@ The two arguments are:
# CUSTOM FORMATS # CUSTOM FORMATS
The *battery* module allows to define custom formats based on up to two factors. The best fitting format will be selected. The *battery* module allows one to define custom formats based on up to two factors. The best fitting format will be selected.
*format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery. *format-<state>*: With *states*, a custom format can be set depending on the capacity of your battery.

View File

@ -24,7 +24,8 @@ The *clock* module displays the current date and time.
*timezone*: ++ *timezone*: ++
typeof: string ++ typeof: string ++
default: inferred local timezone ++ default: inferred local timezone ++
The timezone to display the time in, e.g. America/New_York. The timezone to display the time in, e.g. America/New_York. ++
This field will be ignored if *timezones* field is set and have at least one value.
*timezones*: ++ *timezones*: ++
typeof: list of strings ++ typeof: list of strings ++
@ -85,11 +86,17 @@ The *clock* module displays the current date and time.
typeof: double ++ typeof: double ++
Threshold to be used when scrolling. Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
View all valid format options in *strftime(3)*. View all valid format options in *strftime(3)*.
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{calendar}*: Current month calendar *{calendar}*: Current month calendar
*{timezoned_time_list}*: List of time in the rest timezones, if more than one timezone is set in the config
# EXAMPLES # EXAMPLES

View File

@ -20,6 +20,11 @@ The *cpu* module displays the current cpu utilization.
default: {usage}% ++ default: {usage}% ++
The format, how information should be displayed. On {} data gets inserted. The format, how information should be displayed. On {} data gets inserted.
*format-icons*: ++
typeof: array/object ++
Based on the current usage, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*max-length*: ++ *max-length*: ++
typeof: integer ++ typeof: integer ++
The maximum length in character the module should display. The maximum length in character the module should display.
@ -77,7 +82,9 @@ The *cpu* module displays the current cpu utilization.
*{load}*: Current cpu load. *{load}*: Current cpu load.
*{usage}*: Current cpu usage. *{usage}*: Current overall cpu usage.
*{usage*{n}*}*: Current cpu core n usage. Cores are numbered from zero, so first core will be {usage0} and 4th will be {usage3}.
*{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz. *{avg_frequency}*: Current cpu average frequency (based on all cores) in GHz.
@ -85,7 +92,13 @@ The *cpu* module displays the current cpu utilization.
*{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz. *{min_frequency}*: Current cpu min frequency (based on the core with the lowest frequency) in GHz.
# EXAMPLE *{icon}*: Icon for overall cpu usage.
*{icon*{n}*}*: Icon for cpu core n usage. Use like {icon0}.
# EXAMPLES
Basic configuration:
``` ```
"cpu": { "cpu": {
@ -95,6 +108,16 @@ The *cpu* module displays the current cpu utilization.
} }
``` ```
Cpu usage per core rendered as icons:
```
"cpu": {
"interval": 1,
"format": "{icon0}{icon1}{icon2}{icon3} {usage:>2}% ",
"format-icons": ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"],
},
```
# STYLE # STYLE
- *#cpu* - *#cpu*

View File

@ -151,7 +151,8 @@ $text\\n$tooltip\\n$class*
"max-length": 40, "max-length": 40,
"interval": 30, // Remove this if your script is endless and write in loop "interval": 30, // Remove this if your script is endless and write in loop
"exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder "exec": "$HOME/.config/waybar/mediaplayer.sh 2> /dev/null", // Script in resources folder
"exec-if": "pgrep spotify" "exec-if": "pgrep spotify",
"return-type": "json"
} }
``` ```

View File

@ -0,0 +1,92 @@
waybar-inhibitor(5)
# NAME
waybar - inhibitor module
# DESCRIPTION
The *inhibitor* module allows to take an inhibitor lock that logind provides.
See *systemd-inhibit*(1) for more information.
# CONFIGURATION
*what*: ++
typeof: string or array ++
The inhibitor lock or locks that should be taken when active. The available inhibitor locks are *idle*, *shutdown*, *sleep*, *handle-power-key*, *handle-suspend-key*, *handle-hibernate-key* and *handle-lid-switch*.
*format*: ++
typeof: string ++
The format, how the state should be displayed.
*format-icons*: ++
typeof: array ++
Based on the current state, the corresponding icon gets selected.
*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. A click also toggles the state
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++
typeof: bool ++
default: true ++
Option to disable tooltip on hover.
# FORMAT REPLACEMENTS
*{status}*: status (*activated* or *deactivated*)
*{icon}*: Icon, as defined in *format-icons*
# EXAMPLES
```
"inhibitor": {
"what": "handle-lid-switch",
"format": "{icon}",
"format-icons": {
"activated": "",
"deactivated": ""
}
}
```

View File

@ -0,0 +1,82 @@
waybar-keyboard-state(5)
# NAME
waybar - keyboard-state module
# DESCRIPTION
The *keyboard-state* module displays the state of number lock, caps lock, and scroll lock.
You must be a member of the input group to use this module.
# CONFIGURATION
*interval*: ++
typeof: integer ++
default: 1 ++
The interval, in seconds, to poll the keyboard state.
*format*: ++
typeof: string|object ++
default: {name} {icon} ++
The format, how information should be displayed. If a string, the same format is used for all keyboard states. If an object, the fields "numlock", "capslock", and "scrolllock" each specify the format for the corresponding state. Any unspecified states use the default format.
*format-icons*: ++
typeof: object ++
default: {"locked": "locked", "unlocked": "unlocked"} ++
Based on the keyboard state, the corresponding icon gets selected. The same set of icons is used for number, caps, and scroll lock, but the icon is selected from the set independently for each. See *icons*.
*numlock*: ++
typeof: bool ++
default: false ++
Display the number lock state.
*capslock*: ++
typeof: bool ++
default: false ++
Display the caps lock state.
*scrolllock*: ++
typeof: bool ++
default: false ++
Display the scroll lock state.
*device-path*: ++
typeof: string ++
default: chooses first valid input device ++
Which libevdev input device to show the state of. Libevdev devices can be found in /dev/input. The device should support number lock, caps lock, and scroll lock events.
# FORMAT REPLACEMENTS
*{name}*: Caps, Num, or Scroll.
*{icon}*: Icon, as defined in *format-icons*.
# ICONS
The following *format-icons* can be set.
- *locked*: Will be shown when the keyboard state is locked. Default "locked".
- *unlocked*: Will be shown when the keyboard state is not locked. Default "unlocked"
# EXAMPLE:
```
"keyboard-state": {
"numlock": true,
"capslock": true,
"format": "{name} {icon}",
"format-icons": {
"locked": "",
"unlocked": ""
}
}
```
# STYLE
- *#keyboard-state*
- *#keyboard-state label*
- *#keyboard-state label.locked*

View File

@ -22,6 +22,11 @@ Addressed by *memory*
default: {percentage}% ++ default: {percentage}% ++
The format, how information should be displayed. The format, how information should be displayed.
*format-icons*: ++
typeof: array/object ++
Based on the current percentage, the corresponding icon gets selected. ++
The order is *low* to *high*. Or by the state if it is an object.
*rotate*: ++ *rotate*: ++
typeof: integer ++ typeof: integer ++
Positive value to rotate the text label. Positive value to rotate the text label.
@ -79,12 +84,20 @@ Addressed by *memory*
*{percentage}*: Percentage of memory in use. *{percentage}*: Percentage of memory in use.
*{swapPercentage}*: Percentage of swap in use.
*{total}*: Amount of total memory available in GiB. *{total}*: Amount of total memory available in GiB.
*{swapTotal}*: Amount of total swap available in GiB.
*{used}*: Amount of used memory in GiB. *{used}*: Amount of used memory in GiB.
*{swapUsed}*: Amount of used swap in GiB.
*{avail}*: Amount of available memory in GiB. *{avail}*: Amount of available memory in GiB.
*{swapAvail}*: Amount of available swap in GiB.
# EXAMPLES # EXAMPLES
``` ```

View File

@ -172,6 +172,8 @@ Addressed by *mpd*
*{date}*: The date of the current song *{date}*: The date of the current song
*{volume}*: The current volume in percent
*{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration) *{elapsedTime}*: The current position of the current song. To format as a date/time (see example configuration)
*{totalTime}*: The length of the current song. To format as a date/time (see example configuration) *{totalTime}*: The length of the current song. To format as a date/time (see example configuration)

View File

@ -131,6 +131,8 @@ Addressed by *network*
*{ipaddr}*: The first IP of the interface. *{ipaddr}*: The first IP of the interface.
*{gwaddr}*: The default gateway for the interface
*{netmask}*: The subnetmask corresponding to the IP. *{netmask}*: The subnetmask corresponding to the IP.
*{cidr}*: The subnetmask corresponding to the IP in CIDR notation. *{cidr}*: The subnetmask corresponding to the IP in CIDR notation.

View File

@ -109,6 +109,9 @@ Additionally you can control the volume by scrolling *up* or *down* while the cu
# ICONS: # ICONS:
The following strings for *format-icons* are supported. The following strings for *format-icons* are supported.
- the device name
If they are found in the current PulseAudio port name, the corresponding icons will be selected. If they are found in the current PulseAudio port name, the corresponding icons will be selected.
- *default* (Shown, when no other port is found) - *default* (Shown, when no other port is found)
@ -131,6 +134,7 @@ If they are found in the current PulseAudio port name, the corresponding icons w
"format-bluetooth": "{volume}% {icon}", "format-bluetooth": "{volume}% {icon}",
"format-muted": "", "format-muted": "",
"format-icons": { "format-icons": {
"alsa_output.pci-0000_00_1f.3.analog-stereo": "",
"headphones": "", "headphones": "",
"handsfree": "", "handsfree": "",
"headset": "", "headset": "",

View File

@ -15,12 +15,17 @@ Addressed by *river/tags*
*num-tags*: ++ *num-tags*: ++
typeof: uint ++ typeof: uint ++
default: 9 ++ default: 9 ++
The number of tags that should be displayed. The number of tags that should be displayed. Max 32.
*tag-labels*: ++ *tag-labels*: ++
typeof: array ++ typeof: array ++
The label to display for each tag. The label to display for each tag.
*disable-click*: ++
typeof: bool ++
default: false ++
If set to false, you can left click to set focused tag. Right click to toggle tag focus. If set to true this behaviour is disabled.
# EXAMPLE # EXAMPLE
``` ```
@ -34,8 +39,10 @@ Addressed by *river/tags*
- *#tags button* - *#tags button*
- *#tags button.occupied* - *#tags button.occupied*
- *#tags button.focused* - *#tags button.focused*
- *#tags button.urgent*
Note that a tag can be both occupied and focused at the same time. Note that occupied/focused/urgent status may overlap. That is, a tag may be
both occupied and focused at the same time.
# SEE ALSO # SEE ALSO

View File

@ -15,63 +15,39 @@ Addressed by *sway/language*
*format*: ++ *format*: ++
typeof: string ++ typeof: string ++
default: {} ++ default: {} ++
The format, how information should be displayed. On {} data gets inserted. The format, how layout should be displayed.
*rotate*: ++ *tooltip-format*: ++
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 ++ typeof: string ++
Command to execute when clicked on the module. default: {} ++
The format, how layout should be displayed in tooltip.
*on-click-middle*: ++
typeof: string ++
Command to execute when middle-clicked on the module using mousewheel.
*on-click-right*: ++
typeof: string ++
Command to execute when you right clicked on the module.
*on-update*: ++
typeof: string ++
Command to execute when the module is updated.
*on-scroll-up*: ++
typeof: string ++
Command to execute when scrolling up on the module.
*on-scroll-down*: ++
typeof: string ++
Command to execute when scrolling down on the module.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*tooltip*: ++ *tooltip*: ++
typeof: bool ++ typeof: bool ++
default: true ++ default: true ++
Option to disable tooltip on hover. Option to disable tooltip on hover.
# FORMAT REPLACEMENTS
*{short}*: Short name of layout (e.g. "us"). Equals to {}.
*{shortDescription}*: Short description of layout (e.g. "en").
*{long}*: Long name of layout (e.g. "English (Dvorak)").
*{variant}*: Variant of layout (e.g. "dvorak").
*{flag}*: Country flag of layout.
# EXAMPLES # EXAMPLES
``` ```
"sway/language": { "sway/language": {
"format": "{}", "format": "{}",
"max-length": 50 },
"sway/language": {
"format": "{short} {variant}",
} }
``` ```

View File

@ -69,7 +69,7 @@ Addressed by *sway/mode*
# EXAMPLES # EXAMPLES
``` ```
"sway/window": { "sway/mode": {
"format": " {}", "format": " {}",
"max-length": 50 "max-length": 50
} }

View File

@ -66,12 +66,37 @@ Addressed by *sway/window*
default: true ++ default: true ++
Option to disable tooltip on hover. Option to disable tooltip on hover.
*rewrite*: ++
typeof: object ++
Rules to rewrite window title. See *rewrite rules*.
*icon*: ++
typeof: bool ++
default: false ++
Option to hide the application icon.
# 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 # EXAMPLES
``` ```
"sway/window": { "sway/window": {
"format": "{}", "format": "{}",
"max-length": 50 "max-length": 50,
"rewrite": {
"(.*) - Mozilla Firefox": "🌎 $1",
"(.*) - zsh": "> [$1]"
}
} }
``` ```

View File

@ -69,10 +69,6 @@ Addressed by *sway/workspaces*
typeof: string ++ typeof: string ++
Command to execute when the module is updated. Command to execute when the module is updated.
*numeric-first*: ++
typeof: bool ++
Whether to put workspaces starting with numbers before workspaces that do not start with a number.
*disable-auto-back-and-forth*: ++ *disable-auto-back-and-forth*: ++
typeof: bool ++ typeof: bool ++
Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration. Whether to disable *workspace_auto_back_and_forth* when clicking on workspaces. If this is set to *true*, clicking on a workspace you are already on won't do anything, even if *workspace_auto_back_and_forth* is enabled in the Sway configuration.
@ -120,7 +116,6 @@ n.b.: the list of outputs can be obtained from command line using *swaymsg -t ge
"sway/workspaces": { "sway/workspaces": {
"disable-scroll": true, "disable-scroll": true,
"all-outputs": true, "all-outputs": true,
"numeric-first": false,
"format": "{name}: {icon}", "format": "{name}: {icon}",
"format-icons": { "format-icons": {
"1": "", "1": "",

View File

@ -16,10 +16,23 @@ Addressed by *tray*
typeof: integer ++ typeof: integer ++
Defines the size of the tray icons. Defines the size of the tray icons.
*show-passive-items*: ++
typeof: bool ++
default: false ++
Defines visibility of the tray icons with *Passive* status.
*smooth-scrolling-threshold*: ++
typeof: double ++
Threshold to be used when scrolling.
*spacing*: ++ *spacing*: ++
typeof: integer ++ typeof: integer ++
Defines the spacing between the tray icons. Defines the spacing between the tray icons.
*reverse-direction*: ++
typeof: bool ++
Defines if new app icons should be added in a reverse order
*on-update*: ++ *on-update*: ++
typeof: string ++ typeof: string ++
Command to execute when the module is updated. Command to execute when the module is updated.
@ -37,3 +50,6 @@ Addressed by *tray*
# STYLE # STYLE
- *#tray* - *#tray*
- *#tray > .passive*
- *#tray > .active*
- *#tray > .needs-attention*

View File

@ -68,10 +68,20 @@ Addressed by *wlr/taskbar*
typeof: string ++ typeof: string ++
Command to execute when the module is updated. Command to execute when the module is updated.
*ignore-list*: ++
typeof: array ++
List of app_id/titles to be invisible.
*app_ids-mapping*: ++
typeof: object ++
Dictionary of app_id to be replaced with
# FORMAT REPLACEMENTS # FORMAT REPLACEMENTS
*{icon}*: The icon of the application. *{icon}*: The icon of the application.
*{title}*: The application name as in desktop file if appropriate desktop fils found, otherwise same as {app_id}
*{title}*: The title of the application. *{title}*: The title of the application.
*{app_id}*: The app_id (== application name) of the application. *{app_id}*: The app_id (== application name) of the application.
@ -83,10 +93,15 @@ Addressed by *wlr/taskbar*
# CLICK ACTIONS # CLICK ACTIONS
*activate*: Bring the application into foreground. *activate*: Bring the application into foreground.
*minimize*: Toggle application's minimized state. *minimize*: Toggle application's minimized state.
*minimize-raise*: Bring the application into foreground or toggle its minimized state. *minimize-raise*: Bring the application into foreground or toggle its minimized state.
*maximize*: Toggle application's maximized state. *maximize*: Toggle application's maximized state.
*fullscreen*: Toggle application's fullscreen state. *fullscreen*: Toggle application's fullscreen state.
*close*: Close the application. *close*: Close the application.
# EXAMPLES # EXAMPLES
@ -98,7 +113,13 @@ Addressed by *wlr/taskbar*
"icon-theme": "Numix-Circle", "icon-theme": "Numix-Circle",
"tooltip-format": "{title}", "tooltip-format": "{title}",
"on-click": "activate", "on-click": "activate",
"on-click-middle": "close" "on-click-middle": "close",
"ignore-list": [
"Alacritty"
],
"app_ids-mapping": {
"firefoxdeveloperedition": "firefox-developer-edition"
}
} }
``` ```

View File

@ -0,0 +1,88 @@
waybar-wlr-workspaces(5)
# NAME
waybar - wlr workspaces module
# DESCRIPTION
The *workspaces* module displays the currently used workspaces in wayland compositor.
# CONFIGURATION
Addressed by *wlr/workspaces*
*format*: ++
typeof: string ++
default: {name} ++
The format, how information should be displayed.
*format-icons*: ++
typeof: array ++
Based on the workspace name and state, the corresponding icon gets selected. See *icons*.
*sort-by-name*: ++
typeof: bool ++
default: true ++
Should workspaces be sorted by name.
*sort-by-coordinates*: ++
typeof: bool ++
default: true ++
Should workspaces be sorted by coordinates.
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.
*all-outputs*: ++
typeof: bool ++
default: false ++
If set to false workspaces group will be shown only in assigned output. Otherwise all workspace groups are shown.
*active-only*: ++
typeof: bool ++
default: false ++
If set to true only active or urgent workspaces will be shown.
# FORMAT REPLACEMENTS
*{name}*: Name of workspace assigned by compositor
*{icon}*: Icon, as defined in *format-icons*.
# CLICK ACTIONS
*activate*: Switch to workspace.
*close*: Close the workspace.
# ICONS
Additional to workspace name matching, the following *format-icons* can be set.
- *default*: Will be shown, when no string match is found.
- *focused*: Will be shown, when workspace is focused
# EXAMPLES
```
"wlr/workspaces": {
"format": "{name}: {icon}",
"format-icons": {
"1": "",
"2": "",
"3": "",
"4": "",
"5": "",
"focused": "",
"default": ""
}
}
```
# Style
- *#workspaces*
- *#workspaces button*
- *#workspaces button.active*
- *#workspaces button.urgent*
- *#workspaces button.hidden*

View File

@ -64,16 +64,58 @@ Also a minimal example configuration can be found on the at the bottom of this m
typeof: integer ++ typeof: integer ++
Margins value without units. Margins value without units.
*spacing* ++
typeof: integer ++
Size of gaps in between of the different modules.
*name* ++ *name* ++
typeof: string ++ typeof: string ++
Optional name added as a CSS class, for styling multiple waybars. Optional name added as a CSS class, for styling multiple waybars.
*mode* ++
typeof: string ++
Selects one of the preconfigured display modes. This is an equivalent of the sway-bar(5) *mode* command and supports the same values: *dock*, *hide*, *invisible*, *overlay*. ++
Note: *hide* and *invisible* modes may be not as useful without Sway IPC.
*exclusive* ++
typeof: bool ++
default: *true* ++
Option to request an exclusive zone from the compositor. Disable this to allow drawing application windows underneath or on top of the bar.
*fixed-center* ++
typeof: bool ++
default: *true*
Prefer fixed center position for the `modules-center` block. The center block will stay in the middle of the bar whenever possible. It can still be pushed around if other blocks need more space.
When false, the center block is centered in the space between the left and right block.
*passthrough* ++
typeof: bool ++
default: *false* ++
Option to pass any pointer events to the window under the bar.
Intended to be used with either *top* or *overlay* layers and without exclusive zone.
*gtk-layer-shell* ++ *gtk-layer-shell* ++
typeof: bool ++ typeof: bool ++
default: true ++ default: true ++
Option to disable the use of gtk-layer-shell for popups. Option to disable the use of gtk-layer-shell for popups.
Only functional if compiled with gtk-layer-shell support. Only functional if compiled with gtk-layer-shell support.
*ipc* ++
typeof: bool ++
default: false ++
Option to subscribe to the Sway IPC bar configuration and visibility events and control waybar with *swaymsg bar* commands. ++
Requires *bar_id* value from sway configuration to be either passed with the *-b* commandline argument or specified with the *id* option.
*id* ++
typeof: string ++
*bar_id* for the Sway IPC. Use this if you need to override the value passed with the *-b bar_id* commandline argument for the specific bar instance.
*include* ++
typeof: string|array ++
Paths to additional configuration files.
Each file can contain a single object with any of the bar configuration options. In case of duplicate options, the first defined value takes precedence, i.e. including file -> first included file -> etc. Nested includes are permitted, but make sure to avoid circular imports.
For a multi-bar config, the include directive affects only current bar configuration object.
# MODULE FORMAT # MODULE FORMAT
You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat). You can use PangoMarkupFormat (See https://developer.gnome.org/pango/stable/PangoMarkupFormat.html#PangoMarkupFormat).
@ -182,6 +224,28 @@ When positioning Waybar on the left or right side of the screen, sometimes it's
Valid options for the "rotate" property are: 0, 90, 180 and 270. Valid options for the "rotate" property are: 0, 90, 180 and 270.
## Grouping modules
Module groups allow stacking modules in the direction orthogonal to the bar direction. When the bar is positioned on the top or bottom of the screen, modules in a group are stacked vertically. Likewise, when positioned on the left or right, modules in a group are stacked horizontally.
A module group is defined by specifying a module named "group/some-group-name". The group must also be configured with a list of contained modules. Example:
```
{
"modules-right": ["group/hardware", "clock"],
"group/hardware": {
"modules": [
"cpu",
"memory",
"battery"
]
},
...
}
```
# SUPPORTED MODULES # SUPPORTED MODULES
- *waybar-backlight(5)* - *waybar-backlight(5)*
@ -192,6 +256,7 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-custom(5)* - *waybar-custom(5)*
- *waybar-disk(5)* - *waybar-disk(5)*
- *waybar-idle-inhibitor(5)* - *waybar-idle-inhibitor(5)*
- *waybar-keyboard-state(5)*
- *waybar-memory(5)* - *waybar-memory(5)*
- *waybar-mpd(5)* - *waybar-mpd(5)*
- *waybar-network(5)* - *waybar-network(5)*
@ -202,5 +267,6 @@ Valid options for the "rotate" property are: 0, 90, 180 and 270.
- *waybar-sway-window(5)* - *waybar-sway-window(5)*
- *waybar-sway-workspaces(5)* - *waybar-sway-workspaces(5)*
- *waybar-wlr-taskbar(5)* - *waybar-wlr-taskbar(5)*
- *waybar-wlr-workspaces(5)*
- *waybar-temperature(5)* - *waybar-temperature(5)*
- *waybar-tray(5)* - *waybar-tray(5)*

View File

@ -1,6 +1,6 @@
project( project(
'waybar', 'cpp', 'c', 'waybar', 'cpp', 'c',
version: '0.9.6', version: '0.9.11',
license: 'MIT', license: 'MIT',
meson_version: '>= 0.49.0', meson_version: '>= 0.49.0',
default_options : [ default_options : [
@ -79,14 +79,14 @@ is_netbsd = host_machine.system() == 'netbsd'
is_openbsd = host_machine.system() == 'openbsd' is_openbsd = host_machine.system() == 'openbsd'
thread_dep = dependency('threads') thread_dep = dependency('threads')
fmt = dependency('fmt', version : ['>=5.3.0'], fallback : ['fmt', 'fmt_dep']) fmt = dependency('fmt', version : ['>=7.0.0'], fallback : ['fmt', 'fmt_dep'])
spdlog = dependency('spdlog', version : ['>=1.8.0'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true']) spdlog = dependency('spdlog', version : ['>=1.8.5'], fallback : ['spdlog', 'spdlog_dep'], default_options : ['external_fmt=true'])
wayland_client = dependency('wayland-client') wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor') wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0']) gtkmm = dependency('gtkmm-3.0', version : ['>=3.22.0'])
dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk')) dbusmenu_gtk = dependency('dbusmenu-gtk3-0.4', required: get_option('dbusmenu-gtk'))
giounix = dependency('gio-unix-2.0', required: get_option('dbusmenu-gtk')) giounix = dependency('gio-unix-2.0', required: (get_option('dbusmenu-gtk').enabled() or get_option('logind').enabled()))
jsoncpp = dependency('jsoncpp') jsoncpp = dependency('jsoncpp')
sigcpp = dependency('sigc++-2.0') sigcpp = dependency('sigc++-2.0')
libepoll = dependency('epoll-shim', required: false) libepoll = dependency('epoll-shim', required: false)
@ -94,7 +94,9 @@ libnl = dependency('libnl-3.0', required: get_option('libnl'))
libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl')) libnlgen = dependency('libnl-genl-3.0', required: get_option('libnl'))
libpulse = dependency('libpulse', required: get_option('pulseaudio')) libpulse = dependency('libpulse', required: get_option('pulseaudio'))
libudev = dependency('libudev', required: get_option('libudev')) libudev = dependency('libudev', required: get_option('libudev'))
libevdev = dependency('libevdev', required: get_option('libevdev'))
libmpdclient = dependency('libmpdclient', required: get_option('mpd')) libmpdclient = dependency('libmpdclient', required: get_option('mpd'))
xkbregistry = dependency('xkbregistry')
libsndio = compiler.find_library('sndio', required: get_option('sndio')) libsndio = compiler.find_library('sndio', required: get_option('sndio'))
if libsndio.found() if libsndio.found()
@ -140,6 +142,7 @@ src_files = files(
'src/factory.cpp', 'src/factory.cpp',
'src/AModule.cpp', 'src/AModule.cpp',
'src/ALabel.cpp', 'src/ALabel.cpp',
'src/AIconLabel.cpp',
'src/modules/custom.cpp', 'src/modules/custom.cpp',
'src/modules/disk.cpp', 'src/modules/disk.cpp',
'src/modules/idle_inhibitor.cpp', 'src/modules/idle_inhibitor.cpp',
@ -147,6 +150,8 @@ src_files = files(
'src/main.cpp', 'src/main.cpp',
'src/bar.cpp', 'src/bar.cpp',
'src/client.cpp', 'src/client.cpp',
'src/config.cpp',
'src/group.cpp',
'src/util/ustring_clen.cpp' 'src/util/ustring_clen.cpp'
) )
@ -174,6 +179,7 @@ endif
add_project_arguments('-DHAVE_SWAY', language: 'cpp') add_project_arguments('-DHAVE_SWAY', language: 'cpp')
src_files += [ src_files += [
'src/modules/sway/ipc/client.cpp', 'src/modules/sway/ipc/client.cpp',
'src/modules/sway/bar.cpp',
'src/modules/sway/mode.cpp', 'src/modules/sway/mode.cpp',
'src/modules/sway/language.cpp', 'src/modules/sway/language.cpp',
'src/modules/sway/window.cpp', 'src/modules/sway/window.cpp',
@ -183,6 +189,8 @@ src_files += [
if true if true
add_project_arguments('-DHAVE_WLR', language: 'cpp') add_project_arguments('-DHAVE_WLR', language: 'cpp')
src_files += 'src/modules/wlr/taskbar.cpp' src_files += 'src/modules/wlr/taskbar.cpp'
src_files += 'src/modules/wlr/workspace_manager.cpp'
src_files += 'src/modules/wlr/workspace_manager_binding.cpp'
endif endif
if true if true
@ -215,6 +223,11 @@ if libudev.found() and (is_linux or libepoll.found())
src_files += 'src/modules/backlight.cpp' src_files += 'src/modules/backlight.cpp'
endif endif
if libevdev.found() and (is_linux or libepoll.found())
add_project_arguments('-DHAVE_LIBEVDEV', language: 'cpp')
src_files += 'src/modules/keyboard_state.cpp'
endif
if libmpdclient.found() if libmpdclient.found()
add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp') add_project_arguments('-DHAVE_LIBMPDCLIENT', language: 'cpp')
src_files += 'src/modules/mpd/mpd.cpp' src_files += 'src/modules/mpd/mpd.cpp'
@ -230,6 +243,11 @@ if libsndio.found()
src_files += 'src/modules/sndio.cpp' src_files += 'src/modules/sndio.cpp'
endif endif
if (giounix.found() and not get_option('logind').disabled())
add_project_arguments('-DHAVE_GIO_UNIX', language: 'cpp')
src_files += 'src/modules/inhibitor.cpp'
endif
if get_option('rfkill').enabled() if get_option('rfkill').enabled()
if is_linux if is_linux
add_project_arguments('-DWANT_RFKILL', language: 'cpp') add_project_arguments('-DWANT_RFKILL', language: 'cpp')
@ -247,6 +265,10 @@ else
src_files += 'src/modules/simpleclock.cpp' src_files += 'src/modules/simpleclock.cpp'
endif endif
if get_option('experimental')
add_project_arguments('-DUSE_EXPERIMENTAL', language: 'cpp')
endif
subdir('protocol') subdir('protocol')
executable( executable(
@ -270,9 +292,11 @@ executable(
libudev, libudev,
libepoll, libepoll,
libmpdclient, libmpdclient,
libevdev,
gtk_layer_shell, gtk_layer_shell,
libsndio, libsndio,
tz_dep tz_dep,
xkbregistry
], ],
include_directories: [include_directories('include')], include_directories: [include_directories('include')],
install: true, install: true,
@ -310,6 +334,7 @@ if scdoc.found()
'waybar-custom.5.scd', 'waybar-custom.5.scd',
'waybar-disk.5.scd', 'waybar-disk.5.scd',
'waybar-idle-inhibitor.5.scd', 'waybar-idle-inhibitor.5.scd',
'waybar-keyboard-state.5.scd',
'waybar-memory.5.scd', 'waybar-memory.5.scd',
'waybar-mpd.5.scd', 'waybar-mpd.5.scd',
'waybar-network.5.scd', 'waybar-network.5.scd',
@ -323,10 +348,15 @@ if scdoc.found()
'waybar-tray.5.scd', 'waybar-tray.5.scd',
'waybar-states.5.scd', 'waybar-states.5.scd',
'waybar-wlr-taskbar.5.scd', 'waybar-wlr-taskbar.5.scd',
'waybar-wlr-workspaces.5.scd',
'waybar-bluetooth.5.scd', 'waybar-bluetooth.5.scd',
'waybar-sndio.5.scd', 'waybar-sndio.5.scd',
] ]
if (giounix.found() and not get_option('logind').disabled())
man_files += 'waybar-inhibitor.5.scd'
endif
foreach file : man_files foreach file : man_files
path = '@0@'.format(file) path = '@0@'.format(file)
basename = path.split('/')[-1] basename = path.split('/')[-1]
@ -349,6 +379,15 @@ if scdoc.found()
endforeach endforeach
endif endif
catch2 = dependency(
'catch2',
fallback: ['catch2', 'catch2_dep'],
required: get_option('tests'),
)
if catch2.found()
subdir('test')
endif
clangtidy = find_program('clang-tidy', required: false) clangtidy = find_program('clang-tidy', required: false)
if clangtidy.found() if clangtidy.found()
@ -360,3 +399,4 @@ if clangtidy.found()
'-p', meson.build_root() '-p', meson.build_root()
] + src_files) ] + src_files)
endif endif

View File

@ -1,6 +1,7 @@
option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.') option('libcxx', type : 'boolean', value : false, description : 'Build with Clang\'s libc++ instead of libstdc++ on Linux.')
option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features') option('libnl', type: 'feature', value: 'auto', description: 'Enable libnl support for network related features')
option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features') option('libudev', type: 'feature', value: 'auto', description: 'Enable libudev support for udev related features')
option('libevdev', type: 'feature', value: 'auto', description: 'Enable libevdev support for evdev related features')
option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio') option('pulseaudio', type: 'feature', value: 'auto', description: 'Enable support for pulseaudio')
option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit') option('systemd', type: 'feature', value: 'auto', description: 'Install systemd user service unit')
option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray') option('dbusmenu-gtk', type: 'feature', value: 'auto', description: 'Enable support for tray')
@ -9,3 +10,6 @@ option('mpd', type: 'feature', value: 'auto', description: 'Enable support for t
option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support') option('gtk-layer-shell', type: 'feature', value: 'auto', description: 'Use gtk-layer-shell library for popups support')
option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL') option('rfkill', type: 'feature', value: 'auto', description: 'Enable support for RFKILL')
option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio') option('sndio', type: 'feature', value: 'auto', description: 'Enable support for sndio')
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')

View File

@ -0,0 +1,306 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="ext_workspace_unstable_v1">
<copyright>
Copyright © 2019 Christopher Billington
Copyright © 2020 Ilia Bozhinov
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zext_workspace_manager_v1" version="1">
<description summary="list and control workspaces">
Workspaces, also called virtual desktops, are groups of surfaces. A
compositor with a concept of workspaces may only show some such groups of
surfaces (those of 'active' workspaces) at a time. 'Activating' a
workspace is a request for the compositor to display that workspace's
surfaces as normal, whereas the compositor may hide or otherwise
de-emphasise surfaces that are associated only with 'inactive' workspaces.
Workspaces are grouped by which sets of outputs they correspond to, and
may contain surfaces only from those outputs. In this way, it is possible
for each output to have its own set of workspaces, or for all outputs (or
any other arbitrary grouping) to share workspaces. Compositors may
optionally conceptually arrange each group of workspaces in an
N-dimensional grid.
The purpose of this protocol is to enable the creation of taskbars and
docks by providing them with a list of workspaces and their properties,
and allowing them to activate and deactivate workspaces.
After a client binds the zext_workspace_manager_v1, each workspace will be
sent via the workspace event.
</description>
<event name="workspace_group">
<description summary="a workspace group has been created">
This event is emitted whenever a new workspace group has been created.
All initial details of the workspace group (workspaces, outputs) will be
sent immediately after this event via the corresponding events in
zext_workspace_group_handle_v1.
</description>
<arg name="workspace_group" type="new_id" interface="zext_workspace_group_handle_v1"/>
</event>
<request name="commit">
<description summary="all requests about the workspaces have been sent">
The client must send this request after it has finished sending other
requests. The compositor must process a series of requests preceding a
commit request atomically.
This allows changes to the workspace properties to be seen as atomic,
even if they happen via multiple events, and even if they involve
multiple zext_workspace_handle_v1 objects, for example, deactivating one
workspace and activating another.
</description>
</request>
<event name="done">
<description summary="all information about the workspace groups has been sent">
This event is sent after all changes in all workspace groups have been
sent.
This allows changes to one or more zext_workspace_group_handle_v1
properties to be seen as atomic, even if they happen via multiple
events. In particular, an output moving from one workspace group to
another sends an output_enter event and an output_leave event to the two
zext_workspace_group_handle_v1 objects in question. The compositor sends
the done event only after updating the output information in both
workspace groups.
</description>
</event>
<event name="finished">
<description summary="the compositor has finished with the workspace_manager">
This event indicates that the compositor is done sending events to the
zext_workspace_manager_v1. The server will destroy the object
immediately after sending this request, so it will become invalid and
the client should free any resources associated with it.
</description>
</event>
<request name="stop">
<description summary="stop sending events">
Indicates the client no longer wishes to receive events for new
workspace groups. However the compositor may emit further workspace
events, until the finished event is emitted.
The client must not send any more requests after this one.
</description>
</request>
</interface>
<interface name="zext_workspace_group_handle_v1" version="1">
<description summary="a workspace group assigned to a set of outputs">
A zext_workspace_group_handle_v1 object represents a a workspace group
that is assigned a set of outputs and contains a number of workspaces.
The set of outputs assigned to the workspace group is conveyed to the client via
output_enter and output_leave events, and its workspaces are conveyed with
workspace events.
For example, a compositor which has a set of workspaces for each output may
advertise a workspace group (and its workspaces) per output, whereas a compositor
where a workspace spans all outputs may advertise a single workspace group for all
outputs.
</description>
<event name="output_enter">
<description summary="output assigned to workspace group">
This event is emitted whenever an output is assigned to the workspace
group.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="output_leave">
<description summary="output removed from workspace group">
This event is emitted whenever an output is removed from the workspace
group.
</description>
<arg name="output" type="object" interface="wl_output"/>
</event>
<event name="workspace">
<description summary="workspace added to workspace group">
This event is emitted whenever a new workspace has been created.
All initial details of the workspace (name, coordinates, state) will
be sent immediately after this event via the corresponding events in
zext_workspace_handle_v1.
</description>
<arg name="workspace" type="new_id" interface="zext_workspace_handle_v1"/>
</event>
<event name="remove">
<description summary="this workspace group has been destroyed">
This event means the zext_workspace_group_handle_v1 has been destroyed.
It is guaranteed there won't be any more events for this
zext_workspace_group_handle_v1. The zext_workspace_group_handle_v1 becomes
inert so any requests will be ignored except the destroy request.
The compositor must remove all workspaces belonging to a workspace group
before removing the workspace group.
</description>
</event>
<request name="create_workspace">
<description summary="create a new workspace">
Request that the compositor create a new workspace with the given name.
There is no guarantee that the compositor will create a new workspace,
or that the created workspace will have the provided name.
</description>
<arg name="workspace" type="string"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the zext_workspace_handle_v1 object">
Destroys the zext_workspace_handle_v1 object.
This request should be called either when the client does not want to
use the workspace object any more or after the remove event to finalize
the destruction of the object.
</description>
</request>
</interface>
<interface name="zext_workspace_handle_v1" version="1">
<description summary="a workspace handing a group of surfaces">
A zext_workspace_handle_v1 object represents a a workspace that handles a
group of surfaces.
Each workspace has a name, conveyed to the client with the name event; a
list of states, conveyed to the client with the state event; and
optionally a set of coordinates, conveyed to the client with the
coordinates event. The client may request that the compositor activate or
deactivate the workspace.
Each workspace can belong to only a single workspace group.
Depepending on the compositor policy, there might be workspaces with
the same name in different workspace groups, but these workspaces are still
separate (e.g. one of them might be active while the other is not).
</description>
<event name="name">
<description summary="workspace name changed">
This event is emitted immediately after the zext_workspace_handle_v1 is
created and whenever the name of the workspace changes.
</description>
<arg name="name" type="string"/>
</event>
<event name="coordinates">
<description summary="workspace coordinates changed">
This event is used to organize workspaces into an N-dimensional grid
within a workspace group, and if supported, is emitted immediately after
the zext_workspace_handle_v1 is created and whenever the coordinates of
the workspace change. Compositors may not send this event if they do not
conceptually arrange workspaces in this way. If compositors simply
number workspaces, without any geometric interpretation, they may send
1D coordinates, which clients should not interpret as implying any
geometry. Sending an empty array means that the compositor no longer
orders the workspace geometrically.
Coordinates have an arbitrary number of dimensions N with an uint32
position along each dimension. By convention if N > 1, the first
dimension is X, the second Y, the third Z, and so on. The compositor may
chose to utilize these events for a more novel workspace layout
convention, however. No guarantee is made about the grid being filled or
bounded; there may be a workspace at coordinate 1 and another at
coordinate 1000 and none in between. Within a workspace group, however,
workspaces must have unique coordinates of equal dimensionality.
</description>
<arg name="coordinates" type="array"/>
</event>
<event name="state">
<description summary="the state of the workspace changed">
This event is emitted immediately after the zext_workspace_handle_v1 is
created and each time the workspace state changes, either because of a
compositor action or because of a request in this protocol.
</description>
<arg name="state" type="array"/>
</event>
<enum name="state">
<description summary="types of states on the workspace">
The different states that a workspace can have.
</description>
<entry name="active" value="0" summary="the workspace is active"/>
<entry name="urgent" value="1" summary="the workspace requests attention"/>
<entry name="hidden" value="2">
<description summary="the workspace is not visible">
The workspace is not visible in its workspace group, and clients
attempting to visualize the compositor workspace state should not
display such workspaces.
</description>
</entry>
</enum>
<event name="remove">
<description summary="this workspace has been destroyed">
This event means the zext_workspace_handle_v1 has been destroyed. It is
guaranteed there won't be any more events for this
zext_workspace_handle_v1. The zext_workspace_handle_v1 becomes inert so
any requests will be ignored except the destroy request.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="destroy the zext_workspace_handle_v1 object">
Destroys the zext_workspace_handle_v1 object.
This request should be called either when the client does not want to
use the workspace object any more or after the remove event to finalize
the destruction of the object.
</description>
</request>
<request name="activate">
<description summary="activate the workspace">
Request that this workspace be activated.
There is no guarantee the workspace will be actually activated, and
behaviour may be compositor-dependent. For example, activating a
workspace may or may not deactivate all other workspaces in the same
group.
</description>
</request>
<request name="deactivate">
<description summary="activate the workspace">
Request that this workspace be deactivated.
There is no guarantee the workspace will be actually deactivated.
</description>
</request>
<request name="remove">
<description summary="remove the workspace">
Request that this workspace be removed.
There is no guarantee the workspace will be actually removed.
</description>
</request>
</interface>
</protocol>

View File

@ -27,7 +27,9 @@ client_protocols = [
[wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'], [wl_protocol_dir, 'unstable/idle-inhibit/idle-inhibit-unstable-v1.xml'],
['wlr-layer-shell-unstable-v1.xml'], ['wlr-layer-shell-unstable-v1.xml'],
['wlr-foreign-toplevel-management-unstable-v1.xml'], ['wlr-foreign-toplevel-management-unstable-v1.xml'],
['ext-workspace-unstable-v1.xml'],
['river-status-unstable-v1.xml'], ['river-status-unstable-v1.xml'],
['river-control-unstable-v1.xml'],
] ]
client_protos_src = [] client_protos_src = []

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_control_unstable_v1">
<copyright>
Copyright 2020 The River Developers
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright>
<interface name="zriver_control_v1" version="1">
<description summary="run compositor commands">
This interface allows clients to run compositor commands and receive a
success/failure response with output or a failure message respectively.
Each command is built up in a series of add_argument requests and
executed with a run_command request. The first argument is the command
to be run.
A complete list of commands should be made available in the man page of
the compositor.
</description>
<request name="destroy" type="destructor">
<description summary="destroy the river_control object">
This request indicates that the client will not use the
river_control object any more. Objects that have been created
through this instance are not affected.
</description>
</request>
<request name="add_argument">
<description summary="add an argument to the current command">
Arguments are stored by the server in the order they were sent until
the run_command request is made.
</description>
<arg name="argument" type="string" summary="the argument to add"/>
</request>
<request name="run_command">
<description summary="run the current command">
Execute the command built up using the add_argument request for the
given seat.
</description>
<arg name="seat" type="object" interface="wl_seat"/>
<arg name="callback" type="new_id" interface="zriver_command_callback_v1"
summary="callback object"/>
</request>
</interface>
<interface name="zriver_command_callback_v1" version="1">
<description summary="callback object">
This object is created by the run_command request. Exactly one of the
success or failure events will be sent. This object will be destroyed
by the compositor after one of the events is sent.
</description>
<event name="success">
<description summary="command successful">
Sent when the command has been successfully received and executed by
the compositor. Some commands may produce output, in which case the
output argument will be a non-empty string.
</description>
<arg name="output" type="string" summary="the output of the command"/>
</event>
<event name="failure">
<description summary="command failed">
Sent when the command could not be carried out. This could be due to
sending a non-existent command, no command, not enough arguments, too
many arguments, invalid arguments, etc.
</description>
<arg name="failure_message" type="string"
summary="a message explaining why failure occurred"/>
</event>
</interface>
</protocol>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<protocol name="river_status_unstable_v1"> <protocol name="river_status_unstable_v1">
<copyright> <copyright>
Copyright 2020 Isaac Freund Copyright 2020 The River Developers
Permission to use, copy, modify, and/or distribute this software for any Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above purpose with or without fee is hereby granted, provided that the above
@ -16,7 +16,7 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
</copyright> </copyright>
<interface name="zriver_status_manager_v1" version="1"> <interface name="zriver_status_manager_v1" version="2">
<description summary="manage river status objects"> <description summary="manage river status objects">
A global factory for objects that receive status information specific A global factory for objects that receive status information specific
to river. It could be used to implement, for example, a status bar. to river. It could be used to implement, for example, a status bar.
@ -47,7 +47,7 @@
</request> </request>
</interface> </interface>
<interface name="zriver_output_status_v1" version="1"> <interface name="zriver_output_status_v1" version="2">
<description summary="track output tags and focus"> <description summary="track output tags and focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
windowing state of an output. windowing state of an output.
@ -75,12 +75,21 @@
</description> </description>
<arg name="tags" type="array" summary="array of 32-bit bitfields"/> <arg name="tags" type="array" summary="array of 32-bit bitfields"/>
</event> </event>
<event name="urgent_tags" since="2">
<description summary="tags of the output with an urgent view">
Sent once on binding the interface and again whenever the set of
tags with at least one urgent view changes.
</description>
<arg name="tags" type="uint" summary="32-bit bitfield"/>
</event>
</interface> </interface>
<interface name="zriver_seat_status_v1" version="1"> <interface name="zriver_seat_status_v1" version="1">
<description summary="track seat focus"> <description summary="track seat focus">
This interface allows clients to receive information about the current This interface allows clients to receive information about the current
focus of a seat. focus of a seat. Note that (un)focused_output events will only be sent
if the client has bound the relevant wl_output globals.
</description> </description>
<request name="destroy" type="destructor"> <request name="destroy" type="destructor">

View File

@ -3,10 +3,11 @@
// "position": "bottom", // Waybar position (top|bottom|left|right) // "position": "bottom", // Waybar position (top|bottom|left|right)
"height": 30, // Waybar height (to be removed for auto height) "height": 30, // Waybar height (to be removed for auto height)
// "width": 1280, // Waybar width // "width": 1280, // Waybar width
"spacing": 4, // Gaps between modules (4px)
// Choose the order of the modules // Choose the order of the modules
"modules-left": ["sway/workspaces", "sway/mode", "custom/media"], "modules-left": ["sway/workspaces", "sway/mode", "custom/media"],
"modules-center": ["sway/window"], "modules-center": ["sway/window"],
"modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "sway/language", "battery", "battery#bat2", "clock", "tray"], "modules-right": ["mpd", "idle_inhibitor", "pulseaudio", "network", "cpu", "memory", "temperature", "backlight", "keyboard-state", "sway/language", "battery", "battery#bat2", "clock", "tray"],
// Modules configuration // Modules configuration
// "sway/workspaces": { // "sway/workspaces": {
// "disable-scroll": true, // "disable-scroll": true,
@ -23,11 +24,20 @@
// "default": "" // "default": ""
// } // }
// }, // },
"keyboard-state": {
"numlock": true,
"capslock": true,
"format": "{name} {icon}",
"format-icons": {
"locked": "",
"unlocked": ""
}
},
"sway/mode": { "sway/mode": {
"format": "<span style=\"italic\">{}</span>" "format": "<span style=\"italic\">{}</span>"
}, },
"mpd": { "mpd": {
"format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ ", "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
"format-disconnected": "Disconnected ", "format-disconnected": "Disconnected ",
"format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ", "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
"unknown-tag": "N/A", "unknown-tag": "N/A",
@ -108,7 +118,8 @@
"network": { "network": {
// "interface": "wlp2*", // (Optional) To force the use of this interface // "interface": "wlp2*", // (Optional) To force the use of this interface
"format-wifi": "{essid} ({signalStrength}%) ", "format-wifi": "{essid} ({signalStrength}%) ",
"format-ethernet": "{ifname}: {ipaddr}/{cidr} ", "format-ethernet": "{ipaddr}/{cidr} ",
"tooltip-format": "{ifname} via {gwaddr} ",
"format-linked": "{ifname} (No IP) ", "format-linked": "{ifname} (No IP) ",
"format-disconnected": "Disconnected ⚠", "format-disconnected": "Disconnected ⚠",
"format-alt": "{ifname}: {ipaddr}/{cidr}" "format-alt": "{ifname}: {ipaddr}/{cidr}"
@ -145,3 +156,4 @@
// "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name // "exec": "$HOME/.config/waybar/mediaplayer.py --player spotify 2> /dev/null" // Filter player based on name
} }
} }

View File

@ -79,7 +79,7 @@ def signal_handler(sig, frame):
def parse_arguments(): def parse_arguments():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# Increase verbosity with every occurence of -v # Increase verbosity with every occurrence of -v
parser.add_argument('-v', '--verbose', action='count', default=0) parser.add_argument('-v', '--verbose', action='count', default=0)
# Define for which player we're listening # Define for which player we're listening
@ -110,6 +110,7 @@ def main():
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
for player in manager.props.player_names: for player in manager.props.player_names:
if arguments.player is not None and arguments.player != player.name: if arguments.player is not None and arguments.player != player.name:

View File

@ -1,10 +1,7 @@
* { * {
border: none;
border-radius: 0;
/* `otf-font-awesome` is required to be installed for icons */ /* `otf-font-awesome` is required to be installed for icons */
font-family: Roboto, Helvetica, Arial, sans-serif; font-family: FontAwesome, Roboto, Helvetica, Arial, sans-serif;
font-size: 13px; font-size: 13px;
min-height: 0;
} }
window#waybar { window#waybar {
@ -43,6 +40,9 @@ window#waybar.chromium {
color: #ffffff; color: #ffffff;
/* Use box-shadow instead of border so the text isn't offset */ /* Use box-shadow instead of border so the text isn't offset */
box-shadow: inset 0 -3px transparent; box-shadow: inset 0 -3px transparent;
/* Avoid rounded borders under each workspace name */
border: none;
border-radius: 0;
} }
/* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */ /* https://github.com/Alexays/Waybar/wiki/FAQ#the-workspace-buttons-have-a-strange-hover-effect */
@ -80,7 +80,6 @@ window#waybar.chromium {
#idle_inhibitor, #idle_inhibitor,
#mpd { #mpd {
padding: 0 10px; padding: 0 10px;
margin: 0 4px;
color: #ffffff; color: #ffffff;
} }
@ -195,6 +194,15 @@ label:focus {
background-color: #2980b9; background-color: #2980b9;
} }
#tray > .passive {
-gtk-icon-effect: dim;
}
#tray > .needs-attention {
-gtk-icon-effect: highlight;
background-color: #eb4d4b;
}
#idle_inhibitor { #idle_inhibitor {
background-color: #2d3436; background-color: #2d3436;
} }
@ -228,3 +236,19 @@ label:focus {
margin: 0 5px; margin: 0 5px;
min-width: 16px; min-width: 16px;
} }
#keyboard-state {
background: #97e1ad;
color: #000000;
padding: 0 0px;
margin: 0 5px;
min-width: 16px;
}
#keyboard-state > label {
padding: 0 5px;
}
#keyboard-state > label.locked {
background: rgba(0, 0, 0, 0.2);
}

View File

@ -3,6 +3,7 @@ Description=Highly customizable Wayland bar for Sway and Wlroots based composito
Documentation=https://github.com/Alexays/Waybar/wiki/ Documentation=https://github.com/Alexays/Waybar/wiki/
PartOf=graphical-session.target PartOf=graphical-session.target
After=graphical-session.target After=graphical-session.target
Requisite=graphical-session.target
[Service] [Service]
ExecStart=@prefix@/bin/waybar ExecStart=@prefix@/bin/waybar

28
src/AIconLabel.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "AIconLabel.hpp"
#include <gdkmm/pixbuf.h>
namespace waybar {
AIconLabel::AIconLabel(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)
: ALabel(config, name, id, format, interval, ellipsize, enable_click, enable_scroll) {
event_box_.remove();
box_.set_orientation(Gtk::Orientation::ORIENTATION_HORIZONTAL);
box_.set_spacing(8);
box_.add(image_);
box_.add(label_);
event_box_.add(box_);
}
auto AIconLabel::update() -> void {
image_.set_visible(image_.get_visible() && iconEnabled());
ALabel::update();
}
bool AIconLabel::iconEnabled() const {
return config_["icon"].isBool() ? config_["icon"].asBool() : false;
}
} // namespace waybar

View File

@ -67,8 +67,10 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
} }
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); if (size) {
format_icons = format_icons[idx]; auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
} }
if (format_icons.isString()) { if (format_icons.isString()) {
return format_icons.asString(); return format_icons.asString();
@ -76,7 +78,7 @@ std::string ALabel::getIcon(uint16_t percentage, const std::string& alt, uint16_
return ""; return "";
} }
std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts, uint16_t max) { std::string ALabel::getIcon(uint16_t percentage, const std::vector<std::string>& alts, uint16_t max) {
auto format_icons = config_["format-icons"]; auto format_icons = config_["format-icons"];
if (format_icons.isObject()) { if (format_icons.isObject()) {
std::string _alt = "default"; std::string _alt = "default";
@ -90,8 +92,10 @@ std::string ALabel::getIcon(uint16_t percentage, std::vector<std::string>& alts,
} }
if (format_icons.isArray()) { if (format_icons.isArray()) {
auto size = format_icons.size(); auto size = format_icons.size();
auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1); if (size) {
format_icons = format_icons[idx]; auto idx = std::clamp(percentage / ((max == 0 ? 100 : max) / size), 0U, size - 1);
format_icons = format_icons[idx];
}
} }
if (format_icons.isString()) { if (format_icons.isString()) {
return format_icons.asString(); return format_icons.asString();

View File

@ -6,7 +6,9 @@ namespace waybar {
AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id, AModule::AModule(const Json::Value& config, const std::string& name, const std::string& id,
bool enable_click, bool enable_scroll) bool enable_click, bool enable_scroll)
: name_(std::move(name)), config_(std::move(config)) { : name_(std::move(name)), config_(std::move(config))
, distance_scrolled_y_(0.0)
, distance_scrolled_x_(0.0) {
// configure events' user commands // configure events' user commands
if (config_["on-click"].isString() || config_["on-click-middle"].isString() || if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-backward"].isString() || config_["on-click-forward"].isString() || config_["on-click-backward"].isString() || config_["on-click-forward"].isString() ||

View File

@ -9,20 +9,103 @@
#include "bar.hpp" #include "bar.hpp"
#include "client.hpp" #include "client.hpp"
#include "factory.hpp" #include "factory.hpp"
#include "group.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
#ifdef HAVE_SWAY
#include "modules/sway/bar.hpp"
#endif
namespace waybar { namespace waybar {
static constexpr const char* MIN_HEIGHT_MSG = static constexpr const char* MIN_HEIGHT_MSG =
"Requested height: {} exceeds the minimum height: {} required by the modules"; "Requested height: {} is less than the minimum height: {} required by the modules";
static constexpr const char* MIN_WIDTH_MSG = static constexpr const char* MIN_WIDTH_MSG =
"Requested width: {} exceeds the minimum width: {} required by the modules"; "Requested width: {} is less than the minimum width: {} required by the modules";
static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}"; static constexpr const char* BAR_SIZE_MSG = "Bar configured (width: {}, height: {}) for output: {}";
static constexpr const char* SIZE_DEFINED = static constexpr const char* SIZE_DEFINED =
"{} size is defined in the config file so it will stay like that"; "{} size is defined in the config file so it will stay like that";
const Bar::bar_mode_map Bar::PRESET_MODES = { //
{"default",
{// Special mode to hold the global bar configuration
.layer = bar_layer::BOTTOM,
.exclusive = true,
.passthrough = false,
.visible = true}},
{"dock",
{// Modes supported by the sway config; see man sway-bar(5)
.layer = bar_layer::BOTTOM,
.exclusive = true,
.passthrough = false,
.visible = true}},
{"hide",
{//
.layer = bar_layer::TOP,
.exclusive = false,
.passthrough = false,
.visible = true}},
{"invisible",
{//
.layer = bar_layer::BOTTOM,
.exclusive = false,
.passthrough = true,
.visible = false}},
{"overlay",
{//
.layer = bar_layer::TOP,
.exclusive = false,
.passthrough = true,
.visible = true}}};
const std::string_view Bar::MODE_DEFAULT = "default";
const std::string_view Bar::MODE_INVISIBLE = "invisible";
const std::string_view DEFAULT_BAR_ID = "bar-0";
/* Deserializer for enum bar_layer */
void from_json(const Json::Value& j, bar_layer& l) {
if (j == "bottom") {
l = bar_layer::BOTTOM;
} else if (j == "top") {
l = bar_layer::TOP;
} else if (j == "overlay") {
l = bar_layer::OVERLAY;
}
}
/* Deserializer for struct bar_mode */
void from_json(const Json::Value& j, bar_mode& m) {
if (j.isObject()) {
if (auto v = j["layer"]; v.isString()) {
from_json(v, m.layer);
}
if (auto v = j["exclusive"]; v.isBool()) {
m.exclusive = v.asBool();
}
if (auto v = j["passthrough"]; v.isBool()) {
m.passthrough = v.asBool();
}
if (auto v = j["visible"]; v.isBool()) {
m.visible = v.asBool();
}
}
}
/* Deserializer for JSON Object -> map<string compatible type, Value>
* Assumes that all the values in the object are deserializable to the same type.
*/
template <typename Key, typename Value,
typename = std::enable_if_t<std::is_convertible<std::string_view, Key>::value>>
void from_json(const Json::Value& j, std::map<Key, Value>& m) {
if (j.isObject()) {
for (auto it = j.begin(); it != j.end(); ++it) {
from_json(*it, m[it.key().asString()]);
}
}
}
#ifdef HAVE_GTK_LAYER_SHELL #ifdef HAVE_GTK_LAYER_SHELL
struct GLSSurfaceImpl : public BarSurface, public sigc::trackable { struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} { GLSSurfaceImpl(Gtk::Window& window, struct waybar_output& output) : window_{window} {
@ -33,6 +116,7 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj()); gtk_layer_set_monitor(window_.gobj(), output.monitor->gobj());
gtk_layer_set_namespace(window_.gobj(), "waybar"); gtk_layer_set_namespace(window_.gobj(), "waybar");
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &GLSSurfaceImpl::onMap));
window.signal_configure_event().connect_notify( window.signal_configure_event().connect_notify(
sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure)); sigc::mem_fun(*this, &GLSSurfaceImpl::onConfigure));
} }
@ -62,6 +146,18 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
gtk_layer_set_layer(window_.gobj(), layer); gtk_layer_set_layer(window_.gobj(), layer);
} }
void setPassThrough(bool enable) override {
passthrough_ = enable;
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override { void setPosition(const std::string_view& position) override {
auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM; auto unanchored = GTK_LAYER_SHELL_EDGE_BOTTOM;
vertical_ = false; vertical_ = false;
@ -93,8 +189,11 @@ struct GLSSurfaceImpl : public BarSurface, public sigc::trackable {
std::string output_name_; std::string output_name_;
uint32_t width_; uint32_t width_;
uint32_t height_; uint32_t height_;
bool passthrough_ = false;
bool vertical_ = false; bool vertical_ = false;
void onMap(GdkEventAny* ev) { setPassThrough(passthrough_); }
void onConfigure(GdkEventConfigure* ev) { void onConfigure(GdkEventConfigure* ev) {
/* /*
* GTK wants new size for the window. * GTK wants new size for the window.
@ -182,6 +281,20 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
} }
} }
void setPassThrough(bool enable) override {
passthrough_ = enable;
/* GTK overwrites any region changes applied directly to the wl_surface,
* thus the same GTK region API as in the GLS impl has to be used. */
auto gdk_window = window_.get_window();
if (gdk_window) {
Cairo::RefPtr<Cairo::Region> region;
if (enable) {
region = Cairo::Region::create();
}
gdk_window->input_shape_combine_region(region, 0, 0);
}
}
void setPosition(const std::string_view& position) override { void setPosition(const std::string_view& position) override {
anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
if (position == "bottom") { if (position == "bottom") {
@ -230,6 +343,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
uint32_t height_ = 0; uint32_t height_ = 0;
uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; uint8_t anchor_ = HORIZONTAL_ANCHOR | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP;
bool exclusive_zone_ = true; bool exclusive_zone_ = true;
bool passthrough_ = false;
struct bar_margins margins_; struct bar_margins margins_;
zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM; zwlr_layer_shell_v1_layer layer_ = ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM;
@ -262,6 +376,7 @@ struct RawSurfaceImpl : public BarSurface, public sigc::trackable {
setSurfaceSize(width_, height_); setSurfaceSize(width_, height_);
setExclusiveZone(exclusive_zone_); setExclusiveZone(exclusive_zone_);
setPassThrough(passthrough_);
commit(); commit();
wl_display_roundtrip(client->wl_display); wl_display_roundtrip(client->wl_display);
@ -359,7 +474,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
: output(w_output), : output(w_output),
config(w_config), config(w_config),
window{Gtk::WindowType::WINDOW_TOPLEVEL}, window{Gtk::WindowType::WINDOW_TOPLEVEL},
layer_{bar_layer::BOTTOM},
left_(Gtk::ORIENTATION_HORIZONTAL, 0), left_(Gtk::ORIENTATION_HORIZONTAL, 0),
center_(Gtk::ORIENTATION_HORIZONTAL, 0), center_(Gtk::ORIENTATION_HORIZONTAL, 0),
right_(Gtk::ORIENTATION_HORIZONTAL, 0), right_(Gtk::ORIENTATION_HORIZONTAL, 0),
@ -371,12 +485,6 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
window.get_style_context()->add_class(config["name"].asString()); window.get_style_context()->add_class(config["name"].asString());
window.get_style_context()->add_class(config["position"].asString()); window.get_style_context()->add_class(config["position"].asString());
if (config["layer"] == "top") {
layer_ = bar_layer::TOP;
} else if (config["layer"] == "overlay") {
layer_ = bar_layer::OVERLAY;
}
auto position = config["position"].asString(); auto position = config["position"].asString();
if (position == "right" || position == "left") { if (position == "right" || position == "left") {
@ -386,11 +494,18 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0); box_ = Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0);
vertical = true; vertical = true;
} }
left_.get_style_context()->add_class("modules-left"); left_.get_style_context()->add_class("modules-left");
center_.get_style_context()->add_class("modules-center"); center_.get_style_context()->add_class("modules-center");
right_.get_style_context()->add_class("modules-right"); right_.get_style_context()->add_class("modules-right");
if (config["spacing"].isInt()) {
int spacing = config["spacing"].asInt();
left_.set_spacing(spacing);
center_.set_spacing(spacing);
right_.set_spacing(spacing);
}
uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0; uint32_t height = config["height"].isUInt() ? config["height"].asUInt() : 0;
uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0; uint32_t width = config["width"].isUInt() ? config["width"].asUInt() : 0;
@ -451,14 +566,43 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output); surface_impl_ = std::make_unique<RawSurfaceImpl>(window, *output);
} }
surface_impl_->setLayer(layer_);
surface_impl_->setExclusiveZone(true);
surface_impl_->setMargins(margins_); surface_impl_->setMargins(margins_);
surface_impl_->setPosition(position); surface_impl_->setPosition(position);
surface_impl_->setSize(width, height); surface_impl_->setSize(width, height);
/* Read custom modes if available */
if (auto modes = config.get("modes", {}); modes.isObject()) {
from_json(modes, configured_modes);
}
/* Update "default" mode with the global bar options */
from_json(config, configured_modes[MODE_DEFAULT]);
if (auto mode = config.get("mode", {}); mode.isString()) {
setMode(config["mode"].asString());
} else {
setMode(MODE_DEFAULT);
}
window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap)); window.signal_map_event().connect_notify(sigc::mem_fun(*this, &Bar::onMap));
#if HAVE_SWAY
if (auto ipc = config["ipc"]; ipc.isBool() && ipc.asBool()) {
bar_id = Client::inst()->bar_id;
if (auto id = config["id"]; id.isString()) {
bar_id = id.asString();
}
if (bar_id.empty()) {
bar_id = DEFAULT_BAR_ID;
}
try {
_ipc_client = std::make_unique<BarIpcClient>(*this);
} catch (const std::exception& exc) {
spdlog::warn("Failed to open bar ipc connection: {}", exc.what());
}
}
#endif
setupWidgets(); setupWidgets();
window.show_all(); window.show_all();
@ -473,6 +617,44 @@ waybar::Bar::Bar(struct waybar_output* w_output, const Json::Value& w_config)
} }
} }
/* Need to define it here because of forward declared members */
waybar::Bar::~Bar() = default;
void waybar::Bar::setMode(const std::string_view& mode) {
using namespace std::literals::string_literals;
auto style = window.get_style_context();
/* remove styles added by previous setMode calls */
style->remove_class("mode-"s + last_mode_);
auto it = configured_modes.find(mode);
if (it != configured_modes.end()) {
last_mode_ = mode;
style->add_class("mode-"s + last_mode_);
setMode(it->second);
} else {
spdlog::warn("Unknown mode \"{}\" requested", mode);
last_mode_ = MODE_DEFAULT;
style->add_class("mode-"s + last_mode_);
setMode(configured_modes.at(MODE_DEFAULT));
}
}
void waybar::Bar::setMode(const struct bar_mode& mode) {
surface_impl_->setLayer(mode.layer);
surface_impl_->setExclusiveZone(mode.exclusive);
surface_impl_->setPassThrough(mode.passthrough);
if (mode.visible) {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
} else {
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
}
surface_impl_->commit();
}
void waybar::Bar::onMap(GdkEventAny*) { void waybar::Bar::onMap(GdkEventAny*) {
/* /*
* Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor). * Obtain a pointer to the custom layer surface for modules that require it (idle_inhibitor).
@ -483,17 +665,7 @@ void waybar::Bar::onMap(GdkEventAny*) {
void waybar::Bar::setVisible(bool value) { void waybar::Bar::setVisible(bool value) {
visible = value; visible = value;
if (!visible) { setMode(visible ? MODE_DEFAULT : MODE_INVISIBLE);
window.get_style_context()->add_class("hidden");
window.set_opacity(0);
surface_impl_->setLayer(bar_layer::BOTTOM);
} else {
window.get_style_context()->remove_class("hidden");
window.set_opacity(1);
surface_impl_->setLayer(layer_);
}
surface_impl_->setExclusiveZone(visible);
surface_impl_->commit();
} }
void waybar::Bar::toggle() { setVisible(!visible); } void waybar::Bar::toggle() { setVisible(!visible); }
@ -539,19 +711,7 @@ void waybar::Bar::setupAltFormatKeyForModuleList(const char* module_list_name) {
} }
void waybar::Bar::handleSignal(int signal) { void waybar::Bar::handleSignal(int signal) {
for (auto& module : modules_left_) { for (auto& module : modules_all_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_center_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) {
custom->refresh(signal);
}
}
for (auto& module : modules_right_) {
auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get()); auto* custom = dynamic_cast<waybar::modules::Custom*>(module.get());
if (custom != nullptr) { if (custom != nullptr) {
custom->refresh(signal); custom->refresh(signal);
@ -559,21 +719,38 @@ void waybar::Bar::handleSignal(int signal) {
} }
} }
void waybar::Bar::getModules(const Factory& factory, const std::string& pos) { void waybar::Bar::getModules(const Factory& factory, const std::string& pos, Gtk::Box* group = nullptr) {
if (config[pos].isArray()) { auto module_list = group ? config[pos]["modules"] : config[pos];
for (const auto& name : config[pos]) { if (module_list.isArray()) {
for (const auto& name : module_list) {
try { try {
auto module = factory.makeModule(name.asString()); auto ref = name.asString();
if (pos == "modules-left") { AModule* module;
modules_left_.emplace_back(module);
if (ref.compare(0, 6, "group/") == 0 && ref.size() > 6) {
auto group_module = new waybar::Group(ref, *this, config[ref]);
getModules(factory, ref, &group_module->box);
module = group_module;
} else {
module = factory.makeModule(ref);
} }
if (pos == "modules-center") {
modules_center_.emplace_back(module); std::shared_ptr<AModule> module_sp(module);
modules_all_.emplace_back(module_sp);
if (group) {
group->pack_start(*module, false, false);
} else {
if (pos == "modules-left") {
modules_left_.emplace_back(module_sp);
}
if (pos == "modules-center") {
modules_center_.emplace_back(module_sp);
}
if (pos == "modules-right") {
modules_right_.emplace_back(module_sp);
}
} }
if (pos == "modules-right") { module->dp.connect([module, name] {
modules_right_.emplace_back(module);
}
module->dp.connect([module, &name] {
try { try {
module->update(); module->update();
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -590,7 +767,11 @@ void waybar::Bar::getModules(const Factory& factory, const std::string& pos) {
auto waybar::Bar::setupWidgets() -> void { auto waybar::Bar::setupWidgets() -> void {
window.add(box_); window.add(box_);
box_.pack_start(left_, false, false); box_.pack_start(left_, false, false);
box_.set_center_widget(center_); if (config["fixed-center"].isBool() ? config["fixed-center"].asBool() : true) {
box_.set_center_widget(center_);
} else {
box_.pack_start(center_, true, false);
}
box_.pack_end(right_, false, false); box_.pack_end(right_, false, false);
// Convert to button code for every module that is used. // Convert to button code for every module that is used.

View File

@ -3,12 +3,10 @@
#include <fmt/ostream.h> #include <fmt/ostream.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <fstream>
#include <iostream> #include <iostream>
#include "idle-inhibit-unstable-v1-client-protocol.h" #include "idle-inhibit-unstable-v1-client-protocol.h"
#include "util/clara.hpp" #include "util/clara.hpp"
#include "util/json.hpp"
#include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h"
waybar::Client *waybar::Client::inst() { waybar::Client *waybar::Client::inst() {
@ -16,23 +14,6 @@ waybar::Client *waybar::Client::inst() {
return c; return c;
} }
const std::string waybar::Client::getValidPath(const std::vector<std::string> &paths) const {
wordexp_t p;
for (const std::string &path : paths) {
if (wordexp(path.c_str(), &p, 0) == 0) {
if (access(*p.we_wordv, F_OK) == 0) {
std::string result = *p.we_wordv;
wordfree(&p);
return result;
}
wordfree(&p);
}
}
return std::string();
}
void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name, void waybar::Client::handleGlobal(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
auto client = static_cast<Client *>(data); auto client = static_cast<Client *>(data);
@ -70,29 +51,6 @@ void waybar::Client::handleOutput(struct waybar_output &output) {
zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output); zxdg_output_v1_add_listener(output.xdg_output.get(), &xdgOutputListener, &output);
} }
bool waybar::Client::isValidOutput(const Json::Value &config, struct waybar_output &output) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() &&
(output_conf.asString() == output.name || output_conf.asString() == output.identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != output.name &&
config_output.substr(1) != output.identifier;
}
return config_output == output.name || config_output == output.identifier;
}
}
return true;
}
struct waybar::waybar_output &waybar::Client::getOutput(void *addr) { struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
auto it = std::find_if( auto it = std::find_if(
outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; }); outputs_.begin(), outputs_.end(), [&addr](const auto &output) { return &output == addr; });
@ -103,17 +61,7 @@ struct waybar::waybar_output &waybar::Client::getOutput(void *addr) {
} }
std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) { std::vector<Json::Value> waybar::Client::getOutputConfigs(struct waybar_output &output) {
std::vector<Json::Value> configs; return config.getOutputConfigs(output.name, output.identifier);
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, output)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, output)) {
configs.push_back(config_);
}
return configs;
} }
void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) { void waybar::Client::handleOutputDone(void *data, struct zxdg_output_v1 * /*xdg_output*/) {
@ -203,42 +151,14 @@ void waybar::Client::handleDeferredMonitorRemoval(Glib::RefPtr<Gdk::Monitor> mon
outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; }); outputs_.remove_if([&monitor](const auto &output) { return output.monitor == monitor; });
} }
std::tuple<const std::string, const std::string> waybar::Client::getConfigs( const std::string waybar::Client::getStyle(const std::string &style) {
const std::string &config, const std::string &style) const { auto css_file = style.empty() ? Config::findConfigPath({"style.css"}) : style;
auto config_file = config.empty() ? getValidPath({ if (!css_file) {
"$XDG_CONFIG_HOME/waybar/config", throw std::runtime_error("Missing required resource files");
"$HOME/.config/waybar/config",
"$HOME/waybar/config",
"/etc/xdg/waybar/config",
SYSCONFDIR "/xdg/waybar/config",
"./resources/config",
})
: config;
auto css_file = style.empty() ? getValidPath({
"$XDG_CONFIG_HOME/waybar/style.css",
"$HOME/.config/waybar/style.css",
"$HOME/waybar/style.css",
"/etc/xdg/waybar/style.css",
SYSCONFDIR "/xdg/waybar/style.css",
"./resources/style.css",
})
: style;
if (css_file.empty() || config_file.empty()) {
throw std::runtime_error("Missing required resources files");
} }
spdlog::info("Resources files: {}, {}", config_file, css_file); spdlog::info("Using CSS file {}", css_file.value());
return {config_file, css_file}; return css_file.value();
} };
auto waybar::Client::setupConfig(const std::string &config_file) -> void {
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
config_ = parser.parse(str);
}
auto waybar::Client::setupCss(const std::string &css_file) -> void { auto waybar::Client::setupCss(const std::string &css_file) -> void {
css_provider_ = Gtk::CssProvider::create(); css_provider_ = Gtk::CssProvider::create();
@ -277,14 +197,13 @@ void waybar::Client::bindInterfaces() {
int waybar::Client::main(int argc, char *argv[]) { int waybar::Client::main(int argc, char *argv[]) {
bool show_help = false; bool show_help = false;
bool show_version = false; bool show_version = false;
std::string config; std::string config_opt;
std::string style; std::string style_opt;
std::string bar_id;
std::string log_level; std::string log_level;
auto cli = clara::detail::Help(show_help) | auto cli = clara::detail::Help(show_help) |
clara::detail::Opt(show_version)["-v"]["--version"]("Show version") | clara::detail::Opt(show_version)["-v"]["--version"]("Show version") |
clara::detail::Opt(config, "config")["-c"]["--config"]("Config path") | clara::detail::Opt(config_opt, "config")["-c"]["--config"]("Config path") |
clara::detail::Opt(style, "style")["-s"]["--style"]("Style path") | clara::detail::Opt(style_opt, "style")["-s"]["--style"]("Style path") |
clara::detail::Opt( clara::detail::Opt(
log_level, log_level,
"trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") | "trace|debug|info|warning|error|critical|off")["-l"]["--log-level"]("Log level") |
@ -315,8 +234,8 @@ int waybar::Client::main(int argc, char *argv[]) {
throw std::runtime_error("Bar need to run under Wayland"); throw std::runtime_error("Bar need to run under Wayland");
} }
wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj()); wl_display = gdk_wayland_display_get_wl_display(gdk_display->gobj());
auto [config_file, css_file] = getConfigs(config, style); config.load(config_opt);
setupConfig(config_file); auto css_file = getStyle(style_opt);
setupCss(css_file); setupCss(css_file);
bindInterfaces(); bindInterfaces();
gtk_app->hold(); gtk_app->hold();

153
src/config.cpp Normal file
View File

@ -0,0 +1,153 @@
#include "config.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <wordexp.h>
#include <fstream>
#include <stdexcept>
#include "util/json.hpp"
namespace waybar {
const std::vector<std::string> Config::CONFIG_DIRS = {
"$XDG_CONFIG_HOME/waybar/",
"$HOME/.config/waybar/",
"$HOME/waybar/",
"/etc/xdg/waybar/",
SYSCONFDIR "/xdg/waybar/",
"./resources/",
};
std::optional<std::string> tryExpandPath(const std::string &path) {
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);
return result;
}
wordfree(&p);
}
return std::nullopt;
}
std::optional<std::string> Config::findConfigPath(const std::vector<std::string> &names,
const std::vector<std::string> &dirs) {
std::vector<std::string> paths;
for (const auto &dir : dirs) {
for (const auto &name : names) {
if (auto res = tryExpandPath(dir + name); res) {
return res;
}
}
}
return std::nullopt;
}
void Config::setupConfig(Json::Value &dst, const std::string &config_file, int depth) {
if (depth > 100) {
throw std::runtime_error("Aborting due to likely recursive include in config files");
}
std::ifstream file(config_file);
if (!file.is_open()) {
throw std::runtime_error("Can't open config file");
}
std::string str((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
util::JsonParser parser;
Json::Value tmp_config = parser.parse(str);
if (tmp_config.isArray()) {
for (auto &config_part : tmp_config) {
resolveConfigIncludes(config_part, depth);
}
} else {
resolveConfigIncludes(tmp_config, depth);
}
mergeConfig(dst, tmp_config);
}
void Config::resolveConfigIncludes(Json::Value &config, int depth) {
Json::Value includes = config["include"];
if (includes.isArray()) {
for (const auto &include : includes) {
spdlog::info("Including resource file: {}", include.asString());
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);
}
}
void Config::mergeConfig(Json::Value &a_config_, Json::Value &b_config_) {
if (!a_config_) {
// For the first config
a_config_ = b_config_;
} else if (a_config_.isObject() && b_config_.isObject()) {
for (const auto &key : b_config_.getMemberNames()) {
// [] creates key with default value. Use `get` to avoid that.
if (a_config_.get(key, Json::Value::nullSingleton()).isObject() &&
b_config_[key].isObject()) {
mergeConfig(a_config_[key], b_config_[key]);
} else if (!a_config_.isMember(key)) {
// do not allow overriding value set by top or previously included config
a_config_[key] = b_config_[key];
} else {
spdlog::trace("Option {} is already set; ignoring value {}", key, b_config_[key]);
}
}
} else {
spdlog::error("Cannot merge config, conflicting or invalid JSON types");
}
}
bool isValidOutput(const Json::Value &config, const std::string &name,
const std::string &identifier) {
if (config["output"].isArray()) {
for (auto const &output_conf : config["output"]) {
if (output_conf.isString() &&
(output_conf.asString() == name || output_conf.asString() == identifier)) {
return true;
}
}
return false;
} else if (config["output"].isString()) {
auto config_output = config["output"].asString();
if (!config_output.empty()) {
if (config_output.substr(0, 1) == "!") {
return config_output.substr(1) != name && config_output.substr(1) != identifier;
}
return config_output == name || config_output == identifier;
}
}
return true;
}
void Config::load(const std::string &config) {
auto file = config.empty() ? findConfigPath({"config", "config.jsonc"}) : config;
if (!file) {
throw std::runtime_error("Missing required resource files");
}
config_file_ = file.value();
spdlog::info("Using configuration file {}", config_file_);
setupConfig(config_, config_file_, 0);
}
std::vector<Json::Value> Config::getOutputConfigs(const std::string &name,
const std::string &identifier) {
std::vector<Json::Value> configs;
if (config_.isArray()) {
for (auto const &config : config_) {
if (config.isObject() && isValidOutput(config, name, identifier)) {
configs.push_back(config);
}
}
} else if (isValidOutput(config_, name, identifier)) {
configs.push_back(config_);
}
return configs;
}
} // namespace waybar

View File

@ -30,6 +30,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "wlr/taskbar") { if (ref == "wlr/taskbar") {
return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]); return new waybar::modules::wlr::Taskbar(id, bar_, config_[name]);
} }
#ifdef USE_EXPERIMENTAL
if (ref == "wlr/workspaces") {
return new waybar::modules::wlr::WorkspaceManager(id, bar_, config_[name]);
}
#endif
#endif #endif
#ifdef HAVE_RIVER #ifdef HAVE_RIVER
if (ref == "river/tags") { if (ref == "river/tags") {
@ -70,6 +75,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
return new waybar::modules::Backlight(id, config_[name]); return new waybar::modules::Backlight(id, config_[name]);
} }
#endif #endif
#ifdef HAVE_LIBEVDEV
if (ref == "keyboard-state") {
return new waybar::modules::KeyboardState(id, bar_, config_[name]);
}
#endif
#ifdef HAVE_LIBPULSE #ifdef HAVE_LIBPULSE
if (ref == "pulseaudio") { if (ref == "pulseaudio") {
return new waybar::modules::Pulseaudio(id, config_[name]); return new waybar::modules::Pulseaudio(id, config_[name]);
@ -84,6 +94,11 @@ waybar::AModule* waybar::Factory::makeModule(const std::string& name) const {
if (ref == "sndio") { if (ref == "sndio") {
return new waybar::modules::Sndio(id, config_[name]); return new waybar::modules::Sndio(id, config_[name]);
} }
#endif
#ifdef HAVE_GIO_UNIX
if (ref == "inhibitor") {
return new waybar::modules::Inhibitor(id, bar_, config_[name]);
}
#endif #endif
if (ref == "temperature") { if (ref == "temperature") {
return new waybar::modules::Temperature(id, config_[name]); return new waybar::modules::Temperature(id, config_[name]);

19
src/group.cpp Normal file
View File

@ -0,0 +1,19 @@
#include "group.hpp"
#include <fmt/format.h>
#include <util/command.hpp>
namespace waybar {
Group::Group(const std::string& name, const Bar& bar, const Json::Value& config)
: AModule(config, name, "", false, false),
box{bar.vertical ? Gtk::ORIENTATION_HORIZONTAL : Gtk::ORIENTATION_VERTICAL, 0}
{
}
auto Group::update() -> void {
// noop
}
Group::operator Gtk::Widget&() { return box; }
} // namespace waybar

View File

@ -173,7 +173,7 @@ auto waybar::modules::Backlight::update() -> void {
return; return;
} }
const auto percent = best->get_max() == 0 ? 100 : best->get_actual() * 100 / best->get_max(); const uint8_t percent = best->get_max() == 0 ? 100 : round(best->get_actual() * 100.0f / best->get_max());
label_.set_markup(fmt::format( label_.set_markup(fmt::format(
format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent)))); format_, fmt::arg("percent", std::to_string(percent)), fmt::arg("icon", getIcon(percent))));
getState(percent); getState(percent);

View File

@ -161,15 +161,38 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
uint32_t energy_now; uint32_t energy_now;
uint32_t energy_full_design; uint32_t energy_full_design;
std::string _status; std::string _status;
std::ifstream(bat / "status") >> _status; std::getline(std::ifstream(bat / "status"), _status);
auto rate_path = fs::exists(bat / "current_now") ? "current_now" : "power_now";
std::ifstream(bat / rate_path) >> power_now; // Some battery will report current and charge in μA/μAh.
auto now_path = fs::exists(bat / "charge_now") ? "charge_now" : "energy_now"; // Scale these by the voltage to get μW/μWh.
std::ifstream(bat / now_path) >> energy_now; if (fs::exists(bat / "current_now")) {
auto full_path = fs::exists(bat / "charge_full") ? "charge_full" : "energy_full"; uint32_t voltage_now;
std::ifstream(bat / full_path) >> energy_full; uint32_t current_now;
auto full_design_path = fs::exists(bat / "charge_full_design") ? "charge_full_design" : "energy_full_design"; uint32_t charge_now;
std::ifstream(bat / full_design_path) >> energy_full_design; uint32_t charge_full;
uint32_t charge_full_design;
std::ifstream(bat / "voltage_now") >> voltage_now;
std::ifstream(bat / "current_now") >> 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;
} else {
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;
}
// Show the "smallest" status among all batteries // Show the "smallest" status among all batteries
if (status_gt(status, _status)) { if (status_gt(status, _status)) {
@ -216,7 +239,7 @@ const std::tuple<uint8_t, float, std::string, float> waybar::modules::Battery::g
capacity = 100.f; capacity = 100.f;
} }
uint8_t cap = round(capacity); uint8_t cap = round(capacity);
if (cap == 100) { if (cap == 100 && status == "Charging") {
// If we've reached 100% just mark as full as some batteries can stay // If we've reached 100% just mark as full as some batteries can stay
// stuck reporting they're still charging but not yet done // stuck reporting they're still charging but not yet done
status = "Full"; status = "Full";
@ -245,7 +268,7 @@ const std::string waybar::modules::Battery::getAdapterStatus(uint8_t capacity) c
} }
const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) { const std::string waybar::modules::Battery::formatTimeRemaining(float hoursRemaining) {
hoursRemaining = std::fabs(hoursRemaining); hoursRemaining = std::fabs(hoursRemaining);
uint16_t full_hours = static_cast<uint16_t>(hoursRemaining); uint16_t full_hours = static_cast<uint16_t>(hoursRemaining);
uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours)); uint16_t minutes = static_cast<uint16_t>(60 * (hoursRemaining - full_hours));
auto format = std::string("{H} h {M} min"); auto format = std::string("{H} h {M} min");

View File

@ -16,7 +16,7 @@ auto waybar::modules::Bluetooth::update() -> void {
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
auto tooltip_format = config_["tooltip-format"].asString(); auto tooltip_format = config_["tooltip-format"].asString();
auto tooltip_text = fmt::format(tooltip_format, status); auto tooltip_text = fmt::format(tooltip_format, status, fmt::arg("status", status));
label_.set_tooltip_text(tooltip_text); label_.set_tooltip_text(tooltip_text);
} else { } else {
label_.set_tooltip_text(status); label_.set_tooltip_text(status);

View File

@ -1,24 +1,73 @@
#include "modules/clock.hpp" #include "modules/clock.hpp"
#include <time.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#if FMT_VERSION < 60000
#include <fmt/time.h>
#else
#include <fmt/chrono.h>
#endif
#include <ctime>
#include <sstream> #include <sstream>
#include <type_traits> #include <type_traits>
#include "util/ustring_clen.hpp" #include "util/ustring_clen.hpp"
#include "util/waybar_time.hpp"
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
#include <langinfo.h> #include <langinfo.h>
#include <locale.h> #include <locale.h>
#endif #endif
using waybar::modules::waybar_time; using waybar::waybar_time;
waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config) waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
: ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true), fixed_time_zone_(false) { : ALabel(config, "clock", id, "{:%H:%M}", 60, false, false, true),
if (config_["timezone"].isString()) { current_time_zone_idx_(0),
spdlog::warn("As using a timezone, some format args may be missing as the date library havn't got a release since 2018."); is_calendar_in_tooltip_(false),
time_zone_ = date::locate_zone(config_["timezone"].asString()); is_timezoned_list_in_tooltip_(false)
fixed_time_zone_ = true; {
if (config_["timezones"].isArray() && !config_["timezones"].empty()) {
for (const auto& zone_name: config_["timezones"]) {
if (!zone_name.isString() || zone_name.asString().empty()) {
time_zones_.push_back(nullptr);
continue;
}
time_zones_.push_back(
date::locate_zone(
zone_name.asString()
)
);
}
} else if (config_["timezone"].isString() && !config_["timezone"].asString().empty()) {
time_zones_.push_back(
date::locate_zone(
config_["timezone"].asString()
)
);
}
// If all timezones are parsed and no one is good, add nullptr to the timezones vector, to mark that local time should be shown.
if (!time_zones_.size()) {
time_zones_.push_back(nullptr);
}
if (!is_timezone_fixed()) {
spdlog::warn("As using a timezone, some format args may be missing as the date library haven't got a release since 2018.");
}
// Check if a particular placeholder is present in the tooltip format, to know what to calculate on update.
if (config_["tooltip-format"].isString()) {
std::string trimmed_format = config_["tooltip-format"].asString();
trimmed_format.erase(std::remove_if(trimmed_format.begin(),
trimmed_format.end(),
[](unsigned char x){return std::isspace(x);}),
trimmed_format.end());
if (trimmed_format.find("{" + kCalendarPlaceholder + "}") != std::string::npos) {
is_calendar_in_tooltip_ = true;
}
if (trimmed_format.find("{" + KTimezonedTimeListPlaceholder + "}") != std::string::npos) {
is_timezoned_list_in_tooltip_ = true;
}
} }
if (config_["locale"].isString()) { if (config_["locale"].isString()) {
@ -36,38 +85,46 @@ waybar::modules::Clock::Clock(const std::string& id, const Json::Value& config)
}; };
} }
const date::time_zone* waybar::modules::Clock::current_timezone() {
return time_zones_[current_time_zone_idx_] ? time_zones_[current_time_zone_idx_] : date::current_zone();
}
bool waybar::modules::Clock::is_timezone_fixed() {
return time_zones_[current_time_zone_idx_] != nullptr;
}
auto waybar::modules::Clock::update() -> void { auto waybar::modules::Clock::update() -> void {
if (!fixed_time_zone_) { auto time_zone = current_timezone();
// Time zone can change. Be sure to pick that. auto now = std::chrono::system_clock::now();
time_zone_ = date::current_zone();
}
auto now = std::chrono::system_clock::now();
waybar_time wtime = {locale_, waybar_time wtime = {locale_,
date::make_zoned(time_zone_, date::floor<std::chrono::seconds>(now))}; date::make_zoned(time_zone, date::floor<std::chrono::seconds>(now))};
std::string text = "";
std::string text; if (!is_timezone_fixed()) {
if (!fixed_time_zone_) {
// As date dep is not fully compatible, prefer fmt // As date dep is not fully compatible, prefer fmt
tzset(); tzset();
auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now)); auto localtime = fmt::localtime(std::chrono::system_clock::to_time_t(now));
text = fmt::format(format_, localtime); text = fmt::format(locale_, format_, localtime);
label_.set_markup(text);
} else { } else {
text = fmt::format(format_, wtime); text = fmt::format(format_, wtime);
label_.set_markup(text);
} }
label_.set_markup(text);
if (tooltipEnabled()) { if (tooltipEnabled()) {
if (config_["tooltip-format"].isString()) { if (config_["tooltip-format"].isString()) {
const auto calendar = calendar_text(wtime); std::string calendar_lines = "";
auto tooltip_format = config_["tooltip-format"].asString(); std::string timezoned_time_lines = "";
auto tooltip_text = fmt::format(tooltip_format, wtime, fmt::arg("calendar", calendar)); if (is_calendar_in_tooltip_) {
label_.set_tooltip_markup(tooltip_text); calendar_lines = calendar_text(wtime);
} else { }
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); label_.set_tooltip_markup(text);
} }
} }
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }
@ -82,22 +139,16 @@ bool waybar::modules::Clock::handleScroll(GdkEventScroll *e) {
if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) { if (dir != SCROLL_DIR::UP && dir != SCROLL_DIR::DOWN) {
return true; return true;
} }
if (!config_["timezones"].isArray() || config_["timezones"].empty()) { if (time_zones_.size() == 1) {
return true; return true;
} }
auto nr_zones = config_["timezones"].size();
auto nr_zones = time_zones_.size();
if (dir == SCROLL_DIR::UP) { if (dir == SCROLL_DIR::UP) {
size_t new_idx = time_zone_idx_ + 1; size_t new_idx = current_time_zone_idx_ + 1;
time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx; current_time_zone_idx_ = new_idx == nr_zones ? 0 : new_idx;
} else { } else {
time_zone_idx_ = time_zone_idx_ == 0 ? nr_zones - 1 : time_zone_idx_ - 1; current_time_zone_idx_ = current_time_zone_idx_ == 0 ? nr_zones - 1 : current_time_zone_idx_ - 1;
}
auto zone_name = config_["timezones"][time_zone_idx_];
if (!zone_name.isString() || zone_name.empty()) {
fixed_time_zone_ = false;
} else {
time_zone_ = date::locate_zone(zone_name.asString());
fixed_time_zone_ = true;
} }
update(); update();
@ -168,6 +219,26 @@ auto waybar::modules::Clock::weekdays_header(const date::weekday& first_dow, std
os << "\n"; os << "\n";
} }
auto waybar::modules::Clock::timezones_text(std::chrono::_V2::system_clock::time_point *now) -> std::string {
if (time_zones_.size() == 1) {
return "";
}
std::stringstream os;
waybar_time wtime;
for (size_t time_zone_idx = 0; time_zone_idx < time_zones_.size(); ++time_zone_idx) {
if (static_cast<int>(time_zone_idx) == current_time_zone_idx_) {
continue;
}
const date::time_zone* timezone = time_zones_[time_zone_idx];
if (!timezone) {
timezone = date::current_zone();
}
wtime = {locale_, date::make_zoned(timezone, date::floor<std::chrono::seconds>(*now))};
os << fmt::format(format_, wtime) << "\n";
}
return os.str();
}
#ifdef HAVE_LANGINFO_1STDAY #ifdef HAVE_LANGINFO_1STDAY
template <auto fn> template <auto fn>
using deleter_from_fn = std::integral_constant<decltype(fn), fn>; using deleter_from_fn = std::integral_constant<decltype(fn), fn>;
@ -191,11 +262,3 @@ auto waybar::modules::Clock::first_day_of_week() -> date::weekday {
#endif #endif
return date::Sunday; return date::Sunday;
} }
template <>
struct fmt::formatter<waybar_time> : fmt::formatter<std::tm> {
template <typename FormatContext>
auto format(const waybar_time& t, FormatContext& ctx) {
return format_to(ctx.out(), "{}", date::format(t.locale, fmt::to_string(tm_format), t.ztime));
}
};

View File

@ -1,5 +1,14 @@
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
// In the 80000 version of fmt library authors decided to optimize imports
// and moved declarations required for fmt::dynamic_format_arg_store in new
// header fmt/args.h
#if (FMT_VERSION >= 80000)
#include <fmt/args.h>
#else
#include <fmt/core.h>
#endif
waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config) waybar::modules::Cpu::Cpu(const std::string& id, const Json::Value& config)
: ALabel(config, "cpu", id, "{usage}%", 10) { : ALabel(config, "cpu", id, "{usage}%", 10) {
thread_ = [this] { thread_ = [this] {
@ -17,7 +26,8 @@ auto waybar::modules::Cpu::update() -> void {
label_.set_tooltip_text(tooltip); label_.set_tooltip_text(tooltip);
} }
auto format = format_; auto format = format_;
auto state = getState(cpu_usage); auto total_usage = cpu_usage.empty() ? 0 : cpu_usage[0];
auto state = getState(total_usage);
if (!state.empty() && config_["format-" + state].isString()) { if (!state.empty() && config_["format-" + state].isString()) {
format = config_["format-" + state].asString(); format = config_["format-" + state].asString();
} }
@ -26,12 +36,23 @@ auto waybar::modules::Cpu::update() -> void {
event_box_.hide(); event_box_.hide();
} else { } else {
event_box_.show(); event_box_.show();
label_.set_markup(fmt::format(format, auto icons = std::vector<std::string>{state};
fmt::arg("load", cpu_load), fmt::dynamic_format_arg_store<fmt::format_context> store;
fmt::arg("usage", cpu_usage), store.push_back(fmt::arg("load", cpu_load));
fmt::arg("max_frequency", max_frequency), store.push_back(fmt::arg("load", cpu_load));
fmt::arg("min_frequency", min_frequency), store.push_back(fmt::arg("usage", total_usage));
fmt::arg("avg_frequency", avg_frequency))); store.push_back(fmt::arg("icon", getIcon(total_usage, icons)));
store.push_back(fmt::arg("max_frequency", max_frequency));
store.push_back(fmt::arg("min_frequency", min_frequency));
store.push_back(fmt::arg("avg_frequency", avg_frequency));
for (size_t i = 1; i < cpu_usage.size(); ++i) {
auto core_i = i - 1;
auto core_format = fmt::format("usage{}", core_i);
store.push_back(fmt::arg(core_format.c_str(), cpu_usage[i]));
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));
} }
// Call parent update // Call parent update
@ -41,19 +62,19 @@ auto waybar::modules::Cpu::update() -> void {
double waybar::modules::Cpu::getCpuLoad() { double waybar::modules::Cpu::getCpuLoad() {
double load[1]; double load[1];
if (getloadavg(load, 1) != -1) { if (getloadavg(load, 1) != -1) {
return load[0]; return std::ceil(load[0] * 100.0) / 100.0;
} }
throw std::runtime_error("Can't get Cpu load"); throw std::runtime_error("Can't get Cpu load");
} }
std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() { std::tuple<std::vector<uint16_t>, std::string> waybar::modules::Cpu::getCpuUsage() {
if (prev_times_.empty()) { if (prev_times_.empty()) {
prev_times_ = parseCpuinfo(); prev_times_ = parseCpuinfo();
std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::this_thread::sleep_for(std::chrono::milliseconds(100));
} }
std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo(); std::vector<std::tuple<size_t, size_t>> curr_times = parseCpuinfo();
std::string tooltip; std::string tooltip;
uint16_t usage = 0; std::vector<uint16_t> usage;
for (size_t i = 0; i < curr_times.size(); ++i) { for (size_t i = 0; i < curr_times.size(); ++i) {
auto [curr_idle, curr_total] = curr_times[i]; auto [curr_idle, curr_total] = curr_times[i];
auto [prev_idle, prev_total] = prev_times_[i]; auto [prev_idle, prev_total] = prev_times_[i];
@ -61,11 +82,11 @@ std::tuple<uint16_t, std::string> waybar::modules::Cpu::getCpuUsage() {
const float delta_total = curr_total - prev_total; const float delta_total = curr_total - prev_total;
uint16_t tmp = 100 * (1 - delta_idle / delta_total); uint16_t tmp = 100 * (1 - delta_idle / delta_total);
if (i == 0) { if (i == 0) {
usage = tmp;
tooltip = fmt::format("Total: {}%", tmp); tooltip = fmt::format("Total: {}%", tmp);
} else { } else {
tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp); tooltip = tooltip + fmt::format("\nCore{}: {}%", i - 1, tmp);
} }
usage.push_back(tmp);
} }
prev_times_ = curr_times; prev_times_ = curr_times;
return {usage, tooltip}; return {usage, tooltip};

View File

@ -1,3 +1,4 @@
#include <filesystem>
#include "modules/cpu.hpp" #include "modules/cpu.hpp"
std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() { std::vector<std::tuple<size_t, size_t>> waybar::modules::Cpu::parseCpuinfo() {
@ -46,5 +47,31 @@ std::vector<float> waybar::modules::Cpu::parseCpuFrequencies() {
frequencies.push_back(frequency); frequencies.push_back(frequency);
} }
info.close(); info.close();
if (frequencies.size() <= 0) {
std::string cpufreq_dir = "/sys/devices/system/cpu/cpufreq";
if (std::filesystem::exists(cpufreq_dir)) {
std::vector<std::string> frequency_files = {
"/cpuinfo_min_freq",
"/cpuinfo_max_freq"
};
for (auto& p: std::filesystem::directory_iterator(cpufreq_dir)) {
for (auto freq_file: frequency_files) {
std::string freq_file_path = p.path().string() + freq_file;
if (std::filesystem::exists(freq_file_path)) {
std::string freq_value;
std::ifstream freq(freq_file_path);
if (freq.is_open()) {
getline(freq, freq_value);
float frequency = std::strtol(freq_value.c_str(), nullptr, 10);
frequencies.push_back(frequency / 1000);
freq.close();
}
}
}
}
}
}
return frequencies; return frequencies;
} }

View File

@ -45,9 +45,9 @@ auto waybar::modules::Disk::update() -> void {
} }
auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true); auto free = pow_format(stats.f_bavail * stats.f_frsize, "B", true);
auto used = pow_format((stats.f_blocks - stats.f_bavail) * stats.f_frsize, "B", true); auto used = pow_format((stats.f_blocks - stats.f_bfree) * stats.f_frsize, "B", true);
auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true); auto total = pow_format(stats.f_blocks * stats.f_frsize, "B", true);
auto percentage_used = (stats.f_blocks - stats.f_bavail) * 100 / stats.f_blocks; auto percentage_used = (stats.f_blocks - stats.f_bfree) * 100 / stats.f_blocks;
auto format = format_; auto format = format_;
auto state = getState(percentage_used); auto state = getState(percentage_used);

175
src/modules/inhibitor.cpp Normal file
View File

@ -0,0 +1,175 @@
#include "modules/inhibitor.hpp"
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include <spdlog/spdlog.h>
namespace {
using DBus = std::unique_ptr<GDBusConnection, void(*)(GDBusConnection*)>;
auto dbus() -> DBus {
GError *error = nullptr;
GDBusConnection* connection =
g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
if (error) {
spdlog::error("g_bus_get_sync() failed: {}", error->message);
g_error_free(error);
connection = nullptr;
}
auto destructor = [](GDBusConnection* connection) {
GError *error = nullptr;
g_dbus_connection_close_sync(connection, nullptr, &error);
if (error) {
spdlog::error(
"g_bus_connection_close_sync failed(): {}",
error->message);
g_error_free(error);
}
};
return DBus{connection, destructor};
}
auto getLocks(const DBus& bus, const std::string& inhibitors) -> int {
GError *error = nullptr;
GUnixFDList* fd_list;
int handle;
auto reply = g_dbus_connection_call_with_unix_fd_list_sync(bus.get(),
"org.freedesktop.login1",
"/org/freedesktop/login1",
"org.freedesktop.login1.Manager",
"Inhibit",
g_variant_new(
"(ssss)",
inhibitors.c_str(),
"waybar",
"Asked by user",
"block"),
G_VARIANT_TYPE("(h)"),
G_DBUS_CALL_FLAGS_NONE,
-1,
nullptr,
&fd_list,
nullptr,
&error);
if (error) {
spdlog::error(
"g_dbus_connection_call_with_unix_fd_list_sync() failed: {}",
error->message);
g_error_free(error);
handle = -1;
} else {
gint index;
g_variant_get(reply, "(h)", &index);
g_variant_unref(reply);
handle = g_unix_fd_list_get(fd_list, index, nullptr);
g_object_unref(fd_list);
}
return handle;
}
auto checkInhibitor(const std::string& inhibitor) -> const std::string& {
static const auto inhibitors = std::array{
"idle",
"shutdown",
"sleep",
"handle-power-key",
"handle-suspend-key",
"handle-hibernate-key",
"handle-lid-switch"
};
if (std::find(inhibitors.begin(), inhibitors.end(), inhibitor)
== inhibitors.end()) {
throw std::runtime_error("invalid logind inhibitor " + inhibitor);
}
return inhibitor;
}
auto getInhibitors(const Json::Value& config) -> std::string {
std::string inhibitors = "idle";
if (config["what"].empty()) {
return inhibitors;
}
if (config["what"].isString()) {
return checkInhibitor(config["what"].asString());
}
if (config["what"].isArray()) {
inhibitors = checkInhibitor(config["what"][0].asString());
for (decltype(config["what"].size()) i = 1; i < config["what"].size(); ++i) {
inhibitors += ":" + checkInhibitor(config["what"][i].asString());
}
return inhibitors;
}
return inhibitors;
}
}
namespace waybar::modules {
Inhibitor::Inhibitor(const std::string& id, const Bar& bar,
const Json::Value& config)
: ALabel(config, "inhibitor", id, "{status}", true),
dbus_(::dbus()),
inhibitors_(::getInhibitors(config)) {
event_box_.add_events(Gdk::BUTTON_PRESS_MASK);
event_box_.signal_button_press_event().connect(
sigc::mem_fun(*this, &Inhibitor::handleToggle));
dp.emit();
}
Inhibitor::~Inhibitor() {
if (handle_ != -1) {
::close(handle_);
}
}
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);
if (tooltipEnabled()) {
label_.set_tooltip_text(status_text);
}
return ALabel::update();
}
auto Inhibitor::handleToggle(GdkEventButton* const& e) -> bool {
if (e->button == 1) {
if (activated()) {
::close(handle_);
handle_ = -1;
} else {
handle_ = ::getLocks(dbus_, inhibitors_);
if (handle_ == -1) {
spdlog::error("cannot get inhibitor locks");
}
}
}
return ALabel::handleToggle(e);
}
} // waybar::modules

View File

@ -0,0 +1,205 @@
#include "modules/keyboard_state.hpp"
#include <errno.h>
#include <filesystem>
#include <spdlog/spdlog.h>
#include <string.h>
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
}
class errno_error : public std::runtime_error {
public:
int code;
errno_error(int code, const std::string& msg)
: std::runtime_error(getErrorMsg(code, msg.c_str())),
code(code) {}
errno_error(int code, const char* msg)
: std::runtime_error(getErrorMsg(code, msg)),
code(code) {}
private:
static auto getErrorMsg(int err, const char* msg) -> std::string {
std::string error_msg{msg};
error_msg += ": ";
#if (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 32)
// strerrorname_np gets the error code's name; it's nice to have, but it's a recent GNU extension
const auto errno_name = strerrorname_np(err);
error_msg += errno_name;
error_msg += " ";
#endif
const auto errno_str = strerror(err);
error_msg += errno_str;
return error_msg;
}
};
auto openFile(const std::string& path, int flags) -> int {
int fd = open(path.c_str(), flags);
if (fd < 0) {
if (errno == EACCES) {
throw errno_error(errno, "Can't open " + path + " (are you in the input group?)");
} else {
throw errno_error(errno, "Can't open " + path);
}
}
return fd;
}
auto closeFile(int fd) -> void {
int res = close(fd);
if (res < 0) {
throw errno_error(errno, "Can't close file");
}
}
auto openDevice(int fd) -> libevdev* {
libevdev* dev;
int err = libevdev_new_from_fd(fd, &dev);
if (err < 0) {
throw errno_error(-err, "Can't create libevdev device");
}
return dev;
}
auto supportsLockStates(const libevdev* dev) -> bool {
return libevdev_has_event_type(dev, EV_LED)
&& libevdev_has_event_code(dev, EV_LED, LED_NUML)
&& libevdev_has_event_code(dev, EV_LED, LED_CAPSL)
&& libevdev_has_event_code(dev, EV_LED, LED_SCROLLL);
}
waybar::modules::KeyboardState::KeyboardState(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "keyboard-state", id, false, !config["disable-scroll"].asBool()),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
numlock_label_(""),
capslock_label_(""),
numlock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["numlock"].isString() ? config_["format"]["numlock"].asString()
: "{name} {icon}"),
capslock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["capslock"].isString() ? config_["format"]["capslock"].asString()
: "{name} {icon}"),
scrolllock_format_(config_["format"].isString() ? config_["format"].asString()
: config_["format"]["scrolllock"].isString() ? config_["format"]["scrolllock"].asString()
: "{name} {icon}"),
interval_(std::chrono::seconds(config_["interval"].isUInt() ? config_["interval"].asUInt() : 1)),
icon_locked_(config_["format-icons"]["locked"].isString()
? config_["format-icons"]["locked"].asString()
: "locked"),
icon_unlocked_(config_["format-icons"]["unlocked"].isString()
? config_["format-icons"]["unlocked"].asString()
: "unlocked"),
fd_(0),
dev_(nullptr) {
box_.set_name("keyboard-state");
if (config_["numlock"].asBool()) {
box_.pack_end(numlock_label_, false, false, 0);
}
if (config_["capslock"].asBool()) {
box_.pack_end(capslock_label_, false, false, 0);
}
if (config_["scrolllock"].asBool()) {
box_.pack_end(scrolllock_label_, false, false, 0);
}
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
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");
}
}
thread_ = [this] {
dp.emit();
thread_.sleep_for(interval_);
};
}
waybar::modules::KeyboardState::~KeyboardState() {
libevdev_free(dev_);
try {
closeFile(fd_);
} catch (const std::runtime_error& e) {
spdlog::warn(e.what());
}
}
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);
}
}
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;
Gtk::Label& label;
const std::string& format;
const char* name;
} label_states[] = {
{(bool) numl, numlock_label_, numlock_format_, "Num"},
{(bool) capsl, capslock_label_, capslock_format_, "Caps"},
{(bool) scrolll, scrolllock_label_, scrolllock_format_, "Scroll"},
};
for (auto& label_state : label_states) {
std::string text;
text = fmt::format(label_state.format,
fmt::arg("icon", label_state.state ? icon_locked_ : icon_unlocked_),
fmt::arg("name", label_state.name));
label_state.label.set_markup(text);
if (label_state.state) {
label_state.label.get_style_context()->add_class("locked");
} else {
label_state.label.get_style_context()->remove_class("locked");
}
}
AModule::update();
}

View File

@ -12,21 +12,36 @@ auto waybar::modules::Memory::update() -> void {
parseMeminfo(); parseMeminfo();
unsigned long memtotal = meminfo_["MemTotal"]; unsigned long memtotal = meminfo_["MemTotal"];
unsigned long swaptotal = 0;
if (meminfo_.count("SwapTotal")) {
swaptotal = meminfo_["SwapTotal"];
}
unsigned long memfree; unsigned long memfree;
unsigned long swapfree = 0;
if (meminfo_.count("SwapFree")) {
swapfree = meminfo_["SwapFree"];
}
if (meminfo_.count("MemAvailable")) { if (meminfo_.count("MemAvailable")) {
// New kernels (3.4+) have an accurate available memory field. // New kernels (3.4+) have an accurate available memory field.
memfree = meminfo_["MemAvailable"]; memfree = meminfo_["MemAvailable"] + meminfo_["zfs_size"];
} else { } else {
// Old kernel; give a best-effort approximation of available memory. // Old kernel; give a best-effort approximation of available memory.
memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] + memfree = meminfo_["MemFree"] + meminfo_["Buffers"] + meminfo_["Cached"] +
meminfo_["SReclaimable"] - meminfo_["Shmem"]; meminfo_["SReclaimable"] - meminfo_["Shmem"] + meminfo_["zfs_size"];
} }
if (memtotal > 0 && memfree >= 0) { if (memtotal > 0 && memfree >= 0) {
auto total_ram_gigabytes = memtotal / std::pow(1024, 2); auto total_ram_gigabytes = memtotal / std::pow(1024, 2);
auto total_swap_gigabytes = swaptotal / std::pow(1024, 2);
int used_ram_percentage = 100 * (memtotal - memfree) / memtotal; 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_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_ram_gigabytes = memfree / std::pow(1024, 2);
auto available_swap_gigabytes = swapfree / std::pow(1024, 2);
auto format = format_; auto format = format_;
auto state = getState(used_ram_percentage); auto state = getState(used_ram_percentage);
@ -38,12 +53,18 @@ auto waybar::modules::Memory::update() -> void {
event_box_.hide(); event_box_.hide();
} else { } else {
event_box_.show(); event_box_.show();
auto icons = std::vector<std::string>{state};
label_.set_markup(fmt::format(format, label_.set_markup(fmt::format(format,
used_ram_percentage, used_ram_percentage,
fmt::arg("icon", getIcon(used_ram_percentage, icons)),
fmt::arg("total", total_ram_gigabytes), fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage),
fmt::arg("used", used_ram_gigabytes), fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes))); fmt::arg("swapUsed", used_swap_gigabytes),
fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} }
if (tooltipEnabled()) { if (tooltipEnabled()) {
@ -52,9 +73,13 @@ auto waybar::modules::Memory::update() -> void {
label_.set_tooltip_text(fmt::format(tooltip_format, label_.set_tooltip_text(fmt::format(tooltip_format,
used_ram_percentage, used_ram_percentage,
fmt::arg("total", total_ram_gigabytes), fmt::arg("total", total_ram_gigabytes),
fmt::arg("swapTotal", total_swap_gigabytes),
fmt::arg("percentage", used_ram_percentage), fmt::arg("percentage", used_ram_percentage),
fmt::arg("swapPercentage", used_swap_percentage),
fmt::arg("used", used_ram_gigabytes), fmt::arg("used", used_ram_gigabytes),
fmt::arg("avail", available_ram_gigabytes))); fmt::arg("swapUsed", used_swap_gigabytes),
fmt::arg("avail", available_ram_gigabytes),
fmt::arg("swapAvail", available_swap_gigabytes)));
} else { } else {
label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1)); label_.set_tooltip_text(fmt::format("{:.{}f}GiB used", used_ram_gigabytes, 1));
} }

View File

@ -1,8 +1,29 @@
#include "modules/memory.hpp" #include "modules/memory.hpp"
static unsigned zfsArcSize() {
std::ifstream zfs_arc_stats{"/proc/spl/kstat/zfs/arcstats"};
if (zfs_arc_stats.is_open()) {
std::string name;
std::string type;
unsigned long data{0};
std::string line;
while (std::getline(zfs_arc_stats, line)) {
std::stringstream(line) >> name >> type >> data;
if (name == "size") {
return data / 1024; // convert to kB
}
}
}
return 0;
}
void waybar::modules::Memory::parseMeminfo() { void waybar::modules::Memory::parseMeminfo() {
const std::string data_dir_ = "/proc/meminfo"; const std::string data_dir_ = "/proc/meminfo";
std::ifstream info(data_dir_); std::ifstream info(data_dir_);
if (!info.is_open()) { if (!info.is_open()) {
throw std::runtime_error("Can't open " + data_dir_); throw std::runtime_error("Can't open " + data_dir_);
} }
@ -17,4 +38,6 @@ void waybar::modules::Memory::parseMeminfo() {
int64_t value = std::stol(line.substr(posDelim + 1)); int64_t value = std::stol(line.substr(posDelim + 1));
meminfo_[name] = value; meminfo_[name] = value;
} }
meminfo_["zfs_size"] = zfsArcSize();
} }

View File

@ -100,7 +100,7 @@ void waybar::modules::MPD::setLabel() {
auto format = format_; auto format = format_;
Glib::ustring artist, album_artist, album, title; Glib::ustring artist, album_artist, album, title;
std::string date; std::string date;
int song_pos = 0, queue_length = 0; int song_pos = 0, queue_length = 0, volume = 0;
std::chrono::seconds elapsedTime, totalTime; std::chrono::seconds elapsedTime, totalTime;
std::string stateIcon = ""; std::string stateIcon = "";
@ -129,7 +129,11 @@ void waybar::modules::MPD::setLabel() {
album = getTag(MPD_TAG_ALBUM); album = getTag(MPD_TAG_ALBUM);
title = getTag(MPD_TAG_TITLE); title = getTag(MPD_TAG_TITLE);
date = getTag(MPD_TAG_DATE); date = getTag(MPD_TAG_DATE);
song_pos = mpd_status_get_song_pos(status_.get()); song_pos = mpd_status_get_song_pos(status_.get()) + 1;
volume = mpd_status_get_volume(status_.get());
if (volume < 0) {
volume = 0;
}
queue_length = mpd_status_get_queue_length(status_.get()); queue_length = mpd_status_get_queue_length(status_.get());
elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get())); elapsedTime = std::chrono::seconds(mpd_status_get_elapsed_time(status_.get()));
totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get())); totalTime = std::chrono::seconds(mpd_status_get_total_time(status_.get()));
@ -156,6 +160,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("album", Glib::Markup::escape_text(album).raw()), fmt::arg("album", Glib::Markup::escape_text(album).raw()),
fmt::arg("title", Glib::Markup::escape_text(title).raw()), fmt::arg("title", Glib::Markup::escape_text(title).raw()),
fmt::arg("date", Glib::Markup::escape_text(date).raw()), fmt::arg("date", Glib::Markup::escape_text(date).raw()),
fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("songPosition", song_pos),
@ -180,6 +185,7 @@ void waybar::modules::MPD::setLabel() {
fmt::arg("album", album.raw()), fmt::arg("album", album.raw()),
fmt::arg("title", title.raw()), fmt::arg("title", title.raw()),
fmt::arg("date", date), fmt::arg("date", date),
fmt::arg("volume", volume),
fmt::arg("elapsedTime", elapsedTime), fmt::arg("elapsedTime", elapsedTime),
fmt::arg("totalTime", totalTime), fmt::arg("totalTime", totalTime),
fmt::arg("songPosition", song_pos), fmt::arg("songPosition", song_pos),

File diff suppressed because it is too large Load Diff

View File

@ -54,7 +54,9 @@ void waybar::modules::Pulseaudio::contextStateCb(pa_context *c, void *data) {
c, c,
static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) | static_cast<enum pa_subscription_mask>(static_cast<int>(PA_SUBSCRIPTION_MASK_SERVER) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) | static_cast<int>(PA_SUBSCRIPTION_MASK_SINK) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE)), static_cast<int>(PA_SUBSCRIPTION_MASK_SINK_INPUT) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE) |
static_cast<int>(PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT)),
nullptr, nullptr,
nullptr); nullptr);
break; break;
@ -79,6 +81,13 @@ bool waybar::modules::Pulseaudio::handleScroll(GdkEventScroll *e) {
if (dir == SCROLL_DIR::NONE) { if (dir == SCROLL_DIR::NONE) {
return true; return true;
} }
if (config_["reverse-scrolling"].asInt() == 1){
if (dir == SCROLL_DIR::UP) {
dir = SCROLL_DIR::DOWN;
} else if (dir == SCROLL_DIR::DOWN) {
dir = SCROLL_DIR::UP;
}
}
double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100; double volume_tick = static_cast<double>(PA_VOLUME_NORM) / 100;
pa_volume_t change = volume_tick; pa_volume_t change = volume_tick;
pa_cvolume pa_volume = pa_volume_; pa_cvolume pa_volume = pa_volume_;
@ -114,8 +123,12 @@ void waybar::modules::Pulseaudio::subscribeCb(pa_context * conte
pa_context_get_server_info(context, serverInfoCb, data); pa_context_get_server_info(context, serverInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK) { } else if (facility == PA_SUBSCRIPTION_EVENT_SINK) {
pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data); pa_context_get_sink_info_by_index(context, idx, sinkInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SINK_INPUT) {
pa_context_get_sink_info_list(context, sinkInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) { } else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE) {
pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data); pa_context_get_source_info_by_index(context, idx, sourceInfoCb, data);
} else if (facility == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT) {
pa_context_get_source_info_list(context, sourceInfoCb, data);
} }
} }
@ -151,8 +164,24 @@ void waybar::modules::Pulseaudio::sourceInfoCb(pa_context * /*context*/, const p
*/ */
void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i, void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_sink_info *i,
int /*eol*/, void *data) { int /*eol*/, void *data) {
if (i == nullptr)
return;
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
if (i != nullptr && pa->default_sink_name_ == i->name) { if (pa->current_sink_name_ == i->name) {
if (i->state != PA_SINK_RUNNING) {
pa->current_sink_running_ = false;
} else {
pa->current_sink_running_ = true;
}
}
if (!pa->current_sink_running_ && i->state == PA_SINK_RUNNING) {
pa->current_sink_name_ = i->name;
pa->current_sink_running_ = true;
}
if (pa->current_sink_name_ == i->name) {
pa->pa_volume_ = i->volume; pa->pa_volume_ = i->volume;
float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM}; float volume = static_cast<float>(pa_cvolume_avg(&(pa->pa_volume_))) / float{PA_VOLUME_NORM};
pa->sink_idx_ = i->index; pa->sink_idx_ = i->index;
@ -175,11 +204,11 @@ void waybar::modules::Pulseaudio::sinkInfoCb(pa_context * /*context*/, const pa_
void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i, void waybar::modules::Pulseaudio::serverInfoCb(pa_context *context, const pa_server_info *i,
void *data) { void *data) {
auto pa = static_cast<waybar::modules::Pulseaudio *>(data); auto pa = static_cast<waybar::modules::Pulseaudio *>(data);
pa->default_sink_name_ = i->default_sink_name; pa->current_sink_name_ = i->default_sink_name;
pa->default_source_name_ = i->default_source_name; pa->default_source_name_ = i->default_source_name;
pa_context_get_sink_info_by_name(context, i->default_sink_name, sinkInfoCb, data); pa_context_get_sink_info_list(context, sinkInfoCb, data);
pa_context_get_source_info_by_name(context, i->default_source_name, sourceInfoCb, data); pa_context_get_source_info_list(context, sourceInfoCb, data);
} }
static const std::array<std::string, 9> ports = { static const std::array<std::string, 9> ports = {
@ -194,22 +223,26 @@ static const std::array<std::string, 9> ports = {
"phone", "phone",
}; };
const std::string waybar::modules::Pulseaudio::getPortIcon() const { const std::vector<std::string> waybar::modules::Pulseaudio::getPulseIcon() const {
std::vector<std::string> res = {current_sink_name_, default_source_name_};
std::string nameLC = port_name_ + form_factor_; std::string nameLC = port_name_ + form_factor_;
std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower); std::transform(nameLC.begin(), nameLC.end(), nameLC.begin(), ::tolower);
for (auto const &port : ports) { for (auto const &port : ports) {
if (nameLC.find(port) != std::string::npos) { if (nameLC.find(port) != std::string::npos) {
return port; res.push_back(port);
return res;
} }
} }
return port_name_; return res;
} }
auto waybar::modules::Pulseaudio::update() -> void { auto waybar::modules::Pulseaudio::update() -> void {
auto format = format_; auto format = format_;
std::string tooltip_format;
if (!alt_) { if (!alt_) {
std::string format_name = "format"; std::string format_name = "format";
if (monitor_.find("a2dp_sink") != std::string::npos) { if (monitor_.find("a2dp_sink") != std::string::npos || // PulseAudio
monitor_.find("a2dp-sink") != std::string::npos) { // PipeWire
format_name = format_name + "-bluetooth"; format_name = format_name + "-bluetooth";
label_.get_style_context()->add_class("bluetooth"); label_.get_style_context()->add_class("bluetooth");
} else { } else {
@ -222,28 +255,53 @@ auto waybar::modules::Pulseaudio::update() -> void {
} }
format_name = format_name + "-muted"; format_name = format_name + "-muted";
label_.get_style_context()->add_class("muted"); label_.get_style_context()->add_class("muted");
label_.get_style_context()->add_class("sink-muted");
} else { } else {
label_.get_style_context()->remove_class("muted"); label_.get_style_context()->remove_class("muted");
label_.get_style_context()->remove_class("sink-muted");
} }
format = format =
config_[format_name].isString() ? config_[format_name].asString() : format; config_[format_name].isString() ? config_[format_name].asString() : format;
} }
// TODO: find a better way to split source/sink // TODO: find a better way to split source/sink
std::string format_source = "{volume}%"; std::string format_source = "{volume}%";
if (source_muted_ && config_["format-source-muted"].isString()) { if (source_muted_) {
format_source = config_["format-source-muted"].asString(); label_.get_style_context()->add_class("source-muted");
} else if (!source_muted_ && config_["format-source"].isString()) { if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString(); format_source = config_["format-source-muted"].asString();
}
} else {
label_.get_style_context()->remove_class("source-muted");
if (config_["format-source-muted"].isString()) {
format_source = config_["format-source"].asString();
}
} }
format_source = fmt::format(format_source, fmt::arg("volume", source_volume_)); format_source = fmt::format(format_source, fmt::arg("volume", source_volume_));
label_.set_markup(fmt::format(format, label_.set_markup(fmt::format(format,
fmt::arg("desc", desc_), fmt::arg("desc", desc_),
fmt::arg("volume", volume_), fmt::arg("volume", volume_),
fmt::arg("format_source", format_source), fmt::arg("format_source", format_source),
fmt::arg("icon", getIcon(volume_, getPortIcon())))); fmt::arg("source_volume", source_volume_),
fmt::arg("source_desc", source_desc_),
fmt::arg("icon", getIcon(volume_, getPulseIcon()))));
getState(volume_); getState(volume_);
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(desc_); if (tooltip_format.empty() && config_["tooltip-format"].isString()) {
tooltip_format = config_["tooltip-format"].asString();
}
if (!tooltip_format.empty()) {
label_.set_tooltip_text(fmt::format(
tooltip_format,
fmt::arg("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_);
}
} }
// Call parent update // Call parent update

View File

@ -7,7 +7,6 @@
#include "client.hpp" #include "client.hpp"
#include "modules/river/tags.hpp" #include "modules/river/tags.hpp"
#include "river-status-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
namespace waybar::modules::river { namespace waybar::modules::river {
@ -22,29 +21,71 @@ static void listen_view_tags(void *data, struct zriver_output_status_v1 *zriver_
static_cast<Tags *>(data)->handle_view_tags(tags); static_cast<Tags *>(data)->handle_view_tags(tags);
} }
static void listen_urgent_tags(void *data, struct zriver_output_status_v1 *zriver_output_status_v1,
uint32_t tags) {
static_cast<Tags *>(data)->handle_urgent_tags(tags);
}
static const zriver_output_status_v1_listener output_status_listener_impl{ static const zriver_output_status_v1_listener output_status_listener_impl{
.focused_tags = listen_focused_tags, .focused_tags = listen_focused_tags,
.view_tags = listen_view_tags, .view_tags = listen_view_tags,
.urgent_tags = listen_urgent_tags,
};
static void listen_command_success(void *data,
struct zriver_command_callback_v1 *zriver_command_callback_v1,
const char *output) {
// Do nothing but keep listener to avoid crashing when command was successful
}
static void listen_command_failure(void *data,
struct zriver_command_callback_v1 *zriver_command_callback_v1,
const char *output) {
spdlog::error("failure when selecting/toggling tags {}", output);
}
static const zriver_command_callback_v1_listener command_callback_listener_impl {
.success = listen_command_success,
.failure = listen_command_failure,
}; };
static void handle_global(void *data, struct wl_registry *registry, uint32_t name, static void handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) { const char *interface, uint32_t version) {
if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) { if (std::strcmp(interface, zriver_status_manager_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 2);
if (version < ZRIVER_OUTPUT_STATUS_V1_URGENT_TAGS_SINCE_VERSION) {
spdlog::warn("river server does not support urgent tags");
}
static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>( static_cast<Tags *>(data)->status_manager_ = static_cast<struct zriver_status_manager_v1 *>(
wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version)); wl_registry_bind(registry, name, &zriver_status_manager_v1_interface, version));
} }
if (std::strcmp(interface, zriver_control_v1_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(data)->control_ = static_cast<struct zriver_control_v1 *>(
wl_registry_bind(registry, name, &zriver_control_v1_interface, version));
}
if (std::strcmp(interface, wl_seat_interface.name) == 0) {
version = std::min<uint32_t>(version, 1);
static_cast<Tags *>(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) { static void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
/* Ignore event */ /* Ignore event */
} }
static const wl_registry_listener registry_listener_impl = {.global = handle_global, static const wl_registry_listener registry_listener_impl = {.global = handle_global,
.global_remove = handle_global_remove}; .global_remove = handle_global_remove};
Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config) Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &config)
: waybar::AModule(config, "tags", id, false, false), : waybar::AModule(config, "tags", id, false, false),
status_manager_{nullptr}, status_manager_{nullptr},
control_{nullptr},
seat_{nullptr},
bar_(bar), bar_(bar),
box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, box_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0},
output_status_{nullptr} { output_status_{nullptr} {
@ -58,14 +99,23 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
return; return;
} }
if (!control_) {
spdlog::error("river_control_v1 not advertised");
}
if (!seat_) {
spdlog::error("wl_seat not advertised");
}
box_.set_name("tags"); box_.set_name("tags");
if (!id.empty()) { if (!id.empty()) {
box_.get_style_context()->add_class(id); box_.get_style_context()->add_class(id);
} }
event_box_.add(box_); event_box_.add(box_);
// Default to 9 tags // Default to 9 tags, cap at 32
const uint32_t num_tags = config["num-tags"].isUInt() ? config_["num-tags"].asUInt() : 9; const uint32_t num_tags =
config["num-tags"].isUInt() ? std::min<uint32_t>(32, config_["num-tags"].asUInt()) : 9;
std::vector<std::string> tag_labels(num_tags); std::vector<std::string> tag_labels(num_tags);
for (uint32_t tag = 0; tag < num_tags; ++tag) { for (uint32_t tag = 0; tag < num_tags; ++tag) {
@ -78,11 +128,17 @@ Tags::Tags(const std::string &id, const waybar::Bar &bar, const Json::Value &con
} }
} }
uint32_t i = 1;
for (const auto &tag_label : tag_labels) { for (const auto &tag_label : tag_labels) {
Gtk::Button &button = buttons_.emplace_back(tag_label); Gtk::Button &button = buttons_.emplace_back(tag_label);
button.set_relief(Gtk::RELIEF_NONE); button.set_relief(Gtk::RELIEF_NONE);
box_.pack_start(button, false, false, 0); box_.pack_start(button, false, false, 0);
if (!config_["disable-click"].asBool()) {
button.signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_primary_clicked), i));
button.signal_button_press_event().connect(sigc::bind(sigc::mem_fun(*this, &Tags::handle_button_press), i));
}
button.show(); button.show();
i <<= 1;
} }
struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()); struct wl_output *output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
@ -96,6 +152,31 @@ Tags::~Tags() {
if (output_status_) { if (output_status_) {
zriver_output_status_v1_destroy(output_status_); zriver_output_status_v1_destroy(output_status_);
} }
if (control_) {
zriver_control_v1_destroy(control_);
}
}
void Tags::handle_primary_clicked(uint32_t tag) {
// Send river command to select tag on left mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "set-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
}
bool Tags::handle_button_press(GdkEventButton *event_button, uint32_t tag) {
if (event_button->type == GDK_BUTTON_PRESS && event_button->button == 3) {
// Send river command to toggle tag on right mouse click
zriver_command_callback_v1 *callback;
zriver_control_v1_add_argument(control_, "toggle-focused-tags");
zriver_control_v1_add_argument(control_, std::to_string(tag).c_str());
callback = zriver_control_v1_run_command(control_, seat_);
zriver_command_callback_v1_add_listener(callback, &command_callback_listener_impl, nullptr);
}
return true;
} }
void Tags::handle_focused_tags(uint32_t tags) { void Tags::handle_focused_tags(uint32_t tags) {
@ -129,4 +210,16 @@ void Tags::handle_view_tags(struct wl_array *view_tags) {
} }
} }
void Tags::handle_urgent_tags(uint32_t tags) {
uint32_t i = 0;
for (auto &button : buttons_) {
if ((1 << i) & tags) {
button.get_style_context()->add_class("urgent");
} else {
button.get_style_context()->remove_class("urgent");
}
++i;
}
}
} /* namespace waybar::modules::river */ } /* namespace waybar::modules::river */

View File

@ -4,7 +4,7 @@
namespace waybar::modules::SNI { namespace waybar::modules::SNI {
Host::Host(const std::size_t id, const Json::Value& config, Host::Host(const std::size_t id, const Json::Value& config, const Bar& bar,
const std::function<void(std::unique_ptr<Item>&)>& on_add, const std::function<void(std::unique_ptr<Item>&)>& on_add,
const std::function<void(std::unique_ptr<Item>&)>& on_remove) const std::function<void(std::unique_ptr<Item>&)>& on_remove)
: bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" + : bus_name_("org.kde.StatusNotifierHost-" + std::to_string(getpid()) + "-" +
@ -13,6 +13,7 @@ Host::Host(const std::size_t id, const Json::Value& config,
bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_, bus_name_id_(Gio::DBus::own_name(Gio::DBus::BusType::BUS_TYPE_SESSION, bus_name_,
sigc::mem_fun(*this, &Host::busAcquired))), sigc::mem_fun(*this, &Host::busAcquired))),
config_(config), config_(config),
bar_(bar),
on_add_(on_add), on_add_(on_add),
on_remove_(on_remove) {} on_remove_(on_remove) {}
@ -136,7 +137,7 @@ void Host::addRegisteredItem(std::string service) {
return bus_name == item->bus_name && object_path == item->object_path; return bus_name == item->bus_name && object_path == item->object_path;
}); });
if (it == items_.end()) { if (it == items_.end()) {
items_.emplace_back(new Item(bus_name, object_path, config_)); items_.emplace_back(new Item(bus_name, object_path, config_, bar_));
on_add_(items_.back()); on_add_(items_.back());
} }
} }

View File

@ -1,15 +1,14 @@
#include "modules/sni/item.hpp" #include "modules/sni/item.hpp"
#include <glibmm/main.h>
#include <spdlog/spdlog.h>
#include <fstream>
template <> #include <gdkmm/general.h>
struct fmt::formatter<Glib::ustring> : formatter<std::string> { #include <glibmm/main.h>
template <typename FormatContext> #include <gtkmm/tooltip.h>
auto format(const Glib::ustring& value, FormatContext& ctx) { #include <spdlog/spdlog.h>
return formatter<std::string>::format(value, ctx);
} #include <fstream>
}; #include <map>
#include "util/format.hpp"
template <> template <>
struct fmt::formatter<Glib::VariantBase> : formatter<std::string> { struct fmt::formatter<Glib::VariantBase> : formatter<std::string> {
@ -34,19 +33,31 @@ namespace waybar::modules::SNI {
static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name; static const Glib::ustring SNI_INTERFACE_NAME = sn_item_interface_info()->name;
static const unsigned UPDATE_DEBOUNCE_TIME = 10; static const unsigned UPDATE_DEBOUNCE_TIME = 10;
Item::Item(const std::string& bn, const std::string& op, const Json::Value& config) Item::Item(const std::string& bn, const std::string& op, const Json::Value& config, const Bar& bar)
: bus_name(bn), : bus_name(bn),
object_path(op), object_path(op),
icon_size(16), icon_size(16),
effective_icon_size(0), effective_icon_size(0),
icon_theme(Gtk::IconTheme::create()), icon_theme(Gtk::IconTheme::create()) {
update_pending_(false) {
if (config["icon-size"].isUInt()) { if (config["icon-size"].isUInt()) {
icon_size = config["icon-size"].asUInt(); icon_size = config["icon-size"].asUInt();
} }
if (config["smooth-scrolling-threshold"].isNumeric()) {
scroll_threshold_ = config["smooth-scrolling-threshold"].asDouble();
}
if (config["show-passive-items"].isBool()) {
show_passive_ = config["show-passive-items"].asBool();
}
auto &window = const_cast<Bar &>(bar).window;
window.signal_configure_event().connect_notify(sigc::mem_fun(*this, &Item::onConfigure));
event_box.add(image); event_box.add(image);
event_box.add_events(Gdk::BUTTON_PRESS_MASK); event_box.add_events(Gdk::BUTTON_PRESS_MASK | Gdk::SCROLL_MASK | Gdk::SMOOTH_SCROLL_MASK);
event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick)); event_box.signal_button_press_event().connect(sigc::mem_fun(*this, &Item::handleClick));
event_box.signal_scroll_event().connect(sigc::mem_fun(*this, &Item::handleScroll));
// initial visibility
event_box.show_all();
event_box.set_visible(show_passive_);
cancellable_ = Gio::Cancellable::create(); cancellable_ = Gio::Cancellable::create();
@ -60,6 +71,10 @@ Item::Item(const std::string& bn, const std::string& op, const Json::Value& conf
interface); interface);
} }
void Item::onConfigure(GdkEventConfigure* ev) {
this->updateImage();
}
void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) { void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
try { try {
this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result); this->proxy_ = Gio::DBus::Proxy::create_for_bus_finish(result);
@ -73,12 +88,11 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal)); this->proxy_->signal_signal().connect(sigc::mem_fun(*this, &Item::onSignal));
if (this->id.empty() || this->category.empty() || this->status.empty()) { if (this->id.empty() || this->category.empty()) {
spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path); spdlog::error("Invalid Status Notifier Item: {}, {}", bus_name, object_path);
return; return;
} }
this->updateImage(); this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) { } catch (const Glib::Error& err) {
spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what()); spdlog::error("Failed to create DBus Proxy for {} {}: {}", bus_name, object_path, err.what());
@ -88,10 +102,24 @@ void Item::proxyReady(Glib::RefPtr<Gio::AsyncResult>& result) {
} }
template <typename T> template <typename T>
T get_variant(Glib::VariantBase& value) { T get_variant(const Glib::VariantBase& value) {
return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get(); return Glib::VariantBase::cast_dynamic<Glib::Variant<T>>(value).get();
} }
template <>
ToolTip get_variant<ToolTip>(const Glib::VariantBase& value) {
ToolTip result;
// Unwrap (sa(iiay)ss)
auto container = value.cast_dynamic<Glib::VariantContainerBase>(value);
result.icon_name = get_variant<Glib::ustring>(container.get_child(0));
result.text = get_variant<Glib::ustring>(container.get_child(2));
auto description = get_variant<Glib::ustring>(container.get_child(3));
if (!description.empty()) {
result.text = fmt::format("<b>{}</b>\n{}", result.text, description);
}
return result;
}
void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) { void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
try { try {
spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value); spdlog::trace("Set tray item property: {}.{} = {}", id.empty() ? bus_name : id, name, value);
@ -102,10 +130,11 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
id = get_variant<std::string>(value); id = get_variant<std::string>(value);
} else if (name == "Title") { } else if (name == "Title") {
title = get_variant<std::string>(value); title = get_variant<std::string>(value);
if (tooltip.text.empty()) {
event_box.set_tooltip_markup(title);
}
} else if (name == "Status") { } else if (name == "Status") {
status = get_variant<std::string>(value); setStatus(get_variant<Glib::ustring>(value));
} else if (name == "WindowId") {
window_id = get_variant<int32_t>(value);
} else if (name == "IconName") { } else if (name == "IconName") {
icon_name = get_variant<std::string>(value); icon_name = get_variant<std::string>(value);
} else if (name == "IconPixmap") { } else if (name == "IconPixmap") {
@ -121,7 +150,10 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} else if (name == "AttentionMovieName") { } else if (name == "AttentionMovieName") {
attention_movie_name = get_variant<std::string>(value); attention_movie_name = get_variant<std::string>(value);
} else if (name == "ToolTip") { } else if (name == "ToolTip") {
// TODO: tooltip tooltip = get_variant<ToolTip>(value);
if (!tooltip.text.empty()) {
event_box.set_tooltip_markup(tooltip.text);
}
} else if (name == "IconThemePath") { } else if (name == "IconThemePath") {
icon_theme_path = get_variant<std::string>(value); icon_theme_path = get_variant<std::string>(value);
if (!icon_theme_path.empty()) { if (!icon_theme_path.empty()) {
@ -148,9 +180,22 @@ void Item::setProperty(const Glib::ustring& name, Glib::VariantBase& value) {
} }
} }
void Item::getUpdatedProperties() { void Item::setStatus(const Glib::ustring& value) {
update_pending_ = false; Glib::ustring lower = value.lowercase();
event_box.set_visible(show_passive_ || lower.compare("passive") != 0);
auto style = event_box.get_style_context();
for (const auto& class_name : style->list_classes()) {
style->remove_class(class_name);
}
if (lower.compare("needsattention") == 0) {
// convert status to dash-case for CSS
lower = "needs-attention";
}
style->add_class(lower);
}
void Item::getUpdatedProperties() {
auto params = Glib::VariantContainerBase::create_tuple( auto params = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)}); {Glib::Variant<Glib::ustring>::create(SNI_INTERFACE_NAME)});
proxy_->call("org.freedesktop.DBus.Properties.GetAll", proxy_->call("org.freedesktop.DBus.Properties.GetAll",
@ -167,33 +212,48 @@ void Item::processUpdatedProperties(Glib::RefPtr<Gio::AsyncResult>& _result) {
auto properties = properties_variant.get(); auto properties = properties_variant.get();
for (const auto& [name, value] : properties) { for (const auto& [name, value] : properties) {
Glib::VariantBase old_value; if (update_pending_.count(name.raw())) {
proxy_->get_cached_property(old_value, name);
if (!old_value || !value.equal(old_value)) {
proxy_->set_cached_property(name, value);
setProperty(name, const_cast<Glib::VariantBase&>(value)); setProperty(name, const_cast<Glib::VariantBase&>(value));
} }
} }
this->updateImage(); this->updateImage();
// this->event_box.set_tooltip_text(this->title);
} catch (const Glib::Error& err) { } catch (const Glib::Error& err) {
spdlog::warn("Failed to update properties: {}", err.what()); spdlog::warn("Failed to update properties: {}", err.what());
} catch (const std::exception& err) { } catch (const std::exception& err) {
spdlog::warn("Failed to update properties: {}", err.what()); spdlog::warn("Failed to update properties: {}", err.what());
} }
update_pending_.clear();
} }
/**
* Mapping from a signal name to a set of possibly changed properties.
* Commented signals are not handled by the tray module at the moment.
*/
static const std::map<std::string_view, std::set<std::string_view>> signal2props = {
{"NewTitle", {"Title"}},
{"NewIcon", {"IconName", "IconPixmap"}},
// {"NewAttentionIcon", {"AttentionIconName", "AttentionIconPixmap", "AttentionMovieName"}},
// {"NewOverlayIcon", {"OverlayIconName", "OverlayIconPixmap"}},
{"NewIconThemePath", {"IconThemePath"}},
{"NewToolTip", {"ToolTip"}},
{"NewStatus", {"Status"}},
// {"XAyatanaNewLabel", {"XAyatanaLabel"}},
};
void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name, void Item::onSignal(const Glib::ustring& sender_name, const Glib::ustring& signal_name,
const Glib::VariantContainerBase& arguments) { const Glib::VariantContainerBase& arguments) {
spdlog::trace("Tray item '{}' got signal {}", id, signal_name); spdlog::trace("Tray item '{}' got signal {}", id, signal_name);
if (!update_pending_ && signal_name.compare(0, 3, "New") == 0) { auto changed = signal2props.find(signal_name.raw());
/* Debounce signals and schedule update of all properties. if (changed != signal2props.end()) {
* Based on behavior of Plasma dataengine for StatusNotifierItem. if (update_pending_.empty()) {
*/ /* Debounce signals and schedule update of all properties.
update_pending_ = true; * Based on behavior of Plasma dataengine for StatusNotifierItem.
Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties), */
UPDATE_DEBOUNCE_TIME); Glib::signal_timeout().connect_once(sigc::mem_fun(*this, &Item::getUpdatedProperties),
UPDATE_DEBOUNCE_TIME);
}
update_pending_.insert(changed->second.begin(), changed->second.end());
} }
} }
@ -222,7 +282,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
if (array != nullptr) { if (array != nullptr) {
g_free(array); g_free(array);
} }
#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 68
array = static_cast<guchar*>(g_memdup2(data, size));
#else
array = static_cast<guchar*>(g_memdup(data, size)); array = static_cast<guchar*>(g_memdup(data, size));
#endif
lwidth = width; lwidth = width;
lheight = height; lheight = height;
} }
@ -252,35 +316,41 @@ Glib::RefPtr<Gdk::Pixbuf> Item::extractPixBuf(GVariant* variant) {
} }
void Item::updateImage() { void Item::updateImage() {
image.set_from_icon_name("image-missing", Gtk::ICON_SIZE_MENU); auto pixbuf = getIconPixbuf();
image.set_pixel_size(icon_size); auto scaled_icon_size = getScaledIconSize();
if (!icon_name.empty()) {
try { if (!pixbuf) {
// Try to find icons specified by path and filename 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
// the aspect ratio is maintained, but the height matches the requested icon size.
if (pixbuf->get_height() != scaled_icon_size) {
int width = scaled_icon_size * pixbuf->get_width() / pixbuf->get_height();
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());
image.set(surface);
}
Glib::RefPtr<Gdk::Pixbuf> Item::getIconPixbuf() {
try {
if (!icon_name.empty()) {
std::ifstream temp(icon_name); std::ifstream temp(icon_name);
if (temp.is_open()) { if (temp.is_open()) {
auto pixbuf = Gdk::Pixbuf::create_from_file(icon_name); return Gdk::Pixbuf::create_from_file(icon_name);
if (pixbuf->gobj() != nullptr) {
// An icon specified by path and filename may be the wrong size for
// the tray
// Keep the aspect ratio and scale to make the height equal to icon_size
// If people have non square icons, assume they want it to grow in width not height
int width = icon_size * pixbuf->get_width() / pixbuf->get_height();
pixbuf = pixbuf->scale_simple(width, icon_size, Gdk::InterpType::INTERP_BILINEAR);
image.set(pixbuf);
}
} else {
image.set(getIconByName(icon_name, icon_size));
} }
} catch (Glib::Error& e) { return getIconByName(icon_name, getScaledIconSize());
spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what())); } else if (icon_pixmap) {
return icon_pixmap;
} }
} else if (icon_pixmap) { } catch (Glib::Error& e) {
// An icon extracted may be the wrong size for the tray spdlog::error("Item '{}': {}", id, static_cast<std::string>(e.what()));
icon_pixmap = icon_pixmap->scale_simple(icon_size, icon_size, Gdk::InterpType::INTERP_BILINEAR); }
image.set(icon_pixmap); return getIconByName("image-missing", getScaledIconSize());
}
} }
Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) { Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int request_size) {
@ -315,6 +385,11 @@ Glib::RefPtr<Gdk::Pixbuf> Item::getIconByName(const std::string& name, int reque
name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE); name.c_str(), tmp_size, Gtk::IconLookupFlags::ICON_LOOKUP_FORCE_SIZE);
} }
double Item::getScaledIconSize() {
// apply the scale factor from the Gtk window to the requested icon size
return icon_size * image.get_scale_factor();
}
void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) { void Item::onMenuDestroyed(Item* self, GObject* old_menu_pointer) {
if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) { if (old_menu_pointer == reinterpret_cast<GObject*>(self->dbus_menu)) {
self->gtk_menu = nullptr; self->gtk_menu = nullptr;
@ -360,4 +435,52 @@ bool Item::handleClick(GdkEventButton* const& ev) {
return false; return false;
} }
bool Item::handleScroll(GdkEventScroll* const& ev) {
int dx = 0, dy = 0;
switch (ev->direction) {
case GDK_SCROLL_UP:
dy = -1;
break;
case GDK_SCROLL_DOWN:
dy = 1;
break;
case GDK_SCROLL_LEFT:
dx = -1;
break;
case GDK_SCROLL_RIGHT:
dx = 1;
break;
case GDK_SCROLL_SMOOTH:
distance_scrolled_x_ += ev->delta_x;
distance_scrolled_y_ += ev->delta_y;
// check against the configured threshold and ensure that the absolute value >= 1
if (distance_scrolled_x_ > scroll_threshold_) {
dx = (int)lround(std::max(distance_scrolled_x_, 1.0));
distance_scrolled_x_ = 0;
} else if (distance_scrolled_x_ < -scroll_threshold_) {
dx = (int)lround(std::min(distance_scrolled_x_, -1.0));
distance_scrolled_x_ = 0;
}
if (distance_scrolled_y_ > scroll_threshold_) {
dy = (int)lround(std::max(distance_scrolled_y_, 1.0));
distance_scrolled_y_ = 0;
} else if (distance_scrolled_y_ < -scroll_threshold_) {
dy = (int)lround(std::min(distance_scrolled_y_, -1.0));
distance_scrolled_y_ = 0;
}
break;
}
if (dx != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dx), Glib::Variant<Glib::ustring>::create("horizontal")});
proxy_->call("Scroll", parameters);
}
if (dy != 0) {
auto parameters = Glib::VariantContainerBase::create_tuple(
{Glib::Variant<int>::create(dy), Glib::Variant<Glib::ustring>::create("vertical")});
proxy_->call("Scroll", parameters);
}
return true;
}
} // namespace waybar::modules::SNI } // namespace waybar::modules::SNI

View File

@ -7,7 +7,7 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
: AModule(config, "tray", id), : AModule(config, "tray", id),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0), box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0),
watcher_(SNI::Watcher::getInstance()), watcher_(SNI::Watcher::getInstance()),
host_(nb_hosts_, config, std::bind(&Tray::onAdd, this, std::placeholders::_1), host_(nb_hosts_, config, bar, std::bind(&Tray::onAdd, this, std::placeholders::_1),
std::bind(&Tray::onRemove, this, std::placeholders::_1)) { std::bind(&Tray::onRemove, this, std::placeholders::_1)) {
spdlog::warn( spdlog::warn(
"For a functional tray you must have libappindicator-* installed and export " "For a functional tray you must have libappindicator-* installed and export "
@ -25,7 +25,11 @@ Tray::Tray(const std::string& id, const Bar& bar, const Json::Value& config)
} }
void Tray::onAdd(std::unique_ptr<Item>& item) { void Tray::onAdd(std::unique_ptr<Item>& item) {
box_.pack_start(item->event_box); if (config_["reverse-direction"].isBool() && config_["reverse-direction"].asBool()) {
box_.pack_end(item->event_box);
} else {
box_.pack_start(item->event_box);
}
dp.emit(); dp.emit();
} }
@ -35,11 +39,8 @@ void Tray::onRemove(std::unique_ptr<Item>& item) {
} }
auto Tray::update() -> void { auto Tray::update() -> void {
if (box_.get_children().empty()) { // Show tray only when items are available
box_.hide(); box_.set_visible(!box_.get_children().empty());
} else {
box_.show_all();
}
// Call parent update // Call parent update
AModule::update(); AModule::update();
} }

107
src/modules/sway/bar.cpp Normal file
View File

@ -0,0 +1,107 @@
#include "modules/sway/bar.hpp"
#include <fmt/ostream.h>
#include <spdlog/spdlog.h>
#include <stdexcept>
#include "bar.hpp"
#include "modules/sway/ipc/ipc.hpp"
namespace waybar::modules::sway {
BarIpcClient::BarIpcClient(waybar::Bar& bar) : bar_{bar} {
{
sigc::connection handle =
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &BarIpcClient::onInitialConfig));
ipc_.sendCmd(IPC_GET_BAR_CONFIG, bar_.bar_id);
handle.disconnect();
}
signal_config_.connect(sigc::mem_fun(*this, &BarIpcClient::onConfigUpdate));
signal_visible_.connect(sigc::mem_fun(*this, &BarIpcClient::onVisibilityUpdate));
ipc_.subscribe(R"(["bar_state_update", "barconfig_update"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &BarIpcClient::onIpcEvent));
// Launch worker
ipc_.setWorker([this] {
try {
ipc_.handleEvent();
} catch (const std::exception& e) {
spdlog::error("BarIpcClient::handleEvent {}", e.what());
}
});
}
struct swaybar_config parseConfig(const Json::Value& payload) {
swaybar_config conf;
if (auto id = payload["id"]; id.isString()) {
conf.id = id.asString();
}
if (auto mode = payload["mode"]; mode.isString()) {
conf.mode = mode.asString();
}
if (auto hs = payload["hidden_state"]; hs.isString()) {
conf.hidden_state = hs.asString();
}
return conf;
}
void BarIpcClient::onInitialConfig(const struct Ipc::ipc_response& res) {
auto payload = parser_.parse(res.payload);
if (auto success = payload.get("success", true); !success.asBool()) {
auto err = payload.get("error", "Unknown error");
throw std::runtime_error(err.asString());
}
auto config = parseConfig(payload);
onConfigUpdate(config);
}
void BarIpcClient::onIpcEvent(const struct Ipc::ipc_response& res) {
try {
auto payload = parser_.parse(res.payload);
if (auto id = payload["id"]; id.isString() && id.asString() != bar_.bar_id) {
spdlog::trace("swaybar ipc: ignore event for {}", id.asString());
return;
}
if (payload.isMember("visible_by_modifier")) {
// visibility change for hidden bar
signal_visible_(payload["visible_by_modifier"].asBool());
} else {
// configuration update
auto config = parseConfig(payload);
signal_config_(std::move(config));
}
} catch (const std::exception& e) {
spdlog::error("BarIpcClient::onEvent {}", e.what());
}
}
void BarIpcClient::onConfigUpdate(const swaybar_config& config) {
spdlog::info("config update for {}: id {}, mode {}, hidden_state {}",
bar_.bar_id,
config.id,
config.mode,
config.hidden_state);
bar_config_ = config;
update();
}
void BarIpcClient::onVisibilityUpdate(bool visible_by_modifier) {
spdlog::debug("visiblity update for {}: {}", bar_.bar_id, visible_by_modifier);
visible_by_modifier_ = visible_by_modifier;
update();
}
void BarIpcClient::update() {
bool visible = visible_by_modifier_;
if (bar_config_.mode == "invisible") {
visible = false;
} else if (bar_config_.mode != "hide" || bar_config_.hidden_state != "hide") {
visible = true;
}
bar_.setMode(visible ? bar_config_.mode : Bar::MODE_INVISIBLE);
}
} // namespace waybar::modules::sway

View File

@ -1,10 +1,34 @@
#include "modules/sway/language.hpp" #include "modules/sway/language.hpp"
#include <fmt/core.h>
#include <json/json.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <xkbcommon/xkbregistry.h>
#include <cstring>
#include <string>
#include <vector>
#include "modules/sway/ipc/ipc.hpp"
#include "util/string.hpp"
namespace waybar::modules::sway { namespace waybar::modules::sway {
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) Language::Language(const std::string& id, const Json::Value& config)
: ALabel(config, "language", id, "{}", 0, true) { : ALabel(config, "language", id, "{}", 0, true) {
is_variant_displayed = format_.find("{variant}") != std::string::npos;
if (format_.find("{}") != std::string::npos || format_.find("{short}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortName);
}
if (format_.find("{shortDescription}") != std::string::npos) {
displayed_short_flag |= static_cast<std::byte>(DispayedShortFlag::ShortDescription);
}
if (config.isMember("tooltip-format")) {
tooltip_format_ = config["tooltip-format"].asString();
}
ipc_.subscribe(R"(["input"])"); ipc_.subscribe(R"(["input"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent)); ipc_.signal_event.connect(sigc::mem_fun(*this, &Language::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Language::onCmd));
@ -21,18 +45,31 @@ Language::Language(const std::string& id, const Json::Value& config)
} }
void Language::onCmd(const struct Ipc::ipc_response& res) { void Language::onCmd(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_GET_INPUTS)) {
return;
}
try { try {
auto payload = parser_.parse(res.payload); std::lock_guard<std::mutex> lock(mutex_);
//Display current layout of a device with a maximum count of layouts, expecting that all will be OK auto payload = parser_.parse(res.payload);
Json::Value::ArrayIndex maxId = 0, max = 0; std::vector<std::string> used_layouts;
for(Json::Value::ArrayIndex i = 0; i < payload.size(); i++) { // Display current layout of a device with a maximum count of layouts, expecting that all will
if(payload[i]["xkb_layout_names"].size() > max) { // be OK
max = payload[i]["xkb_layout_names"].size(); Json::ArrayIndex max_id = 0, max = 0;
maxId = i; for (Json::ArrayIndex i = 0; i < payload.size(); i++) {
auto size = payload[i][XKB_LAYOUT_NAMES_KEY].size();
if (size > max) {
max = size;
max_id = i;
} }
} }
auto layout_name = payload[maxId]["xkb_active_layout_name"].asString().substr(0,2);
lang_ = Glib::Markup::escape_text(layout_name); for (const auto& layout : payload[max_id][XKB_LAYOUT_NAMES_KEY]) {
used_layouts.push_back(layout.asString());
}
init_layouts_map(used_layouts);
set_current_layout(payload[max_id][XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Language: {}", e.what()); spdlog::error("Language: {}", e.what());
@ -40,12 +77,15 @@ void Language::onCmd(const struct Ipc::ipc_response& res) {
} }
void Language::onEvent(const struct Ipc::ipc_response& res) { void Language::onEvent(const struct Ipc::ipc_response& res) {
if (res.type != static_cast<uint32_t>(IPC_EVENT_INPUT)) {
return;
}
try { try {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
auto payload = parser_.parse(res.payload)["input"]; auto payload = parser_.parse(res.payload)["input"];
if (payload["type"].asString() == "keyboard") { if (payload["type"].asString() == "keyboard") {
auto layout_name = payload["xkb_active_layout_name"].asString().substr(0,2); set_current_layout(payload[XKB_ACTIVE_LAYOUT_NAME_KEY].asString());
lang_ = Glib::Markup::escape_text(layout_name);
} }
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -54,17 +94,138 @@ void Language::onEvent(const struct Ipc::ipc_response& res) {
} }
auto Language::update() -> void { auto Language::update() -> void {
if (lang_.empty()) { std::lock_guard<std::mutex> lock(mutex_);
event_box_.hide(); auto display_layout = trim(fmt::format(format_,
} else { fmt::arg("short", layout_.short_name),
label_.set_markup(fmt::format(format_, lang_)); fmt::arg("shortDescription", layout_.short_description),
if (tooltipEnabled()) { fmt::arg("long", layout_.full_name),
label_.set_tooltip_text(lang_); fmt::arg("variant", layout_.variant),
fmt::arg("flag", layout_.country_flag())));
label_.set_markup(display_layout);
if (tooltipEnabled()) {
if (tooltip_format_ != "") {
auto tooltip_display_layout = trim(fmt::format(tooltip_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_tooltip_markup(tooltip_display_layout);
} else {
label_.set_tooltip_markup(display_layout);
} }
event_box_.show();
} }
event_box_.show();
// Call parent update // Call parent update
ALabel::update(); ALabel::update();
} }
auto Language::set_current_layout(std::string current_layout) -> void {
layout_ = layouts_map_[current_layout];
}
auto Language::init_layouts_map(const std::vector<std::string>& used_layouts) -> void {
std::map<std::string, std::vector<Layout*>> found_by_short_names;
XKBContext xkb_context;
auto layout = xkb_context.next_layout();
for (; layout != nullptr; layout = xkb_context.next_layout()) {
if (std::find(used_layouts.begin(), used_layouts.end(), layout->full_name) ==
used_layouts.end()) {
continue;
}
if (!is_variant_displayed) {
auto short_name = layout->short_name;
if (found_by_short_names.count(short_name) > 0) {
found_by_short_names[short_name].push_back(layout);
} else {
found_by_short_names[short_name] = {layout};
}
}
layouts_map_.emplace(layout->full_name, *layout);
}
if (is_variant_displayed || found_by_short_names.size() == 0) {
return;
}
std::map<std::string, int> short_name_to_number_map;
for (const auto& used_layout_name : used_layouts) {
auto found = layouts_map_.find(used_layout_name);
if (found == layouts_map_.end())
continue;
auto used_layout = &found->second;
auto layouts_with_same_name_list = found_by_short_names[used_layout->short_name];
if (layouts_with_same_name_list.size() < 2) {
continue;
}
if (short_name_to_number_map.count(used_layout->short_name) == 0) {
short_name_to_number_map[used_layout->short_name] = 1;
}
if (displayed_short_flag != static_cast<std::byte>(0)) {
int& number = short_name_to_number_map[used_layout->short_name];
used_layout->short_name =
used_layout->short_name + std::to_string(number);
used_layout->short_description =
used_layout->short_description + std::to_string(number);
++number;
}
}
}
Language::XKBContext::XKBContext() {
context_ = rxkb_context_new(RXKB_CONTEXT_NO_DEFAULT_INCLUDES);
rxkb_context_include_path_append_default(context_);
rxkb_context_parse_default_ruleset(context_);
}
auto Language::XKBContext::next_layout() -> Layout* {
if (xkb_layout_ == nullptr) {
xkb_layout_ = rxkb_layout_first(context_);
} else {
xkb_layout_ = rxkb_layout_next(xkb_layout_);
}
if (xkb_layout_ == nullptr) {
return nullptr;
}
auto description = std::string(rxkb_layout_get_description(xkb_layout_));
auto name = std::string(rxkb_layout_get_name(xkb_layout_));
auto variant_ = rxkb_layout_get_variant(xkb_layout_);
std::string variant = variant_ == nullptr ? "" : std::string(variant_);
auto short_description_ = rxkb_layout_get_brief(xkb_layout_);
std::string short_description;
if (short_description_ != nullptr) {
short_description = std::string(short_description_);
base_layouts_by_name_.emplace(name, xkb_layout_);
} else {
auto base_layout = base_layouts_by_name_[name];
short_description = base_layout == nullptr ? "" : std::string(rxkb_layout_get_brief(base_layout));
}
delete layout_;
layout_ = new Layout{description, name, variant, short_description};
return layout_;
}
Language::XKBContext::~XKBContext() {
rxkb_context_unref(context_);
delete layout_;
}
std::string Language::Layout::country_flag() const {
if (short_name.size() != 2) return "";
unsigned char result[] = "\xf0\x9f\x87\x00\xf0\x9f\x87\x00";
result[3] = short_name[0] + 0x45;
result[7] = short_name[1] + 0x45;
// Check if both emojis are in A-Z symbol bounds
if (result[3] < 0xa6 || result[3] > 0xbf) return "";
if (result[7] < 0xa6 || result[7] > 0xbf) return "";
return std::string{reinterpret_cast<char*>(result)};
}
} // namespace waybar::modules::sway } // namespace waybar::modules::sway

View File

@ -1,10 +1,20 @@
#include "modules/sway/window.hpp" #include "modules/sway/window.hpp"
#include <gdkmm/pixbuf.h>
#include <glibmm/fileutils.h>
#include <glibmm/keyfile.h>
#include <glibmm/miscutils.h>
#include <gtkmm/enums.h>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <filesystem>
#include <regex>
#include <string>
namespace waybar::modules::sway { namespace waybar::modules::sway {
Window::Window(const std::string& id, const Bar& bar, const Json::Value& config) Window::Window(const std::string& id, const Bar& bar, const Json::Value& config)
: ALabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) { : AIconLabel(config, "window", id, "{}", 0, true), bar_(bar), windowId_(-1) {
ipc_.subscribe(R"(["window","workspace"])"); ipc_.subscribe(R"(["window","workspace"])");
ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent)); ipc_.signal_event.connect(sigc::mem_fun(*this, &Window::onEvent));
ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd)); ipc_.signal_cmd.connect(sigc::mem_fun(*this, &Window::onCmd));
@ -28,12 +38,60 @@ void Window::onCmd(const struct Ipc::ipc_response& res) {
auto payload = parser_.parse(res.payload); auto payload = parser_.parse(res.payload);
auto output = payload["output"].isString() ? payload["output"].asString() : ""; auto output = payload["output"].isString() ? payload["output"].asString() : "";
std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output); std::tie(app_nb_, windowId_, window_, app_id_) = getFocusedNode(payload["nodes"], output);
updateAppIcon();
dp.emit(); dp.emit();
} catch (const std::exception& e) { } catch (const std::exception& e) {
spdlog::error("Window: {}", e.what()); spdlog::error("Window: {}", e.what());
} }
} }
std::optional<std::string> getDesktopFilePath(const std::string& app_id) {
const auto data_dirs = Glib::get_system_data_dirs();
for (const auto& data_dir : data_dirs) {
const auto desktop_file_path = data_dir + "applications/" + app_id + ".desktop";
if (std::filesystem::exists(desktop_file_path)) {
return desktop_file_path;
}
}
return {};
}
std::optional<Glib::ustring> getIconName(const std::string& app_id) {
const auto desktop_file_path = getDesktopFilePath(app_id);
if (!desktop_file_path.has_value()) {
return {};
}
try {
Glib::KeyFile desktop_file;
desktop_file.load_from_file(desktop_file_path.value());
const auto icon_name = desktop_file.get_string("Desktop Entry", "Icon");
if (icon_name.empty()) {
return {};
}
return icon_name;
} catch (Glib::FileError& error) {
spdlog::warn(
"Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str());
} catch (Glib::KeyFileError& error) {
spdlog::warn(
"Error while loading desktop file {}: {}", desktop_file_path.value(), error.what().c_str());
}
return {};
}
void Window::updateAppIcon() {
if (!iconEnabled()) {
return;
}
const auto icon_name = getIconName(app_id_);
if (icon_name.has_value()) {
image_.set_from_icon_name(icon_name.value(), Gtk::ICON_SIZE_LARGE_TOOLBAR);
image_.set_visible(true);
return;
}
image_.set_visible(false);
}
auto Window::update() -> void { auto Window::update() -> void {
if (!old_app_id_.empty()) { if (!old_app_id_.empty()) {
bar_.window.get_style_context()->remove_class(old_app_id_); bar_.window.get_style_context()->remove_class(old_app_id_);
@ -56,26 +114,33 @@ auto Window::update() -> void {
bar_.window.get_style_context()->remove_class("solo"); bar_.window.get_style_context()->remove_class("solo");
bar_.window.get_style_context()->remove_class("empty"); bar_.window.get_style_context()->remove_class("empty");
} }
label_.set_markup(fmt::format(format_, fmt::arg("title", window_), label_.set_markup(fmt::format(format_, fmt::arg("title", rewriteTitle(window_)),
fmt::arg("app_id", app_id_))); fmt::arg("app_id", app_id_)));
if (tooltipEnabled()) { if (tooltipEnabled()) {
label_.set_tooltip_text(window_); label_.set_tooltip_text(window_);
} }
// Call parent update // Call parent update
ALabel::update(); AIconLabel::update();
} }
int leafNodesInWorkspace(const Json::Value& node) { int leafNodesInWorkspace(const Json::Value& node) {
auto const& nodes = node["nodes"]; auto const& nodes = node["nodes"];
if(nodes.empty()) { auto const& floating_nodes = node["floating_nodes"];
if(nodes.empty() && floating_nodes.empty()) {
if(node["type"] == "workspace") if(node["type"] == "workspace")
return 0; return 0;
else else
return 1; return 1;
} }
int sum = 0; int sum = 0;
for(auto const& node : nodes) if (!nodes.empty()) {
sum += leafNodesInWorkspace(node); for(auto const& node : nodes)
sum += leafNodesInWorkspace(node);
}
if (!floating_nodes.empty()) {
for(auto const& node : floating_nodes)
sum += leafNodesInWorkspace(node);
}
return sum; return sum;
} }
@ -131,4 +196,30 @@ 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 } // namespace waybar::modules::sway

View File

@ -2,6 +2,7 @@
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
#include <algorithm>
#include <cctype> #include <cctype>
#include <string> #include <string>
@ -98,6 +99,7 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
Json::Value v; Json::Value v;
v["name"] = p_w_name; v["name"] = p_w_name;
v["target_output"] = bar_.output->name; v["target_output"] = bar_.output->name;
v["num"] = convertWorkspaceNameToNum(p_w_name);
workspaces_.emplace_back(std::move(v)); workspaces_.emplace_back(std::move(v));
break; break;
} }
@ -107,57 +109,59 @@ void Workspaces::onCmd(const struct Ipc::ipc_response &res) {
Json::Value v; Json::Value v;
v["name"] = p_w_name; v["name"] = p_w_name;
v["target_output"] = ""; v["target_output"] = "";
v["num"] = convertWorkspaceNameToNum(p_w_name);
workspaces_.emplace_back(std::move(v)); workspaces_.emplace_back(std::move(v));
} }
} }
} }
// config option to sort numeric workspace names before others // sway has a defined ordering of workspaces that should be preserved in
bool config_numeric_first = config_["numeric-first"].asBool(); // the representation displayed by waybar to ensure that commands such
// as "workspace prev" or "workspace next" make sense when looking at
// the workspace representation in the bar.
// Due to waybar's own feature of persistent workspaces unknown to sway,
// custom sorting logic is necessary to make these workspaces appear
// naturally in the list of workspaces without messing up sway's
// sorting. For this purpose, a custom numbering property is created
// that preserves the order provided by sway while inserting numbered
// persistent workspaces at their natural positions.
//
// All of this code assumes that sway provides numbered workspaces first
// and other workspaces are sorted by their creation time.
//
// In a first pass, the maximum "num" value is computed to enqueue
// unnumbered workspaces behind numbered ones when computing the sort
// attribute.
int max_num = -1;
for (auto & workspace : workspaces_) {
max_num = std::max(workspace["num"].asInt(), max_num);
}
for (auto & workspace : workspaces_) {
auto workspace_num = workspace["num"].asInt();
if (workspace_num > -1) {
workspace["sort"] = workspace_num;
} else {
workspace["sort"] = ++max_num;
}
}
std::sort(workspaces_.begin(), std::sort(workspaces_.begin(),
workspaces_.end(), workspaces_.end(),
[config_numeric_first](const Json::Value &lhs, const Json::Value &rhs) { [](const Json::Value &lhs, const Json::Value &rhs) {
// the "num" property (integer type):
// The workspace number or -1 for workspaces that do
// not start with a number.
// We could rely on sway providing this property:
//
// auto l = lhs["num"].asInt();
// auto r = rhs["num"].asInt();
//
// We cannot rely on the "num" property as provided by sway
// via IPC, because persistent workspace might not exist in
// sway's view. However, we need this property also for
// not-yet created persistent workspace. As such, we simply
// duplicate sway's logic of assigning the "num" property
// into waybar (see convertWorkspaceNameToNum). This way the
// sorting should work out even when we include workspaces
// that do not currently exist.
auto lname = lhs["name"].asString(); auto lname = lhs["name"].asString();
auto rname = rhs["name"].asString(); auto rname = rhs["name"].asString();
int l = convertWorkspaceNameToNum(lname); int l = lhs["sort"].asInt();
int r = convertWorkspaceNameToNum(rname); int r = rhs["sort"].asInt();
if (l == r) { if (l == r) {
// in case both integers are the same, lexicographical // In case both integers are the same, lexicographical
// sort. This also covers the case when both don't have a // sort. The code above already ensure that this will only
// number (i.e., l == r == -1). // happend in case of explicitly numbered workspaces.
return lname < rname; return lname < rname;
} }
// one of the workspaces doesn't begin with a number, so
// num is -1.
if (l < 0 || r < 0) {
if (config_numeric_first) {
return r < 0;
}
return l < 0;
}
// both workspaces have a "num" so let's just compare those
return l < r; return l < r;
}); });
} }
dp.emit(); dp.emit();
} catch (const std::exception &e) { } catch (const std::exception &e) {

View File

@ -4,6 +4,7 @@
#include "glibmm/fileutils.h" #include "glibmm/fileutils.h"
#include "glibmm/refptr.h" #include "glibmm/refptr.h"
#include "util/format.hpp" #include "util/format.hpp"
#include "util/string.hpp"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>
@ -11,7 +12,9 @@
#include <cstring> #include <cstring>
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <utility>
#include <fmt/core.h>
#include <gdkmm/monitor.h> #include <gdkmm/monitor.h>
#include <gtkmm/icontheme.h> #include <gtkmm/icontheme.h>
@ -24,27 +27,6 @@
namespace waybar::modules::wlr { namespace waybar::modules::wlr {
/* String manipulation methods */
const std::string WHITESPACE = " \n\r\t\f\v";
static std::string ltrim(const std::string& s)
{
size_t start = s.find_first_not_of(WHITESPACE);
return (start == std::string::npos) ? "" : s.substr(start);
}
static std::string rtrim(const std::string& s)
{
size_t end = s.find_last_not_of(WHITESPACE);
return (end == std::string::npos) ? "" : s.substr(0, end + 1);
}
static std::string trim(const std::string& s)
{
return rtrim(ltrim(s));
}
/* Icon loading functions */ /* Icon loading functions */
static std::vector<std::string> search_prefix() static std::vector<std::string> search_prefix()
{ {
@ -86,8 +68,7 @@ static Glib::RefPtr<Gdk::Pixbuf> load_icon_from_file(std::string icon_path, int
} }
} }
/* Method 1 - get the correct icon name from the desktop file */ static Glib::RefPtr<Gio::DesktopAppInfo> get_app_info_by_name(const std::string& app_id)
static std::string get_from_desktop_app_info(const std::string &app_id)
{ {
static std::vector<std::string> prefixes = search_prefix(); static std::vector<std::string> prefixes = search_prefix();
@ -103,33 +84,29 @@ static std::string get_from_desktop_app_info(const std::string &app_id)
".desktop" ".desktop"
}; };
Glib::RefPtr<Gio::DesktopAppInfo> app_info; for (auto& prefix : prefixes) {
for (auto& folder : app_folders) {
for (auto& suffix : suffixes) {
auto app_info_ = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (!app_info_) {
continue;
}
for (auto& prefix : prefixes) return app_info_;
for (auto& folder : app_folders) }
for (auto& suffix : suffixes) }
if (!app_info) }
app_info = Gio::DesktopAppInfo::create_from_filename(prefix + folder + app_id + suffix);
if (app_info && app_info->get_icon()) return {};
return app_info->get_icon()->to_string();
return "";
} }
/* Method 2 - use the app_id and check whether there is an icon with this name in the icon theme */ Glib::RefPtr<Gio::DesktopAppInfo> get_desktop_app_info(const std::string &app_id)
static std::string get_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id)
{ {
if (icon_theme->lookup_icon(app_id, 24)) auto app_info = get_app_info_by_name(app_id);
return app_id; if (app_info) {
return app_info;
}
return "";
}
/* Method 3 - as last resort perform a search for most appropriate desktop info file */
static std::string get_from_desktop_app_info_search(const std::string &app_id)
{
std::string desktop_file = ""; std::string desktop_file = "";
gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str()); gchar*** desktop_list = g_desktop_app_info_search(app_id.c_str());
@ -151,65 +128,84 @@ static std::string get_from_desktop_app_info_search(const std::string &app_id)
} }
g_free(desktop_list); g_free(desktop_list);
return get_from_desktop_app_info(desktop_file); return get_app_info_by_name(desktop_file);
} }
static bool image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, void Task::set_app_info_from_app_id_list(const std::string& app_id_list) {
const std::string &app_id_list, int size)
{
std::string app_id; std::string app_id;
std::istringstream stream(app_id_list); std::istringstream stream(app_id_list);
bool found = false;
/* Wayfire sends a list of app-id's in space separated format, other compositors /* Wayfire sends a list of app-id's in space separated format, other compositors
* send a single app-id, but in any case this works fine */ * send a single app-id, but in any case this works fine */
while (stream >> app_id) while (stream >> app_id)
{ {
app_info_ = get_desktop_app_info(app_id);
if (app_info_) {
return;
}
auto lower_app_id = app_id;
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(),
[](char c){ return std::tolower(c); });
app_info_ = get_desktop_app_info(lower_app_id);
if (app_info_) {
return;
}
size_t start = 0, end = app_id.size(); size_t start = 0, end = app_id.size();
start = app_id.rfind(".", end); start = app_id.rfind(".", end);
std::string app_name = app_id.substr(start+1, app_id.size()); std::string app_name = app_id.substr(start+1, app_id.size());
app_info_ = get_desktop_app_info(app_name);
if (app_info_) {
return;
}
auto lower_app_id = app_id; start = app_id.find("-");
std::transform(lower_app_id.begin(), lower_app_id.end(), lower_app_id.begin(), app_name = app_id.substr(0, start);
[](char c){ return std::tolower(c); }); app_info_ = get_desktop_app_info(app_name);
}
}
std::string icon_name = get_from_icon_theme(icon_theme, app_id); static std::string get_icon_name_from_icon_theme(const Glib::RefPtr<Gtk::IconTheme>& icon_theme,
const std::string &app_id)
{
if (icon_theme->lookup_icon(app_id, 24))
return app_id;
if (icon_name.empty()) return "";
icon_name = get_from_icon_theme(icon_theme, lower_app_id); }
if (icon_name.empty())
icon_name = get_from_icon_theme(icon_theme, app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(lower_app_id);
if (icon_name.empty())
icon_name = get_from_desktop_app_info(app_name);
if (icon_name.empty())
icon_name = get_from_desktop_app_info_search(app_id);
if (icon_name.empty()) bool Task::image_load_icon(Gtk::Image& image, const Glib::RefPtr<Gtk::IconTheme>& icon_theme, Glib::RefPtr<Gio::DesktopAppInfo> app_info, int size)
icon_name = "unknown"; {
std::string ret_icon_name = "unknown";
if (app_info) {
std::string icon_name = get_icon_name_from_icon_theme(icon_theme, app_info->get_startup_wm_class());
if (!icon_name.empty()) {
ret_icon_name = icon_name;
} else {
if (app_info->get_icon()) {
ret_icon_name = app_info->get_icon()->to_string();
}
}
}
Glib::RefPtr<Gdk::Pixbuf> pixbuf; Glib::RefPtr<Gdk::Pixbuf> pixbuf;
try { try {
pixbuf = icon_theme->load_icon(icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE); pixbuf = icon_theme->load_icon(ret_icon_name, size, Gtk::ICON_LOOKUP_FORCE_SIZE);
} catch(...) { } catch(...) {
if (Glib::file_test(icon_name, Glib::FILE_TEST_EXISTS)) if (Glib::file_test(ret_icon_name, Glib::FILE_TEST_EXISTS))
pixbuf = load_icon_from_file(icon_name, size); pixbuf = load_icon_from_file(ret_icon_name, size);
else else
pixbuf = {}; pixbuf = {};
} }
if (pixbuf) { if (pixbuf) {
image.set(pixbuf); image.set(pixbuf);
found = true; return true;
break; }
}
}
return found; return false;
} }
/* Task class implementation */ /* Task class implementation */
@ -276,8 +272,7 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) : struct zwlr_foreign_toplevel_handle_v1 *tl_handle, struct wl_seat *seat) :
bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat}, bar_{bar}, config_{config}, tbar_{tbar}, handle_{tl_handle}, seat_{seat},
id_{global_id++}, id_{global_id++},
content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}, content_{bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0}
button_visible_{false}
{ {
zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this); zwlr_foreign_toplevel_handle_v1_add_listener(handle_, &toplevel_handle_impl, this);
@ -290,13 +285,15 @@ Task::Task(const waybar::Bar &bar, const Json::Value &config, Taskbar *tbar,
content_.show(); content_.show();
button_.add(content_); button_.add(content_);
with_icon_ = false;
format_before_.clear(); format_before_.clear();
format_after_.clear(); format_after_.clear();
if (config_["format"].isString()) { if (config_["format"].isString()) {
/* The user defined a format string, use it */ /* The user defined a format string, use it */
auto format = config_["format"].asString(); auto format = config_["format"].asString();
if (format.find("{name}") != std::string::npos) {
with_name_ = true;
}
auto icon_pos = format.find("{icon}"); auto icon_pos = format.find("{icon}");
if (icon_pos == 0) { if (icon_pos == 0) {
@ -377,19 +374,54 @@ std::string Task::state_string(bool shortened) const
void Task::handle_title(const char *title) void Task::handle_title(const char *title)
{ {
title_ = title; title_ = title;
hide_if_ignored();
}
void Task::hide_if_ignored()
{
if (tbar_->ignore_list().count(app_id_) || tbar_->ignore_list().count(title_)) {
ignored_ = true;
if (button_visible_) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
handle_output_leave(output);
}
} else {
bool is_was_ignored = ignored_;
ignored_ = false;
if (is_was_ignored) {
auto output = gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj());
handle_output_enter(output);
}
}
} }
void Task::handle_app_id(const char *app_id) void Task::handle_app_id(const char *app_id)
{ {
app_id_ = app_id; app_id_ = app_id;
hide_if_ignored();
if (!with_icon_) auto ids_replace_map = tbar_->app_ids_replace_map();
if (ids_replace_map.count(app_id_)) {
auto replaced_id = ids_replace_map[app_id_];
spdlog::debug(fmt::format("Task ({}) [{}] app_id was replaced with {}", id_, app_id_, replaced_id));
app_id_ = replaced_id;
}
if (!with_icon_ && !with_name_) {
return; return;
}
set_app_info_from_app_id_list(app_id_);
name_ = app_info_ ? app_info_->get_display_name() : app_id;
if (!with_icon_) {
return;
}
int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16; int icon_size = config_["icon-size"].isInt() ? config_["icon-size"].asInt() : 16;
bool found = false; bool found = false;
for (auto& icon_theme : tbar_->icon_themes()) { for (auto& icon_theme : tbar_->icon_themes()) {
if (image_load_icon(icon_, icon_theme, app_id_, icon_size)) { if (image_load_icon(icon_, icon_theme, app_info_, icon_size)) {
found = true; found = true;
break; break;
} }
@ -403,6 +435,11 @@ void Task::handle_app_id(const char *app_id)
void Task::handle_output_enter(struct wl_output *output) void Task::handle_output_enter(struct wl_output *output)
{ {
if (ignored_) {
spdlog::debug("{} is ignored", repr());
return;
}
spdlog::debug("{} entered output {}", repr(), (void*)output); spdlog::debug("{} entered output {}", repr(), (void*)output);
if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) { if (!button_visible_ && (tbar_->all_outputs() || tbar_->show_output(output))) {
@ -540,14 +577,17 @@ void Task::update()
{ {
bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false; bool markup = config_["markup"].isBool() ? config_["markup"].asBool() : false;
std::string title = title_; std::string title = title_;
std::string name = name_;
std::string app_id = app_id_; std::string app_id = app_id_;
if (markup) { if (markup) {
title = Glib::Markup::escape_text(title); title = Glib::Markup::escape_text(title);
name = Glib::Markup::escape_text(name);
app_id = Glib::Markup::escape_text(app_id); app_id = Glib::Markup::escape_text(app_id);
} }
if (!format_before_.empty()) { if (!format_before_.empty()) {
auto txt = fmt::format(format_before_, auto txt = fmt::format(format_before_,
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("app_id", app_id),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
@ -561,6 +601,7 @@ void Task::update()
if (!format_after_.empty()) { if (!format_after_.empty()) {
auto txt = fmt::format(format_after_, auto txt = fmt::format(format_after_,
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("app_id", app_id),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
@ -575,6 +616,7 @@ void Task::update()
if (!format_tooltip_.empty()) { if (!format_tooltip_.empty()) {
auto txt = fmt::format(format_tooltip_, auto txt = fmt::format(format_tooltip_,
fmt::arg("title", title), fmt::arg("title", title),
fmt::arg("name", name),
fmt::arg("app_id", app_id), fmt::arg("app_id", app_id),
fmt::arg("state", state_string()), fmt::arg("state", state_string()),
fmt::arg("short_state", state_string(true)) fmt::arg("short_state", state_string(true))
@ -694,6 +736,23 @@ Taskbar::Taskbar(const std::string &id, const waybar::Bar &bar, const Json::Valu
icon_themes_.push_back(it); icon_themes_.push_back(it);
} }
// Load ignore-list
if (config_["ignore-list"].isArray()) {
for (auto& app_name : config_["ignore-list"]) {
ignore_list_.emplace(app_name.asString());
}
}
// Load app_id remappings
if (config_["app_ids-mapping"].isObject()) {
const Json::Value& mapping = config_["app_ids-mapping"];
const std::vector<std::string> app_ids = config_["app_ids-mapping"].getMemberNames();
for (auto& app_id : app_ids) {
app_ids_replace_map_.emplace(app_id, mapping[app_id].asString());
}
}
icon_themes_.push_back(Gtk::IconTheme::get_default()); icon_themes_.push_back(Gtk::IconTheme::get_default());
} }
@ -825,9 +884,10 @@ bool Taskbar::all_outputs() const
return config_["all-outputs"].isBool() && config_["all-outputs"].asBool(); return config_["all-outputs"].isBool() && config_["all-outputs"].asBool();
} }
std::vector<Glib::RefPtr<Gtk::IconTheme>> Taskbar::icon_themes() const const std::vector<Glib::RefPtr<Gtk::IconTheme>>& Taskbar::icon_themes() const { return icon_themes_; }
{
return icon_themes_; const std::unordered_set<std::string>& Taskbar::ignore_list() const { return ignore_list_; }
}
const std::map<std::string, std::string>& Taskbar::app_ids_replace_map() const { return app_ids_replace_map_; }
} /* namespace waybar::modules::wlr */ } /* namespace waybar::modules::wlr */

View File

@ -0,0 +1,472 @@
#include "modules/wlr/workspace_manager.hpp"
#include <gdk/gdkwayland.h>
#include <gtkmm.h>
#include <spdlog/spdlog.h>
#include <algorithm>
#include <iterator>
#include <vector>
#include "gtkmm/widget.h"
#include "modules/wlr/workspace_manager_binding.hpp"
namespace waybar::modules::wlr {
uint32_t WorkspaceGroup::workspace_global_id = 0;
uint32_t WorkspaceManager::group_global_id = 0;
std::map<std::string, std::string> Workspace::icons_map_;
WorkspaceManager::WorkspaceManager(const std::string &id, const waybar::Bar &bar,
const Json::Value &config)
: waybar::AModule(config, "workspaces", id, false, false),
bar_(bar),
box_(bar.vertical ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL, 0) {
auto config_sort_by_name = config_["sort-by-name"];
if (config_sort_by_name.isBool()) {
sort_by_name_ = config_sort_by_name.asBool();
}
auto config_sort_by_coordinates = config_["sort-by-coordinates"];
if (config_sort_by_coordinates.isBool()) {
sort_by_coordinates_ = config_sort_by_coordinates.asBool();
}
auto config_all_outputs = config_["all-outputs"];
if (config_all_outputs.isBool()) {
all_outputs_ = config_all_outputs.asBool();
}
auto config_active_only = config_["active-only"];
if (config_active_only.isBool()) {
active_only_ = config_active_only.asBool();
creation_delayed_ = active_only_;
}
box_.set_name("workspaces");
if (!id.empty()) {
box_.get_style_context()->add_class(id);
}
event_box_.add(box_);
add_registry_listener(this);
if (!workspace_manager_) {
return;
}
}
auto WorkspaceManager::workspace_comparator() const
-> std::function<bool(std::unique_ptr<Workspace> &, std::unique_ptr<Workspace> &)> {
return [=](std::unique_ptr<Workspace> &lhs, std::unique_ptr<Workspace> &rhs) {
auto is_name_less = lhs->get_name() < rhs->get_name();
auto is_name_eq = lhs->get_name() == rhs->get_name();
auto is_coords_less = lhs->get_coords() < rhs->get_coords();
if (sort_by_name_) {
if (sort_by_coordinates_) {
return is_name_eq ? is_coords_less : is_name_less;
} else {
return is_name_less;
}
}
if (sort_by_coordinates_) {
return is_coords_less;
}
return lhs->id() < rhs->id();
};
}
auto WorkspaceManager::sort_workspaces() -> void {
std::vector<std::reference_wrapper<std::unique_ptr<Workspace>>> all_workspaces;
for (auto &group : groups_) {
auto &group_workspaces = group->workspaces();
all_workspaces.reserve(all_workspaces.size() +
std::distance(group_workspaces.begin(), group_workspaces.end()));
if (!active_only()) {
all_workspaces.insert(all_workspaces.end(), group_workspaces.begin(), group_workspaces.end());
continue;
}
for (auto &workspace : group_workspaces) {
if (!workspace->is_active()) {
continue;
}
all_workspaces.push_back(workspace);
}
}
std::sort(all_workspaces.begin(), all_workspaces.end(), workspace_comparator());
for (size_t i = 0; i < all_workspaces.size(); ++i) {
box_.reorder_child(all_workspaces[i].get()->get_button_ref(), i);
}
}
auto WorkspaceManager::register_manager(wl_registry *registry, uint32_t name, uint32_t version)
-> void {
if (workspace_manager_) {
spdlog::warn("Register workspace manager again although already registered!");
return;
}
if (version != 1) {
spdlog::warn("Using different workspace manager protocol version: {}", version);
}
workspace_manager_ = workspace_manager_bind(registry, name, version, this);
}
auto WorkspaceManager::handle_workspace_group_create(
zext_workspace_group_handle_v1 *workspace_group_handle) -> void {
auto new_id = ++group_global_id;
groups_.push_back(
std::make_unique<WorkspaceGroup>(bar_, box_, config_, *this, workspace_group_handle, new_id));
spdlog::debug("Workspace group {} created", new_id);
}
auto WorkspaceManager::handle_finished() -> void {
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::handle_done() -> void {
for (auto &group : groups_) {
group->handle_done();
}
dp.emit();
}
auto WorkspaceManager::update() -> void {
for (auto &group : groups_) {
group->update();
}
if (creation_delayed()) {
creation_delayed_ = false;
sort_workspaces();
}
AModule::update();
}
WorkspaceManager::~WorkspaceManager() {
if (!workspace_manager_) {
return;
}
zext_workspace_manager_v1_destroy(workspace_manager_);
workspace_manager_ = nullptr;
}
auto WorkspaceManager::remove_workspace_group(uint32_t id) -> void {
auto it = std::find_if(groups_.begin(),
groups_.end(),
[id](const std::unique_ptr<WorkspaceGroup> &g) { return g->id() == id; });
if (it == groups_.end()) {
spdlog::warn("Can't find group with id {}", id);
return;
}
groups_.erase(it);
}
auto WorkspaceManager::commit() -> void { zext_workspace_manager_v1_commit(workspace_manager_); }
WorkspaceGroup::WorkspaceGroup(const Bar &bar, Gtk::Box &box, const Json::Value &config,
WorkspaceManager &manager,
zext_workspace_group_handle_v1 *workspace_group_handle, uint32_t id)
: bar_(bar),
box_(box),
config_(config),
workspace_manager_(manager),
workspace_group_handle_(workspace_group_handle),
id_(id) {
add_workspace_group_listener(workspace_group_handle, this);
}
auto WorkspaceGroup::active_only() const -> bool { return workspace_manager_.active_only(); }
auto WorkspaceGroup::creation_delayed() const -> bool {
return workspace_manager_.creation_delayed();
}
auto WorkspaceGroup::add_button(Gtk::Button &button) -> void {
box_.pack_start(button, false, false);
}
WorkspaceGroup::~WorkspaceGroup() {
if (!workspace_group_handle_) {
return;
}
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
}
auto WorkspaceGroup::handle_workspace_create(zext_workspace_handle_v1 *workspace) -> void {
auto new_id = ++workspace_global_id;
workspaces_.push_back(std::make_unique<Workspace>(bar_, config_, *this, workspace, new_id));
spdlog::debug("Workspace {} created", new_id);
}
auto WorkspaceGroup::handle_remove() -> void {
zext_workspace_group_handle_v1_destroy(workspace_group_handle_);
workspace_group_handle_ = nullptr;
workspace_manager_.remove_workspace_group(id_);
}
auto WorkspaceGroup::handle_output_enter(wl_output *output) -> void {
spdlog::debug("Output {} assigned to {} group", (void *)output, id_);
output_ = output;
if (!is_visible() || workspace_manager_.creation_delayed()) {
return;
}
for (auto &workspace : workspaces_) {
add_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::is_visible() const -> bool {
return output_ != nullptr &&
(workspace_manager_.all_outputs() ||
output_ == gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj()));
}
auto WorkspaceGroup::handle_output_leave() -> void {
spdlog::debug("Output {} remove from {} group", (void *)output_, id_);
output_ = nullptr;
if (output_ != gdk_wayland_monitor_get_wl_output(bar_.output->monitor->gobj())) {
return;
}
for (auto &workspace : workspaces_) {
remove_button(workspace->get_button_ref());
}
}
auto WorkspaceGroup::update() -> void {
for (auto &workspace : workspaces_) {
if (workspace_manager_.creation_delayed()) {
add_button(workspace->get_button_ref());
if (is_visible() && (workspace->is_active() || workspace->is_urgent())) {
workspace->show();
}
}
workspace->update();
}
}
auto WorkspaceGroup::remove_workspace(uint32_t id) -> void {
auto it = std::find_if(workspaces_.begin(),
workspaces_.end(),
[id](const std::unique_ptr<Workspace> &w) { return w->id() == id; });
if (it == workspaces_.end()) {
spdlog::warn("Can't find workspace with id {}", id);
return;
}
workspaces_.erase(it);
}
auto WorkspaceGroup::handle_done() -> void {
need_to_sort = false;
if (!is_visible()) {
return;
}
for (auto &workspace : workspaces_) {
workspace->handle_done();
}
if (creation_delayed()) {
return;
}
if (!workspace_manager_.all_outputs()) {
sort_workspaces();
} else {
workspace_manager_.sort_workspaces();
}
}
auto WorkspaceGroup::commit() -> void { workspace_manager_.commit(); }
auto WorkspaceGroup::sort_workspaces() -> void {
std::sort(workspaces_.begin(), workspaces_.end(), workspace_manager_.workspace_comparator());
for (size_t i = 0; i < workspaces_.size(); ++i) {
box_.reorder_child(workspaces_[i]->get_button_ref(), i);
}
}
auto WorkspaceGroup::remove_button(Gtk::Button &button) -> void { box_.remove(button); }
Workspace::Workspace(const Bar &bar, const Json::Value &config, WorkspaceGroup &workspace_group,
zext_workspace_handle_v1 *workspace, uint32_t id)
: bar_(bar),
config_(config),
workspace_group_(workspace_group),
workspace_handle_(workspace),
id_(id) {
add_workspace_listener(workspace, this);
auto config_format = config["format"];
format_ = config_format.isString() ? config_format.asString() : "{name}";
with_icon_ = format_.find("{icon}") != std::string::npos;
if (with_icon_ && icons_map_.empty()) {
auto format_icons = config["format-icons"];
for (auto &name : format_icons.getMemberNames()) {
icons_map_.emplace(name, format_icons[name].asString());
}
}
/* Handle click events if configured */
if (config_["on-click"].isString() || config_["on-click-middle"].isString() ||
config_["on-click-right"].isString()) {
button_.add_events(Gdk::BUTTON_PRESS_MASK);
button_.signal_button_press_event().connect(sigc::mem_fun(*this, &Workspace::handle_clicked),
false);
}
button_.set_relief(Gtk::RELIEF_NONE);
content_.set_center_widget(label_);
button_.add(content_);
if (!workspace_group.is_visible()) {
return;
}
workspace_group.add_button(button_);
button_.show_all();
}
Workspace::~Workspace() {
workspace_group_.remove_button(button_);
if (!workspace_handle_) {
return;
}
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
}
auto Workspace::update() -> void {
label_.set_markup(fmt::format(
format_, fmt::arg("name", name_), fmt::arg("icon", with_icon_ ? get_icon() : "")));
}
auto Workspace::handle_state(const std::vector<uint32_t> &state) -> void {
state_ = 0;
for (auto state_entry : state) {
switch (state_entry) {
case ZEXT_WORKSPACE_HANDLE_V1_STATE_ACTIVE:
state_ |= (uint32_t)State::ACTIVE;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_URGENT:
state_ |= (uint32_t)State::URGENT;
break;
case ZEXT_WORKSPACE_HANDLE_V1_STATE_HIDDEN:
state_ |= (uint32_t)State::HIDDEN;
break;
}
}
}
auto Workspace::handle_remove() -> void {
zext_workspace_handle_v1_destroy(workspace_handle_);
workspace_handle_ = nullptr;
workspace_group_.remove_workspace(id_);
}
auto add_or_remove_class(Glib::RefPtr<Gtk::StyleContext> context, bool condition,
const std::string &class_name) {
if (condition) {
context->add_class(class_name);
} else {
context->remove_class(class_name);
}
}
auto Workspace::handle_done() -> void {
spdlog::debug("Workspace {} changed to state {}", id_, state_);
auto style_context = button_.get_style_context();
add_or_remove_class(style_context, is_active(), "active");
add_or_remove_class(style_context, is_urgent(), "urgent");
add_or_remove_class(style_context, is_hidden(), "hidden");
if (workspace_group_.creation_delayed()) {
return;
}
if (workspace_group_.active_only() && (is_active() || is_urgent())) {
button_.show_all();
} else if (workspace_group_.active_only() && !(is_active() || is_urgent())) {
button_.hide();
}
}
auto Workspace::get_icon() -> std::string {
if (is_active()) {
auto active_icon_it = icons_map_.find("active");
if (active_icon_it != icons_map_.end()) {
return active_icon_it->second;
}
}
auto named_icon_it = icons_map_.find(name_);
if (named_icon_it != icons_map_.end()) {
return named_icon_it->second;
}
auto default_icon_it = icons_map_.find("default");
if (default_icon_it != icons_map_.end()) {
return default_icon_it->second;
}
return name_;
}
auto Workspace::handle_clicked(GdkEventButton *bt) -> bool {
std::string action;
if (config_["on-click"].isString() && bt->button == 1) {
action = config_["on-click"].asString();
} else if (config_["on-click-middle"].isString() && bt->button == 2) {
action = config_["on-click-middle"].asString();
} else if (config_["on-click-right"].isString() && bt->button == 3) {
action = config_["on-click-right"].asString();
}
if (action.empty())
return true;
else if (action == "activate") {
zext_workspace_handle_v1_activate(workspace_handle_);
} else if (action == "close") {
zext_workspace_handle_v1_remove(workspace_handle_);
} else {
spdlog::warn("Unknown action {}", action);
}
workspace_group_.commit();
return true;
}
auto Workspace::show() -> void { button_.show_all(); }
auto Workspace::hide() -> void { button_.hide(); }
auto Workspace::handle_name(const std::string &name) -> void {
if (name_ != name) {
workspace_group_.set_need_to_sort();
}
name_ = name;
}
auto Workspace::handle_coordinates(const std::vector<uint32_t> &coordinates) -> void {
if (coordinates_ != coordinates) {
workspace_group_.set_need_to_sort();
}
coordinates_ = coordinates;
}
} // namespace waybar::modules::wlr

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